Replace mquandalle:collection-mutations with collection helpers

This commit is contained in:
Harry Adel 2026-01-21 19:22:54 +02:00
parent aca661583d
commit 94a3575e2c
35 changed files with 718 additions and 1321 deletions

View file

@ -1431,93 +1431,80 @@ Boards.helpers({
isTemplatesBoard() {
return this.type === 'template-container';
},
});
Boards.mutations({
archive() {
return { $set: { archived: true, archivedAt: new Date() } };
async archive() {
return await Boards.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } });
},
restore() {
return { $set: { archived: false } };
async restore() {
return await Boards.updateAsync(this._id, { $set: { archived: false } });
},
rename(title) {
return { $set: { title } };
async rename(title) {
return await Boards.updateAsync(this._id, { $set: { title } });
},
setDescription(description) {
return { $set: { description } };
async setDescription(description) {
return await Boards.updateAsync(this._id, { $set: { description } });
},
setColor(color) {
return { $set: { color } };
async setColor(color) {
return await Boards.updateAsync(this._id, { $set: { color } });
},
setBackgroundImageURL(backgroundImageURL) {
async setBackgroundImageURL(backgroundImageURL) {
const currentUser = ReactiveCache.getCurrentUser();
if(currentUser.isBoardAdmin()) {
return { $set: { backgroundImageURL } };
} else if (currentUser.isAdmin()) {
return { $set: { backgroundImageURL } };
} else {
return false;
if (currentUser.isBoardAdmin() || currentUser.isAdmin()) {
return await Boards.updateAsync(this._id, { $set: { backgroundImageURL } });
}
return false;
},
setVisibility(visibility) {
return { $set: { permission: visibility } };
async setVisibility(visibility) {
return await Boards.updateAsync(this._id, { $set: { permission: visibility } });
},
addLabel(name, color) {
// If label with the same name and color already exists we don't want to
// create another one because they would be indistinguishable in the UI
// (they would still have different `_id` but that is not exposed to the
// user).
async addLabel(name, color) {
if (!this.getLabel(name, color)) {
const _id = Random.id(6);
return { $push: { labels: { _id, name, color } } };
return await Boards.updateAsync(this._id, { $push: { labels: { _id, name, color } } });
}
return {};
return null;
},
editLabel(labelId, name, color) {
async editLabel(labelId, name, color) {
if (!this.getLabel(name, color)) {
const labelIndex = this.labelIndex(labelId);
return {
return await Boards.updateAsync(this._id, {
$set: {
[`labels.${labelIndex}.name`]: name,
[`labels.${labelIndex}.color`]: color,
},
};
});
}
return {};
return null;
},
removeLabel(labelId) {
return { $pull: { labels: { _id: labelId } } };
async removeLabel(labelId) {
return await Boards.updateAsync(this._id, { $pull: { labels: { _id: labelId } } });
},
changeOwnership(fromId, toId) {
async changeOwnership(fromId, toId) {
const memberIndex = this.memberIndex(fromId);
return {
$set: {
[`members.${memberIndex}.userId`]: toId,
},
};
return await Boards.updateAsync(this._id, {
$set: { [`members.${memberIndex}.userId`]: toId },
});
},
addMember(memberId) {
async addMember(memberId) {
const memberIndex = this.memberIndex(memberId);
if (memberIndex >= 0) {
return {
$set: {
[`members.${memberIndex}.isActive`]: true,
},
};
return await Boards.updateAsync(this._id, {
$set: { [`members.${memberIndex}.isActive`]: true },
});
}
return {
return await Boards.updateAsync(this._id, {
$push: {
members: {
userId: memberId,
@ -1532,32 +1519,28 @@ Boards.mutations({
isReadAssignedOnly: false,
},
},
};
});
},
removeMember(memberId) {
async removeMember(memberId) {
const memberIndex = this.memberIndex(memberId);
// we do not allow the only one admin to be removed
const allowRemove =
!this.members[memberIndex].isAdmin || this.activeAdmins().length > 1;
if (!allowRemove) {
return {
$set: {
[`members.${memberIndex}.isActive`]: true,
},
};
return await Boards.updateAsync(this._id, {
$set: { [`members.${memberIndex}.isActive`]: true },
});
}
return {
return await Boards.updateAsync(this._id, {
$set: {
[`members.${memberIndex}.isActive`]: false,
[`members.${memberIndex}.isAdmin`]: false,
},
};
});
},
setMemberPermission(
async setMemberPermission(
memberId,
isAdmin,
isNoComments,
@ -1570,12 +1553,11 @@ Boards.mutations({
currentUserId = Meteor.userId(),
) {
const memberIndex = this.memberIndex(memberId);
// do not allow change permission of self
if (memberId === currentUserId) {
isAdmin = this.members[memberIndex].isAdmin;
}
return {
return await Boards.updateAsync(this._id, {
$set: {
[`members.${memberIndex}.isAdmin`]: isAdmin,
[`members.${memberIndex}.isNoComments`]: isNoComments,
@ -1586,144 +1568,143 @@ Boards.mutations({
[`members.${memberIndex}.isReadOnly`]: isReadOnly,
[`members.${memberIndex}.isReadAssignedOnly`]: isReadAssignedOnly,
},
};
});
},
setAllowsSubtasks(allowsSubtasks) {
return { $set: { allowsSubtasks } };
async setAllowsSubtasks(allowsSubtasks) {
return await Boards.updateAsync(this._id, { $set: { allowsSubtasks } });
},
setAllowsCreator(allowsCreator) {
return { $set: { allowsCreator } };
async setAllowsCreator(allowsCreator) {
return await Boards.updateAsync(this._id, { $set: { allowsCreator } });
},
setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) {
return { $set: { allowsCreatorOnMinicard } };
async setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) {
return await Boards.updateAsync(this._id, { $set: { allowsCreatorOnMinicard } });
},
setAllowsMembers(allowsMembers) {
return { $set: { allowsMembers } };
async setAllowsMembers(allowsMembers) {
return await Boards.updateAsync(this._id, { $set: { allowsMembers } });
},
setAllowsChecklists(allowsChecklists) {
return { $set: { allowsChecklists } };
async setAllowsChecklists(allowsChecklists) {
return await Boards.updateAsync(this._id, { $set: { allowsChecklists } });
},
setAllowsAssignee(allowsAssignee) {
return { $set: { allowsAssignee } };
async setAllowsAssignee(allowsAssignee) {
return await Boards.updateAsync(this._id, { $set: { allowsAssignee } });
},
setAllowsAssignedBy(allowsAssignedBy) {
return { $set: { allowsAssignedBy } };
async setAllowsAssignedBy(allowsAssignedBy) {
return await Boards.updateAsync(this._id, { $set: { allowsAssignedBy } });
},
setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) {
return { $set: { allowsShowListsOnMinicard } };
async setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) {
return await Boards.updateAsync(this._id, { $set: { allowsShowListsOnMinicard } });
},
setAllowsChecklistAtMinicard(allowsChecklistAtMinicard) {
return { $set: { allowsChecklistAtMinicard } };
async setAllowsChecklistAtMinicard(allowsChecklistAtMinicard) {
return await Boards.updateAsync(this._id, { $set: { allowsChecklistAtMinicard } });
},
setAllowsRequestedBy(allowsRequestedBy) {
return { $set: { allowsRequestedBy } };
async setAllowsRequestedBy(allowsRequestedBy) {
return await Boards.updateAsync(this._id, { $set: { allowsRequestedBy } });
},
setAllowsCardSortingByNumber(allowsCardSortingByNumber) {
return { $set: { allowsCardSortingByNumber } };
async setAllowsCardSortingByNumber(allowsCardSortingByNumber) {
return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumber } });
},
setAllowsShowLists(allowsShowLists) {
return { $set: { allowsShowLists } };
async setAllowsShowLists(allowsShowLists) {
return await Boards.updateAsync(this._id, { $set: { allowsShowLists } });
},
setAllowsAttachments(allowsAttachments) {
return { $set: { allowsAttachments } };
async setAllowsAttachments(allowsAttachments) {
return await Boards.updateAsync(this._id, { $set: { allowsAttachments } });
},
setAllowsLabels(allowsLabels) {
return { $set: { allowsLabels } };
async setAllowsLabels(allowsLabels) {
return await Boards.updateAsync(this._id, { $set: { allowsLabels } });
},
setAllowsComments(allowsComments) {
return { $set: { allowsComments } };
async setAllowsComments(allowsComments) {
return await Boards.updateAsync(this._id, { $set: { allowsComments } });
},
setAllowsDescriptionTitle(allowsDescriptionTitle) {
return { $set: { allowsDescriptionTitle } };
async setAllowsDescriptionTitle(allowsDescriptionTitle) {
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTitle } });
},
setAllowsCardNumber(allowsCardNumber) {
return { $set: { allowsCardNumber } };
async setAllowsCardNumber(allowsCardNumber) {
return await Boards.updateAsync(this._id, { $set: { allowsCardNumber } });
},
setAllowsDescriptionText(allowsDescriptionText) {
return { $set: { allowsDescriptionText } };
async setAllowsDescriptionText(allowsDescriptionText) {
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionText } });
},
setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) {
return { $set: { allowsDescriptionTextOnMinicard } };
async setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) {
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTextOnMinicard } });
},
setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) {
return { $set: { allowsCoverAttachmentOnMinicard } };
async setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) {
return await Boards.updateAsync(this._id, { $set: { allowsCoverAttachmentOnMinicard } });
},
setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) {
return { $set: { allowsBadgeAttachmentOnMinicard } };
async setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) {
return await Boards.updateAsync(this._id, { $set: { allowsBadgeAttachmentOnMinicard } });
},
setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) {
return { $set: { allowsCardSortingByNumberOnMinicard } };
async setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) {
return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumberOnMinicard } });
},
setAllowsActivities(allowsActivities) {
return { $set: { allowsActivities } };
async setAllowsActivities(allowsActivities) {
return await Boards.updateAsync(this._id, { $set: { allowsActivities } });
},
setAllowsReceivedDate(allowsReceivedDate) {
return { $set: { allowsReceivedDate } };
async setAllowsReceivedDate(allowsReceivedDate) {
return await Boards.updateAsync(this._id, { $set: { allowsReceivedDate } });
},
setAllowsCardCounterList(allowsCardCounterList) {
return { $set: { allowsCardCounterList } };
async setAllowsCardCounterList(allowsCardCounterList) {
return await Boards.updateAsync(this._id, { $set: { allowsCardCounterList } });
},
setAllowsBoardMemberList(allowsBoardMemberList) {
return { $set: { allowsBoardMemberList } };
async setAllowsBoardMemberList(allowsBoardMemberList) {
return await Boards.updateAsync(this._id, { $set: { allowsBoardMemberList } });
},
setAllowsStartDate(allowsStartDate) {
return { $set: { allowsStartDate } };
async setAllowsStartDate(allowsStartDate) {
return await Boards.updateAsync(this._id, { $set: { allowsStartDate } });
},
setAllowsEndDate(allowsEndDate) {
return { $set: { allowsEndDate } };
async setAllowsEndDate(allowsEndDate) {
return await Boards.updateAsync(this._id, { $set: { allowsEndDate } });
},
setAllowsDueDate(allowsDueDate) {
return { $set: { allowsDueDate } };
async setAllowsDueDate(allowsDueDate) {
return await Boards.updateAsync(this._id, { $set: { allowsDueDate } });
},
setSubtasksDefaultBoardId(subtasksDefaultBoardId) {
return { $set: { subtasksDefaultBoardId } };
async setSubtasksDefaultBoardId(subtasksDefaultBoardId) {
return await Boards.updateAsync(this._id, { $set: { subtasksDefaultBoardId } });
},
setSubtasksDefaultListId(subtasksDefaultListId) {
return { $set: { subtasksDefaultListId } };
async setSubtasksDefaultListId(subtasksDefaultListId) {
return await Boards.updateAsync(this._id, { $set: { subtasksDefaultListId } });
},
setPresentParentTask(presentParentTask) {
return { $set: { presentParentTask } };
async setPresentParentTask(presentParentTask) {
return await Boards.updateAsync(this._id, { $set: { presentParentTask } });
},
move(sortIndex) {
return { $set: { sort: sortIndex } };
async move(sortIndex) {
return await Boards.updateAsync(this._id, { $set: { sort: sortIndex } });
},
toggleShowActivities() {
return { $set: { showActivities: !this.showActivities } };
async toggleShowActivities() {
return await Boards.updateAsync(this._id, { $set: { showActivities: !this.showActivities } });
},
});
@ -1909,14 +1890,14 @@ if (Meteor.isServer) {
check(boardId, String);
return ReactiveCache.getBoard(boardId, {}, { backgroundImageUrl: 1 });
},
quitBoard(boardId) {
async quitBoard(boardId) {
check(boardId, String);
const board = ReactiveCache.getBoard(boardId);
if (board) {
const userId = Meteor.userId();
const index = board.memberIndex(userId);
if (index >= 0) {
board.removeMember(userId);
await board.removeMember(userId);
return true;
} else throw new Meteor.Error('error-board-notAMember');
} else throw new Meteor.Error('error-board-doesNotExist');
@ -1993,14 +1974,14 @@ if (Meteor.isServer) {
});
Meteor.methods({
archiveBoard(boardId) {
async archiveBoard(boardId) {
check(boardId, String);
const board = ReactiveCache.getBoard(boardId);
if (board) {
const userId = Meteor.userId();
const index = board.memberIndex(userId);
if (index >= 0) {
board.archive();
await board.archive();
return true;
} else throw new Meteor.Error('error-board-notAMember');
} else throw new Meteor.Error('error-board-doesNotExist');
@ -2159,7 +2140,7 @@ if (Meteor.isServer) {
}
if (modifier.$set) {
const boardId = doc._id;
foreachRemovedMember(doc, modifier.$set, memberId => {
foreachRemovedMember(doc, modifier.$set, async memberId => {
Cards.update(
{ boardId },
{
@ -2182,7 +2163,7 @@ if (Meteor.isServer) {
);
const board = Boards._transform(doc);
board.setWatcher(memberId, false);
await board.setWatcher(memberId, false);
// Remove board from users starred list
if (!board.isPublic()) {
@ -2589,7 +2570,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
* @param {boolean} isReadOnly ReadOnly capability
* @param {boolean} isReadAssignedOnly ReadAssignedOnly capability
*/
JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function(
JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', async function(
req,
res,
) {
@ -2606,7 +2587,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
return data;
}
}
const query = board.setMemberPermission(
const query = await board.setMemberPermission(
memberId,
isTrue(isAdmin),
isTrue(isNoComments),

View file

@ -572,15 +572,17 @@ Cards.helpers({
});
},
mapCustomFieldsToBoard(boardId) {
async mapCustomFieldsToBoard(boardId) {
// Map custom fields to new board
return this.customFields.map(cf => {
const result = [];
for (const cf of this.customFields) {
const oldCf = ReactiveCache.getCustomField(cf._id);
// Check if oldCf is undefined or null
if (!oldCf) {
//console.error(`Custom field with ID ${cf._id} not found.`);
return cf; // Skip this field if oldCf is not found
result.push(cf); // Skip this field if oldCf is not found
continue;
}
const newCf = ReactiveCache.getCustomField({
@ -592,11 +594,12 @@ Cards.helpers({
if (newCf) {
cf._id = newCf._id;
} else if (!_.contains(oldCf.boardIds, boardId)) {
oldCf.addBoard(boardId);
await oldCf.addBoard(boardId);
}
return cf;
});
result.push(cf);
}
return result;
},
@ -1203,11 +1206,11 @@ Cards.helpers({
}
},
assignMember(memberId) {
async assignMember(memberId) {
let ret;
if (this.isLinkedBoard()) {
const board = ReactiveCache.getBoard(this.linkedId);
ret = board.addMember(memberId);
ret = await board.addMember(memberId);
} else {
ret = Cards.update(
{ _id: this.getRealId() },
@ -1234,7 +1237,7 @@ Cards.helpers({
}
},
unassignMember(memberId) {
async unassignMember(memberId) {
if (this.isLinkedCard()) {
return Cards.update(
{ _id: this.linkedId },
@ -1242,7 +1245,7 @@ Cards.helpers({
);
} else if (this.isLinkedBoard()) {
const board = ReactiveCache.getBoard(this.linkedId);
return board.removeMember(memberId);
return await board.removeMember(memberId);
} else {
return Cards.update({ _id: this._id }, { $pull: { members: memberId } });
}
@ -1991,52 +1994,41 @@ Cards.helpers({
}
return pokerWinnersListMap[0].pokerCard;
},
});
Cards.mutations({
applyToChildren(funct) {
ReactiveCache.getCards({
parentId: this._id,
}).forEach(card => {
funct(card);
async applyToChildren(funct) {
const cards = ReactiveCache.getCards({ parentId: this._id });
for (const card of cards) {
await funct(card);
}
},
async archive() {
await this.applyToChildren(async card => {
await card.archive();
});
return await Cards.updateAsync(this._id, {
$set: { archived: true, archivedAt: new Date() },
});
},
archive() {
this.applyToChildren(card => {
return card.archive();
async restore() {
await this.applyToChildren(async card => {
await card.restore();
});
return await Cards.updateAsync(this._id, {
$set: { archived: false },
});
return {
$set: {
archived: true,
archivedAt: new Date(),
},
};
},
restore() {
this.applyToChildren(card => {
return card.restore();
});
return {
$set: {
archived: false,
},
};
},
moveToEndOfList({ listId, swimlaneId } = {}) {
async moveToEndOfList({ listId, swimlaneId } = {}) {
swimlaneId = swimlaneId || this.swimlaneId;
const boardId = this.boardId;
let sortIndex = 0;
// This should never happen, but there was a bug that was fixed in commit
// ea0239538a68e225c867411a4f3e0d27c158383.
if (!swimlaneId) {
const board = ReactiveCache.getBoard(boardId);
swimlaneId = board.getDefaultSwimline()._id;
}
// Move the minicard to the end of the target list
let parentElementDom = $(`#swimlane-${swimlaneId}`).get(0);
if (!parentElementDom) parentElementDom = $(':root');
@ -2045,7 +2037,7 @@ Cards.mutations({
.get(0);
if (lastCardDom) sortIndex = Utils.calculateIndex(lastCardDom, null).base;
return this.moveOptionalArgs({
return await this.moveOptionalArgs({
boardId,
swimlaneId,
listId,
@ -2053,22 +2045,19 @@ Cards.mutations({
});
},
moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) {
async moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) {
boardId = boardId || this.boardId;
swimlaneId = swimlaneId || this.swimlaneId;
// This should never happen, but there was a bug that was fixed in commit
// ea0239538a68e225c867411a4f3e0d27c158383.
if (!swimlaneId) {
const board = ReactiveCache.getBoard(boardId);
swimlaneId = board.getDefaultSwimline()._id;
}
listId = listId || this.listId;
if (sort === undefined || sort === null) sort = this.sort;
return this.move(boardId, swimlaneId, listId, sort);
return await this.move(boardId, swimlaneId, listId, sort);
},
move(boardId, swimlaneId, listId, sort = null) {
// Capture previous state for history tracking
async move(boardId, swimlaneId, listId, sort = null) {
const previousState = {
boardId: this.boardId,
swimlaneId: this.swimlaneId,
@ -2076,20 +2065,13 @@ Cards.mutations({
sort: this.sort,
};
const mutatedFields = {
boardId,
swimlaneId,
listId,
};
const mutatedFields = { boardId, swimlaneId, listId };
if (sort !== null) {
mutatedFields.sort = sort;
}
// we must only copy the labels and custom fields if the target board
// differs from the source board
if (this.boardId !== boardId) {
// Get label names
const oldBoard = ReactiveCache.getBoard(this.boardId);
const oldBoardLabels = oldBoard.labels;
const oldCardLabels = _.pluck(
@ -2108,7 +2090,6 @@ Cards.mutations({
'_id',
);
// assign the new card number from the target board
const newCardNumber = newBoard.getNextCardNumber();
Object.assign(mutatedFields, {
@ -2119,11 +2100,8 @@ Cards.mutations({
mutatedFields.customFields = this.mapCustomFieldsToBoard(newBoard._id);
}
Cards.update(this._id, {
$set: mutatedFields,
});
await Cards.updateAsync(this._id, { $set: mutatedFields });
// Track position change in user history (server-side only)
if (Meteor.isServer && Meteor.userId() && typeof UserPositionHistory !== 'undefined') {
try {
UserPositionHistory.trackChange({
@ -2145,7 +2123,6 @@ Cards.mutations({
}
}
// Ensure attachments follow the card to its new board/list/swimlane
if (Meteor.isServer) {
const updateMeta = {};
if (mutatedFields.boardId !== undefined) updateMeta['meta.boardId'] = mutatedFields.boardId;
@ -2154,293 +2131,159 @@ Cards.mutations({
if (Object.keys(updateMeta).length > 0) {
try {
Attachments.collection.update(
await Attachments.collection.updateAsync(
{ 'meta.cardId': this._id },
{ $set: updateMeta },
{ multi: true },
);
} catch (err) {
// Do not block the move if attachment update fails
// eslint-disable-next-line no-console
console.error('Failed to update attachments metadata after moving card', this._id, err);
}
}
}
},
addLabel(labelId) {
async addLabel(labelId) {
this.labelIds.push(labelId);
return {
$addToSet: {
labelIds: labelId,
},
};
return await Cards.updateAsync(this._id, { $addToSet: { labelIds: labelId } });
},
removeLabel(labelId) {
async removeLabel(labelId) {
this.labelIds = _.without(this.labelIds, labelId);
return {
$pull: {
labelIds: labelId,
},
};
return await Cards.updateAsync(this._id, { $pull: { labelIds: labelId } });
},
toggleLabel(labelId) {
async toggleLabel(labelId) {
if (this.labelIds && this.labelIds.indexOf(labelId) > -1) {
return this.removeLabel(labelId);
return await this.removeLabel(labelId);
} else {
return this.addLabel(labelId);
return await this.addLabel(labelId);
}
},
setColor(newColor) {
async setColor(newColor) {
if (newColor === 'white') {
newColor = null;
}
return {
$set: {
color: newColor,
},
};
return await Cards.updateAsync(this._id, { $set: { color: newColor } });
},
assignMember(memberId) {
return {
$addToSet: {
members: memberId,
},
};
async assignMember(memberId) {
return await Cards.updateAsync(this._id, { $addToSet: { members: memberId } });
},
assignAssignee(assigneeId) {
// If there is not any assignee, allow one assignee, not more.
/*
if (this.getAssignees().length === 0) {
return {
$addToSet: {
assignees: assigneeId,
},
};
*/
// Allow more that one assignee:
// https://github.com/wekan/wekan/issues/3302
return {
$addToSet: {
assignees: assigneeId,
},
};
//} else {
// return false,
//}
async assignAssignee(assigneeId) {
return await Cards.updateAsync(this._id, { $addToSet: { assignees: assigneeId } });
},
unassignMember(memberId) {
return {
$pull: {
members: memberId,
},
};
async unassignMember(memberId) {
return await Cards.updateAsync(this._id, { $pull: { members: memberId } });
},
unassignAssignee(assigneeId) {
return {
$pull: {
assignees: assigneeId,
},
};
async unassignAssignee(assigneeId) {
return await Cards.updateAsync(this._id, { $pull: { assignees: assigneeId } });
},
toggleMember(memberId) {
async toggleMember(memberId) {
if (this.members && this.members.indexOf(memberId) > -1) {
return this.unassignMember(memberId);
return await this.unassignMember(memberId);
} else {
return this.assignMember(memberId);
return await this.assignMember(memberId);
}
},
toggleAssignee(assigneeId) {
async toggleAssignee(assigneeId) {
if (this.assignees && this.assignees.indexOf(assigneeId) > -1) {
return this.unassignAssignee(assigneeId);
return await this.unassignAssignee(assigneeId);
} else {
return this.assignAssignee(assigneeId);
return await this.assignAssignee(assigneeId);
}
},
assignCustomField(customFieldId) {
return {
$addToSet: {
customFields: {
_id: customFieldId,
value: null,
},
},
};
async assignCustomField(customFieldId) {
return await Cards.updateAsync(this._id, {
$addToSet: { customFields: { _id: customFieldId, value: null } },
});
},
unassignCustomField(customFieldId) {
return {
$pull: {
customFields: {
_id: customFieldId,
},
},
};
async unassignCustomField(customFieldId) {
return await Cards.updateAsync(this._id, {
$pull: { customFields: { _id: customFieldId } },
});
},
toggleCustomField(customFieldId) {
async toggleCustomField(customFieldId) {
if (this.customFields && this.customFieldIndex(customFieldId) > -1) {
return this.unassignCustomField(customFieldId);
return await this.unassignCustomField(customFieldId);
} else {
return this.assignCustomField(customFieldId);
return await this.assignCustomField(customFieldId);
}
},
toggleShowActivities() {
return {
$set: {
showActivities: !this.showActivities,
}
};
async toggleShowActivities() {
return await Cards.updateAsync(this._id, {
$set: { showActivities: !this.showActivities },
});
},
toggleShowChecklistAtMinicard() {
return {
$set: {
showChecklistAtMinicard: !this.showChecklistAtMinicard,
}
};
async toggleShowChecklistAtMinicard() {
return await Cards.updateAsync(this._id, {
$set: { showChecklistAtMinicard: !this.showChecklistAtMinicard },
});
},
setCustomField(customFieldId, value) {
// todo
async setCustomField(customFieldId, value) {
const index = this.customFieldIndex(customFieldId);
if (index > -1) {
const update = {
$set: {},
};
const update = { $set: {} };
update.$set[`customFields.${index}.value`] = value;
return update;
return await Cards.updateAsync(this._id, update);
}
// TODO
// Ignatz 18.05.2018: Return null to silence ESLint. No Idea if that is correct
return null;
},
setCover(coverId) {
return {
$set: {
coverId,
},
};
async setCover(coverId) {
return await Cards.updateAsync(this._id, { $set: { coverId } });
},
unsetCover() {
return {
$unset: {
coverId: '',
},
};
async unsetCover() {
return await Cards.updateAsync(this._id, { $unset: { coverId: '' } });
},
//setReceived(receivedAt) {
// return {
// $set: {
// receivedAt,
// },
// };
//},
unsetReceived() {
return {
$unset: {
receivedAt: '',
},
};
async unsetReceived() {
return await Cards.updateAsync(this._id, { $unset: { receivedAt: '' } });
},
//setStart(startAt) {
// return {
// $set: {
// startAt,
// },
// };
//},
unsetStart() {
return {
$unset: {
startAt: '',
},
};
async unsetStart() {
return await Cards.updateAsync(this._id, { $unset: { startAt: '' } });
},
//setDue(dueAt) {
// return {
// $set: {
// dueAt,
// },
// };
//},
unsetDue() {
return {
$unset: {
dueAt: '',
},
};
async unsetDue() {
return await Cards.updateAsync(this._id, { $unset: { dueAt: '' } });
},
//setEnd(endAt) {
// return {
// $set: {
// endAt,
// },
// };
//},
unsetEnd() {
return {
$unset: {
endAt: '',
},
};
async unsetEnd() {
return await Cards.updateAsync(this._id, { $unset: { endAt: '' } });
},
setOvertime(isOvertime) {
return {
$set: {
isOvertime,
},
};
async setOvertime(isOvertime) {
return await Cards.updateAsync(this._id, { $set: { isOvertime } });
},
setSpentTime(spentTime) {
return {
$set: {
spentTime,
},
};
async setSpentTime(spentTime) {
return await Cards.updateAsync(this._id, { $set: { spentTime } });
},
unsetSpentTime() {
return {
$unset: {
spentTime: '',
isOvertime: false,
},
};
async unsetSpentTime() {
return await Cards.updateAsync(this._id, { $unset: { spentTime: '', isOvertime: false } });
},
setParentId(parentId) {
return {
$set: {
parentId,
},
};
async setParentId(parentId) {
return await Cards.updateAsync(this._id, { $set: { parentId } });
},
setVoteQuestion(question, publicVote, allowNonBoardMembers) {
return {
async setVoteQuestion(question, publicVote, allowNonBoardMembers) {
return await Cards.updateAsync(this._id, {
$set: {
vote: {
question,
@ -2450,61 +2293,42 @@ Cards.mutations({
negative: [],
},
},
};
});
},
unsetVote() {
return {
$unset: {
vote: '',
},
};
async unsetVote() {
return await Cards.updateAsync(this._id, { $unset: { vote: '' } });
},
setVoteEnd(end) {
return {
$set: { 'vote.end': end },
};
async setVoteEnd(end) {
return await Cards.updateAsync(this._id, { $set: { 'vote.end': end } });
},
unsetVoteEnd() {
return {
$unset: { 'vote.end': '' },
};
async unsetVoteEnd() {
return await Cards.updateAsync(this._id, { $unset: { 'vote.end': '' } });
},
setVote(userId, forIt) {
async setVote(userId, forIt) {
switch (forIt) {
case true:
// vote for it
return {
$pull: {
'vote.negative': userId,
},
$addToSet: {
'vote.positive': userId,
},
};
return await Cards.updateAsync(this._id, {
$pull: { 'vote.negative': userId },
$addToSet: { 'vote.positive': userId },
});
case false:
// vote against
return {
$pull: {
'vote.positive': userId,
},
$addToSet: {
'vote.negative': userId,
},
};
return await Cards.updateAsync(this._id, {
$pull: { 'vote.positive': userId },
$addToSet: { 'vote.negative': userId },
});
default:
// Remove votes
return {
$pull: {
'vote.positive': userId,
'vote.negative': userId,
},
};
return await Cards.updateAsync(this._id, {
$pull: { 'vote.positive': userId, 'vote.negative': userId },
});
}
},
setPokerQuestion(question, allowNonBoardMembers) {
return {
async setPokerQuestion(question, allowNonBoardMembers) {
return await Cards.updateAsync(this._id, {
$set: {
poker: {
question,
@ -2521,246 +2345,47 @@ Cards.mutations({
unsure: [],
},
},
};
});
},
setPokerEstimation(estimation) {
return {
$set: { 'poker.estimation': estimation },
};
async setPokerEstimation(estimation) {
return await Cards.updateAsync(this._id, { $set: { 'poker.estimation': estimation } });
},
unsetPokerEstimation() {
return {
$unset: { 'poker.estimation': '' },
};
async unsetPokerEstimation() {
return await Cards.updateAsync(this._id, { $unset: { 'poker.estimation': '' } });
},
unsetPoker() {
return {
$unset: {
poker: '',
},
};
async unsetPoker() {
return await Cards.updateAsync(this._id, { $unset: { poker: '' } });
},
setPokerEnd(end) {
return {
$set: { 'poker.end': end },
};
async setPokerEnd(end) {
return await Cards.updateAsync(this._id, { $set: { 'poker.end': end } });
},
unsetPokerEnd() {
return {
$unset: { 'poker.end': '' },
};
async unsetPokerEnd() {
return await Cards.updateAsync(this._id, { $unset: { 'poker.end': '' } });
},
setPoker(userId, state) {
switch (state) {
case 'one':
// poker one
return {
$pull: {
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.one': userId,
},
};
case 'two':
// poker two
return {
$pull: {
'poker.one': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.two': userId,
},
};
case 'three':
// poker three
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.three': userId,
},
};
async setPoker(userId, state) {
const pokerFields = ['one', 'two', 'three', 'five', 'eight', 'thirteen', 'twenty', 'forty', 'oneHundred', 'unsure'];
const pullFields = {};
pokerFields.forEach(f => { pullFields[`poker.${f}`] = userId; });
case 'five':
// poker five
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.five': userId,
},
};
case 'eight':
// poker eight
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.eight': userId,
},
};
case 'thirteen':
// poker thirteen
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.thirteen': userId,
},
};
case 'twenty':
// poker twenty
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.twenty': userId,
},
};
case 'forty':
// poker forty
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.forty': userId,
},
};
case 'oneHundred':
// poker one hundred
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.unsure': userId,
},
$addToSet: {
'poker.oneHundred': userId,
},
};
case 'unsure':
// poker unsure
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
},
$addToSet: {
'poker.unsure': userId,
},
};
default:
// Remove pokers
return {
$pull: {
'poker.one': userId,
'poker.two': userId,
'poker.three': userId,
'poker.five': userId,
'poker.eight': userId,
'poker.thirteen': userId,
'poker.twenty': userId,
'poker.forty': userId,
'poker.oneHundred': userId,
'poker.unsure': userId,
},
};
if (pokerFields.includes(state)) {
delete pullFields[`poker.${state}`];
return await Cards.updateAsync(this._id, {
$pull: pullFields,
$addToSet: { [`poker.${state}`]: userId },
});
} else {
return await Cards.updateAsync(this._id, { $pull: pullFields });
}
},
replayPoker() {
return {
async replayPoker() {
return await Cards.updateAsync(this._id, {
$set: {
'poker.one': [],
'poker.two': [],
@ -2773,7 +2398,7 @@ Cards.mutations({
'poker.oneHundred': [],
'poker.unsure': [],
},
};
});
},
});
@ -4544,7 +4169,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
JsonRoutes.add(
'POST',
'/api/boards/:boardId/lists/:listId/cards/:cardId/archive',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramListId = req.params.listId;
@ -4558,7 +4183,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
if (!card) {
throw new Meteor.Error(404, 'Card not found');
}
card.archive();
await card.archive();
JsonRoutes.sendResult(res, {
code: 200,
data: {
@ -4583,7 +4208,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
JsonRoutes.add(
'POST',
'/api/boards/:boardId/lists/:listId/cards/:cardId/unarchive',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramListId = req.params.listId;
@ -4597,7 +4222,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
if (!card) {
throw new Meteor.Error(404, 'Card not found');
}
card.restore();
await card.restore();
JsonRoutes.sendResult(res, {
code: 200,
data: {

View file

@ -90,29 +90,24 @@ ChecklistItems.before.insert((userId, doc) => {
}
});
// Mutations
ChecklistItems.mutations({
setTitle(title) {
return { $set: { title } };
ChecklistItems.helpers({
async setTitle(title) {
return await ChecklistItems.updateAsync(this._id, { $set: { title } });
},
check() {
return { $set: { isFinished: true } };
async check() {
return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: true } });
},
uncheck() {
return { $set: { isFinished: false } };
async uncheck() {
return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: false } });
},
toggleItem() {
return { $set: { isFinished: !this.isFinished } };
async toggleItem() {
return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: !this.isFinished } });
},
move(checklistId, sortIndex) {
async move(checklistId, sortIndex) {
const cardId = ReactiveCache.getChecklist(checklistId).cardId;
const mutatedFields = {
cardId,
checklistId,
sort: sortIndex,
};
return { $set: mutatedFields };
return await ChecklistItems.updateAsync(this._id, {
$set: { cardId, checklistId, sort: sortIndex },
});
},
});

View file

@ -150,22 +150,49 @@ Checklists.helpers({
}
return ret;
},
checkAllItems() {
async checkAllItems() {
const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id });
checkItems.forEach(function(item) {
item.check();
});
for (const item of checkItems) {
await item.check();
}
},
uncheckAllItems() {
async uncheckAllItems() {
const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id });
checkItems.forEach(function(item) {
item.uncheck();
});
for (const item of checkItems) {
await item.uncheck();
}
},
itemIndex(itemId) {
const items = ReactiveCache.getChecklist({ _id: this._id }).items;
return _.pluck(items, '_id').indexOf(itemId);
},
async setTitle(title) {
return await Checklists.updateAsync(this._id, { $set: { title } });
},
/** move the checklist to another card
* @param newCardId move the checklist to this cardId
*/
async move(newCardId) {
// Note: Activities and ChecklistItems updates are now handled server-side
// in the moveChecklist Meteor method to avoid client-side permission issues
return await Checklists.updateAsync(this._id, { $set: { cardId: newCardId } });
},
async toggleHideCheckedChecklistItems() {
return await Checklists.updateAsync(this._id, {
$set: { hideCheckedChecklistItems: !this.hideCheckedChecklistItems },
});
},
async toggleHideAllChecklistItems() {
return await Checklists.updateAsync(this._id, {
$set: { hideAllChecklistItems: !this.hideAllChecklistItems },
});
},
async toggleShowChecklistAtMinicard() {
return await Checklists.updateAsync(this._id, {
$set: { showChecklistAtMinicard: !this.showChecklistAtMinicard },
});
},
});
Checklists.allow({
@ -191,46 +218,6 @@ Checklists.before.insert((userId, doc) => {
}
});
Checklists.mutations({
setTitle(title) {
return { $set: { title } };
},
/** move the checklist to another card
* @param newCardId move the checklist to this cardId
*/
move(newCardId) {
// Note: Activities and ChecklistItems updates are now handled server-side
// in the moveChecklist Meteor method to avoid client-side permission issues
// update the checklist itself
return {
$set: {
cardId: newCardId,
},
};
},
toggleHideCheckedChecklistItems() {
return {
$set: {
hideCheckedChecklistItems: !this.hideCheckedChecklistItems,
}
};
},
toggleHideAllChecklistItems() {
return {
$set: {
hideAllChecklistItems: !this.hideAllChecklistItems,
}
};
},
toggleShowChecklistAtMinicard() {
return {
$set: {
showChecklistAtMinicard: !this.showChecklistAtMinicard,
}
};
},
});
if (Meteor.isServer) {
Meteor.methods({

View file

@ -382,14 +382,14 @@ export class CsvCreator {
}
}
create(board, currentBoardId) {
async create(board, currentBoardId) {
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && currentBoardId) {
const currentBoard = ReactiveCache.getBoard(currentBoardId);
currentBoard.archive();
await currentBoard.archive();
}
this.mapHeadertoCardFieldIndex(board[0]);
const boardId = this.createBoard(board);

View file

@ -152,17 +152,14 @@ CustomFields.addToAllCards = cf => {
);
};
CustomFields.mutations({
addBoard(boardId) {
CustomFields.helpers({
async addBoard(boardId) {
if (boardId) {
return {
$push: {
boardIds: boardId,
},
};
} else {
return null;
return await CustomFields.updateAsync(this._id, {
$push: { boardIds: boardId },
});
}
return null;
},
});

View file

@ -226,7 +226,7 @@ Lists.helpers({
});
},
move(boardId, swimlaneId) {
async move(boardId, swimlaneId) {
const boardList = ReactiveCache.getList({
boardId,
title: this.title,
@ -235,9 +235,9 @@ Lists.helpers({
let listId;
if (boardList) {
listId = boardList._id;
this.cards().forEach(card => {
card.move(boardId, this._id, boardList._id);
});
for (const card of this.cards()) {
await card.move(boardId, this._id, boardList._id);
}
} else {
console.log('list.title:', this.title);
console.log('boardList:', boardList);
@ -251,9 +251,9 @@ Lists.helpers({
});
}
this.cards(swimlaneId).forEach(card => {
card.move(boardId, swimlaneId, listId);
});
for (const card of this.cards(swimlaneId)) {
await card.move(boardId, swimlaneId, listId);
}
},
cards(swimlaneId) {
@ -342,61 +342,55 @@ Lists.helpers({
remove() {
Lists.remove({ _id: this._id });
},
});
Lists.mutations({
rename(title) {
async rename(title) {
// Basic client-side validation - server will handle full sanitization
if (typeof title === 'string') {
// Basic length check to prevent abuse
const sanitizedTitle = title.length > 1000 ? title.substring(0, 1000) : title;
return { $set: { title: sanitizedTitle } };
return await Lists.updateAsync(this._id, { $set: { title: sanitizedTitle } });
}
return { $set: { title } };
return await Lists.updateAsync(this._id, { $set: { title } });
},
star(enable = true) {
return { $set: { starred: !!enable } };
async star(enable = true) {
return await Lists.updateAsync(this._id, { $set: { starred: !!enable } });
},
collapse(enable = true) {
return { $set: { collapsed: !!enable } };
async collapse(enable = true) {
return await Lists.updateAsync(this._id, { $set: { collapsed: !!enable } });
},
archive() {
async archive() {
if (this.isTemplateList()) {
this.cards().forEach(card => {
return card.archive();
});
for (const card of this.cards()) {
await card.archive();
}
}
return { $set: { archived: true, archivedAt: new Date() } };
return await Lists.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } });
},
restore() {
async restore() {
if (this.isTemplateList()) {
this.allCards().forEach(card => {
return card.restore();
});
for (const card of this.allCards()) {
await card.restore();
}
}
return { $set: { archived: false } };
return await Lists.updateAsync(this._id, { $set: { archived: false } });
},
toggleSoftLimit(toggle) {
return { $set: { 'wipLimit.soft': toggle } };
async toggleSoftLimit(toggle) {
return await Lists.updateAsync(this._id, { $set: { 'wipLimit.soft': toggle } });
},
toggleWipLimit(toggle) {
return { $set: { 'wipLimit.enabled': toggle } };
async toggleWipLimit(toggle) {
return await Lists.updateAsync(this._id, { $set: { 'wipLimit.enabled': toggle } });
},
setWipLimit(limit) {
return { $set: { 'wipLimit.value': limit } };
async setWipLimit(limit) {
return await Lists.updateAsync(this._id, { $set: { 'wipLimit.value': limit } });
},
setColor(newColor) {
return {
$set: {
color: newColor,
},
};
async setColor(newColor) {
return await Lists.updateAsync(this._id, { $set: { color: newColor } });
},
});
@ -422,49 +416,49 @@ Lists.archivedListIds = () => {
};
Meteor.methods({
applyWipLimit(listId, limit) {
async applyWipLimit(listId, limit) {
check(listId, String);
check(limit, Number);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = ReactiveCache.getList(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
if (!board || !board.hasAdmin(this.userId)) {
throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.');
}
if (limit === 0) {
limit = 1;
}
list.setWipLimit(limit);
await list.setWipLimit(limit);
},
enableWipLimit(listId) {
async enableWipLimit(listId) {
check(listId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = ReactiveCache.getList(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
if (!board || !board.hasAdmin(this.userId)) {
throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.');
}
if (list.getWipLimit('value') === 0) {
list.setWipLimit(1);
await list.setWipLimit(1);
}
list.toggleWipLimit(!list.getWipLimit('enabled'));
},

View file

@ -50,13 +50,10 @@ Rules.attachSchema(
}),
);
Rules.mutations({
rename(description) {
return { $set: { description } };
},
});
Rules.helpers({
async rename(description) {
return await Rules.updateAsync(this._id, { $set: { description } });
},
getAction() {
return ReactiveCache.getAction(this.actionId);
},

View file

@ -171,8 +171,8 @@ Swimlanes.helpers({
});
},
move(toBoardId) {
this.lists().forEach(list => {
async move(toBoardId) {
for (const list of this.lists()) {
const toList = ReactiveCache.getList({
boardId: toBoardId,
title: list.title,
@ -193,13 +193,14 @@ Swimlanes.helpers({
});
}
ReactiveCache.getCards({
const cards = ReactiveCache.getCards({
listId: list._id,
swimlaneId: this._id,
}).forEach(card => {
card.move(toBoardId, this._id, toListId);
});
});
for (const card of cards) {
await card.move(toBoardId, this._id, toListId);
}
}
Swimlanes.update(this._id, {
$set: {
@ -317,40 +318,34 @@ Swimlanes.helpers({
remove() {
Swimlanes.remove({ _id: this._id });
},
});
Swimlanes.mutations({
rename(title) {
return { $set: { title } };
async rename(title) {
return await Swimlanes.updateAsync(this._id, { $set: { title } });
},
// NOTE: collapse() removed - collapsed state is per-user only
// Use user.setCollapsedSwimlane(boardId, swimlaneId, collapsed) instead
archive() {
async archive() {
if (this.isTemplateSwimlane()) {
this.myLists().forEach(list => {
return list.archive();
});
for (const list of this.myLists()) {
await list.archive();
}
}
return { $set: { archived: true, archivedAt: new Date() } };
return await Swimlanes.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } });
},
restore() {
async restore() {
if (this.isTemplateSwimlane()) {
this.myLists().forEach(list => {
return list.restore();
});
for (const list of this.myLists()) {
await list.restore();
}
}
return { $set: { archived: false } };
return await Swimlanes.updateAsync(this._id, { $set: { archived: false } });
},
setColor(newColor) {
return {
$set: {
color: newColor,
},
};
async setColor(newColor) {
return await Swimlanes.updateAsync(this._id, { $set: { color: newColor } });
},
});

View file

@ -767,7 +767,7 @@ export class TrelloCreator {
}
}
create(board, currentBoardId) {
async create(board, currentBoardId) {
// TODO : Make isSandstorm variable global
const isSandstorm =
Meteor.settings &&
@ -775,7 +775,7 @@ export class TrelloCreator {
Meteor.settings.public.sandstorm;
if (isSandstorm && currentBoardId) {
const currentBoard = ReactiveCache.getBoard(currentBoardId);
currentBoard.archive();
await currentBoard.archive();
}
this.parseActions(board.actions);
const boardId = this.createBoardAndLabels(board);

View file

@ -3,16 +3,6 @@ import { Meteor } from 'meteor/meteor';
Triggers = new Mongo.Collection('triggers');
Triggers.mutations({
rename(description) {
return {
$set: {
description,
},
};
},
});
Triggers.before.insert((userId, doc) => {
doc.createdAt = new Date();
doc.updatedAt = doc.createdAt;
@ -36,6 +26,12 @@ Triggers.allow({
});
Triggers.helpers({
async rename(description) {
return await Triggers.updateAsync(this._id, {
$set: { description },
});
},
description() {
return this.desc;
},

View file

@ -1593,376 +1593,206 @@ Users.helpers({
}
return null;
},
});
Users.mutations({
/** set the confirmed board id/swimlane id/list id of a board
* @param boardId the current board id
* @param options an object with the confirmed field values
*/
setMoveAndCopyDialogOption(boardId, options) {
async setMoveAndCopyDialogOption(boardId, options) {
let currentOptions = this.getMoveAndCopyDialogOptions();
currentOptions[boardId] = options;
return {
$set: {
'profile.moveAndCopyDialog': currentOptions,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.moveAndCopyDialog': currentOptions } });
},
/** set the confirmed board id/swimlane id/list id/card id of a board (move checklist)
* @param boardId the current board id
* @param options an object with the confirmed field values
*/
setMoveChecklistDialogOption(boardId, options) {
async setMoveChecklistDialogOption(boardId, options) {
let currentOptions = this.getMoveChecklistDialogOptions();
currentOptions[boardId] = options;
return {
$set: {
'profile.moveChecklistDialog': currentOptions,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.moveChecklistDialog': currentOptions } });
},
/** set the confirmed board id/swimlane id/list id/card id of a board (copy checklist)
* @param boardId the current board id
* @param options an object with the confirmed field values
*/
setCopyChecklistDialogOption(boardId, options) {
async setCopyChecklistDialogOption(boardId, options) {
let currentOptions = this.getCopyChecklistDialogOptions();
currentOptions[boardId] = options;
return {
$set: {
'profile.copyChecklistDialog': currentOptions,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.copyChecklistDialog': currentOptions } });
},
toggleBoardStar(boardId) {
async toggleBoardStar(boardId) {
const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet';
return {
[queryKind]: {
'profile.starredBoards': boardId,
},
};
return await Users.updateAsync(this._id, { [queryKind]: { 'profile.starredBoards': boardId } });
},
/**
* Set per-user board sort index for a board
* Stored at profile.boardSortIndex[boardId] = sortIndex (Number)
*/
setBoardSortIndex(boardId, sortIndex) {
async setBoardSortIndex(boardId, sortIndex) {
const mapping = (this.profile && this.profile.boardSortIndex) || {};
mapping[boardId] = sortIndex;
return {
$set: {
'profile.boardSortIndex': mapping,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.boardSortIndex': mapping } });
},
toggleAutoWidth(boardId) {
async toggleAutoWidth(boardId) {
const { autoWidthBoards = {} } = this.profile || {};
autoWidthBoards[boardId] = !autoWidthBoards[boardId];
return {
$set: {
'profile.autoWidthBoards': autoWidthBoards,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.autoWidthBoards': autoWidthBoards } });
},
toggleKeyboardShortcuts() {
async toggleKeyboardShortcuts() {
const { keyboardShortcuts = true } = this.profile || {};
return {
$set: {
'profile.keyboardShortcuts': !keyboardShortcuts,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.keyboardShortcuts': !keyboardShortcuts } });
},
toggleVerticalScrollbars() {
async toggleVerticalScrollbars() {
const { verticalScrollbars = true } = this.profile || {};
return {
$set: {
'profile.verticalScrollbars': !verticalScrollbars,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.verticalScrollbars': !verticalScrollbars } });
},
toggleShowWeekOfYear() {
async toggleShowWeekOfYear() {
const { showWeekOfYear = true } = this.profile || {};
return {
$set: {
'profile.showWeekOfYear': !showWeekOfYear,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.showWeekOfYear': !showWeekOfYear } });
},
addInvite(boardId) {
return {
$addToSet: {
'profile.invitedBoards': boardId,
},
};
async addInvite(boardId) {
return await Users.updateAsync(this._id, { $addToSet: { 'profile.invitedBoards': boardId } });
},
removeInvite(boardId) {
return {
$pull: {
'profile.invitedBoards': boardId,
},
};
async removeInvite(boardId) {
return await Users.updateAsync(this._id, { $pull: { 'profile.invitedBoards': boardId } });
},
addTag(tag) {
return {
$addToSet: {
'profile.tags': tag,
},
};
async addTag(tag) {
return await Users.updateAsync(this._id, { $addToSet: { 'profile.tags': tag } });
},
removeTag(tag) {
return {
$pull: {
'profile.tags': tag,
},
};
async removeTag(tag) {
return await Users.updateAsync(this._id, { $pull: { 'profile.tags': tag } });
},
toggleTag(tag) {
if (this.hasTag(tag)) this.removeTag(tag);
else this.addTag(tag);
async toggleTag(tag) {
if (this.hasTag(tag)) {
return await this.removeTag(tag);
} else {
return await this.addTag(tag);
}
},
setListSortBy(value) {
return {
$set: {
'profile.listSortBy': value,
},
};
async setListSortBy(value) {
return await Users.updateAsync(this._id, { $set: { 'profile.listSortBy': value } });
},
setName(value) {
return {
$set: {
'profile.fullname': value,
},
};
async setName(value) {
return await Users.updateAsync(this._id, { $set: { 'profile.fullname': value } });
},
toggleDesktopHandles(value = false) {
return {
$set: {
'profile.showDesktopDragHandles': !value,
},
};
async toggleDesktopHandles(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.showDesktopDragHandles': !value } });
},
toggleFieldsGrid(value = false) {
return {
$set: {
'profile.customFieldsGrid': !value,
},
};
async toggleFieldsGrid(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.customFieldsGrid': !value } });
},
toggleCardMaximized(value = false) {
return {
$set: {
'profile.cardMaximized': !value,
},
};
async toggleCardMaximized(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.cardMaximized': !value } });
},
toggleCardCollapsed(value = false) {
return {
$set: {
'profile.cardCollapsed': !value,
},
};
async toggleCardCollapsed(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.cardCollapsed': !value } });
},
toggleShowActivities(value = false) {
return {
$set: {
'profile.showActivities': !value,
},
};
async toggleShowActivities(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.showActivities': !value } });
},
toggleLabelText(value = false) {
return {
$set: {
'profile.hiddenMinicardLabelText': !value,
},
};
},
toggleRescueCardDescription(value = false) {
return {
$set: {
'profile.rescueCardDescription': !value,
},
};
},
toggleGreyIcons(value = false) {
return {
$set: {
'profile.GreyIcons': !value,
},
};
async toggleLabelText(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.hiddenMinicardLabelText': !value } });
},
addNotification(activityId) {
return {
$addToSet: {
'profile.notifications': {
activity: activityId,
read: null,
},
},
};
async toggleRescueCardDescription(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.rescueCardDescription': !value } });
},
removeNotification(activityId) {
return {
$pull: {
'profile.notifications': {
activity: activityId,
},
},
};
async toggleGreyIcons(value = false) {
return await Users.updateAsync(this._id, { $set: { 'profile.GreyIcons': !value } });
},
addEmailBuffer(text) {
return {
$addToSet: {
'profile.emailBuffer': text,
},
};
async addNotification(activityId) {
return await Users.updateAsync(this._id, {
$addToSet: { 'profile.notifications': { activity: activityId, read: null } },
});
},
clearEmailBuffer() {
return {
$set: {
'profile.emailBuffer': [],
},
};
async removeNotification(activityId) {
return await Users.updateAsync(this._id, {
$pull: { 'profile.notifications': { activity: activityId } },
});
},
setAvatarUrl(avatarUrl) {
return {
$set: {
'profile.avatarUrl': avatarUrl,
},
};
async addEmailBuffer(text) {
return await Users.updateAsync(this._id, { $addToSet: { 'profile.emailBuffer': text } });
},
setShowCardsCountAt(limit) {
return {
$set: {
'profile.showCardsCountAt': limit,
},
};
async clearEmailBuffer() {
return await Users.updateAsync(this._id, { $set: { 'profile.emailBuffer': [] } });
},
setStartDayOfWeek(startDay) {
return {
$set: {
'profile.startDayOfWeek': startDay,
},
};
async setAvatarUrl(avatarUrl) {
return await Users.updateAsync(this._id, { $set: { 'profile.avatarUrl': avatarUrl } });
},
setDateFormat(dateFormat) {
return {
$set: {
'profile.dateFormat': dateFormat,
},
};
async setShowCardsCountAt(limit) {
return await Users.updateAsync(this._id, { $set: { 'profile.showCardsCountAt': limit } });
},
setBoardView(view) {
return {
$set: {
'profile.boardView': view,
},
};
async setStartDayOfWeek(startDay) {
return await Users.updateAsync(this._id, { $set: { 'profile.startDayOfWeek': startDay } });
},
setListWidth(boardId, listId, width) {
async setDateFormat(dateFormat) {
return await Users.updateAsync(this._id, { $set: { 'profile.dateFormat': dateFormat } });
},
async setBoardView(view) {
return await Users.updateAsync(this._id, { $set: { 'profile.boardView': view } });
},
async setListWidth(boardId, listId, width) {
let currentWidths = this.getListWidths();
if (!currentWidths[boardId]) {
currentWidths[boardId] = {};
}
if (!currentWidths[boardId]) currentWidths[boardId] = {};
currentWidths[boardId][listId] = width;
return {
$set: {
'profile.listWidths': currentWidths,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.listWidths': currentWidths } });
},
setListConstraint(boardId, listId, constraint) {
async setListConstraint(boardId, listId, constraint) {
let currentConstraints = this.getListConstraints();
if (!currentConstraints[boardId]) {
currentConstraints[boardId] = {};
}
if (!currentConstraints[boardId]) currentConstraints[boardId] = {};
currentConstraints[boardId][listId] = constraint;
return {
$set: {
'profile.listConstraints': currentConstraints,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.listConstraints': currentConstraints } });
},
setSwimlaneHeight(boardId, swimlaneId, height) {
async setSwimlaneHeight(boardId, swimlaneId, height) {
let currentHeights = this.getSwimlaneHeights();
if (!currentHeights[boardId]) {
currentHeights[boardId] = {};
}
if (!currentHeights[boardId]) currentHeights[boardId] = {};
currentHeights[boardId][swimlaneId] = height;
return {
$set: {
'profile.swimlaneHeights': currentHeights,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.swimlaneHeights': currentHeights } });
},
setCollapsedList(boardId, listId, collapsed) {
async setCollapsedList(boardId, listId, collapsed) {
const current = (this.profile && this.profile.collapsedLists) || {};
if (!current[boardId]) current[boardId] = {};
current[boardId][listId] = !!collapsed;
return {
$set: {
'profile.collapsedLists': current,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.collapsedLists': current } });
},
setCollapsedSwimlane(boardId, swimlaneId, collapsed) {
async setCollapsedSwimlane(boardId, swimlaneId, collapsed) {
const current = (this.profile && this.profile.collapsedSwimlanes) || {};
if (!current[boardId]) current[boardId] = {};
current[boardId][swimlaneId] = !!collapsed;
return {
$set: {
'profile.collapsedSwimlanes': current,
},
};
return await Users.updateAsync(this._id, { $set: { 'profile.collapsedSwimlanes': current } });
},
setZoomLevel(level) {
return {
$set: {
'profile.zoomLevel': level,
},
};
async setZoomLevel(level) {
return await Users.updateAsync(this._id, { $set: { 'profile.zoomLevel': level } });
},
setMobileMode(enabled) {
return {
$set: {
'profile.mobileMode': enabled,
},
};
async setMobileMode(enabled) {
return await Users.updateAsync(this._id, { $set: { 'profile.mobileMode': enabled } });
},
setCardZoom(level) {
return {
$set: {
'profile.cardZoom': level,
},
};
async setCardZoom(level) {
return await Users.updateAsync(this._id, { $set: { 'profile.cardZoom': level } });
},
});
@ -3340,7 +3170,7 @@ if (Meteor.isServer) {
* @return_type {_id: string,
* title: string}
*/
JsonRoutes.add('PUT', '/api/users/:userId', function (req, res) {
JsonRoutes.add('PUT', '/api/users/:userId', async function (req, res) {
try {
Authentication.checkUserId(req.userId);
const id = req.params.userId;
@ -3350,7 +3180,7 @@ if (Meteor.isServer) {
});
if (data !== undefined) {
if (action === 'takeOwnership') {
data = ReactiveCache.getBoards(
const boards = ReactiveCache.getBoards(
{
'members.userId': id,
'members.isAdmin': true,
@ -3360,16 +3190,18 @@ if (Meteor.isServer) {
sort: 1 /* boards default sorting */,
},
},
).map(function (board) {
);
data = [];
for (const board of boards) {
if (board.hasMember(req.userId)) {
board.removeMember(req.userId);
await board.removeMember(req.userId);
}
board.changeOwnership(id, req.userId);
return {
data.push({
_id: board._id,
title: board.title,
};
});
});
}
} else {
if (action === 'disableLogin' && id !== req.userId) {
Users.update(

View file

@ -19,13 +19,13 @@ const simpleWatchable = collection => {
findWatcher(userId) {
return _.contains(this.watchers, userId);
},
});
collection.mutations({
setWatcher(userId, level) {
async setWatcher(userId, level) {
// if level undefined or null or false, then remove
if (!level) return { $pull: { watchers: userId } };
return { $addToSet: { watchers: userId } };
if (!level) {
return await collection.updateAsync(this._id, { $pull: { watchers: userId } });
}
return await collection.updateAsync(this._id, { $addToSet: { watchers: userId } });
},
});
};
@ -66,20 +66,20 @@ const complexWatchable = collection => {
const watcher = this.findWatcher(userId);
return watcher ? watcher.level : complexWatchDefault;
},
});
collection.mutations({
setWatcher(userId, level) {
async setWatcher(userId, level) {
// if level undefined or null or false, then remove
if (level === complexWatchDefault) level = null;
if (!level) return { $pull: { watchers: { userId } } };
if (!level) {
return await collection.updateAsync(this._id, { $pull: { watchers: { userId } } });
}
const index = this.watcherIndex(userId);
if (index < 0) return { $push: { watchers: { userId, level } } };
return {
$set: {
[`watchers.${index}.level`]: level,
},
};
if (index < 0) {
return await collection.updateAsync(this._id, { $push: { watchers: { userId, level } } });
}
return await collection.updateAsync(this._id, {
$set: { [`watchers.${index}.level`]: level },
});
},
});
};

View file

@ -970,7 +970,7 @@ export class WekanCreator {
// }
}
create(board, currentBoardId) {
async create(board, currentBoardId) {
// TODO : Make isSandstorm variable global
const isSandstorm =
Meteor.settings &&
@ -978,7 +978,7 @@ export class WekanCreator {
Meteor.settings.public.sandstorm;
if (isSandstorm && currentBoardId) {
const currentBoard = ReactiveCache.getBoard(currentBoardId);
currentBoard.archive();
await currentBoard.archive();
}
this.parseActivities(board);
const boardId = this.createBoardAndLabels(board);