summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/accountSettings.js12
-rw-r--r--models/activities.js14
-rw-r--r--models/attachments.js12
-rw-r--r--models/boards.js313
-rw-r--r--models/cards.js134
-rw-r--r--models/checklists.js2
-rw-r--r--models/export.js18
-rw-r--r--models/lists.js6
-rw-r--r--models/settings.js20
-rw-r--r--models/trelloCreator.js24
-rw-r--r--models/users.js286
-rw-r--r--models/wekanCreator.js2
12 files changed, 750 insertions, 93 deletions
diff --git a/models/accountSettings.js b/models/accountSettings.js
index f61614b8..a20303f5 100644
--- a/models/accountSettings.js
+++ b/models/accountSettings.js
@@ -82,4 +82,16 @@ if (Meteor.isServer) {
});
}
+AccountSettings.helpers({
+ allowEmailChange() {
+ return AccountSettings.findOne('accounts-allowEmailChange').booleanValue;
+ },
+ allowUserNameChange() {
+ return AccountSettings.findOne('accounts-allowUserNameChange').booleanValue;
+ },
+ allowUserDelete() {
+ return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
+ },
+});
+
export default AccountSettings;
diff --git a/models/activities.js b/models/activities.js
index 19e3fb7d..b5fcb7d8 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;
@@ -279,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({
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/boards.js b/models/boards.js
index 857aa963..26dc6127 100644
--- a/models/boards.js
+++ b/models/boards.js
@@ -185,6 +185,7 @@ Boards.attachSchema(
isActive: true,
isNoComments: false,
isCommentOnly: false,
+ isWorker: false,
},
];
}
@@ -222,6 +223,13 @@ Boards.attachSchema(
type: Boolean,
optional: true,
},
+ 'members.$.isWorker': {
+ /**
+ * Is the member only allowed to move card, assign himself to card and comment
+ */
+ type: Boolean,
+ optional: true,
+ },
permission: {
/**
* visibility of the board
@@ -270,6 +278,7 @@ Boards.attachSchema(
optional: true,
defaultValue: null,
},
+
subtasksDefaultListId: {
/**
* The default List ID assigned to subtasks.
@@ -278,6 +287,19 @@ Boards.attachSchema(
optional: true,
defaultValue: null,
},
+
+ dateSettingsDefaultBoardId: {
+ type: String,
+ optional: true,
+ defaultValue: null,
+ },
+
+ dateSettingsDefaultListId: {
+ type: String,
+ optional: true,
+ defaultValue: null,
+ },
+
allowsSubtasks: {
/**
* Does the board allows subtasks?
@@ -285,6 +307,127 @@ Boards.attachSchema(
type: Boolean,
defaultValue: true,
},
+
+ allowsAttachments: {
+ /**
+ * Does the board allows attachments?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsChecklists: {
+ /**
+ * Does the board allows checklists?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsComments: {
+ /**
+ * Does the board allows comments?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsDescriptionTitle: {
+ /**
+ * Does the board allows description title?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsDescriptionText: {
+ /**
+ * Does the board allows description text?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsActivities: {
+ /**
+ * Does the board allows comments?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsLabels: {
+ /**
+ * Does the board allows labels?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsAssignee: {
+ /**
+ * Does the board allows assignee?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsMembers: {
+ /**
+ * Does the board allows members?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsRequestedBy: {
+ /**
+ * Does the board allows requested by?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsAssignedBy: {
+ /**
+ * Does the board allows requested by?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsReceivedDate: {
+ /**
+ * Does the board allows received date?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsStartDate: {
+ /**
+ * Does the board allows start date?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsEndDate: {
+ /**
+ * Does the board allows end date?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
+ allowsDueDate: {
+ /**
+ * Does the board allows due date?
+ */
+ type: Boolean,
+ defaultValue: true,
+ },
+
presentParentTask: {
/**
* Controls how to present the parent task:
@@ -350,6 +493,14 @@ Boards.attachSchema(
type: String,
defaultValue: 'board',
},
+ sort: {
+ /**
+ * Sort value
+ */
+ type: Number,
+ decimal: true,
+ defaultValue: -1,
+ },
}),
);
@@ -538,6 +689,7 @@ Boards.helpers({
isActive: true,
isAdmin: false,
isNoComments: true,
+ isWorker: false,
});
},
@@ -547,6 +699,17 @@ Boards.helpers({
isActive: true,
isAdmin: false,
isCommentOnly: true,
+ isWorker: false,
+ });
+ },
+
+ hasWorker(memberId) {
+ return !!_.findWhere(this.members, {
+ userId: memberId,
+ isActive: true,
+ isAdmin: false,
+ isCommentOnly: false,
+ isWorker: true,
});
},
@@ -651,7 +814,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);
@@ -690,6 +857,39 @@ Boards.helpers({
return Boards.findOne(this.getDefaultSubtasksBoardId());
},
+ //Date Settings option such as received date, start date and so on.
+ getDefaultDateSettingsBoardId() {
+ if (
+ this.dateSettingsDefaultBoardId === null ||
+ this.dateSettingsDefaultBoardId === undefined
+ ) {
+ this.dateSettingsDefaultBoardId = Boards.insert({
+ title: `^${this.title}^`,
+ permission: this.permission,
+ members: this.members,
+ color: this.color,
+ description: TAPi18n.__('default-dates-board', {
+ board: this.title,
+ }),
+ });
+
+ Swimlanes.insert({
+ title: TAPi18n.__('default'),
+ boardId: this.dateSettingsDefaultBoardId,
+ });
+ Boards.update(this._id, {
+ $set: {
+ dateSettingsDefaultBoardId: this.dateSettingsDefaultBoardId,
+ },
+ });
+ }
+ return this.dateSettingsDefaultBoardId;
+ },
+
+ getDefaultDateSettingsBoard() {
+ return Boards.findOne(this.getDefaultDateSettingsBoardId());
+ },
+
getDefaultSubtasksListId() {
if (
this.subtasksDefaultListId === null ||
@@ -708,6 +908,24 @@ Boards.helpers({
return Lists.findOne(this.getDefaultSubtasksListId());
},
+ getDefaultDateSettingsListId() {
+ if (
+ this.dateSettingsDefaultListId === null ||
+ this.dateSettingsDefaultListId === undefined
+ ) {
+ this.dateSettingsDefaultListId = Lists.insert({
+ title: TAPi18n.__('queue'),
+ boardId: this._id,
+ });
+ this.setDateSettingsDefaultListId(this.dateSettingsDefaultListId);
+ }
+ return this.dateSettingsDefaultListId;
+ },
+
+ getDefaultDateSettingsList() {
+ return Lists.findOne(this.getDefaultDateSettingsListId());
+ },
+
getDefaultSwimline() {
let result = Swimlanes.findOne({ boardId: this._id });
if (result === undefined) {
@@ -849,6 +1067,7 @@ Boards.mutations({
isActive: true,
isNoComments: false,
isCommentOnly: false,
+ isWorker: false,
},
},
};
@@ -881,6 +1100,7 @@ Boards.mutations({
isAdmin,
isNoComments,
isCommentOnly,
+ isWorker,
currentUserId = Meteor.userId(),
) {
const memberIndex = this.memberIndex(memberId);
@@ -894,6 +1114,7 @@ Boards.mutations({
[`members.${memberIndex}.isAdmin`]: isAdmin,
[`members.${memberIndex}.isNoComments`]: isNoComments,
[`members.${memberIndex}.isCommentOnly`]: isCommentOnly,
+ [`members.${memberIndex}.isWorker`]: isWorker,
},
};
},
@@ -902,6 +1123,66 @@ Boards.mutations({
return { $set: { allowsSubtasks } };
},
+ setAllowsMembers(allowsMembers) {
+ return { $set: { allowsMembers } };
+ },
+
+ setAllowsChecklists(allowsChecklists) {
+ return { $set: { allowsChecklists } };
+ },
+
+ setAllowsAssignee(allowsAssignee) {
+ return { $set: { allowsAssignee } };
+ },
+
+ setAllowsAssignedBy(allowsAssignedBy) {
+ return { $set: { allowsAssignedBy } };
+ },
+
+ setAllowsRequestedBy(allowsRequestedBy) {
+ return { $set: { allowsRequestedBy } };
+ },
+
+ setAllowsAttachments(allowsAttachments) {
+ return { $set: { allowsAttachments } };
+ },
+
+ setAllowsLabels(allowsLabels) {
+ return { $set: { allowsLabels } };
+ },
+
+ setAllowsComments(allowsComments) {
+ return { $set: { allowsComments } };
+ },
+
+ setAllowsDescriptionTitle(allowsDescriptionTitle) {
+ return { $set: { allowsDescriptionTitle } };
+ },
+
+ setAllowsDescriptionText(allowsDescriptionText) {
+ return { $set: { allowsDescriptionText } };
+ },
+
+ setAllowsActivities(allowsActivities) {
+ return { $set: { allowsActivities } };
+ },
+
+ setAllowsReceivedDate(allowsReceivedDate) {
+ return { $set: { allowsReceivedDate } };
+ },
+
+ setAllowsStartDate(allowsStartDate) {
+ return { $set: { allowsStartDate } };
+ },
+
+ setAllowsEndDate(allowsEndDate) {
+ return { $set: { allowsEndDate } };
+ },
+
+ setAllowsDueDate(allowsDueDate) {
+ return { $set: { allowsDueDate } };
+ },
+
setSubtasksDefaultBoardId(subtasksDefaultBoardId) {
return { $set: { subtasksDefaultBoardId } };
},
@@ -913,6 +1194,10 @@ Boards.mutations({
setPresentParentTask(presentParentTask) {
return { $set: { presentParentTask } };
},
+
+ move(sortIndex) {
+ return { $set: { sort: sortIndex } };
+ },
});
function boardRemover(userId, doc) {
@@ -1010,6 +1295,17 @@ 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(() => {
@@ -1193,7 +1489,7 @@ if (Meteor.isServer) {
'members.userId': paramUserId,
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
).map(function(board) {
return {
@@ -1223,7 +1519,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,
@@ -1281,6 +1582,7 @@ if (Meteor.isServer) {
* @param {boolean} [isActive] is the board active (default true)
* @param {boolean} [isNoComments] disable comments (default false)
* @param {boolean} [isCommentOnly] only enable comments (default false)
+ * @param {boolean} [isWorker] only move cards, assign himself to card and comment (default false)
* @param {string} [permission] "private" board <== Set to "public" if you
* want public Wekan board
* @param {string} [color] the color of the board
@@ -1300,6 +1602,7 @@ if (Meteor.isServer) {
isActive: req.body.isActive || true,
isNoComments: req.body.isNoComments || false,
isCommentOnly: req.body.isCommentOnly || false,
+ isWorker: req.body.isWorker || false,
},
],
permission: req.body.permission || 'private',
@@ -1403,6 +1706,7 @@ if (Meteor.isServer) {
* @param {boolean} isAdmin admin capability
* @param {boolean} isNoComments NoComments capability
* @param {boolean} isCommentOnly CommentsOnly capability
+ * @param {boolean} isWorker Worker capability
*/
JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function(
req,
@@ -1411,7 +1715,7 @@ if (Meteor.isServer) {
try {
const boardId = req.params.boardId;
const memberId = req.params.memberId;
- const { isAdmin, isNoComments, isCommentOnly } = req.body;
+ const { isAdmin, isNoComments, isCommentOnly, isWorker } = req.body;
Authentication.checkBoardAccess(req.userId, boardId);
const board = Boards.findOne({ _id: boardId });
function isTrue(data) {
@@ -1426,6 +1730,7 @@ if (Meteor.isServer) {
isTrue(isAdmin),
isTrue(isNoComments),
isTrue(isCommentOnly),
+ isTrue(isWorker),
req.userId,
);
diff --git a/models/cards.js b/models/cards.js
index 496c69b3..4197f7ab 100644
--- a/models/cards.js
+++ b/models/cards.js
@@ -304,6 +304,42 @@ 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,
+ },
+ 'vote.public': {
+ type: Boolean,
+ defaultValue: false,
+ },
}),
);
@@ -980,6 +1016,50 @@ 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;
+ }
+ },
+
+ 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;
@@ -1396,6 +1476,58 @@ Cards.mutations({
},
};
},
+ setVoteQuestion(question, publicVote) {
+ return {
+ $set: {
+ vote: {
+ question,
+ public: publicVote,
+ 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
@@ -2008,7 +2140,7 @@ if (Meteor.isServer) {
const paramBoardId = req.params.boardId;
// Check user has permission to add card to the board
const board = Boards.findOne({
- _id: paramBoardId
+ _id: paramBoardId,
});
const addPermission = allowIsBoardMemberCommentOnly(req.userId, board);
Authentication.checkAdminOrCondition(req.userId, addPermission);
diff --git a/models/checklists.js b/models/checklists.js
index 11aba71b..cf73e500 100644
--- a/models/checklists.js
+++ b/models/checklists.js
@@ -288,7 +288,7 @@ if (Meteor.isServer) {
const paramBoardId = req.params.boardId;
// Check user has permission to add checklist to the card
const board = Boards.findOne({
- _id: paramBoardId
+ _id: paramBoardId,
});
const addPermission = allowIsBoardMemberCommentOnly(req.userId, board);
Authentication.checkAdminOrCondition(req.userId, addPermission);
diff --git a/models/export.js b/models/export.js
index cc979ce0..339123c8 100644
--- a/models/export.js
+++ b/models/export.js
@@ -24,7 +24,6 @@ if (Meteor.isServer) {
JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) {
const boardId = req.params.boardId;
let user = null;
-
const loginToken = req.query.authToken;
if (loginToken) {
const hashToken = Accounts._hashLoginToken(loginToken);
@@ -35,7 +34,6 @@ if (Meteor.isServer) {
Authentication.checkUserId(req.userId);
user = Users.findOne({ _id: req.userId, isAdmin: true });
}
-
const exporter = new Exporter(boardId);
if (exporter.canExport(user)) {
JsonRoutes.sendResult(res, {
@@ -137,8 +135,11 @@ export class Exporter {
// [Old] for attachments we only export IDs and absolute url to original doc
// [New] Encode attachment to base64
+
const getBase64Data = function(doc, callback) {
- let buffer = new Buffer(0);
+ let buffer = Buffer.allocUnsafe(0);
+ buffer.fill(0);
+
// callback has the form function (err, res) {}
const tmpFile = path.join(
os.tmpdir(),
@@ -149,14 +150,16 @@ export class Exporter {
readStream.on('data', function(chunk) {
buffer = Buffer.concat([buffer, chunk]);
});
+
readStream.on('error', function(err) {
- callback(err, null);
+ callback(null, null);
});
readStream.on('end', function() {
// done
fs.unlink(tmpFile, () => {
//ignored
});
+
callback(null, buffer.toString('base64'));
});
readStream.pipe(tmpWriteable);
@@ -165,11 +168,14 @@ export class Exporter {
result.attachments = Attachments.find(byBoard)
.fetch()
.map(attachment => {
+ let filebase64 = null;
+ filebase64 = getBase64DataSync(attachment);
+
return {
_id: attachment._id,
cardId: attachment.cardId,
- // url: FlowRouter.url(attachment.url()),
- file: getBase64DataSync(attachment),
+ //url: FlowRouter.url(attachment.url()),
+ file: filebase64,
name: attachment.original.name,
type: attachment.original.type,
};
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/settings.js b/models/settings.js
index 8eb02c5b..fb823205 100644
--- a/models/settings.js
+++ b/models/settings.js
@@ -33,14 +33,6 @@ Settings.attachSchema(
type: String,
optional: true,
},
- customHTMLafterBodyStart: {
- type: String,
- optional: true,
- },
- customHTMLbeforeBodyEnd: {
- type: String,
- optional: true,
- },
displayAuthenticationMethod: {
type: Boolean,
optional: true,
@@ -206,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]);
@@ -322,6 +318,10 @@ if (Meteor.isServer) {
return isCasEnabled();
},
+ _isApiEnabled() {
+ return isApiEnabled();
+ },
+
// Gets all connection methods to use it in the Template
getAuthenticationsEnabled() {
return {
@@ -334,6 +334,10 @@ if (Meteor.isServer) {
getDefaultAuthenticationMethod() {
return process.env.DEFAULT_AUTHENTICATION_METHOD;
},
+
+ isPasswordLoginDisabled() {
+ return process.env.PASSWORD_LOGIN_ENABLED === 'false';
+ },
});
}
diff --git a/models/trelloCreator.js b/models/trelloCreator.js
index cb1a6a67..1c5bcd93 100644
--- a/models/trelloCreator.js
+++ b/models/trelloCreator.js
@@ -285,6 +285,30 @@ 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,
+ public: true,
+ positive: positiveVotes,
+ };
+ }
+ }
+
// insert card
const cardId = Cards.direct.insert(cardToCreate);
// keep track of Trello id => Wekan id
diff --git a/models/users.js b/models/users.js
index 83a224ba..a1bc5b0f 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 =
@@ -165,7 +167,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': {
@@ -175,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
@@ -352,8 +374,18 @@ if (Meteor.isClient) {
return board && board.hasCommentOnly(this._id);
},
- isBoardAdmin() {
+ isNotWorker() {
+ const board = Boards.findOne(Session.get('currentBoard'));
+ return board && board.hasMember(this._id) && !board.hasWorker(this._id);
+ },
+
+ isWorker() {
const board = Boards.findOne(Session.get('currentBoard'));
+ return board && board.hasWorker(this._id);
+ },
+
+ isBoardAdmin(boardId = Session.get('currentBoard')) {
+ const board = Boards.findOne(boardId);
return board && board.hasAdmin(this._id);
},
});
@@ -361,12 +393,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) {
@@ -376,7 +416,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) {
@@ -419,6 +464,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;
@@ -469,6 +528,15 @@ Users.helpers({
return profile.language || 'en';
},
+ getStartDayOfWeek() {
+ const profile = this.profile || {};
+ if (typeof profile.startDayOfWeek === 'undefined') {
+ // default is 'Monday' (1)
+ return 1;
+ }
+ return profile.startDayOfWeek;
+ },
+
getTemplatesBoardId() {
return (this.profile || {}).templatesBoardId;
},
@@ -563,7 +631,7 @@ Users.mutations({
addNotification(activityId) {
return {
$addToSet: {
- 'profile.notifications': activityId,
+ 'profile.notifications': { activity: activityId },
},
};
},
@@ -571,7 +639,7 @@ Users.mutations({
removeNotification(activityId) {
return {
$pull: {
- 'profile.notifications': activityId,
+ 'profile.notifications': { activity: activityId },
},
};
},
@@ -600,6 +668,10 @@ Users.mutations({
return { $set: { 'profile.showCardsCountAt': limit } };
},
+ setStartDayOfWeek(startDay) {
+ return { $set: { 'profile.startDayOfWeek': startDay } };
+ },
+
setBoardView(view) {
return {
$set: {
@@ -610,16 +682,6 @@ Users.mutations({
});
Meteor.methods({
- 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);
@@ -640,51 +702,101 @@ 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);
- }
+ changeStartDayOfWeek(startDay) {
+ check(startDay, Number);
+ Meteor.user().setStartDayOfWeek(startDay);
},
});
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);
@@ -716,8 +828,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);
@@ -738,6 +851,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,
@@ -852,6 +975,39 @@ 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_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;
+
+ SyncedCron.add({
+ name: 'notification_cleanup',
+ 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);
+ 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(() => {
@@ -865,6 +1021,9 @@ if (Meteor.isServer) {
},
{ unique: true },
);
+ Meteor.defer(() => {
+ addCronJob();
+ });
});
// OLD WAY THIS CODE DID WORK: When user is last admin of board,
@@ -1170,10 +1329,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);
}
diff --git a/models/wekanCreator.js b/models/wekanCreator.js
index ec85d93f..9914f817 100644
--- a/models/wekanCreator.js
+++ b/models/wekanCreator.js
@@ -441,7 +441,7 @@ export class WekanCreator {
});
} else if (att.file) {
file.attachData(
- new Buffer(att.file, 'base64'),
+ Buffer.from(att.file, 'base64'),
{
type: att.type,
},