cards.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  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 = Boards.colorMap();
  174. }
  175. hasErrors() {
  176. for (const value of Object.values(this.notFound)) {
  177. if (value.length) {
  178. return true;
  179. }
  180. }
  181. return false;
  182. }
  183. errorMessages() {
  184. const messages = [];
  185. this.notFound.boards.forEach(board => {
  186. messages.push({ tag: 'board-title-not-found', value: board });
  187. });
  188. this.notFound.swimlanes.forEach(swim => {
  189. messages.push({ tag: 'swimlane-title-not-found', value: swim });
  190. });
  191. this.notFound.lists.forEach(list => {
  192. messages.push({ tag: 'list-title-not-found', value: list });
  193. });
  194. this.notFound.comments.forEach(comments => {
  195. comments.forEach(text => {
  196. messages.push({ tag: 'comment-not-found', value: text });
  197. });
  198. });
  199. this.notFound.labels.forEach(label => {
  200. messages.push({
  201. tag: 'label-not-found',
  202. value: label,
  203. color: Boards.labelColors().includes(label),
  204. });
  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. const boardsSelector = {};
  231. let archived = false;
  232. let endAt = null;
  233. if (queryParams.status.length) {
  234. queryParams.status.forEach(status => {
  235. if (status === 'archived') {
  236. archived = true;
  237. } else if (status === 'all') {
  238. archived = null;
  239. } else if (status === 'ended') {
  240. endAt = { $nin: [null, ''] };
  241. } else if (['private', 'public'].includes(status)) {
  242. boardsSelector.permission = status;
  243. }
  244. });
  245. }
  246. selector = {
  247. type: 'cardType-card',
  248. // boardId: { $in: Boards.userBoardIds(userId) },
  249. $and: [],
  250. };
  251. if (archived !== null) {
  252. if (archived) {
  253. selector.boardId = {
  254. $in: Boards.userBoardIds(userId, null, boardsSelector),
  255. };
  256. selector.$and.push({
  257. $or: [
  258. {
  259. boardId: {
  260. $in: Boards.userBoardIds(userId, archived, boardsSelector),
  261. },
  262. },
  263. { swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } },
  264. { listId: { $in: Lists.archivedListIds() } },
  265. { archived: true },
  266. ],
  267. });
  268. } else {
  269. selector.boardId = {
  270. $in: Boards.userBoardIds(userId, false, boardsSelector),
  271. };
  272. selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() };
  273. selector.listId = { $nin: Lists.archivedListIds() };
  274. selector.archived = false;
  275. }
  276. } else {
  277. selector.boardId = {
  278. $in: Boards.userBoardIds(userId, null, boardsSelector),
  279. };
  280. }
  281. if (endAt !== null) {
  282. selector.endAt = endAt;
  283. }
  284. if (queryParams.boards.length) {
  285. const queryBoards = [];
  286. queryParams.boards.forEach(query => {
  287. const boards = Boards.userSearch(userId, {
  288. title: new RegExp(escapeForRegex(query), 'i'),
  289. });
  290. if (boards.count()) {
  291. boards.forEach(board => {
  292. queryBoards.push(board._id);
  293. });
  294. } else {
  295. errors.notFound.boards.push(query);
  296. }
  297. });
  298. selector.boardId.$in = queryBoards;
  299. }
  300. if (queryParams.swimlanes.length) {
  301. const querySwimlanes = [];
  302. queryParams.swimlanes.forEach(query => {
  303. const swimlanes = Swimlanes.find({
  304. title: new RegExp(escapeForRegex(query), 'i'),
  305. });
  306. if (swimlanes.count()) {
  307. swimlanes.forEach(swim => {
  308. querySwimlanes.push(swim._id);
  309. });
  310. } else {
  311. errors.notFound.swimlanes.push(query);
  312. }
  313. });
  314. // eslint-disable-next-line no-prototype-builtins
  315. if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) {
  316. selector.swimlaneId = { $in: [] };
  317. }
  318. selector.swimlaneId.$in = querySwimlanes;
  319. }
  320. if (queryParams.lists.length) {
  321. const queryLists = [];
  322. queryParams.lists.forEach(query => {
  323. const lists = Lists.find({
  324. title: new RegExp(escapeForRegex(query), 'i'),
  325. });
  326. if (lists.count()) {
  327. lists.forEach(list => {
  328. queryLists.push(list._id);
  329. });
  330. } else {
  331. errors.notFound.lists.push(query);
  332. }
  333. });
  334. // eslint-disable-next-line no-prototype-builtins
  335. if (!selector.hasOwnProperty('listId')) {
  336. selector.listId = { $in: [] };
  337. }
  338. selector.listId.$in = queryLists;
  339. }
  340. if (queryParams.comments.length) {
  341. const cardIds = CardComments.textSearch(userId, queryParams.comments).map(
  342. com => {
  343. return com.cardId;
  344. },
  345. );
  346. if (cardIds.length) {
  347. selector._id = { $in: cardIds };
  348. } else {
  349. errors.notFound.comments.push(queryParams.comments);
  350. }
  351. }
  352. ['dueAt', 'createdAt', 'modifiedAt'].forEach(field => {
  353. if (queryParams[field]) {
  354. selector[field] = {};
  355. selector[field][queryParams[field]['operator']] = new Date(
  356. queryParams[field]['value'],
  357. );
  358. }
  359. });
  360. const queryMembers = [];
  361. const queryAssignees = [];
  362. if (queryParams.users.length) {
  363. queryParams.users.forEach(query => {
  364. const users = Users.find({
  365. username: query,
  366. });
  367. if (users.count()) {
  368. users.forEach(user => {
  369. queryMembers.push(user._id);
  370. queryAssignees.push(user._id);
  371. });
  372. } else {
  373. errors.notFound.users.push(query);
  374. }
  375. });
  376. }
  377. if (queryParams.members.length) {
  378. queryParams.members.forEach(query => {
  379. const users = Users.find({
  380. username: query,
  381. });
  382. if (users.count()) {
  383. users.forEach(user => {
  384. queryMembers.push(user._id);
  385. });
  386. } else {
  387. errors.notFound.members.push(query);
  388. }
  389. });
  390. }
  391. if (queryParams.assignees.length) {
  392. queryParams.assignees.forEach(query => {
  393. const users = Users.find({
  394. username: query,
  395. });
  396. if (users.count()) {
  397. users.forEach(user => {
  398. queryAssignees.push(user._id);
  399. });
  400. } else {
  401. errors.notFound.assignees.push(query);
  402. }
  403. });
  404. }
  405. if (queryMembers.length && queryAssignees.length) {
  406. selector.$and.push({
  407. $or: [
  408. { members: { $in: queryMembers } },
  409. { assignees: { $in: queryAssignees } },
  410. ],
  411. });
  412. } else if (queryMembers.length) {
  413. selector.members = { $in: queryMembers };
  414. } else if (queryAssignees.length) {
  415. selector.assignees = { $in: queryAssignees };
  416. }
  417. if (queryParams.labels.length) {
  418. queryParams.labels.forEach(label => {
  419. const queryLabels = [];
  420. let boards = Boards.userSearch(userId, {
  421. labels: { $elemMatch: { color: label.toLowerCase() } },
  422. });
  423. if (boards.count()) {
  424. boards.forEach(board => {
  425. // eslint-disable-next-line no-console
  426. // console.log('board:', board);
  427. // eslint-disable-next-line no-console
  428. // console.log('board.labels:', board.labels);
  429. board.labels
  430. .filter(boardLabel => {
  431. return boardLabel.color === label.toLowerCase();
  432. })
  433. .forEach(boardLabel => {
  434. queryLabels.push(boardLabel._id);
  435. });
  436. });
  437. } else {
  438. // eslint-disable-next-line no-console
  439. // console.log('label:', label);
  440. const reLabel = new RegExp(escapeForRegex(label), 'i');
  441. // eslint-disable-next-line no-console
  442. // console.log('reLabel:', reLabel);
  443. boards = Boards.userSearch(userId, {
  444. labels: { $elemMatch: { name: reLabel } },
  445. });
  446. if (boards.count()) {
  447. boards.forEach(board => {
  448. board.labels
  449. .filter(boardLabel => {
  450. if (!boardLabel.name) {
  451. return false;
  452. }
  453. return boardLabel.name.match(reLabel);
  454. })
  455. .forEach(boardLabel => {
  456. queryLabels.push(boardLabel._id);
  457. });
  458. });
  459. } else {
  460. errors.notFound.labels.push(label);
  461. }
  462. }
  463. selector.labelIds = { $in: queryLabels };
  464. });
  465. }
  466. if (queryParams.has.length) {
  467. queryParams.has.forEach(has => {
  468. switch (has.field) {
  469. case 'attachment':
  470. const attachments = Attachments.find({}, { fields: { cardId: 1 } });
  471. selector.$and.push({
  472. _id: { $in: attachments.map(a => a.cardId) },
  473. });
  474. break;
  475. case 'checklist':
  476. const checklists = Checklists.find({}, { fields: { cardId: 1 } });
  477. selector.$and.push({ _id: { $in: checklists.map(a => a.cardId) } });
  478. break;
  479. case 'description':
  480. case 'startAt':
  481. case 'dueAt':
  482. case 'endAt':
  483. if (has.exists) {
  484. selector[has.field] = { $exists: true, $nin: [null, ''] };
  485. } else {
  486. selector[has.field] = { $in: [null, ''] };
  487. }
  488. break;
  489. case 'assignees':
  490. case 'members':
  491. if (has.exists) {
  492. selector[has.field] = { $exists: true, $nin: [null, []] };
  493. } else {
  494. selector[has.field] = { $in: [null, []] };
  495. }
  496. break;
  497. }
  498. });
  499. }
  500. if (queryParams.text) {
  501. const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
  502. const items = ChecklistItems.find(
  503. { title: regex },
  504. { fields: { cardId: 1 } },
  505. );
  506. const checklists = Checklists.find(
  507. {
  508. $or: [
  509. { title: regex },
  510. { _id: { $in: items.map(item => item.checklistId) } },
  511. ],
  512. },
  513. { fields: { cardId: 1 } },
  514. );
  515. const attachments = Attachments.find({ 'original.name': regex });
  516. // const comments = CardComments.find(
  517. // { text: regex },
  518. // { fields: { cardId: 1 } },
  519. // );
  520. selector.$and.push({
  521. $or: [
  522. { title: regex },
  523. { description: regex },
  524. { customFields: { $elemMatch: { value: regex } } },
  525. {
  526. _id: {
  527. $in: CardComments.textSearch(userId, [queryParams.text]).map(
  528. com => com.cardId,
  529. ),
  530. },
  531. },
  532. { _id: { $in: checklists.map(list => list.cardId) } },
  533. { _id: { $in: attachments.map(attach => attach.cardId) } },
  534. // { _id: { $in: comments.map(com => com.cardId) } },
  535. ],
  536. });
  537. }
  538. if (selector.$and.length === 0) {
  539. delete selector.$and;
  540. }
  541. }
  542. // eslint-disable-next-line no-console
  543. // console.log('selector:', selector);
  544. // eslint-disable-next-line no-console
  545. // console.log('selector.$and:', selector.$and);
  546. const projection = {
  547. fields: {
  548. _id: 1,
  549. archived: 1,
  550. boardId: 1,
  551. swimlaneId: 1,
  552. listId: 1,
  553. title: 1,
  554. type: 1,
  555. sort: 1,
  556. members: 1,
  557. assignees: 1,
  558. colors: 1,
  559. dueAt: 1,
  560. createdAt: 1,
  561. modifiedAt: 1,
  562. labelIds: 1,
  563. customFields: 1,
  564. },
  565. sort: {
  566. boardId: 1,
  567. swimlaneId: 1,
  568. listId: 1,
  569. sort: 1,
  570. },
  571. skip,
  572. limit,
  573. };
  574. if (queryParams.sort) {
  575. const order = queryParams.sort.order === 'asc' ? 1 : -1;
  576. switch (queryParams.sort.name) {
  577. case 'dueAt':
  578. projection.sort = {
  579. dueAt: order,
  580. boardId: 1,
  581. swimlaneId: 1,
  582. listId: 1,
  583. sort: 1,
  584. };
  585. break;
  586. case 'modifiedAt':
  587. projection.sort = {
  588. modifiedAt: order,
  589. boardId: 1,
  590. swimlaneId: 1,
  591. listId: 1,
  592. sort: 1,
  593. };
  594. break;
  595. case 'createdAt':
  596. projection.sort = {
  597. createdAt: order,
  598. boardId: 1,
  599. swimlaneId: 1,
  600. listId: 1,
  601. sort: 1,
  602. };
  603. break;
  604. case 'system':
  605. projection.sort = {
  606. boardId: order,
  607. swimlaneId: order,
  608. listId: order,
  609. modifiedAt: order,
  610. sort: order,
  611. };
  612. break;
  613. }
  614. }
  615. // eslint-disable-next-line no-console
  616. // console.log('projection:', projection);
  617. return findCards(sessionId, selector, projection, errors);
  618. });
  619. Meteor.publish('brokenCards', function() {
  620. const user = Users.findOne({ _id: this.userId });
  621. const permiitedBoards = [null];
  622. let selector = {};
  623. selector.$or = [
  624. { permission: 'public' },
  625. { members: { $elemMatch: { userId: user._id, isActive: true } } },
  626. ];
  627. Boards.find(selector).forEach(board => {
  628. permiitedBoards.push(board._id);
  629. });
  630. selector = {
  631. boardId: { $in: permiitedBoards },
  632. $or: [
  633. { boardId: { $in: [null, ''] } },
  634. { swimlaneId: { $in: [null, ''] } },
  635. { listId: { $in: [null, ''] } },
  636. ],
  637. };
  638. const cards = Cards.find(selector, {
  639. fields: {
  640. _id: 1,
  641. archived: 1,
  642. boardId: 1,
  643. swimlaneId: 1,
  644. listId: 1,
  645. title: 1,
  646. type: 1,
  647. sort: 1,
  648. members: 1,
  649. assignees: 1,
  650. colors: 1,
  651. dueAt: 1,
  652. },
  653. });
  654. const boards = [];
  655. const swimlanes = [];
  656. const lists = [];
  657. const users = [];
  658. cards.forEach(card => {
  659. if (card.boardId) boards.push(card.boardId);
  660. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  661. if (card.listId) lists.push(card.listId);
  662. if (card.members) {
  663. card.members.forEach(userId => {
  664. users.push(userId);
  665. });
  666. }
  667. if (card.assignees) {
  668. card.assignees.forEach(userId => {
  669. users.push(userId);
  670. });
  671. }
  672. });
  673. return [
  674. cards,
  675. Boards.find({ _id: { $in: boards } }),
  676. Swimlanes.find({ _id: { $in: swimlanes } }),
  677. Lists.find({ _id: { $in: lists } }),
  678. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  679. ];
  680. });
  681. Meteor.publish('nextPage', function(sessionId) {
  682. check(sessionId, String);
  683. const session = SessionData.findOne({ sessionId });
  684. const projection = session.getProjection();
  685. projection.skip = session.lastHit;
  686. return findCards(sessionId, session.getSelector(), projection);
  687. });
  688. Meteor.publish('previousPage', function(sessionId) {
  689. check(sessionId, String);
  690. const session = SessionData.findOne({ sessionId });
  691. const projection = session.getProjection();
  692. projection.skip = session.lastHit - session.resultsCount - projection.limit;
  693. return findCards(sessionId, session.getSelector(), projection);
  694. });
  695. function findCards(sessionId, selector, projection, errors = null) {
  696. const userId = Meteor.userId();
  697. // eslint-disable-next-line no-console
  698. // console.log('selector:', selector);
  699. // eslint-disable-next-line no-console
  700. // console.log('projection:', projection);
  701. let cards;
  702. if (!errors || !errors.hasErrors()) {
  703. cards = Cards.find(selector, projection);
  704. }
  705. // eslint-disable-next-line no-console
  706. // console.log('count:', cards.count());
  707. const update = {
  708. $set: {
  709. totalHits: 0,
  710. lastHit: 0,
  711. resultsCount: 0,
  712. cards: [],
  713. selector: SessionData.pickle(selector),
  714. projection: SessionData.pickle(projection),
  715. },
  716. };
  717. if (errors) {
  718. update.$set.errors = errors.errorMessages();
  719. }
  720. if (cards) {
  721. update.$set.totalHits = cards.count();
  722. update.$set.lastHit =
  723. projection.skip + projection.limit < cards.count()
  724. ? projection.skip + projection.limit
  725. : cards.count();
  726. update.$set.cards = cards.map(card => {
  727. return card._id;
  728. });
  729. update.$set.resultsCount = update.$set.cards.length;
  730. }
  731. SessionData.upsert({ userId, sessionId }, update);
  732. // remove old session data
  733. SessionData.remove({
  734. userId,
  735. modifiedAt: {
  736. $lt: new Date(
  737. moment()
  738. .subtract(1, 'day')
  739. .format(),
  740. ),
  741. },
  742. });
  743. if (cards) {
  744. const boards = [];
  745. const swimlanes = [];
  746. const lists = [];
  747. const customFieldIds = [];
  748. const users = [this.userId];
  749. cards.forEach(card => {
  750. if (card.boardId) boards.push(card.boardId);
  751. if (card.swimlaneId) swimlanes.push(card.swimlaneId);
  752. if (card.listId) lists.push(card.listId);
  753. if (card.members) {
  754. card.members.forEach(userId => {
  755. users.push(userId);
  756. });
  757. }
  758. if (card.assignees) {
  759. card.assignees.forEach(userId => {
  760. users.push(userId);
  761. });
  762. }
  763. if (card.customFields) {
  764. card.customFields.forEach(field => {
  765. customFieldIds.push(field._id);
  766. });
  767. }
  768. });
  769. const fields = {
  770. _id: 1,
  771. title: 1,
  772. archived: 1,
  773. sort: 1,
  774. type: 1,
  775. };
  776. return [
  777. cards,
  778. Boards.find(
  779. { _id: { $in: boards } },
  780. { fields: { ...fields, labels: 1, color: 1 } },
  781. ),
  782. Swimlanes.find(
  783. { _id: { $in: swimlanes } },
  784. { fields: { ...fields, color: 1 } },
  785. ),
  786. Lists.find({ _id: { $in: lists } }, { fields }),
  787. CustomFields.find({ _id: { $in: customFieldIds } }),
  788. Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
  789. Checklists.find({ cardId: { $in: cards.map(c => c._id) } }),
  790. Attachments.find({ cardId: { $in: cards.map(c => c._id) } }),
  791. CardComments.find({ cardId: { $in: cards.map(c => c._id) } }),
  792. SessionData.find({ userId, sessionId }),
  793. ];
  794. }
  795. return [SessionData.find({ userId: userId, sessionId })];
  796. }