cards.js 20 KB

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