summaryrefslogtreecommitdiffstats
path: root/collections/boards.js
diff options
context:
space:
mode:
authorMaxime Quandalle <maxime@quandalle.com>2015-05-12 19:20:58 +0200
committerMaxime Quandalle <maxime@quandalle.com>2015-05-12 19:33:50 +0200
commit2dbea30842ec63a68055245fe26633bb7913daf3 (patch)
treee9143893a3d3bf4ad34dd3a97d6f3466561c8756 /collections/boards.js
downloadwekan-2dbea30842ec63a68055245fe26633bb7913daf3.tar.gz
wekan-2dbea30842ec63a68055245fe26633bb7913daf3.tar.bz2
wekan-2dbea30842ec63a68055245fe26633bb7913daf3.zip
Renaissance
_,,ad8888888888bba,_ ,ad88888I888888888888888ba, ,88888888I88888888888888888888a, ,d888888888I8888888888888888888888b, d88888PP"""" ""YY88888888888888888888b, ,d88"'__,,--------,,,,.;ZZZY8888888888888, ,8IIl'" ;;l"ZZZIII8888888888, ,I88l;' ;lZZZZZ888III8888888, ,II88Zl;. ;llZZZZZ888888I888888, ,II888Zl;. .;;;;;lllZZZ888888I8888b ,II8888Z;; `;;;;;''llZZ8888888I8888, II88888Z;' .;lZZZ8888888I888b II88888Z; _,aaa, .,aaaaa,__.l;llZZZ88888888I888 II88888IZZZZZZZZZ, .ZZZZZZZZZZZZZZ;llZZ88888888I888, II88888IZZ<'(@@>Z| |ZZZ<'(@@>ZZZZ;;llZZ888888888I88I ,II88888; `""" ;| |ZZ; `""" ;;llZ8888888888I888 II888888l `;; .;llZZ8888888888I888, ,II888888Z; ;;; .;;llZZZ8888888888I888I III888888Zl; .., `;; ,;;lllZZZ88888888888I888 II88888888Z;;...;(_ _) ,;;;llZZZZ88888888888I888, II88888888Zl;;;;;' `--'Z;. .,;;;;llZZZZ88888888888I888b ]I888888888Z;;;;' ";llllll;..;;;lllZZZZ88888888888I8888, II888888888Zl.;;"Y88bd888P";;,..;lllZZZZZ88888888888I8888I II8888888888Zl;.; `"PPP";;;,..;lllZZZZZZZ88888888888I88888 II888888888888Zl;;. `;;;l;;;;lllZZZZZZZZW88888888888I88888 `II8888888888888Zl;. ,;;lllZZZZZZZZWMZ88888888888I88888 II8888888888888888ZbaalllZZZZZZZZZWWMZZZ8888888888I888888, `II88888888888888888b"WWZZZZZWWWMMZZZZZZI888888888I888888b `II88888888888888888;ZZMMMMMMZZZZZZZZllI888888888I8888888 `II8888888888888888 `;lZZZZZZZZZZZlllll888888888I8888888, II8888888888888888, `;lllZZZZllllll;;.Y88888888I8888888b, ,II8888888888888888b .;;lllllll;;;.;..88888888I88888888b, II888888888888888PZI;. .`;;;.;;;..; ...88888888I8888888888, II888888888888PZ;;';;. ;. .;. .;. .. Y8888888I88888888888b, ,II888888888PZ;;' `8888888I8888888888888b, II888888888' 888888I8888888888888888 ,II888888888 ,888888I8888888888888888 ,d88888888888 d888888I8888888888ZZZZZZ ,ad888888888888I 8888888I8888ZZZZZZZZZZZZ 888888888888888' 888888IZZZZZZZZZZZZZZZZZ 8888888888P'8P' Y888ZZZZZZZZZZZZZZZZZZZZ 888888888, " ,ZZZZZZZZZZZZZZZZZZZZZZZ 8888888888, ,ZZZZZZZZZZZZZZZZZZZZZZZZZZ 888888888888a, _ ,ZZZZZZZZZZZZZZZZZZZZ88888888 888888888888888ba,_d' ,ZZZZZZZZZZZZZZZZZ8888888888888 8888888888888888888888bbbaaa,,,______,ZZZZZZZZZZZZZZZ88888888888888888 88888888888888888888888888888888888ZZZZZZZZZZZZZZZ88888888888888888888 8888888888888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888 888888888888888888888888888888888ZZZZZZZZZZZZZZ88888888888888888888888 8888888888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888888 88888888888888888888888888888ZZZZZZZZZZZZZZ888888888888888888888888888 8888888888888888888888888888ZZZZZZZZZZZZZZ88888888888888888 Normand 8 88888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888 Veilleux 8 8888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888888888888
Diffstat (limited to 'collections/boards.js')
-rw-r--r--collections/boards.js251
1 files changed, 251 insertions, 0 deletions
diff --git a/collections/boards.js b/collections/boards.js
new file mode 100644
index 00000000..e406b10c
--- /dev/null
+++ b/collections/boards.js
@@ -0,0 +1,251 @@
+Boards = new Mongo.Collection('boards');
+
+Boards.attachSchema(new SimpleSchema({
+ title: {
+ type: String
+ },
+ slug: {
+ type: String
+ },
+ archived: {
+ type: Boolean
+ },
+ createdAt: {
+ type: Date,
+ denyUpdate: true
+ },
+ // XXX Inconsistent field naming
+ modifiedAt: {
+ type: Date,
+ denyInsert: true,
+ optional: true
+ },
+ // De-normalized number of users that have starred this board
+ stars: {
+ type: Number
+ },
+ // De-normalized label system
+ 'labels.$._id': {
+ // We don't specify that this field must be unique in the board because that
+ // will cause performance penalties and is not necessary since this field is
+ // always set on the server.
+ // XXX Actually if we create a new label, the `_id` is set on the client
+ // without being overwritten by the server, could it be a problem?
+ type: String
+ },
+ 'labels.$.name': {
+ type: String,
+ optional: true
+ },
+ 'labels.$.color': {
+ type: String,
+ allowedValues: [
+ 'green', 'yellow', 'orange', 'red', 'purple',
+ 'blue', 'sky', 'lime', 'pink', 'black'
+ ]
+ },
+ // XXX We might want to maintain more informations under the member sub-
+ // documents like de-normalized meta-data (the date the member joined the
+ // board, the number of contributions, etc.).
+ 'members.$.userId': {
+ type: String
+ },
+ 'members.$.isAdmin': {
+ type: Boolean
+ },
+ 'members.$.isActive': {
+ type: Boolean
+ },
+ permission: {
+ type: String,
+ allowedValues: ['public', 'private']
+ },
+ color: {
+ type: String,
+ allowedValues: ['nephritis', 'pomegranate', 'belize',
+ 'wisteria', 'midnight', 'pumpkin']
+ }
+}));
+
+if (Meteor.isServer) {
+ Boards.allow({
+ insert: Meteor.userId,
+ update: allowIsBoardAdmin,
+ remove: allowIsBoardAdmin,
+ fetch: ['members']
+ });
+
+ // The number of users that have starred this board is managed by trusted code
+ // and the user is not allowed to update it
+ Boards.deny({
+ update: function(userId, board, fieldNames) {
+ return _.contains(fieldNames, 'stars');
+ },
+ fetch: []
+ });
+
+ // We can't remove a member if it is the last administrator
+ Boards.deny({
+ update: function(userId, doc, fieldNames, modifier) {
+ if (! _.contains(fieldNames, 'members'))
+ return false;
+
+ // We only care in case of a $pull operation, ie remove a member
+ if (! _.isObject(modifier.$pull && modifier.$pull.members))
+ return false;
+
+ // If there is more than one admin, it's ok to remove anyone
+ var nbAdmins = _.filter(doc.members, function(member) {
+ return member.isAdmin;
+ }).length;
+ if (nbAdmins > 1)
+ return false;
+
+ // If all the previous conditions where verified, we can't remove
+ // a user if it's an admin
+ var removedMemberId = modifier.$pull.members.userId;
+ return !! _.findWhere(doc.members, {
+ userId: removedMemberId,
+ isAdmin: true
+ });
+ },
+ fetch: ['members']
+ });
+}
+
+Boards.helpers({
+ isPublic: function() {
+ return this.permission === 'public';
+ },
+ lists: function() {
+ return Lists.find({ boardId: this._id, archived: false },
+ { sort: { sort: 1 }});
+ },
+ activities: function() {
+ return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }});
+ },
+ absoluteUrl: function() {
+ return Router.path('Board', { boardId: this._id, slug: this.slug });
+ },
+ colorClass: function() {
+ return 'board-color-' + this.color;
+ }
+});
+
+Boards.before.insert(function(userId, doc) {
+ // XXX We need to improve slug management. Only the id should be necessary
+ // to identify a board in the code.
+ // XXX If the board title is updated, the slug should also be updated.
+ // In some cases (Chinese and Japanese for instance) the `getSlug` function
+ // return an empty string. This is causes bugs in our application so we set
+ // a default slug in this case.
+ doc.slug = getSlug(doc.title) || 'board';
+ doc.createdAt = new Date();
+ doc.archived = false;
+ doc.members = [{
+ userId: userId,
+ isAdmin: true,
+ isActive: true
+ }];
+ doc.stars = 0;
+ doc.color = Boards.simpleSchema()._schema.color.allowedValues[0];
+
+ // Handle labels
+ var colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
+ var defaultLabelsColors = _.clone(colors).splice(0, 6);
+ doc.labels = [];
+ _.each(defaultLabelsColors, function(val) {
+ doc.labels.push({
+ _id: Random.id(6),
+ name: '',
+ color: val
+ });
+ });
+
+ // We randomly chose one of the default background colors for the board
+ if (Meteor.isClient) {
+ doc.background = {
+ type: 'color',
+ color: Random.choice(Boards.simpleSchema()._schema.color.allowedValues)
+ };
+ }
+});
+
+Boards.before.update(function(userId, doc, fieldNames, modifier) {
+ modifier.$set = modifier.$set || {};
+ modifier.$set.modifiedAt = new Date();
+});
+
+if (Meteor.isServer) {
+ // Let MongoDB ensure that a member is not included twice in the same board
+ Meteor.startup(function() {
+ Boards._collection._ensureIndex({
+ _id: 1,
+ 'members.userId': 1
+ }, { unique: true });
+ });
+
+ // Genesis: the first activity of the newly created board
+ Boards.after.insert(function(userId, doc) {
+ Activities.insert({
+ type: 'board',
+ activityTypeId: doc._id,
+ activityType: 'createBoard',
+ boardId: doc._id,
+ userId: userId
+ });
+ });
+
+ // If the user remove one label from a board, we cant to remove reference of
+ // this label in any card of this board.
+ Boards.after.update(function(userId, doc, fieldNames, modifier) {
+ if (! _.contains(fieldNames, 'labels') ||
+ ! modifier.$pull ||
+ ! modifier.$pull.labels ||
+ ! modifier.$pull.labels._id)
+ return;
+
+ var removedLabelId = modifier.$pull.labels._id;
+ Cards.update(
+ { boardId: doc._id },
+ {
+ $pull: {
+ labels: removedLabelId
+ }
+ },
+ { multi: true }
+ );
+ });
+
+ // Add a new activity if we add or remove a member to the board
+ Boards.after.update(function(userId, doc, fieldNames, modifier) {
+ if (! _.contains(fieldNames, 'members'))
+ return;
+
+ var memberId;
+
+ // Say hello to the new member
+ if (modifier.$push && modifier.$push.members) {
+ memberId = modifier.$push.members.userId;
+ Activities.insert({
+ type: 'member',
+ activityType: 'addBoardMember',
+ boardId: doc._id,
+ userId: userId,
+ memberId: memberId
+ });
+ }
+
+ // Say goodbye to the former member
+ if (modifier.$pull && modifier.$pull.members) {
+ memberId = modifier.$pull.members.userId;
+ Activities.insert({
+ type: 'member',
+ activityType: 'removeBoardMember',
+ boardId: doc._id,
+ userId: userId,
+ memberId: memberId
+ });
+ }
+ });
+}