123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- 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,
- },
- archived: {
- type: Boolean,
- autoValue() { // eslint-disable-line consistent-return
- if (this.isInsert && !this.isSet) {
- return false;
- }
- },
- },
- listId: {
- 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,
- },
- coverId: {
- type: String,
- optional: true,
- },
- createdAt: {
- type: Date,
- autoValue() { // eslint-disable-line consistent-return
- if (this.isInsert) {
- return new Date();
- } else {
- this.unset();
- }
- },
- },
- dateLastActivity: {
- type: Date,
- autoValue() {
- return new Date();
- },
- },
- description: {
- type: String,
- optional: true,
- },
- labelIds: {
- type: [String],
- optional: true,
- },
- members: {
- type: [String],
- optional: true,
- },
- startAt: {
- type: Date,
- optional: true,
- },
- dueAt: {
- type: Date,
- 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,
- },
- }));
- 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.members, memberId);
- },
- activities() {
- return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }});
- },
- comments() {
- return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
- },
- attachments() {
- 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() {
- return Checklists.find({ cardId: this._id }, { sort: { createdAt: 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;
- },
- absoluteUrl() {
- const board = this.board();
- return FlowRouter.url('card', {
- boardId: board._id,
- slug: board.slug,
- cardId: this._id,
- });
- },
- });
- Cards.mutations({
- archive() {
- return { $set: { archived: true }};
- },
- restore() {
- return { $set: { archived: false }};
- },
- setTitle(title) {
- return { $set: { title }};
- },
- setDescription(description) {
- return { $set: { description }};
- },
- move(listId, sortIndex) {
- const mutatedFields = { listId };
- if (sortIndex) {
- mutatedFields.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);
- }
- },
- assignMember(memberId) {
- return { $addToSet: { members: memberId }};
- },
- unassignMember(memberId) {
- return { $pull: { members: memberId }};
- },
- toggleMember(memberId) {
- if (this.members && this.members.indexOf(memberId) > -1) {
- return this.unassignMember(memberId);
- } else {
- return this.assignMember(memberId);
- }
- },
- setCover(coverId) {
- return { $set: { coverId }};
- },
- unsetCover() {
- return { $unset: { coverId: '' }};
- },
- setStart(startAt) {
- return { $set: { startAt }};
- },
- unsetStart() {
- return { $unset: { startAt: '' }};
- },
- setDue(dueAt) {
- return { $set: { dueAt }};
- },
- unsetDue() {
- return { $unset: { dueAt: '' }};
- },
- });
- 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) => {
- Activities.insert({
- userId,
- activityType: 'createCard',
- boardId: doc.boardId,
- listId: doc.listId,
- cardId: doc._id,
- });
- });
- // New activity for card (un)archivage
- Cards.after.update((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,
- });
- }
- }
- });
- // New activity for card moves
- Cards.after.update(function(userId, doc, fieldNames) {
- const oldListId = this.previous.listId;
- if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {
- Activities.insert({
- userId,
- oldListId,
- activityType: 'moveCard',
- listId: doc.listId,
- boardId: doc.boardId,
- cardId: doc._id,
- });
- }
- });
- // Add a new activity if we add or remove a member to the card
- Cards.before.update((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,
- });
- }
- }
- });
- // Remove all activities associated with a card if we remove the card
- // Remove also card_comments / checklists / attachments
- Cards.after.remove((userId, doc) => {
- Activities.remove({
- cardId: doc._id,
- });
- Checklists.remove({
- cardId: doc._id,
- });
- CardComments.remove({
- cardId: doc._id,
- });
- Attachments.remove({
- cardId: doc._id,
- });
- });
- }
|