mirror of
https://github.com/vale981/Vulcan
synced 2025-03-09 04:16:37 -04:00

Using {{pathFor}}, path(), and url() where possible. Passing in path to Meteor.absoluteUrl() where the IronRouter functions didn't make sense. Also deleting some random unused code.
516 lines
14 KiB
JavaScript
516 lines
14 KiB
JavaScript
|
|
// ------------------------------------------------------------------------------------------- //
|
|
// ----------------------------------------- Schema ----------------------------------------- //
|
|
// ------------------------------------------------------------------------------------------- //
|
|
|
|
SimpleSchema.extendOptions({
|
|
editable: Match.Optional(Boolean) // editable: true means the field can be edited by the document's owner
|
|
});
|
|
|
|
postSchemaObject = {
|
|
_id: {
|
|
type: String,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
createdAt: {
|
|
type: Date,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
postedAt: {
|
|
type: Date,
|
|
optional: true,
|
|
autoform: {
|
|
group: 'admin',
|
|
type: "bootstrap-datetimepicker"
|
|
}
|
|
},
|
|
url: {
|
|
type: String,
|
|
label: "URL",
|
|
optional: true,
|
|
editable: true,
|
|
autoform: {
|
|
editable: true,
|
|
type: "bootstrap-url"
|
|
}
|
|
},
|
|
title: {
|
|
type: String,
|
|
optional: false,
|
|
label: "Title",
|
|
editable: true,
|
|
autoform: {
|
|
editable: true
|
|
}
|
|
},
|
|
body: {
|
|
type: String,
|
|
optional: true,
|
|
editable: true,
|
|
autoform: {
|
|
editable: true,
|
|
rows: 5
|
|
}
|
|
},
|
|
htmlBody: {
|
|
type: String,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
viewCount: {
|
|
type: Number,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
commentCount: {
|
|
type: Number,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
commenters: {
|
|
type: [String],
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
lastCommentedAt: {
|
|
type: Date,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
clickCount: {
|
|
type: Number,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
baseScore: {
|
|
type: Number,
|
|
decimal: true,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
upvotes: {
|
|
type: Number,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
upvoters: {
|
|
type: [String], // XXX
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
downvotes: {
|
|
type: Number,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
downvoters: {
|
|
type: [String], // XXX
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
score: {
|
|
type: Number,
|
|
decimal: true,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
status: {
|
|
type: Number,
|
|
optional: true,
|
|
autoValue: function () {
|
|
return getSetting('requirePostsApproval', false) ? STATUS_PENDING: STATUS_APPROVED
|
|
},
|
|
autoform: {
|
|
noselect: true,
|
|
options: postStatuses,
|
|
group: 'admin'
|
|
}
|
|
},
|
|
sticky: {
|
|
type: Boolean,
|
|
optional: true,
|
|
autoform: {
|
|
group: 'admin',
|
|
leftLabel: "Sticky"
|
|
}
|
|
},
|
|
inactive: {
|
|
type: Boolean,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
author: {
|
|
type: String,
|
|
optional: true,
|
|
autoform: {
|
|
omit: true
|
|
}
|
|
},
|
|
userId: {
|
|
type: String, // XXX
|
|
optional: true,
|
|
autoform: {
|
|
group: 'admin',
|
|
options: function () {
|
|
return Meteor.users.find().map(function (user) {
|
|
return {
|
|
value: user._id,
|
|
label: getDisplayName(user)
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// add any extra properties to postSchemaObject (provided by packages for example)
|
|
_.each(addToPostSchema, function(item){
|
|
postSchemaObject[item.propertyName] = item.propertySchema;
|
|
});
|
|
|
|
Posts = new Meteor.Collection("posts");
|
|
|
|
PostSchema = new SimpleSchema(postSchemaObject);
|
|
Posts.attachSchema(PostSchema);
|
|
|
|
// Posts.deny({
|
|
// update: function(userId, post, fieldNames) {
|
|
// if(isAdminById(userId))
|
|
// return false;
|
|
// // deny the update if it contains something other than the following fields
|
|
// return (_.without(fieldNames, 'title', 'url', 'body', 'shortUrl', 'shortTitle', 'categories').length > 0);
|
|
// }
|
|
// });
|
|
|
|
// Posts.allow({
|
|
// update: canEditById,
|
|
// remove: canEditById
|
|
// });
|
|
|
|
// ------------------------------------------------------------------------------------------- //
|
|
// ----------------------------------------- Helpers ----------------------------------------- //
|
|
// ------------------------------------------------------------------------------------------- //
|
|
|
|
getPostProperties = function (post) {
|
|
|
|
var postAuthor = Meteor.users.findOne(post.userId);
|
|
var p = {
|
|
postAuthorName : getDisplayName(postAuthor),
|
|
postTitle : cleanUp(post.title),
|
|
profileUrl: getProfileUrlBySlugOrId(post.userId),
|
|
postUrl: getPostPageUrl(post),
|
|
thumbnailUrl: post.thumbnailUrl,
|
|
linkUrl: !!post.url ? getOutgoingUrl(post.url) : getPostPageUrl(post._id)
|
|
};
|
|
|
|
if(post.url)
|
|
p.url = post.url;
|
|
|
|
if(post.htmlBody)
|
|
p.htmlBody = post.htmlBody;
|
|
|
|
return p;
|
|
};
|
|
|
|
getPostPageUrl = function(post){
|
|
return getSiteUrl()+'posts/'+post._id;
|
|
};
|
|
|
|
getPostEditUrl = function(id){
|
|
return getSiteUrl()+'posts/'+id+'/edit';
|
|
};
|
|
|
|
// for a given post, return its link if it has one, or else its post page URL
|
|
getPostLink = function (post) {
|
|
return !!post.url ? getOutgoingUrl(post.url) : getPostPageUrl(post);
|
|
};
|
|
|
|
checkForPostsWithSameUrl = function (url) {
|
|
|
|
// check that there are no previous posts with the same link in the past 6 months
|
|
var sixMonthsAgo = moment().subtract(6, 'months').toDate();
|
|
var postWithSameLink = Posts.findOne({url: url, postedAt: {$gte: sixMonthsAgo}});
|
|
|
|
if(typeof postWithSameLink !== 'undefined'){
|
|
Meteor.call('upvotePost', postWithSameLink);
|
|
// note: error.details returns undefined on the client, so add post ID to reason
|
|
throw new Meteor.Error('603', i18n.t('this_link_has_already_been_posted') + '|' + postWithSameLink._id, postWithSameLink._id);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------- //
|
|
// ------------------------------------------ Hooks ------------------------------------------ //
|
|
// ------------------------------------------------------------------------------------------- //
|
|
|
|
Posts.before.insert(function (userId, doc) {
|
|
if(Meteor.isServer && !!doc.body)
|
|
doc.htmlBody = sanitize(marked(doc.body));
|
|
});
|
|
|
|
Posts.before.update(function (userId, doc, fieldNames, modifier, options) {
|
|
// if body is being modified, update htmlBody too
|
|
if (Meteor.isServer && modifier.$set && modifier.$set.body) {
|
|
modifier.$set = modifier.$set || {};
|
|
modifier.$set.htmlBody = sanitize(marked(modifier.$set.body));
|
|
}
|
|
});
|
|
|
|
// ------------------------------------------------------------------------------------------- //
|
|
// ----------------------------------------- Methods ----------------------------------------- //
|
|
// ------------------------------------------------------------------------------------------- //
|
|
|
|
postClicks = [];
|
|
postViews = [];
|
|
|
|
Meteor.methods({
|
|
|
|
submitPost: function(post){
|
|
var title = cleanUp(post.title),
|
|
body = post.body,
|
|
userId = this.userId,
|
|
user = Meteor.users.findOne(userId),
|
|
timeSinceLastPost=timeSinceLast(user, Posts),
|
|
numberOfPostsInPast24Hours=numberOfItemsInPast24Hours(user, Posts),
|
|
postInterval = Math.abs(parseInt(getSetting('postInterval', 30))),
|
|
maxPostsPer24Hours = Math.abs(parseInt(getSetting('maxPostsPerDay', 30))),
|
|
postId = '';
|
|
|
|
// ------------------------------ Checks ------------------------------ //
|
|
|
|
// check that user can post
|
|
if (!user || !canPost(user))
|
|
throw new Meteor.Error(601, i18n.t('you_need_to_login_or_be_invited_to_post_new_stories'));
|
|
|
|
// check that user provided a title
|
|
if(!post.title)
|
|
throw new Meteor.Error(602, i18n.t('please_fill_in_a_title'));
|
|
|
|
// check that there are no posts with the same URL
|
|
if(!!post.url)
|
|
checkForPostsWithSameUrl(post.url);
|
|
|
|
// --------------------------- Rate Limiting -------------------------- //
|
|
|
|
if(!isAdmin(Meteor.user())){
|
|
// check that user waits more than X seconds between posts
|
|
if(!this.isSimulation && timeSinceLastPost < postInterval)
|
|
throw new Meteor.Error(604, i18n.t('please_wait')+(postInterval-timeSinceLastPost)+i18n.t('seconds_before_posting_again'));
|
|
|
|
// check that the user doesn't post more than Y posts per day
|
|
if(!this.isSimulation && numberOfPostsInPast24Hours > maxPostsPer24Hours)
|
|
throw new Meteor.Error(605, i18n.t('sorry_you_cannot_submit_more_than')+maxPostsPer24Hours+i18n.t('posts_per_day'));
|
|
}
|
|
|
|
// ------------------------------ Properties ------------------------------ //
|
|
|
|
// Basic Properties
|
|
properties = {
|
|
title: title,
|
|
body: body,
|
|
userId: userId,
|
|
author: getDisplayNameById(userId),
|
|
upvotes: 0,
|
|
downvotes: 0,
|
|
commentCount: 0,
|
|
clickCount: 0,
|
|
viewCount: 0,
|
|
baseScore: 0,
|
|
score: 0,
|
|
inactive: false
|
|
};
|
|
|
|
// UserId
|
|
if(isAdmin(Meteor.user()) && !!post.userId){ // only let admins post as other users
|
|
properties.userId = post.userId;
|
|
}
|
|
|
|
// Status
|
|
var defaultPostStatus = getSetting('requirePostsApproval') ? STATUS_PENDING : STATUS_APPROVED;
|
|
if(isAdmin(Meteor.user()) && !!post.status){ // if user is admin and a custom status has been set
|
|
properties.status = post.status;
|
|
}else{ // else use default status
|
|
properties.status = defaultPostStatus;
|
|
}
|
|
|
|
// CreatedAt
|
|
properties.createdAt = new Date();
|
|
|
|
// PostedAt
|
|
if(properties.status == 2){ // only set postedAt if post is approved
|
|
if(isAdmin(Meteor.user()) && !!post.postedAt){ // if user is admin and a custom postDate has been set
|
|
properties.postedAt = post.postedAt;
|
|
}else{ // else use current time
|
|
properties.postedAt = new Date();
|
|
}
|
|
}
|
|
|
|
post = _.extend(post, properties);
|
|
|
|
// ------------------------------ Callbacks ------------------------------ //
|
|
|
|
// run all post submit server callbacks on post object successively
|
|
post = postSubmitMethodCallbacks.reduce(function(result, currentFunction) {
|
|
return currentFunction(result);
|
|
}, post);
|
|
|
|
// ------------------------------ Insert ------------------------------ //
|
|
|
|
// console.log(post)
|
|
post._id = Posts.insert(post);
|
|
|
|
// ------------------------------ Callbacks ------------------------------ //
|
|
|
|
// run all post submit server callbacks on post object successively
|
|
post = postAfterSubmitMethodCallbacks.reduce(function(result, currentFunction) {
|
|
return currentFunction(result);
|
|
}, post);
|
|
|
|
// ------------------------------ After Insert ------------------------------ //
|
|
|
|
// increment posts count
|
|
Meteor.users.update({_id: userId}, {$inc: {postCount: 1}});
|
|
|
|
var postAuthor = Meteor.users.findOne(post.userId);
|
|
|
|
Meteor.call('upvotePost', post, postAuthor);
|
|
|
|
return post;
|
|
},
|
|
|
|
editPost: function (postId, updateObject) {
|
|
|
|
var user = Meteor.user();
|
|
|
|
// console.log(updateObject)
|
|
|
|
// ------------------------------ Checks ------------------------------ //
|
|
|
|
// check that user can edit
|
|
if (!user || !canEdit(user, Posts.findOne(postId)))
|
|
throw new Meteor.Error(601, i18n.t('sorry_you_cannot_edit_this_post'));
|
|
|
|
// ------------------------------ Callbacks ------------------------------ //
|
|
|
|
// run all post submit server callbacks on updateObject successively
|
|
updateObject = postEditMethodCallbacks.reduce(function(result, currentFunction) {
|
|
return currentFunction(result);
|
|
}, updateObject);
|
|
|
|
console.log(updateObject)
|
|
|
|
// ------------------------------ Update ------------------------------ //
|
|
|
|
Posts.update(postId, updateObject);
|
|
|
|
// ------------------------------ Callbacks ------------------------------ //
|
|
|
|
// run all post submit server callbacks on updateObject successively
|
|
updateObject = postAfterEditMethodCallbacks.reduce(function(result, currentFunction) {
|
|
return currentFunction(result);
|
|
}, updateObject);
|
|
|
|
// ------------------------------ After Update ------------------------------ //
|
|
|
|
return Posts.findOne(postId);
|
|
|
|
},
|
|
|
|
setPostedAt: function(post, customPostedAt){
|
|
|
|
var postedAt = new Date(); // default to current date and time
|
|
|
|
if(isAdmin(Meteor.user()) && typeof customPostedAt !== 'undefined') // if user is admin and a custom datetime has been set
|
|
postedAt = customPostedAt;
|
|
|
|
Posts.update(post._id, {$set: {postedAt: postedAt}});
|
|
},
|
|
|
|
approvePost: function(post){
|
|
if(isAdmin(Meteor.user())){
|
|
var now = new Date();
|
|
Posts.update(post._id, {$set: {status: 2, postedAt: now}});
|
|
}else{
|
|
throwError('You need to be an admin to do that.');
|
|
}
|
|
},
|
|
|
|
unapprovePost: function(post){
|
|
if(isAdmin(Meteor.user())){
|
|
Posts.update(post._id, {$set: {status: 1}});
|
|
}else{
|
|
throwError('You need to be an admin to do that.');
|
|
}
|
|
},
|
|
|
|
increasePostViews: function(postId, sessionId){
|
|
this.unblock();
|
|
|
|
// only let users increment a post's view counter once per session
|
|
var view = {_id: postId, userId: this.userId, sessionId: sessionId};
|
|
|
|
if(_.where(postViews, view).length == 0){
|
|
postViews.push(view);
|
|
Posts.update(postId, { $inc: { viewCount: 1 }});
|
|
}
|
|
},
|
|
|
|
increasePostClicks: function(postId, sessionId){
|
|
this.unblock();
|
|
|
|
// only let clients increment a post's click counter once per session
|
|
var click = {_id: postId, userId: this.userId, sessionId: sessionId};
|
|
|
|
if(_.where(postClicks, click).length == 0){
|
|
postClicks.push(click);
|
|
Posts.update(postId, { $inc: { clickCount: 1 }});
|
|
}
|
|
},
|
|
|
|
deletePostById: function(postId) {
|
|
// remove post comments
|
|
// if(!this.isSimulation) {
|
|
// Comments.remove({post: postId});
|
|
// }
|
|
// NOTE: actually, keep comments after all
|
|
|
|
// decrement post count
|
|
var post = Posts.findOne({_id: postId});
|
|
if(!Meteor.userId() || !canEditById(Meteor.userId(), post)) throw new Meteor.Error(606, 'You need permission to edit or delete a post');
|
|
|
|
Meteor.users.update({_id: post.userId}, {$inc: {postCount: -1}});
|
|
Posts.remove(postId);
|
|
}
|
|
|
|
});
|