cards.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  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. // eslint-disable-next-line no-console
  156. // console.log('queryParams:', queryParams);
  157. const userId = Meteor.userId();
  158. // eslint-disable-next-line no-console
  159. // console.log('userId:', userId);
  160. const errors = new (class {
  161. constructor() {
  162. this.notFound = {
  163. boards: [],
  164. swimlanes: [],
  165. lists: [],
  166. labels: [],
  167. users: [],
  168. members: [],
  169. assignees: [],
  170. status: [],
  171. comments: [],
  172. };
  173. this.colorMap = {};
  174. for (const color of Boards.simpleSchema()._schema['labels.$.color']
  175. .allowedValues) {
  176. this.colorMap[TAPi18n.__(`color-${color}`)] = color;
  177. }
  178. }
  179. hasErrors() {
  180. for (const value of Object.values(this.notFound)) {
  181. if (value.length) {
  182. return true;
  183. }
  184. }
  185. return false;
  186. }
  187. errorMessages() {
  188. const messages = [];
  189. this.notFound.boards.forEach(board => {
  190. messages.push({ tag: 'board-title-not-found', value: board });
  191. });
  192. this.notFound.swimlanes.forEach(swim => {
  193. messages.push({ tag: 'swimlane-title-not-found', value: swim });
  194. });
  195. this.notFound.lists.forEach(list => {
  196. messages.push({ tag: 'list-title-not-found', value: list });
  197. });
  198. this.notFound.comments.forEach(comments => {
  199. comments.forEach(text => {
  200. messages.push({ tag: 'comment-not-found', value: text });
  201. });
  202. });
  203. this.notFound.labels.forEach(label => {
  204. messages.push({ tag: 'label-not-found', value: label, color: true });
  205. });
  206. this.notFound.users.forEach(user => {
  207. messages.push({ tag: 'user-username-not-found', value: user });
  208. });
  209. this.notFound.members.forEach(user => {
  210. messages.push({ tag: 'user-username-not-found', value: user });
  211. });
  212. this.notFound.assignees.forEach(user => {
  213. messages.push({ tag: 'user-username-not-found', value: user });
  214. });
  215. return messages;
  216. }
  217. })();
  218. let selector = {};
  219. let skip = 0;
  220. if (queryParams.skip) {
  221. skip = queryParams.skip;
  222. }
  223. let limit = 25;
  224. if (queryParams.limit) {
  225. limit = queryParams.limit;
  226. }
  227. if (queryParams.selector) {
  228. selector = queryParams.selector;
  229. } else {
  230. let archived = false;
  231. let endAt = null;
  232. if (queryParams.status.length) {
  233. queryParams.status.forEach(status => {
  234. if (status === 'archived') {
  235. archived = true;
  236. } else if (status === 'all') {
  237. archived = null;
  238. } else if (status === 'ended') {
  239. endAt = { $nin: [null, ''] };
  240. }
  241. });
  242. }
  243. selector = {
  244. type: 'cardType-card',
  245. // boardId: { $in: Boards.userBoardIds(userId) },
  246. $and: [],
  247. };
  248. const boardsSelector = {};
  249. if (archived !== null) {
  250. boardsSelector.archived = archived;
  251. if (archived) {
  252. selector.boardId = { $in: Boards.userBoardIds(userId, null) };
  253. selector.$and.push({
  254. $or: [
  255. { boardId: { $in: Boards.userBoardIds(userId, archived) } },
  256. { swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } },
  257. { listId: { $in: Lists.archivedListIds() } },
  258. { archived: true },
  259. ],
  260. });
  261. } else {
  262. selector.boardId = { $in: Boards.userBoardIds(userId, false) };
  263. selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() };
  264. selector.listId = { $nin: Lists.archivedListIds() };
  265. selector.archived = false;
  266. }
  267. } else {
  268. selector.boardId = { $in: Boards.userBoardIds(userId, null) };
  269. }
  270. if (endAt !== null) {
  271. selector.endAt = endAt;
  272. }
  273. if (queryParams.boards.length) {
  274. const queryBoards = [];
  275. queryParams.boards.forEach(query => {
  276. const boards = Boards.userSearch(userId, {
  277. title: new RegExp(escapeForRegex(query), 'i'),
  278. });
  279. if (boards.count()) {
  280. boards.forEach(board => {
  281. queryBoards.push(board._id);
  282. });
  283. } else {
  284. errors.notFound.boards.push(query);
  285. }
  286. });
  287. selector.boardId.$in = queryBoards;
  288. }
  289. if (queryParams.swimlanes.length) {
  290. const querySwimlanes = [];
  291. queryParams.swimlanes.forEach(query => {
  292. const swimlanes = Swimlanes.find({
  293. title: new RegExp(escapeForRegex(query), 'i'),
  294. });
  295. if (swimlanes.count()) {
  296. swimlanes.forEach(swim => {
  297. querySwimlanes.push(swim._id);
  298. });
  299. } else {
  300. errors.notFound.swimlanes.push(query);
  301. }
  302. });
  303. if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) {
  304. selector.swimlaneId = { $in: [] };
  305. }
  306. selector.swimlaneId.$in = querySwimlanes;
  307. }
  308. if (queryParams.lists.length) {
  309. const queryLists = [];
  310. queryParams.lists.forEach(query => {
  311. const lists = Lists.find({
  312. title: new RegExp(escapeForRegex(query), 'i'),
  313. });
  314. if (lists.count()) {
  315. lists.forEach(list => {
  316. queryLists.push(list._id);
  317. });
  318. } else {
  319. errors.notFound.lists.push(query);
  320. }
  321. });
  322. if (!selector.hasOwnProperty('listId')) {
  323. selector.listId = { $in: [] };
  324. }
  325. selector.listId.$in = queryLists;
  326. }
  327. if (queryParams.comments.length) {
  328. const cardIds = CardComments.textSearch(userId, queryParams.comments).map(
  329. com => {
  330. return com.cardId;
  331. },
  332. );
  333. if (cardIds.length) {
  334. selector._id = { $in: cardIds };
  335. } else {
  336. errors.notFound.comments.push(queryParams.comments);
  337. }
  338. }
  339. if (queryParams.dueAt !== null) {
  340. selector.dueAt = { $lte: new Date(queryParams.dueAt) };
  341. }
  342. if (queryParams.createdAt !== null) {
  343. selector.createdAt = { $gte: new Date(queryParams.createdAt) };
  344. }
  345. if (queryParams.modifiedAt !== null) {
  346. selector.modifiedAt = { $gte: new Date(queryParams.modifiedAt) };
  347. }
  348. const queryMembers = [];
  349. const queryAssignees = [];
  350. if (queryParams.users.length) {
  351. queryParams.users.forEach(query => {
  352. const users = Users.find({
  353. username: query,
  354. });
  355. if (users.count()) {
  356. users.forEach(user => {
  357. queryMembers.push(user._id);
  358. queryAssignees.push(user._id);
  359. });
  360. } else {
  361. errors.notFound.users.push(query);
  362. }
  363. });
  364. }
  365. if (queryParams.members.length) {
  366. queryParams.members.forEach(query => {
  367. const users = Users.find({
  368. username: query,
  369. });
  370. if (users.count()) {
  371. users.forEach(user => {
  372. queryMembers.push(user._id);
  373. });
  374. } else {
  375. errors.notFound.members.push(query);
  376. }
  377. });
  378. }
  379. if (queryParams.assignees.length) {
  380. queryParams.assignees.forEach(query => {
  381. const users = Users.find({
  382. username: query,
  383. });
  384. if (users.count()) {
  385. users.forEach(user => {
  386. queryAssignees.push(user._id);
  387. });
  388. } else {
  389. errors.notFound.assignees.push(query);
  390. }
  391. });
  392. }
  393. if (queryMembers.length && queryAssignees.length) {
  394. selector.$and.push({
  395. $or: [
  396. { members: { $in: queryMembers } },
  397. { assignees: { $in: queryAssignees } },
  398. ],
  399. });
  400. } else if (queryMembers.length) {
  401. selector.members = { $in: queryMembers };
  402. } else if (queryAssignees.length) {
  403. selector.assignees = { $in: queryAssignees };
  404. }
  405. if (queryParams.labels.length) {
  406. queryParams.labels.forEach(label => {
  407. const queryLabels = [];
  408. let boards = Boards.userSearch(userId, {
  409. labels: { $elemMatch: { color: label.toLowerCase() } },
  410. });
  411. if (boards.count()) {
  412. boards.forEach(board => {
  413. // eslint-disable-next-line no-console
  414. // console.log('board:', board);
  415. // eslint-disable-next-line no-console
  416. // console.log('board.labels:', board.labels);
  417. board.labels
  418. .filter(boardLabel => {
  419. return boardLabel.color === label.toLowerCase();
  420. })
  421. .forEach(boardLabel => {
  422. queryLabels.push(boardLabel._id);
  423. });
  424. });
  425. } else {
  426. // eslint-disable-next-line no-console
  427. // console.log('label:', label);
  428. const reLabel = new RegExp(escapeForRegex(label), 'i');
  429. // eslint-disable-next-line no-console
  430. // console.log('reLabel:', reLabel);
  431. boards = Boards.userSearch(userId, {
  432. labels: { $elemMatch: { name: reLabel } },
  433. });
  434. if (boards.count()) {
  435. boards.forEach(board => {
  436. board.labels
  437. .filter(boardLabel => {
  438. return boardLabel.name.match(reLabel);
  439. })
  440. .forEach(boardLabel => {
  441. queryLabels.push(boardLabel._id);
  442. });
  443. });
  444. } else {
  445. errors.notFound.labels.push(label);
  446. }
  447. }
  448. selector.labelIds = { $in: queryLabels };
  449. });
  450. }
  451. if (queryParams.text) {
  452. const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
  453. selector.$and.push({
  454. $or: [
  455. { title: regex },
  456. { description: regex },
  457. { customFields: { $elemMatch: { value: regex } } },
  458. {
  459. _id: {
  460. $in: CardComments.textSearch(userId, [queryParams.text]).map(
  461. com => com.cardId,
  462. ),
  463. },
  464. },
  465. ],
  466. });
  467. }
  468. if (selector.$and.length === 0) {
  469. delete selector.$and;
  470. }
  471. }
  472. // eslint-disable-next-line no-console
  473. // console.log('selector:', selector);
  474. // eslint-disable-next-line no-console
  475. // console.log('selector.$and:', selector.$and);
  476. let cards = null;
  477. if (!errors.hasErrors()) {
  478. const projection = {
  479. fields: {
  480. _id: 1,
  481. archived: 1,
  482. boardId: 1,
  483. swimlaneId: 1,
  484. listId: 1,
  485. title: 1,
  486. type: 1,
  487. sort: 1,
  488. members: 1,
  489. assignees: 1,
  490. colors: 1,
  491. dueAt: 1,
  492. createdAt: 1,
  493. modifiedAt: 1,
  494. labelIds: 1,
  495. customFields: 1,
  496. },
  497. skip,
  498. limit,
  499. };
  500. if (queryParams.sort === 'due') {
  501. projection.sort = {
  502. dueAt: 1,
  503. boardId: 1,
  504. swimlaneId: 1,
  505. listId: 1,
  506. sort: 1,
  507. };
  508. } else if (queryParams.sort === 'modified') {
  509. projection.sort = {
  510. modifiedAt: -1,
  511. boardId: 1,
  512. swimlaneId: 1,
  513. listId: 1,
  514. sort: 1,
  515. };
  516. } else if (queryParams.sort === 'created') {
  517. projection.sort = {
  518. createdAt: -1,
  519. boardId: 1,
  520. swimlaneId: 1,
  521. listId: 1,
  522. sort: 1,
  523. };
  524. } else if (queryParams.sort === 'system') {
  525. projection.sort = {
  526. boardId: 1,
  527. swimlaneId: 1,
  528. listId: 1,
  529. modifiedAt: 1,
  530. sort: 1,
  531. };
  532. }
  533. // eslint-disable-next-line no-console
  534. // console.log('projection:', projection);
  535. cards = Cards.find(selector, projection);
  536. // eslint-disable-next-line no-console
  537. // console.log('count:', cards.count());
  538. }
  539. const update = {
  540. $set: {
  541. totalHits: 0,
  542. lastHit: 0,
  543. resultsCount: 0,
  544. cards: [],
  545. errors: errors.errorMessages(),
  546. selector: SessionData.pickle(selector),
  547. },
  548. };
  549. if (cards) {
  550. update.$set.totalHits = cards.count();
  551. update.$set.lastHit =
  552. skip + limit < cards.count() ? skip + limit : cards.count();
  553. update.$set.cards = cards.map(card => {
  554. return card._id;
  555. });
  556. update.$set.resultsCount = update.$set.cards.length;
  557. }
  558. SessionData.upsert({ userId, sessionId }, update);
  559. // remove old session data
  560. SessionData.remove({
  561. userId,
  562. modifiedAt: {
  563. $lt: new Date(
  564. moment()
  565. .subtract(1, 'day')
  566. .format(),
  567. ),
  568. },
  569. });
  570. if (cards) {
  571. const boards = [];
  572. const swimlanes = [];
  573. const lists = [];
  574. const customFieldIds = [];
  575. const users = [this.userId];
  576. cards.forEach(card => {
  577. if (card.boardId) boards.push(card.boardId);
  578. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  579. if (card.listId) lists.push(card.listId);
  580. if (card.members) {
  581. card.members.forEach(userId => {
  582. users.push(userId);
  583. });
  584. }
  585. if (card.assignees) {
  586. card.assignees.forEach(userId => {
  587. users.push(userId);
  588. });
  589. }
  590. if (card.customFields) {
  591. card.customFields.forEach(field => {
  592. customFieldIds.push(field._id);
  593. });
  594. }
  595. });
  596. const fields = {
  597. _id: 1,
  598. title: 1,
  599. archived: 1,
  600. sort: 1,
  601. type: 1,
  602. };
  603. return [
  604. cards,
  605. Boards.find(
  606. { _id: { $in: boards } },
  607. { fields: { ...fields, labels: 1, color: 1 } },
  608. ),
  609. Swimlanes.find(
  610. { _id: { $in: swimlanes } },
  611. { fields: { ...fields, color: 1 } },
  612. ),
  613. Lists.find({ _id: { $in: lists } }, { fields }),
  614. CustomFields.find({ _id: { $in: customFieldIds } }),
  615. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  616. SessionData.find({ userId: this.userId, sessionId }),
  617. ];
  618. }
  619. return [SessionData.find({ userId: this.userId, sessionId })];
  620. });
  621. Meteor.publish('brokenCards', function() {
  622. const user = Users.findOne({ _id: this.userId });
  623. const permiitedBoards = [null];
  624. let selector = {};
  625. selector.$or = [
  626. { permission: 'public' },
  627. { members: { $elemMatch: { userId: user._id, isActive: true } } },
  628. ];
  629. Boards.find(selector).forEach(board => {
  630. permiitedBoards.push(board._id);
  631. });
  632. selector = {
  633. boardId: { $in: permiitedBoards },
  634. $or: [
  635. { boardId: { $in: [null, ''] } },
  636. { swimlaneId: { $in: [null, ''] } },
  637. { listId: { $in: [null, ''] } },
  638. ],
  639. };
  640. const cards = Cards.find(selector, {
  641. fields: {
  642. _id: 1,
  643. archived: 1,
  644. boardId: 1,
  645. swimlaneId: 1,
  646. listId: 1,
  647. title: 1,
  648. type: 1,
  649. sort: 1,
  650. members: 1,
  651. assignees: 1,
  652. colors: 1,
  653. dueAt: 1,
  654. },
  655. });
  656. const boards = [];
  657. const swimlanes = [];
  658. const lists = [];
  659. const users = [];
  660. cards.forEach(card => {
  661. if (card.boardId) boards.push(card.boardId);
  662. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  663. if (card.listId) lists.push(card.listId);
  664. if (card.members) {
  665. card.members.forEach(userId => {
  666. users.push(userId);
  667. });
  668. }
  669. if (card.assignees) {
  670. card.assignees.forEach(userId => {
  671. users.push(userId);
  672. });
  673. }
  674. });
  675. return [
  676. cards,
  677. Boards.find({ _id: { $in: boards } }),
  678. Swimlanes.find({ _id: { $in: swimlanes } }),
  679. Lists.find({ _id: { $in: lists } }),
  680. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  681. ];
  682. });