cardComments.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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)) });
  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. });
  157. CardComments.after.remove((userId, doc) => {
  158. const activity = Activities.findOne({ commentId: doc._id });
  159. if (activity) {
  160. Activities.remove(activity._id);
  161. }
  162. });
  163. }
  164. //CARD COMMENT REST API
  165. if (Meteor.isServer) {
  166. /**
  167. * @operation get_all_comments
  168. * @summary Get all comments attached to a card
  169. *
  170. * @param {string} boardId the board ID of the card
  171. * @param {string} cardId the ID of the card
  172. * @return_type [{_id: string,
  173. * comment: string,
  174. * authorId: string}]
  175. */
  176. JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function(
  177. req,
  178. res,
  179. ) {
  180. try {
  181. Authentication.checkUserId(req.userId);
  182. const paramBoardId = req.params.boardId;
  183. const paramCardId = req.params.cardId;
  184. JsonRoutes.sendResult(res, {
  185. code: 200,
  186. data: CardComments.find({
  187. boardId: paramBoardId,
  188. cardId: paramCardId,
  189. }).map(function(doc) {
  190. return {
  191. _id: doc._id,
  192. comment: doc.text,
  193. authorId: doc.userId,
  194. };
  195. }),
  196. });
  197. } catch (error) {
  198. JsonRoutes.sendResult(res, {
  199. code: 200,
  200. data: error,
  201. });
  202. }
  203. });
  204. /**
  205. * @operation get_comment
  206. * @summary Get a comment on a card
  207. *
  208. * @param {string} boardId the board ID of the card
  209. * @param {string} cardId the ID of the card
  210. * @param {string} commentId the ID of the comment to retrieve
  211. * @return_type CardComments
  212. */
  213. JsonRoutes.add(
  214. 'GET',
  215. '/api/boards/:boardId/cards/:cardId/comments/:commentId',
  216. function(req, res) {
  217. try {
  218. Authentication.checkUserId(req.userId);
  219. const paramBoardId = req.params.boardId;
  220. const paramCommentId = req.params.commentId;
  221. const paramCardId = req.params.cardId;
  222. JsonRoutes.sendResult(res, {
  223. code: 200,
  224. data: CardComments.findOne({
  225. _id: paramCommentId,
  226. cardId: paramCardId,
  227. boardId: paramBoardId,
  228. }),
  229. });
  230. } catch (error) {
  231. JsonRoutes.sendResult(res, {
  232. code: 200,
  233. data: error,
  234. });
  235. }
  236. },
  237. );
  238. /**
  239. * @operation new_comment
  240. * @summary Add a comment on a card
  241. *
  242. * @param {string} boardId the board ID of the card
  243. * @param {string} cardId the ID of the card
  244. * @param {string} authorId the user who 'posted' the comment
  245. * @param {string} text the content of the comment
  246. * @return_type {_id: string}
  247. */
  248. JsonRoutes.add(
  249. 'POST',
  250. '/api/boards/:boardId/cards/:cardId/comments',
  251. function(req, res) {
  252. try {
  253. Authentication.checkUserId(req.userId);
  254. const paramBoardId = req.params.boardId;
  255. const paramCardId = req.params.cardId;
  256. const id = CardComments.direct.insert({
  257. userId: req.body.authorId,
  258. text: req.body.comment,
  259. cardId: paramCardId,
  260. boardId: paramBoardId,
  261. });
  262. JsonRoutes.sendResult(res, {
  263. code: 200,
  264. data: {
  265. _id: id,
  266. },
  267. });
  268. const cardComment = CardComments.findOne({
  269. _id: id,
  270. cardId: paramCardId,
  271. boardId: paramBoardId,
  272. });
  273. commentCreation(req.body.authorId, cardComment);
  274. } catch (error) {
  275. JsonRoutes.sendResult(res, {
  276. code: 200,
  277. data: error,
  278. });
  279. }
  280. },
  281. );
  282. /**
  283. * @operation delete_comment
  284. * @summary Delete a comment on a card
  285. *
  286. * @param {string} boardId the board ID of the card
  287. * @param {string} cardId the ID of the card
  288. * @param {string} commentId the ID of the comment to delete
  289. * @return_type {_id: string}
  290. */
  291. JsonRoutes.add(
  292. 'DELETE',
  293. '/api/boards/:boardId/cards/:cardId/comments/:commentId',
  294. function(req, res) {
  295. try {
  296. Authentication.checkUserId(req.userId);
  297. const paramBoardId = req.params.boardId;
  298. const paramCommentId = req.params.commentId;
  299. const paramCardId = req.params.cardId;
  300. CardComments.remove({
  301. _id: paramCommentId,
  302. cardId: paramCardId,
  303. boardId: paramBoardId,
  304. });
  305. JsonRoutes.sendResult(res, {
  306. code: 200,
  307. data: {
  308. _id: paramCardId,
  309. },
  310. });
  311. } catch (error) {
  312. JsonRoutes.sendResult(res, {
  313. code: 200,
  314. data: error,
  315. });
  316. }
  317. },
  318. );
  319. }
  320. export default CardComments;