| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167 | Cards = new Mongo.Collection('cards');// XXX To improve pub/sub performances a card document should include a// de-normalized number of comments so we don't have to publish the whole list// of comments just to display the number of them in the board view.Cards.attachSchema(new SimpleSchema({  title: {    type: String,    optional: true,    defaultValue: '',  },  archived: {    type: Boolean,    autoValue() { // eslint-disable-line consistent-return      if (this.isInsert && !this.isSet) {        return false;      }    },  },  parentId: {    type: String,    optional: true,    defaultValue: '',  },  listId: {    type: String,    optional: true,    defaultValue: '',  },  swimlaneId: {    type: String,  },  // The system could work without this `boardId` information (we could deduce  // the board identifier from the card), but it would make the system more  // difficult to manage and less efficient.  boardId: {    type: String,    optional: true,    defaultValue: '',  },  coverId: {    type: String,    optional: true,    defaultValue: '',  },  createdAt: {    type: Date,    autoValue() { // eslint-disable-line consistent-return      if (this.isInsert) {        return new Date();      } else {        this.unset();      }    },  },  customFields: {    type: [Object],    optional: true,    defaultValue: [],  },  'customFields.$': {    type: new SimpleSchema({      _id: {        type: String,        optional: true,        defaultValue: '',      },      value: {        type: Match.OneOf(String, Number, Boolean, Date),        optional: true,        defaultValue: '',      },    }),  },  dateLastActivity: {    type: Date,    autoValue() {      return new Date();    },  },  description: {    type: String,    optional: true,    defaultValue: '',  },  requestedBy: {    type: String,    optional: true,    defaultValue: '',  },  assignedBy: {    type: String,    optional: true,    defaultValue: '',  },  labelIds: {    type: [String],    optional: true,    defaultValue: [],  },  members: {    type: [String],    optional: true,    defaultValue: [],  },  receivedAt: {    type: Date,    optional: true,  },  startAt: {    type: Date,    optional: true,  },  dueAt: {    type: Date,    optional: true,  },  endAt: {    type: Date,    optional: true,  },  spentTime: {    type: Number,    decimal: true,    optional: true,    defaultValue: 0,  },  isOvertime: {    type: Boolean,    defaultValue: false,    optional: true,  },  // XXX Should probably be called `authorId`. Is it even needed since we have  // the `members` field?  userId: {    type: String,    autoValue() { // eslint-disable-line consistent-return      if (this.isInsert && !this.isSet) {        return this.userId;      }    },  },  sort: {    type: Number,    decimal: true,    defaultValue: '',  },  subtaskSort: {    type: Number,    decimal: true,    defaultValue: -1,    optional: true,  },  type: {    type: String,    defaultValue: '',  },  linkedId: {    type: String,    optional: true,    defaultValue: '',  },}));Cards.allow({  insert(userId, doc) {    return allowIsBoardMember(userId, Boards.findOne(doc.boardId));  },  update(userId, doc) {    return allowIsBoardMember(userId, Boards.findOne(doc.boardId));  },  remove(userId, doc) {    return allowIsBoardMember(userId, Boards.findOne(doc.boardId));  },  fetch: ['boardId'],});Cards.helpers({  list() {    return Lists.findOne(this.listId);  },  board() {    return Boards.findOne(this.boardId);  },  labels() {    const boardLabels = this.board().labels;    const cardLabels = _.filter(boardLabels, (label) => {      return _.contains(this.labelIds, label._id);    });    return cardLabels;  },  hasLabel(labelId) {    return _.contains(this.labelIds, labelId);  },  user() {    return Users.findOne(this.userId);  },  isAssigned(memberId) {    return _.contains(this.getMembers(), memberId);  },  activities() {    if (this.isLinkedCard()) {      return Activities.find({cardId: this.linkedId}, {sort: {createdAt: -1}});    } else if (this.isLinkedBoard()) {      return Activities.find({boardId: this.linkedId}, {sort: {createdAt: -1}});    } else {      return Activities.find({cardId: this._id}, {sort: {createdAt: -1}});    }  },  comments() {    if (this.isLinkedCard()) {      return CardComments.find({cardId: this.linkedId}, {sort: {createdAt: -1}});    } else {      return CardComments.find({cardId: this._id}, {sort: {createdAt: -1}});    }  },  attachments() {    if (this.isLinkedCard()) {      return Attachments.find({cardId: this.linkedId}, {sort: {uploadedAt: -1}});    } else {      return Attachments.find({cardId: this._id}, {sort: {uploadedAt: -1}});    }  },  cover() {    const cover = Attachments.findOne(this.coverId);    // if we return a cover before it is fully stored, we will get errors when we try to display it    // todo XXX we could return a default "upload pending" image in the meantime?    return cover && cover.url() && cover;  },  checklists() {    if (this.isLinkedCard()) {      return Checklists.find({cardId: this.linkedId}, {sort: { sort: 1 } });    } else {      return Checklists.find({cardId: this._id}, {sort: { sort: 1 } });    }  },  checklistItemCount() {    const checklists = this.checklists().fetch();    return checklists.map((checklist) => {      return checklist.itemCount();    }).reduce((prev, next) => {      return prev + next;    }, 0);  },  checklistFinishedCount() {    const checklists = this.checklists().fetch();    return checklists.map((checklist) => {      return checklist.finishedCount();    }).reduce((prev, next) => {      return prev + next;    }, 0);  },  checklistFinished() {    return this.hasChecklist() && this.checklistItemCount() === this.checklistFinishedCount();  },  hasChecklist() {    return this.checklistItemCount() !== 0;  },  subtasks() {    return Cards.find({      parentId: this._id,      archived: false,    }, {sort: { sort: 1 } });  },  allSubtasks() {    return Cards.find({      parentId: this._id,      archived: false,    }, {sort: { sort: 1 } });  },  subtasksCount() {    return Cards.find({      parentId: this._id,      archived: false,    }).count();  },  subtasksFinishedCount() {    return Cards.find({      parentId: this._id,      archived: true}).count();  },  subtasksFinished() {    const finishCount = this.subtasksFinishedCount();    return finishCount > 0 && this.subtasksCount() === finishCount;  },  allowsSubtasks() {    return this.subtasksCount() !== 0;  },  customFieldIndex(customFieldId) {    return _.pluck(this.customFields, '_id').indexOf(customFieldId);  },  // customFields with definitions  customFieldsWD() {    // get all definitions    const definitions = CustomFields.find({      boardId: this.boardId,    }).fetch();    // match right definition to each field    if (!this.customFields) return [];    return this.customFields.map((customField) => {      const definition = definitions.find((definition) => {        return definition._id === customField._id;      });      //search for "True Value" which is for DropDowns other then the Value (which is the id)      let trueValue = customField.value;      if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0)      {        for (let i = 0; i < definition.settings.dropdownItems.length; i++)        {          if (definition.settings.dropdownItems[i]._id === customField.value)          {            trueValue = definition.settings.dropdownItems[i].name;          }        }      }      return {        _id: customField._id,        value: customField.value,        trueValue,        definition,      };    });  },  absoluteUrl() {    const board = this.board();    return FlowRouter.url('card', {      boardId: board._id,      slug: board.slug,      cardId: this._id,    });  },  canBeRestored() {    const list = Lists.findOne({_id: this.listId});    if(!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()){      return false;    }    return true;  },  parentCard() {    if (this.parentId === '') {      return null;    }    return Cards.findOne(this.parentId);  },  parentCardName() {    let result = '';    if (this.parentId !== '') {      const card = Cards.findOne(this.parentId);      if (card) {        result = card.title;      }    }    return result;  },  parentListId() {    const result = [];    let crtParentId = this.parentId;    while (crtParentId !== '') {      const crt = Cards.findOne(crtParentId);      if ((crt === null) || (crt === undefined)) {        // maybe it has been deleted        break;      }      if (crtParentId in result) {        // circular reference        break;      }      result.unshift(crtParentId);      crtParentId = crt.parentId;    }    return result;  },  parentList() {    const resultId = [];    const result = [];    let crtParentId = this.parentId;    while (crtParentId !== '') {      const crt = Cards.findOne(crtParentId);      if ((crt === null) || (crt === undefined)) {        // maybe it has been deleted        break;      }      if (crtParentId in resultId) {        // circular reference        break;      }      resultId.unshift(crtParentId);      result.unshift(crt);      crtParentId = crt.parentId;    }    return result;  },  parentString(sep) {    return this.parentList().map(function(elem){      return elem.title;    }).join(sep);  },  isTopLevel() {    return this.parentId === '';  },  isLinkedCard() {    return this.type === 'cardType-linkedCard';  },  isLinkedBoard() {    return this.type === 'cardType-linkedBoard';  },  isLinked() {    return this.isLinkedCard() || this.isLinkedBoard();  },  setDescription(description) {    if (this.isLinkedCard()) {      return Cards.update({_id: this.linkedId}, {$set: {description}});    } else if (this.isLinkedBoard()) {      return Boards.update({_id: this.linkedId}, {$set: {description}});    } else {      return Cards.update(        {_id: this._id},        {$set: {description}}      );    }  },  getDescription() {    if (this.isLinkedCard()) {      const card = Cards.findOne({_id: this.linkedId});      if (card && card.description)        return card.description;      else        return null;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({_id: this.linkedId});      if (board && board.description)        return board.description;      else        return null;    } else if (this.description) {      return this.description;    } else {      return null;    }  },  getMembers() {    if (this.isLinkedCard()) {      const card = Cards.findOne({_id: this.linkedId});      return card.members;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({_id: this.linkedId});      return board.activeMembers().map((member) => {        return member.userId;      });    } else {      return this.members;    }  },  assignMember(memberId) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        { $addToSet: { members: memberId }}      );    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({_id: this.linkedId});      return board.addMember(memberId);    } else {      return Cards.update(        { _id: this._id },        { $addToSet: { members: memberId}}      );    }  },  unassignMember(memberId) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        { $pull: { members: memberId }}      );    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({_id: this.linkedId});      return board.removeMember(memberId);    } else {      return Cards.update(        { _id: this._id },        { $pull: { members: memberId}}      );    }  },  toggleMember(memberId) {    if (this.getMembers() && this.getMembers().indexOf(memberId) > -1) {      return this.unassignMember(memberId);    } else {      return this.assignMember(memberId);    }  },  getReceived() {    if (this.isLinkedCard()) {      const card = Cards.findOne({_id: this.linkedId});      return card.receivedAt;    } else {      return this.receivedAt;    }  },  setReceived(receivedAt) {    if (this.isLinkedCard()) {      return Cards.update(        {_id: this.linkedId},        {$set: {receivedAt}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {receivedAt}}      );    }  },  getStart() {    if (this.isLinkedCard()) {      const card = Cards.findOne({_id: this.linkedId});      return card.startAt;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({_id: this.linkedId});      return board.startAt;    } else {      return this.startAt;    }  },  setStart(startAt) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {startAt}}      );    } else if (this.isLinkedBoard()) {      return Boards.update(        {_id: this.linkedId},        {$set: {startAt}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {startAt}}      );    }  },  getDue() {    if (this.isLinkedCard()) {      const card = Cards.findOne({_id: this.linkedId});      return card.dueAt;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({_id: this.linkedId});      return board.dueAt;    } else {      return this.dueAt;    }  },  setDue(dueAt) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {dueAt}}      );    } else if (this.isLinkedBoard()) {      return Boards.update(        {_id: this.linkedId},        {$set: {dueAt}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {dueAt}}      );    }  },  getEnd() {    if (this.isLinkedCard()) {      const card = Cards.findOne({_id: this.linkedId});      return card.endAt;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({_id: this.linkedId});      return board.endAt;    } else {      return this.endAt;    }  },  setEnd(endAt) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {endAt}}      );    } else if (this.isLinkedBoard()) {      return Boards.update(        {_id: this.linkedId},        {$set: {endAt}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {endAt}}      );    }  },  getIsOvertime() {    if (this.isLinkedCard()) {      const card = Cards.findOne({ _id: this.linkedId });      return card.isOvertime;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({ _id: this.linkedId});      return board.isOvertime;    } else {      return this.isOvertime;    }  },  setIsOvertime(isOvertime) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {isOvertime}}      );    } else if (this.isLinkedBoard()) {      return Boards.update(        {_id: this.linkedId},        {$set: {isOvertime}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {isOvertime}}      );    }  },  getSpentTime() {    if (this.isLinkedCard()) {      const card = Cards.findOne({ _id: this.linkedId });      return card.spentTime;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({ _id: this.linkedId});      return board.spentTime;    } else {      return this.spentTime;    }  },  setSpentTime(spentTime) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {spentTime}}      );    } else if (this.isLinkedBoard()) {      return Boards.update(        {_id: this.linkedId},        {$set: {spentTime}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {spentTime}}      );    }  },  getId() {    if (this.isLinked()) {      return this.linkedId;    } else {      return this._id;    }  },  getTitle() {    if (this.isLinkedCard()) {      const card = Cards.findOne({ _id: this.linkedId });      return card.title;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({ _id: this.linkedId});      return board.title;    } else {      return this.title;    }  },  getBoardTitle() {    if (this.isLinkedCard()) {      const card = Cards.findOne({ _id: this.linkedId });      const board = Boards.findOne({ _id: card.boardId });      return board.title;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({ _id: this.linkedId});      return board.title;    } else {      const board = Boards.findOne({ _id: this.boardId });      return board.title;    }  },  setTitle(title) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {title}}      );    } else if (this.isLinkedBoard()) {      return Boards.update(        {_id: this.linkedId},        {$set: {title}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {title}}      );    }  },  getArchived() {    if (this.isLinkedCard()) {      const card = Cards.findOne({ _id: this.linkedId });      return card.archived;    } else if (this.isLinkedBoard()) {      const board = Boards.findOne({ _id: this.linkedId});      return board.archived;    } else {      return this.archived;    }  },  setRequestedBy(requestedBy) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {requestedBy}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {requestedBy}}      );    }  },  getRequestedBy() {    if (this.isLinkedCard()) {      const card = Cards.findOne({ _id: this.linkedId });      return card.requestedBy;    } else  {      return this.requestedBy;    }  },  setAssignedBy(assignedBy) {    if (this.isLinkedCard()) {      return Cards.update(        { _id: this.linkedId },        {$set: {assignedBy}}      );    } else {      return Cards.update(        {_id: this._id},        {$set: {assignedBy}}      );    }  },  getAssignedBy() {    if (this.isLinkedCard()) {      const card = Cards.findOne({ _id: this.linkedId });      return card.assignedBy;    } else  {      return this.assignedBy;    }  },});Cards.mutations({  applyToChildren(funct) {    Cards.find({ parentId: this._id }).forEach((card) => {      funct(card);    });  },  archive() {    this.applyToChildren((card) => { return card.archive(); });    return {$set: {archived: true}};  },  restore() {    this.applyToChildren((card) => { return card.restore(); });    return {$set: {archived: false}};  },  move(swimlaneId, listId, sortIndex) {    const list = Lists.findOne(listId);    const mutatedFields = {      swimlaneId,      listId,      boardId: list.boardId,      sort: sortIndex,    };    return {$set: mutatedFields};  },  addLabel(labelId) {    return {$addToSet: {labelIds: labelId}};  },  removeLabel(labelId) {    return {$pull: {labelIds: labelId}};  },  toggleLabel(labelId) {    if (this.labelIds && this.labelIds.indexOf(labelId) > -1) {      return this.removeLabel(labelId);    } else {      return this.addLabel(labelId);    }  },  assignCustomField(customFieldId) {    return {$addToSet: {customFields: {_id: customFieldId, value: null}}};  },  unassignCustomField(customFieldId) {    return {$pull: {customFields: {_id: customFieldId}}};  },  toggleCustomField(customFieldId) {    if (this.customFields && this.customFieldIndex(customFieldId) > -1) {      return this.unassignCustomField(customFieldId);    } else {      return this.assignCustomField(customFieldId);    }  },  setCustomField(customFieldId, value) {    // todo    const index = this.customFieldIndex(customFieldId);    if (index > -1) {      const update = {$set: {}};      update.$set[`customFields.${index}.value`] = value;      return update;    }    // TODO    // Ignatz 18.05.2018: Return null to silence ESLint. No Idea if that is correct    return null;  },  setCover(coverId) {    return {$set: {coverId}};  },  unsetCover() {    return {$unset: {coverId: ''}};  },  setParentId(parentId) {    return {$set: {parentId}};  },});//FUNCTIONS FOR creation of Activitiesfunction cardMove(userId, doc, fieldNames, oldListId) {  if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {    Activities.insert({      userId,      oldListId,      activityType: 'moveCard',      listId: doc.listId,      boardId: doc.boardId,      cardId: doc._id,    });  }}function cardState(userId, doc, fieldNames) {  if (_.contains(fieldNames, 'archived')) {    if (doc.archived) {      Activities.insert({        userId,        activityType: 'archivedCard',        boardId: doc.boardId,        listId: doc.listId,        cardId: doc._id,      });    } else {      Activities.insert({        userId,        activityType: 'restoredCard',        boardId: doc.boardId,        listId: doc.listId,        cardId: doc._id,      });    }  }}function cardMembers(userId, doc, fieldNames, modifier) {  if (!_.contains(fieldNames, 'members'))    return;  let memberId;  // Say hello to the new member  if (modifier.$addToSet && modifier.$addToSet.members) {    memberId = modifier.$addToSet.members;    if (!_.contains(doc.members, memberId)) {      Activities.insert({        userId,        memberId,        activityType: 'joinMember',        boardId: doc.boardId,        cardId: doc._id,      });    }  }  // Say goodbye to the former member  if (modifier.$pull && modifier.$pull.members) {    memberId = modifier.$pull.members;    // Check that the former member is member of the card    if (_.contains(doc.members, memberId)) {      Activities.insert({        userId,        memberId,        activityType: 'unjoinMember',        boardId: doc.boardId,        cardId: doc._id,      });    }  }}function cardCreation(userId, doc) {  Activities.insert({    userId,    activityType: 'createCard',    boardId: doc.boardId,    listId: doc.listId,    cardId: doc._id,  });}function cardRemover(userId, doc) {  Activities.remove({    cardId: doc._id,  });  Checklists.remove({    cardId: doc._id,  });  Subtasks.remove({    cardId: doc._id,  });  CardComments.remove({    cardId: doc._id,  });  Attachments.remove({    cardId: doc._id,  });}if (Meteor.isServer) {  // Cards are often fetched within a board, so we create an index to make these  // queries more efficient.  Meteor.startup(() => {    Cards._collection._ensureIndex({boardId: 1, createdAt: -1});  });  Cards.after.insert((userId, doc) => {    cardCreation(userId, doc);  });  // New activity for card (un)archivage  Cards.after.update((userId, doc, fieldNames) => {    cardState(userId, doc, fieldNames);  });  //New activity for card moves  Cards.after.update(function (userId, doc, fieldNames) {    const oldListId = this.previous.listId;    cardMove(userId, doc, fieldNames, oldListId);  });  // Add a new activity if we add or remove a member to the card  Cards.before.update((userId, doc, fieldNames, modifier) => {    cardMembers(userId, doc, fieldNames, modifier);  });  // Remove all activities associated with a card if we remove the card  // Remove also card_comments / checklists / attachments  Cards.after.remove((userId, doc) => {    cardRemover(userId, doc);  });}//LISTS REST APIif (Meteor.isServer) {  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res) {    const paramBoardId = req.params.boardId;    const paramListId = req.params.listId;    Authentication.checkBoardAccess(req.userId, paramBoardId);    JsonRoutes.sendResult(res, {      code: 200,      data: Cards.find({boardId: paramBoardId, listId: paramListId, archived: false}).map(function (doc) {        return {          _id: doc._id,          title: doc.title,          description: doc.description,        };      }),    });  });  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) {    const paramBoardId = req.params.boardId;    const paramListId = req.params.listId;    const paramCardId = req.params.cardId;    Authentication.checkBoardAccess(req.userId, paramBoardId);    JsonRoutes.sendResult(res, {      code: 200,      data: Cards.findOne({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}),    });  });  JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res) {    Authentication.checkUserId(req.userId);    const paramBoardId = req.params.boardId;    const paramListId = req.params.listId;    const check = Users.findOne({_id: req.body.authorId});    const members = req.body.members || [req.body.authorId];    if (typeof  check !== 'undefined') {      const id = Cards.direct.insert({        title: req.body.title,        boardId: paramBoardId,        listId: paramListId,        description: req.body.description,        userId: req.body.authorId,        swimlaneId: req.body.swimlaneId,        sort: 0,        members,      });      JsonRoutes.sendResult(res, {        code: 200,        data: {          _id: id,        },      });      const card = Cards.findOne({_id:id});      cardCreation(req.body.authorId, card);    } else {      JsonRoutes.sendResult(res, {        code: 401,      });    }  });  JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) {    Authentication.checkUserId(req.userId);    const paramBoardId = req.params.boardId;    const paramCardId = req.params.cardId;    const paramListId = req.params.listId;    if (req.body.hasOwnProperty('title')) {      const newTitle = req.body.title;      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},        {$set: {title: newTitle}});    }    if (req.body.hasOwnProperty('listId')) {      const newParamListId = req.body.listId;      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},        {$set: {listId: newParamListId}});      const card = Cards.findOne({_id: paramCardId} );      cardMove(req.body.authorId, card, {fieldName: 'listId'}, paramListId);    }    if (req.body.hasOwnProperty('description')) {      const newDescription = req.body.description;      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},        {$set: {description: newDescription}});    }    if (req.body.hasOwnProperty('labelIds')) {      const newlabelIds = req.body.labelIds;      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},        {$set: {labelIds: newlabelIds}});    }    JsonRoutes.sendResult(res, {      code: 200,      data: {        _id: paramCardId,      },    });  });  JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) {    Authentication.checkUserId(req.userId);    const paramBoardId = req.params.boardId;    const paramListId = req.params.listId;    const paramCardId = req.params.cardId;    Cards.direct.remove({_id: paramCardId, listId: paramListId, boardId: paramBoardId});    const card = Cards.find({_id: paramCardId} );    cardRemover(req.body.authorId, card);    JsonRoutes.sendResult(res, {      code: 200,      data: {        _id: paramCardId,      },    });  });}
 |