Skip to content

Commit

Permalink
Permissions handling for slides (towards #3)
Browse files Browse the repository at this point in the history
  • Loading branch information
blahah committed Feb 13, 2016
1 parent f0655aa commit ab5cb69
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 56 deletions.
1 change: 1 addition & 0 deletions slidewinder/.meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ mquandalle:bower
jparker:crypto-sha1
meteorhacks:picker
easysearch:elasticsearch
aldeed:simple-schema
2 changes: 2 additions & 0 deletions slidewinder/.meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ [email protected]
[email protected]
[email protected]
ahref:[email protected]
aldeed:[email protected]
[email protected]
[email protected]_1
[email protected]
Expand Down Expand Up @@ -67,6 +68,7 @@ [email protected]
[email protected]
lyuyea:[email protected]
materialize:[email protected]
mdg:[email protected]
[email protected]
[email protected]
meteorhacks:[email protected]
Expand Down
62 changes: 43 additions & 19 deletions slidewinder/client/js/slides.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ Template.slides.helpers({
slidesIndex: function() { return SlideIndex; }
});


Template.slides.events({
'click #new-slide-btn': function() {
FlowRouter.go('/slides/create');
},
'click .edit-slide-btn': function() {
FlowRouter.go('/slides/edit/' + this.__originalId);
// only the owner can edit a slide - also enforced in the router,
// and on the server
if (Meteor.userId() === this.owner) {
FlowRouter.go('/slides/edit/' + this.__originalId);
} else {
Materialize.toast("You can only edit slides that belong to you.", 4000, 'flash-err');
}
},
'click .delete-slide-btn': function() {
var div = $('<div>')
Expand All @@ -26,21 +31,22 @@ Template.slides.events({
div.appendTo(card);
},
'click .fave-slide-btn': function() {
var filter = { _id: this.__originalId, };
var update = {};
if (!(_.isArray(this.faves))) {
this.faves = [];
update.$set = { faves: [Meteor.userId()] };
} else if (_.include(this.faves, Meteor.userId())) {
update.$pull = { faves: Meteor.userId() };
} else {
update.$push = { faves: Meteor.userId() };
}
Slides.update(filter, update);
Slides.methods.toggleFave.call({
slideId: this.__originalId
}, (err, res) => {
if (err) {
Materialize.toast(err, 4000, 'flash-err');
}
});
},
'click .confirm-slide-del-btn': function() {
var filter = { _id: this.__originalId, };
Slides.remove(filter);
Slides.methods.removeSlide.call({
slideId: this.__originalId
}, (err, res) => {
if (err) {
Materialize.toast(err, 4000, 'flash-err');
}
});
},
'click .cancel-slide-del-btn': function() {
$('#' + this.__originalId).find('.confirm-delete').remove();
Expand Down Expand Up @@ -87,7 +93,6 @@ Template.slide_image_card.helpers({
}
})


Template.create_slide_sidebar.helpers({
templates: function() {
return templates;
Expand Down Expand Up @@ -206,14 +211,25 @@ Template.create_slide.events({
if ($('a.fa.fa-eye').hasClass('active')) {
var slidedata = getSlideData();
var author = Meteor.user().profile.name;
Meteor.call('renderSlide', author, slidedata, showSlidePreview);
Slides.methods.renderSlide.call(
{ author: author, slidedata: slidedata },
showSlidePreview
);
}
return false;
},
'click #save_slide_btn': function(e) {
var slidedata = getSlideData();
Meteor.call('saveSlide', slidedata);
FlowRouter.go('/slides');
Slides.methods.saveSlide.call({
slidedata: slidedata
}, (err, res) => {
if (err) {
Materialize.toast(err, 4000, 'flash-err');
setTimeout(function(){ FlowRouter.go('/slides') }, 2000);
} else {
FlowRouter.go('/slides');
}
});
}
})

Expand All @@ -238,13 +254,21 @@ Template.edit_slide.onRendered(function() {
// load the card for editing
var slide_id = FlowRouter.getParam("slideId");
var res = Slides.find(slide_id);

if (res.count() == 0) {
Materialize.toast("The slide you're trying to edit doesn't exist :(", 4000, 'flash-err');
setTimeout(function(){ FlowRouter.go('/slides') }, 2000);
return;
}

var slide = res.fetch()[0];

if (Meteor.userId() != slide.owner) {
Materialize.toast("You can only edit slides that belong to you.", 4000, 'flash-err');
setTimeout(function(){ FlowRouter.go('/slides') }, 2000);
return;
}

setSlideData(slide);

});
1 change: 0 additions & 1 deletion slidewinder/client/js/slidewinder.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Deps.autorun(function(){

Session.set('pagetitle', 'slidewinder')

Slides = new Mongo.Collection('slides');
Decks = new Mongo.Collection('decks');

Tracker.autorun(function () {
Expand Down
208 changes: 208 additions & 0 deletions slidewinder/lib/slides.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
Slides = new Mongo.Collection('slides');

var slidewinder = {}

if (Meteor.isServer) {
slidewinder = Meteor.npmRequire('slidewinder');
} else {
slidewinder = {
slide: function(a) {
return {};
}
}
}

// Deny all client-side updates on all collections
Slides.deny({
insert() { return true; },
update() { return true; },
remove() { return true; },
});

// Define a namespace for Methods related to the Slides collection
// Allows overriding for tests by replacing the implementation (2)
Slides.methods = {};

// Render a slide as a preview. Given a username and a slide data object,
// create a full slide and use it to populate a dummy deck. Finally,
// render the dummy deck and pass the rendered HTML back to the client.
Slides.methods.renderSlide = {
name: 'Slides.methods.renderSlide',

validate(args) {
return true;
},

run({ username, slidedata }) {
if (Meteor.isServer) {
// create a full slide object from the slide data
var s = new slidewinder.slide(slidedata);
// and a dummy deck to render it in
var d = new slidewinder.deck(null, {
author: username,
title: 'slide preview',
tags: []
}, 'remark');

// store the rendered deck HTML
html = ''
d.preprocess.call(d, [s], function() {
d.render.call(d, function(){
html = d.renderedDeck;
});
});

// pass it back to the client
return html;
}
},

// this is part of the Meteor Methods advanced boilerplate
// see http://guide.meteor.com/methods.html#advanced-boilerplate
call(args, callback) {
const options = {
returnStubValue: true,
throwStubExceptions: true
}

Meteor.apply(this.name, [args], options, callback);
}
};

// Save a slide that has either been newly created or edited. If this is
// an edit, we only proceed if the user is the owner of the slide.
Slides.methods.saveSlide = {
name: 'Slides.methods.saveSlide',

validate(args) {
return true;
},

run({ slidedata }) {
if (Meteor.isServer) {

// create a full slide object from the data
// this takes care of basic housekeeping like ensuring the slide
// has an ID
var s = new slidewinder.slide(slidedata);

// because some slides are public, we need to make sure that if this
// is an edit operation, the user trying to edit is the owner.
var res = Slides.find(s._id);
if (res.count() > 0) {
var isOwner = res.fetch()[0].owner == this.userId;
if (!isOwner) {
throw new Meteor.Error(
'Slides.methods.saveSlide.unauthorized',
"Cannot edit slides that don't belong to you"
);
}
}

// update the Slides collection with this slide. We use the
// upsert operation to handle both edits and insertions in a single
// command.
Slides.update({ _id: s._id }, s, { upsert: true });

}

return slidedata.owner === this.userId;
},

// this is part of the Meteor Methods advanced boilerplate
// see http://guide.meteor.com/methods.html#advanced-boilerplate
call(args, callback) {
const options = {
returnStubValue: true,
throwStubExceptions: true
}

Meteor.apply(this.name, [args], options, callback);
}
};

// Remove a slide, only if it belongs to this user
Slides.methods.removeSlide = {
name: 'Slides.methods.removeSlide',

validate(args) {
new SimpleSchema({
slideId: { type: String }
}).validate(args)
},

run({ slideId }) {

Slides.remove({ _id: slideId, owner: this.userId });

},

// this is part of the Meteor Methods advanced boilerplate
// see http://guide.meteor.com/methods.html#advanced-boilerplate
call(args, callback) {
const options = {
returnStubValue: true,
throwStubExceptions: true
}

Meteor.apply(this.name, [args], options, callback);
}
};

// Toggle whether a given slide is favourited by this user
Slides.methods.toggleFave = {
name: 'Slides.methods.toggleFave',

validate(args) {
new SimpleSchema({
slideId: { type: String }
}).validate(args)
},

run({ slideId }) {

var slide = Slides.findOne(slideId);
var update = {};
if (!(_.isArray(slide.faves))) {
slide.faves = [];
update.$set = { faves: [this.userId] };
} else if (_.include(slide.faves, this.userId)) {
update.$pull = { faves: this.userId };
} else {
update.$push = { faves: this.userId };
}

Slides.update({ _id: slideId }, update);
},

// this is part of the Meteor Methods advanced boilerplate
// see http://guide.meteor.com/methods.html#advanced-boilerplate
call(args, callback) {
const options = {
returnStubValue: true,
throwStubExceptions: true
}

Meteor.apply(this.name, [args], options, callback);
}
};

// Register the methods with Meteor's DDP system
Meteor.methods({
[Slides.methods.renderSlide.name]: function (args) {
Slides.methods.renderSlide.validate.call(this, args);
Slides.methods.renderSlide.run.call(this, args);
},
[Slides.methods.saveSlide.name]: function (args) {
Slides.methods.saveSlide.validate.call(this, args);
Slides.methods.saveSlide.run.call(this, args);
},
[Slides.methods.removeSlide.name]: function (args) {
Slides.methods.removeSlide.validate.call(this, args);
Slides.methods.removeSlide.run.call(this, args);
},
[Slides.methods.toggleFave.name]: function (args) {
Slides.methods.toggleFave.validate.call(this, args);
Slides.methods.toggleFave.run.call(this, args);
}
});
13 changes: 13 additions & 0 deletions slidewinder/server/slides.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Publish the collection, optionally giving access to all public slides
// or only the user's own slides.
Meteor.publish("slides", function (everyone) {
var cond = [
{ owner: this.userId }
]
if (everyone) {
cond.push({ private: false });
}
return Slides.find({
$or: cond
});
});
Loading

0 comments on commit ab5cb69

Please sign in to comment.