| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- import { getIdStringForRef } from '@growi/core';
- import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
- import { Comment, CommentEvent, commentEvent } from '~/features/comment/server';
- import { SupportedAction, SupportedTargetModel, SupportedEventModel } from '~/interfaces/activity';
- import loggerFactory from '~/utils/logger';
- import { GlobalNotificationSettingEvent } from '../models/GlobalNotificationSetting';
- import { preNotifyService } from '../service/pre-notify';
- /**
- * @swagger
- * tags:
- * name: Comments
- */
- /**
- * @swagger
- *
- * components:
- * schemas:
- * Comment:
- * description: Comment
- * type: object
- * properties:
- * _id:
- * type: string
- * description: revision ID
- * example: 5e079a0a0afa6700170a75fb
- * __v:
- * type: number
- * description: DB record version
- * example: 0
- * page:
- * $ref: '#/components/schemas/Page/properties/_id'
- * creator:
- * $ref: '#/components/schemas/User/properties/_id'
- * revision:
- * $ref: '#/components/schemas/Revision/properties/_id'
- * comment:
- * type: string
- * description: comment
- * example: good
- * commentPosition:
- * type: number
- * description: comment position
- * example: 0
- * createdAt:
- * type: string
- * description: date created at
- * example: 2010-01-01T00:00:00.000Z
- */
- module.exports = function(crowi, app) {
- const logger = loggerFactory('growi:routes:comment');
- const User = crowi.model('User');
- const Page = crowi.model('Page');
- const ApiResponse = require('../util/apiResponse');
- const activityEvent = crowi.event('activity');
- const globalNotificationService = crowi.getGlobalNotificationService();
- const userNotificationService = crowi.getUserNotificationService();
- const { body } = require('express-validator');
- const mongoose = require('mongoose');
- const ObjectId = mongoose.Types.ObjectId;
- const actions = {};
- const api = {};
- actions.api = api;
- api.validators = {};
- /**
- * @swagger
- *
- * /comments.get:
- * get:
- * tags: [Comments, CrowiCompatibles]
- * operationId: getComments
- * summary: /comments.get
- * description: Get comments of the page of the revision
- * parameters:
- * - in: query
- * name: page_id
- * schema:
- * $ref: '#/components/schemas/Page/properties/_id'
- * - in: query
- * name: revision_id
- * schema:
- * $ref: '#/components/schemas/Revision/properties/_id'
- * responses:
- * 200:
- * description: Succeeded to get comments of the page of the revision.
- * content:
- * application/json:
- * schema:
- * properties:
- * ok:
- * $ref: '#/components/schemas/V1Response/properties/ok'
- * comments:
- * type: array
- * items:
- * $ref: '#/components/schemas/Comment'
- * 403:
- * $ref: '#/components/responses/403'
- * 500:
- * $ref: '#/components/responses/500'
- */
- /**
- * @api {get} /comments.get Get comments of the page of the revision
- * @apiName GetComments
- * @apiGroup Comment
- *
- * @apiParam {String} page_id Page Id.
- * @apiParam {String} revision_id Revision Id.
- */
- api.get = async function(req, res) {
- const pageId = req.query.page_id;
- const revisionId = req.query.revision_id;
- // check whether accessible
- const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
- if (!isAccessible) {
- return res.json(ApiResponse.error('Current user is not accessible to this page.'));
- }
- let query = null;
- try {
- if (revisionId) {
- query = Comment.findCommentsByRevisionId(revisionId);
- }
- else {
- query = Comment.findCommentsByPageId(pageId);
- }
- }
- catch (err) {
- return res.json(ApiResponse.error(err));
- }
- const comments = await query.populate('creator');
- comments.forEach((comment) => {
- if (comment.creator != null && comment.creator instanceof User) {
- comment.creator = serializeUserSecurely(comment.creator);
- }
- });
- res.json(ApiResponse.success({ comments }));
- };
- api.validators.add = function() {
- const validator = [
- body('commentForm.page_id').exists(),
- body('commentForm.revision_id').exists(),
- body('commentForm.comment').exists(),
- body('commentForm.comment_position').isInt(),
- body('commentForm.is_markdown').isBoolean(),
- body('commentForm.replyTo').exists().custom((value) => {
- if (value === '') {
- return undefined;
- }
- return ObjectId(value);
- }),
- body('slackNotificationForm.isSlackEnabled').isBoolean().exists(),
- ];
- return validator;
- };
- /**
- * @swagger
- *
- * /comments.add:
- * post:
- * tags: [Comments, CrowiCompatibles]
- * operationId: addComment
- * summary: /comments.add
- * description: Post comment for the page
- * requestBody:
- * content:
- * application/json:
- * schema:
- * properties:
- * commentForm:
- * type: object
- * properties:
- * page_id:
- * $ref: '#/components/schemas/Page/properties/_id'
- * revision_id:
- * $ref: '#/components/schemas/Revision/properties/_id'
- * comment:
- * $ref: '#/components/schemas/Comment/properties/comment'
- * comment_position:
- * $ref: '#/components/schemas/Comment/properties/commentPosition'
- * required:
- * - commentForm
- * responses:
- * 200:
- * description: Succeeded to post comment for the page.
- * content:
- * application/json:
- * schema:
- * properties:
- * ok:
- * $ref: '#/components/schemas/V1Response/properties/ok'
- * comment:
- * $ref: '#/components/schemas/Comment'
- * 403:
- * $ref: '#/components/responses/403'
- * 500:
- * $ref: '#/components/responses/500'
- */
- /**
- * @api {post} /comments.add Post comment for the page
- * @apiName PostComment
- * @apiGroup Comment
- *
- * @apiParam {String} page_id Page Id.
- * @apiParam {String} revision_id Revision Id.
- * @apiParam {String} comment Comment body
- * @apiParam {Number} comment_position=-1 Line number of the comment
- */
- api.add = async function(req, res) {
- const { commentForm, slackNotificationForm } = req.body;
- const { validationResult } = require('express-validator');
- const errors = validationResult(req.body);
- if (!errors.isEmpty()) {
- return res.json(ApiResponse.error('コメントを入力してください。'));
- }
- const pageId = commentForm.page_id;
- const revisionId = commentForm.revision_id;
- const comment = commentForm.comment;
- const position = commentForm.comment_position || -1;
- const replyTo = commentForm.replyTo;
- // check whether accessible
- const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
- if (!isAccessible) {
- return res.json(ApiResponse.error('Current user is not accessible to this page.'));
- }
- if (comment === '') {
- return res.json(ApiResponse.error('Comment text is required'));
- }
- let createdComment;
- try {
- createdComment = await Comment.add(pageId, req.user._id, revisionId, comment, position, replyTo);
- commentEvent.emit(CommentEvent.CREATE, createdComment);
- }
- catch (err) {
- logger.error(err);
- return res.json(ApiResponse.error(err));
- }
- // update page
- const page = await Page.findOneAndUpdate(
- { _id: pageId },
- {
- lastUpdateUser: req.user,
- updatedAt: new Date(),
- },
- );
- const parameters = {
- targetModel: SupportedTargetModel.MODEL_PAGE,
- target: page,
- eventModel: SupportedEventModel.MODEL_COMMENT,
- event: createdComment,
- action: SupportedAction.ACTION_COMMENT_CREATE,
- };
- /** @type {import('../service/pre-notify').GetAdditionalTargetUsers} */
- const getAdditionalTargetUsers = async(activity) => {
- const mentionedUsers = await crowi.commentService.getMentionedUsers(activity.event);
- return mentionedUsers;
- };
- activityEvent.emit('update', res.locals.activity._id, parameters, page, preNotifyService.generatePreNotify, getAdditionalTargetUsers);
- res.json(ApiResponse.success({ comment: createdComment }));
- // global notification
- try {
- await globalNotificationService.fire(GlobalNotificationSettingEvent.COMMENT, page, req.user, {
- comment: createdComment,
- });
- }
- catch (err) {
- logger.error('Comment notification failed', err);
- }
- // slack notification
- if (slackNotificationForm.isSlackEnabled) {
- const { slackChannels } = slackNotificationForm;
- try {
- const results = await userNotificationService.fire(page, req.user, slackChannels, 'comment', {}, createdComment);
- results.forEach((result) => {
- if (result.status === 'rejected') {
- logger.error('Create user notification failed', result.reason);
- }
- });
- }
- catch (err) {
- logger.error('Create user notification failed', err);
- }
- }
- };
- /**
- * @swagger
- *
- * /comments.update:
- * post:
- * tags: [Comments, CrowiCompatibles]
- * operationId: updateComment
- * summary: /comments.update
- * description: Update comment dody
- * requestBody:
- * content:
- * application/json:
- * schema:
- * properties:
- * form:
- * type: object
- * properties:
- * commentForm:
- * type: object
- * properties:
- * page_id:
- * $ref: '#/components/schemas/Page/properties/_id'
- * revision_id:
- * $ref: '#/components/schemas/Revision/properties/_id'
- * comment_id:
- * $ref: '#/components/schemas/Comment/properties/_id'
- * comment:
- * $ref: '#/components/schemas/Comment/properties/comment'
- * required:
- * - form
- * responses:
- * 200:
- * description: Succeeded to update comment dody.
- * content:
- * application/json:
- * schema:
- * properties:
- * ok:
- * $ref: '#/components/schemas/V1Response/properties/ok'
- * comment:
- * $ref: '#/components/schemas/Comment'
- * 403:
- * $ref: '#/components/responses/403'
- * 500:
- * $ref: '#/components/responses/500'
- */
- /**
- * @api {post} /comments.update Update comment dody
- * @apiName UpdateComment
- * @apiGroup Comment
- */
- api.update = async function(req, res) {
- const { commentForm } = req.body;
- const commentStr = commentForm?.comment;
- const commentId = commentForm?.comment_id;
- const revision = commentForm?.revision_id;
- if (commentStr === '') {
- return res.json(ApiResponse.error('Comment text is required'));
- }
- if (commentId == null) {
- return res.json(ApiResponse.error('\'comment_id\' is undefined'));
- }
- let updatedComment;
- try {
- const comment = await Comment.findById(commentId).exec();
- if (comment == null) {
- throw new Error('This comment does not exist.');
- }
- // check whether accessible
- const pageId = comment.page;
- const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
- if (!isAccessible) {
- throw new Error('Current user is not accessible to this page.');
- }
- if (req.user._id.toString() !== comment.creator.toString()) {
- throw new Error('Current user is not operatable to this comment.');
- }
- updatedComment = await Comment.findOneAndUpdate(
- { _id: commentId },
- { $set: { comment: commentStr, revision } },
- );
- commentEvent.emit(CommentEvent.UPDATE, updatedComment);
- }
- catch (err) {
- logger.error(err);
- return res.json(ApiResponse.error(err));
- }
- const parameters = { action: SupportedAction.ACTION_COMMENT_UPDATE };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- res.json(ApiResponse.success({ comment: updatedComment }));
- // process notification if needed
- };
- /**
- * @swagger
- *
- * /comments.remove:
- * post:
- * tags: [Comments, CrowiCompatibles]
- * operationId: removeComment
- * summary: /comments.remove
- * description: Remove specified comment
- * requestBody:
- * content:
- * application/json:
- * schema:
- * properties:
- * comment_id:
- * $ref: '#/components/schemas/Comment/properties/_id'
- * required:
- * - comment_id
- * responses:
- * 200:
- * description: Succeeded to remove specified comment.
- * content:
- * application/json:
- * schema:
- * properties:
- * ok:
- * $ref: '#/components/schemas/V1Response/properties/ok'
- * comment:
- * $ref: '#/components/schemas/Comment'
- * 403:
- * $ref: '#/components/responses/403'
- * 500:
- * $ref: '#/components/responses/500'
- */
- /**
- * @api {post} /comments.remove Remove specified comment
- * @apiName RemoveComment
- * @apiGroup Comment
- *
- * @apiParam {String} comment_id Comment Id.
- */
- api.remove = async function(req, res) {
- const commentId = req.body.comment_id;
- if (!commentId) {
- return Promise.resolve(res.json(ApiResponse.error('\'comment_id\' is undefined')));
- }
- try {
- /** @type {import('mongoose').HydratedDocument<import('~/interfaces/comment').IComment>} */
- const comment = await Comment.findById(commentId).exec();
- if (comment == null) {
- throw new Error('This comment does not exist.');
- }
- // check whether accessible
- const pageId = getIdStringForRef(comment.page);
- const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
- if (!isAccessible) {
- throw new Error('Current user is not accessible to this page.');
- }
- if (getIdStringForRef(req.user) !== getIdStringForRef(comment.creator)) {
- throw new Error('Current user is not operatable to this comment.');
- }
- await Comment.removeWithReplies(comment);
- await Page.updateCommentCount(comment.page);
- commentEvent.emit(CommentEvent.DELETE, comment);
- }
- catch (err) {
- return res.json(ApiResponse.error(err));
- }
- const parameters = { action: SupportedAction.ACTION_COMMENT_REMOVE };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.json(ApiResponse.success({}));
- };
- return actions;
- };
|