Explorar o código

REST API - Meteor 1.4 - first step issue

Lauri Ojansivu %!s(int64=8) %!d(string=hai) anos
pai
achega
0319bcf7bb
Modificáronse 9 ficheiros con 361 adicións e 137 borrados
  1. 6 2
      .eslintrc.json
  2. 12 12
      .meteor/versions
  3. 11 11
      client/components/boards/boardHeader.js
  4. 79 22
      models/boards.js
  5. 79 19
      models/cards.js
  6. 41 39
      models/export.js
  7. 55 3
      models/lists.js
  8. 75 29
      models/users.js
  9. 3 0
      package.json

+ 6 - 2
.eslintrc.json

@@ -52,8 +52,8 @@
     "prefer-const": 2,
     "prefer-const": 2,
     "prefer-spread": 2,
     "prefer-spread": 2,
     "prefer-template": 2,
     "prefer-template": 2,
-    "no-console":"off",
-    "no-unused-vars":"warn"
+    "no-console": 0,
+    "no-unused-vars" : "warn"
   },
   },
   "globals": {
   "globals": {
     "Meteor": false,
     "Meteor": false,
@@ -125,6 +125,10 @@
     "Checklists": true,
     "Checklists": true,
     "Settings": true,
     "Settings": true,
     "InvitationCodes": true,
     "InvitationCodes": true,
+<<<<<<< HEAD
     "Winston":true
     "Winston":true
+=======
+    "JsonRoutes" : true
+>>>>>>> 3a5150f6eef86816471f7b0134d3d93cf6686413
   }
   }
 }
 }

+ 12 - 12
.meteor/versions

@@ -1,6 +1,6 @@
 3stack:presence@1.0.5
 3stack:presence@1.0.5
-accounts-base@1.2.15
-accounts-password@1.3.4
+accounts-base@1.2.16
+accounts-password@1.3.5
 aldeed:collection2@2.10.0
 aldeed:collection2@2.10.0
 aldeed:collection2-core@1.2.0
 aldeed:collection2-core@1.2.0
 aldeed:schema-deny@1.1.0
 aldeed:schema-deny@1.1.0
@@ -11,7 +11,7 @@ allow-deny@1.0.5
 arillo:flow-router-helpers@0.5.2
 arillo:flow-router-helpers@0.5.2
 audit-argument-checks@1.0.7
 audit-argument-checks@1.0.7
 autoupdate@1.3.12
 autoupdate@1.3.12
-babel-compiler@6.14.1
+babel-compiler@6.18.1
 babel-runtime@1.0.1
 babel-runtime@1.0.1
 base64@1.0.10
 base64@1.0.10
 binary-heap@1.0.10
 binary-heap@1.0.10
@@ -44,16 +44,16 @@ coffeescript@1.12.3_1
 cottz:publish-relations@2.0.7
 cottz:publish-relations@2.0.7
 dburles:collection-helpers@1.1.0
 dburles:collection-helpers@1.1.0
 ddp@1.2.5
 ddp@1.2.5
-ddp-client@1.3.3
+ddp-client@1.3.4
 ddp-common@1.2.8
 ddp-common@1.2.8
 ddp-rate-limiter@1.0.7
 ddp-rate-limiter@1.0.7
-ddp-server@1.3.13
+ddp-server@1.3.14
 deps@1.0.12
 deps@1.0.12
 diff-sequence@1.0.7
 diff-sequence@1.0.7
-ecmascript@0.6.3
+ecmascript@0.7.2
 ecmascript-runtime@0.3.15
 ecmascript-runtime@0.3.15
 ejson@1.0.13
 ejson@1.0.13
-email@1.1.18
+email@1.2.0
 es5-shim@4.6.15
 es5-shim@4.6.15
 fastclick@1.0.13
 fastclick@1.0.13
 fortawesome:fontawesome@4.7.0
 fortawesome:fontawesome@4.7.0
@@ -92,8 +92,8 @@ minifier-js@1.2.18
 minifiers@1.1.8-faster-rebuild.0
 minifiers@1.1.8-faster-rebuild.0
 minimongo@1.0.21
 minimongo@1.0.21
 mobile-status-bar@1.0.14
 mobile-status-bar@1.0.14
-modules@0.7.9
-modules-runtime@0.7.9
+modules@0.8.1
+modules-runtime@0.7.10
 mongo@1.1.16
 mongo@1.1.16
 mongo-id@1.0.6
 mongo-id@1.0.6
 mongo-livedata@1.0.12
 mongo-livedata@1.0.12
@@ -123,7 +123,7 @@ raix:eventemitter@0.1.3
 raix:handlebar-helpers@0.2.5
 raix:handlebar-helpers@0.2.5
 rajit:bootstrap3-datepicker@1.6.4
 rajit:bootstrap3-datepicker@1.6.4
 random@1.0.10
 random@1.0.10
