checklists.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. Checklists = new Mongo.Collection('checklists');
  2. Checklists.attachSchema(new SimpleSchema({
  3. cardId: {
  4. type: String,
  5. },
  6. title: {
  7. type: String,
  8. },
  9. items: {
  10. type: [Object],
  11. defaultValue: [],
  12. },
  13. 'items.$._id': {
  14. type: String,
  15. },
  16. 'items.$.title': {
  17. type: String,
  18. },
  19. 'items.$.sort': {
  20. type: Number,
  21. decimal: true,
  22. },
  23. 'items.$.isFinished': {
  24. type: Boolean,
  25. defaultValue: false,
  26. },
  27. finishedAt: {
  28. type: Date,
  29. optional: true,
  30. },
  31. createdAt: {
  32. type: Date,
  33. denyUpdate: false,
  34. autoValue() { // eslint-disable-line consistent-return
  35. if (this.isInsert) {
  36. return new Date();
  37. } else {
  38. this.unset();
  39. }
  40. },
  41. },
  42. sort: {
  43. type: Number,
  44. decimal: true,
  45. },
  46. }));
  47. const self = Checklists;
  48. Checklists.helpers({
  49. itemCount() {
  50. return this.items.length;
  51. },
  52. getItemsSorted() {
  53. return _.sortBy(this.items, 'sort');
  54. },
  55. finishedCount() {
  56. return this.items.filter((item) => {
  57. return item.isFinished;
  58. }).length;
  59. },
  60. isFinished() {
  61. return 0 !== this.itemCount() && this.itemCount() === this.finishedCount();
  62. },
  63. getItem(_id) {
  64. return _.findWhere(this.items, { _id });
  65. },
  66. itemIndex(itemId) {
  67. const items = self.findOne({_id : this._id}).items;
  68. return _.pluck(items, '_id').indexOf(itemId);
  69. },
  70. getNewItemId() {
  71. const itemCount = this.itemCount();
  72. let idx = 0;
  73. if (itemCount > 0) {
  74. const lastId = this.items[itemCount - 1]._id;
  75. const lastIdSuffix = lastId.substr(this._id.length);
  76. idx = parseInt(lastIdSuffix, 10) + 1;
  77. }
  78. return `${this._id}${idx}`;
  79. },
  80. });
  81. Checklists.allow({
  82. insert(userId, doc) {
  83. return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
  84. },
  85. update(userId, doc) {
  86. return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
  87. },
  88. remove(userId, doc) {
  89. return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
  90. },
  91. fetch: ['userId', 'cardId'],
  92. });
  93. Checklists.before.insert((userId, doc) => {
  94. doc.createdAt = new Date();
  95. if (!doc.userId) {
  96. doc.userId = userId;
  97. }
  98. });
  99. Checklists.mutations({
  100. //for checklist itself
  101. setTitle(title) {
  102. return { $set: { title } };
  103. },
  104. //for items in checklist
  105. addItem(title) {
  106. const _id = this.getNewItemId();
  107. return {
  108. $addToSet: {
  109. items: {
  110. _id, title,
  111. isFinished: false,
  112. sort: this.itemCount(),
  113. },
  114. },
  115. };
  116. },
  117. addFullItem(item) {
  118. const itemsUpdate = {};
  119. this.items.forEach(function(iterItem, index) {
  120. if (iterItem.sort >= item.sort) {
  121. itemsUpdate[`items.${index}.sort`] = iterItem.sort + 1;
  122. }
  123. });
  124. if (!_.isEmpty(itemsUpdate)) {
  125. self.direct.update({ _id: this._id }, { $set: itemsUpdate });
  126. }
  127. return { $addToSet: { items: item } };
  128. },
  129. removeItem(itemId) {
  130. const item = this.getItem(itemId);
  131. const itemsUpdate = {};
  132. this.items.forEach(function(iterItem, index) {
  133. if (iterItem.sort > item.sort) {
  134. itemsUpdate[`items.${index}.sort`] = iterItem.sort - 1;
  135. }
  136. });
  137. if (!_.isEmpty(itemsUpdate)) {
  138. self.direct.update({ _id: this._id }, { $set: itemsUpdate });
  139. }
  140. return { $pull: { items: { _id: itemId } } };
  141. },
  142. editItem(itemId, title) {
  143. if (this.getItem(itemId)) {
  144. const itemIndex = this.itemIndex(itemId);
  145. return {
  146. $set: {
  147. [`items.${itemIndex}.title`]: title,
  148. },
  149. };
  150. }
  151. return {};
  152. },
  153. finishItem(itemId) {
  154. if (this.getItem(itemId)) {
  155. const itemIndex = this.itemIndex(itemId);
  156. return {
  157. $set: {
  158. [`items.${itemIndex}.isFinished`]: true,
  159. },
  160. };
  161. }
  162. return {};
  163. },
  164. resumeItem(itemId) {
  165. if (this.getItem(itemId)) {
  166. const itemIndex = this.itemIndex(itemId);
  167. return {
  168. $set: {
  169. [`items.${itemIndex}.isFinished`]: false,
  170. },
  171. };
  172. }
  173. return {};
  174. },
  175. toggleItem(itemId) {
  176. const item = this.getItem(itemId);
  177. if (item) {
  178. const itemIndex = this.itemIndex(itemId);
  179. return {
  180. $set: {
  181. [`items.${itemIndex}.isFinished`]: !item.isFinished,
  182. },
  183. };
  184. }
  185. return {};
  186. },
  187. sortItems(itemIDs) {
  188. const validItems = [];
  189. itemIDs.forEach((itemID) => {
  190. if (this.getItem(itemID)) {
  191. validItems.push(this.itemIndex(itemID));
  192. }
  193. });
  194. const modifiedValues = {};
  195. for (let i = 0; i < validItems.length; i++) {
  196. modifiedValues[`items.${validItems[i]}.sort`] = i;
  197. }
  198. return {
  199. $set: modifiedValues,
  200. };
  201. },
  202. });
  203. if (Meteor.isServer) {
  204. Meteor.startup(() => {
  205. Checklists._collection._ensureIndex({ cardId: 1, createdAt: 1 });
  206. });
  207. Checklists.after.insert((userId, doc) => {
  208. Activities.insert({
  209. userId,
  210. activityType: 'addChecklist',
  211. cardId: doc.cardId,
  212. boardId: Cards.findOne(doc.cardId).boardId,
  213. checklistId: doc._id,
  214. });
  215. });
  216. //TODO: so there will be no activity for adding item into checklist, maybe will be implemented in the future.
  217. // The future is now
  218. Checklists.after.update((userId, doc, fieldNames, modifier) => {
  219. if (fieldNames.includes('items')) {
  220. if (modifier.$addToSet) {
  221. Activities.insert({
  222. userId,
  223. activityType: 'addChecklistItem',
  224. cardId: doc.cardId,
  225. boardId: Cards.findOne(doc.cardId).boardId,
  226. checklistId: doc._id,
  227. checklistItemId: modifier.$addToSet.items._id,
  228. });
  229. } else if (modifier.$pull) {
  230. const activity = Activities.findOne({
  231. checklistItemId: modifier.$pull.items._id,
  232. });
  233. if (activity) {
  234. Activities.remove(activity._id);
  235. }
  236. }
  237. }
  238. });
  239. Checklists.before.remove((userId, doc) => {
  240. const activities = Activities.find({ checklistId: doc._id });
  241. if (activities) {
  242. activities.forEach((activity) => {
  243. Activities.remove(activity._id);
  244. });
  245. }
  246. });
  247. }
  248. //CARD COMMENT REST API
  249. if (Meteor.isServer) {
  250. JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res, next) {
  251. Authentication.checkUserId( req.userId);
  252. const paramCardId = req.params.cardId;
  253. JsonRoutes.sendResult(res, {
  254. code: 200,
  255. data: Checklists.find({ cardId: paramCardId }).map(function (doc) {
  256. return {
  257. _id: doc._id,
  258. title: doc.title,
  259. };
  260. }),
  261. });
  262. });
  263. JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res, next) {
  264. Authentication.checkUserId( req.userId);
  265. const paramChecklistId = req.params.checklistId;
  266. const paramCardId = req.params.cardId;
  267. JsonRoutes.sendResult(res, {
  268. code: 200,
  269. data: Checklists.findOne({ _id: paramChecklistId, cardId: paramCardId }),
  270. });
  271. });
  272. JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res, next) {
  273. Authentication.checkUserId( req.userId);
  274. const paramCardId = req.params.cardId;
  275. const checklistToSend = {};
  276. checklistToSend.cardId = paramCardId;
  277. checklistToSend.title = req.body.title;
  278. checklistToSend.items = [];
  279. const id = Checklists.insert(checklistToSend);
  280. const checklist = Checklists.findOne({_id: id});
  281. req.body.items.forEach(function (item) {
  282. checklist.addItem(item);
  283. }, this);
  284. JsonRoutes.sendResult(res, {
  285. code: 200,
  286. data: {
  287. _id: id,
  288. },
  289. });
  290. });
  291. JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res, next) {
  292. Authentication.checkUserId( req.userId);
  293. const paramCommentId = req.params.commentId;
  294. const paramCardId = req.params.cardId;
  295. Checklists.remove({ _id: paramCommentId, cardId: paramCardId });
  296. JsonRoutes.sendResult(res, {
  297. code: 200,
  298. data: {
  299. _id: paramCardId,
  300. },
  301. });
  302. });
  303. }