linear
Version:
A simple setup micro-forum built in Node.js with Express and MongoDB.
582 lines (404 loc) • 13.1 kB
JavaScript
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const logger = require('../utils/logger');
const messageSchema = new mongoose.Schema({
'contents': {
'required': true,
'type': String
},
'createdAt': {
'default': Date.now,
'type': Date
},
'createdBy': {
'ref': 'profile',
'type': mongoose.Schema.Types.ObjectId
},
'editedBy': {
'ref': 'profile',
'type': mongoose.Schema.Types.ObjectId
},
'updatedAt': {
'default': Date.now,
'type': Date
}
});
const postSchema = new mongoose.Schema({
'contents': String,
'createdAt': {
'default': Date.now,
'type': Date
},
'createdBy': {
'ref': 'profile',
'type': mongoose.Schema.Types.ObjectId
},
'editedBy': {
'ref': 'profile',
'type': mongoose.Schema.Types.ObjectId
},
'messageCount': {
'default': 0,
'type': Number
},
'messages': [messageSchema],
'pinned': {
'default': false,
'type': Boolean
},
'slug': String,
'title': {
'required': true,
'type': String
},
'updatedAt': {
'default': Date.now,
'type': Date
},
'views': {
'default': 0,
'type': Number
}
});
postSchema.statics.createPost = function (data, profileId) {
return new Promise((resolve, reject) => {
const post = new this({
'contents': data.contents,
'createdBy': profileId,
'slug': data.title.toLowerCase()
.replace(/[^a-z]+/g, '-')
.replace(/^-|-$/g, ''),
'title': data.title
});
post.save((err, results) => {
if (err || !results) {
logger.err('Error creating new post.', err, data);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve(this.showPostById(results._id));
}
});
});
};
postSchema.statics.updatePostById = function (postId, data, profileId) {
return new Promise((resolve, reject) => {
this.findOne({
'_id': postId,
'createdBy': profileId
})
.exec((execError, post) => {
if (execError || !post) {
logger.err('Error updating existing post.', execError, data);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
if (typeof data.title !== 'undefined') {
post.title = data.title;
}
if (typeof data.contents !== 'undefined') {
post.contents = data.contents;
}
post.editedBy = profileId;
post.save((saveError, results) => {
if (saveError || !results) {
logger.err('Error updating existing post.', saveError, data);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve(this.showPostById(postId));
}
});
}
});
});
};
postSchema.statics.addMessageToPostById = function (postId, data, profileId) {
return new Promise((resolve, reject) => {
this.findOneAndUpdate(
{
'_id': postId
},
{
'$push': {
'messages': {
'contents': data.contents,
'createdBy': profileId
}
},
'$set': {
'updatedAt': Date.now()
}
},
{
'new': true
},
(updateError, results) => {
if (updateError || !results) {
logger.err('Error updating existing message.', updateError, data);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve(this.showMessageById(postId, results.messages.slice(-1)[0]._id));
}
}
);
});
};
postSchema.statics.updateMessageToPostById = function (postId, messageId, data, profileId) {
return new Promise((resolve, reject) => {
this.findOneAndUpdate(
{
'_id': postId,
'messages': {
'$elemMatch': {
'_id': messageId,
'createdBy': profileId
}
}
},
{
'$set': {
'messages.$.contents': data.contents,
'messages.$.editedBy': profileId,
'messages.$.updatedAt': Date.now(),
'updatedAt': Date.now()
}
},
{
'new': true
},
(updateError, results) => {
if (updateError || !results) {
logger.err('Error updating existing message.', updateError, data);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve(this.showMessageById(postId, messageId));
}
}
);
});
};
postSchema.statics.deletePostById = function (postId, profileId) {
return new Promise((resolve, reject) => {
this.findOneAndRemove(
{
'_id': postId,
'createdBy': profileId
},
(removeError, post) => {
if (removeError || !post) {
logger.err('Error deleting existing post.', removeError);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve([]);
}
}
);
});
};
postSchema.statics.deleteMessageFromPostById = function (postId, messageId, profileId) {
return new Promise((resolve, reject) => {
this.findOneAndUpdate(
{
'_id': postId,
'messages': {
'$elemMatch': {
'_id': messageId,
'createdBy': profileId
}
}
},
{
'$pull': {
'messages': {
'_id': messageId,
'createdBy': profileId
}
},
'$set': {
'updatedAt': Date.now()
}
},
{
'new': true
},
(updateError, results) => {
if (updateError || !results) {
logger.err('Error deleting existing message.', updateError);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve({});
}
}
);
});
};
postSchema.statics.showPostById = function (postId) {
return new Promise((resolve, reject) => {
this.findByIdAndUpdate(postId, {
'$inc': {
'views': 1
}
})
.populate('createdBy')
.populate('editedBy')
.populate('messages.createdBy')
.populate('messages.editedBy')
.exec((updateError, results) => {
if (updateError || !results) {
logger.err(`Post id ${postId} not found.`);
reject({
'message': 'Post not found.',
'status': 404
});
} else {
resolve(results);
}
});
});
};
postSchema.statics.showMessageById = function (postId, messageId) {
return new Promise((resolve, reject) => {
this.find({
'_id': postId,
'messages._id': messageId
}, {
'messages.$': true
})
.populate('messages.createdBy')
.populate('messages.editedBy')
.exec((findError, results) => {
if (findError || !results || !results[0] || !results[0].messages) {
logger.err(`Message id ${messageId} not found.`);
reject({
'message': 'Message not found.',
'status': 404
});
} else {
resolve(results[0].messages[0]);
}
});
});
};
postSchema.statics.listPosts = function () {
return new Promise((resolve, reject) => {
this.find()
.populate('createdBy')
.populate('editedBy')
.sort({
'pinned': -1,
'updatedAt': -1
})
.select('createdAt updatedAt views title slug contents messageCount createdBy editedBy')
.exec((findError, results) => {
if (findError || !results) {
logger.err('Error retrieving post list.', findError);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve(results);
}
});
});
};
postSchema.statics.listMessagesByPostId = function (postId) {
return new Promise((resolve, reject) => {
this.findByIdAndUpdate(postId, {
'$inc': {
'views': 1
}
})
.populate('createdBy')
.populate('editedBy')
.populate('messages.createdBy')
.populate('messages.editedBy')
.exec((updateError, results) => {
if (updateError || !results) {
logger.err(`Post id ${postId} not found.`);
reject({
'message': 'Post not found.',
'status': 404
});
} else {
resolve(results.messages);
}
});
});
};
postSchema.statics.searchPosts = function (query) {
return new Promise((resolve, reject) => {
if (query) {
let modifiedQuery = query;
modifiedQuery = modifiedQuery.trim().toLowerCase();
modifiedQuery = modifiedQuery.replace(/[^0-9a-z]+/, '|').replace(/^\||\|$/, '');
if (modifiedQuery) {
this.find({
'title': {
'$regex': new RegExp(modifiedQuery, 'i')
}
})
.populate('createdBy')
.populate('editedBy')
.sort({
'updatedAt': -1
})
.select('createdAt updatedAt views title slug contents messageCount createdBy editedBy')
.exec((findError, results) => {
if (findError || !results) {
logger.err('Error retrieving search results.', findError, query);
reject({
'message': 'Internal Server Error',
'status': 500
});
} else {
resolve(results);
}
});
} else {
resolve([]);
}
} else {
resolve([]);
}
});
};
messageSchema.virtual('id').get(function () {
return this._id.toHexString();
});
messageSchema.set('toJSON', {
'virtuals': true
});
postSchema.post('findOneAndUpdate', (doc, next) => {
if (doc && doc.messageCount !== doc.messages.length) {
doc.messageCount = doc.messages.length;
return doc.save(next);
}
return next();
});
postSchema.virtual('id').get(function () {
return this._id.toHexString();
});
postSchema.set('toJSON', {
'virtuals': true
});
module.exports = mongoose.model('post', postSchema);