-rate-limit@1.0.7
+rate-limit@1.0.8
 reactive-dict@1.1.8
 reactive-dict@1.1.8
 reactive-var@1.0.11
 reactive-var@1.0.11
 reload@1.1.11
 reload@1.1.11
@@ -134,7 +134,7 @@ service-configuration@1.0.11
 session@1.1.7
 session@1.1.7
 sha@1.0.9
 sha@1.0.9
 shell-server@0.2.3
 shell-server@0.2.3
-simple:json-routes@1.0.4
+simple:json-routes@2.1.0
 softwarerero:accounts-t9n@1.3.9
 softwarerero:accounts-t9n@1.3.9
 spacebars@1.0.15
 spacebars@1.0.15
 spacebars-compiler@1.1.2
 spacebars-compiler@1.1.2
@@ -156,6 +156,6 @@ useraccounts:core@1.14.2
 useraccounts:flow-routing@1.14.2
 useraccounts:flow-routing@1.14.2
 useraccounts:unstyled@1.14.2
 useraccounts:unstyled@1.14.2
 verron:autosize@3.0.8
 verron:autosize@3.0.8
-webapp@1.3.14
+webapp@1.3.15
 webapp-hashing@1.0.9
 webapp-hashing@1.0.9
 zimme:active-route@2.3.2
 zimme:active-route@2.3.2

+ 11 - 11
client/components/boards/boardHeader.js

@@ -15,17 +15,17 @@ Template.boardMenuPopup.events({
   }),
   }),
 });
 });
 
 
