From e0ca960a35cf006880019ba28fc82aa30f289a71 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sat, 22 Feb 2020 02:49:14 +0200 Subject: Create New User in Admin Panel. Works, but does not save fullname yet, so currently it's needed to edit add fullname later. Thanks to xet7 ! Related #802 --- models/users.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'models') diff --git a/models/users.js b/models/users.js index 7e23835c..00076253 100644 --- a/models/users.js +++ b/models/users.js @@ -620,6 +620,34 @@ Users.mutations({ }); Meteor.methods({ + setCreateUser(fullname, username, password, isAdmin, isActive, email) { + if (Meteor.user().isAdmin) { + check(fullname, String); + check(username, String); + check(password, String); + check(isAdmin, String); + check(isActive, String); + check(email, String); + + const nUsersWithUsername = Users.find({ username }).count(); + const nUsersWithEmail = Users.find({ email }).count(); + if (nUsersWithUsername > 0) { + throw new Meteor.Error('username-already-taken'); + } else if (nUsersWithEmail > 0) { + throw new Meteor.Error('email-already-taken'); + } else { + Accounts.createUser({ + fullname, + username, + password, + isAdmin, + isActive, + email: email.toLowerCase(), + from: 'admin', + }); + } + } + }, setUsername(username, userId) { check(username, String); check(userId, String); -- cgit v1.2.3-1-g7c22 From aac7c380c8c389b0683b2bd64e2cc856993f0e30 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sun, 1 Mar 2020 20:59:53 +0200 Subject: - Fix critical and moderate security vulnerabilities reported at 2020-02-26 with responsible disclosure by [Dejan Zelic](https://twitter.com/dejandayoff), Justin Benjamin and others at [Offensive Security](https://twitter.com/offsectraining), that follow standard 90 days before public disclosure. Thanks to xet7. - Fix webhook error that prevented some card etc deleting from web UI of board. Thanks to xet7. - Add some more Font Awesome icons. Thanks to xet7. - Remove autofocus from many form input boxes so that they would not cause warnings. Thanks to xet7. --- models/activities.js | 9 ++- models/users.js | 169 +++++++++++++++++++++++++++------------------------ 2 files changed, 95 insertions(+), 83 deletions(-) (limited to 'models') diff --git a/models/activities.js b/models/activities.js index 19e3fb7d..568859a9 100644 --- a/models/activities.js +++ b/models/activities.js @@ -108,7 +108,7 @@ if (Meteor.isServer) { let participants = []; let watchers = []; let title = 'act-activity-notify'; - let board = null; + const board = Boards.findOne(activity.boardId); const description = `act-${activity.activityType}`; const params = { activityId: activity._id, @@ -122,8 +122,11 @@ if (Meteor.isServer) { params.userId = activity.userId; } if (activity.boardId) { - board = activity.board(); - params.board = board.title; + if (board.title.length > 0) { + params.board = board.title; + } else { + params.board = ''; + } title = 'act-withBoardTitle'; params.url = board.absoluteUrl(); params.boardId = activity.boardId; diff --git a/models/users.js b/models/users.js index 00076253..d56f14ff 100644 --- a/models/users.js +++ b/models/users.js @@ -620,44 +620,6 @@ Users.mutations({ }); Meteor.methods({ - setCreateUser(fullname, username, password, isAdmin, isActive, email) { - if (Meteor.user().isAdmin) { - check(fullname, String); - check(username, String); - check(password, String); - check(isAdmin, String); - check(isActive, String); - check(email, String); - - const nUsersWithUsername = Users.find({ username }).count(); - const nUsersWithEmail = Users.find({ email }).count(); - if (nUsersWithUsername > 0) { - throw new Meteor.Error('username-already-taken'); - } else if (nUsersWithEmail > 0) { - throw new Meteor.Error('email-already-taken'); - } else { - Accounts.createUser({ - fullname, - username, - password, - isAdmin, - isActive, - email: email.toLowerCase(), - from: 'admin', - }); - } - } - }, - setUsername(username, userId) { - check(username, String); - check(userId, String); - const nUsersWithUsername = Users.find({ username }).count(); - if (nUsersWithUsername > 0) { - throw new Meteor.Error('username-already-taken'); - } else { - Users.update(userId, { $set: { username } }); - } - }, setListSortBy(value) { check(value, String); Meteor.user().setListSortBy(value); @@ -678,51 +640,97 @@ Meteor.methods({ check(limit, Number); Meteor.user().setShowCardsCountAt(limit); }, - setEmail(email, userId) { - if (Array.isArray(email)) { - email = email.shift(); - } - check(email, String); - const existingUser = Users.findOne( - { 'emails.address': email }, - { fields: { _id: 1 } }, - ); - if (existingUser) { - throw new Meteor.Error('email-already-taken'); - } else { - Users.update(userId, { - $set: { - emails: [ - { - address: email, - verified: false, - }, - ], - }, - }); - } - }, - setUsernameAndEmail(username, email, userId) { - check(username, String); - if (Array.isArray(email)) { - email = email.shift(); - } - check(email, String); - check(userId, String); - Meteor.call('setUsername', username, userId); - Meteor.call('setEmail', email, userId); - }, - setPassword(newPassword, userId) { - check(userId, String); - check(newPassword, String); - if (Meteor.user().isAdmin) { - Accounts.setPassword(userId, newPassword); - } - }, }); if (Meteor.isServer) { Meteor.methods({ + setCreateUser(fullname, username, password, isAdmin, isActive, email) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(fullname, String); + check(username, String); + check(password, String); + check(isAdmin, String); + check(isActive, String); + check(email, String); + + const nUsersWithUsername = Users.find({ username }).count(); + const nUsersWithEmail = Users.find({ email }).count(); + if (nUsersWithUsername > 0) { + throw new Meteor.Error('username-already-taken'); + } else if (nUsersWithEmail > 0) { + throw new Meteor.Error('email-already-taken'); + } else { + Accounts.createUser({ + fullname, + username, + password, + isAdmin, + isActive, + email: email.toLowerCase(), + from: 'admin', + }); + } + } + }, + setUsername(username, userId) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(username, String); + check(userId, String); + const nUsersWithUsername = Users.find({ username }).count(); + if (nUsersWithUsername > 0) { + throw new Meteor.Error('username-already-taken'); + } else { + Users.update(userId, { $set: { username } }); + } + } + }, + setEmail(email, userId) { + if (Meteor.user() && Meteor.user().isAdmin) { + if (Array.isArray(email)) { + email = email.shift(); + } + check(email, String); + const existingUser = Users.findOne( + { 'emails.address': email }, + { fields: { _id: 1 } }, + ); + if (existingUser) { + throw new Meteor.Error('email-already-taken'); + } else { + Users.update(userId, { + $set: { + emails: [ + { + address: email, + verified: false, + }, + ], + }, + }); + } + } + }, + setUsernameAndEmail(username, email, userId) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(username, String); + if (Array.isArray(email)) { + email = email.shift(); + } + check(email, String); + check(userId, String); + Meteor.call('setUsername', username, userId); + Meteor.call('setEmail', email, userId); + } + }, + setPassword(newPassword, userId) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(userId, String); + check(newPassword, String); + if (Meteor.user().isAdmin) { + Accounts.setPassword(userId, newPassword); + } + } + }, // we accept userId, username, email inviteUserToBoard(username, boardId) { check(username, String); @@ -754,8 +762,9 @@ if (Meteor.isServer) { throw new Meteor.Error('error-user-notAllowSelf'); } else { if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist'); - if (Settings.findOne().disableRegistration) + if (Settings.findOne({ disableRegistration: true })) { throw new Meteor.Error('error-user-notCreated'); + } // Set in lowercase email before creating account const email = username.toLowerCase(); username = email.substring(0, posAt); -- cgit v1.2.3-1-g7c22 From 9819c9f801128d07374b0703b482bdb83a672297 Mon Sep 17 00:00:00 2001 From: Jonathan Baird Date: Fri, 27 Mar 2020 11:35:03 -0600 Subject: add a notification drawer like trello --- models/attachments.js | 12 ++++++------ models/lists.js | 6 ++++++ models/users.js | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 42 insertions(+), 9 deletions(-) (limited to 'models') diff --git a/models/attachments.js b/models/attachments.js index 9b8ec04f..3fe1d745 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -219,6 +219,9 @@ if (Meteor.isServer) { type: 'card', activityType: 'addAttachment', attachmentId: doc._id, + // this preserves the name so that notifications can be meaningful after + // this file is removed + attachmentName: doc.original.name, boardId: doc.boardId, cardId: doc.cardId, listId: doc.listId, @@ -246,18 +249,15 @@ if (Meteor.isServer) { type: 'card', activityType: 'deleteAttachment', attachmentId: doc._id, + // this preserves the name so that notifications can be meaningful after + // this file is removed + attachmentName: doc.original.name, boardId: doc.boardId, cardId: doc.cardId, listId: doc.listId, swimlaneId: doc.swimlaneId, }); }); - - Attachments.files.after.remove((userId, doc) => { - Activities.remove({ - attachmentId: doc._id, - }); - }); } export default Attachments; diff --git a/models/lists.js b/models/lists.js index f06b15b1..b123ab4f 100644 --- a/models/lists.js +++ b/models/lists.js @@ -369,6 +369,9 @@ if (Meteor.isServer) { activityType: 'createList', boardId: doc.boardId, listId: doc._id, + // this preserves the name so that the activity can be useful after the + // list is deleted + title: doc.title, }); }); @@ -397,6 +400,9 @@ if (Meteor.isServer) { activityType: 'archivedList', listId: doc._id, boardId: doc.boardId, + // this preserves the name so that the activity can be useful after the + // list is deleted + title: doc.title, }); } }); diff --git a/models/users.js b/models/users.js index d56f14ff..20581e65 100644 --- a/models/users.js +++ b/models/users.js @@ -165,7 +165,20 @@ Users.attachSchema( /** * enabled notifications for the user */ - type: [String], + type: [Object], + optional: true, + }, + 'profile.notifications.$.activity': { + /** + * The id of the activity this notification references + */ + type: String, + }, + 'profile.notifications.$.read': { + /** + * the date on which this notification was read + */ + type: Date, optional: true, }, 'profile.showCardsCountAt': { @@ -429,6 +442,20 @@ Users.helpers({ return _.contains(notifications, activityId); }, + notifications() { + const { notifications = [] } = this.profile || {}; + for (const index in notifications) { + if (!notifications.hasOwnProperty(index)) continue; + const notification = notifications[index]; + // this preserves their db sort order for editing + notification.dbIndex = index; + notification.activity = Activities.findOne(notification.activity); + } + // this sorts them newest to oldest to match Trello's behavior + notifications.reverse(); + return notifications; + }, + hasShowDesktopDragHandles() { const profile = this.profile || {}; return profile.showDesktopDragHandles || false; @@ -573,7 +600,7 @@ Users.mutations({ addNotification(activityId) { return { $addToSet: { - 'profile.notifications': activityId, + 'profile.notifications': { activity: activityId }, }, }; }, @@ -581,7 +608,7 @@ Users.mutations({ removeNotification(activityId) { return { $pull: { - 'profile.notifications': activityId, + 'profile.notifications': { activity: activityId }, }, }; }, -- cgit v1.2.3-1-g7c22 From 6e86292b997d40e36822efd10d6940e93da4abd0 Mon Sep 17 00:00:00 2001 From: Pedro Sousa <18445484+slvrpdr@users.noreply.github.com> Date: Fri, 3 Apr 2020 16:23:48 +0100 Subject: Search also a Card's Custom Fields --- models/boards.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'models') diff --git a/models/boards.js b/models/boards.js index 8862f301..35ee1a36 100644 --- a/models/boards.js +++ b/models/boards.js @@ -806,7 +806,11 @@ Boards.helpers({ if (term) { const regex = new RegExp(term, 'i'); - query.$or = [{ title: regex }, { description: regex }]; + query.$or = [ + { title: regex }, + { description: regex }, + { customFields: { $elemMatch: { value: regex } } }, + ]; } return Cards.find(query, projection); -- cgit v1.2.3-1-g7c22 From 2bbc312ad0600da06b7d18f57630ad19cd90efd2 Mon Sep 17 00:00:00 2001 From: Nico Date: Tue, 7 Apr 2020 20:43:35 +0200 Subject: Voteing feature --- models/cards.js | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 11 deletions(-) (limited to 'models') diff --git a/models/cards.js b/models/cards.js index eed1b958..1ee4ba68 100644 --- a/models/cards.js +++ b/models/cards.js @@ -304,6 +304,38 @@ Cards.attachSchema( optional: true, defaultValue: '', }, + vote: { + /** + * vote object, see below + */ + type: Object, + optional: true, + }, + 'vote.question': { + type: String, + defaultValue: '', + }, + 'vote.positive': { + /** + * list of members (user IDs) + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'vote.negative': { + /** + * list of members (user IDs) + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'vote.end': { + type: Date, + optional: true, + defaultValue: null + } }), ); @@ -696,7 +728,7 @@ Cards.helpers({ parentString(sep) { return this.parentList() - .map(function(elem) { + .map(function (elem) { return elem.title; }) .join(sep); @@ -980,6 +1012,22 @@ Cards.helpers({ } }, + getVoteQuestion() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card && card.vote) return card.vote.question; + else return null; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board && board.vote) return board.vote.question; + else return null; + } else if (this.vote) { + return this.vote.question; + } else { + return null; + } + }, + getId() { if (this.isLinked()) { return this.linkedId; @@ -1396,6 +1444,57 @@ Cards.mutations({ }, }; }, + setVoteQuestion(question) { + return { + $set: { + vote: { + question, + positive:[], + negative:[] + }, + } + } + }, + unsetVote() { + return { + $unset: { + vote: '', + }, + }; + }, + setVote(userId, forIt) { + switch (forIt) { + case true: + // vote for it + return { + $pull:{ + "vote.negative": userId + }, + $addToSet: { + "vote.positive": userId + } + } + case false: + // vote against + return { + $pull:{ + "vote.positive": userId + }, + $addToSet: { + "vote.negative" : userId + } + } + + default: + // Remove votes + return { + $pull:{ + "vote.positive": userId, + "vote.negative" : userId + }, + } + } + }, }); //FUNCTIONS FOR creation of Activities @@ -1798,7 +1897,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; const oldBoardId = this.previous.boardId; @@ -1844,7 +1943,7 @@ if (Meteor.isServer) { // change list modifiedAt, when user modified the key values in timingaction array, if it's endAt, put the modifiedAt of list back to one year ago for sorting purpose const modifiedAt = new Date( new Date(value).getTime() - - (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), + (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), ); // set it as 1 year before const boardId = list.boardId; Lists.direct.update( @@ -1898,7 +1997,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', - function(req, res) { + function (req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; Authentication.checkBoardAccess(req.userId, paramBoardId); @@ -1908,7 +2007,7 @@ if (Meteor.isServer) { boardId: paramBoardId, swimlaneId: paramSwimlaneId, archived: false, - }).map(function(doc) { + }).map(function (doc) { return { _id: doc._id, title: doc.title, @@ -1932,7 +2031,7 @@ if (Meteor.isServer) { * title: string, * description: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function ( req, res, ) { @@ -1945,7 +2044,7 @@ if (Meteor.isServer) { boardId: paramBoardId, listId: paramListId, archived: false, - }).map(function(doc) { + }).map(function (doc) { return { _id: doc._id, title: doc.title, @@ -1967,7 +2066,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; @@ -1999,7 +2098,7 @@ if (Meteor.isServer) { * @param {string} [assignees] the array of maximum one ID of assignee of the new card * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function( + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function ( req, res, ) { @@ -2106,7 +2205,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -2405,7 +2504,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; -- cgit v1.2.3-1-g7c22 From 4d066b1f3095326c6ef085ccc405bb1e19f0dd03 Mon Sep 17 00:00:00 2001 From: Jonathan Baird Date: Wed, 8 Apr 2020 11:54:00 -0600 Subject: stop notifying users about their own behavior --- models/activities.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'models') diff --git a/models/activities.js b/models/activities.js index 568859a9..b5fcb7d8 100644 --- a/models/activities.js +++ b/models/activities.js @@ -282,7 +282,10 @@ if (Meteor.isServer) { ); } Notifications.getUsers(watchers).forEach(user => { - Notifications.notify(user, title, description, params); + // don't notify a user of their own behavior + if (user._id !== userId) { + Notifications.notify(user, title, description, params); + } }); const integrations = Integrations.find({ -- cgit v1.2.3-1-g7c22 From 1e20e2601fa9c2951d811861ff97f3f555aac6af Mon Sep 17 00:00:00 2001 From: Jonathan Baird Date: Wed, 8 Apr 2020 11:54:00 -0600 Subject: add a scheduled notification cleanup job --- models/users.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'models') diff --git a/models/users.js b/models/users.js index 20581e65..9b2a5465 100644 --- a/models/users.js +++ b/models/users.js @@ -1,3 +1,5 @@ +import { SyncedCron } from 'meteor/percolate:synced-cron'; + // Sandstorm context is detected using the METEOR_SETTINGS environment variable // in the package definition. const isSandstorm = @@ -926,6 +928,37 @@ if (Meteor.isServer) { }); } +const addCronJob = _.debounce( + Meteor.bindEnvironment(function notificationCleanupDebounced() { + // passed in the removeAge has to be a number standing for the number of days after a notification is read before we remove it + const envRemoveAge = process.env.NOTIFICATION_REMOVAL_AGE; + // default notifications will be removed 2 days after they are read + const defaultRemoveAge = 2; + const removeAge = parseInt(envRemoveAge, 10) || defaultRemoveAge; + + SyncedCron.add({ + name: 'notification_cleanup', + schedule: parser => parser.text('every 1 days'), + job: () => { + for (const user of Users.find()) { + for (const notification of user.profile.notifications) { + if (notification.read) { + const removeDate = new Date(notification.read); + removeDate.setDate(removeDate.getDate() + removeAge); + if (removeDate <= new Date()) { + user.removeNotification(notification.activity); + } + } + } + } + }, + }); + + SyncedCron.start(); + }), + 500, +); + if (Meteor.isServer) { // Let mongoDB ensure username unicity Meteor.startup(() => { @@ -939,6 +972,9 @@ if (Meteor.isServer) { }, { unique: true }, ); + Meteor.defer(() => { + addCronJob(); + }); }); // OLD WAY THIS CODE DID WORK: When user is last admin of board, -- cgit v1.2.3-1-g7c22 From 5ebb47cb0ec7272894a37d99579ede872251f55c Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Wed, 8 Apr 2020 23:16:48 +0300 Subject: Add setting default NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2 to all Wekan platforms https://github.com/wekan/wekan/pull/2998 Thanks to xet7 ! --- models/cards.js | 62 ++++++++++++++++++++++++++++----------------------------- models/users.js | 3 ++- 2 files changed, 33 insertions(+), 32 deletions(-) (limited to 'models') diff --git a/models/cards.js b/models/cards.js index 1ee4ba68..94b174bb 100644 --- a/models/cards.js +++ b/models/cards.js @@ -334,8 +334,8 @@ Cards.attachSchema( 'vote.end': { type: Date, optional: true, - defaultValue: null - } + defaultValue: null, + }, }), ); @@ -728,7 +728,7 @@ Cards.helpers({ parentString(sep) { return this.parentList() - .map(function (elem) { + .map(function(elem) { return elem.title; }) .join(sep); @@ -1449,11 +1449,11 @@ Cards.mutations({ $set: { vote: { question, - positive:[], - negative:[] + positive: [], + negative: [], }, - } - } + }, + }; }, unsetVote() { return { @@ -1467,32 +1467,32 @@ Cards.mutations({ case true: // vote for it return { - $pull:{ - "vote.negative": userId + $pull: { + 'vote.negative': userId, }, $addToSet: { - "vote.positive": userId - } - } + 'vote.positive': userId, + }, + }; case false: // vote against return { - $pull:{ - "vote.positive": userId + $pull: { + 'vote.positive': userId, }, $addToSet: { - "vote.negative" : userId - } - } + 'vote.negative': userId, + }, + }; default: // Remove votes return { - $pull:{ - "vote.positive": userId, - "vote.negative" : userId + $pull: { + 'vote.positive': userId, + 'vote.negative': userId, }, - } + }; } }, }); @@ -1897,7 +1897,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; const oldBoardId = this.previous.boardId; @@ -1943,7 +1943,7 @@ if (Meteor.isServer) { // change list modifiedAt, when user modified the key values in timingaction array, if it's endAt, put the modifiedAt of list back to one year ago for sorting purpose const modifiedAt = new Date( new Date(value).getTime() - - (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), + (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), ); // set it as 1 year before const boardId = list.boardId; Lists.direct.update( @@ -1997,7 +1997,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', - function (req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; Authentication.checkBoardAccess(req.userId, paramBoardId); @@ -2007,7 +2007,7 @@ if (Meteor.isServer) { boardId: paramBoardId, swimlaneId: paramSwimlaneId, archived: false, - }).map(function (doc) { + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -2031,7 +2031,7 @@ if (Meteor.isServer) { * title: string, * description: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function ( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { @@ -2044,7 +2044,7 @@ if (Meteor.isServer) { boardId: paramBoardId, listId: paramListId, archived: false, - }).map(function (doc) { + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -2066,7 +2066,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; @@ -2098,7 +2098,7 @@ if (Meteor.isServer) { * @param {string} [assignees] the array of maximum one ID of assignee of the new card * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function ( + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { @@ -2205,7 +2205,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -2504,7 +2504,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; diff --git a/models/users.js b/models/users.js index 9b2a5465..8a05a0d2 100644 --- a/models/users.js +++ b/models/users.js @@ -931,7 +931,8 @@ if (Meteor.isServer) { const addCronJob = _.debounce( Meteor.bindEnvironment(function notificationCleanupDebounced() { // passed in the removeAge has to be a number standing for the number of days after a notification is read before we remove it - const envRemoveAge = process.env.NOTIFICATION_REMOVAL_AGE; + const envRemoveAge = + process.env.NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE; // default notifications will be removed 2 days after they are read const defaultRemoveAge = 2; const removeAge = parseInt(envRemoveAge, 10) || defaultRemoveAge; -- cgit v1.2.3-1-g7c22 From f09219cbfd620e04fd48539bd11eced20c81137b Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 9 Apr 2020 01:55:01 +0200 Subject: Remove export button if WITH_API is not enabled #2938 https://github.com/wekan/wekan/issues/2938#issuecomment-589782402 --- models/settings.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'models') diff --git a/models/settings.js b/models/settings.js index 63bcd7f3..0d671aa4 100644 --- a/models/settings.js +++ b/models/settings.js @@ -198,6 +198,10 @@ if (Meteor.isServer) { return process.env.CAS_ENABLED === 'true'; } + function isApiEnabled() { + return process.env.WITH_API === 'true'; + } + Meteor.methods({ sendInvitation(emails, boards) { check(emails, [String]); @@ -314,6 +318,10 @@ if (Meteor.isServer) { return isCasEnabled(); }, + _isApiEnabled() { + return isApiEnabled(); + }, + // Gets all connection methods to use it in the Template getAuthenticationsEnabled() { return { -- cgit v1.2.3-1-g7c22 From e661d03e8d7ea8c1d2190de2c7c59eaf0700534b Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 9 Apr 2020 02:00:15 +0200 Subject: Add vote import from Trello --- models/trelloCreator.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'models') diff --git a/models/trelloCreator.js b/models/trelloCreator.js index cb1a6a67..48dce7eb 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.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(); }); @@ -285,6 +285,29 @@ export class TrelloCreator { cardToCreate.members = wekanMembers; } } + // add vote + if (card.idMembersVoted) { + // Trello only know's positive votes + const positiveVotes = []; + card.idMembersVoted.forEach(trelloId => { + if (this.members[trelloId]) { + const wekanId = this.members[trelloId]; + // we may map multiple Trello members to the same wekan user + // in which case we risk adding the same user multiple times + if (!positiveVotes.find(wId => wId === wekanId)) { + positiveVotes.push(wekanId); + } + } + return true; + }) + if (positiveVotes.length > 0) { + cardToCreate.vote = { + question: cardToCreate.title, + positive: positiveVotes, + } + } + } + // insert card const cardId = Cards.direct.insert(cardToCreate); // keep track of Trello id => Wekan id @@ -345,7 +368,7 @@ export class TrelloCreator { // 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) { - file.attachData(att.url, function(error) { + file.attachData(att.url, function (error) { file.boardId = boardId; file.cardId = cardId; file.userId = self._user(att.idMemberCreator); -- cgit v1.2.3-1-g7c22 From 6ced47aa8c7c62764778f8123708455c0f33f82f Mon Sep 17 00:00:00 2001 From: Pedro Sousa <18445484+slvrpdr@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:10:50 +0100 Subject: When adding a user to a board that has subtasks, also add user to the subtask board --- models/users.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'models') diff --git a/models/users.js b/models/users.js index 8a05a0d2..b1f3e26c 100644 --- a/models/users.js +++ b/models/users.js @@ -814,6 +814,16 @@ if (Meteor.isServer) { board.addMember(user._id); user.addInvite(boardId); + //Check if there is a subtasks board + if (board.subtasksDefaultBoardId){ + const subBoard = Boards.findOne(board.subtasksDefaultBoardId); + //If there is, also add user to that board + if (subBoard) { + subBoard.addMember(user._id); + user.addInvite(subBoard._id); + } + } + try { const params = { user: user.username, -- cgit v1.2.3-1-g7c22 From 8f28a409c7dd90319f785e3fc4c1b26803f67f31 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 10 Apr 2020 01:46:36 +0200 Subject: Public vote --- models/cards.js | 56 ++++++++++++++++++++++++++++++++++++++----------- models/trelloCreator.js | 1 + 2 files changed, 45 insertions(+), 12 deletions(-) (limited to 'models') diff --git a/models/cards.js b/models/cards.js index 94b174bb..5a812679 100644 --- a/models/cards.js +++ b/models/cards.js @@ -336,6 +336,10 @@ Cards.attachSchema( optional: true, defaultValue: null, }, + 'vote.public': { + type: Boolean, + defaultValue: false, + }, }), ); @@ -728,7 +732,7 @@ Cards.helpers({ parentString(sep) { return this.parentList() - .map(function(elem) { + .map(function (elem) { return elem.title; }) .join(sep); @@ -1028,6 +1032,33 @@ Cards.helpers({ } }, + getVotePublic() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card && card.vote) return card.vote.public; + else return null; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board && board.vote) return board.vote.public; + else return null; + } else if (this.vote) { + return this.vote.public; + } else { + return null; + } + }, + + voteMemberPositive() { + if (this.vote && this.vote.positive) + return Users.find({ _id: { $in: this.vote.positive } }) + return [] + }, + voteMemberNegative() { + if (this.vote && this.vote.negative) + return Users.find({ _id: { $in: this.vote.negative } }) + return [] + }, + getId() { if (this.isLinked()) { return this.linkedId; @@ -1444,11 +1475,12 @@ Cards.mutations({ }, }; }, - setVoteQuestion(question) { + setVoteQuestion(question, public) { return { $set: { vote: { question, + public, positive: [], negative: [], }, @@ -1897,7 +1929,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; const oldBoardId = this.previous.boardId; @@ -1943,7 +1975,7 @@ if (Meteor.isServer) { // change list modifiedAt, when user modified the key values in timingaction array, if it's endAt, put the modifiedAt of list back to one year ago for sorting purpose const modifiedAt = new Date( new Date(value).getTime() - - (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), + (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), ); // set it as 1 year before const boardId = list.boardId; Lists.direct.update( @@ -1997,7 +2029,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', - function(req, res) { + function (req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; Authentication.checkBoardAccess(req.userId, paramBoardId); @@ -2007,7 +2039,7 @@ if (Meteor.isServer) { boardId: paramBoardId, swimlaneId: paramSwimlaneId, archived: false, - }).map(function(doc) { + }).map(function (doc) { return { _id: doc._id, title: doc.title, @@ -2031,7 +2063,7 @@ if (Meteor.isServer) { * title: string, * description: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function ( req, res, ) { @@ -2044,7 +2076,7 @@ if (Meteor.isServer) { boardId: paramBoardId, listId: paramListId, archived: false, - }).map(function(doc) { + }).map(function (doc) { return { _id: doc._id, title: doc.title, @@ -2066,7 +2098,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; @@ -2098,7 +2130,7 @@ if (Meteor.isServer) { * @param {string} [assignees] the array of maximum one ID of assignee of the new card * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function( + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function ( req, res, ) { @@ -2205,7 +2237,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -2504,7 +2536,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; diff --git a/models/trelloCreator.js b/models/trelloCreator.js index 48dce7eb..28982f43 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -303,6 +303,7 @@ export class TrelloCreator { if (positiveVotes.length > 0) { cardToCreate.vote = { question: cardToCreate.title, + public: true, positive: positiveVotes, } } -- cgit v1.2.3-1-g7c22 From 269382869e20a9b172dfa5f56f4d06a7722993ef Mon Sep 17 00:00:00 2001 From: Jonathan Baird Date: Sat, 11 Apr 2020 17:28:15 -0600 Subject: fix error in notifications cleanup cron --- models/users.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'models') diff --git a/models/users.js b/models/users.js index b1f3e26c..3700d1c8 100644 --- a/models/users.js +++ b/models/users.js @@ -815,7 +815,7 @@ if (Meteor.isServer) { user.addInvite(boardId); //Check if there is a subtasks board - if (board.subtasksDefaultBoardId){ + if (board.subtasksDefaultBoardId) { const subBoard = Boards.findOne(board.subtasksDefaultBoardId); //If there is, also add user to that board if (subBoard) { @@ -823,7 +823,7 @@ if (Meteor.isServer) { user.addInvite(subBoard._id); } } - + try { const params = { user: user.username, @@ -952,6 +952,7 @@ const addCronJob = _.debounce( schedule: parser => parser.text('every 1 days'), job: () => { for (const user of Users.find()) { + if (!user.profile || !user.profile.notifications) continue; for (const notification of user.profile.notifications) { if (notification.read) { const removeDate = new Date(notification.read); -- cgit v1.2.3-1-g7c22 From 35ae07e2a65c5ab5ba6784cdb67631918a41ccc3 Mon Sep 17 00:00:00 2001 From: salleman Date: Mon, 13 Apr 2020 15:46:29 +0200 Subject: debug isBoardAdmin on main page --- models/users.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'models') diff --git a/models/users.js b/models/users.js index 3700d1c8..a9eeb38b 100644 --- a/models/users.js +++ b/models/users.js @@ -377,8 +377,8 @@ if (Meteor.isClient) { return board && board.hasWorker(this._id); }, - isBoardAdmin() { - const board = Boards.findOne(Session.get('currentBoard')); + isBoardAdmin(boardId = Session.get('currentBoard')) { + const board = Boards.findOne(boardId); return board && board.hasAdmin(this._id); }, }); -- cgit v1.2.3-1-g7c22 From 3e817257ef6d7a527aaad040cdcdcc642caea3c1 Mon Sep 17 00:00:00 2001 From: salleman Date: Mon, 13 Apr 2020 21:06:27 +0200 Subject: hide password auth with PASSWORD_LOGIN_ENABLED variable --- models/settings.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'models') diff --git a/models/settings.js b/models/settings.js index 0d671aa4..3bebd977 100644 --- a/models/settings.js +++ b/models/settings.js @@ -334,6 +334,11 @@ if (Meteor.isServer) { getDefaultAuthenticationMethod() { return process.env.DEFAULT_AUTHENTICATION_METHOD; }, + + isPasswordDisabled() { + return process.env.PASSWORD_LOGIN_ENABLED === 'false'; + }, + }); } -- cgit v1.2.3-1-g7c22 From 18610d2fe61dd7e5f7fbf914fabba6eaab412e6c Mon Sep 17 00:00:00 2001 From: Allemand <37148072+salleman33@users.noreply.github.com> Date: Tue, 14 Apr 2020 08:49:47 +0200 Subject: Update settings.js --- models/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'models') diff --git a/models/settings.js b/models/settings.js index 3bebd977..03ef9052 100644 --- a/models/settings.js +++ b/models/settings.js @@ -335,7 +335,7 @@ if (Meteor.isServer) { return process.env.DEFAULT_AUTHENTICATION_METHOD; }, - isPasswordDisabled() { + isPasswordLoginDisabled() { return process.env.PASSWORD_LOGIN_ENABLED === 'false'; }, -- cgit v1.2.3-1-g7c22 From 2400c910135dbcdddd82954951fc3a970748af55 Mon Sep 17 00:00:00 2001 From: boeserwolf Date: Sun, 19 Apr 2020 10:48:44 +0300 Subject: Add sort field to boards model --- models/boards.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'models') diff --git a/models/boards.js b/models/boards.js index 35ee1a36..fba690a7 100644 --- a/models/boards.js +++ b/models/boards.js @@ -493,6 +493,14 @@ Boards.attachSchema( type: String, defaultValue: 'board', }, + sort: { + /** + * Sort value + */ + type: Number, + decimal: true, + defaultValue: -1, + }, }), ); -- cgit v1.2.3-1-g7c22 From 10fcc19b7f9307e71f01b6abca055806d69f7d4e Mon Sep 17 00:00:00 2001 From: boeserwolf Date: Sun, 19 Apr 2020 12:30:21 +0300 Subject: Add sortDefault helper for sorting boards --- models/boards.js | 9 +++++++-- models/users.js | 30 +++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 9 deletions(-) (limited to 'models') diff --git a/models/boards.js b/models/boards.js index fba690a7..fdb07f89 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1474,7 +1474,7 @@ if (Meteor.isServer) { 'members.userId': paramUserId, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ).map(function(board) { return { @@ -1504,7 +1504,12 @@ if (Meteor.isServer) { Authentication.checkUserId(req.userId); JsonRoutes.sendResult(res, { code: 200, - data: Boards.find({ permission: 'public' }).map(function(doc) { + data: Boards.find( + { permission: 'public' }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ).map(function(doc) { return { _id: doc._id, title: doc.title, diff --git a/models/users.js b/models/users.js index a9eeb38b..f4b7329a 100644 --- a/models/users.js +++ b/models/users.js @@ -386,12 +386,20 @@ if (Meteor.isClient) { Users.helpers({ boards() { - return Boards.find({ 'members.userId': this._id }); + return Boards.find( + { 'members.userId': this._id }, + { sort: { sort: 1 /* boards default sorting */ } }, + ); }, starredBoards() { const { starredBoards = [] } = this.profile || {}; - return Boards.find({ archived: false, _id: { $in: starredBoards } }); + return Boards.find( + { archived: false, _id: { $in: starredBoards } }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ); }, hasStarred(boardId) { @@ -401,7 +409,12 @@ Users.helpers({ invitedBoards() { const { invitedBoards = [] } = this.profile || {}; - return Boards.find({ archived: false, _id: { $in: invitedBoards } }); + return Boards.find( + { archived: false, _id: { $in: invitedBoards } }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ); }, isInvitedTo(boardId) { @@ -1292,10 +1305,13 @@ if (Meteor.isServer) { let data = Meteor.users.findOne({ _id: id }); if (data !== undefined) { if (action === 'takeOwnership') { - data = Boards.find({ - 'members.userId': id, - 'members.isAdmin': true, - }).map(function(board) { + data = Boards.find( + { + 'members.userId': id, + 'members.isAdmin': true, + }, + { sort: { sort: 1 /* boards default sorting */ } }, + ).map(function(board) { if (board.hasMember(req.userId)) { board.removeMember(req.userId); } -- cgit v1.2.3-1-g7c22 From b3efa71d1373ede679282be35cba947dc2b868ff Mon Sep 17 00:00:00 2001 From: boeserwolf Date: Sun, 19 Apr 2020 12:38:56 +0300 Subject: Add move function to boards mutations --- models/boards.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'models') diff --git a/models/boards.js b/models/boards.js index fdb07f89..4c2d96da 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1194,6 +1194,10 @@ Boards.mutations({ setPresentParentTask(presentParentTask) { return { $set: { presentParentTask } }; }, + + move(sortIndex) { + return { $set: { sort: sortIndex } }; + }, }); function boardRemover(userId, doc) { -- cgit v1.2.3-1-g7c22 From b42d8346cda99258f4ab5689ebd02fdc7c2e85c3 Mon Sep 17 00:00:00 2001 From: boeserwolf Date: Sun, 19 Apr 2020 15:53:13 +0300 Subject: Insert new boards at last position --- models/boards.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'models') diff --git a/models/boards.js b/models/boards.js index 4c2d96da..170ebc5a 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1295,6 +1295,14 @@ if (Meteor.isServer) { }); } +// Insert new board at last position in sort order. +Boards.before.insert((userId, doc) => { + const lastBoard = Boards.findOne({ sort: { $exists: true } }, { sort: { sort: -1 } }); + if (lastBoard && typeof lastBoard.sort !== 'undefined') { + doc.sort = lastBoard.sort + 1; + } +}); + if (Meteor.isServer) { // Let MongoDB ensure that a member is not included twice in the same board Meteor.startup(() => { -- cgit v1.2.3-1-g7c22 From b2acc3ba45c48d3bb3b25e84bc31f5b80e7961d4 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Tue, 21 Apr 2020 17:06:39 +0200 Subject: Multiple lint issue fixes Found by using the command `meteor npm run lint:eslint:fix`. --- models/cards.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'models') diff --git a/models/cards.js b/models/cards.js index 5a812679..485837ef 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1475,12 +1475,12 @@ Cards.mutations({ }, }; }, - setVoteQuestion(question, public) { + setVoteQuestion(question, public_) { return { $set: { vote: { question, - public, + public_, positive: [], negative: [], }, -- cgit v1.2.3-1-g7c22 From 8e14459cff4da1391f536dfbc6441abb21e9c215 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Wed, 22 Apr 2020 14:44:08 +0200 Subject: Implement option to change the first day of week in user settings Implements #2535. --- models/users.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'models') diff --git a/models/users.js b/models/users.js index f4b7329a..f4f4f38e 100644 --- a/models/users.js +++ b/models/users.js @@ -190,6 +190,13 @@ Users.attachSchema( type: Number, optional: true, }, + 'profile.startDayOfWeek': { + /** + * startDayOfWeek field of the user + */ + type: Number, + optional: true, + }, 'profile.starredBoards': { /** * list of starred board IDs @@ -521,6 +528,11 @@ Users.helpers({ return profile.language || 'en'; }, + getStartDayOfWeek() { + const profile = this.profile || {}; + return profile.startDayOfWeek || 1; + }, + getTemplatesBoardId() { return (this.profile || {}).templatesBoardId; }, @@ -652,6 +664,10 @@ Users.mutations({ return { $set: { 'profile.showCardsCountAt': limit } }; }, + setStartDayOfWeek(startDay) { + return { $set: { 'profile.startDayOfWeek': startDay } }; + }, + setBoardView(view) { return { $set: { @@ -682,6 +698,10 @@ Meteor.methods({ check(limit, Number); Meteor.user().setShowCardsCountAt(limit); }, + changeStartDayOfWeek(startDay) { + check(startDay, Number); + Meteor.user().setStartDayOfWeek(startDay); + }, }); if (Meteor.isServer) { -- cgit v1.2.3-1-g7c22 From 9e95c06415e614e587d684ff9660cc53c5f8c8d3 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Wed, 22 Apr 2020 21:00:31 +0300 Subject: Fix lint errors in lint error fix. Thanks to xet7 ! --- models/boards.js | 5 ++++- models/cards.js | 34 +++++++++++++++++----------------- models/settings.js | 1 - models/trelloCreator.js | 8 ++++---- 4 files changed, 25 insertions(+), 23 deletions(-) (limited to 'models') diff --git a/models/boards.js b/models/boards.js index 170ebc5a..26dc6127 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1297,7 +1297,10 @@ if (Meteor.isServer) { // Insert new board at last position in sort order. Boards.before.insert((userId, doc) => { - const lastBoard = Boards.findOne({ sort: { $exists: true } }, { sort: { sort: -1 } }); + const lastBoard = Boards.findOne( + { sort: { $exists: true } }, + { sort: { sort: -1 } }, + ); if (lastBoard && typeof lastBoard.sort !== 'undefined') { doc.sort = lastBoard.sort + 1; } diff --git a/models/cards.js b/models/cards.js index 485837ef..72153132 100644 --- a/models/cards.js +++ b/models/cards.js @@ -732,7 +732,7 @@ Cards.helpers({ parentString(sep) { return this.parentList() - .map(function (elem) { + .map(function(elem) { return elem.title; }) .join(sep); @@ -1050,13 +1050,13 @@ Cards.helpers({ voteMemberPositive() { if (this.vote && this.vote.positive) - return Users.find({ _id: { $in: this.vote.positive } }) - return [] + return Users.find({ _id: { $in: this.vote.positive } }); + return []; }, voteMemberNegative() { if (this.vote && this.vote.negative) - return Users.find({ _id: { $in: this.vote.negative } }) - return [] + return Users.find({ _id: { $in: this.vote.negative } }); + return []; }, getId() { @@ -1475,12 +1475,12 @@ Cards.mutations({ }, }; }, - setVoteQuestion(question, public_) { + setVoteQuestion(question, public) { return { $set: { vote: { question, - public_, + public, positive: [], negative: [], }, @@ -1929,7 +1929,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; const oldBoardId = this.previous.boardId; @@ -1975,7 +1975,7 @@ if (Meteor.isServer) { // change list modifiedAt, when user modified the key values in timingaction array, if it's endAt, put the modifiedAt of list back to one year ago for sorting purpose const modifiedAt = new Date( new Date(value).getTime() - - (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), + (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), ); // set it as 1 year before const boardId = list.boardId; Lists.direct.update( @@ -2029,7 +2029,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', - function (req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; Authentication.checkBoardAccess(req.userId, paramBoardId); @@ -2039,7 +2039,7 @@ if (Meteor.isServer) { boardId: paramBoardId, swimlaneId: paramSwimlaneId, archived: false, - }).map(function (doc) { + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -2063,7 +2063,7 @@ if (Meteor.isServer) { * title: string, * description: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function ( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { @@ -2076,7 +2076,7 @@ if (Meteor.isServer) { boardId: paramBoardId, listId: paramListId, archived: false, - }).map(function (doc) { + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -2098,7 +2098,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; @@ -2130,7 +2130,7 @@ if (Meteor.isServer) { * @param {string} [assignees] the array of maximum one ID of assignee of the new card * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function ( + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { @@ -2237,7 +2237,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -2536,7 +2536,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; diff --git a/models/settings.js b/models/settings.js index 03ef9052..fb823205 100644 --- a/models/settings.js +++ b/models/settings.js @@ -338,7 +338,6 @@ if (Meteor.isServer) { isPasswordLoginDisabled() { return process.env.PASSWORD_LOGIN_ENABLED === 'false'; }, - }); } diff --git a/models/trelloCreator.js b/models/trelloCreator.js index 28982f43..1c5bcd93 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.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(); }); @@ -299,13 +299,13 @@ export class TrelloCreator { } } return true; - }) + }); if (positiveVotes.length > 0) { cardToCreate.vote = { question: cardToCreate.title, public: true, positive: positiveVotes, - } + }; } } @@ -369,7 +369,7 @@ export class TrelloCreator { // 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) { - file.attachData(att.url, function (error) { + file.attachData(att.url, function(error) { file.boardId = boardId; file.cardId = cardId; file.userId = self._user(att.idMemberCreator); -- cgit v1.2.3-1-g7c22 From 405f176bbb04a02e9aa9be662df6412100ffb257 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Wed, 22 Apr 2020 21:04:44 +0200 Subject: Fix getStartDayOfWeek function In case profile.startDayOfWeek is 0 it's evaluated to false and 1 is returned. Let's fix this by differentiating between undefined and an actual value. --- models/users.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'models') diff --git a/models/users.js b/models/users.js index f4f4f38e..ebb14f5f 100644 --- a/models/users.js +++ b/models/users.js @@ -530,7 +530,8 @@ Users.helpers({ getStartDayOfWeek() { const profile = this.profile || {}; - return profile.startDayOfWeek || 1; + // default is 'Monday' (1) + return profile.startDayOfWeek ?? 1; }, getTemplatesBoardId() { -- cgit v1.2.3-1-g7c22 From 7bb0aa74884d026bb6a0192bd2c4d0fb43f2953b Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 26 Apr 2020 02:41:26 +0200 Subject: Additional vote features --- models/cards.js | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) (limited to 'models') diff --git a/models/cards.js b/models/cards.js index 72153132..1633689e 100644 --- a/models/cards.js +++ b/models/cards.js @@ -340,6 +340,10 @@ Cards.attachSchema( type: Boolean, defaultValue: false, }, + 'vote.allowNonBoardMembers': { + type: Boolean, + defaultValue: false, + }, }), ); @@ -347,8 +351,8 @@ Cards.allow({ insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + update(userId, doc, fields) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)) || _.isEqual(fields, ['vote', 'modifiedAt', 'dateLastActivity']); }, remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); @@ -732,7 +736,7 @@ Cards.helpers({ parentString(sep) { return this.parentList() - .map(function(elem) { + .map(function (elem) { return elem.title; }) .join(sep); @@ -1152,6 +1156,26 @@ Cards.helpers({ isTemplateCard() { return this.type === 'template-card'; }, + + votePublic() { + if (this.vote) return this.vote.public; + return null; + }, + voteAllowNonBoardMembers() { + if (this.vote) return this.vote.allowNonBoardMembers; + return null; + }, + voteCountNegative() { + if (this.vote && this.vote.negative) return this.vote.negative.length; + return null; + }, + voteCountPositive() { + if (this.vote && this.vote.positive) return this.vote.positive.length; + return null; + }, + voteCount() { + return this.voteCountPositive() + this.voteCountNegative() + }, }); Cards.mutations({ @@ -1475,12 +1499,13 @@ Cards.mutations({ }, }; }, - setVoteQuestion(question, public) { + setVoteQuestion(question, public, allowNonBoardMembers) { return { $set: { vote: { question, public, + allowNonBoardMembers, positive: [], negative: [], }, @@ -1929,7 +1954,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; const oldBoardId = this.previous.boardId; @@ -1975,7 +2000,7 @@ if (Meteor.isServer) { // change list modifiedAt, when user modified the key values in timingaction array, if it's endAt, put the modifiedAt of list back to one year ago for sorting purpose const modifiedAt = new Date( new Date(value).getTime() - - (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), + (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), ); // set it as 1 year before const boardId = list.boardId; Lists.direct.update( @@ -2029,7 +2054,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', - function(req, res) { + function (req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; Authentication.checkBoardAccess(req.userId, paramBoardId); @@ -2039,7 +2064,7 @@ if (Meteor.isServer) { boardId: paramBoardId, swimlaneId: paramSwimlaneId, archived: false, - }).map(function(doc) { + }).map(function (doc) { return { _id: doc._id, title: doc.title, @@ -2063,7 +2088,7 @@ if (Meteor.isServer) { * title: string, * description: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function ( req, res, ) { @@ -2076,7 +2101,7 @@ if (Meteor.isServer) { boardId: paramBoardId, listId: paramListId, archived: false, - }).map(function(doc) { + }).map(function (doc) { return { _id: doc._id, title: doc.title, @@ -2098,7 +2123,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; @@ -2130,7 +2155,7 @@ if (Meteor.isServer) { * @param {string} [assignees] the array of maximum one ID of assignee of the new card * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function( + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function ( req, res, ) { @@ -2237,7 +2262,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -2536,7 +2561,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function(req, res) { + function (req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; -- cgit v1.2.3-1-g7c22 From ee106d1cb41b8e7b4ae757936f0f46688325f685 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 27 Apr 2020 02:54:40 +0300 Subject: Revert In Progress additional vote features. Translations are not removed. Thanks to xet7 ! Related https://github.com/wekan/wekan/pull/3048 --- models/cards.js | 53 ++++++++++++++--------------------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) (limited to 'models') diff --git a/models/cards.js b/models/cards.js index 1633689e..72153132 100644 --- a/models/cards.js +++ b/models/cards.js @@ -340,10 +340,6 @@ Cards.attachSchema( type: Boolean, defaultValue: false, }, - 'vote.allowNonBoardMembers': { - type: Boolean, - defaultValue: false, - }, }), ); @@ -351,8 +347,8 @@ Cards.allow({ insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update(userId, doc, fields) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)) || _.isEqual(fields, ['vote', 'modifiedAt', 'dateLastActivity']); + update(userId, doc) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); @@ -736,7 +732,7 @@ Cards.helpers({ parentString(sep) { return this.parentList() - .map(function (elem) { + .map(function(elem) { return elem.title; }) .join(sep); @@ -1156,26 +1152,6 @@ Cards.helpers({ isTemplateCard() { return this.type === 'template-card'; }, - - votePublic() { - if (this.vote) return this.vote.public; - return null; - }, - voteAllowNonBoardMembers() { - if (this.vote) return this.vote.allowNonBoardMembers; - return null; - }, - voteCountNegative() { - if (this.vote && this.vote.negative) return this.vote.negative.length; - return null; - }, - voteCountPositive() { - if (this.vote && this.vote.positive) return this.vote.positive.length; - return null; - }, - voteCount() { - return this.voteCountPositive() + this.voteCountNegative() - }, }); Cards.mutations({ @@ -1499,13 +1475,12 @@ Cards.mutations({ }, }; }, - setVoteQuestion(question, public, allowNonBoardMembers) { + setVoteQuestion(question, public) { return { $set: { vote: { question, public, - allowNonBoardMembers, positive: [], negative: [], }, @@ -1954,7 +1929,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; const oldBoardId = this.previous.boardId; @@ -2000,7 +1975,7 @@ if (Meteor.isServer) { // change list modifiedAt, when user modified the key values in timingaction array, if it's endAt, put the modifiedAt of list back to one year ago for sorting purpose const modifiedAt = new Date( new Date(value).getTime() - - (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), + (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), ); // set it as 1 year before const boardId = list.boardId; Lists.direct.update( @@ -2054,7 +2029,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', - function (req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; Authentication.checkBoardAccess(req.userId, paramBoardId); @@ -2064,7 +2039,7 @@ if (Meteor.isServer) { boardId: paramBoardId, swimlaneId: paramSwimlaneId, archived: false, - }).map(function (doc) { + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -2088,7 +2063,7 @@ if (Meteor.isServer) { * title: string, * description: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function ( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { @@ -2101,7 +2076,7 @@ if (Meteor.isServer) { boardId: paramBoardId, listId: paramListId, archived: false, - }).map(function (doc) { + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -2123,7 +2098,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; @@ -2155,7 +2130,7 @@ if (Meteor.isServer) { * @param {string} [assignees] the array of maximum one ID of assignee of the new card * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function ( + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { @@ -2262,7 +2237,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -2561,7 +2536,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', - function (req, res) { + function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; -- cgit v1.2.3-1-g7c22 From 9ae20a3f51e63c29f536e2f5b3e66a2c7d88c691 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 28 Apr 2020 03:24:18 +0300 Subject: Fix Cards and Users docs not generated because of syntax error and new Javascript syntax. Wekan uses wekan/releases/generate-docs*.sh Python code to generate OpenAPI docs, it did not show any errors while generating docs, only left out parts of API docs. Thanks to pvcon13 and xet7 ! --- models/cards.js | 5 +++-- models/users.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'models') diff --git a/models/cards.js b/models/cards.js index 72153132..4197f7ab 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1053,6 +1053,7 @@ Cards.helpers({ return Users.find({ _id: { $in: this.vote.positive } }); return []; }, + voteMemberNegative() { if (this.vote && this.vote.negative) return Users.find({ _id: { $in: this.vote.negative } }); @@ -1475,12 +1476,12 @@ Cards.mutations({ }, }; }, - setVoteQuestion(question, public) { + setVoteQuestion(question, publicVote) { return { $set: { vote: { question, - public, + public: publicVote, positive: [], negative: [], }, diff --git a/models/users.js b/models/users.js index ebb14f5f..2d84141c 100644 --- a/models/users.js +++ b/models/users.js @@ -531,7 +531,7 @@ Users.helpers({ getStartDayOfWeek() { const profile = this.profile || {}; // default is 'Monday' (1) - return profile.startDayOfWeek ?? 1; + return profile.startDayOfWeek || 1; }, getTemplatesBoardId() { -- cgit v1.2.3-1-g7c22 From 153d729544fb5bbfa5fa40d236303cbc01352334 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Tue, 28 Apr 2020 20:50:55 +0200 Subject: Fix getStartDayOfWeek function In case profile.startDayOfWeek is 0 it's evaluated to false and 1 is returned. Let's fix this by differentiating between undefined and an actual value. Fixes: 9ae20a3f51e63c29f536e2f5b3e66a2c7d88c691 --- models/users.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'models') diff --git a/models/users.js b/models/users.js index 2d84141c..a1bc5b0f 100644 --- a/models/users.js +++ b/models/users.js @@ -530,8 +530,11 @@ Users.helpers({ getStartDayOfWeek() { const profile = this.profile || {}; - // default is 'Monday' (1) - return profile.startDayOfWeek || 1; + if (typeof profile.startDayOfWeek === 'undefined') { + // default is 'Monday' (1) + return 1; + } + return profile.startDayOfWeek; }, getTemplatesBoardId() { -- cgit v1.2.3-1-g7c22