You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
4.4 KiB
174 lines
4.4 KiB
3 years ago
|
// comment.js
|
||
|
// Copyright (C) 2021 Digital Telepresence, LLC
|
||
|
// License: Apache-2.0
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
const path = require('path');
|
||
|
|
||
|
const mongoose = require('mongoose');
|
||
|
|
||
|
const Comment = mongoose.model('Comment'); // jshint ignore:line
|
||
|
|
||
|
const pug = require('pug');
|
||
|
const striptags = require('striptags');
|
||
|
|
||
|
const { SiteService, SiteError } = require('../../lib/site-lib');
|
||
|
|
||
|
class CommentService extends SiteService {
|
||
|
|
||
|
constructor (dtp) {
|
||
|
super(dtp, module.exports);
|
||
|
this.populateComment = [
|
||
|
{
|
||
|
path: 'author',
|
||
|
select: '',
|
||
|
},
|
||
|
{
|
||
|
path: 'replyTo',
|
||
|
},
|
||
|
];
|
||
|
}
|
||
|
|
||
|
async start ( ) {
|
||
|
this.templates = { };
|
||
|
this.templates.comment = pug.compileFile(path.join(this.dtp.config.root, 'app', 'views', 'comment', 'components', 'comment-standalone.pug'));
|
||
|
}
|
||
|
|
||
|
async create (author, resourceType, resource, commentDefinition) {
|
||
|
const NOW = new Date();
|
||
|
let comment = new Comment();
|
||
|
|
||
|
comment.created = NOW;
|
||
|
comment.resourceType = resourceType;
|
||
|
comment.resource = resource._id;
|
||
|
comment.author = author._id;
|
||
|
if (commentDefinition.replyTo) {
|
||
|
comment.replyTo = mongoose.Types.ObjectId(commentDefinition.replyTo);
|
||
|
}
|
||
|
if (commentDefinition.content) {
|
||
|
comment.content = striptags(commentDefinition.content.trim());
|
||
|
}
|
||
|
|
||
|
await comment.save();
|
||
|
|
||
|
const model = mongoose.model(resourceType);
|
||
|
await model.updateOne(
|
||
|
{ _id: resource._id },
|
||
|
{
|
||
|
$inc: { 'stats.commentCount': 1 },
|
||
|
},
|
||
|
);
|
||
|
|
||
|
/*
|
||
|
* increment the reply count of every parent comment until you reach a
|
||
|
* comment with no parent.
|
||
|
*/
|
||
|
|
||
|
let replyTo = comment.replyTo;
|
||
|
while (replyTo) {
|
||
|
await Comment.updateOne(
|
||
|
{ _id: replyTo },
|
||
|
{
|
||
|
$inc: { 'stats.replyCount': 1 },
|
||
|
},
|
||
|
);
|
||
|
let parent = await Comment.findById(replyTo).select('replyTo').lean();
|
||
|
if (parent.replyTo) {
|
||
|
replyTo = parent.replyTo;
|
||
|
} else {
|
||
|
replyTo = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
comment = comment.toObject();
|
||
|
comment.author = author;
|
||
|
return comment;
|
||
|
}
|
||
|
|
||
|
async update (comment, commentDefinition) {
|
||
|
const updateOp = { $set: { } };
|
||
|
|
||
|
if (!commentDefinition.content || (commentDefinition.content.length === 0)) {
|
||
|
throw new SiteError(406, 'The comment cannot be empty');
|
||
|
}
|
||
|
updateOp.$set.content = striptags(commentDefinition.content.trim());
|
||
|
updateOp.$push = {
|
||
|
contentHistory: {
|
||
|
created: new Date(),
|
||
|
content: comment.content,
|
||
|
},
|
||
|
};
|
||
|
this.log.info('updating comment content', { commentId: comment._id });
|
||
|
await Comment.updateOne({ _id: comment._id }, updateOp);
|
||
|
}
|
||
|
|
||
|
async setStatus (comment, status) {
|
||
|
await Comment.updateOne({ _id: comment._id }, { $set: { status } });
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pushes the current comment content to the contentHistory array, sets the
|
||
|
* content field to 'Content removed' and updates the comment status to the
|
||
|
* status provided. This preserves the comment content, but removes it from
|
||
|
* public view.
|
||
|
* @param {Document} comment
|
||
|
* @param {String} status
|
||
|
*/
|
||
|
async remove (comment, status = 'removed') {
|
||
|
await Comment.updateOne(
|
||
|
{ _id: comment._id },
|
||
|
{
|
||
|
$set: {
|
||
|
status,
|
||
|
content: 'Comment removed',
|
||
|
},
|
||
|
$push: {
|
||
|
contentHistory: {
|
||
|
created: new Date(),
|
||
|
content: comment.content,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
);
|
||
|
}
|
||
|
|
||
|
async getForResource (resource, statuses, pagination) {
|
||
|
const comments = await Comment
|
||
|
.find({ resource: resource._id, status: { $in: statuses } })
|
||
|
.sort({ created: 1 })
|
||
|
.skip(pagination.skip)
|
||
|
.limit(pagination.cpp)
|
||
|
.populate(this.populateComment)
|
||
|
.lean();
|
||
|
return comments;
|
||
|
}
|
||
|
|
||
|
async getContentHistory (comment, pagination) {
|
||
|
/*
|
||
|
* Extract a page from the contentHistory using $slice on the array
|
||
|
*/
|
||
|
const fullComment = await Comment
|
||
|
.findOne(
|
||
|
{ _id: comment._id },
|
||
|
{
|
||
|
contentHistory: {
|
||
|
$sort: { created: 1 },
|
||
|
$slice: [pagination.skip, pagination.cpp],
|
||
|
},
|
||
|
}
|
||
|
)
|
||
|
.select('contentHistory').lean();
|
||
|
if (!fullComment) {
|
||
|
throw new SiteError(404, 'Comment not found');
|
||
|
}
|
||
|
return fullComment.contentHistory || [ ];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = {
|
||
|
slug: 'comment',
|
||
|
name: 'comment',
|
||
|
create: (dtp) => { return new CommentService(dtp); },
|
||
|
};
|