-Template.boardMenuPopup.helpers({
-  exportUrl() {
-    const boardId = Session.get('currentBoard');
-    const loginToken = Accounts._storedLoginToken();
-    return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`);
-  },
-  exportFilename() {
-    const boardId = Session.get('currentBoard');
-    return `wekan-export-board-${boardId}.json`;
-  },
-});
+// Template.boardMenuPopup.helpers({
+//  exportUrl() {
+//    const boardId = Session.get('currentBoard');
+//    const loginToken = Accounts._storedLoginToken();
+//    return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`);
+//  },
+//  exportFilename() {
+//    const boardId = Session.get('currentBoard');
+//    return `wekan-export-board-${boardId}.json`;
+//  },
+// });
 
 
 Template.boardChangeTitlePopup.events({
 Template.boardChangeTitlePopup.events({
   submit(evt, tpl) {
   submit(evt, tpl) {

+ 79 - 22
models/boards.js

@@ -156,7 +156,7 @@ Boards.helpers({
    * Is supplied user authorized to view this board?
    * Is supplied user authorized to view this board?
    */
    */
   isVisibleBy(user) {
   isVisibleBy(user) {
-    if(this.isPublic()) {
+    if (this.isPublic()) {
       // public boards are visible to everyone
       // public boards are visible to everyone
       return true;
       return true;
     } else {
     } else {
@@ -172,7 +172,7 @@ Boards.helpers({
    * @returns {boolean} the member that matches, or undefined/false
    * @returns {boolean} the member that matches, or undefined/false
    */
    */
   isActiveMember(userId) {
   isActiveMember(userId) {
-    if(userId) {
+    if (userId) {
       return this.members.find((member) => (member.userId === userId && member.isActive));
       return this.members.find((member) => (member.userId === userId && member.isActive));
     } else {
     } else {
       return false;
       return false;
@@ -184,23 +184,23 @@ Boards.helpers({
   },
   },
 
 
   lists() {
   lists() {
-    return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 }});
+    return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } });
   },
   },
 
 
   activities() {
   activities() {
-    return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }});
+    return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 } });
   },
   },
 
 
   activeMembers() {
   activeMembers() {
-    return _.where(this.members, {isActive: true});
+    return _.where(this.members, { isActive: true });
   },
   },
 
 
   activeAdmins() {
   activeAdmins() {
-    return _.where(this.members, {isActive: true, isAdmin: true});
+    return _.where(this.members, { isActive: true, isAdmin: true });
   },
   },
 
 
   memberUsers() {
   memberUsers() {
-    return Users.find({ _id: {$in: _.pluck(this.members, 'userId')} });
+    return Users.find({ _id: { $in: _.pluck(this.members, 'userId') } });
   },
   },
 
 
   getLabel(name, color) {
   getLabel(name, color) {
@@ -216,15 +216,15 @@ Boards.helpers({
   },
   },
 
 
   hasMember(memberId) {
   hasMember(memberId) {
-    return !!_.findWhere(this.members, {userId: memberId, isActive: true});
+    return !!_.findWhere(this.members, { userId: memberId, isActive: true });
   },
   },
 
 
   hasAdmin(memberId) {
   hasAdmin(memberId) {
-    return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: true});
+    return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: true });
   },
   },
 
 
   hasCommentOnly(memberId) {
   hasCommentOnly(memberId) {
-    return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: false, isCommentOnly: true});
+    return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: false, isCommentOnly: true });
   },
   },
 
 
   absoluteUrl() {
   absoluteUrl() {
@@ -239,34 +239,34 @@ Boards.helpers({
   // XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove...
   // XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove...
   pushLabel(name, color) {
   pushLabel(name, color) {
     const _id = Random.id(6);
     const _id = Random.id(6);
-    Boards.direct.update(this._id, { $push: {labels: { _id, name, color }}});
+    Boards.direct.update(this._id, { $push: { labels: { _id, name, color } } });
     return _id;
     return _id;
   },
   },
 });
 });
 
 
 Boards.mutations({
 Boards.mutations({
   archive() {
   archive() {
-    return { $set: { archived: true }};
+    return { $set: { archived: true } };
   },
   },
 
 
   restore() {
   restore() {
-    return { $set: { archived: false }};
+    return { $set: { archived: false } };
   },
   },
 
 
   rename(title) {
   rename(title) {
-    return { $set: { title }};
+    return { $set: { title } };
   },
   },
 
 
   setDescription(description) {
   setDescription(description) {
-    return { $set: {description} };
+    return { $set: { description } };
   },
   },
 
 
   setColor(color) {
   setColor(color) {
-    return { $set: { color }};
+    return { $set: { color } };
   },
   },
 
 
   setVisibility(visibility) {
   setVisibility(visibility) {
-    return { $set: { permission: visibility }};
+    return { $set: { permission: visibility } };
   },
   },
 
 
   addLabel(name, color) {
   addLabel(name, color) {
@@ -276,7 +276,7 @@ Boards.mutations({
     // user).
     // user).
     if (!this.getLabel(name, color)) {
     if (!this.getLabel(name, color)) {
       const _id = Random.id(6);
       const _id = Random.id(6);
-      return { $push: {labels: { _id, name, color }}};
+      return { $push: { labels: { _id, name, color } } };
     }
     }
     return {};
     return {};
   },
   },
@@ -295,7 +295,7 @@ Boards.mutations({
   },
   },
 
 
   removeLabel(labelId) {
   removeLabel(labelId) {
-    return { $pull: { labels: { _id: labelId }}};
+    return { $pull: { labels: { _id: labelId } } };
   },
   },
 
 
   addMember(memberId) {
   addMember(memberId) {
@@ -386,7 +386,7 @@ if (Meteor.isServer) {
         return false;
         return false;
 
 
       // If there is more than one admin, it's ok to remove anyone
       // If there is more than one admin, it's ok to remove anyone
-      const nbAdmins = _.where(doc.members, {isActive: true, isAdmin: true}).length;
+      const nbAdmins = _.where(doc.members, { isActive: true, isAdmin: true }).length;
       if (nbAdmins > 1)
       if (nbAdmins > 1)
         return false;
         return false;
 
 
@@ -408,7 +408,7 @@ if (Meteor.isServer) {
       if (board) {
       if (board) {
         const userId = Meteor.userId();
         const userId = Meteor.userId();
         const index = board.memberIndex(userId);
         const index = board.memberIndex(userId);
-        if (index>=0) {
+        if (index >= 0) {
           board.removeMember(userId);
           board.removeMember(userId);
           return true;
           return true;
         } else throw new Meteor.Error('error-board-notAMember');
         } else throw new Meteor.Error('error-board-notAMember');
@@ -424,7 +424,7 @@ if (Meteor.isServer) {
       _id: 1,
       _id: 1,
       'members.userId': 1,
       'members.userId': 1,
     }, { unique: true });
     }, { unique: true });
-    Boards._collection._ensureIndex({'members.userId': 1});
+    Boards._collection._ensureIndex({ 'members.userId': 1 });
   });
   });
 
 
   // Genesis: the first activity of the newly created board
   // Genesis: the first activity of the newly created board
@@ -553,3 +553,60 @@ if (Meteor.isServer) {
     }
     }
   });
   });
 }
 }
+
+//BOARDS REST API
+if (Meteor.isServer) {
+  JsonRoutes.add('GET', '/api/boards', function (req, res, next) {
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Boards.find({ permission: 'public' }).map(function (doc) {
+        return {
+          _id: doc._id,
+          title: doc.title,
+        };
+      }),
+    });
+  });
+
+  JsonRoutes.add('GET', '/api/boards/:id', function (req, res, next) {
+    const id = req.params.id;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Boards.findOne({ _id: id }),
+    });
+  });
+
+  JsonRoutes.add('POST', '/api/boards', function (req, res, next) {
+    const id = Boards.insert({
+      title: req.body.title,
+      members: [
+        {
+          userId: req.body.owner,
+          isAdmin: true,
+          isActive: true,
+          isCommentOnly: false,
+        },
+      ],
+      permission: 'public',
+      color: 'belize',
+    });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: id,
+      },
+    });
+  });
+
+  JsonRoutes.add('DELETE', '/api/boards/:id', function (req, res, next) {
+    const id = req.params.id;
+    Boards.remove({ _id: id });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data:{
+        _id: id,
+      },
+    });
+  });
+
+}

