cardComments.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. const escapeForRegex = require('escape-string-regexp');
  2. CardComments = new Mongo.Collection('card_comments');
  3. /**
  4. * A comment on a card
  5. */
  6. CardComments.attachSchema(
  7. new SimpleSchema({
  8. boardId: {
  9. /**
  10. * the board ID
  11. */
  12. type: String,
  13. },
  14. cardId: {
  15. /**
  16. * the card ID
  17. */
  18. type: String,
  19. },
  20. // XXX Rename in `content`? `text` is a bit vague...
  21. text: {
  22. /**
  23. * the text of the comment
  24. */
  25. type: String,
  26. },
  27. createdAt: {
  28. /**
  29. * when was the comment created
  30. */
  31. type: Date,
  32. denyUpdate: false,
  33. // eslint-disable-next-line consistent-return
  34. autoValue() {
  35. if (this.isInsert) {
  36. return new Date();
  37. } else if (this.isUpsert) {
  38. return { $setOnInsert: new Date() };
  39. } else {
  40. this.unset();
  41. }
  42. },
  43. },
  44. modifiedAt: {
  45. type: Date,
  46. denyUpdate: false,
  47. // eslint-disable-next-line consistent-return
  48. autoValue() {
  49. if (this.isInsert || this.isUpsert || this.isUpdate) {
  50. return new Date();
  51. } else {
  52. this.unset();
  53. }
  54. },
  55. },
  56. // XXX Should probably be called `authorId`
  57. userId: {
  58. /**
  59. * the author ID of the comment
  60. */
  61. type: String,
  62. // eslint-disable-next-line consistent-return
  63. autoValue() {
  64. if (this.isInsert && !this.isSet) {
  65. return this.userId;
  66. }
  67. },
  68. },
  69. }),
  70. );
  71. CardComments.allow({
  72. insert(userId, doc) {
  73. return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
  74. },
  75. update(userId, doc) {
  76. return userId === doc.userId;
  77. },
  78. remove(userId, doc) {
  79. return userId === doc.userId;
  80. },
  81. fetch: ['userId', 'boardId'],
  82. });
  83. CardComments.helpers({
  84. copy(newCardId) {
  85. this.cardId = newCardId;
  86. delete this._id;
  87. CardComments.insert(this);
  88. },
  89. user() {
  90. return Users.findOne(this.userId);
  91. },
  92. });
  93. CardComments.hookOptions.after.update = { fetchPrevious: false };
  94. function commentCreation(userId, doc) {
  95. const card = Cards.findOne(doc.cardId);
  96. Activities.insert({
  97. userId,
  98. activityType: 'addComment',
  99. boardId: doc.boardId,
  100. cardId: doc.cardId,
  101. commentId: doc._id,
  102. listId: card.listId,
  103. swimlaneId: card.swimlaneId,
  104. });
  105. }
  106. CardComments.textSearch = (userId, textArray) => {
  107. const selector = {
  108. boardId: { $in: Boards.userBoardIds(userId) },
  109. $and: [],
  110. };
  111. for (const text of textArray) {
  112. selector.$and.push({ text: new RegExp(escapeForRegex(text), 'i') });
  113. }
  114. // eslint-disable-next-line no-console
  115. // console.log('cardComments selector:', selector);
  116. const comments = CardComments.find(selector);
  117. // eslint-disable-next-line no-console
  118. // console.log('count:', comments.count());
  119. // eslint-disable-next-line no-console
  120. // console.log('cards with comments:', comments.map(com => { return com.cardId }));
  121. return comments;
  122. };
  123. if (Meteor.isServer) {
  124. // Comments are often fetched within a card, so we create an index to make these
  125. // queries more efficient.
  126. Meteor.startup(() => {
  127. CardComments._collection._ensureIndex({ modifiedAt: -1 });
  128. CardComments._collection._ensureIndex({ cardId: 1, createdAt: -1 });
  129. });
  130. CardComments.after.insert((userId, doc) => {
  131. commentCreation(userId, doc);
  132. });
  133. CardComments.after.update((userId, doc) => {
  134. const card = Cards.findOne(doc.cardId);
  135. Activities.insert({
  136. userId,
  137. activityType: 'editComment',
  138. boardId: doc.boardId,
  139. cardId: doc.cardId,
  140. commentId: doc._id,
  141. listId: card.listId,
  142. swimlaneId: card.swimlaneId,
  143. });
  144. });
  145. CardComments.before.remove((userId, doc) => {
  146. const card = Cards.findOne(doc.cardId);
  147. Activities.insert({
  148. userId,
  149. activityType: 'deleteComment',
  150. boardId: doc.boardId,
  151. cardId: doc.cardId,
  152. commentId: doc._id,
  153. listId: card.listId,
  154. swimlaneId: card.swimlaneId,
  155. });
  156. const activity = Activities.findOne({ commentId: doc._id });
  157. if (activity) {
  158. Activities.remove(activity._id);
  159. }
  160. });
  161. }
  162. //CARD COMMENT REST API
  163. if (Meteor.isServer) {
  164. /**
  165. * @operation get_all_comments
  166. * @summary Get all comments attached to a card
  167. *
  168. * @param {string} boardId the board ID of the card
  169. * @param {string} cardId the ID of the card
  170. * @return_type [{_id: string,
  171. * comment: string,
  172. * authorId: string}]
  173. */
  174. JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function(
  175. req,
  176. res,
  177. ) {
  178. try {
  179. const paramBoardId = req.params.boardId;
  180. Authentication.checkBoardAccess(req.userId, paramBoardId);
  181. const paramCardId = req.params.cardId;
  182. JsonRoutes.sendResult(res, {
  183. code: 200,
  184. data: CardComments.find({
  185. boardId: paramBoardId,
  186. cardId: paramCardId,
  187. }).map(function(doc) {
  188. return {
  189. _id: doc._id,
  190. comment: doc.text,
  191. authorId: doc.userId,
  192. };
  193. }),
  194. });
  195. } catch (error) {
  196. JsonRoutes.sendResult(res, {
  197. code: 200,
  198. data: error,
  199. });
  200. }
  201. });
  202. /**
  203. * @operation get_comment
  204. * @summary Get a comment on a card
  205. *
  206. * @param {string} boardId the board ID of the card
  207. * @param {string} cardId the ID of the card
  208. * @param {string} commentId the ID of the comment to retrieve
  209. * @return_type CardComments
  210. */
  211. JsonRoutes.add(
  212. 'GET',
  213. '/api/boards/:boardId/cards/:cardId/comments/:commentId',
  214. function(req, res) {
  215. try {
  216. const paramBoardId = req.params.boardId;
  217. Authentication.checkBoardAccess(req.userId, paramBoardId);
  218. const paramCommentId = req.params.commentId;
  219. const paramCardId = req.params.cardId;
  220. JsonRoutes.sendResult(res, {
  221. code: 200,
  222. data: CardComments.findOne({
  223. _id: paramCommentId,
  224. cardId: paramCardId,
  225. boardId: paramBoardId,
  226. }),
  227. });
  228. } catch (error) {
  229. JsonRoutes.sendResult(res, {
  230. code: 200,
  231. data: error,
  232. });
  233. }
  234. },
  235. );
  236. /**
  237. * @operation new_comment
  238. * @summary Add a comment on a card
  239. *
  240. * @param {string} boardId the board ID of the card
  241. * @param {string} cardId the ID of the card
  242. * @param {string} authorId the user who 'posted' the comment
  243. * @param {string} text the content of the comment
  244. * @return_type {_id: string}
  245. */
  246. JsonRoutes.add(
  247. 'POST',
  248. '/api/boards/:boardId/cards/:cardId/comments',
  249. function(req, res) {
  250. try {
  251. const paramBoardId = req.params.boardId;
  252. Authentication.checkBoardAccess(req.userId, paramBoardId);
  253. const paramCardId = req.params.cardId;
  254. const id = CardComments.direct.insert({
  255. userId: req.body.authorId,
  256. text: req.body.comment,
  257. cardId: paramCardId,
  258. boardId: paramBoardId,
  259. });
  260. JsonRoutes.sendResult(res, {
  261. code: 200,
  262. data: {
  263. _id: id,
  264. },
  265. });
  266. const cardComment = CardComments.findOne({
  267. _id: id,
  268. cardId: paramCardId,
  269. boardId: paramBoardId,
  270. });
  271. commentCreation(req.body.authorId, cardComment);
  272. } catch (error) {
  273. JsonRoutes.sendResult(res, {
  274. code: 200,
  275. data: error,
  276. });
  277. }
  278. },
  279. );
  280. /**
  281. * @operation delete_comment
  282. * @summary Delete a comment on a card
  283. *
  284. * @param {string} boardId the board ID of the card
  285. * @param {string} cardId the ID of the card
  286. * @param {string} commentId the ID of the comment to delete
  287. * @return_type {_id: string}
  288. */
  289. JsonRoutes.add(
  290. 'DELETE',
  291. '/api/boards/:boardId/cards/:cardId/comments/:commentId',
  292. function(req, res) {
  293. try {
  294. const paramBoardId = req.params.boardId;
  295. Authentication.checkBoardAccess(req.userId, paramBoardId);
  296. const paramCommentId = req.params.commentId;
  297. const paramCardId = req.params.cardId;
  298. CardComments.remove({
  299. _id: paramCommentId,
  300. cardId: paramCardId,
  301. boardId: paramBoardId,
  302. });
  303. JsonRoutes.sendResult(res, {
  304. code: 200,
  305. data: {
  306. _id: paramCardId,
  307. },
  308. });
  309. } catch (error) {
  310. JsonRoutes.sendResult(res, {
  311. code: 200,
  312. data: error,
  313. });
  314. }
  315. },
  316. );
  317. }
  318. export default CardComments;