Merge branch 'zarnifoulette-devel' into devel

Fix: Activity user messed up when creating a card using the REST-API.
Thanks to zarnifoulette ! Closes #1045
This commit is contained in:
Lauri Ojansivu 2017-07-19 21:35:50 +03:00
commit 3140067330
3 changed files with 209 additions and 153 deletions

View file

@ -4,7 +4,11 @@ This release adds the following new features:
* [Export and import attachments as base64 encoded files](https://github.com/wekan/wekan/pull/1134); * [Export and import attachments as base64 encoded files](https://github.com/wekan/wekan/pull/1134);
Thanks to GitHub user GhassenRjab for contributions. and fixes the following bugs:
* [Activity user messed up when creating a card using the REST-API](https://github.com/wekan/wekan/pull/1116);
Thanks to GitHub users GhassenRjab and zarnifoulette for their contributions.
# v0.28 2017-07-15 Wekan release # v0.28 2017-07-15 Wekan release

View file

@ -56,6 +56,16 @@ CardComments.helpers({
CardComments.hookOptions.after.update = { fetchPrevious: false }; CardComments.hookOptions.after.update = { fetchPrevious: false };
function commentCreation(userId, doc){
Activities.insert({
userId,
activityType: 'addComment',
boardId: doc.boardId,
cardId: doc.cardId,
commentId: doc._id,
});
}
if (Meteor.isServer) { if (Meteor.isServer) {
// Comments are often fetched within a card, so we create an index to make these // Comments are often fetched within a card, so we create an index to make these
// queries more efficient. // queries more efficient.
@ -64,13 +74,7 @@ if (Meteor.isServer) {
}); });
CardComments.after.insert((userId, doc) => { CardComments.after.insert((userId, doc) => {
Activities.insert({ commentCreation(userId, doc);
userId,
activityType: 'addComment',
boardId: doc.boardId,
cardId: doc.cardId,
commentId: doc._id,
});
}); });
CardComments.after.remove((userId, doc) => { CardComments.after.remove((userId, doc) => {
@ -114,12 +118,16 @@ if (Meteor.isServer) {
Authentication.checkUserId( req.userId); Authentication.checkUserId( req.userId);
const paramBoardId = req.params.boardId; const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId; const paramCardId = req.params.cardId;
const id = CardComments.insert({ const id = CardComments.direct.insert({
userId: req.body.authorId, userId: req.body.authorId,
text: req.body.comment, text: req.body.comment,
cardId: paramCardId, cardId: paramCardId,
boardId: paramBoardId, boardId: paramBoardId,
}); });
const cardComment = CardComments.findOne({_id: id, cardId:paramCardId, boardId: paramBoardId });
commentCreation(req.body.authorId, cardComment);
JsonRoutes.sendResult(res, { JsonRoutes.sendResult(res, {
code: 200, code: 200,
data: { data: {

View file

@ -18,9 +18,9 @@ Cards.attachSchema(new SimpleSchema({
listId: { listId: {
type: String, type: String,
}, },
// The system could work without this `boardId` information (we could deduce // The system could work without this `boardId` information (we could deduce
// the board identifier from the card), but it would make the system more // the board identifier from the card), but it would make the system more
// difficult to manage and less efficient. // difficult to manage and less efficient.
boardId: { boardId: {
type: String, type: String,
}, },
@ -64,8 +64,8 @@ Cards.attachSchema(new SimpleSchema({
type: Date, type: Date,
optional: true, optional: true,
}, },
// XXX Should probably be called `authorId`. Is it even needed since we have // XXX Should probably be called `authorId`. Is it even needed since we have
// the `members` field? // the `members` field?
userId: { userId: {
type: String, type: String,
autoValue() { // eslint-disable-line consistent-return autoValue() { // eslint-disable-line consistent-return
@ -123,26 +123,26 @@ Cards.helpers({
}, },
activities() { activities() {
return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 } }); return Activities.find({cardId: this._id}, {sort: {createdAt: -1}});
}, },
comments() { comments() {
return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 } }); return CardComments.find({cardId: this._id}, {sort: {createdAt: -1}});
}, },
attachments() { attachments() {
return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 } }); return Attachments.find({cardId: this._id}, {sort: {uploadedAt: -1}});
}, },
cover() { cover() {
const cover = Attachments.findOne(this.coverId); 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 // 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? // todo XXX we could return a default "upload pending" image in the meantime?
return cover && cover.url() && cover; return cover && cover.url() && cover;
}, },
checklists() { checklists() {
return Checklists.find({ cardId: this._id }, { sort: { createdAt: 1 } }); return Checklists.find({cardId: this._id}, {sort: {createdAt: 1}});
}, },
checklistItemCount() { checklistItemCount() {
@ -183,35 +183,35 @@ Cards.helpers({
Cards.mutations({ Cards.mutations({
archive() { archive() {
return { $set: { archived: true } }; return {$set: {archived: true}};
}, },
restore() { restore() {
return { $set: { archived: false } }; return {$set: {archived: false}};
}, },
setTitle(title) { setTitle(title) {
return { $set: { title } }; return {$set: {title}};
}, },
setDescription(description) { setDescription(description) {
return { $set: { description } }; return {$set: {description}};
}, },
move(listId, sortIndex) { move(listId, sortIndex) {
const mutatedFields = { listId }; const mutatedFields = {listId};
if (sortIndex) { if (sortIndex) {
mutatedFields.sort = sortIndex; mutatedFields.sort = sortIndex;
} }
return { $set: mutatedFields }; return {$set: mutatedFields};
}, },
addLabel(labelId) { addLabel(labelId) {
return { $addToSet: { labelIds: labelId } }; return {$addToSet: {labelIds: labelId}};
}, },
removeLabel(labelId) { removeLabel(labelId) {
return { $pull: { labelIds: labelId } }; return {$pull: {labelIds: labelId}};
}, },
toggleLabel(labelId) { toggleLabel(labelId) {
@ -223,11 +223,11 @@ Cards.mutations({
}, },
assignMember(memberId) { assignMember(memberId) {
return { $addToSet: { members: memberId } }; return {$addToSet: {members: memberId}};
}, },
unassignMember(memberId) { unassignMember(memberId) {
return { $pull: { members: memberId } }; return {$pull: {members: memberId}};
}, },
toggleMember(memberId) { toggleMember(memberId) {
@ -239,135 +239,159 @@ Cards.mutations({
}, },
setCover(coverId) { setCover(coverId) {
return { $set: { coverId } }; return {$set: {coverId}};
}, },
unsetCover() { unsetCover() {
return { $unset: { coverId: '' } }; return {$unset: {coverId: ''}};
}, },
setStart(startAt) { setStart(startAt) {
return { $set: { startAt } }; return {$set: {startAt}};
}, },
unsetStart() { unsetStart() {
return { $unset: { startAt: '' } }; return {$unset: {startAt: ''}};
}, },
setDue(dueAt) { setDue(dueAt) {
return { $set: { dueAt } }; return {$set: {dueAt}};
}, },
unsetDue() { unsetDue() {
return { $unset: { dueAt: '' } }; 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) => { //FUNCTIONS FOR creation of Activities
function cardMove(userId, doc, fieldNames, oldListId) {
if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) {
Activities.insert({ Activities.insert({
userId, userId,
activityType: 'createCard', oldListId,
boardId: doc.boardId, activityType: 'moveCard',
listId: doc.listId, listId: doc.listId,
boardId: doc.boardId,
cardId: doc._id, cardId: doc._id,
}); });
}); }
}
// New activity for card (un)archivage function cardState(userId, doc, fieldNames) {
Cards.after.update((userId, doc, fieldNames) => { if (_.contains(fieldNames, 'archived')) {
if (_.contains(fieldNames, 'archived')) { if (doc.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({ Activities.insert({
userId, userId,
oldListId, activityType: 'archivedCard',
activityType: 'moveCard', boardId: doc.boardId,
listId: doc.listId, 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, boardId: doc.boardId,
cardId: doc._id, 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 // Say goodbye to the former member
if (modifier.$pull && modifier.$pull.members) { if (modifier.$pull && modifier.$pull.members) {
memberId = modifier.$pull.members; memberId = modifier.$pull.members;
// Check that the former member is member of the card // Check that the former member is member of the card
if (_.contains(doc.members, memberId)) { if (_.contains(doc.members, memberId)) {
Activities.insert({ Activities.insert({
userId, userId,
memberId, memberId,
activityType: 'unjoinMember', activityType: 'unjoinMember',
boardId: doc.boardId, boardId: doc.boardId,
cardId: doc._id, 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,
});
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});
}); });
// Remove all activities associated with a card if we remove the card Cards.after.insert((userId, doc) => {
// Remove also card_comments / checklists / attachments 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) => { Cards.after.remove((userId, doc) => {
Activities.remove({ cardRemover(userId, doc);
cardId: doc._id,
});
Checklists.remove({
cardId: doc._id,
});
CardComments.remove({
cardId: doc._id,
});
Attachments.remove({
cardId: doc._id,
});
}); });
} }
//LISTS REST API //LISTS REST API
@ -375,10 +399,10 @@ if (Meteor.isServer) {
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) { JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) {
const paramBoardId = req.params.boardId; const paramBoardId = req.params.boardId;
const paramListId = req.params.listId; const paramListId = req.params.listId;
Authentication.checkBoardAccess( req.userId, paramBoardId); Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, { JsonRoutes.sendResult(res, {
code: 200, code: 200,
data: Cards.find({ boardId: paramBoardId, listId: paramListId, archived: false }).map(function (doc) { data: Cards.find({boardId: paramBoardId, listId: paramListId, archived: false}).map(function (doc) {
return { return {
_id: doc._id, _id: doc._id,
title: doc.title, title: doc.title,
@ -392,53 +416,69 @@ if (Meteor.isServer) {
const paramBoardId = req.params.boardId; const paramBoardId = req.params.boardId;
const paramListId = req.params.listId; const paramListId = req.params.listId;
const paramCardId = req.params.cardId; const paramCardId = req.params.cardId;
Authentication.checkBoardAccess( req.userId, paramBoardId); Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, { JsonRoutes.sendResult(res, {
code: 200, code: 200,
data: Cards.findOne({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false }), data: Cards.findOne({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}),
}); });
}); });
JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) { JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res, next) {
Authentication.checkUserId( req.userId); Authentication.checkUserId(req.userId);
const paramBoardId = req.params.boardId; const paramBoardId = req.params.boardId;
const paramListId = req.params.listId; const paramListId = req.params.listId;
const id = Cards.insert({ const check = Users.findOne({_id: req.body.authorId});
title: req.body.title, if (typeof check !== 'undefined') {
boardId: paramBoardId, const id = Cards.direct.insert({
listId: paramListId, title: req.body.title,
description: req.body.description, boardId: paramBoardId,
userId : req.body.authorId, listId: paramListId,
sort: 0, description: req.body.description,
members:[ req.body.authorId ], userId: req.body.authorId,
}); sort: 0,
JsonRoutes.sendResult(res, { members: [req.body.authorId],
code: 200, });
data: { JsonRoutes.sendResult(res, {
_id: id, 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, next) { JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res, next) {
Authentication.checkUserId( req.userId); Authentication.checkUserId(req.userId);
const paramBoardId = req.params.boardId; const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId; const paramCardId = req.params.cardId;
const paramListId = req.params.listId; const paramListId = req.params.listId;
if(req.body.hasOwnProperty('title')){
if (req.body.hasOwnProperty('title')) {
const newTitle = req.body.title; const newTitle = req.body.title;
Cards.update({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false }, Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
{$set:{title:newTitle}}); {$set: {title: newTitle}});
} }
if(req.body.hasOwnProperty('listId')){ if (req.body.hasOwnProperty('listId')) {
const newParamListId = req.body.listId; const newParamListId = req.body.listId;
Cards.update({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false }, Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
{$set:{listId:newParamListId}}); {$set: {listId: newParamListId}});
const card = Cards.findOne({_id: paramCardId} );
cardMove(req.body.authorId, card, {fieldName: 'listId'}, paramListId);
} }
if(req.body.hasOwnProperty('description')){ if (req.body.hasOwnProperty('description')) {
const newDescription = req.body.description; const newDescription = req.body.description;
Cards.update({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false }, Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
{$set:{description:newDescription}}); {$set: {description: newDescription}});
} }
JsonRoutes.sendResult(res, { JsonRoutes.sendResult(res, {
code: 200, code: 200,
@ -450,16 +490,20 @@ if (Meteor.isServer) {
JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res, next) { JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res, next) {
Authentication.checkUserId( req.userId); Authentication.checkUserId(req.userId);
const paramBoardId = req.params.boardId; const paramBoardId = req.params.boardId;
const paramListId = req.params.listId; const paramListId = req.params.listId;
const paramCardId = req.params.cardId; const paramCardId = req.params.cardId;
Cards.remove({ _id: paramCardId, listId: paramListId, boardId: paramBoardId });
Cards.direct.remove({_id: paramCardId, listId: paramListId, boardId: paramBoardId});
const card = Cards.find({_id: paramCardId} );
cardRemover(req.body.authorId, card);
JsonRoutes.sendResult(res, { JsonRoutes.sendResult(res, {
code: 200, code: 200,
data: { data: {
_id: paramCardId, _id: paramCardId,
}, },
}); });
}); });
} }