Centralize all mutations at the model level

This commit uses a new package that I need to document. It tries to
solve the long-standing debate in the Meteor community about
allow/deny rules versus methods (RPC).

This approach gives us both the centralized security rules of
allow/deny and the white-list of allowed mutations similarly to Meteor
methods. The idea to have static mutation descriptions is also
inspired by Facebook's Relay/GraphQL.

This will allow the development of a REST API using the high-level
methods instead of the MongoDB queries to do the mapping between the
HTTP requests and our collections.
This commit is contained in:
Maxime Quandalle 2015-09-08 20:19:42 +02:00
parent c04341f1ea
commit 45b662a1dd
26 changed files with 395 additions and 377 deletions

View file

@ -109,14 +109,6 @@ EscapeActions.register('sidebarView',
() => { return Sidebar && Sidebar.getView() !== defaultView; }
);
function getMemberIndex(board, searchId) {
for (let i = 0; i < board.members.length; i++) {
if (board.members[i].userId === searchId)
return i;
}
throw new Meteor.Error('Member not found');
}
Template.memberPopup.helpers({
user() {
return Users.findOne(this.userId);
@ -135,13 +127,8 @@ Template.memberPopup.events({
'click .js-change-role': Popup.open('changePermissions'),
'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const memberIndex = getMemberIndex(currentBoard, this.userId);
Boards.update(currentBoard._id, {
$set: {
[`members.${memberIndex}.isActive`]: false,
},
});
const memberId = this.userId;
currentBoard.removeMember(memberId);
Popup.close();
}),
'click .js-leave-member'() {
@ -209,26 +196,7 @@ Template.addMemberPopup.events({
'click .js-select-member'() {
const userId = this._id;
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const currentMembersIds = _.pluck(currentBoard.members, 'userId');
if (currentMembersIds.indexOf(userId) === -1) {
Boards.update(currentBoard._id, {
$push: {
members: {
userId,
isAdmin: false,
isActive: true,
},
},
});
} else {
const memberIndex = getMemberIndex(currentBoard, userId);
Boards.update(currentBoard._id, {
$set: {
[`members.${memberIndex}.isActive`]: true,
},
});
}
currentBoard.addMember(userId);
Popup.close();
},
});
@ -240,14 +208,9 @@ Template.addMemberPopup.onRendered(function() {
Template.changePermissionsPopup.events({
'click .js-set-admin, click .js-set-normal'(event) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const memberIndex = getMemberIndex(currentBoard, this.userId);
const memberId = this.userId;
const isAdmin = $(event.currentTarget).hasClass('js-set-admin');
Boards.update(currentBoard._id, {
$set: {
[`members.${memberIndex}.isAdmin`]: isAdmin,
},
});
currentBoard.setMemberPermission(memberId, isAdmin);
Popup.back(1);
},
});

View file

@ -29,8 +29,8 @@ BlazeComponent.extendComponent({
events() {
return [{
'click .js-restore-card'() {
const cardId = this.currentData()._id;
Cards.update(cardId, {$set: {archived: false}});
const card = this.currentData();
card.restore();
},
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
const cardId = this._id;
@ -38,8 +38,8 @@ BlazeComponent.extendComponent({
Popup.close();
}),
'click .js-restore-list'() {
const listId = this.currentData()._id;
Lists.update(listId, {$set: {archived: false}});
const list = this.currentData();
list.restore();
},
}];
},

View file

@ -30,9 +30,9 @@ BlazeComponent.extendComponent({
},
}).register('filterSidebar');
function updateSelectedCards(query) {
function mutateSelectedCards(mutationName, ...args) {
Cards.find(MultiSelection.getMongoSelector()).forEach((card) => {
Cards.update(card._id, query);
card[mutationName](...args);
});
}
@ -67,47 +67,34 @@ BlazeComponent.extendComponent({
'click .js-toggle-label-multiselection'(evt) {
const labelId = this.currentData()._id;
const mappedSelection = this.mapSelection('label', labelId);
let operation;
if (_.every(mappedSelection))
operation = '$pull';
else if (_.every(mappedSelection, (bool) => !bool))
operation = '$addToSet';
else {
if (_.every(mappedSelection)) {
mutateSelectedCards('addLabel', labelId);
} else if (_.every(mappedSelection, (bool) => !bool)) {
mutateSelectedCards('removeLabel', labelId);
} else {
const popup = Popup.open('disambiguateMultiLabel');
// XXX We need to have a better integration between the popup and the
// UI components systems.
return popup.call(this.currentData(), evt);
}
updateSelectedCards({
[operation]: {
labelIds: labelId,
},
});
},
'click .js-toggle-member-multiselection'(evt) {
const memberId = this.currentData()._id;
const mappedSelection = this.mapSelection('member', memberId);
let operation;
if (_.every(mappedSelection))
operation = '$pull';
else if (_.every(mappedSelection, (bool) => !bool))
operation = '$addToSet';
else {
if (_.every(mappedSelection)) {
mutateSelectedCards('assignMember', memberId);
} else if (_.every(mappedSelection, (bool) => !bool)) {
mutateSelectedCards('unassignMember', memberId);
} else {
const popup = Popup.open('disambiguateMultiMember');
// XXX We need to have a better integration between the popup and the
// UI components systems.
return popup.call(this.currentData(), evt);
}
updateSelectedCards({
[operation]: {
members: memberId,
},
});
},
'click .js-archive-selection'() {
updateSelectedCards({$set: {archived: true}});
mutateSelectedCards('archive');
},
}];
},
@ -115,22 +102,22 @@ BlazeComponent.extendComponent({
Template.disambiguateMultiLabelPopup.events({
'click .js-remove-label'() {
updateSelectedCards({$pull: {labelIds: this._id}});
mutateSelectedCards('removeLabel', this._id);
Popup.close();
},
'click .js-add-label'() {
updateSelectedCards({$addToSet: {labelIds: this._id}});
mutateSelectedCards('addLabel', this._id);
Popup.close();
},
});
Template.disambiguateMultiMemberPopup.events({
'click .js-unassign-member'() {
updateSelectedCards({$pull: {members: this._id}});
mutateSelectedCards('assignMember', this._id);
Popup.close();
},
'click .js-assign-member'() {
updateSelectedCards({$addToSet: {members: this._id}});
mutateSelectedCards('unassignMember', this._id);
Popup.close();
},
});