+ 79 - 19
models/cards.js

@@ -123,15 +123,15 @@ Cards.helpers({
   },
   },
 
 
   activities() {
   activities() {
-    return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }});
+    return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 } });
   },
   },
 
 
   comments() {
   comments() {
-    return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
+    return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 } });
   },
   },
 
 
   attachments() {
   attachments() {
-    return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }});
+    return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 } });
   },
   },
 
 
   cover() {
   cover() {
@@ -142,7 +142,7 @@ Cards.helpers({
   },
   },
 
 
   checklists() {
   checklists() {
-    return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 }});
+    return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 } });
   },
   },
 
 
   checklistItemCount() {
   checklistItemCount() {
@@ -183,19 +183,19 @@ Cards.helpers({
 
 
 Cards.mutations({
 Cards.mutations({
   archive() {
   archive() {
-    return { $set: { archived: true }};
+    return { $set: { archived: true } };
   },
   },
 
 
   restore() {
   restore() {
-    return { $set: { archived: false }};
+    return { $set: { archived: false } };
   },
   },
 
 
   setTitle(title) {
   setTitle(title) {
-    return { $set: { title }};
+    return { $set: { title } };
   },
   },
 
 
   setDescription(description) {
   setDescription(description) {
-    return { $set: { description }};
+    return { $set: { description } };
   },
   },
 
 
   move(listId, sortIndex) {
   move(listId, sortIndex) {
@@ -207,11 +207,11 @@ Cards.mutations({
   },
   },
 
 
   addLabel(labelId) {
   addLabel(labelId) {
-    return { $addToSet: { labelIds: labelId }};
+    return { $addToSet: { labelIds: labelId } };
   },
   },
 
 
   removeLabel(labelId) {
   removeLabel(labelId) {
-    return { $pull: { labelIds: labelId }};
+    return { $pull: { labelIds: labelId } };
   },
   },
 
 
   toggleLabel(labelId) {
   toggleLabel(labelId) {
@@ -223,11 +223,11 @@ Cards.mutations({
   },
   },
 
 
   assignMember(memberId) {
   assignMember(memberId) {
-    return { $addToSet: { members: memberId }};
+    return { $addToSet: { members: memberId } };
   },
   },
 
 
   unassignMember(memberId) {
   unassignMember(memberId) {
-    return { $pull: { members: memberId }};
+    return { $pull: { members: memberId } };
   },
   },
 
 
   toggleMember(memberId) {
   toggleMember(memberId) {
@@ -239,27 +239,27 @@ Cards.mutations({
   },
   },
 
 
   setCover(coverId) {
   setCover(coverId) {
-    return { $set: { coverId }};
+    return { $set: { coverId } };
   },
   },
 
 
   unsetCover() {
   unsetCover() {
-    return { $unset: { coverId: '' }};
+    return { $unset: { coverId: '' } };
   },
   },
 
 
   setStart(startAt) {
   setStart(startAt) {
-    return { $set: { startAt }};
+    return { $set: { startAt } };
   },
   },
 
 
   unsetStart() {
   unsetStart() {
-    return { $unset: { startAt: '' }};
+    return { $unset: { startAt: '' } };
   },
   },
 
 
   setDue(dueAt) {
   setDue(dueAt) {
-    return { $set: { dueAt }};
+    return { $set: { dueAt } };
   },
   },
 
 
   unsetDue() {
   unsetDue() {
-    return { $unset: { dueAt: '' }};
+    return { $unset: { dueAt: '' } };
   },
   },
 });
 });
 
 
@@ -304,7 +304,7 @@ if (Meteor.isServer) {
   });
   });
 
 
   // New activity for card moves
   // New activity for card moves
-  Cards.after.update(function(userId, doc, fieldNames) {
+  Cards.after.update(function (userId, doc, fieldNames) {
     const oldListId = this.previous.listId;
     const oldListId = this.previous.listId;
     if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {
     if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {
       Activities.insert({
       Activities.insert({
@@ -370,3 +370,63 @@ if (Meteor.isServer) {
     });
     });
   });
   });
 }
 }
+//LISTS REST API
+if (Meteor.isServer) {
+  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    const paramListId = req.params.listId;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Cards.find({ boardId: paramBoardId, listId: paramListId, archived: false }).map(function (doc) {
+        return {
+          _id: doc._id,
+          title: doc.title,
+          description: doc.description,
+        };
+      }),
+    });
+  });
+
+  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    const paramListId = req.params.listId;
+    const paramCardId = req.params.cardId;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Cards.findOne({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false }),
+    });
+  });
+
+  JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    const paramListId = req.params.listId;
+    const id = Cards.insert({
+      title: req.body.title,
+      boardId: paramBoardId,
+      listId: paramListId,
+      description: req.body.description,
+      userId : req.body.authorId,
+      sort: 0,
+      members:[ req.body.authorId ],
+    });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: id,
+      },
+    });
+  });
+
+  JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    const paramListId = req.params.listId;
+    const paramCardId = req.params.cardId;
+    Cards.remove({ _id: paramCardId, listId: paramListId, boardId: paramBoardId });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: paramCardId,
+      },
+    });
+  });
+}

