cards.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. const escapeForRegex = require('escape-string-regexp');
  2. Meteor.publish('card', cardId => {
  3. check(cardId, String);
  4. return Cards.find({ _id: cardId });
  5. });
  6. Meteor.publish('myCards', function() {
  7. const userId = Meteor.userId();
  8. const archivedBoards = [];
  9. Boards.find({ archived: true }).forEach(board => {
  10. archivedBoards.push(board._id);
  11. });
  12. const archivedSwimlanes = [];
  13. Swimlanes.find({ archived: true }).forEach(swimlane => {
  14. archivedSwimlanes.push(swimlane._id);
  15. });
  16. const archivedLists = [];
  17. Lists.find({ archived: true }).forEach(list => {
  18. archivedLists.push(list._id);
  19. });
  20. selector = {
  21. archived: false,
  22. boardId: { $nin: archivedBoards },
  23. swimlaneId: { $nin: archivedSwimlanes },
  24. listId: { $nin: archivedLists },
  25. $or: [{ members: userId }, { assignees: userId }],
  26. };
  27. const cards = Cards.find(selector, {
  28. fields: {
  29. _id: 1,
  30. archived: 1,
  31. boardId: 1,
  32. swimlaneId: 1,
  33. listId: 1,
  34. title: 1,
  35. type: 1,
  36. sort: 1,
  37. members: 1,
  38. assignees: 1,
  39. colors: 1,
  40. dueAt: 1,
  41. },
  42. });
  43. const boards = [];
  44. const swimlanes = [];
  45. const lists = [];
  46. const users = [];
  47. cards.forEach(card => {
  48. if (card.boardId) boards.push(card.boardId);
  49. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  50. if (card.listId) lists.push(card.listId);
  51. if (card.members) {
  52. card.members.forEach(userId => {
  53. users.push(userId);
  54. });
  55. }
  56. if (card.assignees) {
  57. card.assignees.forEach(userId => {
  58. users.push(userId);
  59. });
  60. }
  61. });
  62. return [
  63. cards,
  64. Boards.find({ _id: { $in: boards } }),
  65. Swimlanes.find({ _id: { $in: swimlanes } }),
  66. Lists.find({ _id: { $in: lists } }),
  67. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  68. ];
  69. });
  70. Meteor.publish('dueCards', function(allUsers = false) {
  71. check(allUsers, Boolean);
  72. // eslint-disable-next-line no-console
  73. // console.log('all users:', allUsers);
  74. const user = Users.findOne({ _id: this.userId });
  75. const archivedBoards = [];
  76. Boards.find({ archived: true }).forEach(board => {
  77. archivedBoards.push(board._id);
  78. });
  79. const permiitedBoards = [];
  80. let selector = {
  81. archived: false,
  82. };
  83. selector.$or = [
  84. { permission: 'public' },
  85. { members: { $elemMatch: { userId: user._id, isActive: true } } },
  86. ];
  87. Boards.find(selector).forEach(board => {
  88. permiitedBoards.push(board._id);
  89. });
  90. const archivedSwimlanes = [];
  91. Swimlanes.find({ archived: true }).forEach(swimlane => {
  92. archivedSwimlanes.push(swimlane._id);
  93. });
  94. const archivedLists = [];
  95. Lists.find({ archived: true }).forEach(list => {
  96. archivedLists.push(list._id);
  97. });
  98. selector = {
  99. archived: false,
  100. boardId: { $nin: archivedBoards, $in: permiitedBoards },
  101. swimlaneId: { $nin: archivedSwimlanes },
  102. listId: { $nin: archivedLists },
  103. dueAt: { $ne: null },
  104. endAt: null,
  105. };
  106. if (!allUsers) {
  107. selector.$or = [{ members: user._id }, { assignees: user._id }];
  108. }
  109. const cards = Cards.find(selector, {
  110. fields: {
  111. _id: 1,
  112. archived: 1,
  113. boardId: 1,
  114. swimlaneId: 1,
  115. listId: 1,
  116. title: 1,
  117. type: 1,
  118. sort: 1,
  119. members: 1,
  120. assignees: 1,
  121. colors: 1,
  122. dueAt: 1,
  123. },
  124. });
  125. const boards = [];
  126. const swimlanes = [];
  127. const lists = [];
  128. const users = [];
  129. cards.forEach(card => {
  130. if (card.boardId) boards.push(card.boardId);
  131. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  132. if (card.listId) lists.push(card.listId);
  133. if (card.members) {
  134. card.members.forEach(userId => {
  135. users.push(userId);
  136. });
  137. }
  138. if (card.assignees) {
  139. card.assignees.forEach(userId => {
  140. users.push(userId);
  141. });
  142. }
  143. });
  144. return [
  145. cards,
  146. Boards.find({ _id: { $in: boards } }),
  147. Swimlanes.find({ _id: { $in: swimlanes } }),
  148. Lists.find({ _id: { $in: lists } }),
  149. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  150. ];
  151. });
  152. Meteor.publish('globalSearch', function(sessionId, queryParams) {
  153. check(sessionId, String);
  154. check(queryParams, Object);
  155. const userId = Meteor.userId();
  156. // eslint-disable-next-line no-console
  157. // console.log('userId:', userId);
  158. const errors = new (class {
  159. constructor() {
  160. this.notFound = {
  161. boards: [],
  162. swimlanes: [],
  163. lists: [],
  164. labels: [],
  165. users: [],
  166. members: [],
  167. assignees: [],
  168. status: [],
  169. comments: [],
  170. };
  171. this.colorMap = {};
  172. for (const color of Boards.simpleSchema()._schema['labels.$.color']
  173. .allowedValues) {
  174. this.colorMap[TAPi18n.__(`color-${color}`)] = color;
  175. }
  176. }
  177. hasErrors() {
  178. for (const prop in this.notFound) {
  179. if (this.notFound[prop].length) {
  180. return true;
  181. }
  182. }
  183. return false;
  184. }
  185. errorMessages() {
  186. const messages = [];
  187. this.notFound.boards.forEach(board => {
  188. messages.push({ tag: 'board-title-not-found', value: board });
  189. });
  190. this.notFound.swimlanes.forEach(swim => {
  191. messages.push({ tag: 'swimlane-title-not-found', value: swim });
  192. });
  193. this.notFound.lists.forEach(list => {
  194. messages.push({ tag: 'list-title-not-found', value: list });
  195. });
  196. this.notFound.comments.forEach(comments => {
  197. comments.forEach(text => {
  198. messages.push({ tag: 'comment-not-found', value: text });
  199. });
  200. });
  201. this.notFound.labels.forEach(label => {
  202. messages.push({ tag: 'label-not-found', value: label, color: true });
  203. });
  204. this.notFound.users.forEach(user => {
  205. messages.push({ tag: 'user-username-not-found', value: user });
  206. });
  207. this.notFound.members.forEach(user => {
  208. messages.push({ tag: 'user-username-not-found', value: user });
  209. });
  210. this.notFound.assignees.forEach(user => {
  211. messages.push({ tag: 'user-username-not-found', value: user });
  212. });
  213. return messages;
  214. }
  215. })();
  216. let archived = false;
  217. let endAt = null;
  218. if (queryParams.status.length) {
  219. queryParams.status.forEach(status => {
  220. if (status === 'archived') {
  221. archived = true;
  222. } else if (status === 'all') {
  223. archived = null;
  224. } else if (status === 'ended') {
  225. endAt = { $nin: [null, ''] };
  226. }
  227. });
  228. }
  229. const selector = {
  230. type: 'cardType-card',
  231. // boardId: { $in: Boards.userBoardIds(userId) },
  232. $and: [],
  233. };
  234. const boardsSelector = {};
  235. if (archived !== null) {
  236. boardsSelector.archived = archived;
  237. if (archived) {
  238. selector.boardId = { $in: Boards.userBoardIds(userId, null) };
  239. selector.$and.push({
  240. $or: [
  241. { boardId: { $in: Boards.userBoardIds(userId, archived) } },
  242. { swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } },
  243. { listId: { $in: Lists.archivedListIds() } },
  244. { archived: true },
  245. ],
  246. });
  247. } else {
  248. selector.boardId = { $in: Boards.userBoardIds(userId, false) };
  249. selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() };
  250. selector.listId = { $nin: Lists.archivedListIds() };
  251. selector.archived = false;
  252. }
  253. } else {
  254. selector.boardId = { $in: Boards.userBoardIds(userId, null) };
  255. }
  256. if (endAt !== null) {
  257. selector.endAt = endAt;
  258. }
  259. if (queryParams.boards.length) {
  260. const queryBoards = [];
  261. queryParams.boards.forEach(query => {
  262. const boards = Boards.userSearch(userId, {
  263. title: new RegExp(escapeForRegex(query), 'i'),
  264. });
  265. if (boards.count()) {
  266. boards.forEach(board => {
  267. queryBoards.push(board._id);
  268. });
  269. } else {
  270. errors.notFound.boards.push(query);
  271. }
  272. });
  273. selector.boardId.$in = queryBoards;
  274. }
  275. if (queryParams.swimlanes.length) {
  276. const querySwimlanes = [];
  277. queryParams.swimlanes.forEach(query => {
  278. const swimlanes = Swimlanes.find({
  279. title: new RegExp(escapeForRegex(query), 'i'),
  280. });
  281. if (swimlanes.count()) {
  282. swimlanes.forEach(swim => {
  283. querySwimlanes.push(swim._id);
  284. });
  285. } else {
  286. errors.notFound.swimlanes.push(query);
  287. }
  288. });
  289. selector.swimlaneId.$in = querySwimlanes;
  290. }
  291. if (queryParams.lists.length) {
  292. const queryLists = [];
  293. queryParams.lists.forEach(query => {
  294. const lists = Lists.find({
  295. title: new RegExp(escapeForRegex(query), 'i'),
  296. });
  297. if (lists.count()) {
  298. lists.forEach(list => {
  299. queryLists.push(list._id);
  300. });
  301. } else {
  302. errors.notFound.lists.push(query);
  303. }
  304. });
  305. selector.listId.$in = queryLists;
  306. }
  307. if (queryParams.comments.length) {
  308. const cardIds = CardComments.textSearch(userId, queryParams.comments).map(
  309. com => {
  310. return com.cardId;
  311. },
  312. );
  313. if (cardIds.length) {
  314. selector._id = { $in: cardIds };
  315. } else {
  316. errors.notFound.comments.push(queryParams.comments);
  317. }
  318. }
  319. if (queryParams.dueAt !== null) {
  320. selector.dueAt = { $lte: new Date(queryParams.dueAt) };
  321. }
  322. if (queryParams.createdAt !== null) {
  323. selector.createdAt = { $gte: new Date(queryParams.createdAt) };
  324. }
  325. if (queryParams.modifiedAt !== null) {
  326. selector.modifiedAt = { $gte: new Date(queryParams.modifiedAt) };
  327. }
  328. const queryMembers = [];
  329. const queryAssignees = [];
  330. if (queryParams.users.length) {
  331. queryParams.users.forEach(query => {
  332. const users = Users.find({
  333. username: query,
  334. });
  335. if (users.count()) {
  336. users.forEach(user => {
  337. queryMembers.push(user._id);
  338. queryAssignees.push(user._id);
  339. });
  340. } else {
  341. errors.notFound.users.push(query);
  342. }
  343. });
  344. }
  345. if (queryParams.members.length) {
  346. queryParams.members.forEach(query => {
  347. const users = Users.find({
  348. username: query,
  349. });
  350. if (users.count()) {
  351. users.forEach(user => {
  352. queryMembers.push(user._id);
  353. });
  354. } else {
  355. errors.notFound.members.push(query);
  356. }
  357. });
  358. }
  359. if (queryParams.assignees.length) {
  360. queryParams.assignees.forEach(query => {
  361. const users = Users.find({
  362. username: query,
  363. });
  364. if (users.count()) {
  365. users.forEach(user => {
  366. queryAssignees.push(user._id);
  367. });
  368. } else {
  369. errors.notFound.assignees.push(query);
  370. }
  371. });
  372. }
  373. if (queryMembers.length && queryAssignees.length) {
  374. selector.$and.push({
  375. $or: [
  376. { members: { $in: queryMembers } },
  377. { assignees: { $in: queryAssignees } },
  378. ],
  379. });
  380. } else if (queryMembers.length) {
  381. selector.members = { $in: queryMembers };
  382. } else if (queryAssignees.length) {
  383. selector.assignees = { $in: queryAssignees };
  384. }
  385. if (queryParams.labels.length) {
  386. queryParams.labels.forEach(label => {
  387. const queryLabels = [];
  388. let boards = Boards.userSearch(userId, {
  389. labels: { $elemMatch: { color: label.toLowerCase() } },
  390. });
  391. if (boards.count()) {
  392. boards.forEach(board => {
  393. // eslint-disable-next-line no-console
  394. // console.log('board:', board);
  395. // eslint-disable-next-line no-console
  396. // console.log('board.labels:', board.labels);
  397. board.labels
  398. .filter(boardLabel => {
  399. return boardLabel.color === label.toLowerCase();
  400. })
  401. .forEach(boardLabel => {
  402. queryLabels.push(boardLabel._id);
  403. });
  404. });
  405. } else {
  406. // eslint-disable-next-line no-console
  407. // console.log('label:', label);
  408. const reLabel = new RegExp(escapeForRegex(label), 'i');
  409. // eslint-disable-next-line no-console
  410. // console.log('reLabel:', reLabel);
  411. boards = Boards.userSearch(userId, {
  412. labels: { $elemMatch: { name: reLabel } },
  413. });
  414. if (boards.count()) {
  415. boards.forEach(board => {
  416. board.labels
  417. .filter(boardLabel => {
  418. return boardLabel.name.match(reLabel);
  419. })
  420. .forEach(boardLabel => {
  421. queryLabels.push(boardLabel._id);
  422. });
  423. });
  424. } else {
  425. errors.notFound.labels.push(label);
  426. }
  427. }
  428. selector.labelIds = { $in: queryLabels };
  429. });
  430. }
  431. let cards = null;
  432. if (!errors.hasErrors()) {
  433. if (queryParams.text) {
  434. const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
  435. selector.$and.push({
  436. $or: [
  437. { title: regex },
  438. { description: regex },
  439. { customFields: { $elemMatch: { value: regex } } },
  440. {
  441. _id: {
  442. $in: CardComments.textSearch(userId, [queryParams.text]).map(
  443. com => com.cardId,
  444. ),
  445. },
  446. },
  447. ],
  448. });
  449. }
  450. if (selector.$and.length === 0) {
  451. delete selector.$and;
  452. }
  453. // eslint-disable-next-line no-console
  454. console.log('selector:', selector);
  455. // eslint-disable-next-line no-console
  456. console.log('selector.$and:', selector.$and);
  457. const projection = {
  458. fields: {
  459. _id: 1,
  460. archived: 1,
  461. boardId: 1,
  462. swimlaneId: 1,
  463. listId: 1,
  464. title: 1,
  465. type: 1,
  466. sort: 1,
  467. members: 1,
  468. assignees: 1,
  469. colors: 1,
  470. dueAt: 1,
  471. createdAt: 1,
  472. modifiedAt: 1,
  473. labelIds: 1,
  474. },
  475. limit: 50,
  476. };
  477. if (queryParams.sort === 'due') {
  478. projection.sort = {
  479. dueAt: 1,
  480. boardId: 1,
  481. swimlaneId: 1,
  482. listId: 1,
  483. sort: 1,
  484. };
  485. } else if (queryParams.sort === 'modified') {
  486. projection.sort = {
  487. modifiedAt: -1,
  488. boardId: 1,
  489. swimlaneId: 1,
  490. listId: 1,
  491. sort: 1,
  492. };
  493. } else if (queryParams.sort === 'created') {
  494. projection.sort = {
  495. createdAt: -1,
  496. boardId: 1,
  497. swimlaneId: 1,
  498. listId: 1,
  499. sort: 1,
  500. };
  501. } else if (queryParams.sort === 'system') {
  502. projection.sort = {
  503. boardId: 1,
  504. swimlaneId: 1,
  505. listId: 1,
  506. modifiedAt: 1,
  507. sort: 1,
  508. };
  509. }
  510. cards = Cards.find(selector, projection);
  511. // eslint-disable-next-line no-console
  512. // console.log('count:', cards.count());
  513. }
  514. const update = {
  515. $set: {
  516. totalHits: 0,
  517. lastHit: 0,
  518. cards: [],
  519. errors: errors.errorMessages(),
  520. },
  521. };
  522. if (cards) {
  523. update.$set.totalHits = cards.count();
  524. update.$set.lastHit = cards.count() > 50 ? 50 : cards.count();
  525. update.$set.cards = cards.map(card => {
  526. return card._id;
  527. });
  528. }
  529. SessionData.upsert({ userId, sessionId }, update);
  530. // remove old session data
  531. SessionData.remove({
  532. userId,
  533. modifiedAt: {
  534. $lt: new Date(
  535. moment()
  536. .subtract(1, 'day')
  537. .format(),
  538. ),
  539. },
  540. });
  541. if (cards) {
  542. const boards = [];
  543. const swimlanes = [];
  544. const lists = [];
  545. const users = [this.userId];
  546. cards.forEach(card => {
  547. if (card.boardId) boards.push(card.boardId);
  548. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  549. if (card.listId) lists.push(card.listId);
  550. if (card.members) {
  551. card.members.forEach(userId => {
  552. users.push(userId);
  553. });
  554. }
  555. if (card.assignees) {
  556. card.assignees.forEach(userId => {
  557. users.push(userId);
  558. });
  559. }
  560. });
  561. const fields = {
  562. _id: 1,
  563. title: 1,
  564. archived: 1,
  565. sort: 1,
  566. type: 1,
  567. };
  568. return [
  569. cards,
  570. Boards.find(
  571. { _id: { $in: boards } },
  572. { fields: { ...fields, labels: 1, color: 1 } },
  573. ),
  574. Swimlanes.find(
  575. { _id: { $in: swimlanes } },
  576. { fields: { ...fields, color: 1 } },
  577. ),
  578. Lists.find({ _id: { $in: lists } }, { fields }),
  579. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  580. SessionData.find({ userId: this.userId, sessionId }),
  581. ];
  582. }
  583. return [SessionData.find({ userId: this.userId, sessionId })];
  584. });
  585. Meteor.publish('brokenCards', function() {
  586. const user = Users.findOne({ _id: this.userId });
  587. const permiitedBoards = [null];
  588. let selector = {};
  589. selector.$or = [
  590. { permission: 'public' },
  591. { members: { $elemMatch: { userId: user._id, isActive: true } } },
  592. ];
  593. Boards.find(selector).forEach(board => {
  594. permiitedBoards.push(board._id);
  595. });
  596. selector = {
  597. boardId: { $in: permiitedBoards },
  598. $or: [
  599. { boardId: { $in: [null, ''] } },
  600. { swimlaneId: { $in: [null, ''] } },
  601. { listId: { $in: [null, ''] } },
  602. ],
  603. };
  604. const cards = Cards.find(selector, {
  605. fields: {
  606. _id: 1,
  607. archived: 1,
  608. boardId: 1,
  609. swimlaneId: 1,
  610. listId: 1,
  611. title: 1,
  612. type: 1,
  613. sort: 1,
  614. members: 1,
  615. assignees: 1,
  616. colors: 1,
  617. dueAt: 1,
  618. },
  619. });
  620. const boards = [];
  621. const swimlanes = [];
  622. const lists = [];
  623. const users = [];
  624. cards.forEach(card => {
  625. if (card.boardId) boards.push(card.boardId);
  626. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  627. if (card.listId) lists.push(card.listId);
  628. if (card.members) {
  629. card.members.forEach(userId => {
  630. users.push(userId);
  631. });
  632. }
  633. if (card.assignees) {
  634. card.assignees.forEach(userId => {
  635. users.push(userId);
  636. });
  637. }
  638. });
  639. return [
  640. cards,
  641. Boards.find({ _id: { $in: boards } }),
  642. Swimlanes.find({ _id: { $in: swimlanes } }),
  643. Lists.find({ _id: { $in: lists } }),
  644. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  645. ];
  646. });