Browse Source

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 years ago
parent
commit
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;
 
     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
       Reddit "subreddit" bar.
       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

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

@@ -48,7 +48,7 @@ BlazeComponent.extendComponent({
 
   onRendered: function() {
     var self = this;
-    if (! Meteor.user().isBoardMember())
+    if (! Meteor.userId() || ! Meteor.user().isBoardMember())
       return;
 
     $(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
   // return an empty string. This is causes bugs in our application so we set
   // 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.archived = false;
   doc.members = [{
@@ -153,22 +153,13 @@ Boards.before.insert(function(userId, doc) {
   // Handle labels
   var colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
   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),
       name: '',
       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) {

+ 1 - 4
collections/users.js

@@ -33,13 +33,10 @@ Users.helpers({
 });
 
 Users.before.insert(function(userId, doc) {
-  doc.profile = {};
+  doc.profile = doc.profile || {};
 
   // connect profile.status default
   doc.profile.status = 'offline';
-
-  // slugify to username
-  //doc.username = getSlug(doc.profile.name, '');
 });
 
 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;
 
 using Spk = import "/sandstorm/package.capnp";
@@ -10,18 +12,32 @@ const pkgdef :Spk.PackageDefinition = (
   # "pkgdef" constant.
 
   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.
 
   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 = [
       # Define your "new document" handlers here.
-      ( title = (defaultText = "New board"),
+      (
+        title = (defaultText = "New board"),
         command = .myCommand
         # The command to run when starting for the first time. (".myCommand" is
         # 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
   # 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`
   # 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 = (

+ 62 - 39
sandstorm.js

@@ -8,71 +8,94 @@ var isSandstorm = Meteor.settings && Meteor.settings.public &&
 // redirect the user to this particular board.
 var sandstormBoard = {
   _id: 'sandstorm',
-  slug: 'board',
 
   // XXX Should be shared with the grain instance name.
   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) {
+  // 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) {
     if (! Boards.findOne(sandstormBoard._id)) {
-      Boards.insert(_.extend(sandstormBoard, { userId: doc._id }));
+      Boards.insert(sandstormBoard, {validate: false});
       Boards.update(sandstormBoard._id, {
         $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({
         _id: sandstormBoard._id,
         permission: 'public'
       }, {
         $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) {
-  Router.go('Board', {
-    boardId: sandstormBoard._id,
-    slug: getSlug(sandstormBoard.title)
-  });
-
   // XXX Hack. `Meteor.absoluteUrl` doesn't work in Sandstorm, since every
   // session has a different URL whereas Meteor computes absoluteUrl based on
   // the ROOT_URL environment variable. So we overwrite this function on a