+ 41 - 39
models/export.js

@@ -1,5 +1,5 @@
 /* global JsonRoutes */
 /* global JsonRoutes */
-if(Meteor.isServer) {
+if (Meteor.isServer) {
   // todo XXX once we have a real API in place, move that route there
   // todo XXX once we have a real API in place, move that route there
   // todo XXX also  share the route definition between the client and the server
   // todo XXX also  share the route definition between the client and the server
   // so that we could use something like
   // so that we could use something like
@@ -14,28 +14,28 @@ if(Meteor.isServer) {
    * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
    * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
    * for detailed explanations
    * for detailed explanations
    */
    */
-  JsonRoutes.add('get', '/api/boards/:boardId', function (req, res) {
-    const boardId = req.params.boardId;
-    let user = null;
-    // todo XXX for real API, first look for token in Authentication: header
-    // then fallback to parameter
-    const loginToken = req.query.authToken;
-    if (loginToken) {
-      const hashToken = Accounts._hashLoginToken(loginToken);
-      user = Meteor.users.findOne({
-        'services.resume.loginTokens.hashedToken': hashToken,
-      });
-    }
+  // JsonRoutes.add('get', '/api/boards/:boardId', function (req, res) {
+  //   const boardId = req.params.boardId;
+  //   let user = null;
+  //   // todo XXX for real API, first look for token in Authentication: header
+  //   // then fallback to parameter
+  //   const loginToken = req.query.authToken;
+  //   if (loginToken) {
+  //     const hashToken = Accounts._hashLoginToken(loginToken);
+  //     user = Meteor.users.findOne({
+  //       'services.resume.loginTokens.hashedToken': hashToken,
+  //     });
+  //   }
 
 
-    const exporter = new Exporter(boardId);
-    if(exporter.canExport(user)) {
-      JsonRoutes.sendResult(res, 200, exporter.build());
-    } else {
-      // we could send an explicit error message, but on the other hand the only
-      // way to get there is by hacking the UI so let's keep it raw.
-      JsonRoutes.sendResult(res, 403);
-    }
-  });
+  //   const exporter = new Exporter(boardId);
+  //   if(exporter.canExport(user)) {
+  //     JsonRoutes.sendResult(res, 200, exporter.build());
+  //   } else {
+  //     // we could send an explicit error message, but on the other hand the only
+  //     // way to get there is by hacking the UI so let's keep it raw.
+  //     JsonRoutes.sendResult(res, 403);
+  //   }
+  // });
 }
 }
 
 
 class Exporter {
 class Exporter {
@@ -44,13 +44,13 @@ class Exporter {
   }
   }
 
 
   build() {
   build() {
-    const byBoard = {boardId: this._boardId};
+    const byBoard = { boardId: this._boardId };
     // we do not want to retrieve boardId in related elements
     // we do not want to retrieve boardId in related elements
-    const noBoardId = {fields: {boardId: 0}};
+    const noBoardId = { fields: { boardId: 0 } };
     const result = {
     const result = {
       _format: 'wekan-board-1.0.0',
       _format: 'wekan-board-1.0.0',
     };
     };
-    _.extend(result, Boards.findOne(this._boardId, {fields: {stars: 0}}));
+    _.extend(result, Boards.findOne(this._boardId, { fields: { stars: 0 } }));
     result.lists = Lists.find(byBoard, noBoardId).fetch();
     result.lists = Lists.find(byBoard, noBoardId).fetch();
     result.cards = Cards.find(byBoard, noBoardId).fetch();
     result.cards = Cards.find(byBoard, noBoardId).fetch();
     result.comments = CardComments.find(byBoard, noBoardId).fetch();
     result.comments = CardComments.find(byBoard, noBoardId).fetch();
@@ -69,29 +69,31 @@ class Exporter {
     // 1- only exports users that are linked somehow to that board
     // 1- only exports users that are linked somehow to that board
     // 2- do not export any sensitive information
     // 2- do not export any sensitive information
     const users = {};
     const users = {};
-    result.members.forEach((member) => {users[member.userId] = true;});
-    result.lists.forEach((list) => {users[list.userId] = true;});
+    result.members.forEach((member) => { users[member.userId] = true; });
+    result.lists.forEach((list) => { users[list.userId] = true; });
     result.cards.forEach((card) => {
     result.cards.forEach((card) => {
       users[card.userId] = true;
       users[card.userId] = true;
       if (card.members) {
       if (card.members) {
-        card.members.forEach((memberId) => {users[memberId] = true;});
+        card.members.forEach((memberId) => { users[memberId] = true; });
       }
       }
     });
     });
-    result.comments.forEach((comment) => {users[comment.userId] = true;});
-    result.activities.forEach((activity) => {users[activity.userId] = true;});
-    const byUserIds = {_id: {$in: Object.getOwnPropertyNames(users)}};
+    result.comments.forEach((comment) => { users[comment.userId] = true; });
+    result.activities.forEach((activity) => { users[activity.userId] = true; });
+    const byUserIds = { _id: { $in: Object.getOwnPropertyNames(users) } };
     // we use whitelist to be sure we do not expose inadvertently
     // we use whitelist to be sure we do not expose inadvertently
     // some secret fields that gets added to User later.
     // some secret fields that gets added to User later.
-    const userFields = {fields: {
-      _id: 1,
-      username: 1,
-      'profile.fullname': 1,
-      'profile.initials': 1,
-      'profile.avatarUrl': 1,
-    }};
+    const userFields = {
+      fields: {
+        _id: 1,
+        username: 1,
+        'profile.fullname': 1,
+        'profile.initials': 1,
+        'profile.avatarUrl': 1,
+      },
+    };
     result.users = Users.find(byUserIds, userFields).fetch().map((user) => {
     result.users = Users.find(byUserIds, userFields).fetch().map((user) => {
       // user avatar is stored as a relative url, we export absolute
       // user avatar is stored as a relative url, we export absolute
-      if(user.profile.avatarUrl) {
+      if (user.profile.avatarUrl) {
         user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
         user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
       }
       }
       return user;
       return user;

+ 55 - 3
models/lists.js

@@ -76,15 +76,15 @@ Lists.helpers({
 
 
 Lists.mutations({
 Lists.mutations({
   rename(title) {
   rename(title) {
-    return { $set: { title }};
+    return { $set: { title } };
   },
   },
 
 
   archive() {
   archive() {
-    return { $set: { archived: true }};
+    return { $set: { archived: true } };
   },
   },
 
 
   restore() {
   restore() {
-    return { $set: { archived: false }};
+    return { $set: { archived: false } };
   },
   },
 });
 });
 
 
@@ -128,3 +128,55 @@ if (Meteor.isServer) {
     }
     }
   });
   });
 }
 }
+
+//LISTS REST API
+if (Meteor.isServer) {
+  JsonRoutes.add('GET', '/api/boards/:boardId/lists', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Lists.find({ boardId: paramBoardId, archived: false }).map(function (doc) {
+        return {
+          _id: doc._id,
+          title: doc.title,
+        };
+      }),
+    });
+  });
+
+  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    const paramListId = req.params.listId;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Lists.findOne({ _id: paramListId, boardId: paramBoardId, archived: false }),
+    });
+  });
+
+  JsonRoutes.add('POST', '/api/boards/:boardId/lists', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    const id = Lists.insert({
+      title: req.body.title,
+      boardId: paramBoardId,
+    });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: id,
+      },
+    });
+  });
+
+  JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId', function (req, res, next) {
+    const paramBoardId = req.params.boardId;
+    const paramListId = req.params.listId;
+    Lists.remove({ _id: paramListId, boardId: paramBoardId });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: paramListId,
+      },
+    });
+  });
+
+}

