wekan/models/checklistItems.js
2019-06-28 12:56:51 -05:00

355 lines
9 KiB
JavaScript

ChecklistItems = new Mongo.Collection('checklistItems');
/**
* An item in a checklist
*/
ChecklistItems.attachSchema(
new SimpleSchema({
title: {
/**
* the text of the item
*/
type: String,
},
sort: {
/**
* the sorting field of the item
*/
type: Number,
decimal: true,
},
isFinished: {
/**
* Is the item checked?
*/
type: Boolean,
defaultValue: false,
},
checklistId: {
/**
* the checklist ID the item is attached to
*/
type: String,
},
cardId: {
/**
* the card ID the item is attached to
*/
type: String,
},
createdAt: {
type: Date,
optional: true,
// eslint-disable-next-line consistent-return
autoValue() {
if (this.isInsert) {
return new Date();
} else {
this.unset();
}
},
},
modifiedAt: {
type: Date,
denyUpdate: false,
// eslint-disable-next-line consistent-return
autoValue() {
if (this.isInsert || this.isUpsert || this.isUpdate) {
return new Date();
} else {
this.unset();
}
},
},
}),
);
ChecklistItems.allow({
insert(userId, doc) {
return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
},
update(userId, doc) {
return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
},
remove(userId, doc) {
return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
},
fetch: ['userId', 'cardId'],
});
ChecklistItems.before.insert((userId, doc) => {
if (!doc.userId) {
doc.userId = userId;
}
});
// Mutations
ChecklistItems.mutations({
setTitle(title) {
return { $set: { title } };
},
check() {
return { $set: { isFinished: true } };
},
uncheck() {
return { $set: { isFinished: false } };
},
toggleItem() {
return { $set: { isFinished: !this.isFinished } };
},
move(checklistId, sortIndex) {
const cardId = Checklists.findOne(checklistId).cardId;
const mutatedFields = {
cardId,
checklistId,
sort: sortIndex,
};
return { $set: mutatedFields };
},
});
// Activities helper
function itemCreation(userId, doc) {
const card = Cards.findOne(doc.cardId);
const boardId = card.boardId;
Activities.insert({
userId,
activityType: 'addChecklistItem',
cardId: doc.cardId,
boardId,
checklistId: doc.checklistId,
checklistItemId: doc._id,
checklistItemName: doc.title,
listId: card.listId,
swimlaneId: card.swimlaneId,
});
}
function itemRemover(userId, doc) {
Activities.remove({
checklistItemId: doc._id,
});
}
function publishCheckActivity(userId, doc) {
const card = Cards.findOne(doc.cardId);
const boardId = card.boardId;
let activityType;
if (doc.isFinished) {
activityType = 'checkedItem';
} else {
activityType = 'uncheckedItem';
}
const act = {
userId,
activityType,
cardId: doc.cardId,
boardId,
checklistId: doc.checklistId,
checklistItemId: doc._id,
checklistItemName: doc.title,
listId: card.listId,
swimlaneId: card.swimlaneId,
};
Activities.insert(act);
}
function publishChekListCompleted(userId, doc) {
const card = Cards.findOne(doc.cardId);
const boardId = card.boardId;
const checklistId = doc.checklistId;
const checkList = Checklists.findOne({ _id: checklistId });
if (checkList.isFinished()) {
const act = {
userId,
activityType: 'completeChecklist',
cardId: doc.cardId,
boardId,
checklistId: doc.checklistId,
checklistName: checkList.title,
listId: card.listId,
swimlaneId: card.swimlaneId,
};
Activities.insert(act);
}
}
function publishChekListUncompleted(userId, doc) {
const card = Cards.findOne(doc.cardId);
const boardId = card.boardId;
const checklistId = doc.checklistId;
const checkList = Checklists.findOne({ _id: checklistId });
// BUGS in IFTTT Rules: https://github.com/wekan/wekan/issues/1972
// Currently in checklist all are set as uncompleted/not checked,
// IFTTT Rule does not move card to other list.
// If following line is negated/changed to:
// if(!checkList.isFinished()){
// then unchecking of any checkbox will move card to other list,
// even when all checkboxes are not yet unchecked.
// What is correct code for only moving when all in list is unchecked?
// TIPS: Finding files, ignoring some directories with grep -v:
// cd wekan
// find . | xargs grep 'count' -sl | grep -v .meteor | grep -v node_modules | grep -v .build
// Maybe something related here?
// wekan/client/components/rules/triggers/checklistTriggers.js
if (checkList.isFinished()) {
const act = {
userId,
activityType: 'uncompleteChecklist',
cardId: doc.cardId,
boardId,
checklistId: doc.checklistId,
checklistName: checkList.title,
listId: card.listId,
swimlaneId: card.swimlaneId,
};
Activities.insert(act);
}
}
// Activities
if (Meteor.isServer) {
Meteor.startup(() => {
ChecklistItems._collection._ensureIndex({ modifiedAt: -1 });
ChecklistItems._collection._ensureIndex({ checklistId: 1 });
ChecklistItems._collection._ensureIndex({ cardId: 1 });
});
ChecklistItems.after.update((userId, doc, fieldNames) => {
publishCheckActivity(userId, doc);
publishChekListCompleted(userId, doc, fieldNames);
});
ChecklistItems.before.update((userId, doc, fieldNames) => {
publishChekListUncompleted(userId, doc, fieldNames);
});
ChecklistItems.after.insert((userId, doc) => {
itemCreation(userId, doc);
});
ChecklistItems.before.remove((userId, doc) => {
itemRemover(userId, doc);
const card = Cards.findOne(doc.cardId);
const boardId = card.boardId;
Activities.insert({
userId,
activityType: 'removedChecklistItem',
cardId: doc.cardId,
boardId,
checklistId: doc.checklistId,
checklistItemId: doc._id,
checklistItemName: doc.title,
listId: card.listId,
swimlaneId: card.swimlaneId,
});
});
}
if (Meteor.isServer) {
/**
* @operation get_checklist_item
* @tag Checklists
* @summary Get a checklist item
*
* @param {string} boardId the board ID
* @param {string} cardId the card ID
* @param {string} checklistId the checklist ID
* @param {string} itemId the ID of the item
* @return_type ChecklistItems
*/
JsonRoutes.add(
'GET',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
function(req, res) {
Authentication.checkUserId(req.userId);
const paramItemId = req.params.itemId;
const checklistItem = ChecklistItems.findOne({ _id: paramItemId });
if (checklistItem) {
JsonRoutes.sendResult(res, {
code: 200,
data: checklistItem,
});
} else {
JsonRoutes.sendResult(res, {
code: 500,
});
}
},
);
/**
* @operation edit_checklist_item
* @tag Checklists
* @summary Edit a checklist item
*
* @param {string} boardId the board ID
* @param {string} cardId the card ID
* @param {string} checklistId the checklist ID
* @param {string} itemId the ID of the item
* @param {string} [isFinished] is the item checked?
* @param {string} [title] the new text of the item
* @return_type {_id: string}
*/
JsonRoutes.add(
'PUT',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
function(req, res) {
Authentication.checkUserId(req.userId);
const paramItemId = req.params.itemId;
if (req.body.hasOwnProperty('isFinished')) {
ChecklistItems.direct.update(
{ _id: paramItemId },
{ $set: { isFinished: req.body.isFinished } },
);
}
if (req.body.hasOwnProperty('title')) {
ChecklistItems.direct.update(
{ _id: paramItemId },
{ $set: { title: req.body.title } },
);
}
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: paramItemId,
},
});
},
);
/**
* @operation delete_checklist_item
* @tag Checklists
* @summary Delete a checklist item
*
* @description Note: this operation can't be reverted.
*
* @param {string} boardId the board ID
* @param {string} cardId the card ID
* @param {string} checklistId the checklist ID
* @param {string} itemId the ID of the item to be removed
* @return_type {_id: string}
*/
JsonRoutes.add(
'DELETE',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
function(req, res) {
Authentication.checkUserId(req.userId);
const paramItemId = req.params.itemId;
ChecklistItems.direct.remove({ _id: paramItemId });
JsonRoutes.sendResult(res, {
code: 200,
data: {
_id: paramItemId,
},
});
},
);
}
export default ChecklistItems;