Merge #616 into devel

This commit is contained in:
Maxime Quandalle 2016-07-18 15:40:41 +02:00
commit 4f5cecf738
9 changed files with 242 additions and 105 deletions

View file

@ -74,6 +74,7 @@
"Avatars": true,
"BlazeComponent": false,
"BlazeLayout": false,
"CollectionHooks": false,
"DocHead": false,
"ESSearchResults": false,
"FastRender": false,

View file

@ -9,9 +9,7 @@ BlazeComponent.extendComponent({
},
labels() {
return labelColors.map((color) => {
return { color, name: '' };
});
return labelColors.map((color) => ({ color, name: '' }));
},
isSelected(color) {

View file

@ -294,5 +294,8 @@
"watch": "Watch",
"watching": "Watching",
"watching-info": "You will be notified of any change in this board",
"welcome-board": "Welcome Board",
"welcome-list1": "Basics",
"welcome-list2": "Advanced",
"what-to-do": "What do you want to do?"
}

View file

@ -6,25 +6,77 @@ Boards.attachSchema(new SimpleSchema({
},
slug: {
type: String,
autoValue() { // eslint-disable-line consistent-return
// 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.
if (this.isInsert && !this.isSet) {
let slug = 'board';
const title = this.field('title');
if (title.isSet) {
slug = getSlug(title.value) || slug;
}
return slug;
}
},
},
archived: {
type: Boolean,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return false;
}
},
},
createdAt: {
type: Date,
denyUpdate: true,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert) {
return new Date();
} else {
this.unset();
}
},
},
// XXX Inconsistent field naming
modifiedAt: {
type: Date,
denyInsert: true,
optional: true,
autoValue() { // eslint-disable-line consistent-return
if (this.isUpdate) {
return new Date();
} else {
this.unset();
}
},
},
// De-normalized number of users that have starred this board
stars: {
type: Number,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert) {
return 0;
}
},
},
// De-normalized label system
'labels': {
type: [Object],
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
const colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
const defaultLabelsColors = _.clone(colors).splice(0, 6);
return defaultLabelsColors.map((color) => ({
color,
_id: Random.id(6),
name: '',
}));
}
},
},
'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
@ -47,6 +99,19 @@ Boards.attachSchema(new SimpleSchema({
// 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': {
type: [Object],
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return [{
userId: this.userId,
isAdmin: true,
isActive: true,
isInvited: false,
}];
}
},
},
'members.$.userId': {
type: String,
},
@ -70,6 +135,11 @@ Boards.attachSchema(new SimpleSchema({
'wisteria',
'midnight',
],
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return Boards.simpleSchema()._schema.color.allowedValues[0];
}
},
},
description: {
type: String,
@ -338,41 +408,6 @@ if (Meteor.isServer) {
});
}
Boards.before.insert((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 = doc.slug || getSlug(doc.title) || 'board';
doc.createdAt = new Date();
doc.archived = false;
doc.members = doc.members || [{
userId,
isAdmin: true,
isActive: true,
}];
doc.stars = 0;
doc.color = Boards.simpleSchema()._schema.color.allowedValues[0];
// Handle labels
const colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
const defaultLabelsColors = _.clone(colors).splice(0, 6);
doc.labels = defaultLabelsColors.map((color) => {
return {
color,
_id: Random.id(6),
name: '',
};
});
});
Boards.before.update((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(() => {

View file

@ -16,10 +16,22 @@ CardComments.attachSchema(new SimpleSchema({
createdAt: {
type: Date,
denyUpdate: false,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert) {
return new Date();
} else {
this.unset();
}
},
},
// XXX Should probably be called `authorId`
userId: {
type: String,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return this.userId;
}
},
},
}));
@ -44,11 +56,6 @@ CardComments.helpers({
CardComments.hookOptions.after.update = { fetchPrevious: false };
CardComments.before.insert((userId, doc) => {
doc.createdAt = new Date();
doc.userId = userId;
});
if (Meteor.isServer) {
CardComments.after.insert((userId, doc) => {
Activities.insert({

View file

@ -9,6 +9,11 @@ Cards.attachSchema(new SimpleSchema({
},
archived: {
type: Boolean,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return false;
}
},
},
listId: {
type: String,
@ -25,10 +30,19 @@ Cards.attachSchema(new SimpleSchema({
},
createdAt: {
type: Date,
denyUpdate: true,
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,
@ -46,6 +60,11 @@ Cards.attachSchema(new SimpleSchema({
// the `members` field?
userId: {
type: String,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return this.userId;
}
},
},
sort: {
type: Number,
@ -190,17 +209,6 @@ Cards.mutations({
},
});
Cards.before.insert((userId, doc) => {
doc.createdAt = new Date();
doc.dateLastActivity = new Date();
if(!doc.hasOwnProperty('archived')){
doc.archived = false;
}
if (!doc.userId) {
doc.userId = userId;
}
});
if (Meteor.isServer) {
Cards.after.insert((userId, doc) => {
Activities.insert({

View file

@ -6,13 +6,24 @@ Lists.attachSchema(new SimpleSchema({
},
archived: {
type: Boolean,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return false;
}
},
},
boardId: {
type: String,
},
createdAt: {
type: Date,
denyUpdate: true,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert) {
return new Date();
} else {
this.unset();
}
},
},
sort: {
type: Number,
@ -22,8 +33,14 @@ Lists.attachSchema(new SimpleSchema({
},
updatedAt: {
type: Date,
denyInsert: true,
optional: true,
autoValue() { // eslint-disable-line consistent-return
if (this.isUpdate) {
return new Date();
} else {
this.unset();
}
},
},
}));
@ -73,18 +90,6 @@ Lists.mutations({
Lists.hookOptions.after.update = { fetchPrevious: false };
Lists.before.insert((userId, doc) => {
doc.createdAt = new Date();
doc.archived = false;
if (!doc.userId)
doc.userId = userId;
});
Lists.before.update((userId, doc, fieldNames, modifier) => {
modifier.$set = modifier.$set || {};
modifier.$set.modifiedAt = new Date();
});
if (Meteor.isServer) {
Lists.after.insert((userId, doc) => {
Activities.insert({

View file

@ -14,6 +14,11 @@ UnsavedEditCollection.attachSchema(new SimpleSchema({
},
userId: {
type: String,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return this.userId;
}
},
},
}));
@ -28,7 +33,3 @@ if (Meteor.isServer) {
fetch: ['userId'],
});
}
UnsavedEditCollection.before.insert((userId, doc) => {
doc.userId = userId;
});

View file

@ -1,5 +1,95 @@
Users = Meteor.users;
Users.attachSchema(new SimpleSchema({
username: {
type: String,
optional: true,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
const name = this.field('profile.fullname');
if (name.isSet) {
return name.value.toLowerCase().replace(/\s/g, '');
}
}
},
},
emails: {
type: [Object],
optional: true,
},
'emails.$.address': {
type: String,
regEx: SimpleSchema.RegEx.Email,
},
'emails.$.verified': {
type: Boolean,
},
createdAt: {
type: Date,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert) {
return new Date();
} else {
this.unset();
}
},
},
profile: {
type: Object,
optional: true,
autoValue() { // eslint-disable-line consistent-return
if (this.isInsert && !this.isSet) {
return {};
}
},
},
'profile.avatarUrl': {
type: String,
optional: true,
},
'profile.emailBuffer': {
type: [String],
optional: true,
},
'profile.fullname': {
type: String,
optional: true,
},
'profile.initials': {
type: String,
optional: true,
},
'profile.invitedBoards': {
type: [String],
optional: true,
},
'profile.language': {
type: String,
optional: true,
},
'profile.notifications': {
type: [String],
optional: true,
},
'profile.starredBoards': {
type: [String],
optional: true,
},
'profile.tags': {
type: [String],
optional: true,
},
services: {
type: Object,
optional: true,
blackbox: true,
},
heartbeat: {
type: Date,
optional: true,
},
}));
// Search a user in the complete server database by its name or username. This
// is used for instance to add a new user to a board.
const searchInFields = ['username', 'profile.fullname'];
@ -259,14 +349,6 @@ if (Meteor.isServer) {
});
}
Users.before.insert((userId, doc) => {
doc.profile = doc.profile || {};
if (!doc.username && doc.profile.fullname) {
doc.username = doc.profile.fullname.toLowerCase().replace(/\s/g, '');
}
});
if (Meteor.isServer) {
// Let mongoDB ensure username unicity
Meteor.startup(() => {
@ -306,32 +388,29 @@ if (Meteor.isServer) {
incrementBoards(_.difference(newIds, oldIds), +1);
});
// XXX i18n
const fakeUserId = new Meteor.EnvironmentVariable();
const getUserId = CollectionHooks.getUserId;
CollectionHooks.getUserId = () => {
return fakeUserId.get() || getUserId();
};
Users.after.insert((userId, doc) => {
const ExampleBoard = {
title: 'Welcome Board',
userId: doc._id,
permission: 'private',
const fakeUser = {
extendAutoValueContext: {
userId: doc._id,
},
};
// Insert the Welcome Board
Boards.insert(ExampleBoard, (err, boardId) => {
fakeUserId.withValue(doc._id, () => {
// Insert the Welcome Board
Boards.insert({
title: TAPi18n.__('welcome-board'),
permission: 'private',
}, fakeUser, (err, boardId) => {
['Basics', 'Advanced'].forEach((title) => {
const list = {
title,
boardId,
userId: ExampleBoard.userId,
// XXX Not certain this is a bug, but we except these fields get
// inserted by the Lists.before.insert collection-hook. Since this
// hook is not called in this case, we have to dublicate the logic and
// set them here.
archived: false,
createdAt: new Date(),
};
Lists.insert(list);
['welcome-list1', 'welcome-list2'].forEach((title) => {
Lists.insert({ title: TAPi18n.__(title), boardId }, fakeUser);
});
});
});
});