+ 75 - 29
models/users.js

@@ -1,7 +1,7 @@
 // Sandstorm context is detected using the METEOR_SETTINGS environment variable
 // Sandstorm context is detected using the METEOR_SETTINGS environment variable
 // in the package definition.
 // in the package definition.
 const isSandstorm = Meteor.settings && Meteor.settings.public &&
 const isSandstorm = Meteor.settings && Meteor.settings.public &&
-                    Meteor.settings.public.sandstorm;
+  Meteor.settings.public.sandstorm;
 Users = Meteor.users;
 Users = Meteor.users;
 
 
 Users.attachSchema(new SimpleSchema({
 Users.attachSchema(new SimpleSchema({
@@ -148,32 +148,32 @@ Users.helpers({
   },
   },
 
 
   starredBoards() {
   starredBoards() {
-    const {starredBoards = []} = this.profile;
-    return Boards.find({archived: false, _id: {$in: starredBoards}});
+    const { starredBoards = [] } = this.profile;
+    return Boards.find({ archived: false, _id: { $in: starredBoards } });
   },
   },
 
 
   hasStarred(boardId) {
   hasStarred(boardId) {
-    const {starredBoards = []} = this.profile;
+    const { starredBoards = [] } = this.profile;
     return _.contains(starredBoards, boardId);
     return _.contains(starredBoards, boardId);
   },
   },
 
 
   invitedBoards() {
   invitedBoards() {
-    const {invitedBoards = []} = this.profile;
-    return Boards.find({archived: false, _id: {$in: invitedBoards}});
+    const { invitedBoards = [] } = this.profile;
+    return Boards.find({ archived: false, _id: { $in: invitedBoards } });
   },
   },
 
 
   isInvitedTo(boardId) {
   isInvitedTo(boardId) {
-    const {invitedBoards = []} = this.profile;
+    const { invitedBoards = [] } = this.profile;
     return _.contains(invitedBoards, boardId);
     return _.contains(invitedBoards, boardId);
   },
   },
 
 
   hasTag(tag) {
   hasTag(tag) {
-    const {tags = []} = this.profile;
+    const { tags = [] } = this.profile;
     return _.contains(tags, tag);
     return _.contains(tags, tag);
   },
   },
 
 
   hasNotification(activityId) {
   hasNotification(activityId) {
-    const {notifications = []} = this.profile;
+    const { notifications = [] } = this.profile;
     return _.contains(notifications, activityId);
     return _.contains(notifications, activityId);
   },
   },
 
 
@@ -183,7 +183,7 @@ Users.helpers({
   },
   },
 
 
   getEmailBuffer() {
   getEmailBuffer() {
-    const {emailBuffer = []} = this.profile;
+    const { emailBuffer = [] } = this.profile;
     return emailBuffer;
     return emailBuffer;
   },
   },
 
 
@@ -308,7 +308,7 @@ Users.mutations({
   },
   },
 
 
   setAvatarUrl(avatarUrl) {
   setAvatarUrl(avatarUrl) {
-    return { $set: { 'profile.avatarUrl': avatarUrl }};
+    return { $set: { 'profile.avatarUrl': avatarUrl } };
   },
   },
 
 
   setShowCardsCountAt(limit) {
   setShowCardsCountAt(limit) {
@@ -323,7 +323,7 @@ Meteor.methods({
     if (nUsersWithUsername > 0) {
     if (nUsersWithUsername > 0) {
       throw new Meteor.Error('username-already-taken');
       throw new Meteor.Error('username-already-taken');
     } else {
     } else {
-      Users.update(this.userId, {$set: { username }});
+      Users.update(this.userId, { $set: { username } });
     }
     }
   },
   },
   toggleSystemMessages() {
   toggleSystemMessages() {
@@ -346,19 +346,19 @@ if (Meteor.isServer) {
       const inviter = Meteor.user();
       const inviter = Meteor.user();
       const board = Boards.findOne(boardId);
       const board = Boards.findOne(boardId);
       const allowInvite = inviter &&
       const allowInvite = inviter &&
-          board &&
-          board.members &&
-          _.contains(_.pluck(board.members, 'userId'), inviter._id) &&
-          _.where(board.members, {userId: inviter._id})[0].isActive &&
-          _.where(board.members, {userId: inviter._id})[0].isAdmin;
+        board &&
+        board.members &&
+        _.contains(_.pluck(board.members, 'userId'), inviter._id) &&
+        _.where(board.members, { userId: inviter._id })[0].isActive &&
+        _.where(board.members, { userId: inviter._id })[0].isAdmin;
       if (!allowInvite) throw new Meteor.Error('error-board-notAMember');
       if (!allowInvite) throw new Meteor.Error('error-board-notAMember');
 
 
       this.unblock();
       this.unblock();
 
 
       const posAt = username.indexOf('@');
       const posAt = username.indexOf('@');
       let user = null;
       let user = null;
-      if (posAt>=0) {
-        user = Users.findOne({emails: {$elemMatch: {address: username}}});
+      if (posAt >= 0) {
+        user = Users.findOne({ emails: { $elemMatch: { address: username } } });
       } else {
       } else {
         user = Users.findOne(username) || Users.findOne({ username });
         user = Users.findOne(username) || Users.findOne({ username });
       }
       }
@@ -409,7 +409,7 @@ if (Meteor.isServer) {
   });
   });
   Accounts.onCreateUser((options, user) => {
   Accounts.onCreateUser((options, user) => {
     const userCount = Users.find().count();
     const userCount = Users.find().count();
-    if (!isSandstorm && userCount === 0 ){
+    if (!isSandstorm && userCount === 0) {
       user.isAdmin = true;
       user.isAdmin = true;
       return user;
       return user;
     }
     }
@@ -421,11 +421,11 @@ if (Meteor.isServer) {
     if (!options || !options.profile) {
     if (!options || !options.profile) {
       throw new Meteor.Error('error-invitation-code-blank', 'The invitation code is required');
       throw new Meteor.Error('error-invitation-code-blank', 'The invitation code is required');
     }
     }
-    const invitationCode = InvitationCodes.findOne({code: options.profile.invitationcode, email: options.email, valid: true});
+    const invitationCode = InvitationCodes.findOne({ code: options.profile.invitationcode, email: options.email, valid: true });
     if (!invitationCode) {
     if (!invitationCode) {
       throw new Meteor.Error('error-invitation-code-not-exist', 'The invitation code doesn\'t exist');
       throw new Meteor.Error('error-invitation-code-not-exist', 'The invitation code doesn\'t exist');
-    }else{
-      user.profile = {icode: options.profile.invitationcode};
+    } else {
+      user.profile = { icode: options.profile.invitationcode };
     }
     }
 
 
     return user;
     return user;
@@ -445,7 +445,7 @@ if (Meteor.isServer) {
   // counter.
   // counter.
   // We need to run this code on the server only, otherwise the incrementation
   // We need to run this code on the server only, otherwise the incrementation
   // will be done twice.
   // will be done twice.
-  Users.after.update(function(userId, user, fieldNames) {
+  Users.after.update(function (userId, user, fieldNames) {
     // The `starredBoards` list is hosted on the `profile` field. If this
     // The `starredBoards` list is hosted on the `profile` field. If this
     // field hasn't been modificated we don't need to run this hook.
     // field hasn't been modificated we don't need to run this hook.
     if (!_.contains(fieldNames, 'profile'))
     if (!_.contains(fieldNames, 'profile'))
@@ -464,7 +464,7 @@ if (Meteor.isServer) {
     // direction and then in the other.
     // direction and then in the other.
     function incrementBoards(boardsIds, inc) {
     function incrementBoards(boardsIds, inc) {
       boardsIds.forEach((boardId) => {
       boardsIds.forEach((boardId) => {
-        Boards.update(boardId, {$inc: {stars: inc}});
+        Boards.update(boardId, { $inc: { stars: inc } });
       });
       });
     }
     }
     incrementBoards(_.difference(oldIds, newIds), -1);
     incrementBoards(_.difference(oldIds, newIds), -1);
@@ -505,10 +505,10 @@ if (Meteor.isServer) {
     //invite user to corresponding boards
     //invite user to corresponding boards
     const disableRegistration = Settings.findOne().disableRegistration;
     const disableRegistration = Settings.findOne().disableRegistration;
     if (disableRegistration) {
     if (disableRegistration) {
-      const invitationCode = InvitationCodes.findOne({code: doc.profile.icode, valid:true});
+      const invitationCode = InvitationCodes.findOne({ code: doc.profile.icode, valid: true });
       if (!invitationCode) {
       if (!invitationCode) {
         throw new Meteor.Error('error-invitation-code-not-exist');
         throw new Meteor.Error('error-invitation-code-not-exist');
-      }else{
+      } else {
         invitationCode.boardsToBeInvited.forEach((boardId) => {
         invitationCode.boardsToBeInvited.forEach((boardId) => {
           const board = Boards.findOne(boardId);
           const board = Boards.findOne(boardId);
           board.addMember(doc._id);
           board.addMember(doc._id);
@@ -517,9 +517,55 @@ if (Meteor.isServer) {
           doc.profile = {};
           doc.profile = {};
         }
         }
         doc.profile.invitedBoards = invitationCode.boardsToBeInvited;
         doc.profile.invitedBoards = invitationCode.boardsToBeInvited;
-        Users.update(doc._id, {$set:{profile: doc.profile}});
-        InvitationCodes.update(invitationCode._id, {$set: {valid:false}});
+        Users.update(doc._id, { $set: { profile: doc.profile } });
+        InvitationCodes.update(invitationCode._id, { $set: { valid: false } });
       }
       }
     }
     }
   });
   });
 }
 }
+
+
+// USERS REST API
+if (Meteor.isServer) {
+  JsonRoutes.add('GET', '/api/users', function (req, res, next) {
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Meteor.users.find({}).map(function (doc) {
+        return { _id: doc._id, username: doc.username };
+      }),
+    });
+  });
+  JsonRoutes.add('GET', '/api/users/:id', function (req, res, next) {
+    const id = req.params.id;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: Meteor.users.findOne({ _id: id }),
+    });
+  });
+  JsonRoutes.add('POST', '/api/users/', function (req, res, next) {
+    const id = Accounts.createUser({
+      username: req.body.username,
+      email: req.body.email,
+      password: 'default',
+    });
+
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: id,
+      },
+    });
+  });
+
+  JsonRoutes.add('DELETE', '/api/users/:id', function (req, res, next) {
+    const id = req.params.id;
+    Meteor.users.remove({ _id: id });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: id,
+      },
+    });
+  });
+}
+

+ 3 - 0
package.json

@@ -7,6 +7,9 @@
     "lint": "eslint --ignore-pattern 'packages/*' .",
     "lint": "eslint --ignore-pattern 'packages/*' .",
     "test": "npm run --silent lint"
     "test": "npm run --silent lint"
   },
   },
+  "eslintConfig": {
+    "extends": "@meteorjs/eslint-config-meteor"
+  },
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",
     "url": "git+https://github.com/wekan/wekan.git"
     "url": "git+https://github.com/wekan/wekan.git"