cards.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746
  1. Cards = new Mongo.Collection('cards');
  2. // XXX To improve pub/sub performances a card document should include a
  3. // de-normalized number of comments so we don't have to publish the whole list
  4. // of comments just to display the number of them in the board view.
  5. Cards.attachSchema(new SimpleSchema({
  6. title: {
  7. /**
  8. * the title of the card
  9. */
  10. type: String,
  11. optional: true,
  12. defaultValue: '',
  13. },
  14. archived: {
  15. /**
  16. * is the card archived
  17. */
  18. type: Boolean,
  19. autoValue() { // eslint-disable-line consistent-return
  20. if (this.isInsert && !this.isSet) {
  21. return false;
  22. }
  23. },
  24. },
  25. parentId: {
  26. /**
  27. * ID of the parent card
  28. */
  29. type: String,
  30. optional: true,
  31. defaultValue: '',
  32. },
  33. listId: {
  34. /**
  35. * List ID where the card is
  36. */
  37. type: String,
  38. optional: true,
  39. defaultValue: '',
  40. },
  41. swimlaneId: {
  42. /**
  43. * Swimlane ID where the card is
  44. */
  45. type: String,
  46. },
  47. // The system could work without this `boardId` information (we could deduce
  48. // the board identifier from the card), but it would make the system more
  49. // difficult to manage and less efficient.
  50. boardId: {
  51. /**
  52. * Board ID of the card
  53. */
  54. type: String,
  55. optional: true,
  56. defaultValue: '',
  57. },
  58. coverId: {
  59. /**
  60. * Cover ID of the card
  61. */
  62. type: String,
  63. optional: true,
  64. defaultValue: '',
  65. },
  66. createdAt: {
  67. /**
  68. * creation date
  69. */
  70. type: Date,
  71. autoValue() { // eslint-disable-line consistent-return
  72. if (this.isInsert) {
  73. return new Date();
  74. } else {
  75. this.unset();
  76. }
  77. },
  78. },
  79. customFields: {
  80. /**
  81. * list of custom fields
  82. */
  83. type: [Object],
  84. optional: true,
  85. defaultValue: [],
  86. },
  87. 'customFields.$': {
  88. type: new SimpleSchema({
  89. _id: {
  90. /**
  91. * the ID of the related custom field
  92. */
  93. type: String,
  94. optional: true,
  95. defaultValue: '',
  96. },
  97. value: {
  98. /**
  99. * value attached to the custom field
  100. */
  101. type: Match.OneOf(String, Number, Boolean, Date),
  102. optional: true,
  103. defaultValue: '',
  104. },
  105. }),
  106. },
  107. dateLastActivity: {
  108. /**
  109. * Date of last activity
  110. */
  111. type: Date,
  112. autoValue() {
  113. return new Date();
  114. },
  115. },
  116. description: {
  117. /**
  118. * description of the card
  119. */
  120. type: String,
  121. optional: true,
  122. defaultValue: '',
  123. },
  124. requestedBy: {
  125. /**
  126. * who requested the card (ID of the user)
  127. */
  128. type: String,
  129. optional: true,
  130. defaultValue: '',
  131. },
  132. assignedBy: {
  133. /**
  134. * who assigned the card (ID of the user)
  135. */
  136. type: String,
  137. optional: true,
  138. defaultValue: '',
  139. },
  140. labelIds: {
  141. /**
  142. * list of labels ID the card has
  143. */
  144. type: [String],
  145. optional: true,
  146. defaultValue: [],
  147. },
  148. members: {
  149. /**
  150. * list of members (user IDs)
  151. */
  152. type: [String],
  153. optional: true,
  154. defaultValue: [],
  155. },
  156. receivedAt: {
  157. /**
  158. * Date the card was received
  159. */
  160. type: Date,
  161. optional: true,
  162. },
  163. startAt: {
  164. /**
  165. * Date the card was started to be worked on
  166. */
  167. type: Date,
  168. optional: true,
  169. },
  170. dueAt: {
  171. /**
  172. * Date the card is due
  173. */
  174. type: Date,
  175. optional: true,
  176. },
  177. endAt: {
  178. /**
  179. * Date the card ended
  180. */
  181. type: Date,
  182. optional: true,
  183. },
  184. spentTime: {
  185. /**
  186. * How much time has been spent on this
  187. */
  188. type: Number,
  189. decimal: true,
  190. optional: true,
  191. defaultValue: 0,
  192. },
  193. isOvertime: {
  194. /**
  195. * is the card over time?
  196. */
  197. type: Boolean,
  198. defaultValue: false,
  199. optional: true,
  200. },
  201. // XXX Should probably be called `authorId`. Is it even needed since we have
  202. // the `members` field?
  203. userId: {
  204. /**
  205. * user ID of the author of the card
  206. */
  207. type: String,
  208. autoValue() { // eslint-disable-line consistent-return
  209. if (this.isInsert && !this.isSet) {
  210. return this.userId;
  211. }
  212. },
  213. },
  214. sort: {
  215. /**
  216. * Sort value
  217. */
  218. type: Number,
  219. decimal: true,
  220. defaultValue: '',
  221. },
  222. subtaskSort: {
  223. /**
  224. * subtask sort value
  225. */
  226. type: Number,
  227. decimal: true,
  228. defaultValue: -1,
  229. optional: true,
  230. },
  231. type: {
  232. /**
  233. * type of the card
  234. */
  235. type: String,
  236. defaultValue: '',
  237. },
  238. linkedId: {
  239. /**
  240. * ID of the linked card
  241. */
  242. type: String,
  243. optional: true,
  244. defaultValue: '',
  245. },
  246. }));
  247. Cards.allow({
  248. insert(userId, doc) {
  249. return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
  250. },
  251. update(userId, doc) {
  252. return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
  253. },
  254. remove(userId, doc) {
  255. return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
  256. },
  257. fetch: ['boardId'],
  258. });
  259. Cards.helpers({
  260. list() {
  261. return Lists.findOne(this.listId);
  262. },
  263. board() {
  264. return Boards.findOne(this.boardId);
  265. },
  266. labels() {
  267. const boardLabels = this.board().labels;
  268. const cardLabels = _.filter(boardLabels, (label) => {
  269. return _.contains(this.labelIds, label._id);
  270. });
  271. return cardLabels;
  272. },
  273. hasLabel(labelId) {
  274. return _.contains(this.labelIds, labelId);
  275. },
  276. user() {
  277. return Users.findOne(this.userId);
  278. },
  279. isAssigned(memberId) {
  280. return _.contains(this.getMembers(), memberId);
  281. },
  282. activities() {
  283. if (this.isLinkedCard()) {
  284. return Activities.find({cardId: this.linkedId}, {sort: {createdAt: -1}});
  285. } else if (this.isLinkedBoard()) {
  286. return Activities.find({boardId: this.linkedId}, {sort: {createdAt: -1}});
  287. } else {
  288. return Activities.find({cardId: this._id}, {sort: {createdAt: -1}});
  289. }
  290. },
  291. comments() {
  292. if (this.isLinkedCard()) {
  293. return CardComments.find({cardId: this.linkedId}, {sort: {createdAt: -1}});
  294. } else {
  295. return CardComments.find({cardId: this._id}, {sort: {createdAt: -1}});
  296. }
  297. },
  298. attachments() {
  299. if (this.isLinkedCard()) {
  300. return Attachments.find({cardId: this.linkedId}, {sort: {uploadedAt: -1}});
  301. } else {
  302. return Attachments.find({cardId: this._id}, {sort: {uploadedAt: -1}});
  303. }
  304. },
  305. cover() {
  306. const cover = Attachments.findOne(this.coverId);
  307. // if we return a cover before it is fully stored, we will get errors when we try to display it
  308. // todo XXX we could return a default "upload pending" image in the meantime?
  309. return cover && cover.url() && cover;
  310. },
  311. checklists() {
  312. if (this.isLinkedCard()) {
  313. return Checklists.find({cardId: this.linkedId}, {sort: { sort: 1 } });
  314. } else {
  315. return Checklists.find({cardId: this._id}, {sort: { sort: 1 } });
  316. }
  317. },
  318. checklistItemCount() {
  319. const checklists = this.checklists().fetch();
  320. return checklists.map((checklist) => {
  321. return checklist.itemCount();
  322. }).reduce((prev, next) => {
  323. return prev + next;
  324. }, 0);
  325. },
  326. checklistFinishedCount() {
  327. const checklists = this.checklists().fetch();
  328. return checklists.map((checklist) => {
  329. return checklist.finishedCount();
  330. }).reduce((prev, next) => {
  331. return prev + next;
  332. }, 0);
  333. },
  334. checklistFinished() {
  335. return this.hasChecklist() && this.checklistItemCount() === this.checklistFinishedCount();
  336. },
  337. hasChecklist() {
  338. return this.checklistItemCount() !== 0;
  339. },
  340. subtasks() {
  341. return Cards.find({
  342. parentId: this._id,
  343. archived: false,
  344. }, {
  345. sort: {
  346. sort: 1,
  347. },
  348. });
  349. },
  350. allSubtasks() {
  351. return Cards.find({
  352. parentId: this._id,
  353. archived: false,
  354. }, {
  355. sort: {
  356. sort: 1,
  357. },
  358. });
  359. },
  360. subtasksCount() {
  361. return Cards.find({
  362. parentId: this._id,
  363. archived: false,
  364. }).count();
  365. },
  366. subtasksFinishedCount() {
  367. return Cards.find({
  368. parentId: this._id,
  369. archived: true,
  370. }).count();
  371. },
  372. subtasksFinished() {
  373. const finishCount = this.subtasksFinishedCount();
  374. return finishCount > 0 && this.subtasksCount() === finishCount;
  375. },
  376. allowsSubtasks() {
  377. return this.subtasksCount() !== 0;
  378. },
  379. customFieldIndex(customFieldId) {
  380. return _.pluck(this.customFields, '_id').indexOf(customFieldId);
  381. },
  382. // customFields with definitions
  383. customFieldsWD() {
  384. // get all definitions
  385. const definitions = CustomFields.find({
  386. boardId: this.boardId,
  387. }).fetch();
  388. // match right definition to each field
  389. if (!this.customFields) return [];
  390. return this.customFields.map((customField) => {
  391. const definition = definitions.find((definition) => {
  392. return definition._id === customField._id;
  393. });
  394. //search for "True Value" which is for DropDowns other then the Value (which is the id)
  395. let trueValue = customField.value;
  396. if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) {
  397. for (let i = 0; i < definition.settings.dropdownItems.length; i++) {
  398. if (definition.settings.dropdownItems[i]._id === customField.value) {
  399. trueValue = definition.settings.dropdownItems[i].name;
  400. }
  401. }
  402. }
  403. return {
  404. _id: customField._id,
  405. value: customField.value,
  406. trueValue,
  407. definition,
  408. };
  409. });
  410. },
  411. absoluteUrl() {
  412. const board = this.board();
  413. return FlowRouter.url('card', {
  414. boardId: board._id,
  415. slug: board.slug,
  416. cardId: this._id,
  417. });
  418. },
  419. canBeRestored() {
  420. const list = Lists.findOne({
  421. _id: this.listId,
  422. });
  423. if (!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()) {
  424. return false;
  425. }
  426. return true;
  427. },
  428. parentCard() {
  429. if (this.parentId === '') {
  430. return null;
  431. }
  432. return Cards.findOne(this.parentId);
  433. },
  434. parentCardName() {
  435. let result = '';
  436. if (this.parentId !== '') {
  437. const card = Cards.findOne(this.parentId);
  438. if (card) {
  439. result = card.title;
  440. }
  441. }
  442. return result;
  443. },
  444. parentListId() {
  445. const result = [];
  446. let crtParentId = this.parentId;
  447. while (crtParentId !== '') {
  448. const crt = Cards.findOne(crtParentId);
  449. if ((crt === null) || (crt === undefined)) {
  450. // maybe it has been deleted
  451. break;
  452. }
  453. if (crtParentId in result) {
  454. // circular reference
  455. break;
  456. }
  457. result.unshift(crtParentId);
  458. crtParentId = crt.parentId;
  459. }
  460. return result;
  461. },
  462. parentList() {
  463. const resultId = [];
  464. const result = [];
  465. let crtParentId = this.parentId;
  466. while (crtParentId !== '') {
  467. const crt = Cards.findOne(crtParentId);
  468. if ((crt === null) || (crt === undefined)) {
  469. // maybe it has been deleted
  470. break;
  471. }
  472. if (crtParentId in resultId) {
  473. // circular reference
  474. break;
  475. }
  476. resultId.unshift(crtParentId);
  477. result.unshift(crt);
  478. crtParentId = crt.parentId;
  479. }
  480. return result;
  481. },
  482. parentString(sep) {
  483. return this.parentList().map(function(elem) {
  484. return elem.title;
  485. }).join(sep);
  486. },
  487. isTopLevel() {
  488. return this.parentId === '';
  489. },
  490. isLinkedCard() {
  491. return this.type === 'cardType-linkedCard';
  492. },
  493. isLinkedBoard() {
  494. return this.type === 'cardType-linkedBoard';
  495. },
  496. isLinked() {
  497. return this.isLinkedCard() || this.isLinkedBoard();
  498. },
  499. setDescription(description) {
  500. if (this.isLinkedCard()) {
  501. return Cards.update({_id: this.linkedId}, {$set: {description}});
  502. } else if (this.isLinkedBoard()) {
  503. return Boards.update({_id: this.linkedId}, {$set: {description}});
  504. } else {
  505. return Cards.update(
  506. {_id: this._id},
  507. {$set: {description}}
  508. );
  509. }
  510. },
  511. getDescription() {
  512. if (this.isLinkedCard()) {
  513. const card = Cards.findOne({_id: this.linkedId});
  514. if (card && card.description)
  515. return card.description;
  516. else
  517. return null;
  518. } else if (this.isLinkedBoard()) {
  519. const board = Boards.findOne({_id: this.linkedId});
  520. if (board && board.description)
  521. return board.description;
  522. else
  523. return null;
  524. } else if (this.description) {
  525. return this.description;
  526. } else {
  527. return null;
  528. }
  529. },
  530. getMembers() {
  531. if (this.isLinkedCard()) {
  532. const card = Cards.findOne({_id: this.linkedId});
  533. return card.members;
  534. } else if (this.isLinkedBoard()) {
  535. const board = Boards.findOne({_id: this.linkedId});
  536. return board.activeMembers().map((member) => {
  537. return member.userId;
  538. });
  539. } else {
  540. return this.members;
  541. }
  542. },
  543. assignMember(memberId) {
  544. if (this.isLinkedCard()) {
  545. return Cards.update(
  546. { _id: this.linkedId },
  547. { $addToSet: { members: memberId }}
  548. );
  549. } else if (this.isLinkedBoard()) {
  550. const board = Boards.findOne({_id: this.linkedId});
  551. return board.addMember(memberId);
  552. } else {
  553. return Cards.update(
  554. { _id: this._id },
  555. { $addToSet: { members: memberId}}
  556. );
  557. }
  558. },
  559. unassignMember(memberId) {
  560. if (this.isLinkedCard()) {
  561. return Cards.update(
  562. { _id: this.linkedId },
  563. { $pull: { members: memberId }}
  564. );
  565. } else if (this.isLinkedBoard()) {
  566. const board = Boards.findOne({_id: this.linkedId});
  567. return board.removeMember(memberId);
  568. } else {
  569. return Cards.update(
  570. { _id: this._id },
  571. { $pull: { members: memberId}}
  572. );
  573. }
  574. },
  575. toggleMember(memberId) {
  576. if (this.getMembers() && this.getMembers().indexOf(memberId) > -1) {
  577. return this.unassignMember(memberId);
  578. } else {
  579. return this.assignMember(memberId);
  580. }
  581. },
  582. getReceived() {
  583. if (this.isLinkedCard()) {
  584. const card = Cards.findOne({_id: this.linkedId});
  585. return card.receivedAt;
  586. } else {
  587. return this.receivedAt;
  588. }
  589. },
  590. setReceived(receivedAt) {
  591. if (this.isLinkedCard()) {
  592. return Cards.update(
  593. {_id: this.linkedId},
  594. {$set: {receivedAt}}
  595. );
  596. } else {
  597. return Cards.update(
  598. {_id: this._id},
  599. {$set: {receivedAt}}
  600. );
  601. }
  602. },
  603. getStart() {
  604. if (this.isLinkedCard()) {
  605. const card = Cards.findOne({_id: this.linkedId});
  606. return card.startAt;
  607. } else if (this.isLinkedBoard()) {
  608. const board = Boards.findOne({_id: this.linkedId});
  609. return board.startAt;
  610. } else {
  611. return this.startAt;
  612. }
  613. },
  614. setStart(startAt) {
  615. if (this.isLinkedCard()) {
  616. return Cards.update(
  617. { _id: this.linkedId },
  618. {$set: {startAt}}
  619. );
  620. } else if (this.isLinkedBoard()) {
  621. return Boards.update(
  622. {_id: this.linkedId},
  623. {$set: {startAt}}
  624. );
  625. } else {
  626. return Cards.update(
  627. {_id: this._id},
  628. {$set: {startAt}}
  629. );
  630. }
  631. },
  632. getDue() {
  633. if (this.isLinkedCard()) {
  634. const card = Cards.findOne({_id: this.linkedId});
  635. return card.dueAt;
  636. } else if (this.isLinkedBoard()) {
  637. const board = Boards.findOne({_id: this.linkedId});
  638. return board.dueAt;
  639. } else {
  640. return this.dueAt;
  641. }
  642. },
  643. setDue(dueAt) {
  644. if (this.isLinkedCard()) {
  645. return Cards.update(
  646. { _id: this.linkedId },
  647. {$set: {dueAt}}
  648. );
  649. } else if (this.isLinkedBoard()) {
  650. return Boards.update(
  651. {_id: this.linkedId},
  652. {$set: {dueAt}}
  653. );
  654. } else {
  655. return Cards.update(
  656. {_id: this._id},
  657. {$set: {dueAt}}
  658. );
  659. }
  660. },
  661. getEnd() {
  662. if (this.isLinkedCard()) {
  663. const card = Cards.findOne({_id: this.linkedId});
  664. return card.endAt;
  665. } else if (this.isLinkedBoard()) {
  666. const board = Boards.findOne({_id: this.linkedId});
  667. return board.endAt;
  668. } else {
  669. return this.endAt;
  670. }
  671. },
  672. setEnd(endAt) {
  673. if (this.isLinkedCard()) {
  674. return Cards.update(
  675. { _id: this.linkedId },
  676. {$set: {endAt}}
  677. );
  678. } else if (this.isLinkedBoard()) {
  679. return Boards.update(
  680. {_id: this.linkedId},
  681. {$set: {endAt}}
  682. );
  683. } else {
  684. return Cards.update(
  685. {_id: this._id},
  686. {$set: {endAt}}
  687. );
  688. }
  689. },
  690. getIsOvertime() {
  691. if (this.isLinkedCard()) {
  692. const card = Cards.findOne({ _id: this.linkedId });
  693. return card.isOvertime;
  694. } else if (this.isLinkedBoard()) {
  695. const board = Boards.findOne({ _id: this.linkedId});
  696. return board.isOvertime;
  697. } else {
  698. return this.isOvertime;
  699. }
  700. },
  701. setIsOvertime(isOvertime) {
  702. if (this.isLinkedCard()) {
  703. return Cards.update(
  704. { _id: this.linkedId },
  705. {$set: {isOvertime}}
  706. );
  707. } else if (this.isLinkedBoard()) {
  708. return Boards.update(
  709. {_id: this.linkedId},
  710. {$set: {isOvertime}}
  711. );
  712. } else {
  713. return Cards.update(
  714. {_id: this._id},
  715. {$set: {isOvertime}}
  716. );
  717. }
  718. },
  719. getSpentTime() {
  720. if (this.isLinkedCard()) {
  721. const card = Cards.findOne({ _id: this.linkedId });
  722. return card.spentTime;
  723. } else if (this.isLinkedBoard()) {
  724. const board = Boards.findOne({ _id: this.linkedId});
  725. return board.spentTime;
  726. } else {
  727. return this.spentTime;
  728. }
  729. },
  730. setSpentTime(spentTime) {
  731. if (this.isLinkedCard()) {
  732. return Cards.update(
  733. { _id: this.linkedId },
  734. {$set: {spentTime}}
  735. );
  736. } else if (this.isLinkedBoard()) {
  737. return Boards.update(
  738. {_id: this.linkedId},
  739. {$set: {spentTime}}
  740. );
  741. } else {
  742. return Cards.update(
  743. {_id: this._id},
  744. {$set: {spentTime}}
  745. );
  746. }
  747. },
  748. getId() {
  749. if (this.isLinked()) {
  750. return this.linkedId;
  751. } else {
  752. return this._id;
  753. }
  754. },
  755. getTitle() {
  756. if (this.isLinkedCard()) {
  757. const card = Cards.findOne({ _id: this.linkedId });
  758. return card.title;
  759. } else if (this.isLinkedBoard()) {
  760. const board = Boards.findOne({ _id: this.linkedId});
  761. return board.title;
  762. } else {
  763. return this.title;
  764. }
  765. },
  766. getBoardTitle() {
  767. if (this.isLinkedCard()) {
  768. const card = Cards.findOne({ _id: this.linkedId });
  769. const board = Boards.findOne({ _id: card.boardId });
  770. return board.title;
  771. } else if (this.isLinkedBoard()) {
  772. const board = Boards.findOne({ _id: this.linkedId});
  773. return board.title;
  774. } else {
  775. const board = Boards.findOne({ _id: this.boardId });
  776. return board.title;
  777. }
  778. },
  779. setTitle(title) {
  780. if (this.isLinkedCard()) {
  781. return Cards.update(
  782. { _id: this.linkedId },
  783. {$set: {title}}
  784. );
  785. } else if (this.isLinkedBoard()) {
  786. return Boards.update(
  787. {_id: this.linkedId},
  788. {$set: {title}}
  789. );
  790. } else {
  791. return Cards.update(
  792. {_id: this._id},
  793. {$set: {title}}
  794. );
  795. }
  796. },
  797. getArchived() {
  798. if (this.isLinkedCard()) {
  799. const card = Cards.findOne({ _id: this.linkedId });
  800. return card.archived;
  801. } else if (this.isLinkedBoard()) {
  802. const board = Boards.findOne({ _id: this.linkedId});
  803. return board.archived;
  804. } else {
  805. return this.archived;
  806. }
  807. },
  808. setRequestedBy(requestedBy) {
  809. if (this.isLinkedCard()) {
  810. return Cards.update(
  811. { _id: this.linkedId },
  812. {$set: {requestedBy}}
  813. );
  814. } else {
  815. return Cards.update(
  816. {_id: this._id},
  817. {$set: {requestedBy}}
  818. );
  819. }
  820. },
  821. getRequestedBy() {
  822. if (this.isLinkedCard()) {
  823. const card = Cards.findOne({ _id: this.linkedId });
  824. return card.requestedBy;
  825. } else {
  826. return this.requestedBy;
  827. }
  828. },
  829. setAssignedBy(assignedBy) {
  830. if (this.isLinkedCard()) {
  831. return Cards.update(
  832. { _id: this.linkedId },
  833. {$set: {assignedBy}}
  834. );
  835. } else {
  836. return Cards.update(
  837. {_id: this._id},
  838. {$set: {assignedBy}}
  839. );
  840. }
  841. },
  842. getAssignedBy() {
  843. if (this.isLinkedCard()) {
  844. const card = Cards.findOne({ _id: this.linkedId });
  845. return card.assignedBy;
  846. } else {
  847. return this.assignedBy;
  848. }
  849. },
  850. });
  851. Cards.mutations({
  852. applyToChildren(funct) {
  853. Cards.find({
  854. parentId: this._id,
  855. }).forEach((card) => {
  856. funct(card);
  857. });
  858. },
  859. archive() {
  860. this.applyToChildren((card) => {
  861. return card.archive();
  862. });
  863. return {
  864. $set: {
  865. archived: true,
  866. },
  867. };
  868. },
  869. restore() {
  870. this.applyToChildren((card) => {
  871. return card.restore();
  872. });
  873. return {
  874. $set: {
  875. archived: false,
  876. },
  877. };
  878. },
  879. setTitle(title) {
  880. return {
  881. $set: {
  882. title,
  883. },
  884. };
  885. },
  886. setDescription(description) {
  887. return {
  888. $set: {
  889. description,
  890. },
  891. };
  892. },
  893. setRequestedBy(requestedBy) {
  894. return {
  895. $set: {
  896. requestedBy,
  897. },
  898. };
  899. },
  900. setAssignedBy(assignedBy) {
  901. return {
  902. $set: {
  903. assignedBy,
  904. },
  905. };
  906. },
  907. move(swimlaneId, listId, sortIndex) {
  908. const list = Lists.findOne(listId);
  909. const mutatedFields = {
  910. swimlaneId,
  911. listId,
  912. boardId: list.boardId,
  913. sort: sortIndex,
  914. };
  915. return {
  916. $set: mutatedFields,
  917. };
  918. },
  919. addLabel(labelId) {
  920. return {
  921. $addToSet: {
  922. labelIds: labelId,
  923. },
  924. };
  925. },
  926. removeLabel(labelId) {
  927. return {
  928. $pull: {
  929. labelIds: labelId,
  930. },
  931. };
  932. },
  933. toggleLabel(labelId) {
  934. if (this.labelIds && this.labelIds.indexOf(labelId) > -1) {
  935. return this.removeLabel(labelId);
  936. } else {
  937. return this.addLabel(labelId);
  938. }
  939. },
  940. assignMember(memberId) {
  941. return {
  942. $addToSet: {
  943. members: memberId,
  944. },
  945. };
  946. },
  947. unassignMember(memberId) {
  948. return {
  949. $pull: {
  950. members: memberId,
  951. },
  952. };
  953. },
  954. toggleMember(memberId) {
  955. if (this.members && this.members.indexOf(memberId) > -1) {
  956. return this.unassignMember(memberId);
  957. } else {
  958. return this.assignMember(memberId);
  959. }
  960. },
  961. assignCustomField(customFieldId) {
  962. return {
  963. $addToSet: {
  964. customFields: {
  965. _id: customFieldId,
  966. value: null,
  967. },
  968. },
  969. };
  970. },
  971. unassignCustomField(customFieldId) {
  972. return {
  973. $pull: {
  974. customFields: {
  975. _id: customFieldId,
  976. },
  977. },
  978. };
  979. },
  980. toggleCustomField(customFieldId) {
  981. if (this.customFields && this.customFieldIndex(customFieldId) > -1) {
  982. return this.unassignCustomField(customFieldId);
  983. } else {
  984. return this.assignCustomField(customFieldId);
  985. }
  986. },
  987. setCustomField(customFieldId, value) {
  988. // todo
  989. const index = this.customFieldIndex(customFieldId);
  990. if (index > -1) {
  991. const update = {
  992. $set: {},
  993. };
  994. update.$set[`customFields.${index}.value`] = value;
  995. return update;
  996. }
  997. // TODO
  998. // Ignatz 18.05.2018: Return null to silence ESLint. No Idea if that is correct
  999. return null;
  1000. },
  1001. setCover(coverId) {
  1002. return {
  1003. $set: {
  1004. coverId,
  1005. },
  1006. };
  1007. },
  1008. unsetCover() {
  1009. return {
  1010. $unset: {
  1011. coverId: '',
  1012. },
  1013. };
  1014. },
  1015. setReceived(receivedAt) {
  1016. return {
  1017. $set: {
  1018. receivedAt,
  1019. },
  1020. };
  1021. },
  1022. unsetReceived() {
  1023. return {
  1024. $unset: {
  1025. receivedAt: '',
  1026. },
  1027. };
  1028. },
  1029. setStart(startAt) {
  1030. return {
  1031. $set: {
  1032. startAt,
  1033. },
  1034. };
  1035. },
  1036. unsetStart() {
  1037. return {
  1038. $unset: {
  1039. startAt: '',
  1040. },
  1041. };
  1042. },
  1043. setDue(dueAt) {
  1044. return {
  1045. $set: {
  1046. dueAt,
  1047. },
  1048. };
  1049. },
  1050. unsetDue() {
  1051. return {
  1052. $unset: {
  1053. dueAt: '',
  1054. },
  1055. };
  1056. },
  1057. setEnd(endAt) {
  1058. return {
  1059. $set: {
  1060. endAt,
  1061. },
  1062. };
  1063. },
  1064. unsetEnd() {
  1065. return {
  1066. $unset: {
  1067. endAt: '',
  1068. },
  1069. };
  1070. },
  1071. setOvertime(isOvertime) {
  1072. return {
  1073. $set: {
  1074. isOvertime,
  1075. },
  1076. };
  1077. },
  1078. setSpentTime(spentTime) {
  1079. return {
  1080. $set: {
  1081. spentTime,
  1082. },
  1083. };
  1084. },
  1085. unsetSpentTime() {
  1086. return {
  1087. $unset: {
  1088. spentTime: '',
  1089. isOvertime: false,
  1090. },
  1091. };
  1092. },
  1093. setParentId(parentId) {
  1094. return {
  1095. $set: {
  1096. parentId,
  1097. },
  1098. };
  1099. },
  1100. });
  1101. //FUNCTIONS FOR creation of Activities
  1102. function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) {
  1103. if ((_.contains(fieldNames, 'listId') && doc.listId !== oldListId) ||
  1104. (_.contains(fieldNames, 'swimlaneId') && doc.swimlaneId !== oldSwimlaneId)){
  1105. Activities.insert({
  1106. userId,
  1107. oldListId,
  1108. activityType: 'moveCard',
  1109. listName: Lists.findOne(doc.listId).title,
  1110. listId: doc.listId,
  1111. boardId: doc.boardId,
  1112. cardId: doc._id,
  1113. swimlaneName: Swimlanes.findOne(doc.swimlaneId).title,
  1114. swimlaneId: doc.swimlaneId,
  1115. oldSwimlaneId,
  1116. });
  1117. }
  1118. }
  1119. function cardState(userId, doc, fieldNames) {
  1120. if (_.contains(fieldNames, 'archived')) {
  1121. if (doc.archived) {
  1122. Activities.insert({
  1123. userId,
  1124. activityType: 'archivedCard',
  1125. listName: Lists.findOne(doc.listId).title,
  1126. boardId: doc.boardId,
  1127. listId: doc.listId,
  1128. cardId: doc._id,
  1129. });
  1130. } else {
  1131. Activities.insert({
  1132. userId,
  1133. activityType: 'restoredCard',
  1134. boardId: doc.boardId,
  1135. listName: Lists.findOne(doc.listId).title,
  1136. listId: doc.listId,
  1137. cardId: doc._id,
  1138. });
  1139. }
  1140. }
  1141. }
  1142. function cardMembers(userId, doc, fieldNames, modifier) {
  1143. if (!_.contains(fieldNames, 'members'))
  1144. return;
  1145. let memberId;
  1146. // Say hello to the new member
  1147. if (modifier.$addToSet && modifier.$addToSet.members) {
  1148. memberId = modifier.$addToSet.members;
  1149. const username = Users.findOne(memberId).username;
  1150. if (!_.contains(doc.members, memberId)) {
  1151. Activities.insert({
  1152. userId,
  1153. username,
  1154. activityType: 'joinMember',
  1155. boardId: doc.boardId,
  1156. cardId: doc._id,
  1157. });
  1158. }
  1159. }
  1160. // Say goodbye to the former member
  1161. if (modifier.$pull && modifier.$pull.members) {
  1162. memberId = modifier.$pull.members;
  1163. const username = Users.findOne(memberId).username;
  1164. // Check that the former member is member of the card
  1165. if (_.contains(doc.members, memberId)) {
  1166. Activities.insert({
  1167. userId,
  1168. username,
  1169. activityType: 'unjoinMember',
  1170. boardId: doc.boardId,
  1171. cardId: doc._id,
  1172. });
  1173. }
  1174. }
  1175. }
  1176. function cardLabels(userId, doc, fieldNames, modifier) {
  1177. if (!_.contains(fieldNames, 'labelIds'))
  1178. return;
  1179. let labelId;
  1180. // Say hello to the new label
  1181. if (modifier.$addToSet && modifier.$addToSet.labelIds) {
  1182. labelId = modifier.$addToSet.labelIds;
  1183. if (!_.contains(doc.labelIds, labelId)) {
  1184. const act = {
  1185. userId,
  1186. labelId,
  1187. activityType: 'addedLabel',
  1188. boardId: doc.boardId,
  1189. cardId: doc._id,
  1190. };
  1191. Activities.insert(act);
  1192. }
  1193. }
  1194. // Say goodbye to the label
  1195. if (modifier.$pull && modifier.$pull.labelIds) {
  1196. labelId = modifier.$pull.labelIds;
  1197. // Check that the former member is member of the card
  1198. if (_.contains(doc.labelIds, labelId)) {
  1199. Activities.insert({
  1200. userId,
  1201. labelId,
  1202. activityType: 'removedLabel',
  1203. boardId: doc.boardId,
  1204. cardId: doc._id,
  1205. });
  1206. }
  1207. }
  1208. }
  1209. function cardCreation(userId, doc) {
  1210. Activities.insert({
  1211. userId,
  1212. activityType: 'createCard',
  1213. boardId: doc.boardId,
  1214. listName: Lists.findOne(doc.listId).title,
  1215. listId: doc.listId,
  1216. cardId: doc._id,
  1217. cardTitle:doc.title,
  1218. swimlaneName: Swimlanes.findOne(doc.swimlaneId).title,
  1219. swimlaneId: doc.swimlaneId,
  1220. });
  1221. }
  1222. function cardRemover(userId, doc) {
  1223. Activities.remove({
  1224. cardId: doc._id,
  1225. });
  1226. Checklists.remove({
  1227. cardId: doc._id,
  1228. });
  1229. Subtasks.remove({
  1230. cardId: doc._id,
  1231. });
  1232. CardComments.remove({
  1233. cardId: doc._id,
  1234. });
  1235. Attachments.remove({
  1236. cardId: doc._id,
  1237. });
  1238. }
  1239. if (Meteor.isServer) {
  1240. // Cards are often fetched within a board, so we create an index to make these
  1241. // queries more efficient.
  1242. Meteor.startup(() => {
  1243. Cards._collection._ensureIndex({boardId: 1, createdAt: -1});
  1244. // https://github.com/wekan/wekan/issues/1863
  1245. // Swimlane added a new field in the cards collection of mongodb named parentId.
  1246. // When loading a board, mongodb is searching for every cards, the id of the parent (in the swinglanes collection).
  1247. // With a huge database, this result in a very slow app and high CPU on the mongodb side.
  1248. // To correct it, add Index to parentId:
  1249. Cards._collection._ensureIndex({parentId: 1});
  1250. });
  1251. Cards.after.insert((userId, doc) => {
  1252. cardCreation(userId, doc);
  1253. });
  1254. // New activity for card (un)archivage
  1255. Cards.after.update((userId, doc, fieldNames) => {
  1256. cardState(userId, doc, fieldNames);
  1257. });
  1258. //New activity for card moves
  1259. Cards.after.update(function(userId, doc, fieldNames) {
  1260. const oldListId = this.previous.listId;
  1261. const oldSwimlaneId = this.previous.swimlaneId;
  1262. cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId);
  1263. });
  1264. // Add a new activity if we add or remove a member to the card
  1265. Cards.before.update((userId, doc, fieldNames, modifier) => {
  1266. cardMembers(userId, doc, fieldNames, modifier);
  1267. });
  1268. // Add a new activity if we add or remove a label to the card
  1269. Cards.before.update((userId, doc, fieldNames, modifier) => {
  1270. cardLabels(userId, doc, fieldNames, modifier);
  1271. });
  1272. // Remove all activities associated with a card if we remove the card
  1273. // Remove also card_comments / checklists / attachments
  1274. Cards.after.remove((userId, doc) => {
  1275. cardRemover(userId, doc);
  1276. });
  1277. }
  1278. //SWIMLANES REST API
  1279. if (Meteor.isServer) {
  1280. /**
  1281. * @operation get_swimlane_cards
  1282. * @summary get all cards attached to a swimlane
  1283. *
  1284. * @param {string} boardId the board ID
  1285. * @param {string} swimlaneId the swimlane ID
  1286. * @return_type [{_id: string,
  1287. * title: string,
  1288. * description: string,
  1289. * listId: string}]
  1290. */
  1291. JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', function(req, res) {
  1292. const paramBoardId = req.params.boardId;
  1293. const paramSwimlaneId = req.params.swimlaneId;
  1294. Authentication.checkBoardAccess(req.userId, paramBoardId);
  1295. JsonRoutes.sendResult(res, {
  1296. code: 200,
  1297. data: Cards.find({
  1298. boardId: paramBoardId,
  1299. swimlaneId: paramSwimlaneId,
  1300. archived: false,
  1301. }).map(function(doc) {
  1302. return {
  1303. _id: doc._id,
  1304. title: doc.title,
  1305. description: doc.description,
  1306. listId: doc.listId,
  1307. };
  1308. }),
  1309. });
  1310. });
  1311. }
  1312. //LISTS REST API
  1313. if (Meteor.isServer) {
  1314. /**
  1315. * @operation get_all_cards
  1316. * @summary Get all Cards attached to a List
  1317. *
  1318. * @param {string} boardId the board ID
  1319. * @param {string} listId the list ID
  1320. * @return_type [{_id: string,
  1321. * title: string,
  1322. * description: string}]
  1323. */
  1324. JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function(req, res) {
  1325. const paramBoardId = req.params.boardId;
  1326. const paramListId = req.params.listId;
  1327. Authentication.checkBoardAccess(req.userId, paramBoardId);
  1328. JsonRoutes.sendResult(res, {
  1329. code: 200,
  1330. data: Cards.find({
  1331. boardId: paramBoardId,
  1332. listId: paramListId,
  1333. archived: false,
  1334. }).map(function(doc) {
  1335. return {
  1336. _id: doc._id,
  1337. title: doc.title,
  1338. description: doc.description,
  1339. };
  1340. }),
  1341. });
  1342. });
  1343. /**
  1344. * @operation get_card
  1345. * @summary Get a Card
  1346. *
  1347. * @param {string} boardId the board ID
  1348. * @param {string} listId the list ID of the card
  1349. * @param {string} cardId the card ID
  1350. * @return_type Cards
  1351. */
  1352. JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) {
  1353. const paramBoardId = req.params.boardId;
  1354. const paramListId = req.params.listId;
  1355. const paramCardId = req.params.cardId;
  1356. Authentication.checkBoardAccess(req.userId, paramBoardId);
  1357. JsonRoutes.sendResult(res, {
  1358. code: 200,
  1359. data: Cards.findOne({
  1360. _id: paramCardId,
  1361. listId: paramListId,
  1362. boardId: paramBoardId,
  1363. archived: false,
  1364. }),
  1365. });
  1366. });
  1367. /**
  1368. * @operation new_card
  1369. * @summary Create a new Card
  1370. *
  1371. * @param {string} boardId the board ID of the new card
  1372. * @param {string} listId the list ID of the new card
  1373. * @param {string} authorID the user ID of the person owning the card
  1374. * @param {string} title the title of the new card
  1375. * @param {string} description the description of the new card
  1376. * @param {string} swimlaneId the swimlane ID of the new card
  1377. * @param {string} [members] the member IDs list of the new card
  1378. * @return_type {_id: string}
  1379. */
  1380. JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(req, res) {
  1381. Authentication.checkUserId(req.userId);
  1382. const paramBoardId = req.params.boardId;
  1383. const paramListId = req.params.listId;
  1384. const check = Users.findOne({
  1385. _id: req.body.authorId,
  1386. });
  1387. const members = req.body.members || [req.body.authorId];
  1388. if (typeof check !== 'undefined') {
  1389. const id = Cards.direct.insert({
  1390. title: req.body.title,
  1391. boardId: paramBoardId,
  1392. listId: paramListId,
  1393. description: req.body.description,
  1394. userId: req.body.authorId,
  1395. swimlaneId: req.body.swimlaneId,
  1396. sort: 0,
  1397. members,
  1398. });
  1399. JsonRoutes.sendResult(res, {
  1400. code: 200,
  1401. data: {
  1402. _id: id,
  1403. },
  1404. });
  1405. const card = Cards.findOne({
  1406. _id: id,
  1407. });
  1408. cardCreation(req.body.authorId, card);
  1409. } else {
  1410. JsonRoutes.sendResult(res, {
  1411. code: 401,
  1412. });
  1413. }
  1414. });
  1415. /*
  1416. * Note for the JSDoc:
  1417. * 'list' will be interpreted as the path parameter
  1418. * 'listID' will be interpreted as the body parameter
  1419. */
  1420. /**
  1421. * @operation edit_card
  1422. * @summary Edit Fields in a Card
  1423. *
  1424. * @param {string} boardId the board ID of the card
  1425. * @param {string} list the list ID of the card
  1426. * @param {string} cardId the ID of the card
  1427. * @param {string} [title] the new title of the card
  1428. * @param {string} [listId] the new list ID of the card (move operation)
  1429. * @param {string} [description] the new description of the card
  1430. * @param {string} [authorId] change the owner of the card
  1431. * @param {string} [labelIds] the new list of label IDs attached to the card
  1432. * @param {string} [swimlaneId] the new swimlane ID of the card
  1433. * @param {string} [members] the new list of member IDs attached to the card
  1434. * @param {string} [requestedBy] the new requestedBy field of the card
  1435. * @param {string} [assignedBy] the new assignedBy field of the card
  1436. * @param {string} [receivedAt] the new receivedAt field of the card
  1437. * @param {string} [assignBy] the new assignBy field of the card
  1438. * @param {string} [startAt] the new startAt field of the card
  1439. * @param {string} [dueAt] the new dueAt field of the card
  1440. * @param {string} [endAt] the new endAt field of the card
  1441. * @param {string} [spentTime] the new spentTime field of the card
  1442. * @param {boolean} [isOverTime] the new isOverTime field of the card
  1443. * @param {string} [customFields] the new customFields value of the card
  1444. */
  1445. JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) {
  1446. Authentication.checkUserId(req.userId);
  1447. const paramBoardId = req.params.boardId;
  1448. const paramCardId = req.params.cardId;
  1449. const paramListId = req.params.listId;
  1450. if (req.body.hasOwnProperty('title')) {
  1451. const newTitle = req.body.title;
  1452. Cards.direct.update({
  1453. _id: paramCardId,
  1454. listId: paramListId,
  1455. boardId: paramBoardId,
  1456. archived: false,
  1457. }, {
  1458. $set: {
  1459. title: newTitle,
  1460. },
  1461. });
  1462. }
  1463. if (req.body.hasOwnProperty('listId')) {
  1464. const newParamListId = req.body.listId;
  1465. Cards.direct.update({
  1466. _id: paramCardId,
  1467. listId: paramListId,
  1468. boardId: paramBoardId,
  1469. archived: false,
  1470. }, {
  1471. $set: {
  1472. listId: newParamListId,
  1473. },
  1474. });
  1475. const card = Cards.findOne({
  1476. _id: paramCardId,
  1477. });
  1478. cardMove(req.body.authorId, card, {
  1479. fieldName: 'listId',
  1480. }, paramListId);
  1481. }
  1482. if (req.body.hasOwnProperty('description')) {
  1483. const newDescription = req.body.description;
  1484. Cards.direct.update({
  1485. _id: paramCardId,
  1486. listId: paramListId,
  1487. boardId: paramBoardId,
  1488. archived: false,
  1489. }, {
  1490. $set: {
  1491. description: newDescription,
  1492. },
  1493. });
  1494. }
  1495. if (req.body.hasOwnProperty('labelIds')) {
  1496. let newlabelIds = req.body.labelIds;
  1497. if (_.isString(newlabelIds)) {
  1498. if (newlabelIds === '') {
  1499. newlabelIds = null;
  1500. }
  1501. else {
  1502. newlabelIds = [newlabelIds];
  1503. }
  1504. }
  1505. Cards.direct.update({
  1506. _id: paramCardId,
  1507. listId: paramListId,
  1508. boardId: paramBoardId,
  1509. archived: false,
  1510. }, {
  1511. $set: {
  1512. labelIds: newlabelIds,
  1513. },
  1514. });
  1515. }
  1516. if (req.body.hasOwnProperty('requestedBy')) {
  1517. const newrequestedBy = req.body.requestedBy;
  1518. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1519. {$set: {requestedBy: newrequestedBy}});
  1520. }
  1521. if (req.body.hasOwnProperty('assignedBy')) {
  1522. const newassignedBy = req.body.assignedBy;
  1523. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1524. {$set: {assignedBy: newassignedBy}});
  1525. }
  1526. if (req.body.hasOwnProperty('receivedAt')) {
  1527. const newreceivedAt = req.body.receivedAt;
  1528. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1529. {$set: {receivedAt: newreceivedAt}});
  1530. }
  1531. if (req.body.hasOwnProperty('startAt')) {
  1532. const newstartAt = req.body.startAt;
  1533. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1534. {$set: {startAt: newstartAt}});
  1535. }
  1536. if (req.body.hasOwnProperty('dueAt')) {
  1537. const newdueAt = req.body.dueAt;
  1538. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1539. {$set: {dueAt: newdueAt}});
  1540. }
  1541. if (req.body.hasOwnProperty('endAt')) {
  1542. const newendAt = req.body.endAt;
  1543. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1544. {$set: {endAt: newendAt}});
  1545. }
  1546. if (req.body.hasOwnProperty('spentTime')) {
  1547. const newspentTime = req.body.spentTime;
  1548. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1549. {$set: {spentTime: newspentTime}});
  1550. }
  1551. if (req.body.hasOwnProperty('isOverTime')) {
  1552. const newisOverTime = req.body.isOverTime;
  1553. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1554. {$set: {isOverTime: newisOverTime}});
  1555. }
  1556. if (req.body.hasOwnProperty('customFields')) {
  1557. const newcustomFields = req.body.customFields;
  1558. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1559. {$set: {customFields: newcustomFields}});
  1560. }
  1561. if (req.body.hasOwnProperty('members')) {
  1562. let newmembers = req.body.members;
  1563. if (_.isString(newmembers)) {
  1564. if (newmembers === '') {
  1565. newmembers = null;
  1566. }
  1567. else {
  1568. newmembers = [newmembers];
  1569. }
  1570. }
  1571. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1572. {$set: {members: newmembers}});
  1573. }
  1574. if (req.body.hasOwnProperty('swimlaneId')) {
  1575. const newParamSwimlaneId = req.body.swimlaneId;
  1576. Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
  1577. {$set: {swimlaneId: newParamSwimlaneId}});
  1578. }
  1579. JsonRoutes.sendResult(res, {
  1580. code: 200,
  1581. data: {
  1582. _id: paramCardId,
  1583. },
  1584. });
  1585. });
  1586. /**
  1587. * @operation delete_card
  1588. * @summary Delete a card from a board
  1589. *
  1590. * @description This operation **deletes** a card, and therefore the card
  1591. * is not put in the recycle bin.
  1592. *
  1593. * @param {string} boardId the board ID of the card
  1594. * @param {string} list the list ID of the card
  1595. * @param {string} cardId the ID of the card
  1596. * @return_type {_id: string}
  1597. */
  1598. JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) {
  1599. Authentication.checkUserId(req.userId);
  1600. const paramBoardId = req.params.boardId;
  1601. const paramListId = req.params.listId;
  1602. const paramCardId = req.params.cardId;
  1603. Cards.direct.remove({
  1604. _id: paramCardId,
  1605. listId: paramListId,
  1606. boardId: paramBoardId,
  1607. });
  1608. const card = Cards.find({
  1609. _id: paramCardId,
  1610. });
  1611. cardRemover(req.body.authorId, card);
  1612. JsonRoutes.sendResult(res, {
  1613. code: 200,
  1614. data: {
  1615. _id: paramCardId,
  1616. },
  1617. });
  1618. });
  1619. }