| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- // TODO remove this setting after implemented all
- import { SCOPE } from '@growi/core/dist/interfaces';
- import { ErrorV3 } from '@growi/core/dist/models';
- import express from 'express';
- import { SupportedAction } from '~/interfaces/activity';
- import { accessTokenParser } from '~/server/middlewares/access-token-parser';
- import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
- import adminRequiredFactory from '~/server/middlewares/admin-required';
- import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
- import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
- import loginRequiredFactory from '~/server/middlewares/login-required';
- import ShareLink from '~/server/models/share-link';
- import loggerFactory from '~/utils/logger';
- const logger = loggerFactory('growi:routes:apiv3:share-links');
- const router = express.Router();
- const { body, query, param } = require('express-validator');
- const validator = {};
- const today = new Date();
- /**
- * @swagger
- *
- * components:
- * schemas:
- * ShareLink:
- * type: object
- * properties:
- * _id:
- * type: string
- * description: The unique identifier of the share link
- * relatedPage:
- * type: object
- * properties:
- * _id:
- * type: string
- * description: The unique identifier of the related page
- * path:
- * type: string
- * description: The path of the related page
- * expiredAt:
- * type: string
- * format: date-time
- * description: The expiration date of the share link
- * description:
- * type: string
- * description: The description of the share link
- * createdAt:
- * type: string
- * format: date-time
- * description: The creation date of the share link
- * __v:
- * type: integer
- * description: The version key
- * ShareLinkSimple:
- * type: object
- * properties:
- * relatedPage:
- * type: string
- * description: The unique identifier of the related page
- * expiredAt:
- * type: string
- * format: date-time
- * description: The expiration date of the share link
- * description:
- * type: string
- * description: The description of the share link
- * createdAt:
- * type: string
- * format: date-time
- * description: The creation date of the share link
- * __v:
- * type: integer
- * description: The version key
- * _id:
- * type: string
- * description: The unique identifier of the share link
- */
- /** @param {import('~/server/crowi').default} crowi Crowi instance */
- module.exports = (crowi) => {
- const loginRequired = loginRequiredFactory(crowi);
- const adminRequired = adminRequiredFactory(crowi);
- const addActivity = generateAddActivityMiddleware(crowi);
- const { Page } = crowi.models;
- const activityEvent = crowi.events.activity;
- /**
- * middleware to limit link sharing
- */
- const linkSharingRequired = (req, res, next) => {
- const isLinkSharingDisabled = crowi.configManager.getConfig(
- 'security:disableLinkSharing',
- );
- logger.debug(`isLinkSharingDisabled: ${isLinkSharingDisabled}`);
- if (isLinkSharingDisabled) {
- return res.apiv3Err(
- new ErrorV3('Link sharing is disabled', 'link-sharing-disabled'),
- );
- }
- next();
- };
- validator.getShareLinks = [
- // validate the page id is MongoId
- query('relatedPage').isMongoId().withMessage('Page Id is required'),
- ];
- /**
- * @swagger
- *
- * paths:
- * /share-links/:
- * get:
- * tags: [ShareLinks]
- * security:
- * - cookieAuth: []
- * description: get share links
- * parameters:
- * - name: relatedPage
- * in: query
- * required: true
- * description: page id of share link
- * schema:
- * type: string
- * responses:
- * 200:
- * description: Succeeded to get share links
- * content:
- * application/json:
- * schema:
- * properties:
- * shareLinksResult:
- * type: array
- * items:
- * $ref: '#/components/schemas/ShareLink'
- */
- router.get(
- '/',
- accessTokenParser([SCOPE.READ.FEATURES.SHARE_LINK]),
- loginRequired,
- linkSharingRequired,
- validator.getShareLinks,
- apiV3FormValidator,
- async (req, res) => {
- const { relatedPage } = req.query;
- const page = await Page.findByIdAndViewer(relatedPage, req.user);
- if (page == null) {
- const msg = 'Page is not found or forbidden';
- logger.error('Error', msg);
- return res.apiv3Err(new ErrorV3(msg, 'get-shareLink-failed'));
- }
- try {
- const shareLinksResult = await ShareLink.find({
- relatedPage: { $eq: relatedPage },
- }).populate({ path: 'relatedPage', select: 'path' });
- return res.apiv3({ shareLinksResult });
- } catch (err) {
- const msg = 'Error occurred in get share link';
- logger.error('Error', err);
- return res.apiv3Err(new ErrorV3(msg, 'get-shareLink-failed'));
- }
- },
- );
- validator.shareLinkStatus = [
- // validate the page id is MongoId
- body('relatedPage').isMongoId().withMessage('Page Id is required'),
- // validate expireation date is not empty, is not before today and is date.
- body('expiredAt')
- .if((value) => value != null)
- .isAfter(today.toString())
- .withMessage('Your Selected date is past'),
- // validate the length of description is max 100.
- body('description')
- .isLength({ min: 0, max: 100 })
- .withMessage('Max length is 100'),
- ];
- /**
- * @swagger
- *
- * paths:
- * /share-links/:
- * post:
- * tags: [ShareLinks]
- * security:
- * - cookieAuth: []
- * description: Create new share link
- * requestBody:
- * content:
- * application/json:
- * schema:
- * required:
- * - relatedPage
- * properties:
- * relatedPage:
- * description: page id of share link
- * type: string
- * expiredAt:
- * description: expiration date of share link
- * type: string
- * description:
- * description: description of share link
- * type: string
- * responses:
- * 200:
- * description: Succeeded to create one share link
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/ShareLinkSimple'
- */
- router.post(
- '/',
- accessTokenParser([SCOPE.WRITE.FEATURES.SHARE_LINK]),
- loginRequired,
- excludeReadOnlyUser,
- linkSharingRequired,
- addActivity,
- validator.shareLinkStatus,
- apiV3FormValidator,
- async (req, res) => {
- const { relatedPage, expiredAt, description } = req.body;
- const page = await Page.findByIdAndViewer(relatedPage, req.user);
- if (page == null) {
- const msg = 'Page is not found or forbidden';
- logger.error('Error', msg);
- return res.apiv3Err(new ErrorV3(msg, 'post-shareLink-failed'));
- }
- try {
- const postedShareLink = await ShareLink.create({
- relatedPage,
- expiredAt,
- description,
- });
- activityEvent.emit('update', res.locals.activity._id, {
- action: SupportedAction.ACTION_SHARE_LINK_CREATE,
- });
- return res.apiv3(postedShareLink, 201);
- } catch (err) {
- const msg = 'Error occured in post share link';
- logger.error('Error', err);
- return res.apiv3Err(new ErrorV3(msg, 'post-shareLink-failed'));
- }
- },
- );
- validator.deleteShareLinks = [
- // validate the page id is MongoId
- query('relatedPage').isMongoId().withMessage('Page Id is required'),
- ];
- /**
- * @swagger
- *
- * /share-links/:
- * delete:
- * tags: [ShareLinks]
- * security:
- * - cookieAuth: []
- * summary: delete all share links related one page
- * description: delete all share links related one page
- * parameters:
- * - name: relatedPage
- * in: query
- * required: true
- * description: page id of share link
- * schema:
- * type: string
- * responses:
- * 200:
- * description: Succeeded to delete o all share links related one page
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/ShareLinkSimple'
- */
- router.delete(
- '/',
- accessTokenParser([SCOPE.WRITE.FEATURES.SHARE_LINK]),
- loginRequired,
- excludeReadOnlyUser,
- addActivity,
- validator.deleteShareLinks,
- apiV3FormValidator,
- async (req, res) => {
- const { relatedPage } = req.query;
- const page = await Page.findByIdAndViewer(relatedPage, req.user);
- if (page == null) {
- const msg = 'Page is not found or forbidden';
- logger.error('Error', msg);
- return res.apiv3Err(
- new ErrorV3(msg, 'delete-shareLinks-for-page-failed'),
- );
- }
- try {
- const deletedShareLink = await ShareLink.deleteMany({
- relatedPage: { $eq: relatedPage },
- });
- activityEvent.emit('update', res.locals.activity._id, {
- action: SupportedAction.ACTION_SHARE_LINK_DELETE_BY_PAGE,
- });
- return res.apiv3(deletedShareLink);
- } catch (err) {
- const msg = 'Error occured in delete share link';
- logger.error('Error', err);
- return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
- }
- },
- );
- /**
- * @swagger
- *
- * /share-links/all:
- * delete:
- * tags: [ShareLink Management]
- * security:
- * - cookieAuth: []
- * summary: delete all share links
- * description: delete all share links
- * responses:
- * 200:
- * description: Succeeded to remove all share links
- * content:
- * application/json:
- * schema:
- * properties:
- * deletedCount:
- * type: integer
- * description: The number of share links deleted
- */
- router.delete(
- '/all',
- accessTokenParser([SCOPE.WRITE.FEATURES.SHARE_LINK]),
- loginRequired,
- adminRequired,
- addActivity,
- async (req, res) => {
- try {
- const deletedShareLink = await ShareLink.deleteMany({});
- const { deletedCount } = deletedShareLink;
- activityEvent.emit('update', res.locals.activity._id, {
- action: SupportedAction.ACTION_SHARE_LINK_ALL_DELETE,
- });
- return res.apiv3({ deletedCount });
- } catch (err) {
- const msg = 'Error occurred in delete all share link';
- logger.error('Error', err);
- return res.apiv3Err(new ErrorV3(msg, 'delete-all-shareLink-failed'));
- }
- },
- );
- validator.deleteShareLink = [
- param('id').isMongoId().withMessage('ShareLink Id is required'),
- ];
- /**
- * @swagger
- *
- * /share-links/{id}:
- * delete:
- * tags: [ShareLinks]
- * security:
- * - cookieAuth: []
- * description: delete one share link related one page
- * parameters:
- * - name: id
- * in: path
- * required: true
- * description: id of share link
- * schema:
- * type: string
- * responses:
- * 200:
- * description: Succeeded to delete one share link
- */
- router.delete(
- '/:id',
- accessTokenParser([SCOPE.WRITE.FEATURES.SHARE_LINK]),
- loginRequired,
- excludeReadOnlyUser,
- addActivity,
- validator.deleteShareLink,
- apiV3FormValidator,
- async (req, res) => {
- const { id } = req.params;
- const { user } = req;
- try {
- const shareLinkToDelete = await ShareLink.findOne({ _id: id });
- // check permission
- if (!user.isAdmin) {
- const page = await Page.findByIdAndViewer(
- shareLinkToDelete.relatedPage,
- user,
- );
- const isPageExists =
- (await Page.count({ _id: shareLinkToDelete.relatedPage })) > 0;
- if (page == null && isPageExists) {
- const msg = 'Page is not found or forbidden';
- logger.error('Error', msg);
- return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
- }
- }
- // remove
- await shareLinkToDelete.remove();
- activityEvent.emit('update', res.locals.activity._id, {
- action: SupportedAction.ACTION_SHARE_LINK_DELETE,
- });
- return res.apiv3({ deletedShareLink: shareLinkToDelete });
- } catch (err) {
- const msg = 'Error occurred in delete share link';
- logger.error('Error', err);
- return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
- }
- },
- );
- return router;
- };
|