Bläddra i källkod

Upgrade sandstorm integration

Both sandstorm and LibreBoard have significantly evolved since the
last release of LibreBoard on sandstorm. This commit:

* adds some more attributes on the sandstorm manifest
* introduces support with the sandstorm sharing box
* uses a server redirection to point to the board document
* hides the top shortcut bar on sandstorm

Fixes #163.
Maxime Quandalle 10 år sedan
förälder
incheckning
1b4fcc67f4

+ 1 - 1
client/components/boards/body.js

@@ -70,7 +70,7 @@ BlazeComponent.extendComponent({
       }
       }
     };
     };
 
 
-    if (! Meteor.user().isBoardMember())
+    if (! Meteor.userId() || ! Meteor.user().isBoardMember())
       return;
       return;
 
 
     self.$(lists).sortable({
     self.$(lists).sortable({

+ 19 - 18
client/components/main/header.jade

@@ -5,26 +5,27 @@ template(name="header")
       list all starred boards with a link to go there. This is inspired by the
       list all starred boards with a link to go there. This is inspired by the
       Reddit "subreddit" bar.
       Reddit "subreddit" bar.
       The first link goes to the boards page.
       The first link goes to the boards page.
-    if currentUser
-      #header-quick-access
-        ul
-          li
-            +linkTo(route="Boards")
-              span.fa.fa-home
-              | All boards
-          each currentUser.starredBoards
-            li.separator -
-            li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
-              +linkTo(route="Board" data=this)
-                = title
-          else
-            li.current Star a board to add a shortcut in this bar.
+    unless isSandstorm
+      if currentUser
+        #header-quick-access
+          ul
+            li
+              +linkTo(route="Boards")
+                span.fa.fa-home
+                | All boards
+            each currentUser.starredBoards
+              li.separator -
+              li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
+                +linkTo(route="Board" data=this)
+                  = title
+            else
+              li.current Star a board to add a shortcut in this bar.
 
 
-          li
-            a.js-create-board
-              i.fa.fa-plus(title="Create a new board")
+            li
+              a.js-create-board
+                i.fa.fa-plus(title="Create a new board")
 
 
-        +headerUserBar
+          +headerUserBar
 
 
     //-
     //-
       The main bar is a colorful bar that provide all the meta-data for the
       The main bar is a colorful bar that provide all the meta-data for the

+ 1 - 1
client/components/sidebar/sidebar.js

@@ -48,7 +48,7 @@ BlazeComponent.extendComponent({
 
 
   onRendered: function() {
   onRendered: function() {
     var self = this;
     var self = this;
-    if (! Meteor.user().isBoardMember())
+    if (! Meteor.userId() || ! Meteor.user().isBoardMember())
       return;
       return;
 
 
     $(document).on('mouseover', function() {
     $(document).on('mouseover', function() {

+ 4 - 13
collections/boards.js

@@ -139,7 +139,7 @@ Boards.before.insert(function(userId, doc) {
   // In some cases (Chinese and Japanese for instance) the `getSlug` function
   // In some cases (Chinese and Japanese for instance) the `getSlug` function
   // return an empty string. This is causes bugs in our application so we set
   // return an empty string. This is causes bugs in our application so we set
   // a default slug in this case.
   // a default slug in this case.
-  doc.slug = getSlug(doc.title) || 'board';
+  doc.slug = doc.slug || getSlug(doc.title) || 'board';
   doc.createdAt = new Date();
   doc.createdAt = new Date();
   doc.archived = false;
   doc.archived = false;
   doc.members = [{
   doc.members = [{
@@ -153,22 +153,13 @@ Boards.before.insert(function(userId, doc) {
   // Handle labels
   // Handle labels
   var colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
   var colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
   var defaultLabelsColors = _.clone(colors).splice(0, 6);
   var defaultLabelsColors = _.clone(colors).splice(0, 6);
-  doc.labels = [];
-  _.each(defaultLabelsColors, function(val) {
-    doc.labels.push({
+  doc.labels = _.map(defaultLabelsColors, function(val) {
+    return {
       _id: Random.id(6),
       _id: Random.id(6),
       name: '',
       name: '',
       color: val
       color: val
-    });
-  });
-
-  // We randomly chose one of the default background colors for the board
-  if (Meteor.isClient) {
-    doc.background = {
-      type: 'color',
-      color: Random.choice(Boards.simpleSchema()._schema.color.allowedValues)
     };
     };
-  }
+  });
 });
 });
 
 
 Boards.before.update(function(userId, doc, fieldNames, modifier) {
 Boards.before.update(function(userId, doc, fieldNames, modifier) {

+ 1 - 4
collections/users.js

@@ -33,13 +33,10 @@ Users.helpers({
 });
 });
 
 
 Users.before.insert(function(userId, doc) {
 Users.before.insert(function(userId, doc) {
-  doc.profile = {};
+  doc.profile = doc.profile || {};
 
 
   // connect profile.status default
   // connect profile.status default
   doc.profile.status = 'offline';
   doc.profile.status = 'offline';
-
-  // slugify to username
-  //doc.username = getSlug(doc.profile.name, '');
 });
 });
 
 
 if (Meteor.isServer) {
 if (Meteor.isServer) {

+ 54 - 6
sandstorm-pkgdef.capnp

@@ -1,3 +1,5 @@
+# Use use the meteor-spk tool to generate a sandstorm package (spk) from this
+# meteor application source code. https://github.com/sandstorm-io/meteor-spk
 @0xa5275bd3ad124e12;
 @0xa5275bd3ad124e12;
 
 
 using Spk = import "/sandstorm/package.capnp";
 using Spk = import "/sandstorm/package.capnp";
@@ -10,18 +12,32 @@ const pkgdef :Spk.PackageDefinition = (
   # "pkgdef" constant.
   # "pkgdef" constant.
 
 
   id = "m86q05rdvj14yvn78ghaxynqz7u2svw6rnttptxx49g1785cdv1h",
   id = "m86q05rdvj14yvn78ghaxynqz7u2svw6rnttptxx49g1785cdv1h",
-  # Your app ID is actually its public key. The private key was placed in your
+  # The app ID is actually its public key. The private key was placed in your
   # keyring. All updates must be signed with the same key.
   # keyring. All updates must be signed with the same key.
 
 
   manifest = (
   manifest = (
-    # This manifest is included in your app package to tell Sandstorm
-    # about your app.
+    # This manifest is included in our app package to tell Sandstorm about our
+    # app.
 
 
-    appVersion = 1, # Increment this for every release.
+    appTitle = (defaultText = "LibreBoard"),
+    # The name of the app as it is displayed to the user.
+
+    appVersion = 2,
+    # Increment this for every release.
+
+    appMarketingVersion = (defaultText = "0.9.0"),
+    # Human-readable presentation of the app version.
+
+    minUpgradableAppVersion = 0,
+    # The minimum version of the app which can be safely replaced by this app
+    # package without data loss.  This might be non-zero if the app's data store
+    # format changed drastically in the past and the app is no longer able to
+    # read the old format.
 
 
     actions = [
     actions = [
       # Define your "new document" handlers here.
       # Define your "new document" handlers here.
-      ( title = (defaultText = "New board"),
+      (
+        title = (defaultText = "New board"),
         command = .myCommand
         command = .myCommand
         # The command to run when starting for the first time. (".myCommand" is
         # The command to run when starting for the first time. (".myCommand" is
         # just a constant defined at the bottom of the file.)
         # just a constant defined at the bottom of the file.)
@@ -43,11 +59,43 @@ const pkgdef :Spk.PackageDefinition = (
     ]
     ]
   ),
   ),
 
 
-  alwaysInclude = [ "." ]
+  alwaysInclude = [ "." ],
   # This says that we always want to include all files from the source map. (An
   # This says that we always want to include all files from the source map. (An
   # alternative is to automatically detect dependencies by watching what the app
   # alternative is to automatically detect dependencies by watching what the app
   # opens while running in dev mode. To see what that looks like, run `spk init`
   # opens while running in dev mode. To see what that looks like, run `spk init`
   # without the -A option.)
   # without the -A option.)
+
+  bridgeConfig = (
+    viewInfo = (
+      permissions = [(
+        name = "participate",
+        title = (defaultText = "participate"),
+        description = (defaultText = "allows participating in the board")
+      ), (
+        name = "configure",
+        title = (defaultText = "configure"),
+        description = (defaultText = "allows configuring the board")
+      )],
+
+      roles = [(
+        title = (defaultText = "observer"),
+        permissions = [false, false],
+        verbPhrase = (defaultText = "can read")
+      ), (
+        title = (defaultText = "member"),
+        permissions = [true, false],
+        verbPhrase = (defaultText = "can edit"),
+        default = true
+      # ), (
+      #   title = (defaultText = "administrator"),
+      #   permissions = [true, true],
+      #   verbPhrase = (defaultText = "can configure")
+      #
+      # XXX Administrators configuration options aren’t implemented yet, so this
+      # role is currently useless.
+      )]
+    )
+  )
 );
 );
 
 
 const myCommand :Spk.Manifest.Command = (
 const myCommand :Spk.Manifest.Command = (

+ 62 - 39
sandstorm.js

@@ -8,71 +8,94 @@ var isSandstorm = Meteor.settings && Meteor.settings.public &&
 // redirect the user to this particular board.
 // redirect the user to this particular board.
 var sandstormBoard = {
 var sandstormBoard = {
   _id: 'sandstorm',
   _id: 'sandstorm',
-  slug: 'board',
 
 
   // XXX Should be shared with the grain instance name.
   // XXX Should be shared with the grain instance name.
   title: 'LibreBoard',
   title: 'LibreBoard',
-  permission: 'public',
-  background: {
-    type: 'color',
-    color: '#16A085'
-  },
+  slug: 'libreboard',
 
 
-  // XXX Not certain this is a bug, but we except these fields to get inserted
-  // by the `Lists.before.insert` collection-hook. Since this hook is not called
-  // in this case, we have to duplicate the logic and set them here.
-  archived: false,
-  createdAt: new Date()
+  // Board access security is handled by sandstorm, so in our point of view we
+  // can alway assume that the board is public (unauthorized users won’t be able
+  // to access it anyway).
+  permission: 'public'
+};
+
+// The list of permissions a user have is provided by sandstorm accounts
+// package.
+var userHasPermission = function(user, permission) {
+  var userPermissions = user.services.sandstorm.permissions;
+  return userPermissions.indexOf(permission) > -1;
 };
 };
 
 
-// On the first launch of the instance a user is automatically created thanks to
-// the `accounts-sandstorm` package. After its creation we insert the unique
-// board document. Note that when the `Users.after.insert` hook is called, the
-// user is inserted into the database but not connected. So despite the
-// appearances `userId` is null in this block.
-//
-// If the hard-coded board already exists and we are inserting a new user, we
-// assume that the owner of the board want to share write privileges with the
-// new user.
-// XXX Improve that when the Sandstorm sharing model (“Powerbox”) arrives.
 if (isSandstorm && Meteor.isServer) {
 if (isSandstorm && Meteor.isServer) {
+  // Redirect the user to the hard-coded board. On the first launch the user
+  // will be redirected to the board before its creation. But that’s not a
+  // problem thanks to the reactive board publication. We used to do this
+  // redirection on the client side but that was sometime visible on loading,
+  // and the home page was accessible by pressing the back button of the
+  // browser, a server-side redirection solves both of these issues.
+  //
+  // XXX Maybe sandstorm manifest could provide some kind of "home url"?
+  Router.route('/', function() {
+    var base = this.request.headers['x-sandstorm-base-path'];
+    // XXX If this routing scheme changes, this will break. We should generation
+    // the location url using the router, but at the time of writting, the
+    // router is only accessible on the client.
+    var path = '/boards/' + sandstormBoard._id + '/' + sandstormBoard.slug;
+
+    this.response.writeHead(301, {
+      Location: base + path
+    });
+    this.response.end();
+  }, { where: 'server' });
+
+  // On the first launch of the instance a user is automatically created thanks
+  // to the `accounts-sandstorm` package. After its creation we insert the
+  // unique board document. Note that when the `Users.after.insert` hook is
+  // called, the user is inserted into the database but not connected. So
+  // despite the appearances `userId` is null in this block.
   Users.after.insert(function(userId, doc) {
   Users.after.insert(function(userId, doc) {
     if (! Boards.findOne(sandstormBoard._id)) {
     if (! Boards.findOne(sandstormBoard._id)) {
-      Boards.insert(_.extend(sandstormBoard, { userId: doc._id }));
+      Boards.insert(sandstormBoard, {validate: false});
       Boards.update(sandstormBoard._id, {
       Boards.update(sandstormBoard._id, {
         $set: {
         $set: {
-          'members.0.userId': doc._id
+          // The first member (the grain creator) has all rights
+          'members.0': {
+            userId: doc._id,
+            isActive: true,
+            isAdmin: true
+          }
         }
         }
       });
       });
-      Activities.update({
-        activityTypeId: sandstormBoard._id
-      }, {
-        $set: {
-          userId: doc._id
-        }
+      Activities.update(
+        { activityTypeId: sandstormBoard._id }, {
+        $set: { userId: doc._id }
       });
       });
-    } else {
+    }
+
+    // If the hard-coded board already exists and we are inserting a new user,
+    // we need to update our user collection.
+    else if (userHasPermission(doc, 'participate')) {
       Boards.update({
       Boards.update({
         _id: sandstormBoard._id,
         _id: sandstormBoard._id,
         permission: 'public'
         permission: 'public'
       }, {
       }, {
         $push: {
         $push: {
-          members: doc._id
+          members: {
+            userId: doc._id,
+            isActive: true,
+            isAdmin: userHasPermission(doc, 'configure')
+          }
         }
         }
       });
       });
     }
     }
+
+    // The sandstom user package put the username in `profile.name`. We need to
+    // move this field value to follow our schema.
+    Users.update(doc._id, { $rename: { 'profile.name': 'username' }});
   });
   });
 }
 }
 
 
-// On the client, redirect the user to the hard-coded board. On the first launch
-// the user will be redirected to the board before its creation. But that’s not
-// a problem thanks to the reactive board publication.
 if (isSandstorm && Meteor.isClient) {
 if (isSandstorm && Meteor.isClient) {
-  Router.go('Board', {
-    boardId: sandstormBoard._id,
-    slug: getSlug(sandstormBoard.title)
-  });
-
   // XXX Hack. `Meteor.absoluteUrl` doesn't work in Sandstorm, since every
   // XXX Hack. `Meteor.absoluteUrl` doesn't work in Sandstorm, since every
   // session has a different URL whereas Meteor computes absoluteUrl based on
   // session has a different URL whereas Meteor computes absoluteUrl based on
   // the ROOT_URL environment variable. So we overwrite this function on a
   // the ROOT_URL environment variable. So we overwrite this function on a