diff options
Diffstat (limited to 'models')
-rw-r--r-- | models/activities.js | 3 | ||||
-rw-r--r-- | models/boards.js | 77 | ||||
-rw-r--r-- | models/cards.js | 546 | ||||
-rw-r--r-- | models/export.js | 10 | ||||
-rw-r--r-- | models/lists.js | 6 | ||||
-rw-r--r-- | models/settings.js | 46 | ||||
-rw-r--r-- | models/swimlanes.js | 6 | ||||
-rw-r--r-- | models/trelloCreator.js | 2 | ||||
-rw-r--r-- | models/users.js | 55 | ||||
-rw-r--r-- | models/wekanCreator.js | 1 |
10 files changed, 673 insertions, 79 deletions
diff --git a/models/activities.js b/models/activities.js index fe24c9c4..c14760c2 100644 --- a/models/activities.js +++ b/models/activities.js @@ -128,6 +128,9 @@ if (Meteor.isServer) { params.url = card.absoluteUrl(); params.cardId = activity.cardId; } + if (activity.swimlaneId) { + params.swimlaneId = activity.swimlaneId; + } if (activity.commentId) { const comment = activity.comment(); params.comment = comment.text; diff --git a/models/boards.js b/models/boards.js index c77c9de2..641ecdb9 100644 --- a/models/boards.js +++ b/models/boards.js @@ -110,6 +110,7 @@ Boards.attachSchema(new SimpleSchema({ userId: this.userId, isAdmin: true, isActive: true, + isNoComments: false, isCommentOnly: false, }]; } @@ -124,6 +125,9 @@ Boards.attachSchema(new SimpleSchema({ 'members.$.isActive': { type: Boolean, }, + 'members.$.isNoComments': { + type: Boolean, + }, 'members.$.isCommentOnly': { type: Boolean, }, @@ -177,6 +181,28 @@ Boards.attachSchema(new SimpleSchema({ optional: true, defaultValue: 'no-parent', }, + startAt: { + type: Date, + optional: true, + }, + dueAt: { + type: Date, + optional: true, + }, + endAt: { + type: Date, + optional: true, + }, + spentTime: { + type: Number, + decimal: true, + optional: true, + }, + isOvertime: { + type: Boolean, + defaultValue: false, + optional: true, + }, })); @@ -212,6 +238,10 @@ Boards.helpers({ return this.permission === 'public'; }, + cards() { + return Cards.find({ boardId: this._id, archived: false }, { sort: { title: 1 } }); + }, + lists() { return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); }, @@ -220,10 +250,6 @@ Boards.helpers({ return Swimlanes.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); }, - cards() { - return Cards.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); - }, - hasOvertimeCards(){ const card = Cards.findOne({isOvertime: true, boardId: this._id, archived: false} ); return card !== undefined; @@ -274,6 +300,10 @@ Boards.helpers({ return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: true }); }, + hasNoComments(memberId) { + return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: false, isNoComments: true }); + }, + hasCommentOnly(memberId) { return !!_.findWhere(this.members, { userId: memberId, isActive: true, isAdmin: false, isCommentOnly: true }); }, @@ -298,22 +328,22 @@ Boards.helpers({ return _id; }, - searchCards(term) { + searchCards(term, excludeLinked) { check(term, Match.OneOf(String, null, undefined)); - let query = { boardId: this._id }; + const query = { boardId: this._id }; + if (excludeLinked) { + query.linkedId = null; + } const projection = { limit: 10, sort: { createdAt: -1 } }; if (term) { const regex = new RegExp(term, 'i'); - query = { - boardId: this._id, - $or: [ - { title: regex }, - { description: regex }, - ], - }; + query.$or = [ + { title: regex }, + { description: regex }, + ]; } return Cards.find(query, projection); @@ -376,6 +406,7 @@ Boards.helpers({ cardsInInterval(start, end) { return Cards.find({ + boardId: this._id, $or: [ { startAt: { @@ -482,6 +513,7 @@ Boards.mutations({ userId: memberId, isAdmin: false, isActive: true, + isNoComments: false, isCommentOnly: false, }, }, @@ -509,7 +541,7 @@ Boards.mutations({ }; }, - setMemberPermission(memberId, isAdmin, isCommentOnly) { + setMemberPermission(memberId, isAdmin, isNoComments, isCommentOnly) { const memberIndex = this.memberIndex(memberId); // do not allow change permission of self @@ -520,6 +552,7 @@ Boards.mutations({ return { $set: { [`members.${memberIndex}.isAdmin`]: isAdmin, + [`members.${memberIndex}.isNoComments`]: isNoComments, [`members.${memberIndex}.isCommentOnly`]: isCommentOnly, }, }; @@ -817,18 +850,24 @@ if (Meteor.isServer) { members: [ { userId: req.body.owner, - isAdmin: true, - isActive: true, - isCommentOnly: false, + isAdmin: req.body.isAdmin || true, + isActive: req.body.isActive || true, + isNoComments: req.body.isNoComments || false, + isCommentOnly: req.body.isCommentOnly || false, }, ], - permission: 'public', - color: 'belize', + permission: req.body.permission || 'private', + color: req.body.color || 'belize', + }); + const swimlaneId = Swimlanes.insert({ + title: TAPi18n.__('default'), + boardId: id, }); JsonRoutes.sendResult(res, { code: 200, data: { _id: id, + defaultSwimlaneId: swimlaneId, }, }); } diff --git a/models/cards.js b/models/cards.js index efc25a8f..2595e934 100644 --- a/models/cards.js +++ b/models/cards.js @@ -6,6 +6,8 @@ Cards = new Mongo.Collection('cards'); Cards.attachSchema(new SimpleSchema({ title: { type: String, + optional: true, + defaultValue: '', }, archived: { type: Boolean, @@ -22,6 +24,8 @@ Cards.attachSchema(new SimpleSchema({ }, listId: { type: String, + optional: true, + defaultValue: '', }, swimlaneId: { type: String, @@ -31,10 +35,14 @@ Cards.attachSchema(new SimpleSchema({ // difficult to manage and less efficient. boardId: { type: String, + optional: true, + defaultValue: '', }, coverId: { type: String, optional: true, + defaultValue: '', + }, createdAt: { type: Date, @@ -49,15 +57,19 @@ Cards.attachSchema(new SimpleSchema({ customFields: { type: [Object], optional: true, + defaultValue: [], }, 'customFields.$': { type: new SimpleSchema({ _id: { type: String, + optional: true, + defaultValue: '', }, value: { type: Match.OneOf(String, Number, Boolean, Date), optional: true, + defaultValue: '', }, }), }, @@ -70,22 +82,27 @@ Cards.attachSchema(new SimpleSchema({ description: { type: String, optional: true, + defaultValue: '', }, requestedBy: { type: String, optional: true, + defaultValue: '', }, assignedBy: { type: String, optional: true, + defaultValue: '', }, labelIds: { type: [String], optional: true, + defaultValue: [], }, members: { type: [String], optional: true, + defaultValue: [], }, receivedAt: { type: Date, @@ -107,6 +124,7 @@ Cards.attachSchema(new SimpleSchema({ type: Number, decimal: true, optional: true, + defaultValue: 0, }, isOvertime: { type: Boolean, @@ -126,6 +144,7 @@ Cards.attachSchema(new SimpleSchema({ sort: { type: Number, decimal: true, + defaultValue: '', }, subtaskSort: { type: Number, @@ -133,6 +152,15 @@ Cards.attachSchema(new SimpleSchema({ defaultValue: -1, optional: true, }, + type: { + type: String, + defaultValue: '', + }, + linkedId: { + type: String, + optional: true, + defaultValue: '', + }, })); Cards.allow({ @@ -174,37 +202,33 @@ Cards.helpers({ }, isAssigned(memberId) { - return _.contains(this.members, memberId); + return _.contains(this.getMembers(), memberId); }, activities() { - return Activities.find({ - cardId: this._id - }, { - sort: { - createdAt: -1 - } - }); + if (this.isLinkedCard()) { + return Activities.find({cardId: this.linkedId}, {sort: {createdAt: -1}}); + } else if (this.isLinkedBoard()) { + return Activities.find({boardId: this.linkedId}, {sort: {createdAt: -1}}); + } else { + return Activities.find({cardId: this._id}, {sort: {createdAt: -1}}); + } }, comments() { - return CardComments.find({ - cardId: this._id - }, { - sort: { - createdAt: -1 - } - }); + if (this.isLinkedCard()) { + return CardComments.find({cardId: this.linkedId}, {sort: {createdAt: -1}}); + } else { + return CardComments.find({cardId: this._id}, {sort: {createdAt: -1}}); + } }, attachments() { - return Attachments.find({ - cardId: this._id - }, { - sort: { - uploadedAt: -1 - } - }); + if (this.isLinkedCard()) { + return Attachments.find({cardId: this.linkedId}, {sort: {uploadedAt: -1}}); + } else { + return Attachments.find({cardId: this._id}, {sort: {uploadedAt: -1}}); + } }, cover() { @@ -215,13 +239,11 @@ Cards.helpers({ }, checklists() { - return Checklists.find({ - cardId: this._id - }, { - sort: { - sort: 1 - } - }); + if (this.isLinkedCard()) { + return Checklists.find({cardId: this.linkedId}, {sort: { sort: 1 } }); + } else { + return Checklists.find({cardId: this._id}, {sort: { sort: 1 } }); + } }, checklistItemCount() { @@ -418,6 +440,396 @@ Cards.helpers({ isTopLevel() { return this.parentId === ''; }, + + isLinkedCard() { + return this.type === 'cardType-linkedCard'; + }, + + isLinkedBoard() { + return this.type === 'cardType-linkedBoard'; + }, + + isLinked() { + return this.isLinkedCard() || this.isLinkedBoard(); + }, + + setDescription(description) { + if (this.isLinkedCard()) { + return Cards.update({_id: this.linkedId}, {$set: {description}}); + } else if (this.isLinkedBoard()) { + return Boards.update({_id: this.linkedId}, {$set: {description}}); + } else { + return Cards.update( + {_id: this._id}, + {$set: {description}} + ); + } + }, + + getDescription() { + if (this.isLinkedCard()) { + const card = Cards.findOne({_id: this.linkedId}); + if (card && card.description) + return card.description; + else + return null; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({_id: this.linkedId}); + if (board && board.description) + return board.description; + else + return null; + } else if (this.description) { + return this.description; + } else { + return null; + } + }, + + getMembers() { + if (this.isLinkedCard()) { + const card = Cards.findOne({_id: this.linkedId}); + return card.members; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({_id: this.linkedId}); + return board.activeMembers().map((member) => { + return member.userId; + }); + } else { + return this.members; + } + }, + + assignMember(memberId) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + { $addToSet: { members: memberId }} + ); + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({_id: this.linkedId}); + return board.addMember(memberId); + } else { + return Cards.update( + { _id: this._id }, + { $addToSet: { members: memberId}} + ); + } + }, + + unassignMember(memberId) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + { $pull: { members: memberId }} + ); + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({_id: this.linkedId}); + return board.removeMember(memberId); + } else { + return Cards.update( + { _id: this._id }, + { $pull: { members: memberId}} + ); + } + }, + + toggleMember(memberId) { + if (this.getMembers() && this.getMembers().indexOf(memberId) > -1) { + return this.unassignMember(memberId); + } else { + return this.assignMember(memberId); + } + }, + + getReceived() { + if (this.isLinkedCard()) { + const card = Cards.findOne({_id: this.linkedId}); + return card.receivedAt; + } else { + return this.receivedAt; + } + }, + + setReceived(receivedAt) { + if (this.isLinkedCard()) { + return Cards.update( + {_id: this.linkedId}, + {$set: {receivedAt}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {receivedAt}} + ); + } + }, + + getStart() { + if (this.isLinkedCard()) { + const card = Cards.findOne({_id: this.linkedId}); + return card.startAt; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({_id: this.linkedId}); + return board.startAt; + } else { + return this.startAt; + } + }, + + setStart(startAt) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {startAt}} + ); + } else if (this.isLinkedBoard()) { + return Boards.update( + {_id: this.linkedId}, + {$set: {startAt}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {startAt}} + ); + } + }, + + getDue() { + if (this.isLinkedCard()) { + const card = Cards.findOne({_id: this.linkedId}); + return card.dueAt; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({_id: this.linkedId}); + return board.dueAt; + } else { + return this.dueAt; + } + }, + + setDue(dueAt) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {dueAt}} + ); + } else if (this.isLinkedBoard()) { + return Boards.update( + {_id: this.linkedId}, + {$set: {dueAt}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {dueAt}} + ); + } + }, + + getEnd() { + if (this.isLinkedCard()) { + const card = Cards.findOne({_id: this.linkedId}); + return card.endAt; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({_id: this.linkedId}); + return board.endAt; + } else { + return this.endAt; + } + }, + + setEnd(endAt) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {endAt}} + ); + } else if (this.isLinkedBoard()) { + return Boards.update( + {_id: this.linkedId}, + {$set: {endAt}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {endAt}} + ); + } + }, + + getIsOvertime() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + return card.isOvertime; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId}); + return board.isOvertime; + } else { + return this.isOvertime; + } + }, + + setIsOvertime(isOvertime) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {isOvertime}} + ); + } else if (this.isLinkedBoard()) { + return Boards.update( + {_id: this.linkedId}, + {$set: {isOvertime}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {isOvertime}} + ); + } + }, + + getSpentTime() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + return card.spentTime; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId}); + return board.spentTime; + } else { + return this.spentTime; + } + }, + + setSpentTime(spentTime) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {spentTime}} + ); + } else if (this.isLinkedBoard()) { + return Boards.update( + {_id: this.linkedId}, + {$set: {spentTime}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {spentTime}} + ); + } + }, + + getId() { + if (this.isLinked()) { + return this.linkedId; + } else { + return this._id; + } + }, + + getTitle() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + return card.title; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId}); + return board.title; + } else { + return this.title; + } + }, + + getBoardTitle() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + const board = Boards.findOne({ _id: card.boardId }); + return board.title; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId}); + return board.title; + } else { + const board = Boards.findOne({ _id: this.boardId }); + return board.title; + } + }, + + setTitle(title) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {title}} + ); + } else if (this.isLinkedBoard()) { + return Boards.update( + {_id: this.linkedId}, + {$set: {title}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {title}} + ); + } + }, + + getArchived() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + return card.archived; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId}); + return board.archived; + } else { + return this.archived; + } + }, + + setRequestedBy(requestedBy) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {requestedBy}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {requestedBy}} + ); + } + }, + + getRequestedBy() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + return card.requestedBy; + } else { + return this.requestedBy; + } + }, + + setAssignedBy(assignedBy) { + if (this.isLinkedCard()) { + return Cards.update( + { _id: this.linkedId }, + {$set: {assignedBy}} + ); + } else { + return Cards.update( + {_id: this._id}, + {$set: {assignedBy}} + ); + } + }, + + getAssignedBy() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + return card.assignedBy; + } else { + return this.assignedBy; + } + }, }); Cards.mutations({ @@ -521,6 +933,8 @@ Cards.mutations({ } }, +<<<<<<< HEAD +======= assignMember(memberId) { return { $addToSet: { @@ -545,6 +959,7 @@ Cards.mutations({ } }, +>>>>>>> 36c04edb9f7cf16fb450b76598c4957968d4674b assignCustomField(customFieldId) { return { $addToSet: { @@ -605,6 +1020,8 @@ Cards.mutations({ }; }, +<<<<<<< HEAD +======= setReceived(receivedAt) { return { $set: { @@ -694,6 +1111,7 @@ Cards.mutations({ }; }, +>>>>>>> 36c04edb9f7cf16fb450b76598c4957968d4674b setParentId(parentId) { return { $set: { @@ -701,13 +1119,13 @@ Cards.mutations({ } }; }, - }); //FUNCTIONS FOR creation of Activities -function cardMove(userId, doc, fieldNames, oldListId) { - if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) { +function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) { + if ((_.contains(fieldNames, 'listId') && doc.listId !== oldListId) || + (_.contains(fieldNames, 'swimlaneId') && doc.swimlaneId !== oldSwimlaneId)){ Activities.insert({ userId, oldListId, @@ -716,6 +1134,8 @@ function cardMove(userId, doc, fieldNames, oldListId) { listId: doc.listId, boardId: doc.boardId, cardId: doc._id, + swimlaneId: doc.swimlaneId, + oldSwimlaneId, }); } } @@ -821,6 +1241,7 @@ function cardCreation(userId, doc) { listName: Lists.findOne(doc.listId).title, listId: doc.listId, cardId: doc._id, + swimlaneId: doc.swimlaneId, }); } @@ -846,10 +1267,13 @@ 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, - createdAt: -1 - }); + Cards._collection._ensureIndex({boardId: 1, createdAt: -1}); + // https://github.com/wekan/wekan/issues/1863 + // Swimlane added a new field in the cards collection of mongodb named parentId. + // When loading a board, mongodb is searching for every cards, the id of the parent (in the swinglanes collection). + // With a huge database, this result in a very slow app and high CPU on the mongodb side. + // To correct it, add Index to parentId: + Cards._collection._ensureIndex({parentId: 1}); }); Cards.after.insert((userId, doc) => { @@ -864,7 +1288,8 @@ if (Meteor.isServer) { //New activity for card moves Cards.after.update(function(userId, doc, fieldNames) { const oldListId = this.previous.listId; - cardMove(userId, doc, fieldNames, oldListId); + const oldSwimlaneId = this.previous.swimlaneId; + cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId); }); // Add a new activity if we add or remove a member to the card @@ -1025,6 +1450,51 @@ if (Meteor.isServer) { } }); } + if (req.body.hasOwnProperty('requestedBy')) { + const newrequestedBy = req.body.requestedBy; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {requestedBy: newrequestedBy}}); + } + if (req.body.hasOwnProperty('assignedBy')) { + const newassignedBy = req.body.assignedBy; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {assignedBy: newassignedBy}}); + } + if (req.body.hasOwnProperty('receivedAt')) { + const newreceivedAt = req.body.receivedAt; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {receivedAt: newreceivedAt}}); + } + if (req.body.hasOwnProperty('startAt')) { + const newstartAt = req.body.startAt; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {startAt: newstartAt}}); + } + if (req.body.hasOwnProperty('dueAt')) { + const newdueAt = req.body.dueAt; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {dueAt: newdueAt}}); + } + if (req.body.hasOwnProperty('endAt')) { + const newendAt = req.body.endAt; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {endAt: newendAt}}); + } + if (req.body.hasOwnProperty('spentTime')) { + const newspentTime = req.body.spentTime; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {spentTime: newspentTime}}); + } + if (req.body.hasOwnProperty('isOverTime')) { + const newisOverTime = req.body.isOverTime; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {isOverTime: newisOverTime}}); + } + if (req.body.hasOwnProperty('customFields')) { + const newcustomFields = req.body.customFields; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {customFields: newcustomFields}}); + } JsonRoutes.sendResult(res, { code: 200, data: { @@ -1056,4 +1526,4 @@ if (Meteor.isServer) { }); }); -}
\ No newline at end of file +} diff --git a/models/export.js b/models/export.js index f4ea6789..c65ebf52 100644 --- a/models/export.js +++ b/models/export.js @@ -47,9 +47,8 @@ class Exporter { } build() { - const byBoard = { - boardId: this._boardId - }; + const byBoard = { boardId: this._boardId }; + const byBoardNoLinked = { boardId: this._boardId, linkedId: '' }; // we do not want to retrieve boardId in related elements const noBoardId = { fields: { @@ -65,8 +64,9 @@ class Exporter { } })); result.lists = Lists.find(byBoard, noBoardId).fetch(); - result.cards = Cards.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(); @@ -182,4 +182,4 @@ class Exporter { const board = Boards.findOne(this._boardId); return board && board.isVisibleBy(user); } -}
\ No newline at end of file +} diff --git a/models/lists.js b/models/lists.js index ceda9ad1..bf5aae3c 100644 --- a/models/lists.js +++ b/models/lists.js @@ -63,13 +63,13 @@ Lists.attachSchema(new SimpleSchema({ Lists.allow({ insert(userId, doc) { - return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId)); }, update(userId, doc) { - return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId)); }, remove(userId, doc) { - return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId)); }, fetch: ['boardId'], }); diff --git a/models/settings.js b/models/settings.js index 34f693d9..3b9b4eae 100644 --- a/models/settings.js +++ b/models/settings.js @@ -96,6 +96,14 @@ if (Meteor.isServer) { return (min + Math.round(rand * range)); } + function getEnvVar(name){ + const value = process.env[name]; + if (value){ + return value; + } + throw new Meteor.Error(['var-not-exist', `The environment variable ${name} does not exist`]); + } + function sendInvitationEmail (_id){ const icode = InvitationCodes.findOne(_id); const author = Users.findOne(Meteor.userId()); @@ -124,20 +132,33 @@ if (Meteor.isServer) { 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); - } - }); + // Checks if the email is already link to an account. + const userExist = Users.findOne({email}); + if (userExist){ + throw new Meteor.Error('user-exist', `The user with the email ${email} has already an account.`); + } + // Checks if the email is already link to an invitation. + const invitation = InvitationCodes.findOne({email}); + if (invitation){ + InvitationCodes.update(invitation, {$set : {boardsToBeInvited: boards}}); + sendInvitationEmail(invitation._id); + }else { + 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); + } + }); + } } }); }, @@ -167,5 +188,14 @@ if (Meteor.isServer) { email: user.emails[0].address, }; }, + + getMatomoConf(){ + return { + address: getEnvVar('MATOMO_ADDRESS'), + siteId: getEnvVar('MATOMO_SITE_ID'), + doNotTrack: process.env.MATOMO_DO_NOT_TRACK || false, + withUserName: process.env.MATOMO_WITH_USERNAME || false, + }; + }, }); } diff --git a/models/swimlanes.js b/models/swimlanes.js index 72ef3f36..3559bcd2 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -46,13 +46,13 @@ Swimlanes.attachSchema(new SimpleSchema({ Swimlanes.allow({ insert(userId, doc) { - return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId)); }, update(userId, doc) { - return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId)); }, remove(userId, doc) { - return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId)); }, fetch: ['boardId'], }); diff --git a/models/trelloCreator.js b/models/trelloCreator.js index 30f0bc2b..b5a255cc 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -150,6 +150,7 @@ export class TrelloCreator { userId: Meteor.userId(), isAdmin: true, isActive: true, + isNoComments: false, isCommentOnly: false, swimlaneId: false, }], @@ -177,6 +178,7 @@ export class TrelloCreator { userId: wekanId, isAdmin: this.getAdmin(trelloMembership.memberType), isActive: true, + isNoComments: false, isCommentOnly: false, swimlaneId: false, }); diff --git a/models/users.js b/models/users.js index 5a7fbbe5..01673e4f 100644 --- a/models/users.js +++ b/models/users.js @@ -151,6 +151,16 @@ if (Meteor.isClient) { return board && board.hasMember(this._id); }, + isNotNoComments() { + const board = Boards.findOne(Session.get('currentBoard')); + return board && board.hasMember(this._id) && !board.hasNoComments(this._id); + }, + + isNoComments() { + const board = Boards.findOne(Session.get('currentBoard')); + return board && board.hasNoComments(this._id); + }, + isNotCommentOnly() { const board = Boards.findOne(Session.get('currentBoard')); return board && board.hasMember(this._id) && !board.hasCommentOnly(this._id); @@ -478,6 +488,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 (options.from === 'admin') { user.createdThroughApi = true; return user; @@ -501,9 +535,13 @@ if (Meteor.isServer) { } else { user.profile = {icode: options.profile.invitationcode}; user.profile.boardView = 'board-view-lists'; - } - return user; + // Deletes the invitation code after the user was created successfully. + setTimeout(Meteor.bindEnvironment(() => { + InvitationCodes.remove({'_id': invitationCode._id}); + }), 200); + return user; + } }); } @@ -618,9 +656,20 @@ if (Meteor.isServer) { }); } - // USERS REST API if (Meteor.isServer) { + // Middleware which checks that API is enabled. + JsonRoutes.Middleware.use(function (req, res, next) { + const api = req.url.search('api'); + if (api === 1 && process.env.WITH_API === 'true' || api === -1){ + return next(); + } + else { + res.writeHead(301, {Location: '/'}); + return res.end(); + } + }); + JsonRoutes.add('GET', '/api/user', function(req, res) { try { Authentication.checkLoggedIn(req.userId); diff --git a/models/wekanCreator.js b/models/wekanCreator.js index 33a2767a..6841a6ae 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -188,6 +188,7 @@ export class WekanCreator { wekanId: Meteor.userId(), isActive: true, isAdmin: true, + isNoComments: false, isCommentOnly: false, swimlaneId: false, }], |