summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/activities.js2
-rw-r--r--models/boards.js136
-rw-r--r--models/cardComments.js62
-rw-r--r--models/cards.js131
-rw-r--r--models/checklists.js104
-rw-r--r--models/export.js80
-rw-r--r--models/import.js44
-rw-r--r--models/invitationCodes.js45
-rw-r--r--models/lists.js69
-rw-r--r--models/settings.js145
-rw-r--r--models/unsavedEdits.js3
-rw-r--r--models/users.js174
12 files changed, 861 insertions, 134 deletions
diff --git a/models/activities.js b/models/activities.js
index 7d262ec6..9a41d4aa 100644
--- a/models/activities.js
+++ b/models/activities.js
@@ -52,6 +52,8 @@ if (Meteor.isServer) {
Activities._collection._ensureIndex({ createdAt: -1 });
Activities._collection._ensureIndex({ cardId: 1, createdAt: -1 });
Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 });
+ Activities._collection._ensureIndex({ commentId: 1 }, { partialFilterExpression: { commentId: { $exists: true } } });
+ Activities._collection._ensureIndex({ attachmentId: 1 }, { partialFilterExpression: { attachmentId: { $exists: true } } });
});
Activities.after.insert((userId, doc) => {
diff --git a/models/boards.js b/models/boards.js
index 14943d61..8a7844e2 100644
--- a/models/boards.js
+++ b/models/boards.js
@@ -107,6 +107,7 @@ Boards.attachSchema(new SimpleSchema({
userId: this.userId,
isAdmin: true,
isActive: true,
+ isCommentOnly: false,
}];
}
},
@@ -120,6 +121,9 @@ Boards.attachSchema(new SimpleSchema({
'members.$.isActive': {
type: Boolean,
},
+ 'members.$.isCommentOnly': {
+ type: Boolean,
+ },
permission: {
type: String,
allowedValues: ['public', 'private'],
@@ -152,7 +156,7 @@ Boards.helpers({
* Is supplied user authorized to view this board?
*/
isVisibleBy(user) {
- if(this.isPublic()) {
+ if (this.isPublic()) {
// public boards are visible to everyone
return true;
} else {
@@ -168,7 +172,7 @@ Boards.helpers({
* @returns {boolean} the member that matches, or undefined/false
*/
isActiveMember(userId) {
- if(userId) {
+ if (userId) {
return this.members.find((member) => (member.userId === userId && member.isActive));
} else {
return false;
@@ -180,23 +184,23 @@ Boards.helpers({
},
lists() {
- return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 }});
+ return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } });
},
activities() {
- return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }});
+ return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 } });
},
activeMembers() {
- return _.where(this.members, {isActive: true});
+ return _.where(this.members, { isActive: true });
},
activeAdmins() {
- return _.where(this.members, {isActive: true, isAdmin: true});
+ return _.where(this.members, { isActive: true, isAdmin: true });
},
memberUsers() {
- return Users.find({ _id: {$in: _.pluck(this.members, 'userId')} });
+ return Users.find({ _id: { $in: _.pluck(this.members, 'userId') } });
},
getLabel(name, color) {
@@ -212,11 +216,15 @@ Boards.helpers({
},
hasMember(memberId) {
- return !!_.findWhere(this.members, {userId: memberId, isActive: true});
+ return !!_.findWhere(this.members, { userId: memberId, isActive: true });
},
hasAdmin(memberId) {
- return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: true});
+ return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: true });
+ },
+
+ hasCommentOnly(memberId) {
+ return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: false, isCommentOnly: true });
},
absoluteUrl() {
@@ -231,34 +239,34 @@ Boards.helpers({
// XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove...
pushLabel(name, color) {
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;
},
});
Boards.mutations({
archive() {
- return { $set: { archived: true }};
+ return { $set: { archived: true } };
},
restore() {
- return { $set: { archived: false }};
+ return { $set: { archived: false } };
},
rename(title) {
- return { $set: { title }};
+ return { $set: { title } };
},
setDescription(description) {
- return { $set: {description} };
+ return { $set: { description } };
},
setColor(color) {
- return { $set: { color }};
+ return { $set: { color } };
},
setVisibility(visibility) {
- return { $set: { permission: visibility }};
+ return { $set: { permission: visibility } };
},
addLabel(name, color) {
@@ -268,7 +276,7 @@ Boards.mutations({
// user).
if (!this.getLabel(name, color)) {
const _id = Random.id(6);
- return { $push: {labels: { _id, name, color }}};
+ return { $push: { labels: { _id, name, color } } };
}
return {};
},
@@ -287,7 +295,7 @@ Boards.mutations({
},
removeLabel(labelId) {
- return { $pull: { labels: { _id: labelId }}};
+ return { $pull: { labels: { _id: labelId } } };
},
addMember(memberId) {
@@ -306,6 +314,7 @@ Boards.mutations({
userId: memberId,
isAdmin: false,
isActive: true,
+ isCommentOnly: false,
},
},
};
@@ -332,7 +341,7 @@ Boards.mutations({
};
},
- setMemberPermission(memberId, isAdmin) {
+ setMemberPermission(memberId, isAdmin, isCommentOnly) {
const memberIndex = this.memberIndex(memberId);
// do not allow change permission of self
@@ -343,6 +352,7 @@ Boards.mutations({
return {
$set: {
[`members.${memberIndex}.isAdmin`]: isAdmin,
+ [`members.${memberIndex}.isCommentOnly`]: isCommentOnly,
},
};
},
@@ -376,7 +386,7 @@ if (Meteor.isServer) {
return false;
// 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)
return false;
@@ -398,7 +408,7 @@ if (Meteor.isServer) {
if (board) {
const userId = Meteor.userId();
const index = board.memberIndex(userId);
- if (index>=0) {
+ if (index >= 0) {
board.removeMember(userId);
return true;
} else throw new Meteor.Error('error-board-notAMember');
@@ -414,6 +424,7 @@ if (Meteor.isServer) {
_id: 1,
'members.userId': 1,
}, { unique: true });
+ Boards._collection._ensureIndex({ 'members.userId': 1 });
});
// Genesis: the first activity of the newly created board
@@ -542,3 +553,86 @@ if (Meteor.isServer) {
}
});
}
+
+//BOARDS REST API
+if (Meteor.isServer) {
+ JsonRoutes.add('GET', '/api/users/:userId/boards', function (req, res, next) {
+ Authentication.checkLoggedIn(req.userId);
+ const paramUserId = req.params.userId;
+ // A normal user should be able to see their own boards,
+ // admins can access boards of any user
+ Authentication.checkAdminOrCondition(req.userId, req.userId === paramUserId);
+
+ const data = Boards.find({
+ archived: false,
+ 'members.userId': req.userId,
+ }, {
+ sort: ['title'],
+ }).map(function(board) {
+ return {
+ _id: board._id,
+ title: board.title,
+ };
+ });
+
+ JsonRoutes.sendResult(res, {code: 200, data});
+ });
+
+ JsonRoutes.add('GET', '/api/boards', function (req, res, next) {
+ Authentication.checkUserId(req.userId);
+ 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;
+ Authentication.checkBoardAccess( req.userId, id);
+
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: Boards.findOne({ _id: id }),
+ });
+ });
+
+ JsonRoutes.add('POST', '/api/boards', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ const id = req.params.id;
+ Boards.remove({ _id: id });
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data:{
+ _id: id,
+ },
+ });
+ });
+}
diff --git a/models/cardComments.js b/models/cardComments.js
index 070c148e..e51275a4 100644
--- a/models/cardComments.js
+++ b/models/cardComments.js
@@ -80,3 +80,65 @@ if (Meteor.isServer) {
}
});
}
+
+//CARD COMMENT REST API
+if (Meteor.isServer) {
+ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramBoardId = req.params.boardId;
+ const paramCardId = req.params.cardId;
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: CardComments.find({ boardId: paramBoardId, cardId: paramCardId}).map(function (doc) {
+ return {
+ _id: doc._id,
+ comment: doc.text,
+ authorId: doc.userId,
+ };
+ }),
+ });
+ });
+
+ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramBoardId = req.params.boardId;
+ const paramCommentId = req.params.commentId;
+ const paramCardId = req.params.cardId;
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: CardComments.findOne({ _id: paramCommentId, cardId: paramCardId, boardId: paramBoardId }),
+ });
+ });
+
+ JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/comments', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramBoardId = req.params.boardId;
+ const paramCardId = req.params.cardId;
+ const id = CardComments.insert({
+ userId: req.body.authorId,
+ text: req.body.comment,
+ cardId: paramCardId,
+ boardId: paramBoardId,
+ });
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: id,
+ },
+ });
+ });
+
+ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramBoardId = req.params.boardId;
+ const paramCommentId = req.params.commentId;
+ const paramCardId = req.params.cardId;
+ CardComments.remove({ _id: paramCommentId, cardId: paramCardId, boardId: paramBoardId });
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: paramCardId,
+ },
+ });
+ });
+}
diff --git a/models/cards.js b/models/cards.js
index f6bd0b06..c48b4845 100644
--- a/models/cards.js
+++ b/models/cards.js
@@ -123,15 +123,15 @@ Cards.helpers({
},
activities() {
- return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }});
+ return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 } });
},
comments() {
- return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
+ return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 } });
},
attachments() {
- return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }});
+ return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 } });
},
cover() {
@@ -142,7 +142,7 @@ Cards.helpers({
},
checklists() {
- return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 }});
+ return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 } });
},
checklistItemCount() {
@@ -183,19 +183,19 @@ Cards.helpers({
Cards.mutations({
archive() {
- return { $set: { archived: true }};
+ return { $set: { archived: true } };
},
restore() {
- return { $set: { archived: false }};
+ return { $set: { archived: false } };
},
setTitle(title) {
- return { $set: { title }};
+ return { $set: { title } };
},
setDescription(description) {
- return { $set: { description }};
+ return { $set: { description } };
},
move(listId, sortIndex) {
@@ -207,11 +207,11 @@ Cards.mutations({
},
addLabel(labelId) {
- return { $addToSet: { labelIds: labelId }};
+ return { $addToSet: { labelIds: labelId } };
},
removeLabel(labelId) {
- return { $pull: { labelIds: labelId }};
+ return { $pull: { labelIds: labelId } };
},
toggleLabel(labelId) {
@@ -223,11 +223,11 @@ Cards.mutations({
},
assignMember(memberId) {
- return { $addToSet: { members: memberId }};
+ return { $addToSet: { members: memberId } };
},
unassignMember(memberId) {
- return { $pull: { members: memberId }};
+ return { $pull: { members: memberId } };
},
toggleMember(memberId) {
@@ -239,27 +239,27 @@ Cards.mutations({
},
setCover(coverId) {
- return { $set: { coverId }};
+ return { $set: { coverId } };
},
unsetCover() {
- return { $unset: { coverId: '' }};
+ return { $unset: { coverId: '' } };
},
setStart(startAt) {
- return { $set: { startAt }};
+ return { $set: { startAt } };
},
unsetStart() {
- return { $unset: { startAt: '' }};
+ return { $unset: { startAt: '' } };
},
setDue(dueAt) {
- return { $set: { dueAt }};
+ return { $set: { dueAt } };
},
unsetDue() {
- return { $unset: { dueAt: '' }};
+ return { $unset: { dueAt: '' } };
},
});
@@ -267,7 +267,7 @@ if (Meteor.isServer) {
// Cards are often fetched within a board, so we create an index to make these
// queries more efficient.
Meteor.startup(() => {
- Cards._collection._ensureIndex({ boardId: 1 });
+ Cards._collection._ensureIndex({ boardId: 1, createdAt: -1 });
});
Cards.after.insert((userId, doc) => {
@@ -304,7 +304,7 @@ if (Meteor.isServer) {
});
// New activity for card moves
- Cards.after.update(function(userId, doc, fieldNames) {
+ Cards.after.update(function (userId, doc, fieldNames) {
const oldListId = this.previous.listId;
if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {
Activities.insert({
@@ -340,20 +340,97 @@ if (Meteor.isServer) {
// Say goodbye to the former member
if (modifier.$pull && modifier.$pull.members) {
memberId = modifier.$pull.members;
- Activities.insert({
- userId,
- memberId,
- activityType: 'unjoinMember',
- boardId: doc.boardId,
- cardId: doc._id,
- });
+ // Check that the former member is member of the card
+ if (_.contains(doc.members, memberId)) {
+ Activities.insert({
+ userId,
+ memberId,
+ activityType: 'unjoinMember',
+ boardId: doc.boardId,
+ cardId: doc._id,
+ });
+ }
}
});
// Remove all activities associated with a card if we remove the card
+ // Remove also card_comments / checklists / attachments
Cards.after.remove((userId, doc) => {
Activities.remove({
cardId: doc._id,
});
+ Checklists.remove({
+ cardId: doc._id,
+ });
+ CardComments.remove({
+ cardId: doc._id,
+ });
+ Attachments.remove({
+ cardId: doc._id,
+ });
+ });
+}
+//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;
+ Authentication.checkBoardAccess( req.userId, paramBoardId);
+ 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;
+ Authentication.checkBoardAccess( req.userId, paramBoardId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ 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,
+ },
+ });
});
}
diff --git a/models/checklists.js b/models/checklists.js
index 35be4dcc..537aecb0 100644
--- a/models/checklists.js
+++ b/models/checklists.js
@@ -28,22 +28,29 @@ Checklists.attachSchema(new SimpleSchema({
createdAt: {
type: Date,
denyUpdate: false,
+ autoValue() { // eslint-disable-line consistent-return
+ if (this.isInsert) {
+ return new Date();
+ } else {
+ this.unset();
+ }
+ },
},
}));
Checklists.helpers({
- itemCount () {
+ itemCount() {
return this.items.length;
},
- finishedCount () {
+ finishedCount() {
return this.items.filter((item) => {
return item.isFinished;
}).length;
},
- isFinished () {
+ isFinished() {
return 0 !== this.itemCount() && this.itemCount() === this.finishedCount();
},
- getItem (_id) {
+ getItem(_id) {
return _.findWhere(this.items, { _id });
},
itemIndex(itemId) {
@@ -73,17 +80,17 @@ Checklists.before.insert((userId, doc) => {
Checklists.mutations({
//for checklist itself
- setTitle(title){
- return { $set: { title }};
+ setTitle(title) {
+ return { $set: { title } };
},
//for items in checklist
addItem(title) {
const itemCount = this.itemCount();
const _id = `${this._id}${itemCount}`;
- return { $addToSet: {items: {_id, title, isFinished: false}} };
+ return { $addToSet: { items: { _id, title, isFinished: false } } };
},
removeItem(itemId) {
- return {$pull: {items: {_id : itemId}}};
+ return { $pull: { items: { _id: itemId } } };
},
editItem(itemId, title) {
if (this.getItem(itemId)) {
@@ -133,6 +140,10 @@ Checklists.mutations({
});
if (Meteor.isServer) {
+ Meteor.startup(() => {
+ Checklists._collection._ensureIndex({ cardId: 1, createdAt: 1 });
+ });
+
Checklists.after.insert((userId, doc) => {
Activities.insert({
userId,
@@ -146,13 +157,13 @@ if (Meteor.isServer) {
//TODO: so there will be no activity for adding item into checklist, maybe will be implemented in the future.
// Checklists.after.update((userId, doc) => {
// console.log('update:', doc)
- // Activities.insert({
- // userId,
- // activityType: 'addChecklist',
- // boardId: doc.boardId,
- // cardId: doc.cardId,
- // checklistId: doc._id,
- // });
+ // Activities.insert({
+ // userId,
+ // activityType: 'addChecklist',
+ // boardId: doc.boardId,
+ // cardId: doc.cardId,
+ // checklistId: doc._id,
+ // });
// });
Checklists.before.remove((userId, doc) => {
@@ -162,3 +173,66 @@ if (Meteor.isServer) {
}
});
}
+
+//CARD COMMENT REST API
+if (Meteor.isServer) {
+ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramCardId = req.params.cardId;
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: Checklists.find({ cardId: paramCardId }).map(function (doc) {
+ return {
+ _id: doc._id,
+ title: doc.title,
+ };
+ }),
+ });
+ });
+
+ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramChecklistId = req.params.checklistId;
+ const paramCardId = req.params.cardId;
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: Checklists.findOne({ _id: paramChecklistId, cardId: paramCardId }),
+ });
+ });
+
+ JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramCardId = req.params.cardId;
+
+ const checklistToSend = {};
+ checklistToSend.cardId = paramCardId;
+ checklistToSend.title = req.body.title;
+ checklistToSend.items = [];
+ const id = Checklists.insert(checklistToSend);
+ const checklist = Checklists.findOne({_id: id});
+ req.body.items.forEach(function (item) {
+ checklist.addItem(item);
+ }, this);
+
+
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: id,
+ },
+ });
+ });
+
+ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ const paramCommentId = req.params.commentId;
+ const paramCardId = req.params.cardId;
+ Checklists.remove({ _id: paramCommentId, cardId: paramCardId });
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: paramCardId,
+ },
+ });
+ });
+}
diff --git a/models/export.js b/models/export.js
index b774cf15..7a363dd3 100644
--- a/models/export.js
+++ b/models/export.js
@@ -1,5 +1,5 @@
/* global JsonRoutes */
-if(Meteor.isServer) {
+if (Meteor.isServer) {
// 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
// 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/
* 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 {
@@ -44,13 +44,13 @@ class Exporter {
}
build() {
- const byBoard = {boardId: this._boardId};
+ const byBoard = { boardId: this._boardId };
// we do not want to retrieve boardId in related elements
- const noBoardId = {fields: {boardId: 0}};
+ const noBoardId = { fields: { boardId: 0 } };
const result = {
_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.cards = Cards.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
// 2- do not export any sensitive information
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) => {
users[card.userId] = true;
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
// 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) => {
// 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);
}
return user;
diff --git a/models/import.js b/models/import.js
index 86ef75b3..4e78c53a 100644
--- a/models/import.js
+++ b/models/import.js
@@ -25,6 +25,8 @@ class TrelloCreator {
this.labels = {};
// Map of lists Trello ID => Wekan ID
this.lists = {};
+ // Map of cards Trello ID => Wekan ID
+ this.cards = {};
// The comments, indexed by Trello card id (to map when importing cards)
this.comments = {};
// the members, indexed by Trello member id => Wekan user ID
@@ -119,6 +121,18 @@ class TrelloCreator {
})]);
}
+ checkChecklists(trelloChecklists) {
+ check(trelloChecklists, [Match.ObjectIncluding({
+ idBoard: String,
+ idCard: String,
+ name: String,
+ checkItems: [Match.ObjectIncluding({
+ state: String,
+ name: String,
+ })],
+ })]);
+ }
+
// You must call parseActions before calling this one.
createBoardAndLabels(trelloBoard) {
const boardToCreate = {
@@ -131,6 +145,7 @@ class TrelloCreator {
userId: Meteor.userId(),
isAdmin: true,
isActive: true,
+ isCommentOnly: false,
}],
permission: this.getPermission(trelloBoard.prefs.permissionLevel),
slug: getSlug(trelloBoard.name) || 'board',
@@ -156,6 +171,7 @@ class TrelloCreator {
userId: wekanId,
isAdmin: this.getAdmin(trelloMembership.memberType),
isActive: true,
+ isCommentOnly: false,
});
}
}
@@ -241,6 +257,8 @@ class TrelloCreator {
}
// insert card
const cardId = Cards.direct.insert(cardToCreate);
+ // keep track of Trello id => WeKan id
+ this.cards[card.id] = cardId;
// log activity
Activities.direct.insert({
activityType: 'importCard',
@@ -280,7 +298,7 @@ class TrelloCreator {
createdAt: this._now(commentToCreate.createdAt),
// we attribute the addComment (not the import)
// to the original author - it is needed by some UI elements.
- userId: commentToCreate.userId,
+ userId: this._user(commentToCreate.userId),
});
});
}
@@ -365,6 +383,28 @@ class TrelloCreator {
});
}
+ createChecklists(trelloChecklists) {
+ trelloChecklists.forEach((checklist) => {
+ // Create the checklist
+ const checklistToCreate = {
+ cardId: this.cards[checklist.idCard],
+ title: checklist.name,
+ createdAt: this._now(),
+ };
+ const checklistId = Checklists.direct.insert(checklistToCreate);
+ // Now add the items to the checklist
+ const itemsToCreate = [];
+ checklist.checkItems.forEach((item) => {
+ itemsToCreate.push({
+ _id: checklistId + itemsToCreate.length,
+ title: item.name,
+ isFinished: item.state === 'complete',
+ });
+ });
+ Checklists.direct.update(checklistId, {$set: {items: itemsToCreate}});
+ });
+ }
+
getAdmin(trelloMemberType) {
return trelloMemberType === 'admin';
}
@@ -446,6 +486,7 @@ Meteor.methods({
trelloCreator.checkLabels(trelloBoard.labels);
trelloCreator.checkLists(trelloBoard.lists);
trelloCreator.checkCards(trelloBoard.cards);
+ trelloCreator.checkChecklists(trelloBoard.checklists);
} catch (e) {
throw new Meteor.Error('error-json-schema');
}
@@ -458,6 +499,7 @@ Meteor.methods({
const boardId = trelloCreator.createBoardAndLabels(trelloBoard);
trelloCreator.createLists(trelloBoard.lists, boardId);
trelloCreator.createCards(trelloBoard.cards, boardId);
+ trelloCreator.createChecklists(trelloBoard.checklists);
// XXX add members
return boardId;
},
diff --git a/models/invitationCodes.js b/models/invitationCodes.js
new file mode 100644
index 00000000..5761977a
--- /dev/null
+++ b/models/invitationCodes.js
@@ -0,0 +1,45 @@
+InvitationCodes = new Mongo.Collection('invitation_codes');
+
+InvitationCodes.attachSchema(new SimpleSchema({
+ code: {
+ type: String,
+ },
+ email: {
+ type: String,
+ unique: true,
+ regEx: SimpleSchema.RegEx.Email,
+ },
+ createdAt: {
+ type: Date,
+ denyUpdate: false,
+ },
+ // always be the admin if only one admin
+ authorId: {
+ type: String,
+ },
+ boardsToBeInvited: {
+ type: [String],
+ optional: true,
+ },
+ valid: {
+ type: Boolean,
+ defaultValue: true,
+ },
+}));
+
+InvitationCodes.helpers({
+ author(){
+ return Users.findOne(this.authorId);
+ },
+});
+
+// InvitationCodes.before.insert((userId, doc) => {
+ // doc.createdAt = new Date();
+ // doc.authorId = userId;
+// });
+
+if (Meteor.isServer) {
+ Boards.deny({
+ fetch: ['members'],
+ });
+}
diff --git a/models/lists.js b/models/lists.js
index 682fb096..d9a5b8e2 100644
--- a/models/lists.js
+++ b/models/lists.js
@@ -46,13 +46,13 @@ Lists.attachSchema(new SimpleSchema({
Lists.allow({
insert(userId, doc) {
- return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+ return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
- return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+ return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
- return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+ return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId));
},
fetch: ['boardId'],
});
@@ -76,15 +76,15 @@ Lists.helpers({
Lists.mutations({
rename(title) {
- return { $set: { title }};
+ return { $set: { title } };
},
archive() {
- return { $set: { archived: true }};
+ return { $set: { archived: true } };
},
restore() {
- return { $set: { archived: false }};
+ return { $set: { archived: false } };
},
});
@@ -128,3 +128,60 @@ 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;
+ Authentication.checkBoardAccess( req.userId, paramBoardId);
+
+ 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;
+ Authentication.checkBoardAccess( req.userId, paramBoardId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ const paramBoardId = req.params.boardId;
+ const paramListId = req.params.listId;
+ Lists.remove({ _id: paramListId, boardId: paramBoardId });
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: paramListId,
+ },
+ });
+ });
+
+}
diff --git a/models/settings.js b/models/settings.js
new file mode 100644
index 00000000..e9dce26d
--- /dev/null
+++ b/models/settings.js
@@ -0,0 +1,145 @@
+Settings = new Mongo.Collection('settings');
+
+Settings.attachSchema(new SimpleSchema({
+ disableRegistration: {
+ type: Boolean,
+ },
+ 'mailServer.username': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.password': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.host': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.port': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.enableTLS': {
+ type: Boolean,
+ optional: true,
+ },
+ 'mailServer.from': {
+ type: String,
+ optional: true,
+ },
+ createdAt: {
+ type: Date,
+ denyUpdate: true,
+ },
+ modifiedAt: {
+ type: Date,
+ },
+}));
+Settings.helpers({
+ mailUrl () {
+ if (!this.mailServer.host) {
+ return null;
+ }
+ const protocol = this.mailServer.enableTLS ? 'smtps://' : 'smtp://';
+ if (!this.mailServer.username && !this.mailServer.password) {
+ return `${protocol}${this.mailServer.host}:${this.mailServer.port}/`;
+ }
+ return `${protocol}${this.mailServer.username}:${this.mailServer.password}@${this.mailServer.host}:${this.mailServer.port}/`;
+ },
+});
+Settings.allow({
+ update(userId) {
+ const user = Users.findOne(userId);
+ return user && user.isAdmin;
+ },
+});
+
+Settings.before.update((userId, doc, fieldNames, modifier) => {
+ modifier.$set = modifier.$set || {};
+ modifier.$set.modifiedAt = new Date();
+});
+
+if (Meteor.isServer) {
+ Meteor.startup(() => {
+ const setting = Settings.findOne({});
+ if(!setting){
+ const now = new Date();
+ const domain = process.env.ROOT_URL.match(/\/\/(?:www\.)?(.*)?(?:\/)?/)[1];
+ const from = `Wekan <wekan@${domain}>`;
+ const defaultSetting = {disableRegistration: false, mailServer: {
+ username: '', password: '', host: '', port: '', enableTLS: false, from,
+ }, createdAt: now, modifiedAt: now};
+ Settings.insert(defaultSetting);
+ }
+ const newSetting = Settings.findOne();
+ if (!process.env.MAIL_URL && newSetting.mailUrl())
+ process.env.MAIL_URL = newSetting.mailUrl();
+ Accounts.emailTemplates.from = process.env.MAIL_FROM ? process.env.MAIL_FROM : newSetting.mailServer.from;
+ });
+ Settings.after.update((userId, doc, fieldNames) => {
+ // assign new values to mail-from & MAIL_URL in environment
+ if (_.contains(fieldNames, 'mailServer') && doc.mailServer.host) {
+ const protocol = doc.mailServer.enableTLS ? 'smtps://' : 'smtp://';
+ if (!doc.mailServer.username && !doc.mailServer.password) {
+ process.env.MAIL_URL = `${protocol}${doc.mailServer.host}:${doc.mailServer.port}/`;
+ } else {
+ process.env.MAIL_URL = `${protocol}${doc.mailServer.username}:${doc.mailServer.password}@${doc.mailServer.host}:${doc.mailServer.port}/`;
+ }
+ Accounts.emailTemplates.from = doc.mailServer.from;
+ }
+ });
+
+ function getRandomNum (min, max) {
+ const range = max - min;
+ const rand = Math.random();
+ return (min + Math.round(rand * range));
+ }
+
+ function sendInvitationEmail (_id){
+ const icode = InvitationCodes.findOne(_id);
+ const author = Users.findOne(Meteor.userId());
+ try {
+ const params = {
+ email: icode.email,
+ inviter: Users.findOne(icode.authorId).username,
+ user: icode.email.split('@')[0],
+ icode: icode.code,
+ url: FlowRouter.url('sign-up'),
+ };
+ const lang = author.getLanguage();
+ Email.send({
+ to: icode.email,
+ from: Accounts.emailTemplates.from,
+ subject: TAPi18n.__('email-invite-register-subject', params, lang),
+ text: TAPi18n.__('email-invite-register-text', params, lang),
+ });
+ } catch (e) {
+ InvitationCodes.remove(_id);
+ throw new Meteor.Error('email-fail', e.message);
+ }
+ }
+
+ Meteor.methods({
+ sendInvitation(emails, boards) {
+ check(emails, [String]);
+ check(boards, [String]);
+ const user = Users.findOne(Meteor.userId());
+ if(!user.isAdmin){
+ throw new Meteor.Error('not-allowed');
+ }
+ emails.forEach((email) => {
+ if (email && SimpleSchema.RegEx.Email.test(email)) {
+ const code = getRandomNum(100000, 999999);
+ InvitationCodes.insert({code, email, boardsToBeInvited: boards, createdAt: new Date(), authorId: Meteor.userId()}, function(err, _id){
+ if (!err && _id) {
+ sendInvitationEmail(_id);
+ } else {
+ throw new Meteor.Error('invitation-generated-fail', err.message);
+ }
+ });
+ }
+ });
+ },
+ });
+}
diff --git a/models/unsavedEdits.js b/models/unsavedEdits.js
index 25952fb5..d4f3616a 100644
--- a/models/unsavedEdits.js
+++ b/models/unsavedEdits.js
@@ -26,6 +26,9 @@ if (Meteor.isServer) {
function isAuthor(userId, doc, fieldNames = []) {
return userId === doc.userId && fieldNames.indexOf('userId') === -1;
}
+ Meteor.startup(() => {
+ UnsavedEditCollection._collection._ensureIndex({ userId: 1 });
+ });
UnsavedEditCollection.allow({
insert: isAuthor,
update: isAuthor,
diff --git a/models/users.js b/models/users.js
index 58513231..29504aa8 100644
--- a/models/users.js
+++ b/models/users.js
@@ -1,7 +1,7 @@
// Sandstorm context is detected using the METEOR_SETTINGS environment variable
// in the package definition.
const isSandstorm = Meteor.settings && Meteor.settings.public &&
- Meteor.settings.public.sandstorm;
+ Meteor.settings.public.sandstorm;
Users = Meteor.users;
Users.attachSchema(new SimpleSchema({
@@ -91,6 +91,10 @@ Users.attachSchema(new SimpleSchema({
type: [String],
optional: true,
},
+ 'profile.icode': {
+ type: String,
+ optional: true,
+ },
services: {
type: Object,
optional: true,
@@ -100,6 +104,10 @@ Users.attachSchema(new SimpleSchema({
type: Date,
optional: true,
},
+ isAdmin: {
+ type: Boolean,
+ optional: true,
+ },
}));
// Search a user in the complete server database by its name or username. This
@@ -117,6 +125,16 @@ if (Meteor.isClient) {
return board && board.hasMember(this._id);
},
+ isNotCommentOnly() {
+ const board = Boards.findOne(Session.get('currentBoard'));
+ return board && board.hasMember(this._id) && !board.hasCommentOnly(this._id);
+ },
+
+ isCommentOnly() {
+ const board = Boards.findOne(Session.get('currentBoard'));
+ return board && board.hasCommentOnly(this._id);
+ },
+
isBoardAdmin() {
const board = Boards.findOne(Session.get('currentBoard'));
return board && board.hasAdmin(this._id);
@@ -130,32 +148,32 @@ Users.helpers({
},
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) {
- const {starredBoards = []} = this.profile;
+ const { starredBoards = [] } = this.profile;
return _.contains(starredBoards, boardId);
},
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) {
- const {invitedBoards = []} = this.profile;
+ const { invitedBoards = [] } = this.profile;
return _.contains(invitedBoards, boardId);
},
hasTag(tag) {
- const {tags = []} = this.profile;
+ const { tags = [] } = this.profile;
return _.contains(tags, tag);
},
hasNotification(activityId) {
- const {notifications = []} = this.profile;
+ const { notifications = [] } = this.profile;
return _.contains(notifications, activityId);
},
@@ -165,7 +183,7 @@ Users.helpers({
},
getEmailBuffer() {
- const {emailBuffer = []} = this.profile;
+ const { emailBuffer = [] } = this.profile;
return emailBuffer;
},
@@ -290,7 +308,7 @@ Users.mutations({
},
setAvatarUrl(avatarUrl) {
- return { $set: { 'profile.avatarUrl': avatarUrl }};
+ return { $set: { 'profile.avatarUrl': avatarUrl } };
},
setShowCardsCountAt(limit) {
@@ -305,7 +323,7 @@ Meteor.methods({
if (nUsersWithUsername > 0) {
throw new Meteor.Error('username-already-taken');
} else {
- Users.update(this.userId, {$set: { username }});
+ Users.update(this.userId, { $set: { username } });
}
},
toggleSystemMessages() {
@@ -328,19 +346,19 @@ if (Meteor.isServer) {
const inviter = Meteor.user();
const board = Boards.findOne(boardId);
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');
this.unblock();
const posAt = username.indexOf('@');
let user = null;
- if (posAt>=0) {
- user = Users.findOne({emails: {$elemMatch: {address: username}}});
+ if (posAt >= 0) {
+ user = Users.findOne({ emails: { $elemMatch: { address: username } } });
} else {
user = Users.findOne(username) || Users.findOne({ username });
}
@@ -348,8 +366,9 @@ if (Meteor.isServer) {
if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf');
} else {
if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist');
-
- const email = username;
+ if (Settings.findOne().disableRegistration) throw new Meteor.Error('error-user-notCreated');
+ // Set in lowercase email before creating account
+ const email = username.toLowerCase();
username = email.substring(0, posAt);
const newUserId = Accounts.createUser({ username, email });
if (!newUserId) throw new Meteor.Error('error-user-notCreated');
@@ -377,7 +396,7 @@ if (Meteor.isServer) {
};
const lang = user.getLanguage();
Email.send({
- to: user.emails[0].address,
+ to: user.emails[0].address.toLowerCase(),
from: Accounts.emailTemplates.from,
subject: TAPi18n.__('email-invite-subject', params, lang),
text: TAPi18n.__('email-invite-text', params, lang),
@@ -385,10 +404,32 @@ if (Meteor.isServer) {
} catch (e) {
throw new Meteor.Error('email-fail', e.message);
}
-
return { username: user.username, email: user.emails[0].address };
},
});
+ Accounts.onCreateUser((options, user) => {
+ const userCount = Users.find().count();
+ if (!isSandstorm && userCount === 0) {
+ user.isAdmin = true;
+ return user;
+ }
+ const disableRegistration = Settings.findOne().disableRegistration;
+ if (!disableRegistration) {
+ return user;
+ }
+
+ if (!options || !options.profile) {
+ 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 });
+ if (!invitationCode) {
+ throw new Meteor.Error('error-invitation-code-not-exist', 'The invitation code doesn\'t exist');
+ } else {
+ user.profile = { icode: options.profile.invitationcode };
+ }
+
+ return user;
+ });
}
if (Meteor.isServer) {
@@ -404,7 +445,7 @@ if (Meteor.isServer) {
// counter.
// We need to run this code on the server only, otherwise the incrementation
// 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
// field hasn't been modificated we don't need to run this hook.
if (!_.contains(fieldNames, 'profile'))
@@ -423,7 +464,7 @@ if (Meteor.isServer) {
// direction and then in the other.
function incrementBoards(boardsIds, inc) {
boardsIds.forEach((boardId) => {
- Boards.update(boardId, {$inc: {stars: inc}});
+ Boards.update(boardId, { $inc: { stars: inc } });
});
}
incrementBoards(_.difference(oldIds, newIds), -1);
@@ -458,4 +499,87 @@ if (Meteor.isServer) {
});
});
}
+
+ Users.after.insert((userId, doc) => {
+
+ //invite user to corresponding boards
+ const disableRegistration = Settings.findOne().disableRegistration;
+ if (disableRegistration) {
+ const invitationCode = InvitationCodes.findOne({ code: doc.profile.icode, valid: true });
+ if (!invitationCode) {
+ throw new Meteor.Error('error-invitation-code-not-exist');
+ } else {
+ invitationCode.boardsToBeInvited.forEach((boardId) => {
+ const board = Boards.findOne(boardId);
+ board.addMember(doc._id);
+ });
+ if (!doc.profile) {
+ doc.profile = {};
+ }
+ doc.profile.invitedBoards = invitationCode.boardsToBeInvited;
+ 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/user', function(req, res, next) {
+ Authentication.checkLoggedIn(req.userId);
+ const data = Meteor.users.findOne({ _id: req.userId});
+ delete data.services;
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data,
+ });
+ });
+
+ JsonRoutes.add('GET', '/api/users', function (req, res, next) {
+ Authentication.checkUserId( req.userId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ 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) {
+ Authentication.checkUserId( req.userId);
+ const id = req.params.id;
+ Meteor.users.remove({ _id: id });
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: id,
+ },
+ });
+ });
+}
+