mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 11:21:01 +08:00
Merge pull request #1288 from coralproject/reply-on-hidden-comment
Replies to hidden/deleted comments
This commit is contained in:
@@ -265,6 +265,15 @@ const ErrCannotIgnoreStaff = new APIError('Cannot ignore staff members.', {
|
||||
status: 400,
|
||||
});
|
||||
|
||||
// ErrParentDoesNotVisible is returned when the user tries to reply to a comment
|
||||
// that isn't visible.
|
||||
const ErrParentDoesNotVisible = new APIError(
|
||||
'Cannot reply to a comment that is not visible',
|
||||
{
|
||||
translation_key: 'COMMENT_PARENT_NOT_VISIBLE',
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
APIError,
|
||||
ErrAlreadyExists,
|
||||
@@ -276,24 +285,25 @@ module.exports = {
|
||||
ErrContainsProfanity,
|
||||
ErrEditWindowHasEnded,
|
||||
ErrEmailTaken,
|
||||
ErrEmailVerificationToken,
|
||||
ErrInstallLock,
|
||||
ErrInvalidAssetURL,
|
||||
ErrLoginAttemptMaximumExceeded,
|
||||
ErrMaxRateLimit,
|
||||
ErrMissingEmail,
|
||||
ErrMissingPassword,
|
||||
ErrEmailVerificationToken,
|
||||
ErrPasswordResetToken,
|
||||
ErrMissingUsername,
|
||||
ErrNotAuthorized,
|
||||
ErrNotFound,
|
||||
ErrNotVerified,
|
||||
ErrParentDoesNotVisible,
|
||||
ErrPasswordResetToken,
|
||||
ErrPasswordTooShort,
|
||||
ErrPermissionUpdateUsername,
|
||||
ErrSameUsernameProvided,
|
||||
ErrSettingsInit,
|
||||
ErrSettingsNotInit,
|
||||
ErrSpecialChars,
|
||||
ErrUsernameTaken,
|
||||
ErrSameUsernameProvided,
|
||||
ExtendableError,
|
||||
};
|
||||
|
||||
@@ -198,6 +198,7 @@ en:
|
||||
embedlink:
|
||||
copy: "Copy to Clipboard"
|
||||
error:
|
||||
COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist."
|
||||
EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid."
|
||||
PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid."
|
||||
COMMENT_TOO_SHORT: "Comments should be more than one character, please revise your comment and try again."
|
||||
|
||||
@@ -182,6 +182,7 @@ es:
|
||||
embedlink:
|
||||
copy: "Copiar al portapapeles"
|
||||
error:
|
||||
COMMENT_PARENT_NOT_VISIBLE: "El comentario a la que estás contestando ha sido eliminado o no existe."
|
||||
COMMENT_TOO_SHORT: "Tu comentario debe tener algo escrito"
|
||||
COMMENTING_CLOSED: "Los comentarios ya estan cerrados"
|
||||
confirm_password: "Las contraseñas no coinciden. Inténtelo nuevamente"
|
||||
|
||||
+20
-15
@@ -7,24 +7,31 @@ const SettingsService = require('./settings');
|
||||
const cloneDeep = require('lodash/cloneDeep');
|
||||
const errors = require('../errors');
|
||||
const events = require('./events');
|
||||
const merge = require('lodash/merge');
|
||||
const { COMMENTS_NEW, COMMENTS_EDIT } = require('./events/constants');
|
||||
|
||||
module.exports = class CommentsService {
|
||||
/**
|
||||
* Creates a new Comment that came from a public source.
|
||||
* @param {Mixed} comment either a single comment or an array of comments.
|
||||
* @param {Object} input either a single comment or an array of comments.
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async publicCreate(comment) {
|
||||
// Check to see if this is an array of comments, if so map it out.
|
||||
if (Array.isArray(comment)) {
|
||||
return Promise.all(comment.map(CommentsService.publicCreate));
|
||||
static async publicCreate(input) {
|
||||
// Extract the parent_id from the comment, if there is one.
|
||||
const { status = 'NONE', parent_id = null } = input;
|
||||
|
||||
// Check to see if we are replying to a comment, and if that comment is
|
||||
// visible.
|
||||
if (parent_id !== null) {
|
||||
const parent = await CommentModel.findOne({ id: parent_id });
|
||||
if (parent === null || !parent.visible) {
|
||||
throw errors.ErrParentDoesNotVisible;
|
||||
}
|
||||
}
|
||||
|
||||
const { status = 'NONE' } = comment;
|
||||
|
||||
const commentModel = new CommentModel(
|
||||
Object.assign(
|
||||
// Create the comment in the database.
|
||||
const comment = await CommentModel.create(
|
||||
merge(
|
||||
{
|
||||
status_history: status
|
||||
? [
|
||||
@@ -36,21 +43,19 @@ module.exports = class CommentsService {
|
||||
: [],
|
||||
body_history: [
|
||||
{
|
||||
body: comment.body,
|
||||
body: input.body,
|
||||
created_at: new Date(),
|
||||
},
|
||||
],
|
||||
},
|
||||
comment
|
||||
input
|
||||
)
|
||||
);
|
||||
|
||||
const savedCommentModel = await commentModel.save();
|
||||
|
||||
// Emit that the comment was created!
|
||||
await events.emitAsync(COMMENTS_NEW, savedCommentModel);
|
||||
await events.emitAsync(COMMENTS_NEW, comment);
|
||||
|
||||
return savedCommentModel;
|
||||
return comment;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -7,6 +7,6 @@
|
||||
],
|
||||
"extends": "../.eslintrc.json",
|
||||
"rules": {
|
||||
"mocha/no-exclusive-tests": "warn"
|
||||
"mocha/no-exclusive-tests": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,8 @@ describe('graph.mutations.createComment', () => {
|
||||
}
|
||||
expect(data.createComment).to.have.property('errors').null;
|
||||
expect(data.createComment).to.have.property('comment').not.null;
|
||||
expect(data.createComment.comment).to.have.property('id').not
|
||||
.null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -34,12 +34,14 @@ describe('graph.queries.asset', () => {
|
||||
username: 'usernameC',
|
||||
},
|
||||
]);
|
||||
comments = await CommentsService.publicCreate(
|
||||
[0, 0, 1, 1].map(idx => ({
|
||||
author_id: users[idx].id,
|
||||
asset_id: assets[idx].id,
|
||||
body: `hello there! ${String(Math.random()).slice(2)}`,
|
||||
}))
|
||||
comments = await Promise.all(
|
||||
[0, 0, 1, 1].map(idx =>
|
||||
CommentsService.publicCreate({
|
||||
author_id: users[idx].id,
|
||||
asset_id: assets[idx].id,
|
||||
body: `hello there! ${String(Math.random()).slice(2)}`,
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -176,28 +176,30 @@ describe('services.AssetsService', () => {
|
||||
);
|
||||
|
||||
// Create some comments on both assets.
|
||||
await CommentsService.publicCreate([
|
||||
{
|
||||
asset_id: '1',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
{
|
||||
asset_id: '1',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
{
|
||||
asset_id: '2',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
{
|
||||
asset_id: '2',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
]);
|
||||
await Promise.all(
|
||||
[
|
||||
{
|
||||
asset_id: '1',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
{
|
||||
asset_id: '1',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
{
|
||||
asset_id: '2',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
{
|
||||
asset_id: '2',
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
].map(comment => CommentsService.publicCreate(comment))
|
||||
);
|
||||
|
||||
// Merge all the comments from asset 1 into asset 2, followed by deleting
|
||||
// asset 1.
|
||||
|
||||
@@ -131,6 +131,64 @@ describe('services.CommentsService', () => {
|
||||
});
|
||||
|
||||
describe('#publicCreate()', () => {
|
||||
describe('does not allow replies to comments that are not visible', () => {
|
||||
it('parent not found', async () => {
|
||||
try {
|
||||
await CommentsService.publicCreate({
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
parent_id: 'does not exist',
|
||||
});
|
||||
throw new Error('comment should not have been created');
|
||||
} catch (err) {
|
||||
expect(err).to.have.property(
|
||||
'translation_key',
|
||||
'COMMENT_PARENT_NOT_VISIBLE'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('parent REJECTED', async () => {
|
||||
try {
|
||||
const parent = await CommentsService.publicCreate({
|
||||
body: 'This is a comment!',
|
||||
status: 'REJECTED',
|
||||
});
|
||||
await CommentsService.publicCreate({
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
parent_id: parent.id,
|
||||
});
|
||||
throw new Error('comment should not have been created');
|
||||
} catch (err) {
|
||||
expect(err).to.have.property(
|
||||
'translation_key',
|
||||
'COMMENT_PARENT_NOT_VISIBLE'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('parent SYSTEM_WITHHELD', async () => {
|
||||
try {
|
||||
const parent = await CommentsService.publicCreate({
|
||||
body: 'This is a comment!',
|
||||
status: 'SYSTEM_WITHHELD',
|
||||
});
|
||||
await CommentsService.publicCreate({
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
parent_id: parent.id,
|
||||
});
|
||||
throw new Error('comment should not have been created');
|
||||
} catch (err) {
|
||||
expect(err).to.have.property(
|
||||
'translation_key',
|
||||
'COMMENT_PARENT_NOT_VISIBLE'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a new comment', async () => {
|
||||
const c = await CommentsService.publicCreate({
|
||||
body: 'This is a comment!',
|
||||
@@ -142,31 +200,6 @@ describe('services.CommentsService', () => {
|
||||
expect(c.id).to.be.uuid;
|
||||
expect(c.status).to.be.equal('ACCEPTED');
|
||||
});
|
||||
|
||||
it('creates many new comments', async () => {
|
||||
const [c1, c2, c3] = await CommentsService.publicCreate([
|
||||
{
|
||||
body: 'This is a comment!',
|
||||
status: 'ACCEPTED',
|
||||
},
|
||||
{
|
||||
body: 'This is another comment!',
|
||||
},
|
||||
{
|
||||
body: 'This is a rejected comment!',
|
||||
status: 'REJECTED',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(c1).to.not.be.null;
|
||||
expect(c1.status).to.be.equal('ACCEPTED');
|
||||
|
||||
expect(c2).to.not.be.null;
|
||||
expect(c2.status).to.be.equal('NONE');
|
||||
|
||||
expect(c3).to.not.be.null;
|
||||
expect(c3.status).to.be.equal('REJECTED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#edit', () => {
|
||||
|
||||
Reference in New Issue
Block a user