cards.js 20 KB

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