summaryrefslogtreecommitdiffstats
path: root/collections/cards.js
diff options
context:
space:
mode:
Diffstat (limited to 'collections/cards.js')
-rw-r--r--collections/cards.js287
1 files changed, 287 insertions, 0 deletions
diff --git a/collections/cards.js b/collections/cards.js
new file mode 100644
index 00000000..538b6af4
--- /dev/null
+++ b/collections/cards.js
@@ -0,0 +1,287 @@
+Cards = new Mongo.Collection('cards');
+CardComments = new Mongo.Collection('card_comments');
+
+// 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
+ },
+ 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,
+ denyUpdate: true
+ },
+ dateLastActivity: {
+ type: Date
+ },
+ description: {
+ type: String,
+ optional: true
+ },
+ labelIds: {
+ type: [String],
+ optional: true
+ },
+ members: {
+ type: [String],
+ optional: true
+ },
+ // XXX Should probably be called `authorId`. Is it even needed since we have
+ // the `members` field?
+ userId: {
+ type: String
+ },
+ sort: {
+ type: Number,
+ decimal: true
+ }
+}));
+
+CardComments.attachSchema(new SimpleSchema({
+ boardId: {
+ type: String
+ },
+ cardId: {
+ type: String
+ },
+ // XXX Rename in `content`? `text` is a bit vague...
+ text: {
+ type: String
+ },
+ // XXX We probably don't need this information here, since we already have it
+ // in the associated comment creation activity
+ createdAt: {
+ type: Date,
+ denyUpdate: false
+ },
+ // XXX Should probably be called `authorId`
+ userId: {
+ type: String
+ }
+}));
+
+if (Meteor.isServer) {
+ Cards.allow({
+ insert: function(userId, doc) {
+ return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+ },
+ update: function(userId, doc) {
+ return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+ },
+ remove: function(userId, doc) {
+ return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+ },
+ fetch: ['boardId']
+ });
+
+ CardComments.allow({
+ insert: function(userId, doc) {
+ return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+ },
+ update: function(userId, doc) {
+ return userId === doc.userId;
+ },
+ remove: function(userId, doc) {
+ return userId === doc.userId;
+ },
+ fetch: ['userId', 'boardId']
+ });
+}
+
+Cards.helpers({
+ list: function() {
+ return Lists.findOne(this.listId);
+ },
+ board: function() {
+ return Boards.findOne(this.boardId);
+ },
+ labels: function() {
+ var self = this;
+ var boardLabels = self.board().labels;
+ var cardLabels = _.filter(boardLabels, function(label) {
+ return _.contains(self.labelIds, label._id);
+ });
+ return cardLabels;
+ },
+ user: function() {
+ return Users.findOne(this.userId);
+ },
+ activities: function() {
+ return Activities.find({ type: 'card', cardId: this._id },
+ { sort: { createdAt: -1 }});
+ },
+ comments: function() {
+ return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
+ },
+ attachments: function() {
+ return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }});
+ },
+ cover: function() {
+ return Attachments.findOne(this.coverId);
+ },
+ absoluteUrl: function() {
+ var board = this.board();
+ return Router.path('Card', {
+ boardId: board._id,
+ slug: board.slug,
+ cardId: this._id
+ });
+ },
+ rootUrl: function() {
+ return Meteor.absoluteUrl(this.absoluteUrl().replace('/', ''));
+ }
+});
+
+CardComments.helpers({
+ user: function() {
+ return Users.findOne(this.userId);
+ }
+});
+
+CardComments.hookOptions.after.update = { fetchPrevious: false };
+Cards.before.insert(function(userId, doc) {
+ doc.createdAt = new Date();
+ doc.dateLastActivity = new Date();
+
+ // defaults
+ doc.archived = false;
+
+ // userId native set.
+ if (! doc.userId)
+ doc.userId = userId;
+});
+
+CardComments.before.insert(function(userId, doc) {
+ doc.createdAt = new Date();
+ doc.userId = userId;
+});
+
+if (Meteor.isServer) {
+ Cards.after.insert(function(userId, doc) {
+ Activities.insert({
+ type: 'card',
+ activityType: 'createCard',
+ boardId: doc.boardId,
+ listId: doc.listId,
+ cardId: doc._id,
+ userId: userId
+ });
+ });
+
+ // New activity for card (un)archivage
+ Cards.after.update(function(userId, doc, fieldNames) {
+ if (_.contains(fieldNames, 'archived')) {
+ if (doc.archived) {
+ Activities.insert({
+ type: 'card',
+ activityType: 'archivedCard',
+ boardId: doc.boardId,
+ listId: doc.listId,
+ cardId: doc._id,
+ userId: userId
+ });
+ } else {
+ Activities.insert({
+ type: 'card',
+ activityType: 'restoredCard',
+ boardId: doc.boardId,
+ listId: doc.listId,
+ cardId: doc._id,
+ userId: userId
+ });
+ }
+ }
+ });
+
+ // New activity for card moves
+ Cards.after.update(function(userId, doc, fieldNames) {
+ var oldListId = this.previous.listId;
+ if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {
+ Activities.insert({
+ type: 'card',
+ activityType: 'moveCard',
+ listId: doc.listId,
+ oldListId: oldListId,
+ boardId: doc.boardId,
+ cardId: doc._id,
+ userId: userId
+ });
+ }
+ });
+
+ // Add a new activity if we add or remove a member to the card
+ Cards.before.update(function(userId, doc, fieldNames, modifier) {
+ if (! _.contains(fieldNames, 'members'))
+ return;
+ var memberId;
+ // Say hello to the new member
+ if (modifier.$addToSet && modifier.$addToSet.members) {
+ memberId = modifier.$addToSet.members;
+ if (! _.contains(doc.members, memberId)) {
+ Activities.insert({
+ type: 'card',
+ activityType: 'joinMember',
+ boardId: doc.boardId,
+ cardId: doc._id,
+ userId: userId,
+ memberId: memberId
+ });
+ }
+ }
+
+ // Say goodbye to the former member
+ if (modifier.$pull && modifier.$pull.members) {
+ memberId = modifier.$pull.members;
+ Activities.insert({
+ type: 'card',
+ activityType: 'unjoinMember',
+ boardId: doc.boardId,
+ cardId: doc._id,
+ userId: userId,
+ memberId: memberId
+ });
+ }
+ });
+
+ // Remove all activities associated with a card if we remove the card
+ Cards.after.remove(function(userId, doc) {
+ Activities.remove({
+ cardId: doc._id
+ });
+ });
+
+ CardComments.after.insert(function(userId, doc) {
+ Activities.insert({
+ type: 'comment',
+ activityType: 'addComment',
+ boardId: doc.boardId,
+ cardId: doc.cardId,
+ commentId: doc._id,
+ userId: userId
+ });
+ });
+
+ CardComments.after.remove(function(userId, doc) {
+ var activity = Activities.findOne({ commentId: doc._id });
+ if (activity) {
+ Activities.remove(activity._id);
+ }
+ });
+}