cards.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import escapeForRegex from 'escape-string-regexp';
  3. import Users from '../../models/users';
  4. import {
  5. formatDateTime,
  6. formatDate,
  7. formatTime,
  8. getISOWeek,
  9. isValidDate,
  10. isBefore,
  11. isAfter,
  12. isSame,
  13. add,
  14. subtract,
  15. startOf,
  16. endOf,
  17. format,
  18. parseDate,
  19. now,
  20. createDate,
  21. fromNow,
  22. calendar
  23. } from '/imports/lib/dateUtils';
  24. import Boards from '../../models/boards';
  25. import Lists from '../../models/lists';
  26. import Swimlanes from '../../models/swimlanes';
  27. import Cards from '../../models/cards';
  28. import CardComments from '../../models/cardComments';
  29. import Attachments from '../../models/attachments';
  30. import Checklists from '../../models/checklists';
  31. import ChecklistItems from '../../models/checklistItems';
  32. import SessionData from '../../models/usersessiondata';
  33. import CustomFields from '../../models/customFields';
  34. import {
  35. DEFAULT_LIMIT,
  36. OPERATOR_ASSIGNEE,
  37. OPERATOR_BOARD,
  38. OPERATOR_COMMENT,
  39. OPERATOR_CREATED_AT,
  40. OPERATOR_CREATOR,
  41. OPERATOR_DEBUG,
  42. OPERATOR_DUE,
  43. OPERATOR_HAS,
  44. OPERATOR_LABEL,
  45. OPERATOR_LIMIT,
  46. OPERATOR_LIST,
  47. OPERATOR_MEMBER,
  48. OPERATOR_MODIFIED_AT, OPERATOR_ORG,
  49. OPERATOR_SORT,
  50. OPERATOR_STATUS,
  51. OPERATOR_SWIMLANE, OPERATOR_TEAM,
  52. OPERATOR_USER,
  53. ORDER_ASCENDING,
  54. PREDICATE_ALL,
  55. PREDICATE_ARCHIVED,
  56. PREDICATE_ASSIGNEES,
  57. PREDICATE_ATTACHMENT,
  58. PREDICATE_CHECKLIST,
  59. PREDICATE_CREATED_AT,
  60. PREDICATE_DESCRIPTION,
  61. PREDICATE_DUE_AT,
  62. PREDICATE_END_AT,
  63. PREDICATE_ENDED,
  64. PREDICATE_MEMBERS,
  65. PREDICATE_MODIFIED_AT,
  66. PREDICATE_PRIVATE,
  67. PREDICATE_PUBLIC,
  68. PREDICATE_START_AT,
  69. PREDICATE_SYSTEM,
  70. } from '/config/search-const';
  71. import { QueryErrors, QueryParams, Query } from '/config/query-classes';
  72. import { CARD_TYPES } from '../../config/const';
  73. import Org from "../../models/org";
  74. import Team from "../../models/team";
  75. Meteor.publish('card', cardId => {
  76. check(cardId, String);
  77. const ret = ReactiveCache.getCards(
  78. { _id: cardId },
  79. {},
  80. true,
  81. );
  82. return ret;
  83. });
  84. /** publish all data which is necessary to display card details as popup
  85. * @returns array of cursors
  86. */
  87. Meteor.publishRelations('popupCardData', function(cardId) {
  88. check(cardId, String);
  89. this.cursor(
  90. ReactiveCache.getCards(
  91. { _id: cardId },
  92. {},
  93. true,
  94. ),
  95. function(cardId, card) {
  96. this.cursor(ReactiveCache.getBoards({_id: card.boardId}, {}, true));
  97. this.cursor(ReactiveCache.getLists({boardId: card.boardId}, {}, true));
  98. },
  99. );
  100. const ret = this.ready()
  101. return ret;
  102. });
  103. Meteor.publish('myCards', function(sessionId) {
  104. check(sessionId, String);
  105. const queryParams = new QueryParams();
  106. queryParams.addPredicate(OPERATOR_USER, ReactiveCache.getCurrentUser().username);
  107. queryParams.setPredicate(OPERATOR_LIMIT, 200);
  108. const query = buildQuery(queryParams);
  109. query.projection.sort = {
  110. boardId: 1,
  111. swimlaneId: 1,
  112. listId: 1,
  113. };
  114. const ret = findCards(sessionId, query);
  115. return ret;
  116. });
  117. // Meteor.publish('dueCards', function(sessionId, allUsers = false) {
  118. // check(sessionId, String);
  119. // check(allUsers, Boolean);
  120. //
  121. // // eslint-disable-next-line no-console
  122. // // console.log('all users:', allUsers);
  123. //
  124. // const queryParams = {
  125. // has: [{ field: 'dueAt', exists: true }],
  126. // limit: 25,
  127. // skip: 0,
  128. // sort: { name: 'dueAt', order: 'des' },
  129. // };
  130. //
  131. // if (!allUsers) {
  132. // queryParams.users = [ReactiveCache.getCurrentUser().username];
  133. // }
  134. //
  135. // return buildQuery(sessionId, queryParams);
  136. // });
  137. Meteor.publish('globalSearch', function(sessionId, params, text) {
  138. check(sessionId, String);
  139. check(params, Object);
  140. check(text, String);
  141. // eslint-disable-next-line no-console
  142. // console.log('queryParams:', params);
  143. const ret = findCards(sessionId, buildQuery(new QueryParams(params, text)));
  144. return ret;
  145. });
  146. function buildSelector(queryParams) {
  147. const userId = Meteor.userId();
  148. const errors = new QueryErrors();
  149. let selector = {};
  150. // eslint-disable-next-line no-console
  151. // console.log('queryParams:', queryParams);
  152. if (queryParams.selector) {
  153. selector = queryParams.selector;
  154. } else {
  155. const boardsSelector = {};
  156. let archived = false;
  157. let endAt = null;
  158. if (queryParams.hasOperator(OPERATOR_STATUS)) {
  159. queryParams.getPredicates(OPERATOR_STATUS).forEach(status => {
  160. if (status === PREDICATE_ARCHIVED) {
  161. archived = true;
  162. } else if (status === PREDICATE_ALL) {
  163. archived = null;
  164. } else if (status === PREDICATE_ENDED) {
  165. endAt = { $nin: [null, ''] };
  166. } else if ([PREDICATE_PRIVATE, PREDICATE_PUBLIC].includes(status)) {
  167. boardsSelector.permission = status;
  168. }
  169. });
  170. }
  171. if (queryParams.hasOperator(OPERATOR_ORG)) {
  172. const orgs = [];
  173. queryParams.getPredicates(OPERATOR_ORG).forEach(name => {
  174. const org = ReactiveCache.getOrg({
  175. $or: [
  176. { orgDisplayName: name },
  177. { orgShortName: name }
  178. ]
  179. });
  180. if (org) {
  181. orgs.push(org._id);
  182. } else {
  183. errors.addNotFound(OPERATOR_ORG, name);
  184. }
  185. });
  186. if (orgs.length) {
  187. boardsSelector.orgs = {
  188. $elemMatch: { orgId: { $in: orgs }, isActive: true }
  189. };
  190. }
  191. }
  192. if (queryParams.hasOperator(OPERATOR_TEAM)) {
  193. const teams = [];
  194. queryParams.getPredicates(OPERATOR_TEAM).forEach(name => {
  195. const team = ReactiveCache.getTeam({
  196. $or: [
  197. { teamDisplayName: name },
  198. { teamShortName: name }
  199. ]
  200. });
  201. if (team) {
  202. teams.push(team._id);
  203. } else {
  204. errors.addNotFound(OPERATOR_TEAM, name);
  205. }
  206. });
  207. if (teams.length) {
  208. boardsSelector.teams = {
  209. $elemMatch: { teamId: { $in: teams }, isActive: true }
  210. };
  211. }
  212. }
  213. selector = {
  214. type: 'cardType-card',
  215. // boardId: { $in: Boards.userBoardIds(userId) },
  216. $and: [],
  217. };
  218. if (archived !== null) {
  219. if (archived) {
  220. selector.boardId = {
  221. $in: Boards.userBoardIds(userId, null, boardsSelector),
  222. };
  223. selector.$and.push({
  224. $or: [
  225. {
  226. boardId: {
  227. $in: Boards.userBoardIds(userId, archived, boardsSelector),
  228. },
  229. },
  230. { swimlaneId: { $in: Swimlanes.userArchivedSwimlaneIds(userId) } },
  231. { listId: { $in: Lists.userArchivedListIds(userId) } },
  232. { archived: true },
  233. ],
  234. });
  235. } else {
  236. selector.boardId = {
  237. $in: Boards.userBoardIds(userId, false, boardsSelector),
  238. };
  239. selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() };
  240. selector.listId = { $nin: Lists.archivedListIds() };
  241. selector.archived = false;
  242. }
  243. } else {
  244. selector.boardId = {
  245. $in: Boards.userBoardIds(userId, null, boardsSelector),
  246. };
  247. }
  248. if (endAt !== null) {
  249. selector.endAt = endAt;
  250. }
  251. if (queryParams.hasOperator(OPERATOR_BOARD)) {
  252. const queryBoards = [];
  253. queryParams.getPredicates(OPERATOR_BOARD).forEach(query => {
  254. const boards = Boards.userSearch(userId, {
  255. title: new RegExp(escapeForRegex(query), 'i'),
  256. });
  257. if (boards.length) {
  258. boards.forEach(board => {
  259. queryBoards.push(board._id);
  260. });
  261. } else {
  262. errors.addNotFound(OPERATOR_BOARD, query);
  263. }
  264. });
  265. selector.boardId.$in = queryBoards;
  266. }
  267. if (queryParams.hasOperator(OPERATOR_SWIMLANE)) {
  268. const querySwimlanes = [];
  269. queryParams.getPredicates(OPERATOR_SWIMLANE).forEach(query => {
  270. const swimlanes = ReactiveCache.getSwimlanes({
  271. title: new RegExp(escapeForRegex(query), 'i'),
  272. });
  273. if (swimlanes.length) {
  274. swimlanes.forEach(swim => {
  275. querySwimlanes.push(swim._id);
  276. });
  277. } else {
  278. errors.addNotFound(OPERATOR_SWIMLANE, query);
  279. }
  280. });
  281. // eslint-disable-next-line no-prototype-builtins
  282. if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) {
  283. selector.swimlaneId = { $in: [] };
  284. }
  285. selector.swimlaneId.$in = querySwimlanes;
  286. }
  287. if (queryParams.hasOperator(OPERATOR_LIST)) {
  288. const queryLists = [];
  289. queryParams.getPredicates(OPERATOR_LIST).forEach(query => {
  290. const lists = ReactiveCache.getLists({
  291. title: new RegExp(escapeForRegex(query), 'i'),
  292. });
  293. if (lists.length) {
  294. lists.forEach(list => {
  295. queryLists.push(list._id);
  296. });
  297. } else {
  298. errors.addNotFound(OPERATOR_LIST, query);
  299. }
  300. });
  301. // eslint-disable-next-line no-prototype-builtins
  302. if (!selector.hasOwnProperty('listId')) {
  303. selector.listId = { $in: [] };
  304. }
  305. selector.listId.$in = queryLists;
  306. }
  307. if (queryParams.hasOperator(OPERATOR_COMMENT)) {
  308. const cardIds = CardComments.textSearch(
  309. userId,
  310. queryParams.getPredicates(OPERATOR_COMMENT),
  311. com => {
  312. return com.cardId;
  313. },
  314. );
  315. if (cardIds.length) {
  316. selector._id = { $in: cardIds };
  317. } else {
  318. queryParams.getPredicates(OPERATOR_COMMENT).forEach(comment => {
  319. errors.addNotFound(OPERATOR_COMMENT, comment);
  320. });
  321. }
  322. }
  323. [OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].forEach(field => {
  324. if (queryParams.hasOperator(field)) {
  325. selector[field] = {};
  326. const predicate = queryParams.getPredicate(field);
  327. selector[field][predicate.operator] = new Date(predicate.value);
  328. }
  329. });
  330. const queryUsers = {};
  331. queryUsers[OPERATOR_ASSIGNEE] = [];
  332. queryUsers[OPERATOR_MEMBER] = [];
  333. queryUsers[OPERATOR_CREATOR] = [];
  334. if (queryParams.hasOperator(OPERATOR_USER)) {
  335. const users = [];
  336. queryParams.getPredicates(OPERATOR_USER).forEach(username => {
  337. const user = ReactiveCache.getUser({ username });
  338. if (user) {
  339. users.push(user._id);
  340. } else {
  341. errors.addNotFound(OPERATOR_USER, username);
  342. }
  343. });
  344. if (users.length) {
  345. selector.$and.push({
  346. $or: [{ members: { $in: users } }, { assignees: { $in: users } }],
  347. });
  348. }
  349. }
  350. [OPERATOR_MEMBER, OPERATOR_ASSIGNEE, OPERATOR_CREATOR].forEach(key => {
  351. if (queryParams.hasOperator(key)) {
  352. const users = [];
  353. queryParams.getPredicates(key).forEach(username => {
  354. const user = ReactiveCache.getUser({ username });
  355. if (user) {
  356. users.push(user._id);
  357. } else {
  358. errors.addNotFound(key, username);
  359. }
  360. });
  361. if (users.length) {
  362. selector[key] = { $in: users };
  363. }
  364. }
  365. });
  366. if (queryParams.hasOperator(OPERATOR_LABEL)) {
  367. const queryLabels = [];
  368. queryParams.getPredicates(OPERATOR_LABEL).forEach(label => {
  369. let boards = Boards.userBoards(userId, null, {
  370. labels: { $elemMatch: { color: label.toLowerCase() } },
  371. });
  372. if (boards.length) {
  373. boards.forEach(board => {
  374. // eslint-disable-next-line no-console
  375. // console.log('board:', board);
  376. // eslint-disable-next-line no-console
  377. // console.log('board.labels:', board.labels);
  378. board.labels
  379. .filter(boardLabel => {
  380. return boardLabel.color === label.toLowerCase();
  381. })
  382. .forEach(boardLabel => {
  383. queryLabels.push(boardLabel._id);
  384. });
  385. });
  386. } else {
  387. // eslint-disable-next-line no-console
  388. // console.log('label:', label);
  389. const reLabel = new RegExp(escapeForRegex(label), 'i');
  390. // eslint-disable-next-line no-console
  391. // console.log('reLabel:', reLabel);
  392. boards = Boards.userBoards(userId, null, {
  393. labels: { $elemMatch: { name: reLabel } },
  394. });
  395. if (boards.length) {
  396. boards.forEach(board => {
  397. board.labels
  398. .filter(boardLabel => {
  399. if (!boardLabel.name) {
  400. return false;
  401. }
  402. return boardLabel.name.match(reLabel);
  403. })
  404. .forEach(boardLabel => {
  405. queryLabels.push(boardLabel._id);
  406. });
  407. });
  408. } else {
  409. errors.addNotFound(OPERATOR_LABEL, label);
  410. }
  411. }
  412. });
  413. if (queryLabels.length) {
  414. // eslint-disable-next-line no-console
  415. // console.log('queryLabels:', queryLabels);
  416. selector.labelIds = { $in: _.uniq(queryLabels) };
  417. }
  418. }
  419. if (queryParams.hasOperator(OPERATOR_HAS)) {
  420. queryParams.getPredicates(OPERATOR_HAS).forEach(has => {
  421. switch (has.field) {
  422. case PREDICATE_ATTACHMENT:
  423. selector.$and.push({
  424. _id: {
  425. $in: ReactiveCache.getAttachments({}, { fields: { cardId: 1 } }).map(
  426. a => a.cardId,
  427. ),
  428. },
  429. });
  430. break;
  431. case PREDICATE_CHECKLIST:
  432. selector.$and.push({
  433. _id: {
  434. $in: ReactiveCache.getChecklists({}, { fields: { cardId: 1 } }).map(
  435. a => a.cardId,
  436. ),
  437. },
  438. });
  439. break;
  440. case PREDICATE_DESCRIPTION:
  441. case PREDICATE_START_AT:
  442. case PREDICATE_DUE_AT:
  443. case PREDICATE_END_AT:
  444. if (has.exists) {
  445. selector[has.field] = { $exists: true, $nin: [null, ''] };
  446. } else {
  447. selector[has.field] = { $in: [null, ''] };
  448. }
  449. break;
  450. case PREDICATE_ASSIGNEES:
  451. case PREDICATE_MEMBERS:
  452. if (has.exists) {
  453. selector[has.field] = { $exists: true, $nin: [null, []] };
  454. } else {
  455. selector[has.field] = { $in: [null, []] };
  456. }
  457. break;
  458. }
  459. });
  460. }
  461. if (queryParams.text) {
  462. const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
  463. const items = ReactiveCache.getChecklistItems(
  464. { title: regex },
  465. { fields: { cardId: 1, checklistId: 1 } },
  466. );
  467. const checklists = ReactiveCache.getChecklists(
  468. {
  469. $or: [
  470. { title: regex },
  471. { _id: { $in: items.map(item => item.checklistId) } },
  472. ],
  473. },
  474. { fields: { cardId: 1 } },
  475. );
  476. const attachments = ReactiveCache.getAttachments({ 'original.name': regex });
  477. const comments = ReactiveCache.getCardComments(
  478. { text: regex },
  479. { fields: { cardId: 1 } },
  480. );
  481. let cardsSelector = [
  482. { title: regex },
  483. { description: regex },
  484. { customFields: { $elemMatch: { value: regex } } },
  485. { _id: { $in: checklists.map(list => list.cardId) } },
  486. { _id: { $in: attachments.map(attach => attach.cardId) } },
  487. { _id: { $in: comments.map(com => com.cardId) } },
  488. ];
  489. if (queryParams.text === "false" || queryParams.text === "true") {
  490. cardsSelector.push({ customFields: { $elemMatch: { value: queryParams.text === "true" } } } );
  491. }
  492. selector.$and.push({ $or: cardsSelector });
  493. }
  494. if (selector.$and.length === 0) {
  495. delete selector.$and;
  496. }
  497. }
  498. // eslint-disable-next-line no-console
  499. // console.log('cards selector:', JSON.stringify(selector, null, 2));
  500. const query = new Query();
  501. query.selector = selector;
  502. query.setQueryParams(queryParams);
  503. query._errors = errors;
  504. return query;
  505. }
  506. function buildProjection(query) {
  507. // eslint-disable-next-line no-console
  508. // console.log('query:', query);
  509. let skip = 0;
  510. if (query.getQueryParams().skip) {
  511. skip = query.getQueryParams().skip;
  512. }
  513. let limit = DEFAULT_LIMIT;
  514. const configLimit = parseInt(process.env.RESULTS_PER_PAGE, 10);
  515. if (!isNaN(configLimit) && configLimit > 0) {
  516. limit = configLimit;
  517. }
  518. if (query.getQueryParams().hasOperator(OPERATOR_LIMIT)) {
  519. limit = query.getQueryParams().getPredicate(OPERATOR_LIMIT);
  520. }
  521. const projection = {
  522. fields: {
  523. _id: 1,
  524. archived: 1,
  525. boardId: 1,
  526. swimlaneId: 1,
  527. listId: 1,
  528. title: 1,
  529. type: 1,
  530. sort: 1,
  531. members: 1,
  532. assignees: 1,
  533. colors: 1,
  534. dueAt: 1,
  535. createdAt: 1,
  536. modifiedAt: 1,
  537. labelIds: 1,
  538. customFields: 1,
  539. userId: 1,
  540. description: 1,
  541. },
  542. sort: {
  543. boardId: 1,
  544. swimlaneId: 1,
  545. listId: 1,
  546. sort: 1,
  547. },
  548. skip,
  549. };
  550. if (limit > 0) {
  551. projection.limit = limit;
  552. }
  553. if (query.getQueryParams().hasOperator(OPERATOR_SORT)) {
  554. const order =
  555. query.getQueryParams().getPredicate(OPERATOR_SORT).order ===
  556. ORDER_ASCENDING
  557. ? 1
  558. : -1;
  559. switch (query.getQueryParams().getPredicate(OPERATOR_SORT).name) {
  560. case PREDICATE_DUE_AT:
  561. projection.sort = {
  562. dueAt: order,
  563. boardId: 1,
  564. swimlaneId: 1,
  565. listId: 1,
  566. sort: 1,
  567. };
  568. break;
  569. case PREDICATE_MODIFIED_AT:
  570. projection.sort = {
  571. modifiedAt: order,
  572. boardId: 1,
  573. swimlaneId: 1,
  574. listId: 1,
  575. sort: 1,
  576. };
  577. break;
  578. case PREDICATE_CREATED_AT:
  579. projection.sort = {
  580. createdAt: order,
  581. boardId: 1,
  582. swimlaneId: 1,
  583. listId: 1,
  584. sort: 1,
  585. };
  586. break;
  587. case PREDICATE_SYSTEM:
  588. projection.sort = {
  589. boardId: order,
  590. swimlaneId: order,
  591. listId: order,
  592. modifiedAt: order,
  593. sort: order,
  594. };
  595. break;
  596. }
  597. }
  598. // eslint-disable-next-line no-console
  599. // console.log('projection:', projection);
  600. query.projection = projection;
  601. return query;
  602. }
  603. function buildQuery(queryParams) {
  604. const query = buildSelector(queryParams);
  605. return buildProjection(query);
  606. }
  607. Meteor.publish('brokenCards', function(sessionId) {
  608. check(sessionId, String);
  609. const params = new QueryParams();
  610. params.addPredicate(OPERATOR_STATUS, PREDICATE_ALL);
  611. const query = buildQuery(params);
  612. query.selector.$or = [
  613. { boardId: { $in: [null, ''] } },
  614. { swimlaneId: { $in: [null, ''] } },
  615. { listId: { $in: [null, ''] } },
  616. { type: { $nin: CARD_TYPES } },
  617. ];
  618. // console.log('brokenCards selector:', query.selector);
  619. const ret = findCards(sessionId, query);
  620. return ret;
  621. });
  622. Meteor.publish('nextPage', function(sessionId) {
  623. check(sessionId, String);
  624. const session = ReactiveCache.getSessionData({ sessionId });
  625. const projection = session.getProjection();
  626. projection.skip = session.lastHit;
  627. const ret = findCards(sessionId, new Query(session.getSelector(), projection));
  628. return ret;
  629. });
  630. Meteor.publish('previousPage', function(sessionId) {
  631. check(sessionId, String);
  632. const session = ReactiveCache.getSessionData({ sessionId });
  633. const projection = session.getProjection();
  634. projection.skip = session.lastHit - session.resultsCount - projection.limit;
  635. const ret = findCards(sessionId, new Query(session.getSelector(), projection));
  636. return ret;
  637. });
  638. function findCards(sessionId, query) {
  639. const userId = Meteor.userId();
  640. // eslint-disable-next-line no-console
  641. // console.log('selector:', query.selector);
  642. // console.log('selector.$and:', query.selector.$and);
  643. // eslint-disable-next-line no-console
  644. // console.log('projection:', query.projection);
  645. const cards = ReactiveCache.getCards(query.selector, query.projection, true);
  646. // eslint-disable-next-line no-console
  647. // console.log('count:', cards.count());
  648. const update = {
  649. $set: {
  650. totalHits: 0,
  651. lastHit: 0,
  652. resultsCount: 0,
  653. cards: [],
  654. selector: SessionData.pickle(query.selector),
  655. projection: SessionData.pickle(query.projection),
  656. errors: query.errors(),
  657. debug: query.getQueryParams().getPredicate(OPERATOR_DEBUG)
  658. },
  659. };
  660. if (cards) {
  661. update.$set.totalHits = cards.count();
  662. update.$set.lastHit =
  663. query.projection.skip + query.projection.limit < cards.count()
  664. ? query.projection.skip + query.projection.limit
  665. : cards.count();
  666. update.$set.cards = cards.map(card => {
  667. return card._id;
  668. });
  669. update.$set.resultsCount = update.$set.cards.length;
  670. }
  671. // eslint-disable-next-line no-console
  672. // console.log('sessionId:', sessionId);
  673. // eslint-disable-next-line no-console
  674. // console.log('userId:', userId);
  675. // eslint-disable-next-line no-console
  676. // console.log('update:', update);
  677. SessionData.upsert({ userId, sessionId }, update);
  678. // remove old session data
  679. SessionData.remove({
  680. userId,
  681. modifiedAt: {
  682. $lt: new Date(
  683. subtract(now(), 1, 'day').toISOString(),
  684. ),
  685. },
  686. });
  687. if (cards) {
  688. const boards = [];
  689. const swimlanes = [];
  690. const lists = [];
  691. const customFieldIds = [];
  692. const users = [this.userId];
  693. cards.forEach(card => {
  694. if (card.boardId) boards.push(card.boardId);
  695. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  696. if (card.listId) lists.push(card.listId);
  697. if (card.userId) {
  698. users.push(card.userId);
  699. }
  700. if (card.members) {
  701. card.members.forEach(userId => {
  702. users.push(userId);
  703. });
  704. }
  705. if (card.assignees) {
  706. card.assignees.forEach(userId => {
  707. users.push(userId);
  708. });
  709. }
  710. if (card.customFields) {
  711. card.customFields.forEach(field => {
  712. customFieldIds.push(field._id);
  713. });
  714. }
  715. });
  716. const fields = {
  717. _id: 1,
  718. title: 1,
  719. archived: 1,
  720. sort: 1,
  721. type: 1,
  722. };
  723. return [
  724. cards,
  725. ReactiveCache.getBoards(
  726. { _id: { $in: boards } },
  727. { fields: { ...fields, labels: 1, color: 1 } },
  728. true,
  729. ),
  730. ReactiveCache.getSwimlanes(
  731. { _id: { $in: swimlanes } },
  732. { fields: { ...fields, color: 1 } },
  733. true,
  734. ),
  735. ReactiveCache.getLists({ _id: { $in: lists } }, { fields }, true),
  736. ReactiveCache.getCustomFields({ _id: { $in: customFieldIds } }, {}, true),
  737. ReactiveCache.getUsers({ _id: { $in: users } }, { fields: Users.safeFields }, true),
  738. ReactiveCache.getChecklists({ cardId: { $in: cards.map(c => c._id) } }, {}, true),
  739. ReactiveCache.getChecklistItems({ cardId: { $in: cards.map(c => c._id) } }, {}, true),
  740. ReactiveCache.getAttachments({ 'meta.cardId': { $in: cards.map(c => c._id) } }, {}, true).cursor,
  741. ReactiveCache.getCardComments({ cardId: { $in: cards.map(c => c._id) } }, {}, true),
  742. SessionData.find({ userId, sessionId }),
  743. ];
  744. }
  745. return [SessionData.find({ userId, sessionId })];
  746. }