diff options
Diffstat (limited to 'models')
-rw-r--r-- | models/actions.js | 19 | ||||
-rw-r--r-- | models/activities.js | 8 | ||||
-rw-r--r-- | models/attachments.js | 7 | ||||
-rw-r--r-- | models/boards.js | 34 | ||||
-rw-r--r-- | models/cards.js | 410 | ||||
-rw-r--r-- | models/checklistItems.js | 85 | ||||
-rw-r--r-- | models/checklists.js | 23 | ||||
-rw-r--r-- | models/export.js | 74 | ||||
-rw-r--r-- | models/lists.js | 11 | ||||
-rw-r--r-- | models/rules.js | 48 | ||||
-rw-r--r-- | models/settings.js | 33 | ||||
-rw-r--r-- | models/triggers.js | 58 | ||||
-rw-r--r-- | models/users.js | 136 | ||||
-rw-r--r-- | models/wekanCreator.js | 205 |
14 files changed, 1008 insertions, 143 deletions
diff --git a/models/actions.js b/models/actions.js new file mode 100644 index 00000000..0430b044 --- /dev/null +++ b/models/actions.js @@ -0,0 +1,19 @@ +Actions = new Mongo.Collection('actions'); + +Actions.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, +}); + +Actions.helpers({ + description() { + return this.desc; + }, +}); diff --git a/models/activities.js b/models/activities.js index e49cbf0a..47e3ff1e 100644 --- a/models/activities.js +++ b/models/activities.js @@ -56,6 +56,14 @@ Activities.before.insert((userId, doc) => { doc.createdAt = new Date(); }); + +Activities.after.insert((userId, doc) => { + const activity = Activities._transform(doc); + RulesHelper.executeRules(activity); + +}); + + if (Meteor.isServer) { // For efficiency create indexes on the date of creation, and on the date of // creation in conjunction with the card or board id, as corresponding views diff --git a/models/attachments.js b/models/attachments.js index 91dd0dbc..3da067de 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -86,5 +86,12 @@ if (Meteor.isServer) { Activities.remove({ attachmentId: doc._id, }); + Activities.insert({ + userId, + type: 'card', + activityType: 'deleteAttachment', + boardId: doc.boardId, + cardId: doc.cardId, + }); }); } diff --git a/models/boards.js b/models/boards.js index 2a21d6da..52d0ca87 100644 --- a/models/boards.js +++ b/models/boards.js @@ -280,6 +280,10 @@ Boards.helpers({ return _.findWhere(this.labels, { name, color }); }, + getLabelById(labelId){ + return _.findWhere(this.labels, { _id: labelId }); + }, + labelIndex(labelId) { return _.pluck(this.labels, '_id').indexOf(labelId); }, @@ -537,11 +541,10 @@ Boards.mutations({ }; }, - setMemberPermission(memberId, isAdmin, isNoComments, isCommentOnly) { + setMemberPermission(memberId, isAdmin, isNoComments, isCommentOnly, currentUserId = Meteor.userId()) { const memberIndex = this.memberIndex(memberId); - // do not allow change permission of self - if (memberId === Meteor.userId()) { + if (memberId === currentUserId) { isAdmin = this.members[memberIndex].isAdmin; } @@ -923,4 +926,29 @@ if (Meteor.isServer) { }); } }); + + JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function (req, res) { + try { + const boardId = req.params.boardId; + const memberId = req.params.memberId; + const {isAdmin, isNoComments, isCommentOnly} = req.body; + Authentication.checkBoardAccess(req.userId, boardId); + const board = Boards.findOne({ _id: boardId }); + function isTrue(data){ + return data.toLowerCase() === 'true'; + } + board.setMemberPermission(memberId, isTrue(isAdmin), isTrue(isNoComments), isTrue(isCommentOnly), req.userId); + + JsonRoutes.sendResult(res, { + code: 200, + data: query, + }); + } + catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); } diff --git a/models/cards.js b/models/cards.js index 7a0bcd5c..25692c25 100644 --- a/models/cards.js +++ b/models/cards.js @@ -276,14 +276,22 @@ Cards.helpers({ return Cards.find({ parentId: this._id, archived: false, - }, {sort: { sort: 1 } }); + }, { + sort: { + sort: 1, + }, + }); }, allSubtasks() { return Cards.find({ parentId: this._id, archived: false, - }, {sort: { sort: 1 } }); + }, { + sort: { + sort: 1, + }, + }); }, subtasksCount() { @@ -296,7 +304,8 @@ Cards.helpers({ subtasksFinishedCount() { return Cards.find({ parentId: this._id, - archived: true}).count(); + archived: true, + }).count(); }, subtasksFinished() { @@ -328,12 +337,9 @@ Cards.helpers({ }); //search for "True Value" which is for DropDowns other then the Value (which is the id) let trueValue = customField.value; - if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) - { - for (let i = 0; i < definition.settings.dropdownItems.length; i++) - { - if (definition.settings.dropdownItems[i]._id === customField.value) - { + if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) { + for (let i = 0; i < definition.settings.dropdownItems.length; i++) { + if (definition.settings.dropdownItems[i]._id === customField.value) { trueValue = definition.settings.dropdownItems[i].name; } } @@ -358,8 +364,10 @@ Cards.helpers({ }, canBeRestored() { - const list = Lists.findOne({_id: this.listId}); - if(!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()){ + const list = Lists.findOne({ + _id: this.listId, + }); + if (!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()) { return false; } return true; @@ -424,7 +432,7 @@ Cards.helpers({ }, parentString(sep) { - return this.parentList().map(function(elem){ + return this.parentList().map(function(elem) { return elem.title; }).join(sep); }, @@ -826,19 +834,65 @@ Cards.helpers({ Cards.mutations({ applyToChildren(funct) { - Cards.find({ parentId: this._id }).forEach((card) => { + Cards.find({ + parentId: this._id, + }).forEach((card) => { funct(card); }); }, archive() { - this.applyToChildren((card) => { return card.archive(); }); - return {$set: {archived: true}}; + this.applyToChildren((card) => { + return card.archive(); + }); + return { + $set: { + archived: true, + }, + }; }, restore() { - this.applyToChildren((card) => { return card.restore(); }); - return {$set: {archived: false}}; + this.applyToChildren((card) => { + return card.restore(); + }); + return { + $set: { + archived: false, + }, + }; + }, + + setTitle(title) { + return { + $set: { + title, + }, + }; + }, + + setDescription(description) { + return { + $set: { + description, + }, + }; + }, + + setRequestedBy(requestedBy) { + return { + $set: { + requestedBy, + }, + }; + }, + + setAssignedBy(assignedBy) { + return { + $set: { + assignedBy, + }, + }; }, move(swimlaneId, listId, sortIndex) { @@ -850,15 +904,25 @@ Cards.mutations({ sort: sortIndex, }; - return {$set: mutatedFields}; + return { + $set: mutatedFields, + }; }, addLabel(labelId) { - return {$addToSet: {labelIds: labelId}}; + return { + $addToSet: { + labelIds: labelId, + }, + }; }, removeLabel(labelId) { - return {$pull: {labelIds: labelId}}; + return { + $pull: { + labelIds: labelId, + }, + }; }, toggleLabel(labelId) { @@ -869,12 +933,49 @@ Cards.mutations({ } }, + assignMember(memberId) { + return { + $addToSet: { + members: memberId, + }, + }; + }, + + unassignMember(memberId) { + return { + $pull: { + members: memberId, + }, + }; + }, + + toggleMember(memberId) { + if (this.members && this.members.indexOf(memberId) > -1) { + return this.unassignMember(memberId); + } else { + return this.assignMember(memberId); + } + }, + assignCustomField(customFieldId) { - return {$addToSet: {customFields: {_id: customFieldId, value: null}}}; + return { + $addToSet: { + customFields: { + _id: customFieldId, + value: null, + }, + }, + }; }, unassignCustomField(customFieldId) { - return {$pull: {customFields: {_id: customFieldId}}}; + return { + $pull: { + customFields: { + _id: customFieldId, + }, + }, + }; }, toggleCustomField(customFieldId) { @@ -889,7 +990,9 @@ Cards.mutations({ // todo const index = this.customFieldIndex(customFieldId); if (index > -1) { - const update = {$set: {}}; + const update = { + $set: {}, + }; update.$set[`customFields.${index}.value`] = value; return update; } @@ -899,19 +1002,119 @@ Cards.mutations({ }, setCover(coverId) { - return {$set: {coverId}}; + return { + $set: { + coverId, + }, + }; }, unsetCover() { - return {$unset: {coverId: ''}}; + return { + $unset: { + coverId: '', + }, + }; + }, + + setReceived(receivedAt) { + return { + $set: { + receivedAt, + }, + }; + }, + + unsetReceived() { + return { + $unset: { + receivedAt: '', + }, + }; + }, + + setStart(startAt) { + return { + $set: { + startAt, + }, + }; + }, + + unsetStart() { + return { + $unset: { + startAt: '', + }, + }; + }, + + setDue(dueAt) { + return { + $set: { + dueAt, + }, + }; + }, + + unsetDue() { + return { + $unset: { + dueAt: '', + }, + }; + }, + + setEnd(endAt) { + return { + $set: { + endAt, + }, + }; + }, + + unsetEnd() { + return { + $unset: { + endAt: '', + }, + }; + }, + + setOvertime(isOvertime) { + return { + $set: { + isOvertime, + }, + }; + }, + + setSpentTime(spentTime) { + return { + $set: { + spentTime, + }, + }; + }, + + unsetSpentTime() { + return { + $unset: { + spentTime: '', + isOvertime: false, + }, + }; }, setParentId(parentId) { - return {$set: {parentId}}; + return { + $set: { + parentId, + }, + }; }, }); - //FUNCTIONS FOR creation of Activities function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) { @@ -921,6 +1124,7 @@ function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) { userId, oldListId, activityType: 'moveCard', + listName: Lists.findOne(doc.listId).title, listId: doc.listId, boardId: doc.boardId, cardId: doc._id, @@ -936,6 +1140,7 @@ function cardState(userId, doc, fieldNames) { Activities.insert({ userId, activityType: 'archivedCard', + listName: Lists.findOne(doc.listId).title, boardId: doc.boardId, listId: doc.listId, cardId: doc._id, @@ -945,6 +1150,7 @@ function cardState(userId, doc, fieldNames) { userId, activityType: 'restoredCard', boardId: doc.boardId, + listName: Lists.findOne(doc.listId).title, listId: doc.listId, cardId: doc._id, }); @@ -959,10 +1165,11 @@ function cardMembers(userId, doc, fieldNames, modifier) { // Say hello to the new member if (modifier.$addToSet && modifier.$addToSet.members) { memberId = modifier.$addToSet.members; + const username = Users.findOne(memberId).username; if (!_.contains(doc.members, memberId)) { Activities.insert({ userId, - memberId, + username, activityType: 'joinMember', boardId: doc.boardId, cardId: doc._id, @@ -973,11 +1180,12 @@ function cardMembers(userId, doc, fieldNames, modifier) { // Say goodbye to the former member if (modifier.$pull && modifier.$pull.members) { memberId = modifier.$pull.members; + const username = Users.findOne(memberId).username; // Check that the former member is member of the card if (_.contains(doc.members, memberId)) { Activities.insert({ userId, - memberId, + username, activityType: 'unjoinMember', boardId: doc.boardId, cardId: doc._id, @@ -986,11 +1194,47 @@ function cardMembers(userId, doc, fieldNames, modifier) { } } +function cardLabels(userId, doc, fieldNames, modifier) { + if (!_.contains(fieldNames, 'labelIds')) + return; + let labelId; + // Say hello to the new label + if (modifier.$addToSet && modifier.$addToSet.labelIds) { + labelId = modifier.$addToSet.labelIds; + if (!_.contains(doc.labelIds, labelId)) { + const act = { + userId, + labelId, + activityType: 'addedLabel', + boardId: doc.boardId, + cardId: doc._id, + }; + Activities.insert(act); + } + } + + // Say goodbye to the label + if (modifier.$pull && modifier.$pull.labelIds) { + labelId = modifier.$pull.labelIds; + // Check that the former member is member of the card + if (_.contains(doc.labelIds, labelId)) { + Activities.insert({ + userId, + labelId, + activityType: 'removedLabel', + boardId: doc.boardId, + cardId: doc._id, + }); + } + } +} + function cardCreation(userId, doc) { Activities.insert({ userId, activityType: 'createCard', boardId: doc.boardId, + listName: Lists.findOne(doc.listId).title, listId: doc.listId, cardId: doc._id, swimlaneId: doc.swimlaneId, @@ -1015,7 +1259,6 @@ function cardRemover(userId, doc) { }); } - if (Meteor.isServer) { // Cards are often fetched within a board, so we create an index to make these // queries more efficient. @@ -1039,7 +1282,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; const oldSwimlaneId = this.previous.swimlaneId; cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId); @@ -1050,6 +1293,11 @@ if (Meteor.isServer) { cardMembers(userId, doc, fieldNames, modifier); }); + // Add a new activity if we add or remove a label to the card + Cards.before.update((userId, doc, fieldNames, modifier) => { + cardLabels(userId, doc, fieldNames, modifier); + }); + // Remove all activities associated with a card if we remove the card // Remove also card_comments / checklists / attachments Cards.after.remove((userId, doc) => { @@ -1081,13 +1329,17 @@ if (Meteor.isServer) { } //LISTS REST API if (Meteor.isServer) { - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { 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) { + data: Cards.find({ + boardId: paramBoardId, + listId: paramListId, + archived: false, + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -1097,24 +1349,31 @@ if (Meteor.isServer) { }); }); - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { 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}), + data: Cards.findOne({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }), }); }); - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res) { + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; - const check = Users.findOne({_id: req.body.authorId}); + const check = Users.findOne({ + _id: req.body.authorId, + }); const members = req.body.members || [req.body.authorId]; - if (typeof check !== 'undefined') { + if (typeof check !== 'undefined') { const id = Cards.direct.insert({ title: req.body.title, boardId: paramBoardId, @@ -1132,7 +1391,9 @@ if (Meteor.isServer) { }, }); - const card = Cards.findOne({_id:id}); + const card = Cards.findOne({ + _id: id, + }); cardCreation(req.body.authorId, card); } else { @@ -1142,7 +1403,7 @@ if (Meteor.isServer) { } }); - JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -1150,27 +1411,63 @@ if (Meteor.isServer) { if (req.body.hasOwnProperty('title')) { const newTitle = req.body.title; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {title: newTitle}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + title: newTitle, + }, + }); } if (req.body.hasOwnProperty('listId')) { const newParamListId = req.body.listId; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {listId: newParamListId}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + listId: newParamListId, + }, + }); - const card = Cards.findOne({_id: paramCardId} ); - cardMove(req.body.authorId, card, {fieldName: 'listId'}, paramListId); + const card = Cards.findOne({ + _id: paramCardId, + }); + cardMove(req.body.authorId, card, { + fieldName: 'listId', + }, paramListId); } if (req.body.hasOwnProperty('description')) { const newDescription = req.body.description; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {description: newDescription}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + description: newDescription, + }, + }); } if (req.body.hasOwnProperty('labelIds')) { const newlabelIds = req.body.labelIds; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {labelIds: newlabelIds}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + labelIds: newlabelIds, + }, + }); } if (req.body.hasOwnProperty('requestedBy')) { const newrequestedBy = req.body.requestedBy; @@ -1225,15 +1522,20 @@ if (Meteor.isServer) { }); }); - - JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; - Cards.direct.remove({_id: paramCardId, listId: paramListId, boardId: paramBoardId}); - const card = Cards.find({_id: paramCardId} ); + Cards.direct.remove({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + }); + const card = Cards.find({ + _id: paramCardId, + }); cardRemover(req.body.authorId, card); JsonRoutes.sendResult(res, { code: 200, diff --git a/models/checklistItems.js b/models/checklistItems.js index e075eda2..c85c0260 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -44,6 +44,12 @@ ChecklistItems.mutations({ setTitle(title) { return { $set: { title } }; }, + check(){ + return { $set: { isFinished: true } }; + }, + uncheck(){ + return { $set: { isFinished: false } }; + }, toggleItem() { return { $set: { isFinished: !this.isFinished } }; }, @@ -70,21 +76,100 @@ function itemCreation(userId, doc) { boardId, checklistId: doc.checklistId, checklistItemId: doc._id, + checklistItemName:doc.title, }); } function itemRemover(userId, doc) { + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + Activities.insert({ + userId, + activityType: 'removedChecklistItem', + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistItemId: doc._id, + checklistItemName:doc.title, + }); Activities.remove({ checklistItemId: doc._id, }); } +function publishCheckActivity(userId, doc){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + let activityType; + if(doc.isFinished){ + activityType = 'checkedItem'; + }else{ + activityType = 'uncheckedItem'; + } + const act = { + userId, + activityType, + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistItemId: doc._id, + checklistItemName:doc.title, + }; + Activities.insert(act); +} + +function publishChekListCompleted(userId, doc){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + const checklistId = doc.checklistId; + const checkList = Checklists.findOne({_id:checklistId}); + if(checkList.isFinished()){ + const act = { + userId, + activityType: 'checklistCompleted', + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistName: checkList.title, + }; + Activities.insert(act); + } +} + +function publishChekListUncompleted(userId, doc){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + const checklistId = doc.checklistId; + const checkList = Checklists.findOne({_id:checklistId}); + if(checkList.isFinished()){ + const act = { + userId, + activityType: 'checklistUncompleted', + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistName: checkList.title, + }; + Activities.insert(act); + } +} + // Activities if (Meteor.isServer) { Meteor.startup(() => { ChecklistItems._collection._ensureIndex({ checklistId: 1 }); }); + ChecklistItems.after.update((userId, doc, fieldNames) => { + publishCheckActivity(userId, doc); + publishChekListCompleted(userId, doc, fieldNames); + }); + + ChecklistItems.before.update((userId, doc, fieldNames) => { + publishChekListUncompleted(userId, doc, fieldNames); + }); + + ChecklistItems.after.insert((userId, doc) => { itemCreation(userId, doc); }); diff --git a/models/checklists.js b/models/checklists.js index c58453ef..425a10b2 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -47,6 +47,18 @@ Checklists.helpers({ isFinished() { return 0 !== this.itemCount() && this.itemCount() === this.finishedCount(); }, + checkAllItems(){ + const checkItems = ChecklistItems.find({checklistId: this._id}); + checkItems.forEach(function(item){ + item.check(); + }); + }, + uncheckAllItems(){ + const checkItems = ChecklistItems.find({checklistId: this._id}); + checkItems.forEach(function(item){ + item.uncheck(); + }); + }, itemIndex(itemId) { const items = self.findOne({_id : this._id}).items; return _.pluck(items, '_id').indexOf(itemId); @@ -91,6 +103,7 @@ if (Meteor.isServer) { cardId: doc.cardId, boardId: Cards.findOne(doc.cardId).boardId, checklistId: doc._id, + checklistName:doc.title, }); }); @@ -101,6 +114,16 @@ if (Meteor.isServer) { Activities.remove(activity._id); }); } + Activities.insert({ + userId, + activityType: 'removeChecklist', + cardId: doc.cardId, + boardId: Cards.findOne(doc.cardId).boardId, + checklistId: doc._id, + checklistName:doc.title, + }); + + }); } diff --git a/models/export.js b/models/export.js index 6c0b43fd..0911a631 100644 --- a/models/export.js +++ b/models/export.js @@ -14,7 +14,7 @@ if (Meteor.isServer) { * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ * for detailed explanations */ - JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) { + JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { const boardId = req.params.boardId; let user = null; // todo XXX for real API, first look for token in Authentication: header @@ -28,8 +28,11 @@ if (Meteor.isServer) { } const exporter = new Exporter(boardId); - if(exporter.canExport(user)) { - JsonRoutes.sendResult(res, { code: 200, data: exporter.build() }); + if (exporter.canExport(user)) { + JsonRoutes.sendResult(res, { + code: 200, + data: 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. @@ -47,24 +50,49 @@ class Exporter { const byBoard = { boardId: this._boardId }; const byBoardNoLinked = { boardId: this._boardId, linkedId: '' }; // 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(byBoardNoLinked, noBoardId).fetch(); result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); result.customFields = CustomFields.find(byBoard, noBoardId).fetch(); result.comments = CardComments.find(byBoard, noBoardId).fetch(); result.activities = Activities.find(byBoard, noBoardId).fetch(); + result.rules = Rules.find(byBoard, noBoardId).fetch(); result.checklists = []; result.checklistItems = []; result.subtaskItems = []; + result.triggers = []; + result.actions = []; result.cards.forEach((card) => { - result.checklists.push(...Checklists.find({ cardId: card._id }).fetch()); - result.checklistItems.push(...ChecklistItems.find({ cardId: card._id }).fetch()); - result.subtaskItems.push(...Cards.find({ parentid: card._id }).fetch()); + result.checklists.push(...Checklists.find({ + cardId: card._id, + }).fetch()); + result.checklistItems.push(...ChecklistItems.find({ + cardId: card._id, + }).fetch()); + result.subtaskItems.push(...Cards.find({ + parentid: card._id, + }).fetch()); + }); + result.rules.forEach((rule) => { + result.triggers.push(...Triggers.find({ + _id: rule.triggerId, + }, noBoardId).fetch()); + result.actions.push(...Actions.find({ + _id: rule.actionId, + }, noBoardId).fetch()); }); // [Old] for attachments we only export IDs and absolute url to original doc @@ -101,18 +129,34 @@ 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; }); - result.checklists.forEach((checklist) => { users[checklist.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; + }); + result.checklists.forEach((checklist) => { + users[checklist.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 = { diff --git a/models/lists.js b/models/lists.js index 9bcb9ba1..b99fe8f5 100644 --- a/models/lists.js +++ b/models/lists.js @@ -86,6 +86,17 @@ Lists.helpers({ { sort: ['sort'] }); }, + cardsUnfiltered(swimlaneId) { + const selector = { + listId: this._id, + archived: false, + }; + if (swimlaneId) + selector.swimlaneId = swimlaneId; + return Cards.find(selector, + { sort: ['sort'] }); + }, + allCards() { return Cards.find({ listId: this._id }); }, diff --git a/models/rules.js b/models/rules.js new file mode 100644 index 00000000..7d971980 --- /dev/null +++ b/models/rules.js @@ -0,0 +1,48 @@ +Rules = new Mongo.Collection('rules'); + +Rules.attachSchema(new SimpleSchema({ + title: { + type: String, + optional: false, + }, + triggerId: { + type: String, + optional: false, + }, + actionId: { + type: String, + optional: false, + }, + boardId: { + type: String, + optional: false, + }, +})); + +Rules.mutations({ + rename(description) { + return { $set: { description } }; + }, +}); + +Rules.helpers({ + getAction(){ + return Actions.findOne({_id:this.actionId}); + }, + getTrigger(){ + return Triggers.findOne({_id:this.triggerId}); + }, +}); + + +Rules.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, +}); diff --git a/models/settings.js b/models/settings.js index 5d01f2ea..2f82e52f 100644 --- a/models/settings.js +++ b/models/settings.js @@ -129,6 +129,18 @@ if (Meteor.isServer) { } } + function isLdapEnabled() { + return process.env.LDAP_ENABLE === 'true'; + } + + function isOauth2Enabled() { + return process.env.OAUTH2_ENABLED === 'true'; + } + + function isCasEnabled() { + return process.env.CAS_ENABLED === 'true'; + } + Meteor.methods({ sendInvitation(emails, boards) { check(emails, [String]); @@ -198,5 +210,26 @@ if (Meteor.isServer) { withUserName: process.env.MATOMO_WITH_USERNAME || false, }; }, + + _isLdapEnabled() { + return isLdapEnabled(); + }, + + _isOauth2Enabled() { + return isOauth2Enabled(); + }, + + _isCasEnabled() { + return isCasEnabled(); + }, + + // Gets all connection methods to use it in the Template + getAuthenticationsEnabled() { + return { + ldap: isLdapEnabled(), + oauth2: isOauth2Enabled(), + cas: isCasEnabled(), + }; + }, }); } diff --git a/models/triggers.js b/models/triggers.js new file mode 100644 index 00000000..15982b6e --- /dev/null +++ b/models/triggers.js @@ -0,0 +1,58 @@ +Triggers = new Mongo.Collection('triggers'); + +Triggers.mutations({ + rename(description) { + return { + $set: { + description, + }, + }; + }, +}); + +Triggers.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, +}); + +Triggers.helpers({ + + description() { + return this.desc; + }, + + getRule() { + return Rules.findOne({ + triggerId: this._id, + }); + }, + + fromList() { + return Lists.findOne(this.fromId); + }, + + toList() { + return Lists.findOne(this.toId); + }, + + findList(title) { + return Lists.findOne({ + title, + }); + }, + + labels() { + const boardLabels = this.board().labels; + const cardLabels = _.filter(boardLabels, (label) => { + return _.contains(this.labelIds, label._id); + }); + return cardLabels; + }, +}); diff --git a/models/users.js b/models/users.js index dc0ab608..4f2184e4 100644 --- a/models/users.js +++ b/models/users.js @@ -123,6 +123,11 @@ Users.attachSchema(new SimpleSchema({ type: Boolean, optional: true, }, + 'authenticationMethod': { + type: String, + optional: false, + defaultValue: 'password', + }, })); Users.allow({ @@ -484,29 +489,30 @@ if (Meteor.isServer) { return user; } - // if (user.services.oidc) { - // const email = user.services.oidc.email.toLowerCase(); - // - // user.username = user.services.oidc.username; - // user.emails = [{ address: email, verified: true }]; - // const initials = user.services.oidc.fullname.match(/\b[a-zA-Z]/g).join('').toUpperCase(); - // user.profile = { initials, fullname: user.services.oidc.fullname }; - // - // // see if any existing user has this email address or username, otherwise create new - // const existingUser = Meteor.users.findOne({$or: [{'emails.address': email}, {'username':user.username}]}); - // if (!existingUser) - // return user; - // - // // copy across new service info - // const service = _.keys(user.services)[0]; - // existingUser.services[service] = user.services[service]; - // existingUser.emails = user.emails; - // existingUser.username = user.username; - // existingUser.profile = user.profile; - // - // Meteor.users.remove({_id: existingUser._id}); // remove existing record - // return existingUser; - // } + if (user.services.oidc) { + const email = user.services.oidc.email.toLowerCase(); + user.username = user.services.oidc.username; + user.emails = [{ address: email, verified: true }]; + const initials = user.services.oidc.fullname.match(/\b[a-zA-Z]/g).join('').toUpperCase(); + user.profile = { initials, fullname: user.services.oidc.fullname }; + user.authenticationMethod = 'oauth2'; + + // see if any existing user has this email address or username, otherwise create new + const existingUser = Meteor.users.findOne({$or: [{'emails.address': email}, {'username':user.username}]}); + if (!existingUser) + return user; + + // copy across new service info + const service = _.keys(user.services)[0]; + existingUser.services[service] = user.services[service]; + existingUser.emails = user.emails; + existingUser.username = user.username; + existingUser.profile = user.profile; + existingUser.authenticationMethod = user.authenticationMethod; + + Meteor.users.remove({_id: existingUser._id}); // remove existing record + return existingUser; + } if (options.from === 'admin') { user.createdThroughApi = true; @@ -514,7 +520,10 @@ if (Meteor.isServer) { } const disableRegistration = Settings.findOne().disableRegistration; - if (!disableRegistration) { + // If ldap, bypass the inviation code if the self registration isn't allowed. + // TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type + if (options.ldap || !disableRegistration) { + user.authenticationMethod = 'ldap'; return user; } @@ -632,7 +641,9 @@ if (Meteor.isServer) { //invite user to corresponding boards const disableRegistration = Settings.findOne().disableRegistration; - if (disableRegistration) { + // If ldap, bypass the inviation code if the self registration isn't allowed. + // TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type + if (doc.authenticationMethod !== 'ldap' && disableRegistration) { const invitationCode = InvitationCodes.findOne({code: doc.profile.icode, valid: true}); if (!invitationCode) { throw new Meteor.Error('error-invitation-code-not-exist'); @@ -762,6 +773,81 @@ if (Meteor.isServer) { } }); + JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/add', function (req, res) { + try { + Authentication.checkUserId(req.userId); + const userId = req.params.userId; + const boardId = req.params.boardId; + const action = req.body.action; + const {isAdmin, isNoComments, isCommentOnly} = req.body; + let data = Meteor.users.findOne({ _id: userId }); + if (data !== undefined) { + if (action === 'add') { + data = Boards.find({ + _id: boardId, + }).map(function(board) { + if (!board.hasMember(userId)) { + board.addMember(userId); + function isTrue(data){ + return data.toLowerCase() === 'true'; + } + board.setMemberPermission(userId, isTrue(isAdmin), isTrue(isNoComments), isTrue(isCommentOnly), userId); + } + return { + _id: board._id, + title: board.title, + }; + }); + } + } + JsonRoutes.sendResult(res, { + code: 200, + data: query, + }); + } + catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); + + JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/remove', function (req, res) { + try { + Authentication.checkUserId(req.userId); + const userId = req.params.userId; + const boardId = req.params.boardId; + const action = req.body.action; + let data = Meteor.users.findOne({ _id: userId }); + if (data !== undefined) { + if (action === 'remove') { + data = Boards.find({ + _id: boardId, + }).map(function(board) { + if (board.hasMember(userId)) { + board.removeMember(userId); + } + return { + _id: board._id, + title: board.title, + }; + }); + } + } + JsonRoutes.sendResult(res, { + code: 200, + data: query, + }); + } + catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); + JsonRoutes.add('POST', '/api/users/', function (req, res) { try { Authentication.checkUserId(req.userId); diff --git a/models/wekanCreator.js b/models/wekanCreator.js index d144821f..59d0cfd5 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,4 +1,4 @@ -const DateString = Match.Where(function (dateAsString) { +const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); return moment(dateAsString, moment.ISO_8601).isValid(); }); @@ -42,6 +42,10 @@ export class WekanCreator { this.comments = {}; // the members, indexed by Wekan member id => Wekan user ID this.members = data.membersMapping ? data.membersMapping : {}; + // Map of triggers Wekan ID => Wekan ID + this.triggers = {}; + // Map of actions Wekan ID => Wekan ID + this.actions = {}; // maps a wekanCardId to an array of wekanAttachments this.attachments = {}; @@ -57,10 +61,10 @@ export class WekanCreator { * @param {String} dateString a properly formatted Date */ _now(dateString) { - if(dateString) { + if (dateString) { return new Date(dateString); } - if(!this._nowDate) { + if (!this._nowDate) { this._nowDate = new Date(); } return this._nowDate; @@ -72,9 +76,9 @@ export class WekanCreator { * Otherwise return current logged user. * @param wekanUserId * @private - */ + */ _user(wekanUserId) { - if(wekanUserId && this.members[wekanUserId]) { + if (wekanUserId && this.members[wekanUserId]) { return this.members[wekanUserId]; } return Meteor.userId(); @@ -96,7 +100,7 @@ export class WekanCreator { // allowed values (is it worth the maintenance?) color: String, permission: Match.Where((value) => { - return ['private', 'public'].indexOf(value)>= 0; + return ['private', 'public'].indexOf(value) >= 0; }), })); } @@ -147,6 +151,30 @@ export class WekanCreator { })]); } + checkRules(wekanRules) { + check(wekanRules, [Match.ObjectIncluding({ + triggerId: String, + actionId: String, + title: String, + })]); + } + + checkTriggers(wekanTriggers) { + // XXX More check based on trigger type + check(wekanTriggers, [Match.ObjectIncluding({ + activityType: String, + desc: String, + })]); + } + + checkActions(wekanActions) { + // XXX More check based on action type + check(wekanActions, [Match.ObjectIncluding({ + actionType: String, + desc: String, + })]); + } + // You must call parseActions before calling this one. createBoardAndLabels(boardToImport) { const boardToCreate = { @@ -172,12 +200,12 @@ export class WekanCreator { title: boardToImport.title, }; // now add other members - if(boardToImport.members) { + if (boardToImport.members) { boardToImport.members.forEach((wekanMember) => { // do we already have it in our list? - if(!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId)) + if (!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId)) boardToCreate.members.push({ - ... wekanMember, + ...wekanMember, userId: wekanMember.wekanId, }); }); @@ -194,7 +222,11 @@ export class WekanCreator { boardToCreate.labels.push(labelToCreate); }); const boardId = Boards.direct.insert(boardToCreate); - Boards.direct.update(boardId, {$set: {modifiedAt: this._now()}}); + Boards.direct.update(boardId, { + $set: { + modifiedAt: this._now(), + }, + }); // log activity Activities.direct.insert({ activityType: 'importBoard', @@ -246,21 +278,21 @@ export class WekanCreator { }); } // add members { - if(card.members) { + if (card.members) { const wekanMembers = []; // we can't just map, as some members may not have been mapped card.members.forEach((sourceMemberId) => { - if(this.members[sourceMemberId]) { + if (this.members[sourceMemberId]) { const wekanId = this.members[sourceMemberId]; // we may map multiple Wekan members to the same wekan user // in which case we risk adding the same user multiple times - if(!wekanMembers.find((wId) => wId === wekanId)){ + if (!wekanMembers.find((wId) => wId === wekanId)) { wekanMembers.push(wekanId); } } return true; }); - if(wekanMembers.length>0) { + if (wekanMembers.length > 0) { cardToCreate.members = wekanMembers; } } @@ -321,9 +353,9 @@ export class WekanCreator { // - the template then tries to display the url to the attachment which causes other errors // so we make it server only, and let UI catch up once it is done, forget about latency comp. const self = this; - if(Meteor.isServer) { + if (Meteor.isServer) { if (att.url) { - file.attachData(att.url, function (error) { + file.attachData(att.url, function(error) { file.boardId = boardId; file.cardId = cardId; file.userId = self._user(att.userId); @@ -331,20 +363,26 @@ export class WekanCreator { // attachments' related activities automatically file.source = 'import'; if (error) { - throw(error); + throw (error); } else { const wekanAtt = Attachments.insert(file, () => { // we do nothing }); self.attachmentIds[att._id] = wekanAtt._id; // - if(wekanCoverId === att._id) { - Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}}); + if (wekanCoverId === att._id) { + Cards.direct.update(cardId, { + $set: { + coverId: wekanAtt._id, + }, + }); } } }); } else if (att.file) { - file.attachData(new Buffer(att.file, 'base64'), {type: att.type}, (error) => { + file.attachData(new Buffer(att.file, 'base64'), { + type: att.type, + }, (error) => { file.name(att.name); file.boardId = boardId; file.cardId = cardId; @@ -353,15 +391,19 @@ export class WekanCreator { // attachments' related activities automatically file.source = 'import'; if (error) { - throw(error); + throw (error); } else { const wekanAtt = Attachments.insert(file, () => { // we do nothing }); this.attachmentIds[att._id] = wekanAtt._id; // - if(wekanCoverId === att._id) { - Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}}); + if (wekanCoverId === att._id) { + Cards.direct.update(cardId, { + $set: { + coverId: wekanAtt._id, + }, + }); } } }); @@ -404,7 +446,11 @@ export class WekanCreator { sort: list.sort ? list.sort : listIndex, }; const listId = Lists.direct.insert(listToCreate); - Lists.direct.update(listId, {$set: {'updatedAt': this._now()}}); + Lists.direct.update(listId, { + $set: { + 'updatedAt': this._now(), + }, + }); this.lists[list._id] = listId; // // log activity // Activities.direct.insert({ @@ -437,7 +483,11 @@ export class WekanCreator { sort: swimlane.sort ? swimlane.sort : swimlaneIndex, }; const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate); - Swimlanes.direct.update(swimlaneId, {$set: {'updatedAt': this._now()}}); + Swimlanes.direct.update(swimlaneId, { + $set: { + 'updatedAt': this._now(), + }, + }); this.swimlanes[swimlane._id] = swimlaneId; }); } @@ -459,6 +509,47 @@ export class WekanCreator { return result; } + createTriggers(wekanTriggers, boardId) { + wekanTriggers.forEach((trigger) => { + if (trigger.hasOwnProperty('labelId')) { + trigger.labelId = this.labels[trigger.labelId]; + } + if (trigger.hasOwnProperty('memberId')) { + trigger.memberId = this.members[trigger.memberId]; + } + trigger.boardId = boardId; + const oldId = trigger._id; + delete trigger._id; + this.triggers[oldId] = Triggers.direct.insert(trigger); + }); + } + + createActions(wekanActions, boardId) { + wekanActions.forEach((action) => { + if (action.hasOwnProperty('labelId')) { + action.labelId = this.labels[action.labelId]; + } + if (action.hasOwnProperty('memberId')) { + action.memberId = this.members[action.memberId]; + } + action.boardId = boardId; + const oldId = action._id; + delete action._id; + this.actions[oldId] = Actions.direct.insert(action); + }); + } + + createRules(wekanRules, boardId) { + wekanRules.forEach((rule) => { + // Create the rule + rule.boardId = boardId; + rule.triggerId = this.triggers[rule.triggerId]; + rule.actionId = this.actions[rule.actionId]; + delete rule._id; + Rules.direct.insert(rule); + }); + } + createChecklistItems(wekanChecklistItems) { wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => { // Create the checklistItem @@ -477,7 +568,8 @@ export class WekanCreator { parseActivities(wekanBoard) { wekanBoard.activities.forEach((activity) => { switch (activity.activityType) { - case 'addAttachment': { + case 'addAttachment': + { // We have to be cautious, because the attachment could have been removed later. // In that case Wekan still reports its addition, but removes its 'url' field. // So we test for that @@ -485,12 +577,12 @@ export class WekanCreator { return attachment._id === activity.attachmentId; })[0]; - if ( typeof wekanAttachment !== 'undefined' && wekanAttachment ) { - if(wekanAttachment.url || wekanAttachment.file) { - // we cannot actually create the Wekan attachment, because we don't yet - // have the cards to attach it to, so we store it in the instance variable. + if (typeof wekanAttachment !== 'undefined' && wekanAttachment) { + if (wekanAttachment.url || wekanAttachment.file) { + // we cannot actually create the Wekan attachment, because we don't yet + // have the cards to attach it to, so we store it in the instance variable. const wekanCardId = activity.cardId; - if(!this.attachments[wekanCardId]) { + if (!this.attachments[wekanCardId]) { this.attachments[wekanCardId] = []; } this.attachments[wekanCardId].push(wekanAttachment); @@ -498,7 +590,8 @@ export class WekanCreator { } break; } - case 'addComment': { + case 'addComment': + { const wekanComment = wekanBoard.comments.filter((comment) => { return comment._id === activity.commentId; })[0]; @@ -509,26 +602,31 @@ export class WekanCreator { this.comments[id].push(wekanComment); break; } - case 'createBoard': { + case 'createBoard': + { this.createdAt.board = activity.createdAt; break; } - case 'createCard': { + case 'createCard': + { const cardId = activity.cardId; this.createdAt.cards[cardId] = activity.createdAt; this.createdBy.cards[cardId] = activity.userId; break; } - case 'createList': { + case 'createList': + { const listId = activity.listId; this.createdAt.lists[listId] = activity.createdAt; break; } - case 'createSwimlane': { + case 'createSwimlane': + { const swimlaneId = activity.swimlaneId; this.createdAt.swimlanes[swimlaneId] = activity.createdAt; break; - }} + } + } }); } @@ -537,7 +635,8 @@ export class WekanCreator { switch (activity.activityType) { // Board related activities // TODO: addBoardMember, removeBoardMember - case 'createBoard': { + case 'createBoard': + { Activities.direct.insert({ userId: this._user(activity.userId), type: 'board', @@ -550,7 +649,8 @@ export class WekanCreator { } // List related activities // TODO: removeList, archivedList - case 'createList': { + case 'createList': + { Activities.direct.insert({ userId: this._user(activity.userId), type: 'list', @@ -563,7 +663,8 @@ export class WekanCreator { } // Card related activities // TODO: archivedCard, restoredCard, joinMember, unjoinMember - case 'createCard': { + case 'createCard': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -574,7 +675,8 @@ export class WekanCreator { }); break; } - case 'moveCard': { + case 'moveCard': + { Activities.direct.insert({ userId: this._user(activity.userId), oldListId: this.lists[activity.oldListId], @@ -587,7 +689,8 @@ export class WekanCreator { break; } // Comment related activities - case 'addComment': { + case 'addComment': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -599,7 +702,8 @@ export class WekanCreator { break; } // Attachment related activities - case 'addAttachment': { + case 'addAttachment': + { Activities.direct.insert({ userId: this._user(activity.userId), type: 'card', @@ -612,7 +716,8 @@ export class WekanCreator { break; } // Checklist related activities - case 'addChecklist': { + case 'addChecklist': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -623,7 +728,8 @@ export class WekanCreator { }); break; } - case 'addChecklistItem': { + case 'addChecklistItem': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -636,7 +742,8 @@ export class WekanCreator { createdAt: this._now(activity.createdAt), }); break; - }} + } + } }); } @@ -652,6 +759,9 @@ export class WekanCreator { this.checkSwimlanes(board.swimlanes); this.checkCards(board.cards); this.checkChecklists(board.checklists); + this.checkRules(board.rules); + this.checkActions(board.actions); + this.checkTriggers(board.triggers); this.checkChecklistItems(board.checklistItems); } catch (e) { throw new Meteor.Error('error-json-schema'); @@ -674,6 +784,9 @@ export class WekanCreator { this.createChecklists(board.checklists); this.createChecklistItems(board.checklistItems); this.importActivities(board.activities, boardId); + this.createTriggers(board.triggers, boardId); + this.createActions(board.actions, boardId); + this.createRules(board.rules, boardId); // XXX add members return boardId; } |