Просмотр исходного кода

Merge pull request #10537 from growilabs/support/156162-174668-app-apiv3-routes-biome-4

support: Configure biome for apiv3 js files
Yuki Takei 4 месяцев назад
Родитель
Сommit
e25f762624

+ 1 - 0
apps/app/.eslintrc.js

@@ -72,6 +72,7 @@ module.exports = {
     'src/server/routes/apiv3/app-settings/**',
     'src/server/routes/apiv3/app-settings/**',
     'src/server/routes/apiv3/page/**',
     'src/server/routes/apiv3/page/**',
     'src/server/routes/apiv3/*.ts',
     'src/server/routes/apiv3/*.ts',
+    'src/server/routes/apiv3/*.js',
   ],
   ],
   settings: {
   settings: {
     // resolve path aliases by eslint-import-resolver-typescript
     // resolve path aliases by eslint-import-resolver-typescript

+ 101 - 45
apps/app/src/server/routes/apiv3/attachment.js

@@ -9,7 +9,10 @@ import { SupportedAction } from '~/interfaces/activity';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Attachment } from '~/server/models/attachment';
 import { Attachment } from '~/server/models/attachment';
-import { serializePageSecurely, serializeRevisionSecurely } from '~/server/models/serializers';
+import {
+  serializePageSecurely,
+  serializeRevisionSecurely,
+} from '~/server/models/serializers';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -17,14 +20,10 @@ import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { certifySharedPageAttachmentMiddleware } from '../../middlewares/certify-shared-page-attachment';
 import { certifySharedPageAttachmentMiddleware } from '../../middlewares/certify-shared-page-attachment';
 import { excludeReadOnlyUser } from '../../middlewares/exclude-read-only-user';
 import { excludeReadOnlyUser } from '../../middlewares/exclude-read-only-user';
 
 
-
 const logger = loggerFactory('growi:routes:apiv3:attachment'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:attachment'); // eslint-disable-line no-unused-vars
 
 
 const router = express.Router();
 const router = express.Router();
-const {
-  query, param, body,
-} = require('express-validator');
-
+const { query, param, body } = require('express-validator');
 
 
 /**
 /**
  * @swagger
  * @swagger
@@ -135,8 +134,13 @@ const {
  */
  */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequired = require('../../middlewares/login-required')(crowi, true);
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const loginRequired = require('../../middlewares/login-required')(
+    crowi,
+    true,
+  );
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
   const Page = crowi.model('Page');
   const Page = crowi.model('Page');
   const User = crowi.model('User');
   const User = crowi.model('User');
   const { attachmentService } = crowi;
   const { attachmentService } = crowi;
@@ -151,14 +155,26 @@ module.exports = (crowi) => {
     ],
     ],
     retrieveAttachments: [
     retrieveAttachments: [
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageId').isMongoId().withMessage('pageId is required'),
-      query('pageNumber').optional().isInt().withMessage('pageNumber must be a number'),
-      query('limit').optional().isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'),
+      query('pageNumber')
+        .optional()
+        .isInt()
+        .withMessage('pageNumber must be a number'),
+      query('limit')
+        .optional()
+        .isInt({ max: 100 })
+        .withMessage('You should set less than 100 or not to set limit.'),
     ],
     ],
     retrieveFileLimit: [
     retrieveFileLimit: [
-      query('fileSize').isNumeric().exists({ checkNull: true }).withMessage('fileSize is required'),
+      query('fileSize')
+        .isNumeric()
+        .exists({ checkNull: true })
+        .withMessage('fileSize is required'),
     ],
     ],
     retrieveAddAttachment: [
     retrieveAddAttachment: [
-      body('page_id').isMongoId().exists({ checkNull: true }).withMessage('page_id is required'),
+      body('page_id')
+        .isMongoId()
+        .exists({ checkNull: true })
+        .withMessage('page_id is required'),
     ],
     ],
   };
   };
 
 
@@ -199,18 +215,29 @@ module.exports = (crowi) => {
    *                  type: object
    *                  type: object
    *                  $ref: '#/components/schemas/AttachmentPaginateResult'
    *                  $ref: '#/components/schemas/AttachmentPaginateResult'
    */
    */
-  router.get('/list',
-    accessTokenParser([SCOPE.READ.FEATURES.ATTACHMENT], { acceptLegacy: true }), loginRequired, validator.retrieveAttachments, apiV3FormValidator,
-    async(req, res) => {
-
-      const limit = req.query.limit || await crowi.configManager.getConfig('customize:showPageLimitationS') || 10;
+  router.get(
+    '/list',
+    accessTokenParser([SCOPE.READ.FEATURES.ATTACHMENT], { acceptLegacy: true }),
+    loginRequired,
+    validator.retrieveAttachments,
+    apiV3FormValidator,
+    async (req, res) => {
+      const limit =
+        req.query.limit ||
+        (await crowi.configManager.getConfig(
+          'customize:showPageLimitationS',
+        )) ||
+        10;
       const pageNumber = req.query.pageNumber || 1;
       const pageNumber = req.query.pageNumber || 1;
       const offset = (pageNumber - 1) * limit;
       const offset = (pageNumber - 1) * limit;
 
 
       try {
       try {
         const pageId = req.query.pageId;
         const pageId = req.query.pageId;
         // check whether accessible
         // check whether accessible
-        const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
+        const isAccessible = await Page.isAccessiblePageByViewer(
+          pageId,
+          req.user,
+        );
         if (!isAccessible) {
         if (!isAccessible) {
           const msg = 'Current user is not accessible to this page.';
           const msg = 'Current user is not accessible to this page.';
           return res.apiv3Err(new ErrorV3(msg, 'attachment-list-failed'), 403);
           return res.apiv3Err(new ErrorV3(msg, 'attachment-list-failed'), 403);
@@ -234,13 +261,12 @@ module.exports = (crowi) => {
         });
         });
 
 
         return res.apiv3({ paginateResult });
         return res.apiv3({ paginateResult });
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('Attachment not found', err);
         logger.error('Attachment not found', err);
         return res.apiv3Err(err, 500);
         return res.apiv3Err(err, 500);
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -274,19 +300,23 @@ module.exports = (crowi) => {
    *          500:
    *          500:
    *            $ref: '#/components/responses/InternalServerError'
    *            $ref: '#/components/responses/InternalServerError'
    */
    */
-  router.get('/limit',
-    accessTokenParser([SCOPE.READ.FEATURES.ATTACHMENT], { acceptLegacy: true }), loginRequiredStrictly, validator.retrieveFileLimit, apiV3FormValidator,
-    async(req, res) => {
+  router.get(
+    '/limit',
+    accessTokenParser([SCOPE.READ.FEATURES.ATTACHMENT], { acceptLegacy: true }),
+    loginRequiredStrictly,
+    validator.retrieveFileLimit,
+    apiV3FormValidator,
+    async (req, res) => {
       const { fileUploadService } = crowi;
       const { fileUploadService } = crowi;
       const fileSize = Number(req.query.fileSize);
       const fileSize = Number(req.query.fileSize);
       try {
       try {
         return res.apiv3(await fileUploadService.checkLimit(fileSize));
         return res.apiv3(await fileUploadService.checkLimit(fileSize));
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('File limit retrieval failed', err);
         logger.error('File limit retrieval failed', err);
         return res.apiv3Err(err, 500);
         return res.apiv3Err(err, 500);
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -342,11 +372,19 @@ module.exports = (crowi) => {
    *          500:
    *          500:
    *            $ref: '#/components/responses/InternalServerError'
    *            $ref: '#/components/responses/InternalServerError'
    */
    */
-  router.post('/', uploads.single('file'), accessTokenParser([SCOPE.WRITE.FEATURES.ATTACHMENT], { acceptLegacy: true }),
-    loginRequiredStrictly, excludeReadOnlyUser, validator.retrieveAddAttachment, apiV3FormValidator, addActivity,
+  router.post(
+    '/',
+    uploads.single('file'),
+    accessTokenParser([SCOPE.WRITE.FEATURES.ATTACHMENT], {
+      acceptLegacy: true,
+    }),
+    loginRequiredStrictly,
+    excludeReadOnlyUser,
+    validator.retrieveAddAttachment,
+    apiV3FormValidator,
+    addActivity,
     // Removed autoReap middleware to use file data in asynchronous processes. Instead, implemented file deletion after asynchronous processes complete
     // Removed autoReap middleware to use file data in asynchronous processes. Instead, implemented file deletion after asynchronous processes complete
-    async(req, res) => {
-
+    async (req, res) => {
       const pageId = req.body.page_id;
       const pageId = req.body.page_id;
 
 
       // check params
       // check params
@@ -359,12 +397,21 @@ module.exports = (crowi) => {
         const page = await Page.findOne({ _id: { $eq: pageId } });
         const page = await Page.findOne({ _id: { $eq: pageId } });
 
 
         // check the user is accessible
         // check the user is accessible
-        const isAccessible = await Page.isAccessiblePageByViewer(page.id, req.user);
+        const isAccessible = await Page.isAccessiblePageByViewer(
+          page.id,
+          req.user,
+        );
         if (!isAccessible) {
         if (!isAccessible) {
           return res.apiv3Err(`Forbidden to access to the page '${page.id}'`);
           return res.apiv3Err(`Forbidden to access to the page '${page.id}'`);
         }
         }
 
 
-        const attachment = await attachmentService.createAttachment(file, req.user, pageId, AttachmentType.WIKI_PAGE, () => autoReap(req, res, () => {}));
+        const attachment = await attachmentService.createAttachment(
+          file,
+          req.user,
+          pageId,
+          AttachmentType.WIKI_PAGE,
+          () => autoReap(req, res, () => {}),
+        );
 
 
         const result = {
         const result = {
           page: serializePageSecurely(page),
           page: serializePageSecurely(page),
@@ -372,15 +419,17 @@ module.exports = (crowi) => {
           attachment: attachment.toObject({ virtuals: true }),
           attachment: attachment.toObject({ virtuals: true }),
         };
         };
 
 
-        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ATTACHMENT_ADD });
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_ATTACHMENT_ADD,
+        });
 
 
         res.apiv3(result);
         res.apiv3(result);
-      }
-      catch (err) {
+      } catch (err) {
         logger.error(err);
         logger.error(err);
         return res.apiv3Err(err.message);
         return res.apiv3Err(err.message);
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -407,13 +456,20 @@ module.exports = (crowi) => {
    *            schema:
    *            schema:
    *              type: string
    *              type: string
    */
    */
-  router.get('/:id', accessTokenParser([SCOPE.READ.FEATURES.ATTACHMENT], { acceptLegacy: true }), certifySharedPageAttachmentMiddleware, loginRequired,
-    validator.retrieveAttachment, apiV3FormValidator,
-    async(req, res) => {
+  router.get(
+    '/:id',
+    accessTokenParser([SCOPE.READ.FEATURES.ATTACHMENT], { acceptLegacy: true }),
+    certifySharedPageAttachmentMiddleware,
+    loginRequired,
+    validator.retrieveAttachment,
+    apiV3FormValidator,
+    async (req, res) => {
       try {
       try {
         const attachmentId = req.params.id;
         const attachmentId = req.params.id;
 
 
-        const attachment = await Attachment.findById(attachmentId).populate('creator').exec();
+        const attachment = await Attachment.findById(attachmentId)
+          .populate('creator')
+          .exec();
 
 
         if (attachment == null) {
         if (attachment == null) {
           const message = 'Attachment not found';
           const message = 'Attachment not found';
@@ -425,12 +481,12 @@ module.exports = (crowi) => {
         }
         }
 
 
         return res.apiv3({ attachment });
         return res.apiv3({ attachment });
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('Attachment retrieval failed', err);
         logger.error('Attachment retrieval failed', err);
         return res.apiv3Err(err, 500);
         return res.apiv3Err(err, 500);
       }
       }
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 85 - 55
apps/app/src/server/routes/apiv3/bookmarks.js

@@ -1,7 +1,7 @@
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 
 
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
-import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { serializeBookmarkSecurely } from '~/server/models/serializers/bookmark-serializer';
 import { serializeBookmarkSecurely } from '~/server/models/serializers/bookmark-serializer';
@@ -16,7 +16,6 @@ const logger = loggerFactory('growi:routes:apiv3:bookmarks'); // eslint-disable-
 const express = require('express');
 const express = require('express');
 const { body, query, param } = require('express-validator');
 const { body, query, param } = require('express-validator');
 
 
-
 const router = express.Router();
 const router = express.Router();
 
 
 /**
 /**
@@ -85,8 +84,13 @@ const router = express.Router();
  */
  */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
-  const loginRequired = require('../../middlewares/login-required')(crowi, true);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
+  const loginRequired = require('../../middlewares/login-required')(
+    crowi,
+    true,
+  );
   const addActivity = generateAddActivityMiddleware(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 
 
   const activityEvent = crowi.event('activity');
   const activityEvent = crowi.event('activity');
@@ -94,13 +98,8 @@ module.exports = (crowi) => {
   const { Page, Bookmark } = crowi.models;
   const { Page, Bookmark } = crowi.models;
 
 
   const validator = {
   const validator = {
-    bookmarks: [
-      body('pageId').isString(),
-      body('bool').isBoolean(),
-    ],
-    bookmarkInfo: [
-      query('pageId').isMongoId(),
-    ],
+    bookmarks: [body('pageId').isString(), body('bool').isBoolean()],
+    bookmarkInfo: [query('pageId').isMongoId()],
   };
   };
 
 
   /**
   /**
@@ -125,24 +124,32 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/BookmarkInfo'
    *                  $ref: '#/components/schemas/BookmarkInfo'
    */
    */
-  router.get('/info',
-    accessTokenParser([SCOPE.READ.FEATURES.BOOKMARK], { acceptLegacy: true }), loginRequired, validator.bookmarkInfo, apiV3FormValidator, async(req, res) => {
+  router.get(
+    '/info',
+    accessTokenParser([SCOPE.READ.FEATURES.BOOKMARK], { acceptLegacy: true }),
+    loginRequired,
+    validator.bookmarkInfo,
+    apiV3FormValidator,
+    async (req, res) => {
       const { user } = req;
       const { user } = req;
       const { pageId } = req.query;
       const { pageId } = req.query;
 
 
       const responsesParams = {};
       const responsesParams = {};
 
 
       try {
       try {
-        const bookmarks = await Bookmark.find({ page: pageId }).populate('user');
+        const bookmarks = await Bookmark.find({ page: pageId }).populate(
+          'user',
+        );
         let users = [];
         let users = [];
         if (bookmarks.length > 0) {
         if (bookmarks.length > 0) {
-          users = bookmarks.map(bookmark => serializeUserSecurely(bookmark.user));
+          users = bookmarks.map((bookmark) =>
+            serializeUserSecurely(bookmark.user),
+          );
         }
         }
         responsesParams.sumOfBookmarks = bookmarks.length;
         responsesParams.sumOfBookmarks = bookmarks.length;
         responsesParams.bookmarkedUsers = users;
         responsesParams.bookmarkedUsers = users;
         responsesParams.pageId = pageId;
         responsesParams.pageId = pageId;
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('get-bookmark-document-failed', err);
         logger.error('get-bookmark-document-failed', err);
         return res.apiv3Err(err, 500);
         return res.apiv3Err(err, 500);
       }
       }
@@ -154,15 +161,14 @@ module.exports = (crowi) => {
 
 
       try {
       try {
         const bookmark = await Bookmark.findByPageIdAndUserId(pageId, user._id);
         const bookmark = await Bookmark.findByPageIdAndUserId(pageId, user._id);
-        responsesParams.isBookmarked = (bookmark != null);
+        responsesParams.isBookmarked = bookmark != null;
         return res.apiv3(responsesParams);
         return res.apiv3(responsesParams);
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('get-bookmark-state-failed', err);
         logger.error('get-bookmark-state-failed', err);
         return res.apiv3Err(err, 500);
         return res.apiv3Err(err, 500);
       }
       }
-
-    });
+    },
+  );
 
 
   // select page from bookmark where userid = userid
   // select page from bookmark where userid = userid
   /**
   /**
@@ -192,39 +198,49 @@ module.exports = (crowi) => {
     param('userId').isMongoId().withMessage('userId is required'),
     param('userId').isMongoId().withMessage('userId is required'),
   ];
   ];
 
 
-  router.get('/:userId',
+  router.get(
+    '/:userId',
     accessTokenParser([SCOPE.READ.FEATURES.BOOKMARK], { acceptLegacy: true }),
     accessTokenParser([SCOPE.READ.FEATURES.BOOKMARK], { acceptLegacy: true }),
-    loginRequired, validator.userBookmarkList, apiV3FormValidator, async(req, res) => {
+    loginRequired,
+    validator.userBookmarkList,
+    apiV3FormValidator,
+    async (req, res) => {
       const { userId } = req.params;
       const { userId } = req.params;
 
 
       if (userId == null) {
       if (userId == null) {
         return res.apiv3Err('User id is not found or forbidden', 400);
         return res.apiv3Err('User id is not found or forbidden', 400);
       }
       }
       try {
       try {
-        const bookmarkIdsInFolders = await BookmarkFolder.distinct('bookmarks', { owner: userId });
+        const bookmarkIdsInFolders = await BookmarkFolder.distinct(
+          'bookmarks',
+          { owner: userId },
+        );
         const userRootBookmarks = await Bookmark.find({
         const userRootBookmarks = await Bookmark.find({
           _id: { $nin: bookmarkIdsInFolders },
           _id: { $nin: bookmarkIdsInFolders },
           user: userId,
           user: userId,
-        }).populate({
-          path: 'page',
-          model: 'Page',
-          populate: {
-            path: 'lastUpdateUser',
-            model: 'User',
-          },
-        }).exec();
+        })
+          .populate({
+            path: 'page',
+            model: 'Page',
+            populate: {
+              path: 'lastUpdateUser',
+              model: 'User',
+            },
+          })
+          .exec();
 
 
         // serialize Bookmark
         // serialize Bookmark
-        const serializedUserRootBookmarks = userRootBookmarks.map(bookmark => serializeBookmarkSecurely(bookmark));
+        const serializedUserRootBookmarks = userRootBookmarks.map((bookmark) =>
+          serializeBookmarkSecurely(bookmark),
+        );
 
 
         return res.apiv3({ userRootBookmarks: serializedUserRootBookmarks });
         return res.apiv3({ userRootBookmarks: serializedUserRootBookmarks });
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('get-bookmark-failed', err);
         logger.error('get-bookmark-failed', err);
         return res.apiv3Err(err, 500);
         return res.apiv3Err(err, 500);
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -250,9 +266,14 @@ module.exports = (crowi) => {
    *                    bookmark:
    *                    bookmark:
    *                      $ref: '#/components/schemas/Bookmark'
    *                      $ref: '#/components/schemas/Bookmark'
    */
    */
-  router.put('/',
-    accessTokenParser([SCOPE.WRITE.FEATURES.BOOKMARK], { acceptLegacy: true }), loginRequiredStrictly, addActivity, validator.bookmarks, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/',
+    accessTokenParser([SCOPE.WRITE.FEATURES.BOOKMARK], { acceptLegacy: true }),
+    loginRequiredStrictly,
+    addActivity,
+    validator.bookmarks,
+    apiV3FormValidator,
+    async (req, res) => {
       const { pageId, bool } = req.body;
       const { pageId, bool } = req.body;
       const userId = req.user?._id;
       const userId = req.user?._id;
 
 
@@ -273,22 +294,22 @@ module.exports = (crowi) => {
         if (bookmark == null) {
         if (bookmark == null) {
           if (bool) {
           if (bool) {
             bookmark = await Bookmark.add(page, req.user);
             bookmark = await Bookmark.add(page, req.user);
+          } else {
+            logger.warn(
+              `Removing the bookmark for ${page._id} by ${req.user._id} failed because the bookmark does not exist.`,
+            );
           }
           }
-          else {
-            logger.warn(`Removing the bookmark for ${page._id} by ${req.user._id} failed because the bookmark does not exist.`);
-          }
-        }
-        else {
-        // eslint-disable-next-line no-lonely-if
+        } else {
+          // eslint-disable-next-line no-lonely-if
           if (bool) {
           if (bool) {
-            logger.warn(`Adding the bookmark for ${page._id} by ${req.user._id} failed because the bookmark has already exist.`);
-          }
-          else {
+            logger.warn(
+              `Adding the bookmark for ${page._id} by ${req.user._id} failed because the bookmark has already exist.`,
+            );
+          } else {
             bookmark = await Bookmark.removeBookmark(page, req.user);
             bookmark = await Bookmark.removeBookmark(page, req.user);
           }
           }
         }
         }
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('update-bookmark-failed', err);
         logger.error('update-bookmark-failed', err);
         return res.apiv3Err(err, 500);
         return res.apiv3Err(err, 500);
       }
       }
@@ -301,13 +322,22 @@ module.exports = (crowi) => {
       const parameters = {
       const parameters = {
         targetModel: SupportedTargetModel.MODEL_PAGE,
         targetModel: SupportedTargetModel.MODEL_PAGE,
         target: page,
         target: page,
-        action: bool ? SupportedAction.ACTION_PAGE_BOOKMARK : SupportedAction.ACTION_PAGE_UNBOOKMARK,
+        action: bool
+          ? SupportedAction.ACTION_PAGE_BOOKMARK
+          : SupportedAction.ACTION_PAGE_UNBOOKMARK,
       };
       };
 
 
-      activityEvent.emit('update', res.locals.activity._id, parameters, page, preNotifyService.generatePreNotify);
+      activityEvent.emit(
+        'update',
+        res.locals.activity._id,
+        parameters,
+        page,
+        preNotifyService.generatePreNotify,
+      );
 
 
       return res.apiv3({ bookmark });
       return res.apiv3({ bookmark });
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 424 - 221
apps/app/src/server/routes/apiv3/customize-setting.js

@@ -1,6 +1,7 @@
 /* eslint-disable no-unused-vars */
 /* eslint-disable no-unused-vars */
 
 
 import { GrowiPluginType } from '@growi/core';
 import { GrowiPluginType } from '@growi/core';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import express from 'express';
 import express from 'express';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
@@ -8,7 +9,6 @@ import multer from 'multer';
 
 
 import { GrowiPlugin } from '~/features/growi-plugin/server/models';
 import { GrowiPlugin } from '~/features/growi-plugin/server/models';
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '@growi/core/dist/interfaces';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Attachment } from '~/server/models/attachment';
 import { Attachment } from '~/server/models/attachment';
@@ -18,12 +18,10 @@ import loggerFactory from '~/utils/logger';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
 
-
 const logger = loggerFactory('growi:routes:apiv3:customize-setting');
 const logger = loggerFactory('growi:routes:apiv3:customize-setting');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-
 /**
 /**
  * @swagger
  * @swagger
  *
  *
@@ -195,7 +193,9 @@ const router = express.Router();
  */
  */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 
 
@@ -204,12 +204,8 @@ module.exports = (crowi) => {
   const { customizeService, attachmentService } = crowi;
   const { customizeService, attachmentService } = crowi;
   const uploads = multer({ dest: `${crowi.tmpDir}uploads` });
   const uploads = multer({ dest: `${crowi.tmpDir}uploads` });
   const validator = {
   const validator = {
-    layout: [
-      body('isContainerFluid').isBoolean(),
-    ],
-    theme: [
-      body('theme').isString(),
-    ],
+    layout: [body('isContainerFluid').isBoolean()],
+    theme: [body('theme').isString()],
     sidebar: [
     sidebar: [
       body('isSidebarCollapsedMode').isBoolean(),
       body('isSidebarCollapsedMode').isBoolean(),
       body('isSidebarClosedAtDockMode').optional().isBoolean(),
       body('isSidebarClosedAtDockMode').optional().isBoolean(),
@@ -226,27 +222,28 @@ module.exports = (crowi) => {
       body('isSearchScopeChildrenAsDefault').isBoolean(),
       body('isSearchScopeChildrenAsDefault').isBoolean(),
       body('showPageSideAuthors').isBoolean(),
       body('showPageSideAuthors').isBoolean(),
     ],
     ],
-    CustomizePresentation: [
-      body('isEnabledMarp').isBoolean(),
-    ],
-    customizeTitle: [
-      body('customizeTitle').isString(),
-    ],
+    CustomizePresentation: [body('isEnabledMarp').isBoolean()],
+    customizeTitle: [body('customizeTitle').isString()],
     highlight: [
     highlight: [
-      body('highlightJsStyle').isString().isIn([
-        'github', 'github-gist', 'atom-one-light', 'xcode', 'vs', 'atom-one-dark', 'hybrid', 'monokai', 'tomorrow-night', 'vs2015',
-      ]),
+      body('highlightJsStyle')
+        .isString()
+        .isIn([
+          'github',
+          'github-gist',
+          'atom-one-light',
+          'xcode',
+          'vs',
+          'atom-one-dark',
+          'hybrid',
+          'monokai',
+          'tomorrow-night',
+          'vs2015',
+        ]),
       body('highlightJsStyleBorder').isBoolean(),
       body('highlightJsStyleBorder').isBoolean(),
     ],
     ],
-    customizeScript: [
-      body('customizeScript').isString(),
-    ],
-    customizeCss: [
-      body('customizeCss').isString(),
-    ],
-    customizeNoscript: [
-      body('customizeNoscript').isString(),
-    ],
+    customizeScript: [body('customizeScript').isString()],
+    customizeCss: [body('customizeCss').isString()],
+    customizeNoscript: [body('customizeNoscript').isString()],
     logo: [
     logo: [
       body('isDefaultLogo').isBoolean().optional({ nullable: true }),
       body('isDefaultLogo').isBoolean().optional({ nullable: true }),
       body('customizedLogoSrc').isString().optional({ nullable: true }),
       body('customizedLogoSrc').isString().optional({ nullable: true }),
@@ -275,29 +272,57 @@ module.exports = (crowi) => {
    *                      description: customize params
    *                      description: customize params
    *                      $ref: '#/components/schemas/CustomizeSetting'
    *                      $ref: '#/components/schemas/CustomizeSetting'
    */
    */
-  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, async(req, res) => {
-    const customizeParams = {
-      isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
-      isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
-      pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
-      pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
-      pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
-      pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
-      isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
-      isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
-      showPageSideAuthors: await configManager.getConfig('customize:showPageSideAuthors'),
-      isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
-      isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
-      styleName: await configManager.getConfig('customize:highlightJsStyle'),
-      styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
-      customizeTitle: await configManager.getConfig('customize:title'),
-      customizeScript: await configManager.getConfig('customize:script'),
-      customizeCss: await configManager.getConfig('customize:css'),
-      customizeNoscript: await configManager.getConfig('customize:noscript'),
-    };
+  router.get(
+    '/',
+    accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      const customizeParams = {
+        isEnabledTimeline: await configManager.getConfig(
+          'customize:isEnabledTimeline',
+        ),
+        isEnabledAttachTitleHeader: await configManager.getConfig(
+          'customize:isEnabledAttachTitleHeader',
+        ),
+        pageLimitationS: await configManager.getConfig(
+          'customize:showPageLimitationS',
+        ),
+        pageLimitationM: await configManager.getConfig(
+          'customize:showPageLimitationM',
+        ),
+        pageLimitationL: await configManager.getConfig(
+          'customize:showPageLimitationL',
+        ),
+        pageLimitationXL: await configManager.getConfig(
+          'customize:showPageLimitationXL',
+        ),
+        isEnabledStaleNotification: await configManager.getConfig(
+          'customize:isEnabledStaleNotification',
+        ),
+        isAllReplyShown: await configManager.getConfig(
+          'customize:isAllReplyShown',
+        ),
+        showPageSideAuthors: await configManager.getConfig(
+          'customize:showPageSideAuthors',
+        ),
+        isSearchScopeChildrenAsDefault: await configManager.getConfig(
+          'customize:isSearchScopeChildrenAsDefault',
+        ),
+        isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
+        styleName: await configManager.getConfig('customize:highlightJsStyle'),
+        styleBorder: await configManager.getConfig(
+          'customize:highlightJsStyleBorder',
+        ),
+        customizeTitle: await configManager.getConfig('customize:title'),
+        customizeScript: await configManager.getConfig('customize:script'),
+        customizeCss: await configManager.getConfig('customize:css'),
+        customizeNoscript: await configManager.getConfig('customize:noscript'),
+      };
 
 
-    return res.apiv3({ customizeParams });
-  });
+      return res.apiv3({ customizeParams });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -317,17 +342,24 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/CustomizeLayout'
    *                  $ref: '#/components/schemas/CustomizeLayout'
    */
    */
-  router.get('/layout', accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, async(req, res) => {
-    try {
-      const isContainerFluid = await configManager.getConfig('customize:isContainerFluid');
-      return res.apiv3({ isContainerFluid });
-    }
-    catch (err) {
-      const msg = 'Error occurred in getting layout';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'get-layout-failed'));
-    }
-  });
+  router.get(
+    '/layout',
+    accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      try {
+        const isContainerFluid = await configManager.getConfig(
+          'customize:isContainerFluid',
+        );
+        return res.apiv3({ isContainerFluid });
+      } catch (err) {
+        const msg = 'Error occurred in getting layout';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'get-layout-failed'));
+      }
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -356,9 +388,15 @@ module.exports = (crowi) => {
    *                      description: customized params
    *                      description: customized params
    *                      $ref: '#/components/schemas/CustomizeLayout'
    *                      $ref: '#/components/schemas/CustomizeLayout'
    */
    */
-  router.put('/layout', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.layout, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/layout',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.layout,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:isContainerFluid': req.body.isContainerFluid,
         'customize:isContainerFluid': req.body.isContainerFluid,
       };
       };
@@ -366,20 +404,24 @@ module.exports = (crowi) => {
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const customizedParams = {
         const customizedParams = {
-          isContainerFluid: await configManager.getConfig('customize:isContainerFluid'),
+          isContainerFluid: await configManager.getConfig(
+            'customize:isContainerFluid',
+          ),
         };
         };
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_LAYOUT_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_LAYOUT_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating layout';
         const msg = 'Error occurred in updating layout';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-layout-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-layout-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -408,26 +450,31 @@ module.exports = (crowi) => {
    *                      items:
    *                      items:
    *                        $ref: '#/components/schemas/ThemesMetadata'
    *                        $ref: '#/components/schemas/ThemesMetadata'
    */
    */
-  router.get('/theme', accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]), loginRequiredStrictly, async(req, res) => {
-
-    try {
-      const currentTheme = await configManager.getConfig('customize:theme');
+  router.get(
+    '/theme',
+    accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    async (req, res) => {
+      try {
+        const currentTheme = await configManager.getConfig('customize:theme');
 
 
-      // retrieve plugin manifests
-      const themePlugins = await GrowiPlugin.findEnabledPluginsByType(GrowiPluginType.Theme);
+        // retrieve plugin manifests
+        const themePlugins = await GrowiPlugin.findEnabledPluginsByType(
+          GrowiPluginType.Theme,
+        );
 
 
-      const pluginThemesMetadatas = themePlugins
-        .map(themePlugin => themePlugin.meta.themes)
-        .flat();
+        const pluginThemesMetadatas = themePlugins.flatMap(
+          (themePlugin) => themePlugin.meta.themes,
+        );
 
 
-      return res.apiv3({ currentTheme, pluginThemesMetadatas });
-    }
-    catch (err) {
-      const msg = 'Error occurred in getting theme';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'get-theme-failed'));
-    }
-  });
+        return res.apiv3({ currentTheme, pluginThemesMetadatas });
+      } catch (err) {
+        const msg = 'Error occurred in getting theme';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'get-theme-failed'));
+      }
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -456,8 +503,15 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeTheme'
    *                      $ref: '#/components/schemas/CustomizeTheme'
    */
    */
-  router.put('/theme', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/theme',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.theme,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:theme': req.body.theme,
         'customize:theme': req.body.theme,
       };
       };
@@ -468,16 +522,18 @@ module.exports = (crowi) => {
           theme: await configManager.getConfig('customize:theme'),
           theme: await configManager.getConfig('customize:theme'),
         };
         };
         customizeService.initGrowiTheme();
         customizeService.initGrowiTheme();
-        const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_THEME_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating theme';
         const msg = 'Error occurred in updating theme';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-theme-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-theme-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -497,19 +553,27 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/CustomizeSidebar'
    *                  $ref: '#/components/schemas/CustomizeSidebar'
    */
    */
-  router.get('/sidebar', accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, async(req, res) => {
-
-    try {
-      const isSidebarCollapsedMode = await configManager.getConfig('customize:isSidebarCollapsedMode');
-      const isSidebarClosedAtDockMode = await configManager.getConfig('customize:isSidebarClosedAtDockMode');
-      return res.apiv3({ isSidebarCollapsedMode, isSidebarClosedAtDockMode });
-    }
-    catch (err) {
-      const msg = 'Error occurred in getting sidebar';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'get-sidebar-failed'));
-    }
-  });
+  router.get(
+    '/sidebar',
+    accessTokenParser([SCOPE.READ.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      try {
+        const isSidebarCollapsedMode = await configManager.getConfig(
+          'customize:isSidebarCollapsedMode',
+        );
+        const isSidebarClosedAtDockMode = await configManager.getConfig(
+          'customize:isSidebarClosedAtDockMode',
+        );
+        return res.apiv3({ isSidebarCollapsedMode, isSidebarClosedAtDockMode });
+      } catch (err) {
+        const msg = 'Error occurred in getting sidebar';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'get-sidebar-failed'));
+      }
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -538,31 +602,44 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeSidebar'
    *                      $ref: '#/components/schemas/CustomizeSidebar'
    */
    */
-  router.put('/sidebar', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired,
-    validator.sidebar, apiV3FormValidator, addActivity,
-    async(req, res) => {
+  router.put(
+    '/sidebar',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.sidebar,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:isSidebarCollapsedMode': req.body.isSidebarCollapsedMode,
         'customize:isSidebarCollapsedMode': req.body.isSidebarCollapsedMode,
-        'customize:isSidebarClosedAtDockMode': req.body.isSidebarClosedAtDockMode,
+        'customize:isSidebarClosedAtDockMode':
+          req.body.isSidebarClosedAtDockMode,
       };
       };
 
 
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const customizedParams = {
         const customizedParams = {
-          isSidebarCollapsedMode: await configManager.getConfig('customize:isSidebarCollapsedMode'),
-          isSidebarClosedAtDockMode: await configManager.getConfig('customize:isSidebarClosedAtDockMode'),
+          isSidebarCollapsedMode: await configManager.getConfig(
+            'customize:isSidebarCollapsedMode',
+          ),
+          isSidebarClosedAtDockMode: await configManager.getConfig(
+            'customize:isSidebarClosedAtDockMode',
+          ),
         };
         };
 
 
-        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SIDEBAR_UPDATE });
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_ADMIN_SIDEBAR_UPDATE,
+        });
 
 
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating sidebar';
         const msg = 'Error occurred in updating sidebar';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-sidebar-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-sidebar-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -591,47 +668,77 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeFunction'
    *                      $ref: '#/components/schemas/CustomizeFunction'
    */
    */
-  router.put('/function', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.function, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/function',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.function,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:isEnabledTimeline': req.body.isEnabledTimeline,
         'customize:isEnabledTimeline': req.body.isEnabledTimeline,
-        'customize:isEnabledAttachTitleHeader': req.body.isEnabledAttachTitleHeader,
+        'customize:isEnabledAttachTitleHeader':
+          req.body.isEnabledAttachTitleHeader,
         'customize:showPageLimitationS': req.body.pageLimitationS,
         'customize:showPageLimitationS': req.body.pageLimitationS,
         'customize:showPageLimitationM': req.body.pageLimitationM,
         'customize:showPageLimitationM': req.body.pageLimitationM,
         'customize:showPageLimitationL': req.body.pageLimitationL,
         'customize:showPageLimitationL': req.body.pageLimitationL,
         'customize:showPageLimitationXL': req.body.pageLimitationXL,
         'customize:showPageLimitationXL': req.body.pageLimitationXL,
-        'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification,
+        'customize:isEnabledStaleNotification':
+          req.body.isEnabledStaleNotification,
         'customize:isAllReplyShown': req.body.isAllReplyShown,
         'customize:isAllReplyShown': req.body.isAllReplyShown,
-        'customize:isSearchScopeChildrenAsDefault': req.body.isSearchScopeChildrenAsDefault,
+        'customize:isSearchScopeChildrenAsDefault':
+          req.body.isSearchScopeChildrenAsDefault,
         'customize:showPageSideAuthors': req.body.showPageSideAuthors,
         'customize:showPageSideAuthors': req.body.showPageSideAuthors,
       };
       };
 
 
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const customizedParams = {
         const customizedParams = {
-          isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
-          isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
-          pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
-          pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
-          pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
-          pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
-          isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
-          isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
-          isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
-          showPageSideAuthors: await configManager.getConfig('customize:showPageSideAuthors'),
+          isEnabledTimeline: await configManager.getConfig(
+            'customize:isEnabledTimeline',
+          ),
+          isEnabledAttachTitleHeader: await configManager.getConfig(
+            'customize:isEnabledAttachTitleHeader',
+          ),
+          pageLimitationS: await configManager.getConfig(
+            'customize:showPageLimitationS',
+          ),
+          pageLimitationM: await configManager.getConfig(
+            'customize:showPageLimitationM',
+          ),
+          pageLimitationL: await configManager.getConfig(
+            'customize:showPageLimitationL',
+          ),
+          pageLimitationXL: await configManager.getConfig(
+            'customize:showPageLimitationXL',
+          ),
+          isEnabledStaleNotification: await configManager.getConfig(
+            'customize:isEnabledStaleNotification',
+          ),
+          isAllReplyShown: await configManager.getConfig(
+            'customize:isAllReplyShown',
+          ),
+          isSearchScopeChildrenAsDefault: await configManager.getConfig(
+            'customize:isSearchScopeChildrenAsDefault',
+          ),
+          showPageSideAuthors: await configManager.getConfig(
+            'customize:showPageSideAuthors',
+          ),
+        };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE,
         };
         };
-        const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating function';
         const msg = 'Error occurred in updating function';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-function-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-function-failed'));
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -660,9 +767,15 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizePresentation'
    *                      $ref: '#/components/schemas/CustomizePresentation'
    */
    */
-  router.put('/presentation', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.CustomizePresentation, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/presentation',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.CustomizePresentation,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:isEnabledMarp': req.body.isEnabledMarp,
         'customize:isEnabledMarp': req.body.isEnabledMarp,
       };
       };
@@ -670,18 +783,22 @@ module.exports = (crowi) => {
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const customizedParams = {
         const customizedParams = {
-          isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
+          isEnabledMarp: await configManager.getConfig(
+            'customize:isEnabledMarp',
+          ),
+        };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE,
         };
         };
-        const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating presentaion';
         const msg = 'Error occurred in updating presentaion';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-presentation-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-presentation-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -710,9 +827,15 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeHighlightResponse'
    *                      $ref: '#/components/schemas/CustomizeHighlightResponse'
    */
    */
-  router.put('/highlight', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.highlight, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/highlight',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.highlight,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:highlightJsStyle': req.body.highlightJsStyle,
         'customize:highlightJsStyle': req.body.highlightJsStyle,
         'customize:highlightJsStyleBorder': req.body.highlightJsStyleBorder,
         'customize:highlightJsStyleBorder': req.body.highlightJsStyleBorder,
@@ -721,19 +844,25 @@ module.exports = (crowi) => {
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const customizedParams = {
         const customizedParams = {
-          styleName: await configManager.getConfig('customize:highlightJsStyle'),
-          styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
+          styleName: await configManager.getConfig(
+            'customize:highlightJsStyle',
+          ),
+          styleBorder: await configManager.getConfig(
+            'customize:highlightJsStyleBorder',
+          ),
+        };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE,
         };
         };
-        const parameters = { action: SupportedAction.ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating highlight';
         const msg = 'Error occurred in updating highlight';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-highlight-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-highlight-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -762,9 +891,15 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeTitle'
    *                      $ref: '#/components/schemas/CustomizeTitle'
    */
    */
-  router.put('/customize-title', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.customizeTitle, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/customize-title',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.customizeTitle,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:title': req.body.customizeTitle,
         'customize:title': req.body.customizeTitle,
       };
       };
@@ -777,16 +912,18 @@ module.exports = (crowi) => {
           customizeTitle: await configManager.getConfig('customize:title'),
           customizeTitle: await configManager.getConfig('customize:title'),
         };
         };
         customizeService.initCustomTitle();
         customizeService.initCustomTitle();
-        const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_TITLE_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_CUSTOM_TITLE_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating customizeTitle';
         const msg = 'Error occurred in updating customizeTitle';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeTitle-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeTitle-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -815,27 +952,38 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeNoscript'
    *                      $ref: '#/components/schemas/CustomizeNoscript'
    */
    */
-  router.put('/customize-noscript', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.customizeNoscript, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/customize-noscript',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.customizeNoscript,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:noscript': req.body.customizeNoscript,
         'customize:noscript': req.body.customizeNoscript,
       };
       };
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const customizedParams = {
         const customizedParams = {
-          customizeNoscript: await configManager.getConfig('customize:noscript'),
+          customizeNoscript:
+            await configManager.getConfig('customize:noscript'),
+        };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_CUSTOM_NOSCRIPT_UPDATE,
         };
         };
-        const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_NOSCRIPT_UPDATE };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating customizeNoscript';
         const msg = 'Error occurred in updating customizeNoscript';
         logger.error('Error', err);
         logger.error('Error', err);
-        return res.apiv3Err(new ErrorV3(msg, 'update-customizeNoscript-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'update-customizeNoscript-failed'),
+        );
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -864,9 +1012,15 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeCss'
    *                      $ref: '#/components/schemas/CustomizeCss'
    */
    */
-  router.put('/customize-css', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.customizeCss, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/customize-css',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.customizeCss,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:css': req.body.customizeCss,
         'customize:css': req.body.customizeCss,
       };
       };
@@ -878,16 +1032,18 @@ module.exports = (crowi) => {
           customizeCss: await configManager.getConfig('customize:css'),
           customizeCss: await configManager.getConfig('customize:css'),
         };
         };
         customizeService.initCustomCss();
         customizeService.initCustomCss();
-        const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_CSS_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_CUSTOM_CSS_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating customizeCss';
         const msg = 'Error occurred in updating customizeCss';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeCss-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeCss-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -916,9 +1072,15 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeScript'
    *                      $ref: '#/components/schemas/CustomizeScript'
    */
    */
-  router.put('/customize-script', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.customizeScript, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/customize-script',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.customizeScript,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'customize:script': req.body.customizeScript,
         'customize:script': req.body.customizeScript,
       };
       };
@@ -927,16 +1089,18 @@ module.exports = (crowi) => {
         const customizedParams = {
         const customizedParams = {
           customizeScript: await configManager.getConfig('customize:script'),
           customizeScript: await configManager.getConfig('customize:script'),
         };
         };
-        const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating customizeScript';
         const msg = 'Error occurred in updating customizeScript';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeScript-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeScript-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -965,12 +1129,15 @@ module.exports = (crowi) => {
    *                    customizedParams:
    *                    customizedParams:
    *                      $ref: '#/components/schemas/CustomizeLogo'
    *                      $ref: '#/components/schemas/CustomizeLogo'
    */
    */
-  router.put('/customize-logo', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired,
-    validator.logo, apiV3FormValidator,
-    async(req, res) => {
-      const {
-        isDefaultLogo,
-      } = req.body;
+  router.put(
+    '/customize-logo',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.logo,
+    apiV3FormValidator,
+    async (req, res) => {
+      const { isDefaultLogo } = req.body;
 
 
       const requestParams = {
       const requestParams = {
         'customize:isDefaultLogo': isDefaultLogo,
         'customize:isDefaultLogo': isDefaultLogo,
@@ -978,16 +1145,18 @@ module.exports = (crowi) => {
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const customizedParams = {
         const customizedParams = {
-          isDefaultLogo: await configManager.getConfig('customize:isDefaultLogo'),
+          isDefaultLogo: await configManager.getConfig(
+            'customize:isDefaultLogo',
+          ),
         };
         };
         return res.apiv3({ customizedParams });
         return res.apiv3({ customizedParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating customizeLogo';
         const msg = 'Error occurred in updating customizeLogo';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeLogo-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-customizeLogo-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -1028,15 +1197,24 @@ module.exports = (crowi) => {
    *                            temporaryUrlExpiredAt: {}
    *                            temporaryUrlExpiredAt: {}
    *                            temporaryUrlCached: {}
    *                            temporaryUrlCached: {}
    */
    */
-  router.post('/upload-brand-logo',
-    uploads.single('file'), accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, uploads.single('file'), validator.logo, apiV3FormValidator,
-    async(req, res) => {
-
+  router.post(
+    '/upload-brand-logo',
+    uploads.single('file'),
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    uploads.single('file'),
+    validator.logo,
+    apiV3FormValidator,
+    async (req, res) => {
       if (req.file == null) {
       if (req.file == null) {
-        return res.apiv3Err(new ErrorV3('File error.', 'upload-brand-logo-failed'));
+        return res.apiv3Err(
+          new ErrorV3('File error.', 'upload-brand-logo-failed'),
+        );
       }
       }
       if (req.user == null) {
       if (req.user == null) {
-        return res.apiv3Err(new ErrorV3('param "user" must be set.', 'upload-brand-logo-failed'));
+        return res.apiv3Err(
+          new ErrorV3('param "user" must be set.', 'upload-brand-logo-failed'),
+        );
       }
       }
 
 
       const file = req.file;
       const file = req.file;
@@ -1044,27 +1222,37 @@ module.exports = (crowi) => {
       // check type
       // check type
       const acceptableFileType = /image\/.+/;
       const acceptableFileType = /image\/.+/;
       if (!file.mimetype.match(acceptableFileType)) {
       if (!file.mimetype.match(acceptableFileType)) {
-        const msg = 'File type error. Only image files is allowed to set as user picture.';
+        const msg =
+          'File type error. Only image files is allowed to set as user picture.';
         return res.apiv3Err(new ErrorV3(msg, 'upload-brand-logo-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'upload-brand-logo-failed'));
       }
       }
 
 
       // Check if previous attachment exists and remove it
       // Check if previous attachment exists and remove it
-      const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });
+      const attachments = await Attachment.find({
+        attachmentType: AttachmentType.BRAND_LOGO,
+      });
       if (attachments != null) {
       if (attachments != null) {
         await attachmentService.removeAllAttachments(attachments);
         await attachmentService.removeAllAttachments(attachments);
       }
       }
 
 
       let attachment;
       let attachment;
       try {
       try {
-        attachment = await attachmentService.createAttachment(file, req.user, null, AttachmentType.BRAND_LOGO);
-      }
-      catch (err) {
+        attachment = await attachmentService.createAttachment(
+          file,
+          req.user,
+          null,
+          AttachmentType.BRAND_LOGO,
+        );
+      } catch (err) {
         logger.error(err);
         logger.error(err);
-        return res.apiv3Err(new ErrorV3(err.message, 'upload-brand-logo-failed'));
+        return res.apiv3Err(
+          new ErrorV3(err.message, 'upload-brand-logo-failed'),
+        );
       }
       }
       attachment.toObject({ virtuals: true });
       attachment.toObject({ virtuals: true });
       return res.apiv3({ attachment });
       return res.apiv3({ attachment });
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -1084,24 +1272,39 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  additionalProperties: false
    *                  additionalProperties: false
    */
    */
-  router.delete('/delete-brand-logo', accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]), loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.delete(
+    '/delete-brand-logo',
+    accessTokenParser([SCOPE.WRITE.ADMIN.CUSTOMIZE]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      const attachments = await Attachment.find({
+        attachmentType: AttachmentType.BRAND_LOGO,
+      });
 
 
-    const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });
-
-    if (attachments == null) {
-      return res.apiv3Err(new ErrorV3('attachment not found', 'delete-brand-logo-failed'));
-    }
+      if (attachments == null) {
+        return res.apiv3Err(
+          new ErrorV3('attachment not found', 'delete-brand-logo-failed'),
+        );
+      }
 
 
-    try {
-      await attachmentService.removeAllAttachments(attachments);
-    }
-    catch (err) {
-      logger.error(err);
-      return res.status(500).apiv3Err(new ErrorV3('Error while deleting logo', 'delete-brand-logo-failed'));
-    }
+      try {
+        await attachmentService.removeAllAttachments(attachments);
+      } catch (err) {
+        logger.error(err);
+        return res
+          .status(500)
+          .apiv3Err(
+            new ErrorV3(
+              'Error while deleting logo',
+              'delete-brand-logo-failed',
+            ),
+          );
+      }
 
 
-    return res.apiv3({});
-  });
+      return res.apiv3({});
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 64 - 36
apps/app/src/server/routes/apiv3/export.js

@@ -1,8 +1,7 @@
-import fs from 'fs';
-
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import express from 'express';
 import express from 'express';
-import { param, body } from 'express-validator';
+import { body, param } from 'express-validator';
+import fs from 'fs';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 import sanitize from 'sanitize-filename';
 import sanitize from 'sanitize-filename';
 
 
@@ -14,7 +13,6 @@ import loggerFactory from '~/utils/logger';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
 
-
 const logger = loggerFactory('growi:routes:apiv3:export');
 const logger = loggerFactory('growi:routes:apiv3:export');
 const router = express.Router();
 const router = express.Router();
 
 
@@ -138,7 +136,9 @@ module.exports = (crowi) => {
     socketIoService.getAdminSocket().emit('admin:onProgressForExport', data);
     socketIoService.getAdminSocket().emit('admin:onProgressForExport', data);
   });
   });
   adminEvent.on('onStartZippingForExport', (data) => {
   adminEvent.on('onStartZippingForExport', (data) => {
-    socketIoService.getAdminSocket().emit('admin:onStartZippingForExport', data);
+    socketIoService
+      .getAdminSocket()
+      .emit('admin:onStartZippingForExport', data);
   });
   });
   adminEvent.on('onTerminateForExport', (data) => {
   adminEvent.on('onTerminateForExport', (data) => {
     socketIoService.getAdminSocket().emit('admin:onTerminateForExport', data);
     socketIoService.getAdminSocket().emit('admin:onTerminateForExport', data);
@@ -155,11 +155,15 @@ module.exports = (crowi) => {
         .withMessage('"collections" array cannot be empty')
         .withMessage('"collections" array cannot be empty')
         .bail()
         .bail()
 
 
-        .custom(async(value) => {
+        .custom(async (value) => {
           // Check if all the collections in the request body exist in the database
           // Check if all the collections in the request body exist in the database
-          const listCollectionsResult = await mongoose.connection.db.listCollections().toArray();
-          const allCollectionNames = listCollectionsResult.map(collectionObj => collectionObj.name);
-          if (!value.every(v => allCollectionNames.includes(v))) {
+          const listCollectionsResult = await mongoose.connection.db
+            .listCollections()
+            .toArray();
+          const allCollectionNames = listCollectionsResult.map(
+            (collectionObj) => collectionObj.name,
+          );
+          if (!value.every((v) => allCollectionNames.includes(v))) {
             throw new Error('Invalid collections');
             throw new Error('Invalid collections');
           }
           }
         }),
         }),
@@ -167,11 +171,12 @@ module.exports = (crowi) => {
     deleteFile: [
     deleteFile: [
       // https://regex101.com/r/mD4eZs/6
       // https://regex101.com/r/mD4eZs/6
       // prevent from unexpecting attack doing delete file (path traversal attack)
       // prevent from unexpecting attack doing delete file (path traversal attack)
-      param('fileName').not().matches(/(\.\.\/|\.\.\\)/),
+      param('fileName')
+        .not()
+        .matches(/(\.\.\/|\.\.\\)/),
     ],
     ],
   };
   };
 
 
-
   /**
   /**
    * @swagger
    * @swagger
    *
    *
@@ -193,15 +198,21 @@ module.exports = (crowi) => {
    *                  status:
    *                  status:
    *                    $ref: '#/components/schemas/ExportStatus'
    *                    $ref: '#/components/schemas/ExportStatus'
    */
    */
-  router.get('/status', accessTokenParser([SCOPE.READ.ADMIN.EXPORT_DATA], { acceptLegacy: true }), loginRequired, adminRequired, async(req, res) => {
-    const status = await exportService.getStatus();
+  router.get(
+    '/status',
+    accessTokenParser([SCOPE.READ.ADMIN.EXPORT_DATA], { acceptLegacy: true }),
+    loginRequired,
+    adminRequired,
+    async (req, res) => {
+      const status = await exportService.getStatus();
 
 
-    // TODO: use res.apiv3
-    return res.json({
-      ok: true,
-      status,
-    });
-  });
+      // TODO: use res.apiv3
+      return res.json({
+        ok: true,
+        status,
+      });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -233,28 +244,37 @@ module.exports = (crowi) => {
    *                    type: boolean
    *                    type: boolean
    *                    description: whether the request is succeeded
    *                    description: whether the request is succeeded
    */
    */
-  router.post('/', accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }), loginRequired, adminRequired,
-    validator.generateZipFile, apiV3FormValidator, addActivity, async(req, res) => {
-    // TODO: add express validator
+  router.post(
+    '/',
+    accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }),
+    loginRequired,
+    adminRequired,
+    validator.generateZipFile,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
+      // TODO: add express validator
       try {
       try {
         const { collections } = req.body;
         const { collections } = req.body;
 
 
         exportService.export(collections);
         exportService.export(collections);
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_ARCHIVE_DATA_CREATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_ARCHIVE_DATA_CREATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         // TODO: use res.apiv3
         // TODO: use res.apiv3
         return res.status(200).json({
         return res.status(200).json({
           ok: true,
           ok: true,
         });
         });
-      }
-      catch (err) {
-      // TODO: use ApiV3Error
+      } catch (err) {
+        // TODO: use ApiV3Error
         logger.error(err);
         logger.error(err);
         return res.status(500).send({ status: 'ERROR' });
         return res.status(500).send({ status: 'ERROR' });
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -283,28 +303,36 @@ module.exports = (crowi) => {
    *                    type: boolean
    *                    type: boolean
    *                    description: whether the request is succeeded
    *                    description: whether the request is succeeded
    */
    */
-  router.delete('/:fileName', accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }), loginRequired, adminRequired,
-    validator.deleteFile, apiV3FormValidator, addActivity,
-    async(req, res) => {
-    // TODO: add express validator
+  router.delete(
+    '/:fileName',
+    accessTokenParser([SCOPE.WRITE.ADMIN.EXPORT_DATA], { acceptLegacy: true }),
+    loginRequired,
+    adminRequired,
+    validator.deleteFile,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
+      // TODO: add express validator
       const { fileName } = req.params;
       const { fileName } = req.params;
 
 
       try {
       try {
         const sanitizedFileName = sanitize(fileName);
         const sanitizedFileName = sanitize(fileName);
         const zipFile = exportService.getFile(sanitizedFileName);
         const zipFile = exportService.getFile(sanitizedFileName);
         fs.unlinkSync(zipFile);
         fs.unlinkSync(zipFile);
-        const parameters = { action: SupportedAction.ACTION_ADMIN_ARCHIVE_DATA_DELETE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_ARCHIVE_DATA_DELETE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         // TODO: use res.apiv3
         // TODO: use res.apiv3
         return res.status(200).send({ ok: true });
         return res.status(200).send({ ok: true });
-      }
-      catch (err) {
-      // TODO: use ApiV3Error
+      } catch (err) {
+        // TODO: use ApiV3Error
         logger.error(err);
         logger.error(err);
         return res.status(500).send({ ok: false });
         return res.status(500).send({ ok: false });
       }
       }
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 110 - 62
apps/app/src/server/routes/apiv3/forgot-password.js

@@ -37,7 +37,7 @@ const { body } = require('express-validator');
  *           type: string
  *           type: string
  *         error:
  *         error:
  *           type: string
  *           type: string
-*/
+ */
 
 
 const router = express.Router();
 const router = express.Router();
 
 
@@ -55,13 +55,21 @@ module.exports = (crowi) => {
 
 
   const validator = {
   const validator = {
     password: [
     password: [
-      body('newPassword').isString().not().isEmpty()
+      body('newPassword')
+        .isString()
+        .not()
+        .isEmpty()
         .isLength({ min: minPasswordLength })
         .isLength({ min: minPasswordLength })
-        .withMessage(`password must be at least ${minPasswordLength} characters long`),
+        .withMessage(
+          `password must be at least ${minPasswordLength} characters long`,
+        ),
       // checking if password confirmation matches password
       // checking if password confirmation matches password
-      body('newPasswordConfirm').isString().not().isEmpty()
+      body('newPasswordConfirm')
+        .isString()
+        .not()
+        .isEmpty()
         .custom((value, { req }) => {
         .custom((value, { req }) => {
-          return (value === req.body.newPassword);
+          return value === req.body.newPassword;
         }),
         }),
     ],
     ],
     email: [
     email: [
@@ -74,13 +82,23 @@ module.exports = (crowi) => {
     ],
     ],
   };
   };
 
 
-  const checkPassportStrategyMiddleware = checkForgotPasswordEnabledMiddlewareFactory(crowi, true);
+  const checkPassportStrategyMiddleware =
+    checkForgotPasswordEnabledMiddlewareFactory(crowi, true);
 
 
-  async function sendPasswordResetEmail(templateFileName, locale, email, url, expiredAt) {
+  async function sendPasswordResetEmail(
+    templateFileName,
+    locale,
+    email,
+    url,
+    expiredAt,
+  ) {
     return mailService.send({
     return mailService.send({
       to: email,
       to: email,
       subject: '[GROWI] Password Reset',
       subject: '[GROWI] Password Reset',
-      template: path.join(crowi.localeDir, `${locale}/notifications/${templateFileName}.ejs`),
+      template: path.join(
+        crowi.localeDir,
+        `${locale}/notifications/${templateFileName}.ejs`,
+      ),
       vars: {
       vars: {
         appTitle: appService.getAppTitle(),
         appTitle: appService.getAppTitle(),
         email,
         email,
@@ -118,39 +136,60 @@ module.exports = (crowi) => {
    *              schema:
    *              schema:
    *                type: object
    *                type: object
    */
    */
-  router.post('/', checkPassportStrategyMiddleware, validator.email, apiV3FormValidator, addActivity, async(req, res) => {
-    const { email } = req.body;
-    const locale = configManager.getConfig('app:globalLang');
-    const appUrl = growiInfoService.getSiteUrl();
+  router.post(
+    '/',
+    checkPassportStrategyMiddleware,
+    validator.email,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
+      const { email } = req.body;
+      const locale = configManager.getConfig('app:globalLang');
+      const appUrl = growiInfoService.getSiteUrl();
 
 
-    try {
-      const user = await User.findOne({ email });
+      try {
+        const user = await User.findOne({ email });
 
 
-      // when the user is not found or active
-      if (user == null || user.status !== 2) {
-        // Do not send emails to non GROWI user
-        // For security reason, do not use error messages like "Email does not exist"
-        return res.apiv3();
-      }
+        // when the user is not found or active
+        if (user == null || user.status !== 2) {
+          // Do not send emails to non GROWI user
+          // For security reason, do not use error messages like "Email does not exist"
+          return res.apiv3();
+        }
 
 
-      const passwordResetOrderData = await PasswordResetOrder.createPasswordResetOrder(email);
-      const url = new URL(`/forgot-password/${passwordResetOrderData.token}`, appUrl);
-      const oneTimeUrl = url.href;
-      const grwTzoffsetSec = crowi.appService.getTzoffset() * 60;
-      const expiredAt = subSeconds(passwordResetOrderData.expiredAt, grwTzoffsetSec);
-      const formattedExpiredAt = format(expiredAt, 'yyyy/MM/dd HH:mm');
-      await sendPasswordResetEmail('passwordReset', locale, email, oneTimeUrl, formattedExpiredAt);
+        const passwordResetOrderData =
+          await PasswordResetOrder.createPasswordResetOrder(email);
+        const url = new URL(
+          `/forgot-password/${passwordResetOrderData.token}`,
+          appUrl,
+        );
+        const oneTimeUrl = url.href;
+        const grwTzoffsetSec = crowi.appService.getTzoffset() * 60;
+        const expiredAt = subSeconds(
+          passwordResetOrderData.expiredAt,
+          grwTzoffsetSec,
+        );
+        const formattedExpiredAt = format(expiredAt, 'yyyy/MM/dd HH:mm');
+        await sendPasswordResetEmail(
+          'passwordReset',
+          locale,
+          email,
+          oneTimeUrl,
+          formattedExpiredAt,
+        );
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_USER_FOGOT_PASSWORD });
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_USER_FOGOT_PASSWORD,
+        });
 
 
-      return res.apiv3();
-    }
-    catch (err) {
-      const msg = 'Error occurred during password reset request procedure.';
-      logger.error(err);
-      return res.apiv3Err(`${msg} Cause: ${err}`);
-    }
-  });
+        return res.apiv3();
+      } catch (err) {
+        const msg = 'Error occurred during password reset request procedure.';
+        logger.error(err);
+        return res.apiv3Err(`${msg} Cause: ${err}`);
+      }
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -184,35 +223,44 @@ module.exports = (crowi) => {
    *                    $ref: '#/components/schemas/User'
    *                    $ref: '#/components/schemas/User'
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/', checkPassportStrategyMiddleware, injectResetOrderByTokenMiddleware, validator.password, apiV3FormValidator, addActivity, async(req, res) => {
-    const { passwordResetOrder } = req;
-    const { email } = passwordResetOrder;
-    const grobalLang = configManager.getConfig('app:globalLang');
-    const i18n = grobalLang || req.language;
-    const { newPassword } = req.body;
-
-    const user = await User.findOne({ email });
-
-    // when the user is not found or active
-    if (user == null || user.status !== 2) {
-      return res.apiv3Err('update-password-failed');
-    }
+  router.put(
+    '/',
+    checkPassportStrategyMiddleware,
+    injectResetOrderByTokenMiddleware,
+    validator.password,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
+      const { passwordResetOrder } = req;
+      const { email } = passwordResetOrder;
+      const grobalLang = configManager.getConfig('app:globalLang');
+      const i18n = grobalLang || req.language;
+      const { newPassword } = req.body;
 
 
-    try {
-      const userData = await user.updatePassword(newPassword);
-      const serializedUserData = serializeUserSecurely(userData);
-      passwordResetOrder.revokeOneTimeToken();
-      await sendPasswordResetEmail('passwordResetSuccessful', i18n, email);
+      const user = await User.findOne({ email });
 
 
-      activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_USER_RESET_PASSWORD });
+      // when the user is not found or active
+      if (user == null || user.status !== 2) {
+        return res.apiv3Err('update-password-failed');
+      }
 
 
-      return res.apiv3({ userData: serializedUserData });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err('update-password-failed');
-    }
-  });
+      try {
+        const userData = await user.updatePassword(newPassword);
+        const serializedUserData = serializeUserSecurely(userData);
+        passwordResetOrder.revokeOneTimeToken();
+        await sendPasswordResetEmail('passwordResetSuccessful', i18n, email);
+
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_USER_RESET_PASSWORD,
+        });
+
+        return res.apiv3({ userData: serializedUserData });
+      } catch (err) {
+        logger.error(err);
+        return res.apiv3Err('update-password-failed');
+      }
+    },
+  );
 
 
   // middleware to handle error
   // middleware to handle error
   router.use(httpErrorHandler);
   router.use(httpErrorHandler);

+ 79 - 24
apps/app/src/server/routes/apiv3/index.js

@@ -7,7 +7,6 @@ import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import injectUserRegistrationOrderByTokenMiddleware from '../../middlewares/inject-user-registration-order-by-token-middleware';
 import injectUserRegistrationOrderByTokenMiddleware from '../../middlewares/inject-user-registration-order-by-token-middleware';
 import * as loginFormValidator from '../../middlewares/login-form-validator';
 import * as loginFormValidator from '../../middlewares/login-form-validator';
 import * as registerFormValidator from '../../middlewares/register-form-validator';
 import * as registerFormValidator from '../../middlewares/register-form-validator';
-
 import g2gTransfer from './g2g-transfer';
 import g2gTransfer from './g2g-transfer';
 import importRoute from './import';
 import importRoute from './import';
 import pageListing from './page-listing';
 import pageListing from './page-listing';
@@ -26,7 +25,9 @@ const routerForAuth = express.Router();
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi, app) => {
 module.exports = (crowi, app) => {
   const isInstalled = crowi.configManager.getConfig('app:installed');
   const isInstalled = crowi.configManager.getConfig('app:installed');
-  const minPasswordLength = crowi.configManager.getConfig('app:minPasswordLength');
+  const minPasswordLength = crowi.configManager.getConfig(
+    'app:minPasswordLength',
+  );
 
 
   // add custom functions to express response
   // add custom functions to express response
   require('./response')(express, crowi);
   require('./response')(express, crowi);
@@ -37,45 +38,85 @@ module.exports = (crowi, app) => {
   routerForAdmin.use('/admin-home', require('./admin-home')(crowi));
   routerForAdmin.use('/admin-home', require('./admin-home')(crowi));
   routerForAdmin.use('/markdown-setting', require('./markdown-setting')(crowi));
   routerForAdmin.use('/markdown-setting', require('./markdown-setting')(crowi));
   routerForAdmin.use('/app-settings', require('./app-settings')(crowi));
   routerForAdmin.use('/app-settings', require('./app-settings')(crowi));
-  routerForAdmin.use('/customize-setting', require('./customize-setting')(crowi));
-  routerForAdmin.use('/notification-setting', require('./notification-setting')(crowi));
+  routerForAdmin.use(
+    '/customize-setting',
+    require('./customize-setting')(crowi),
+  );
+  routerForAdmin.use(
+    '/notification-setting',
+    require('./notification-setting')(crowi),
+  );
   routerForAdmin.use('/users', require('./users')(crowi));
   routerForAdmin.use('/users', require('./users')(crowi));
   routerForAdmin.use('/user-groups', require('./user-group')(crowi));
   routerForAdmin.use('/user-groups', require('./user-group')(crowi));
-  routerForAdmin.use('/external-user-groups', require('~/features/external-user-group/server/routes/apiv3/external-user-group')(crowi));
+  routerForAdmin.use(
+    '/external-user-groups',
+    require('~/features/external-user-group/server/routes/apiv3/external-user-group')(
+      crowi,
+    ),
+  );
   routerForAdmin.use('/export', require('./export')(crowi));
   routerForAdmin.use('/export', require('./export')(crowi));
   routerForAdmin.use('/import', importRoute(crowi));
   routerForAdmin.use('/import', importRoute(crowi));
   routerForAdmin.use('/search', require('./search')(crowi));
   routerForAdmin.use('/search', require('./search')(crowi));
   routerForAdmin.use('/security-setting', securitySettings(crowi));
   routerForAdmin.use('/security-setting', securitySettings(crowi));
   routerForAdmin.use('/mongo', require('./mongo')(crowi));
   routerForAdmin.use('/mongo', require('./mongo')(crowi));
-  routerForAdmin.use('/slack-integration-settings', require('./slack-integration-settings')(crowi));
-  routerForAdmin.use('/slack-integration-legacy-settings', require('./slack-integration-legacy-settings')(crowi));
+  routerForAdmin.use(
+    '/slack-integration-settings',
+    require('./slack-integration-settings')(crowi),
+  );
+  routerForAdmin.use(
+    '/slack-integration-legacy-settings',
+    require('./slack-integration-legacy-settings')(crowi),
+  );
   routerForAdmin.use('/activity', require('./activity')(crowi));
   routerForAdmin.use('/activity', require('./activity')(crowi));
   routerForAdmin.use('/g2g-transfer', g2gTransfer(crowi));
   routerForAdmin.use('/g2g-transfer', g2gTransfer(crowi));
   routerForAdmin.use('/plugins', growiPlugin(crowi));
   routerForAdmin.use('/plugins', growiPlugin(crowi));
 
 
   // auth
   // auth
-  const applicationInstalled = require('../../middlewares/application-installed')(crowi);
+  const applicationInstalled =
+    require('../../middlewares/application-installed')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
   const login = require('../login')(crowi, app);
   const login = require('../login')(crowi, app);
   const loginPassport = require('../login-passport')(crowi, app);
   const loginPassport = require('../login-passport')(crowi, app);
 
 
-  routerForAuth.post('/login', applicationInstalled, loginFormValidator.loginRules(), loginFormValidator.loginValidation,
-    addActivity, loginPassport.injectRedirectTo, loginPassport.isEnableLoginWithLocalOrLdap, loginPassport.loginWithLocal, loginPassport.loginWithLdap,
-    loginPassport.cannotLoginErrorHadnler, loginPassport.loginFailure);
+  routerForAuth.post(
+    '/login',
+    applicationInstalled,
+    loginFormValidator.loginRules(),
+    loginFormValidator.loginValidation,
+    addActivity,
+    loginPassport.injectRedirectTo,
+    loginPassport.isEnableLoginWithLocalOrLdap,
+    loginPassport.loginWithLocal,
+    loginPassport.loginWithLdap,
+    loginPassport.cannotLoginErrorHadnler,
+    loginPassport.loginFailure,
+  );
 
 
   routerForAuth.use('/invited', require('./invited')(crowi));
   routerForAuth.use('/invited', require('./invited')(crowi));
   routerForAuth.use('/logout', require('./logout')(crowi));
   routerForAuth.use('/logout', require('./logout')(crowi));
 
 
-  routerForAuth.post('/register',
-    applicationInstalled, registerFormValidator.registerRules(minPasswordLength), registerFormValidator.registerValidation, addActivity, login.register);
+  routerForAuth.post(
+    '/register',
+    applicationInstalled,
+    registerFormValidator.registerRules(minPasswordLength),
+    registerFormValidator.registerValidation,
+    addActivity,
+    login.register,
+  );
 
 
-  routerForAuth.post('/user-activation/register', applicationInstalled, userActivation.registerRules(minPasswordLength),
-    userActivation.validateRegisterForm, userActivation.registerAction(crowi));
+  routerForAuth.post(
+    '/user-activation/register',
+    applicationInstalled,
+    userActivation.registerRules(minPasswordLength),
+    userActivation.validateRegisterForm,
+    userActivation.registerAction(crowi),
+  );
 
 
   // installer
   // installer
-  routerForAdmin.use('/installer', isInstalled
-    ? allreadyInstalledMiddleware
-    : require('./installer')(crowi));
+  routerForAdmin.use(
+    '/installer',
+    isInstalled ? allreadyInstalledMiddleware : require('./installer')(crowi),
+  );
 
 
   if (!isInstalled) {
   if (!isInstalled) {
     return [router, routerForAdmin, routerForAuth];
     return [router, routerForAdmin, routerForAuth];
@@ -87,11 +128,15 @@ module.exports = (crowi, app) => {
   router.use('/user-activities', require('./user-activities')(crowi));
   router.use('/user-activities', require('./user-activities')(crowi));
 
 
   router.use('/user-group-relations', require('./user-group-relation')(crowi));
   router.use('/user-group-relations', require('./user-group-relation')(crowi));
-  router.use('/external-user-group-relations', require('~/features/external-user-group/server/routes/apiv3/external-user-group-relation')(crowi));
+  router.use(
+    '/external-user-group-relations',
+    require('~/features/external-user-group/server/routes/apiv3/external-user-group-relation')(
+      crowi,
+    ),
+  );
 
 
   router.use('/statistics', require('./statistics')(crowi));
   router.use('/statistics', require('./statistics')(crowi));
 
 
-
   router.use('/search', require('./search')(crowi));
   router.use('/search', require('./search')(crowi));
 
 
   router.use('/page', require('./page')(crowi));
   router.use('/page', require('./page')(crowi));
@@ -114,18 +159,28 @@ module.exports = (crowi, app) => {
   const user = require('../user')(crowi, null);
   const user = require('../user')(crowi, null);
   router.get('/check-username', user.api.checkUsername);
   router.get('/check-username', user.api.checkUsername);
 
 
-  router.post('/complete-registration',
+  router.post(
+    '/complete-registration',
     addActivity,
     addActivity,
     injectUserRegistrationOrderByTokenMiddleware,
     injectUserRegistrationOrderByTokenMiddleware,
     userActivation.completeRegistrationRules(),
     userActivation.completeRegistrationRules(),
     userActivation.validateCompleteRegistration,
     userActivation.validateCompleteRegistration,
-    userActivation.completeRegistrationAction(crowi));
+    userActivation.completeRegistrationAction(crowi),
+  );
 
 
   router.use('/user-ui-settings', require('./user-ui-settings')(crowi));
   router.use('/user-ui-settings', require('./user-ui-settings')(crowi));
 
 
   router.use('/bookmark-folder', require('./bookmark-folder')(crowi));
   router.use('/bookmark-folder', require('./bookmark-folder')(crowi));
-  router.use('/templates', require('~/features/templates/server/routes/apiv3')(crowi));
-  router.use('/page-bulk-export', require('~/features/page-bulk-export/server/routes/apiv3/page-bulk-export')(crowi));
+  router.use(
+    '/templates',
+    require('~/features/templates/server/routes/apiv3')(crowi),
+  );
+  router.use(
+    '/page-bulk-export',
+    require('~/features/page-bulk-export/server/routes/apiv3/page-bulk-export')(
+      crowi,
+    ),
+  );
 
 
   router.use('/openai', openaiRouteFactory(crowi));
   router.use('/openai', openaiRouteFactory(crowi));
 
 

+ 1 - 2
apps/app/src/server/routes/apiv3/logout.js

@@ -2,7 +2,6 @@ import { SupportedAction } from '~/interfaces/activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-
 const logger = loggerFactory('growi:routes:apiv3:logout'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:logout'); // eslint-disable-line no-unused-vars
 
 
 const express = require('express');
 const express = require('express');
@@ -29,7 +28,7 @@ module.exports = (crowi) => {
    *        500:
    *        500:
    *          description: Internal server error
    *          description: Internal server error
    */
    */
-  router.post('/', addActivity, async(req, res) => {
+  router.post('/', addActivity, async (req, res) => {
     req.session.destroy();
     req.session.destroy();
 
 
     const activityId = res.locals.activity._id;
     const activityId = res.locals.activity._id;

+ 110 - 49
apps/app/src/server/routes/apiv3/markdown-setting.js

@@ -33,7 +33,6 @@ const validator = {
   ],
   ],
 };
 };
 
 
-
 /**
 /**
  * @swagger
  * @swagger
  *
  *
@@ -123,7 +122,9 @@ const validator = {
 
 
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 
 
@@ -150,20 +151,42 @@ module.exports = (crowi) => {
    *                      description: markdown params
    *                      description: markdown params
    *                      $ref: '#/components/schemas/MarkdownParams'
    *                      $ref: '#/components/schemas/MarkdownParams'
    */
    */
-  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.MARKDOWN]), loginRequiredStrictly, adminRequired, async(req, res) => {
-    const markdownParams = {
-      isEnabledLinebreaks: await crowi.configManager.getConfig('markdown:isEnabledLinebreaks'),
-      isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
-      adminPreferredIndentSize: await crowi.configManager.getConfig('markdown:adminPreferredIndentSize'),
-      isIndentSizeForced: await crowi.configManager.getConfig('markdown:isIndentSizeForced'),
-      isEnabledXss: await crowi.configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
-      xssOption: await crowi.configManager.getConfig('markdown:rehypeSanitize:option'),
-      tagWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-      attrWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:attributes'),
-    };
+  router.get(
+    '/',
+    accessTokenParser([SCOPE.READ.ADMIN.MARKDOWN]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      const markdownParams = {
+        isEnabledLinebreaks: await crowi.configManager.getConfig(
+          'markdown:isEnabledLinebreaks',
+        ),
+        isEnabledLinebreaksInComments: await crowi.configManager.getConfig(
+          'markdown:isEnabledLinebreaksInComments',
+        ),
+        adminPreferredIndentSize: await crowi.configManager.getConfig(
+          'markdown:adminPreferredIndentSize',
+        ),
+        isIndentSizeForced: await crowi.configManager.getConfig(
+          'markdown:isIndentSizeForced',
+        ),
+        isEnabledXss: await crowi.configManager.getConfig(
+          'markdown:rehypeSanitize:isEnabledPrevention',
+        ),
+        xssOption: await crowi.configManager.getConfig(
+          'markdown:rehypeSanitize:option',
+        ),
+        tagWhitelist: await crowi.configManager.getConfig(
+          'markdown:rehypeSanitize:tagNames',
+        ),
+        attrWhitelist: await crowi.configManager.getConfig(
+          'markdown:rehypeSanitize:attributes',
+        ),
+      };
 
 
-    return res.apiv3({ markdownParams });
-  });
+      return res.apiv3({ markdownParams });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -192,33 +215,45 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      $ref: '#/components/schemas/LineBreakParams'
    *                      $ref: '#/components/schemas/LineBreakParams'
    */
    */
-  router.put('/lineBreak', accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
-    loginRequiredStrictly, adminRequired, addActivity, validator.lineBreak, apiV3FormValidator, async(req, res) => {
-
+  router.put(
+    '/lineBreak',
+    accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.lineBreak,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestLineBreakParams = {
       const requestLineBreakParams = {
         'markdown:isEnabledLinebreaks': req.body.isEnabledLinebreaks,
         'markdown:isEnabledLinebreaks': req.body.isEnabledLinebreaks,
-        'markdown:isEnabledLinebreaksInComments': req.body.isEnabledLinebreaksInComments,
+        'markdown:isEnabledLinebreaksInComments':
+          req.body.isEnabledLinebreaksInComments,
       };
       };
 
 
       try {
       try {
         await configManager.updateConfigs(requestLineBreakParams);
         await configManager.updateConfigs(requestLineBreakParams);
         const lineBreaksParams = {
         const lineBreaksParams = {
-          isEnabledLinebreaks: await crowi.configManager.getConfig('markdown:isEnabledLinebreaks'),
-          isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+          isEnabledLinebreaks: await crowi.configManager.getConfig(
+            'markdown:isEnabledLinebreaks',
+          ),
+          isEnabledLinebreaksInComments: await crowi.configManager.getConfig(
+            'markdown:isEnabledLinebreaksInComments',
+          ),
         };
         };
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_LINE_BREAK_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_MARKDOWN_LINE_BREAK_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ lineBreaksParams });
         return res.apiv3({ lineBreaksParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating lineBreak';
         const msg = 'Error occurred in updating lineBreak';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-lineBreak-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-lineBreak-failed'));
       }
       }
-
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -248,9 +283,15 @@ module.exports = (crowi) => {
    *                      description: indent params
    *                      description: indent params
    *                      $ref: '#/components/schemas/IndentParams'
    *                      $ref: '#/components/schemas/IndentParams'
    */
    */
-  router.put('/indent', accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
-    loginRequiredStrictly, adminRequired, addActivity, validator.indent, apiV3FormValidator, async(req, res) => {
-
+  router.put(
+    '/indent',
+    accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.indent,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestIndentParams = {
       const requestIndentParams = {
         'markdown:adminPreferredIndentSize': req.body.adminPreferredIndentSize,
         'markdown:adminPreferredIndentSize': req.body.adminPreferredIndentSize,
         'markdown:isIndentSizeForced': req.body.isIndentSizeForced,
         'markdown:isIndentSizeForced': req.body.isIndentSizeForced,
@@ -259,22 +300,27 @@ module.exports = (crowi) => {
       try {
       try {
         await configManager.updateConfigs(requestIndentParams);
         await configManager.updateConfigs(requestIndentParams);
         const indentParams = {
         const indentParams = {
-          adminPreferredIndentSize: await crowi.configManager.getConfig('markdown:adminPreferredIndentSize'),
-          isIndentSizeForced: await crowi.configManager.getConfig('markdown:isIndentSizeForced'),
+          adminPreferredIndentSize: await crowi.configManager.getConfig(
+            'markdown:adminPreferredIndentSize',
+          ),
+          isIndentSizeForced: await crowi.configManager.getConfig(
+            'markdown:isIndentSizeForced',
+          ),
         };
         };
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_INDENT_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_MARKDOWN_INDENT_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ indentParams });
         return res.apiv3({ indentParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating indent';
         const msg = 'Error occurred in updating indent';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-indent-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-indent-failed'));
       }
       }
-
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -300,16 +346,22 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/XssParams'
    *                  $ref: '#/components/schemas/XssParams'
    */
    */
-  router.put('/xss', accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
-    loginRequiredStrictly, adminRequired, addActivity, validator.xssSetting, apiV3FormValidator, async(req, res) => {
+  router.put(
+    '/xss',
+    accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.xssSetting,
+    apiV3FormValidator,
+    async (req, res) => {
       if (req.body.isEnabledXss && req.body.xssOption == null) {
       if (req.body.isEnabledXss && req.body.xssOption == null) {
         return res.apiv3Err(new ErrorV3('xss option is required'));
         return res.apiv3Err(new ErrorV3('xss option is required'));
       }
       }
 
 
       try {
       try {
         JSON.parse(req.body.attrWhitelist);
         JSON.parse(req.body.attrWhitelist);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating xss';
         const msg = 'Error occurred in updating xss';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-xss-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-xss-failed'));
@@ -325,24 +377,33 @@ module.exports = (crowi) => {
       try {
       try {
         await configManager.updateConfigs(reqestXssParams);
         await configManager.updateConfigs(reqestXssParams);
         const xssParams = {
         const xssParams = {
-          isEnabledXss: await crowi.configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
-          xssOption: await crowi.configManager.getConfig('markdown:rehypeSanitize:option'),
-          tagWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-          attrWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:attributes'),
+          isEnabledXss: await crowi.configManager.getConfig(
+            'markdown:rehypeSanitize:isEnabledPrevention',
+          ),
+          xssOption: await crowi.configManager.getConfig(
+            'markdown:rehypeSanitize:option',
+          ),
+          tagWhitelist: await crowi.configManager.getConfig(
+            'markdown:rehypeSanitize:tagNames',
+          ),
+          attrWhitelist: await crowi.configManager.getConfig(
+            'markdown:rehypeSanitize:attributes',
+          ),
         };
         };
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_XSS_UPDATE };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_MARKDOWN_XSS_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ xssParams });
         return res.apiv3({ xssParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating xss';
         const msg = 'Error occurred in updating xss';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'update-xss-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'update-xss-failed'));
       }
       }
-
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 23 - 11
apps/app/src/server/routes/apiv3/mongo.js

@@ -12,7 +12,9 @@ const router = express.Router();
 
 
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
 
 
   /**
   /**
@@ -38,16 +40,26 @@ module.exports = (crowi) => {
    *                    items:
    *                    items:
    *                      type: string
    *                      type: string
    */
    */
-  router.get('/collections', accessTokenParser([SCOPE.READ.ADMIN.EXPORT_DATA]), loginRequiredStrictly, adminRequired, async(req, res) => {
-    const listCollectionsResult = await mongoose.connection.db.listCollections().toArray();
-    const collections = listCollectionsResult.map(collectionObj => collectionObj.name);
-
-    // TODO: use res.apiv3
-    return res.json({
-      ok: true,
-      collections,
-    });
-  });
+  router.get(
+    '/collections',
+    accessTokenParser([SCOPE.READ.ADMIN.EXPORT_DATA]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      const listCollectionsResult = await mongoose.connection.db
+        .listCollections()
+        .toArray();
+      const collections = listCollectionsResult.map(
+        (collectionObj) => collectionObj.name,
+      );
+
+      // TODO: use res.apiv3
+      return res.json({
+        ok: true,
+        collections,
+      });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 243 - 176
apps/app/src/server/routes/apiv3/notification-setting.js

@@ -1,8 +1,8 @@
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import express from 'express';
 import express from 'express';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { GlobalNotificationSettingType } from '~/server/models/GlobalNotificationSetting';
 import { GlobalNotificationSettingType } from '~/server/models/GlobalNotificationSetting';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
@@ -13,11 +13,9 @@ import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import UpdatePost from '../../models/update-post';
 import UpdatePost from '../../models/update-post';
 
 
-
 // eslint-disable-next-line no-unused-vars
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:notification-setting');
 const logger = loggerFactory('growi:routes:apiv3:notification-setting');
 
 
-
 const router = express.Router();
 const router = express.Router();
 
 
 const { body } = require('express-validator');
 const { body } = require('express-validator');
@@ -28,19 +26,28 @@ const validator = {
     body('channel').isString().trim(),
     body('channel').isString().trim(),
   ],
   ],
   globalNotification: [
   globalNotification: [
-    body('triggerPath').isString().trim().not()
-      .isEmpty(),
+    body('triggerPath').isString().trim().not().isEmpty(),
     body('notifyType').isString().trim().isIn(['mail', 'slack']),
     body('notifyType').isString().trim().isIn(['mail', 'slack']),
-    body('toEmail').trim().custom((value, { req }) => {
-      return (req.body.notifyType === 'mail') ? (!!value && value.match(/.+@.+\..+/)) : true;
-    }),
-    body('slackChannels').trim().custom((value, { req }) => {
-      return (req.body.notifyType === 'slack') ? !!value : true;
-    }),
+    body('toEmail')
+      .trim()
+      .custom((value, { req }) => {
+        return req.body.notifyType === 'mail'
+          ? !!value && value.match(/.+@.+\..+/)
+          : true;
+      }),
+    body('slackChannels')
+      .trim()
+      .custom((value, { req }) => {
+        return req.body.notifyType === 'slack' ? !!value : true;
+      }),
   ],
   ],
   notifyForPageGrant: [
   notifyForPageGrant: [
-    body('isNotificationForOwnerPageEnabled').if(value => value != null).isBoolean(),
-    body('isNotificationForGroupPageEnabled').if(value => value != null).isBoolean(),
+    body('isNotificationForOwnerPageEnabled')
+      .if((value) => value != null)
+      .isBoolean(),
+    body('isNotificationForGroupPageEnabled')
+      .if((value) => value != null)
+      .isBoolean(),
   ],
   ],
 };
 };
 
 
@@ -184,8 +191,10 @@ module.exports = (crowi) => {
 
 
   const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
   const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
 
 
-  const GlobalNotificationMailSetting = crowi.models.GlobalNotificationMailSetting;
-  const GlobalNotificationSlackSetting = crowi.models.GlobalNotificationSlackSetting;
+  const GlobalNotificationMailSetting =
+    crowi.models.GlobalNotificationMailSetting;
+  const GlobalNotificationSlackSetting =
+    crowi.models.GlobalNotificationSlackSetting;
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -208,80 +217,106 @@ module.exports = (crowi) => {
    *                      description: notification params
    *                      description: notification params
    *                      $ref: '#/components/schemas/NotificationParams'
    *                      $ref: '#/components/schemas/NotificationParams'
    */
    */
-  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]), Strictly, adminRequired, async(req, res) => {
-
-    const notificationParams = {
-      // status of slack intagration
-      isSlackbotConfigured: crowi.slackIntegrationService.isSlackbotConfigured,
-      isSlackLegacyConfigured: crowi.slackIntegrationService.isSlackLegacyConfigured,
-      currentBotType: await crowi.configManager.getConfig('slackbot:currentBotType'),
-
-      userNotifications: await UpdatePost.findAll(),
-      isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification:owner-page:isEnabled'),
-      isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification:group-page:isEnabled'),
-      globalNotifications: await GlobalNotificationSetting.findAll(),
-    };
-    return res.apiv3({ notificationParams });
-  });
-
-  /**
-  * @swagger
-  *
-  *    /notification-setting/user-notification:
-  *      post:
-  *        tags: [NotificationSetting]
-  *        security:
-  *         - cookieAuth: []
-  *        description: add user notification setting
-  *        requestBody:
-  *          required: true
-  *          content:
-  *            application/json:
-  *              schema:
-  *                $ref: '#/components/schemas/UserNotificationParams'
-  *        responses:
-  *          200:
-  *            description: Succeeded to add user notification setting
-  *            content:
-  *              application/json:
-  *                schema:
-  *                  properties:
-  *                    responseParams:
-  *                      type: object
-  *                      description: response params
-  *                      properties:
-  *                        createdUser:
-  *                          $ref: '#/components/schemas/User'
-  *                          description: user who set notification
-  *                        userNotifications:
-  *                          type: array
-  *                          items:
-  *                            $ref: '#/components/schemas/UserNotification'
-  *                            description: user notification settings
-  */
-  // eslint-disable-next-line max-len
-  router.post('/user-notification', accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]), Strictly, adminRequired, addActivity, validator.userNotification, apiV3FormValidator, async(req, res) => {
-    const { pathPattern, channel } = req.body;
+  router.get(
+    '/',
+    accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]),
+    Strictly,
+    adminRequired,
+    async (req, res) => {
+      const notificationParams = {
+        // status of slack intagration
+        isSlackbotConfigured:
+          crowi.slackIntegrationService.isSlackbotConfigured,
+        isSlackLegacyConfigured:
+          crowi.slackIntegrationService.isSlackLegacyConfigured,
+        currentBotType: await crowi.configManager.getConfig(
+          'slackbot:currentBotType',
+        ),
 
 
-    try {
-      logger.info('notification.add', pathPattern, channel);
-      const responseParams = {
-        createdUser: await UpdatePost.createUpdatePost(pathPattern, channel, req.user),
         userNotifications: await UpdatePost.findAll(),
         userNotifications: await UpdatePost.findAll(),
+        isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig(
+          'notification:owner-page:isEnabled',
+        ),
+        isNotificationForGroupPageEnabled: await crowi.configManager.getConfig(
+          'notification:group-page:isEnabled',
+        ),
+        globalNotifications: await GlobalNotificationSetting.findAll(),
       };
       };
+      return res.apiv3({ notificationParams });
+    },
+  );
+
+  /**
+   * @swagger
+   *
+   *    /notification-setting/user-notification:
+   *      post:
+   *        tags: [NotificationSetting]
+   *        security:
+   *         - cookieAuth: []
+   *        description: add user notification setting
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/UserNotificationParams'
+   *        responses:
+   *          200:
+   *            description: Succeeded to add user notification setting
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    responseParams:
+   *                      type: object
+   *                      description: response params
+   *                      properties:
+   *                        createdUser:
+   *                          $ref: '#/components/schemas/User'
+   *                          description: user who set notification
+   *                        userNotifications:
+   *                          type: array
+   *                          items:
+   *                            $ref: '#/components/schemas/UserNotification'
+   *                            description: user notification settings
+   */
+  // eslint-disable-next-line max-len
+  router.post(
+    '/user-notification',
+    accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
+    Strictly,
+    adminRequired,
+    addActivity,
+    validator.userNotification,
+    apiV3FormValidator,
+    async (req, res) => {
+      const { pathPattern, channel } = req.body;
 
 
-      const parameters = { action: SupportedAction.ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD };
-      activityEvent.emit('update', res.locals.activity._id, parameters);
+      try {
+        logger.info('notification.add', pathPattern, channel);
+        const responseParams = {
+          createdUser: await UpdatePost.createUpdatePost(
+            pathPattern,
+            channel,
+            req.user,
+          ),
+          userNotifications: await UpdatePost.findAll(),
+        };
 
 
-      return res.apiv3({ responseParams }, 201);
-    }
-    catch (err) {
-      const msg = 'Error occurred in updating user notification';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'update-userNotification-failed'));
-    }
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD,
+        };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
-  });
+        return res.apiv3({ responseParams }, 201);
+      } catch (err) {
+        const msg = 'Error occurred in updating user notification';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-userNotification-failed'));
+      }
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -307,29 +342,36 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                  $ref: '#/components/schemas/UserNotification'
    *                  $ref: '#/components/schemas/UserNotification'
    */
    */
-  router.delete('/user-notification/:id',
+  router.delete(
+    '/user-notification/:id',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     Strictly,
     Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
-    async(req, res) => {
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
 
 
       try {
       try {
-        const deletedNotificaton = await UpdatePost.findOneAndRemove({ _id: id });
+        const deletedNotificaton = await UpdatePost.findOneAndRemove({
+          _id: id,
+        });
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE };
+        const parameters = {
+          action:
+            SupportedAction.ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3(deletedNotificaton);
         return res.apiv3(deletedNotificaton);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in delete user trigger notification';
         const msg = 'Error occurred in delete user trigger notification';
         logger.error('Error', err);
         logger.error('Error', err);
-        return res.apiv3Err(new ErrorV3(msg, 'delete-userTriggerNotification-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'delete-userTriggerNotification-failed'),
+        );
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -357,27 +399,31 @@ module.exports = (crowi) => {
    *                    globalNotification:
    *                    globalNotification:
    *                      $ref: '#/components/schemas/GlobalNotification'
    *                      $ref: '#/components/schemas/GlobalNotification'
    */
    */
-  router.get('/global-notification/:id',
+  router.get(
+    '/global-notification/:id',
     accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.READ.ADMIN.EXTERNAL_NOTIFICATION]),
     Strictly,
     Strictly,
     adminRequired,
     adminRequired,
     validator.globalNotification,
     validator.globalNotification,
-    async(req, res) => {
-
+    async (req, res) => {
       const notificationSettingId = req.params.id;
       const notificationSettingId = req.params.id;
       let globalNotification;
       let globalNotification;
 
 
       if (notificationSettingId) {
       if (notificationSettingId) {
         try {
         try {
-          globalNotification = await GlobalNotificationSetting.findOne({ _id: notificationSettingId });
-        }
-        catch (err) {
-          logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
+          globalNotification = await GlobalNotificationSetting.findOne({
+            _id: notificationSettingId,
+          });
+        } catch (err) {
+          logger.error(
+            `Error in finding a global notification setting with {_id: ${notificationSettingId}}`,
+          );
         }
         }
       }
       }
 
 
       return res.apiv3({ globalNotification });
       return res.apiv3({ globalNotification });
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -407,17 +453,17 @@ module.exports = (crowi) => {
    *                      $ref: '#/components/schemas/GlobalNotification'
    *                      $ref: '#/components/schemas/GlobalNotification'
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.post('/global-notification',
+  router.post(
+    '/global-notification',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     Strictly,
     Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     validator.globalNotification,
     validator.globalNotification,
     apiV3FormValidator,
     apiV3FormValidator,
-    async(req, res) => {
-      const {
-        notifyType, toEmail, slackChannels, triggerPath, triggerEvents,
-      } = req.body;
+    async (req, res) => {
+      const { notifyType, toEmail, slackChannels, triggerPath, triggerEvents } =
+        req.body;
 
 
       let notification;
       let notification;
 
 
@@ -436,17 +482,19 @@ module.exports = (crowi) => {
       try {
       try {
         const createdNotification = await notification.save();
         const createdNotification = await notification.save();
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ createdNotification }, 201);
         return res.apiv3({ createdNotification }, 201);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating global notification';
         const msg = 'Error occurred in updating global notification';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -481,18 +529,18 @@ module.exports = (crowi) => {
    *                      $ref: '#/components/schemas/GlobalNotification'
    *                      $ref: '#/components/schemas/GlobalNotification'
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/global-notification/:id',
+  router.put(
+    '/global-notification/:id',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     Strictly,
     Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     validator.globalNotification,
     validator.globalNotification,
     apiV3FormValidator,
     apiV3FormValidator,
-    async(req, res) => {
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
-      const {
-        notifyType, toEmail, slackChannels, triggerPath, triggerEvents,
-      } = req.body;
+      const { notifyType, toEmail, slackChannels, triggerPath, triggerEvents } =
+        req.body;
 
 
       const models = {
       const models = {
         [GlobalNotificationSettingType.MAIL]: GlobalNotificationMailSetting,
         [GlobalNotificationSettingType.MAIL]: GlobalNotificationMailSetting,
@@ -528,19 +576,20 @@ module.exports = (crowi) => {
 
 
         const createdNotification = await setting.save();
         const createdNotification = await setting.save();
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_UPDATE };
+        const parameters = {
+          action:
+            SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ createdNotification });
         return res.apiv3({ createdNotification });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating global notification';
         const msg = 'Error occurred in updating global notification';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
       }
       }
-
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -566,18 +615,20 @@ module.exports = (crowi) => {
    *                  $ref: '#/components/schemas/NotifyForPageGrant'
    *                  $ref: '#/components/schemas/NotifyForPageGrant'
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
-  router.put('/notify-for-page-grant',
+  router.put(
+    '/notify-for-page-grant',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     Strictly,
     Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
     validator.notifyForPageGrant,
     validator.notifyForPageGrant,
     apiV3FormValidator,
     apiV3FormValidator,
-    async(req, res) => {
-
+    async (req, res) => {
       let requestParams = {
       let requestParams = {
-        'notification:owner-page:isEnabled': req.body.isNotificationForOwnerPageEnabled,
-        'notification:group-page:isEnabled': req.body.isNotificationForGroupPageEnabled,
+        'notification:owner-page:isEnabled':
+          req.body.isNotificationForOwnerPageEnabled,
+        'notification:group-page:isEnabled':
+          req.body.isNotificationForGroupPageEnabled,
       };
       };
 
 
       requestParams = removeNullPropertyFromObject(requestParams);
       requestParams = removeNullPropertyFromObject(requestParams);
@@ -585,22 +636,32 @@ module.exports = (crowi) => {
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const responseParams = {
         const responseParams = {
-          isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification:owner-page:isEnabled'),
-          isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification:group-page:isEnabled'),
+          isNotificationForOwnerPageEnabled:
+            await crowi.configManager.getConfig(
+              'notification:owner-page:isEnabled',
+            ),
+          isNotificationForGroupPageEnabled:
+            await crowi.configManager.getConfig(
+              'notification:group-page:isEnabled',
+            ),
         };
         };
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE };
+        const parameters = {
+          action:
+            SupportedAction.ACTION_ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ responseParams });
         return res.apiv3({ responseParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating notify for page grant';
         const msg = 'Error occurred in updating notify for page grant';
         logger.error('Error', err);
         logger.error('Error', err);
-        return res.apiv3Err(new ErrorV3(msg, 'update-notify-for-page-grant-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'update-notify-for-page-grant-failed'),
+        );
       }
       }
-
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -638,20 +699,20 @@ module.exports = (crowi) => {
    *                      type: string
    *                      type: string
    *                      description: notification id
    *                      description: notification id
    */
    */
-  router.put('/global-notification/:id/enabled',
+  router.put(
+    '/global-notification/:id/enabled',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     Strictly,
     Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
-    async(req, res) => {
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
       const { isEnabled } = req.body;
       const { isEnabled } = req.body;
 
 
       try {
       try {
         if (isEnabled) {
         if (isEnabled) {
           await GlobalNotificationSetting.enable(id);
           await GlobalNotificationSetting.enable(id);
-        }
-        else {
+        } else {
           await GlobalNotificationSetting.disable(id);
           await GlobalNotificationSetting.disable(id);
         }
         }
 
 
@@ -663,64 +724,70 @@ module.exports = (crowi) => {
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ id });
         return res.apiv3({ id });
-
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in toggle of global notification';
         const msg = 'Error occurred in toggle of global notification';
         logger.error('Error', err);
         logger.error('Error', err);
-        return res.apiv3Err(new ErrorV3(msg, 'toggle-globalNotification-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'toggle-globalNotification-failed'),
+        );
       }
       }
-
-    });
+    },
+  );
 
 
   /**
   /**
-  * @swagger
-  *
-  *    /notification-setting/global-notification/{id}:
-  *      delete:
-  *        tags: [NotificationSetting]
-  *        security:
-  *          - cookieAuth: []
-  *        description: delete global notification pattern
-  *        parameters:
-  *          - name: id
-  *            in: path
-  *            required: true
-  *            description: id of global notification
-  *            schema:
-  *              type: string
-  *        responses:
-  *          200:
-  *            description: Succeeded to delete global notification pattern
-  *            content:
-  *              application/json:
-  *                schema:
-  *                  description: deleted notification
-  *                  $ref: '#/components/schemas/GlobalNotification'
-  */
-  router.delete('/global-notification/:id',
+   * @swagger
+   *
+   *    /notification-setting/global-notification/{id}:
+   *      delete:
+   *        tags: [NotificationSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        description: delete global notification pattern
+   *        parameters:
+   *          - name: id
+   *            in: path
+   *            required: true
+   *            description: id of global notification
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Succeeded to delete global notification pattern
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  description: deleted notification
+   *                  $ref: '#/components/schemas/GlobalNotification'
+   */
+  router.delete(
+    '/global-notification/:id',
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     accessTokenParser([SCOPE.WRITE.ADMIN.EXTERNAL_NOTIFICATION]),
     Strictly,
     Strictly,
     adminRequired,
     adminRequired,
     addActivity,
     addActivity,
-    async(req, res) => {
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
 
 
       try {
       try {
-        const deletedNotificaton = await GlobalNotificationSetting.findOneAndRemove({ _id: id });
+        const deletedNotificaton =
+          await GlobalNotificationSetting.findOneAndRemove({ _id: id });
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DELETE };
+        const parameters = {
+          action:
+            SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DELETE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3(deletedNotificaton);
         return res.apiv3(deletedNotificaton);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in delete global notification';
         const msg = 'Error occurred in delete global notification';
         logger.error('Error', err);
         logger.error('Error', err);
-        return res.apiv3Err(new ErrorV3(msg, 'delete-globalNotification-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'delete-globalNotification-failed'),
+        );
       }
       }
-
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 4 - 3
apps/app/src/server/routes/apiv3/response.js

@@ -3,8 +3,8 @@ import { ErrorV3 } from '@growi/core/dist/models';
 import { toArrayIfNot } from '~/utils/array-utils';
 import { toArrayIfNot } from '~/utils/array-utils';
 
 
 const addCustomFunctionToResponse = (express) => {
 const addCustomFunctionToResponse = (express) => {
-
-  express.response.apiv3 = function(obj = {}, status = 200) { // not arrow function
+  express.response.apiv3 = function (obj = {}, status = 200) {
+    // not arrow function
     // obj must be object
     // obj must be object
     if (typeof obj !== 'object' || obj instanceof Array) {
     if (typeof obj !== 'object' || obj instanceof Array) {
       throw new Error('invalid value supplied to res.apiv3');
       throw new Error('invalid value supplied to res.apiv3');
@@ -13,7 +13,8 @@ const addCustomFunctionToResponse = (express) => {
     this.status(status).json(obj);
     this.status(status).json(obj);
   };
   };
 
 
-  express.response.apiv3Err = function(_err, status = 400, info) { // not arrow function
+  express.response.apiv3Err = function (_err, status = 400, info) {
+    // not arrow function
     if (!Number.isInteger(status)) {
     if (!Number.isInteger(status)) {
       throw new Error('invalid status supplied to res.apiv3Err');
       throw new Error('invalid status supplied to res.apiv3Err');
     }
     }

+ 74 - 35
apps/app/src/server/routes/apiv3/revisions.js

@@ -1,9 +1,9 @@
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import express from 'express';
 import express from 'express';
 import { connection } from 'mongoose';
 import { connection } from 'mongoose';
 
 
-import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Revision } from '~/server/models/revision';
 import { Revision } from '~/server/models/revision';
 import { normalizeLatestRevisionIfBroken } from '~/server/service/revision/normalize-latest-revision-if-broken';
 import { normalizeLatestRevisionIfBroken } from '~/server/service/revision/normalize-latest-revision-if-broken';
@@ -17,7 +17,8 @@ const { query, param } = require('express-validator');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-const MIGRATION_FILE_NAME = '20211227060705-revision-path-to-page-id-schema-migration--fixed-7549';
+const MIGRATION_FILE_NAME =
+  '20211227060705-revision-path-to-page-id-schema-migration--fixed-7549';
 
 
 /**
 /**
  * @swagger
  * @swagger
@@ -56,20 +57,27 @@ const MIGRATION_FILE_NAME = '20211227060705-revision-path-to-page-id-schema-migr
  */
  */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const certifySharedPage = require('../../middlewares/certify-shared-page')(crowi);
-  const loginRequired = require('../../middlewares/login-required')(crowi, true);
+  const certifySharedPage = require('../../middlewares/certify-shared-page')(
+    crowi,
+  );
+  const loginRequired = require('../../middlewares/login-required')(
+    crowi,
+    true,
+  );
 
 
-  const {
-    Page,
-    User,
-  } = crowi.models;
+  const { Page, User } = crowi.models;
 
 
   const validator = {
   const validator = {
     retrieveRevisions: [
     retrieveRevisions: [
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageId').isMongoId().withMessage('pageId is required'),
-      query('offset').if(value => value != null).isInt({ min: 0 }).withMessage('offset must be int'),
-      query('limit').if(value => value != null).isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'),
-
+      query('offset')
+        .if((value) => value != null)
+        .isInt({ min: 0 })
+        .withMessage('offset must be int'),
+      query('limit')
+        .if((value) => value != null)
+        .isInt({ max: 100 })
+        .withMessage('You should set less than 100 or not to set limit.'),
     ],
     ],
     retrieveRevisionById: [
     retrieveRevisionById: [
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageId').isMongoId().withMessage('pageId is required'),
@@ -79,14 +87,15 @@ module.exports = (crowi) => {
 
 
   let cachedAppliedAt = null;
   let cachedAppliedAt = null;
 
 
-  const getAppliedAtOfTheMigrationFile = async() => {
-
+  const getAppliedAtOfTheMigrationFile = async () => {
     if (cachedAppliedAt != null) {
     if (cachedAppliedAt != null) {
       return cachedAppliedAt;
       return cachedAppliedAt;
     }
     }
 
 
     const migrationCollection = connection.collection('migrations');
     const migrationCollection = connection.collection('migrations');
-    const migration = await migrationCollection.findOne({ fileName: { $regex: `^${MIGRATION_FILE_NAME}` } });
+    const migration = await migrationCollection.findOne({
+      fileName: { $regex: `^${MIGRATION_FILE_NAME}` },
+    });
     const appliedAt = migration.appliedAt;
     const appliedAt = migration.appliedAt;
 
 
     cachedAppliedAt = appliedAt;
     cachedAppliedAt = appliedAt;
@@ -135,24 +144,42 @@ module.exports = (crowi) => {
    *                    type: number
    *                    type: number
    *                    description: offset of the revisions
    *                    description: offset of the revisions
    */
    */
-  router.get('/list',
-    certifySharedPage, accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), loginRequired, validator.retrieveRevisions, apiV3FormValidator,
-    async(req, res) => {
+  router.get(
+    '/list',
+    certifySharedPage,
+    accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }),
+    loginRequired,
+    validator.retrieveRevisions,
+    apiV3FormValidator,
+    async (req, res) => {
       const pageId = req.query.pageId;
       const pageId = req.query.pageId;
-      const limit = req.query.limit || await crowi.configManager.getConfig('customize:showPageLimitationS') || 10;
+      const limit =
+        req.query.limit ||
+        (await crowi.configManager.getConfig(
+          'customize:showPageLimitationS',
+        )) ||
+        10;
       const { isSharedPage } = req;
       const { isSharedPage } = req;
       const offset = req.query.offset || 0;
       const offset = req.query.offset || 0;
 
 
       // check whether accessible
       // check whether accessible
-      if (!isSharedPage && !(await Page.isAccessiblePageByViewer(pageId, req.user))) {
-        return res.apiv3Err(new ErrorV3('Current user is not accessible to this page.', 'forbidden-page'), 403);
+      if (
+        !isSharedPage &&
+        !(await Page.isAccessiblePageByViewer(pageId, req.user))
+      ) {
+        return res.apiv3Err(
+          new ErrorV3(
+            'Current user is not accessible to this page.',
+            'forbidden-page',
+          ),
+          403,
+        );
       }
       }
 
 
       // Normalize the latest revision which was borken by the migration script '20211227060705-revision-path-to-page-id-schema-migration--fixed-7549.js'
       // Normalize the latest revision which was borken by the migration script '20211227060705-revision-path-to-page-id-schema-migration--fixed-7549.js'
       try {
       try {
         await normalizeLatestRevisionIfBroken(pageId);
         await normalizeLatestRevisionIfBroken(pageId);
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('Error occurred in normalizing the latest revision');
         logger.error('Error occurred in normalizing the latest revision');
       }
       }
 
 
@@ -197,14 +224,13 @@ module.exports = (crowi) => {
         };
         };
 
 
         return res.apiv3(result);
         return res.apiv3(result);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in getting revisions by poge id';
         const msg = 'Error occurred in getting revisions by poge id';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'faild-to-find-revisions'), 500);
         return res.apiv3Err(new ErrorV3(msg, 'faild-to-find-revisions'), 500);
       }
       }
-
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -236,16 +262,30 @@ module.exports = (crowi) => {
    *                    revision:
    *                    revision:
    *                      $ref: '#/components/schemas/Revision'
    *                      $ref: '#/components/schemas/Revision'
    */
    */
-  router.get('/:id',
-    certifySharedPage, accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), loginRequired, validator.retrieveRevisionById, apiV3FormValidator,
-    async(req, res) => {
+  router.get(
+    '/:id',
+    certifySharedPage,
+    accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }),
+    loginRequired,
+    validator.retrieveRevisionById,
+    apiV3FormValidator,
+    async (req, res) => {
       const revisionId = req.params.id;
       const revisionId = req.params.id;
       const pageId = req.query.pageId;
       const pageId = req.query.pageId;
       const { isSharedPage } = req;
       const { isSharedPage } = req;
 
 
       // check whether accessible
       // check whether accessible
-      if (!isSharedPage && !(await Page.isAccessiblePageByViewer(pageId, req.user))) {
-        return res.apiv3Err(new ErrorV3('Current user is not accessible to this page.', 'forbidden-page'), 403);
+      if (
+        !isSharedPage &&
+        !(await Page.isAccessiblePageByViewer(pageId, req.user))
+      ) {
+        return res.apiv3Err(
+          new ErrorV3(
+            'Current user is not accessible to this page.',
+            'forbidden-page',
+          ),
+          403,
+        );
       }
       }
 
 
       try {
       try {
@@ -256,14 +296,13 @@ module.exports = (crowi) => {
         }
         }
 
 
         return res.apiv3({ revision });
         return res.apiv3({ revision });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in getting revision data by id';
         const msg = 'Error occurred in getting revision data by id';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'faild-to-find-revision'), 500);
         return res.apiv3Err(new ErrorV3(msg, 'faild-to-find-revision'), 500);
       }
       }
-
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 80 - 27
apps/app/src/server/routes/apiv3/search.js

@@ -126,23 +126,36 @@ module.exports = (crowi) => {
    *                    description: Status of indices
    *                    description: Status of indices
    *                    $ref: '#/components/schemas/Indices'
    *                    $ref: '#/components/schemas/Indices'
    */
    */
-  router.get('/indices',
-    noCache(), accessTokenParser([SCOPE.READ.ADMIN.FULL_TEXT_SEARCH], { acceptLegacy: true }), loginRequired, adminRequired, async(req, res) => {
+  router.get(
+    '/indices',
+    noCache(),
+    accessTokenParser([SCOPE.READ.ADMIN.FULL_TEXT_SEARCH], {
+      acceptLegacy: true,
+    }),
+    loginRequired,
+    adminRequired,
+    async (req, res) => {
       const { searchService } = crowi;
       const { searchService } = crowi;
 
 
       if (!searchService.isConfigured) {
       if (!searchService.isConfigured) {
-        return res.apiv3Err(new ErrorV3('SearchService is not configured', 'search-service-unconfigured'), 503);
+        return res.apiv3Err(
+          new ErrorV3(
+            'SearchService is not configured',
+            'search-service-unconfigured',
+          ),
+          503,
+        );
       }
       }
 
 
       try {
       try {
         const info = await searchService.getInfoForAdmin();
         const info = await searchService.getInfoForAdmin();
         return res.status(200).send({ info });
         return res.status(200).send({ info });
-      }
-      catch (err) {
+      } catch (err) {
         logger.error(err);
         logger.error(err);
         return res.apiv3Err(err, 503);
         return res.apiv3Err(err, 503);
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -156,26 +169,40 @@ module.exports = (crowi) => {
    *        200:
    *        200:
    *          description: Successfully connected
    *          description: Successfully connected
    */
    */
-  router.post('/connection',
-    accessTokenParser([SCOPE.WRITE.ADMIN.FULL_TEXT_SEARCH], { acceptLegacy: true }), loginRequired, adminRequired, addActivity, async(req, res) => {
+  router.post(
+    '/connection',
+    accessTokenParser([SCOPE.WRITE.ADMIN.FULL_TEXT_SEARCH], {
+      acceptLegacy: true,
+    }),
+    loginRequired,
+    adminRequired,
+    addActivity,
+    async (req, res) => {
       const { searchService } = crowi;
       const { searchService } = crowi;
 
 
       if (!searchService.isConfigured) {
       if (!searchService.isConfigured) {
-        return res.apiv3Err(new ErrorV3('SearchService is not configured', 'search-service-unconfigured'));
+        return res.apiv3Err(
+          new ErrorV3(
+            'SearchService is not configured',
+            'search-service-unconfigured',
+          ),
+        );
       }
       }
 
 
       try {
       try {
         await searchService.reconnectClient();
         await searchService.reconnectClient();
 
 
-        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SEARCH_CONNECTION });
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_ADMIN_SEARCH_CONNECTION,
+        });
 
 
         return res.status(200).send();
         return res.status(200).send();
-      }
-      catch (err) {
+      } catch (err) {
         logger.error(err);
         logger.error(err);
         return res.apiv3Err(err, 503);
         return res.apiv3Err(err, 503);
       }
       }
-    });
+    },
+  );
 
 
   const validatorForPutIndices = [
   const validatorForPutIndices = [
     body('operation').isString().isIn(['rebuild', 'normalize']),
     body('operation').isString().isIn(['rebuild', 'normalize']),
@@ -212,44 +239,70 @@ module.exports = (crowi) => {
    *                    type: string
    *                    type: string
    *                    description: Operation is successfully processed, or requested
    *                    description: Operation is successfully processed, or requested
    */
    */
-  router.put('/indices', accessTokenParser([SCOPE.WRITE.ADMIN.FULL_TEXT_SEARCH], { acceptLegacy: true }), loginRequired, adminRequired, addActivity,
-    validatorForPutIndices, apiV3FormValidator,
-    async(req, res) => {
+  router.put(
+    '/indices',
+    accessTokenParser([SCOPE.WRITE.ADMIN.FULL_TEXT_SEARCH], {
+      acceptLegacy: true,
+    }),
+    loginRequired,
+    adminRequired,
+    addActivity,
+    validatorForPutIndices,
+    apiV3FormValidator,
+    async (req, res) => {
       const operation = req.body.operation;
       const operation = req.body.operation;
 
 
       const { searchService } = crowi;
       const { searchService } = crowi;
 
 
       if (!searchService.isConfigured) {
       if (!searchService.isConfigured) {
-        return res.apiv3Err(new ErrorV3('SearchService is not configured', 'search-service-unconfigured'));
+        return res.apiv3Err(
+          new ErrorV3(
+            'SearchService is not configured',
+            'search-service-unconfigured',
+          ),
+        );
       }
       }
       if (!searchService.isReachable) {
       if (!searchService.isReachable) {
-        return res.apiv3Err(new ErrorV3('SearchService is not reachable', 'search-service-unreachable'));
+        return res.apiv3Err(
+          new ErrorV3(
+            'SearchService is not reachable',
+            'search-service-unreachable',
+          ),
+        );
       }
       }
 
 
       try {
       try {
         switch (operation) {
         switch (operation) {
           case 'normalize':
           case 'normalize':
-          // wait the processing is terminated
+            // wait the processing is terminated
             await searchService.normalizeIndices();
             await searchService.normalizeIndices();
 
 
-            activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SEARCH_INDICES_NORMALIZE });
+            activityEvent.emit('update', res.locals.activity._id, {
+              action: SupportedAction.ACTION_ADMIN_SEARCH_INDICES_NORMALIZE,
+            });
 
 
-            return res.status(200).send({ message: 'Operation is successfully processed.' });
+            return res
+              .status(200)
+              .send({ message: 'Operation is successfully processed.' });
           case 'rebuild':
           case 'rebuild':
-          // NOT wait the processing is terminated
+            // NOT wait the processing is terminated
             searchService.rebuildIndex();
             searchService.rebuildIndex();
 
 
-            activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SEARCH_INDICES_REBUILD });
+            activityEvent.emit('update', res.locals.activity._id, {
+              action: SupportedAction.ACTION_ADMIN_SEARCH_INDICES_REBUILD,
+            });
 
 
-            return res.status(200).send({ message: 'Operation is successfully requested.' });
+            return res
+              .status(200)
+              .send({ message: 'Operation is successfully requested.' });
           default:
           default:
             throw new Error(`Unimplemented operation: ${operation}`);
             throw new Error(`Unimplemented operation: ${operation}`);
         }
         }
-      }
-      catch (err) {
+      } catch (err) {
         return res.apiv3Err(err, 503);
         return res.apiv3Err(err, 503);
       }
       }
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 167 - 119
apps/app/src/server/routes/apiv3/share-links.js

@@ -95,18 +95,24 @@ module.exports = (crowi) => {
    * middleware to limit link sharing
    * middleware to limit link sharing
    */
    */
   const linkSharingRequired = (req, res, next) => {
   const linkSharingRequired = (req, res, next) => {
-    const isLinkSharingDisabled = crowi.configManager.getConfig('security:disableLinkSharing');
+    const isLinkSharingDisabled = crowi.configManager.getConfig(
+      'security:disableLinkSharing',
+    );
     logger.debug(`isLinkSharingDisabled: ${isLinkSharingDisabled}`);
     logger.debug(`isLinkSharingDisabled: ${isLinkSharingDisabled}`);
 
 
     if (isLinkSharingDisabled) {
     if (isLinkSharingDisabled) {
-      return res.apiv3Err(new ErrorV3('Link sharing is disabled', 'link-sharing-disabled'));
+      return res.apiv3Err(
+        new ErrorV3('Link sharing is disabled', 'link-sharing-disabled'),
+      );
     }
     }
     next();
     next();
   };
   };
 
 
   validator.getShareLinks = [
   validator.getShareLinks = [
     // validate the page id is MongoId
     // validate the page id is MongoId
-    query('relatedPage').isMongoId().withMessage('Page Id is required'),
+    query('relatedPage')
+      .isMongoId()
+      .withMessage('Page Id is required'),
   ];
   ];
 
 
   /**
   /**
@@ -138,13 +144,14 @@ module.exports = (crowi) => {
    *                      items:
    *                      items:
    *                        $ref: '#/components/schemas/ShareLink'
    *                        $ref: '#/components/schemas/ShareLink'
    */
    */
-  router.get('/',
+  router.get(
+    '/',
     accessTokenParser([SCOPE.READ.FEATURES.SHARE_LINK]),
     accessTokenParser([SCOPE.READ.FEATURES.SHARE_LINK]),
     loginRequired,
     loginRequired,
     linkSharingRequired,
     linkSharingRequired,
     validator.getShareLinks,
     validator.getShareLinks,
     apiV3FormValidator,
     apiV3FormValidator,
-    async(req, res) => {
+    async (req, res) => {
       const { relatedPage } = req.query;
       const { relatedPage } = req.query;
 
 
       const page = await Page.findByIdAndViewer(relatedPage, req.user);
       const page = await Page.findByIdAndViewer(relatedPage, req.user);
@@ -156,23 +163,32 @@ module.exports = (crowi) => {
       }
       }
 
 
       try {
       try {
-        const shareLinksResult = await ShareLink.find({ relatedPage: { $eq: relatedPage } }).populate({ path: 'relatedPage', select: 'path' });
+        const shareLinksResult = await ShareLink.find({
+          relatedPage: { $eq: relatedPage },
+        }).populate({ path: 'relatedPage', select: 'path' });
         return res.apiv3({ shareLinksResult });
         return res.apiv3({ shareLinksResult });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in get share link';
         const msg = 'Error occurred in get share link';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'get-shareLink-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'get-shareLink-failed'));
       }
       }
-    });
+    },
+  );
 
 
   validator.shareLinkStatus = [
   validator.shareLinkStatus = [
     // validate the page id is MongoId
     // validate the page id is MongoId
-    body('relatedPage').isMongoId().withMessage('Page Id is required'),
+    body('relatedPage')
+      .isMongoId()
+      .withMessage('Page Id is required'),
     // validate expireation date is not empty, is not before today and is date.
     // 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'),
+    body('expiredAt')
+      .if((value) => value != null)
+      .isAfter(today.toString())
+      .withMessage('Your Selected date is past'),
     // validate the length of description is max 100.
     // validate the length of description is max 100.
-    body('description').isLength({ min: 0, max: 100 }).withMessage('Max length is 100'),
+    body('description')
+      .isLength({ min: 0, max: 100 })
+      .withMessage('Max length is 100'),
   ];
   ];
 
 
   /**
   /**
@@ -209,7 +225,8 @@ module.exports = (crowi) => {
    *                schema:
    *                schema:
    *                 $ref: '#/components/schemas/ShareLinkSimple'
    *                 $ref: '#/components/schemas/ShareLinkSimple'
    */
    */
-  router.post('/',
+  router.post(
+    '/',
     accessTokenParser([SCOPE.WRITE.FEATURES.SHARE_LINK]),
     accessTokenParser([SCOPE.WRITE.FEATURES.SHARE_LINK]),
     loginRequired,
     loginRequired,
     excludeReadOnlyUser,
     excludeReadOnlyUser,
@@ -217,7 +234,7 @@ module.exports = (crowi) => {
     addActivity,
     addActivity,
     validator.shareLinkStatus,
     validator.shareLinkStatus,
     apiV3FormValidator,
     apiV3FormValidator,
-    async(req, res) => {
+    async (req, res) => {
       const { relatedPage, expiredAt, description } = req.body;
       const { relatedPage, expiredAt, description } = req.body;
 
 
       const page = await Page.findByIdAndViewer(relatedPage, req.user);
       const page = await Page.findByIdAndViewer(relatedPage, req.user);
@@ -229,146 +246,173 @@ module.exports = (crowi) => {
       }
       }
 
 
       try {
       try {
-        const postedShareLink = await ShareLink.create({ relatedPage, expiredAt, description });
+        const postedShareLink = await ShareLink.create({
+          relatedPage,
+          expiredAt,
+          description,
+        });
 
 
-        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_SHARE_LINK_CREATE });
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_SHARE_LINK_CREATE,
+        });
 
 
         return res.apiv3(postedShareLink, 201);
         return res.apiv3(postedShareLink, 201);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occured in post share link';
         const msg = 'Error occured in post share link';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'post-shareLink-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'post-shareLink-failed'));
       }
       }
-    });
-
+    },
+  );
 
 
   validator.deleteShareLinks = [
   validator.deleteShareLinks = [
     // validate the page id is MongoId
     // validate the page id is MongoId
-    query('relatedPage').isMongoId().withMessage('Page Id is required'),
+    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('/',
+   * @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]),
     accessTokenParser([SCOPE.WRITE.FEATURES.SHARE_LINK]),
     loginRequired,
     loginRequired,
     excludeReadOnlyUser,
     excludeReadOnlyUser,
     addActivity,
     addActivity,
     validator.deleteShareLinks,
     validator.deleteShareLinks,
     apiV3FormValidator,
     apiV3FormValidator,
-    async(req, res) => {
+    async (req, res) => {
       const { relatedPage } = req.query;
       const { relatedPage } = req.query;
       const page = await Page.findByIdAndViewer(relatedPage, req.user);
       const page = await Page.findByIdAndViewer(relatedPage, req.user);
 
 
       if (page == null) {
       if (page == null) {
         const msg = 'Page is not found or forbidden';
         const msg = 'Page is not found or forbidden';
         logger.error('Error', msg);
         logger.error('Error', msg);
-        return res.apiv3Err(new ErrorV3(msg, 'delete-shareLinks-for-page-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'delete-shareLinks-for-page-failed'),
+        );
       }
       }
 
 
       try {
       try {
-        const deletedShareLink = await ShareLink.deleteMany({ relatedPage: { $eq: relatedPage } });
+        const deletedShareLink = await ShareLink.deleteMany({
+          relatedPage: { $eq: relatedPage },
+        });
 
 
-        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_SHARE_LINK_DELETE_BY_PAGE });
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_SHARE_LINK_DELETE_BY_PAGE,
+        });
 
 
         return res.apiv3(deletedShareLink);
         return res.apiv3(deletedShareLink);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occured in delete share link';
         const msg = 'Error occured in delete share link';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
         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'));
-    }
-  });
+   * @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 = [
   validator.deleteShareLink = [
     param('id').isMongoId().withMessage('ShareLink Id is required'),
     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) => {
+   * @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 { id } = req.params;
       const { user } = req;
       const { user } = req;
 
 
@@ -377,8 +421,12 @@ module.exports = (crowi) => {
 
 
         // check permission
         // check permission
         if (!user.isAdmin) {
         if (!user.isAdmin) {
-          const page = await Page.findByIdAndViewer(shareLinkToDelete.relatedPage, user);
-          const isPageExists = (await Page.count({ _id: shareLinkToDelete.relatedPage }) > 0);
+          const page = await Page.findByIdAndViewer(
+            shareLinkToDelete.relatedPage,
+            user,
+          );
+          const isPageExists =
+            (await Page.count({ _id: shareLinkToDelete.relatedPage })) > 0;
           if (page == null && isPageExists) {
           if (page == null && isPageExists) {
             const msg = 'Page is not found or forbidden';
             const msg = 'Page is not found or forbidden';
             logger.error('Error', msg);
             logger.error('Error', msg);
@@ -389,18 +437,18 @@ module.exports = (crowi) => {
         // remove
         // remove
         await shareLinkToDelete.remove();
         await shareLinkToDelete.remove();
 
 
-        activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_SHARE_LINK_DELETE });
+        activityEvent.emit('update', res.locals.activity._id, {
+          action: SupportedAction.ACTION_SHARE_LINK_DELETE,
+        });
 
 
         return res.apiv3({ deletedShareLink: shareLinkToDelete });
         return res.apiv3({ deletedShareLink: shareLinkToDelete });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in delete share link';
         const msg = 'Error occurred in delete share link';
         logger.error('Error', err);
         logger.error('Error', err);
         return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
       }
       }
-
-    });
-
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 62 - 28
apps/app/src/server/routes/apiv3/slack-integration-legacy-settings.js

@@ -1,9 +1,9 @@
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import express from 'express';
 import express from 'express';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -11,17 +11,24 @@ import loggerFactory from '~/utils/logger';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
 
-
 // eslint-disable-next-line no-unused-vars
 // eslint-disable-next-line no-unused-vars
-const logger = loggerFactory('growi:routes:apiv3:slack-integration-legacy-setting');
+const logger = loggerFactory(
+  'growi:routes:apiv3:slack-integration-legacy-setting',
+);
 
 
 const router = express.Router();
 const router = express.Router();
 
 
 const validator = {
 const validator = {
   slackConfiguration: [
   slackConfiguration: [
-    body('webhookUrl').if(value => value != null).isString().trim(),
+    body('webhookUrl')
+      .if((value) => value != null)
+      .isString()
+      .trim(),
     body('isIncomingWebhookPrioritized').isBoolean(),
     body('isIncomingWebhookPrioritized').isBoolean(),
-    body('slackToken').if(value => value != null).isString().trim(),
+    body('slackToken')
+      .if((value) => value != null)
+      .isString()
+      .trim(),
   ],
   ],
 };
 };
 
 
@@ -45,7 +52,9 @@ const validator = {
  */
  */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 
 
@@ -77,16 +86,26 @@ module.exports = (crowi) => {
    *                              type: boolean
    *                              type: boolean
    *                              description: whether slackbot is configured
    *                              description: whether slackbot is configured
    */
    */
-  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.LEGACY_SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, async(req, res) => {
-
-    const slackIntegrationParams = {
-      isSlackbotConfigured: crowi.slackIntegrationService.isSlackbotConfigured,
-      webhookUrl: await crowi.configManager.getConfig('slack:incomingWebhookUrl'),
-      isIncomingWebhookPrioritized: await crowi.configManager.getConfig('slack:isIncomingWebhookPrioritized'),
-      slackToken: await crowi.configManager.getConfig('slack:token'),
-    };
-    return res.apiv3({ slackIntegrationParams });
-  });
+  router.get(
+    '/',
+    accessTokenParser([SCOPE.READ.ADMIN.LEGACY_SLACK_INTEGRATION]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      const slackIntegrationParams = {
+        isSlackbotConfigured:
+          crowi.slackIntegrationService.isSlackbotConfigured,
+        webhookUrl: await crowi.configManager.getConfig(
+          'slack:incomingWebhookUrl',
+        ),
+        isIncomingWebhookPrioritized: await crowi.configManager.getConfig(
+          'slack:isIncomingWebhookPrioritized',
+        ),
+        slackToken: await crowi.configManager.getConfig('slack:token'),
+      };
+      return res.apiv3({ slackIntegrationParams });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -123,35 +142,50 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      $ref: '#/components/schemas/SlackConfigurationParams'
    *                      $ref: '#/components/schemas/SlackConfigurationParams'
    */
    */
-  router.put('/', accessTokenParser([SCOPE.WRITE.ADMIN.LEGACY_SLACK_INTEGRATION]), loginRequiredStrictly, adminRequired, addActivity,
-    validator.slackConfiguration, apiV3FormValidator, async(req, res) => {
-
+  router.put(
+    '/',
+    accessTokenParser([SCOPE.WRITE.ADMIN.LEGACY_SLACK_INTEGRATION]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.slackConfiguration,
+    apiV3FormValidator,
+    async (req, res) => {
       const requestParams = {
       const requestParams = {
         'slack:incomingWebhookUrl': req.body.webhookUrl,
         'slack:incomingWebhookUrl': req.body.webhookUrl,
-        'slack:isIncomingWebhookPrioritized': req.body.isIncomingWebhookPrioritized,
+        'slack:isIncomingWebhookPrioritized':
+          req.body.isIncomingWebhookPrioritized,
         'slack:token': req.body.slackToken,
         'slack:token': req.body.slackToken,
       };
       };
 
 
       try {
       try {
         await configManager.updateConfigs(requestParams);
         await configManager.updateConfigs(requestParams);
         const responseParams = {
         const responseParams = {
-          webhookUrl: await crowi.configManager.getConfig('slack:incomingWebhookUrl'),
-          isIncomingWebhookPrioritized: await crowi.configManager.getConfig('slack:isIncomingWebhookPrioritized'),
+          webhookUrl: await crowi.configManager.getConfig(
+            'slack:incomingWebhookUrl',
+          ),
+          isIncomingWebhookPrioritized: await crowi.configManager.getConfig(
+            'slack:isIncomingWebhookPrioritized',
+          ),
           slackToken: await crowi.configManager.getConfig('slack:token'),
           slackToken: await crowi.configManager.getConfig('slack:token'),
         };
         };
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE };
+        const parameters = {
+          action:
+            SupportedAction.ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ responseParams });
         return res.apiv3({ responseParams });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating slack configuration';
         const msg = 'Error occurred in updating slack configuration';
         logger.error('Error', err);
         logger.error('Error', err);
-        return res.apiv3Err(new ErrorV3(msg, 'update-slackConfiguration-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'update-slackConfiguration-failed'),
+        );
       }
       }
-
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

Разница между файлами не показана из-за своего большого размера
+ 490 - 271
apps/app/src/server/routes/apiv3/slack-integration-settings.js


+ 315 - 153
apps/app/src/server/routes/apiv3/slack-integration.js

@@ -20,14 +20,12 @@ import loggerFactory from '~/utils/logger';
 import { handleError } from '../../service/slack-command-handler/error-handler';
 import { handleError } from '../../service/slack-command-handler/error-handler';
 import { checkPermission } from '../../util/slack-integration';
 import { checkPermission } from '../../util/slack-integration';
 
 
-
 const logger = loggerFactory('growi:routes:apiv3:slack-integration');
 const logger = loggerFactory('growi:routes:apiv3:slack-integration');
 const router = express.Router();
 const router = express.Router();
 const SlackAppIntegration = mongoose.model('SlackAppIntegration');
 const SlackAppIntegration = mongoose.model('SlackAppIntegration');
 
 
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-
   const { slackIntegrationService } = crowi;
   const { slackIntegrationService } = crowi;
 
 
   // Check if the access token is correct
   // Check if the access token is correct
@@ -35,12 +33,15 @@ module.exports = (crowi) => {
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
 
 
     if (tokenPtoG == null) {
     if (tokenPtoG == null) {
-      const message = 'The value of header \'x-growi-ptog-tokens\' must not be empty.';
+      const message =
+        "The value of header 'x-growi-ptog-tokens' must not be empty.";
       logger.warn(message, { body: req.body });
       logger.warn(message, { body: req.body });
       return next(createError(400, message));
       return next(createError(400, message));
     }
     }
 
 
-    const SlackAppIntegrationCount = await SlackAppIntegration.countDocuments({ tokenPtoG });
+    const SlackAppIntegrationCount = await SlackAppIntegration.countDocuments({
+      tokenPtoG,
+    });
 
 
     logger.debug('verifyAccessTokenFromProxy', {
     logger.debug('verifyAccessTokenFromProxy', {
       tokenPtoG,
       tokenPtoG,
@@ -49,9 +50,10 @@ module.exports = (crowi) => {
 
 
     if (SlackAppIntegrationCount === 0) {
     if (SlackAppIntegrationCount === 0) {
       return res.status(403).send({
       return res.status(403).send({
-        message: 'The access token that identifies the request source is slackbot-proxy is invalid. Did you setup with `/growi register`.\n'
-        + 'Or did you delete registration for GROWI ? if so, the link with GROWI has been disconnected. '
-        + 'Please unregister the information registered in the proxy and setup `/growi register` again.',
+        message:
+          'The access token that identifies the request source is slackbot-proxy is invalid. Did you setup with `/growi register`.\n' +
+          'Or did you delete registration for GROWI ? if so, the link with GROWI has been disconnected. ' +
+          'Please unregister the information registered in the proxy and setup `/growi register` again.',
       });
       });
     }
     }
 
 
@@ -59,12 +61,19 @@ module.exports = (crowi) => {
   }
   }
 
 
   async function extractPermissionsCommands(tokenPtoG) {
   async function extractPermissionsCommands(tokenPtoG) {
-    const slackAppIntegration = await SlackAppIntegration.findOne({ tokenPtoG });
+    const slackAppIntegration = await SlackAppIntegration.findOne({
+      tokenPtoG,
+    });
     if (slackAppIntegration == null) return null;
     if (slackAppIntegration == null) return null;
-    const permissionsForBroadcastUseCommands = slackAppIntegration.permissionsForBroadcastUseCommands;
-    const permissionsForSingleUseCommands = slackAppIntegration.permissionsForSingleUseCommands;
-
-    return { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands };
+    const permissionsForBroadcastUseCommands =
+      slackAppIntegration.permissionsForBroadcastUseCommands;
+    const permissionsForSingleUseCommands =
+      slackAppIntegration.permissionsForSingleUseCommands;
+
+    return {
+      permissionsForBroadcastUseCommands,
+      permissionsForSingleUseCommands,
+    };
   }
   }
 
 
   // TODO: move this middleware to each controller
   // TODO: move this middleware to each controller
@@ -78,8 +87,7 @@ module.exports = (crowi) => {
     let growiCommand;
     let growiCommand;
     try {
     try {
       growiCommand = getGrowiCommand(req.body);
       growiCommand = getGrowiCommand(req.body);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err.message);
       logger.error(err.message);
       return next(err);
       return next(err);
     }
     }
@@ -92,11 +100,15 @@ module.exports = (crowi) => {
           blocks: [
           blocks: [
             markdownSectionBlock('*Command is not supported*'),
             markdownSectionBlock('*Command is not supported*'),
             // eslint-disable-next-line max-len
             // eslint-disable-next-line max-len
-            markdownSectionBlock(`\`/growi ${growiCommand.growiCommandType}\` command is not supported in this version of GROWI bot. Run \`/growi help\` to see all supported commands.`),
+            markdownSectionBlock(
+              `\`/growi ${growiCommand.growiCommandType}\` command is not supported in this version of GROWI bot. Run \`/growi help\` to see all supported commands.`,
+            ),
           ],
           ],
         },
         },
       };
       };
-      return next(new SlackCommandHandlerError('Command type is not specified', options));
+      return next(
+        new SlackCommandHandlerError('Command type is not specified', options),
+      );
     }
     }
 
 
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
@@ -108,19 +120,41 @@ module.exports = (crowi) => {
     const siteUrl = growiInfoService.getSiteUrl();
     const siteUrl = growiInfoService.getSiteUrl();
 
 
     let commandPermission;
     let commandPermission;
-    if (extractPermissions != null) { // with proxy
-      const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands } = extractPermissions;
-      commandPermission = Object.fromEntries([...permissionsForBroadcastUseCommands, ...permissionsForSingleUseCommands]);
-      const isPermitted = checkPermission(commandPermission, growiCommand.growiCommandType, fromChannel);
+    if (extractPermissions != null) {
+      // with proxy
+      const {
+        permissionsForBroadcastUseCommands,
+        permissionsForSingleUseCommands,
+      } = extractPermissions;
+      commandPermission = Object.fromEntries([
+        ...permissionsForBroadcastUseCommands,
+        ...permissionsForSingleUseCommands,
+      ]);
+      const isPermitted = checkPermission(
+        commandPermission,
+        growiCommand.growiCommandType,
+        fromChannel,
+      );
       if (isPermitted) return next();
       if (isPermitted) return next();
 
 
-      return next(createError(403, `It is not allowed to send \`/growi ${growiCommand.growiCommandType}\` command to this GROWI: ${siteUrl}`));
+      return next(
+        createError(
+          403,
+          `It is not allowed to send \`/growi ${growiCommand.growiCommandType}\` command to this GROWI: ${siteUrl}`,
+        ),
+      );
     }
     }
 
 
     // without proxy
     // without proxy
-    commandPermission = configManager.getConfig('slackbot:withoutProxy:commandPermission');
-
-    const isPermitted = checkPermission(commandPermission, growiCommand.growiCommandType, fromChannel);
+    commandPermission = configManager.getConfig(
+      'slackbot:withoutProxy:commandPermission',
+    );
+
+    const isPermitted = checkPermission(
+      commandPermission,
+      growiCommand.growiCommandType,
+      fromChannel,
+    );
     if (isPermitted) {
     if (isPermitted) {
       return next();
       return next();
     }
     }
@@ -130,11 +164,15 @@ module.exports = (crowi) => {
         text: 'Command forbidden',
         text: 'Command forbidden',
         blocks: [
         blocks: [
           markdownSectionBlock('*Command is not supported*'),
           markdownSectionBlock('*Command is not supported*'),
-          markdownSectionBlock(`It is not allowed to send \`/growi ${growiCommand.growiCommandType}\` command to this GROWI: ${siteUrl}`),
+          markdownSectionBlock(
+            `It is not allowed to send \`/growi ${growiCommand.growiCommandType}\` command to this GROWI: ${siteUrl}`,
+          ),
         ],
         ],
       },
       },
     };
     };
-    return next(new SlackCommandHandlerError('Command type is not specified', options));
+    return next(
+      new SlackCommandHandlerError('Command type is not specified', options),
+    );
   }
   }
 
 
   // TODO: move this middleware to each controller
   // TODO: move this middleware to each controller
@@ -148,26 +186,49 @@ module.exports = (crowi) => {
     const { interactionPayloadAccessor } = req;
     const { interactionPayloadAccessor } = req;
     const siteUrl = growiInfoService.getSiteUrl();
     const siteUrl = growiInfoService.getSiteUrl();
 
 
-    const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
+    const { actionId, callbackId } =
+      interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
     const callbacIdkOrActionId = callbackId || actionId;
     const callbacIdkOrActionId = callbackId || actionId;
     const fromChannel = interactionPayloadAccessor.getChannel();
     const fromChannel = interactionPayloadAccessor.getChannel();
 
 
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const extractPermissions = await extractPermissionsCommands(tokenPtoG);
     const extractPermissions = await extractPermissionsCommands(tokenPtoG);
     let commandPermission;
     let commandPermission;
-    if (extractPermissions != null) { // with proxy
-      const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands } = extractPermissions;
-      commandPermission = Object.fromEntries([...permissionsForBroadcastUseCommands, ...permissionsForSingleUseCommands]);
-      const isPermitted = checkPermission(commandPermission, callbacIdkOrActionId, fromChannel);
+    if (extractPermissions != null) {
+      // with proxy
+      const {
+        permissionsForBroadcastUseCommands,
+        permissionsForSingleUseCommands,
+      } = extractPermissions;
+      commandPermission = Object.fromEntries([
+        ...permissionsForBroadcastUseCommands,
+        ...permissionsForSingleUseCommands,
+      ]);
+      const isPermitted = checkPermission(
+        commandPermission,
+        callbacIdkOrActionId,
+        fromChannel,
+      );
       if (isPermitted) return next();
       if (isPermitted) return next();
 
 
-      return next(createError(403, `This interaction is forbidden on this GROWI: ${siteUrl}`));
+      return next(
+        createError(
+          403,
+          `This interaction is forbidden on this GROWI: ${siteUrl}`,
+        ),
+      );
     }
     }
 
 
     // without proxy
     // without proxy
-    commandPermission = configManager.getConfig('slackbot:withoutProxy:commandPermission');
-
-    const isPermitted = checkPermission(commandPermission, callbacIdkOrActionId, fromChannel);
+    commandPermission = configManager.getConfig(
+      'slackbot:withoutProxy:commandPermission',
+    );
+
+    const isPermitted = checkPermission(
+      commandPermission,
+      callbacIdkOrActionId,
+      fromChannel,
+    );
     if (isPermitted) {
     if (isPermitted) {
       return next();
       return next();
     }
     }
@@ -177,7 +238,9 @@ module.exports = (crowi) => {
         text: 'Interaction forbidden',
         text: 'Interaction forbidden',
         blocks: [
         blocks: [
           markdownSectionBlock('*Interaction forbidden*'),
           markdownSectionBlock('*Interaction forbidden*'),
-          markdownSectionBlock(`This interaction is forbidden on this GROWI: ${siteUrl}`),
+          markdownSectionBlock(
+            `This interaction is forbidden on this GROWI: ${siteUrl}`,
+          ),
         ],
         ],
       },
       },
     };
     };
@@ -185,7 +248,9 @@ module.exports = (crowi) => {
   }
   }
 
 
   const addSigningSecretToReq = (req, res, next) => {
   const addSigningSecretToReq = (req, res, next) => {
-    req.slackSigningSecret = configManager.getConfig('slackbot:withoutProxy:signingSecret');
+    req.slackSigningSecret = configManager.getConfig(
+      'slackbot:withoutProxy:signingSecret',
+    );
     return next();
     return next();
   };
   };
 
 
@@ -203,11 +268,15 @@ module.exports = (crowi) => {
 
 
   const parseSlackInteractionRequest = (req, res, next) => {
   const parseSlackInteractionRequest = (req, res, next) => {
     if (req.body.payload == null) {
     if (req.body.payload == null) {
-      return next(new Error('The payload is not in the request from slack or proxy.'));
+      return next(
+        new Error('The payload is not in the request from slack or proxy.'),
+      );
     }
     }
 
 
     req.interactionPayload = JSON.parse(req.body.payload);
     req.interactionPayload = JSON.parse(req.body.payload);
-    req.interactionPayloadAccessor = new InteractionPayloadAccessor(req.interactionPayload);
+    req.interactionPayloadAccessor = new InteractionPayloadAccessor(
+      req.interactionPayload,
+    );
 
 
     return next();
     return next();
   };
   };
@@ -229,19 +298,23 @@ module.exports = (crowi) => {
     if (growiCommand == null) {
     if (growiCommand == null) {
       try {
       try {
         growiCommand = parseSlashCommand(body);
         growiCommand = parseSlashCommand(body);
-      }
-      catch (err) {
+      } catch (err) {
         if (err instanceof InvalidGrowiCommandError) {
         if (err instanceof InvalidGrowiCommandError) {
           const options = {
           const options = {
             respondBody: {
             respondBody: {
               text: 'Command type is not specified',
               text: 'Command type is not specified',
               blocks: [
               blocks: [
                 markdownSectionBlock('*Command type is not specified.*'),
                 markdownSectionBlock('*Command type is not specified.*'),
-                markdownSectionBlock('Run `/growi help` to check the commands you can use.'),
+                markdownSectionBlock(
+                  'Run `/growi help` to check the commands you can use.',
+                ),
               ],
               ],
             },
             },
           };
           };
-          throw new SlackCommandHandlerError('Command type is not specified', options);
+          throw new SlackCommandHandlerError(
+            'Command type is not specified',
+            options,
+          );
         }
         }
         throw err;
         throw err;
       }
       }
@@ -255,8 +328,7 @@ module.exports = (crowi) => {
     try {
     try {
       growiCommand = getGrowiCommand(body);
       growiCommand = getGrowiCommand(body);
       respondUtil = getRespondUtil(responseUrl);
       respondUtil = getRespondUtil(responseUrl);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err.message);
       logger.error(err.message);
       return handleError(err, responseUrl);
       return handleError(err, responseUrl);
     }
     }
@@ -274,22 +346,26 @@ module.exports = (crowi) => {
       await respondUtil.respond({
       await respondUtil.respond({
         text: 'Processing your request ...',
         text: 'Processing your request ...',
         blocks: [
         blocks: [
-          markdownSectionBlock(`Processing your request *"/growi ${growiCommand.growiCommandType}"* on GROWI at ${appSiteUrl} ...`),
+          markdownSectionBlock(
+            `Processing your request *"/growi ${growiCommand.growiCommandType}"* on GROWI at ${appSiteUrl} ...`,
+          ),
         ],
         ],
       });
       });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error('Error occurred while request via axios:', err);
       logger.error('Error occurred while request via axios:', err);
     }
     }
 
 
     try {
     try {
-      await slackIntegrationService.handleCommandRequest(growiCommand, client, body, respondUtil);
-    }
-    catch (err) {
+      await slackIntegrationService.handleCommandRequest(
+        growiCommand,
+        client,
+        body,
+        respondUtil,
+      );
+    } catch (err) {
       logger.error(err.message);
       logger.error(err.message);
       return handleError(err, responseUrl);
       return handleError(err, responseUrl);
     }
     }
-
   }
   }
 
 
   // TODO: this method will be a middleware when typescriptize in the future
   // TODO: this method will be a middleware when typescriptize in the future
@@ -335,21 +411,27 @@ module.exports = (crowi) => {
    *               type: string
    *               type: string
    *               example: "No text."
    *               example: "No text."
    */
    */
-  router.post('/commands', addSigningSecretToReq, verifySlackRequest, checkCommandsPermission, async(req, res) => {
-    const { body } = req;
-    const responseUrl = getResponseUrl(req);
-
-    let client;
-    try {
-      client = await slackIntegrationService.generateClientForCustomBotWithoutProxy();
-    }
-    catch (err) {
-      logger.error(err.message);
-      return handleError(err, responseUrl);
-    }
+  router.post(
+    '/commands',
+    addSigningSecretToReq,
+    verifySlackRequest,
+    checkCommandsPermission,
+    async (req, res) => {
+      const { body } = req;
+      const responseUrl = getResponseUrl(req);
+
+      let client;
+      try {
+        client =
+          await slackIntegrationService.generateClientForCustomBotWithoutProxy();
+      } catch (err) {
+        logger.error(err.message);
+        return handleError(err, responseUrl);
+      }
 
 
-    return handleCommands(body, res, client, responseUrl);
-  });
+      return handleCommands(body, res, client, responseUrl);
+    },
+  );
 
 
   // when relation test
   // when relation test
   /**
   /**
@@ -384,15 +466,19 @@ module.exports = (crowi) => {
    *                 challenge:
    *                 challenge:
    *                   type: string
    *                   type: string
    */
    */
-  router.post('/proxied/verify', verifyAccessTokenFromProxy, async(req, res) => {
-    const { body } = req;
-
-    // eslint-disable-next-line max-len
-    // see: https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification
-    if (body.type === 'url_verification') {
-      return res.send({ challenge: body.challenge });
-    }
-  });
+  router.post(
+    '/proxied/verify',
+    verifyAccessTokenFromProxy,
+    async (req, res) => {
+      const { body } = req;
+
+      // eslint-disable-next-line max-len
+      // see: https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification
+      if (body.type === 'url_verification') {
+        return res.send({ challenge: body.challenge });
+      }
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -419,26 +505,30 @@ module.exports = (crowi) => {
    *               type: string
    *               type: string
    *               example: "No text."
    *               example: "No text."
    */
    */
-  router.post('/proxied/commands', verifyAccessTokenFromProxy, checkCommandsPermission, async(req, res) => {
-    const { body } = req;
-    const responseUrl = getResponseUrl(req);
+  router.post(
+    '/proxied/commands',
+    verifyAccessTokenFromProxy,
+    checkCommandsPermission,
+    async (req, res) => {
+      const { body } = req;
+      const responseUrl = getResponseUrl(req);
 
 
-    const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+      const tokenPtoG = req.headers['x-growi-ptog-tokens'];
 
 
-    let client;
-    try {
-      client = await slackIntegrationService.generateClientByTokenPtoG(tokenPtoG);
-    }
-    catch (err) {
-      logger.error(err.message);
-      return handleError(err, responseUrl);
-    }
+      let client;
+      try {
+        client =
+          await slackIntegrationService.generateClientByTokenPtoG(tokenPtoG);
+      } catch (err) {
+        logger.error(err.message);
+        return handleError(err, responseUrl);
+      }
 
 
-    return handleCommands(body, res, client, responseUrl);
-  });
+      return handleCommands(body, res, client, responseUrl);
+    },
+  );
 
 
   async function handleInteractionsRequest(req, res, client) {
   async function handleInteractionsRequest(req, res, client) {
-
     const { interactionPayload, interactionPayloadAccessor } = req;
     const { interactionPayload, interactionPayloadAccessor } = req;
     const { type } = interactionPayload;
     const { type } = interactionPayload;
     const responseUrl = interactionPayloadAccessor.getResponseUrl();
     const responseUrl = interactionPayloadAccessor.getResponseUrl();
@@ -447,16 +537,25 @@ module.exports = (crowi) => {
       const respondUtil = getRespondUtil(responseUrl);
       const respondUtil = getRespondUtil(responseUrl);
       switch (type) {
       switch (type) {
         case 'block_actions':
         case 'block_actions':
-          await slackIntegrationService.handleBlockActionsRequest(client, interactionPayload, interactionPayloadAccessor, respondUtil);
+          await slackIntegrationService.handleBlockActionsRequest(
+            client,
+            interactionPayload,
+            interactionPayloadAccessor,
+            respondUtil,
+          );
           break;
           break;
         case 'view_submission':
         case 'view_submission':
-          await slackIntegrationService.handleViewSubmissionRequest(client, interactionPayload, interactionPayloadAccessor, respondUtil);
+          await slackIntegrationService.handleViewSubmissionRequest(
+            client,
+            interactionPayload,
+            interactionPayloadAccessor,
+            respondUtil,
+          );
           break;
           break;
         default:
         default:
           break;
           break;
       }
       }
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       logger.error(err);
       return handleError(err, responseUrl);
       return handleError(err, responseUrl);
     }
     }
@@ -482,10 +581,18 @@ module.exports = (crowi) => {
    *       200:
    *       200:
    *         description: OK
    *         description: OK
    */
    */
-  router.post('/interactions', addSigningSecretToReq, verifySlackRequest, parseSlackInteractionRequest, checkInteractionsPermission, async(req, res) => {
-    const client = await slackIntegrationService.generateClientForCustomBotWithoutProxy();
-    return handleInteractionsRequest(req, res, client);
-  });
+  router.post(
+    '/interactions',
+    addSigningSecretToReq,
+    verifySlackRequest,
+    parseSlackInteractionRequest,
+    checkInteractionsPermission,
+    async (req, res) => {
+      const client =
+        await slackIntegrationService.generateClientForCustomBotWithoutProxy();
+      return handleInteractionsRequest(req, res, client);
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -507,11 +614,18 @@ module.exports = (crowi) => {
    *       200:
    *       200:
    *         description: OK
    *         description: OK
    */
    */
-  router.post('/proxied/interactions', verifyAccessTokenFromProxy, parseSlackInteractionRequest, checkInteractionsPermission, async(req, res) => {
-    const tokenPtoG = req.headers['x-growi-ptog-tokens'];
-    const client = await slackIntegrationService.generateClientByTokenPtoG(tokenPtoG);
-    return handleInteractionsRequest(req, res, client);
-  });
+  router.post(
+    '/proxied/interactions',
+    verifyAccessTokenFromProxy,
+    parseSlackInteractionRequest,
+    checkInteractionsPermission,
+    async (req, res) => {
+      const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+      const client =
+        await slackIntegrationService.generateClientByTokenPtoG(tokenPtoG);
+      return handleInteractionsRequest(req, res, client);
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -540,13 +654,25 @@ module.exports = (crowi) => {
    *                   items:
    *                   items:
    *                     type: object
    *                     type: object
    */
    */
-  router.get('/supported-commands', verifyAccessTokenFromProxy, async(req, res) => {
-    const tokenPtoG = req.headers['x-growi-ptog-tokens'];
-    const slackAppIntegration = await SlackAppIntegration.findOne({ tokenPtoG });
-    const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands } = slackAppIntegration;
-
-    return res.apiv3({ permissionsForBroadcastUseCommands, permissionsForSingleUseCommands });
-  });
+  router.get(
+    '/supported-commands',
+    verifyAccessTokenFromProxy,
+    async (req, res) => {
+      const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+      const slackAppIntegration = await SlackAppIntegration.findOne({
+        tokenPtoG,
+      });
+      const {
+        permissionsForBroadcastUseCommands,
+        permissionsForSingleUseCommands,
+      } = slackAppIntegration;
+
+      return res.apiv3({
+        permissionsForBroadcastUseCommands,
+        permissionsForSingleUseCommands,
+      });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -575,28 +701,46 @@ module.exports = (crowi) => {
    *             schema:
    *             schema:
    *               type: object
    *               type: object
    */
    */
-  router.post('/events', verifyUrlMiddleware, addSigningSecretToReq, verifySlackRequest, async(req, res) => {
-    const { event } = req.body;
-
-    const growiBotEvent = {
-      eventType: event.type,
-      event,
-    };
-
-    try {
-      const client = await slackIntegrationService.generateClientForCustomBotWithoutProxy();
-      // convert permission object to map
-      const permission = new Map(Object.entries(crowi.configManager.getConfig('slackbot:withoutProxy:eventActionsPermission')));
-
-      await crowi.slackIntegrationService.handleEventsRequest(client, growiBotEvent, permission);
+  router.post(
+    '/events',
+    verifyUrlMiddleware,
+    addSigningSecretToReq,
+    verifySlackRequest,
+    async (req, res) => {
+      const { event } = req.body;
+
+      const growiBotEvent = {
+        eventType: event.type,
+        event,
+      };
 
 
-      return res.apiv3({});
-    }
-    catch (err) {
-      logger.error('Error occurred while handling event request.', err);
-      return res.apiv3Err(new ErrorV3('Error occurred while handling event request.'));
-    }
-  });
+      try {
+        const client =
+          await slackIntegrationService.generateClientForCustomBotWithoutProxy();
+        // convert permission object to map
+        const permission = new Map(
+          Object.entries(
+            crowi.configManager.getConfig(
+              'slackbot:withoutProxy:eventActionsPermission',
+            ),
+          ),
+        );
+
+        await crowi.slackIntegrationService.handleEventsRequest(
+          client,
+          growiBotEvent,
+          permission,
+        );
+
+        return res.apiv3({});
+      } catch (err) {
+        logger.error('Error occurred while handling event request.', err);
+        return res.apiv3Err(
+          new ErrorV3('Error occurred while handling event request.'),
+        );
+      }
+    },
+  );
 
 
   const validator = {
   const validator = {
     validateEventRequest: [
     validateEventRequest: [
@@ -634,33 +778,51 @@ module.exports = (crowi) => {
    *             schema:
    *             schema:
    *               type: object
    *               type: object
    */
    */
-  router.post('/proxied/events', verifyAccessTokenFromProxy, validator.validateEventRequest, async(req, res) => {
-    const { growiBotEvent, data } = req.body;
+  router.post(
+    '/proxied/events',
+    verifyAccessTokenFromProxy,
+    validator.validateEventRequest,
+    async (req, res) => {
+      const { growiBotEvent, data } = req.body;
 
 
-    try {
-      const tokenPtoG = req.headers['x-growi-ptog-tokens'];
-      const SlackAppIntegration = mongoose.model('SlackAppIntegration');
-      const slackAppIntegration = await SlackAppIntegration.findOne({ tokenPtoG });
+      try {
+        const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+        const SlackAppIntegration = mongoose.model('SlackAppIntegration');
+        const slackAppIntegration = await SlackAppIntegration.findOne({
+          tokenPtoG,
+        });
+
+        if (slackAppIntegration == null) {
+          throw new Error(
+            'No SlackAppIntegration exists that corresponds to the tokenPtoG specified.',
+          );
+        }
 
 
-      if (slackAppIntegration == null) {
-        throw new Error('No SlackAppIntegration exists that corresponds to the tokenPtoG specified.');
+        const client =
+          await slackIntegrationService.generateClientBySlackAppIntegration(
+            slackAppIntegration,
+          );
+        const { permissionsForSlackEventActions } = slackAppIntegration;
+
+        await slackIntegrationService.handleEventsRequest(
+          client,
+          growiBotEvent,
+          permissionsForSlackEventActions,
+          data,
+        );
+
+        return res.apiv3({});
+      } catch (err) {
+        logger.error('Error occurred while handling event request.', err);
+        return res.apiv3Err(
+          new ErrorV3('Error occurred while handling event request.'),
+        );
       }
       }
-
-      const client = await slackIntegrationService.generateClientBySlackAppIntegration(slackAppIntegration);
-      const { permissionsForSlackEventActions } = slackAppIntegration;
-
-      await slackIntegrationService.handleEventsRequest(client, growiBotEvent, permissionsForSlackEventActions, data);
-
-      return res.apiv3({});
-    }
-    catch (err) {
-      logger.error('Error occurred while handling event request.', err);
-      return res.apiv3Err(new ErrorV3('Error occurred while handling event request.'));
-    }
-  });
+    },
+  );
 
 
   // error handler
   // error handler
-  router.use(async(err, req, res, next) => {
+  router.use(async (err, req, res, next) => {
     const responseUrl = getResponseUrl(req);
     const responseUrl = getResponseUrl(req);
     if (responseUrl == null) {
     if (responseUrl == null) {
       // pass err to global error handler
       // pass err to global error handler

+ 10 - 12
apps/app/src/server/routes/apiv3/staffs.js

@@ -1,13 +1,12 @@
-import axios from 'axios';
 import { addHours } from 'date-fns/addHours';
 import { addHours } from 'date-fns/addHours';
 import { isAfter } from 'date-fns/isAfter';
 import { isAfter } from 'date-fns/isAfter';
 import { Router } from 'express';
 import { Router } from 'express';
 
 
+import axios from '~/utils/axios';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:routes:apiv3:staffs'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:staffs'); // eslint-disable-line no-unused-vars
 
 
-
 const router = Router();
 const router = Router();
 
 
 const contributors = require('^/resource/Contributor');
 const contributors = require('^/resource/Contributor');
@@ -17,18 +16,19 @@ const contributorsCache = contributors;
 let gcContributors;
 let gcContributors;
 
 
 // Sorting contributors by this method
 // Sorting contributors by this method
-const compareFunction = function(a, b) {
-  return a.order - b.order;
-};
+const compareFunction = (a, b) => a.order - b.order;
 
 
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-
-  router.get('/', async(req, res) => {
+  router.get('/', async (req, res) => {
     const now = new Date();
     const now = new Date();
-    const growiCloudUri = await crowi.configManager.getConfig('app:growiCloudUri');
+    const growiCloudUri =
+      await crowi.configManager.getConfig('app:growiCloudUri');
 
 
-    if (growiCloudUri != null && (expiredAt == null || isAfter(now, expiredAt))) {
+    if (
+      growiCloudUri != null &&
+      (expiredAt == null || isAfter(now, expiredAt))
+    ) {
       const url = new URL('_api/staffCredit', growiCloudUri);
       const url = new URL('_api/staffCredit', growiCloudUri);
       try {
       try {
         const gcContributorsRes = await axios.get(url.toString());
         const gcContributorsRes = await axios.get(url.toString());
@@ -41,8 +41,7 @@ module.exports = (crowi) => {
         contributorsCache.sort(compareFunction);
         contributorsCache.sort(compareFunction);
         // caching 'expiredAt' for 1 hour
         // caching 'expiredAt' for 1 hour
         expiredAt = addHours(now, 1);
         expiredAt = addHours(now, 1);
-      }
-      catch (err) {
+      } catch (err) {
         logger.warn('Getting GROWI.cloud staffcredit is failed');
         logger.warn('Getting GROWI.cloud staffcredit is failed');
       }
       }
     }
     }
@@ -50,5 +49,4 @@ module.exports = (crowi) => {
   });
   });
 
 
   return router;
   return router;
-
 };
 };

+ 45 - 39
apps/app/src/server/routes/apiv3/statistics.js

@@ -24,47 +24,46 @@ const USER_STATUS_MASTER = {
  *         type: object
  *         type: object
  *         properties:
  *         properties:
  *           data:
  *           data:
-*             type: object
-*             properties:
-*               total:
-*                 type: integer
-*                 example: 1
-*               active:
-*                 type: object
-*                 properties:
-*                   total:
-*                     type: integer
-*                     example: 1
-*                   admin:
-*                     type: integer
-*                     example: 1
-*               inactive:
-*                 type: object
-*                 properties:
-*                   total:
-*                     type: integer
-*                     example: 0
-*                   registered:
-*                     type: integer
-*                     example: 0
-*                   suspended:
-*                     type: integer
-*                     example: 0
-*                   deleted:
-*                     type: integer
-*                     example: 0
-*                   invited:
-*                     type: integer
-*                     example: 0
-*/
+ *             type: object
+ *             properties:
+ *               total:
+ *                 type: integer
+ *                 example: 1
+ *               active:
+ *                 type: object
+ *                 properties:
+ *                   total:
+ *                     type: integer
+ *                     example: 1
+ *                   admin:
+ *                     type: integer
+ *                     example: 1
+ *               inactive:
+ *                 type: object
+ *                 properties:
+ *                   total:
+ *                     type: integer
+ *                     example: 0
+ *                   registered:
+ *                     type: integer
+ *                     example: 0
+ *                   suspended:
+ *                     type: integer
+ *                     example: 0
+ *                   deleted:
+ *                     type: integer
+ *                     example: 0
+ *                   invited:
+ *                     type: integer
+ *                     example: 0
+ */
 
 
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-
   const models = crowi.models;
   const models = crowi.models;
   const User = models.User;
   const User = models.User;
 
 
-  const getUserStatistics = async() => {
+  const getUserStatistics = async () => {
     const userCountGroupByStatus = await User.aggregate().group({
     const userCountGroupByStatus = await User.aggregate().group({
       _id: '$status',
       _id: '$status',
       totalCount: { $sum: 1 },
       totalCount: { $sum: 1 },
@@ -86,7 +85,11 @@ module.exports = (crowi) => {
     delete userCountResults.active;
     delete userCountResults.active;
 
 
     // Calculate the total number of inactive users
     // Calculate the total number of inactive users
-    const inactiveUserTotal = userCountResults.invited + userCountResults.deleted + userCountResults.suspended + userCountResults.registered;
+    const inactiveUserTotal =
+      userCountResults.invited +
+      userCountResults.deleted +
+      userCountResults.suspended +
+      userCountResults.registered;
 
 
     // Get admin users
     // Get admin users
     const adminUsers = await User.findAdmins();
     const adminUsers = await User.findAdmins();
@@ -104,7 +107,7 @@ module.exports = (crowi) => {
     };
     };
   };
   };
 
 
-  const getUserStatisticsForNotLoggedIn = async() => {
+  const getUserStatisticsForNotLoggedIn = async () => {
     const data = await getUserStatistics();
     const data = await getUserStatistics();
     delete data.active.admin;
     delete data.active.admin;
     delete data.inactive.invited;
     delete data.inactive.invited;
@@ -132,8 +135,11 @@ module.exports = (crowi) => {
    *                description: Statistics for all user
    *                description: Statistics for all user
    *                $ref: '#/components/schemas/StatisticsUserResponse'
    *                $ref: '#/components/schemas/StatisticsUserResponse'
    */
    */
-  router.get('/user', noCache(), async(req, res) => {
-    const data = req.user == null ? await getUserStatisticsForNotLoggedIn() : await getUserStatistics();
+  router.get('/user', noCache(), async (req, res) => {
+    const data =
+      req.user == null
+        ? await getUserStatisticsForNotLoggedIn()
+        : await getUserStatistics();
     res.status(200).send({ data });
     res.status(200).send({ data });
   });
   });
 
 

+ 44 - 21
apps/app/src/server/routes/apiv3/user-group-relation.js

@@ -1,7 +1,7 @@
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import express from 'express';
 import express from 'express';
 
 
-import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers';
 import UserGroupRelation from '~/server/models/user-group-relation';
 import UserGroupRelation from '~/server/models/user-group-relation';
@@ -17,12 +17,16 @@ const validator = {};
 
 
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
 
 
   validator.list = [
   validator.list = [
     query('groupIds', 'groupIds is required and must be an array').isArray(),
     query('groupIds', 'groupIds is required and must be an array').isArray(),
-    query('childGroupIds', 'childGroupIds must be an array').optional().isArray(),
+    query('childGroupIds', 'childGroupIds must be an array')
+      .optional()
+      .isArray(),
   ];
   ];
 
 
   /**
   /**
@@ -55,28 +59,47 @@ module.exports = (crowi) => {
    *                          items:
    *                          items:
    *                            type: object
    *                            type: object
    */
    */
-  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired, validator.list, async(req, res) => {
-    const { query } = req;
+  router.get(
+    '/',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.list,
+    async (req, res) => {
+      const { query } = req;
 
 
-    try {
-      const relations = await UserGroupRelation.find({ relatedGroup: { $in: query.groupIds } }).populate('relatedUser');
+      try {
+        const relations = await UserGroupRelation.find({
+          relatedGroup: { $in: query.groupIds },
+        }).populate('relatedUser');
 
 
-      let relationsOfChildGroups = null;
-      if (Array.isArray(query.childGroupIds)) {
-        const _relationsOfChildGroups = await UserGroupRelation.find({ relatedGroup: { $in: query.childGroupIds } }).populate('relatedUser');
-        relationsOfChildGroups = _relationsOfChildGroups.map(relation => serializeUserGroupRelationSecurely(relation)); // serialize
-      }
+        let relationsOfChildGroups = null;
+        if (Array.isArray(query.childGroupIds)) {
+          const _relationsOfChildGroups = await UserGroupRelation.find({
+            relatedGroup: { $in: query.childGroupIds },
+          }).populate('relatedUser');
+          relationsOfChildGroups = _relationsOfChildGroups.map((relation) =>
+            serializeUserGroupRelationSecurely(relation),
+          ); // serialize
+        }
 
 
-      const serialized = relations.map(relation => serializeUserGroupRelationSecurely(relation));
+        const serialized = relations.map((relation) =>
+          serializeUserGroupRelationSecurely(relation),
+        );
 
 
-      return res.apiv3({ userGroupRelations: serialized, relationsOfChildGroups });
-    }
-    catch (err) {
-      const msg = 'Error occurred in fetching user group relations';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-relation-list-fetch-failed'));
-    }
-  });
+        return res.apiv3({
+          userGroupRelations: serialized,
+          relationsOfChildGroups,
+        });
+      } catch (err) {
+        const msg = 'Error occurred in fetching user group relations';
+        logger.error('Error', err);
+        return res.apiv3Err(
+          new ErrorV3(msg, 'user-group-relation-list-fetch-failed'),
+        );
+      }
+    },
+  );
 
 
   return router;
   return router;
 };
 };

+ 349 - 192
apps/app/src/server/routes/apiv3/user-group.js

@@ -1,54 +1,61 @@
 import { GroupType } from '@growi/core';
 import { GroupType } from '@growi/core';
+import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import express from 'express';
 import express from 'express';
-import {
-  body, param, query, sanitizeQuery,
-} from 'express-validator';
+import { body, param, query, sanitizeQuery } from 'express-validator';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
-import { SCOPE } from '@growi/core/dist/interfaces';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers/user-group-relation-serializer';
 import { serializeUserGroupRelationSecurely } from '~/server/models/serializers/user-group-relation-serializer';
 import UserGroup from '~/server/models/user-group';
 import UserGroup from '~/server/models/user-group';
 import UserGroupRelation from '~/server/models/user-group-relation';
 import UserGroupRelation from '~/server/models/user-group-relation';
 import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
-import { toPagingLimit, toPagingOffset } from '~/server/util/express-validator/sanitizer';
+import {
+  toPagingLimit,
+  toPagingOffset,
+} from '~/server/util/express-validator/sanitizer';
 import { generalXssFilter } from '~/services/general-xss-filter';
 import { generalXssFilter } from '~/services/general-xss-filter';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 
 
-
 const logger = loggerFactory('growi:routes:apiv3:user-group'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:user-group'); // eslint-disable-line no-unused-vars
 
 
 const router = express.Router();
 const router = express.Router();
 
 
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const loginRequiredStrictly = require('../../middlewares/login-required')(
+    crowi,
+  );
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 
 
   const activityEvent = crowi.event('activity');
   const activityEvent = crowi.event('activity');
 
 
-  const {
-    User,
-    Page,
-  } = crowi.models;
+  const { User, Page } = crowi.models;
 
 
   const validator = {
   const validator = {
     create: [
     create: [
-      body('name', 'Group name is required').trim().exists({ checkFalsy: true }),
+      body('name', 'Group name is required')
+        .trim()
+        .exists({ checkFalsy: true }),
       body('description', 'Description must be a string').optional().isString(),
       body('description', 'Description must be a string').optional().isString(),
       body('parentId', 'ParentId must be a string').optional().isString(),
       body('parentId', 'ParentId must be a string').optional().isString(),
     ],
     ],
     update: [
     update: [
       body('name', 'Group name must be a string').optional().trim().isString(),
       body('name', 'Group name must be a string').optional().trim().isString(),
-      body('description', 'Group description must be a string').optional().isString(),
-      body('parentId', 'ParentId must be a string or null').optional({ nullable: true }).isString(),
-      body('forceUpdateParents', 'forceUpdateParents must be a boolean').optional().isBoolean(),
+      body('description', 'Group description must be a string')
+        .optional()
+        .isString(),
+      body('parentId', 'ParentId must be a string or null')
+        .optional({ nullable: true })
+        .isString(),
+      body('forceUpdateParents', 'forceUpdateParents must be a boolean')
+        .optional()
+        .isBoolean(),
     ],
     ],
     delete: [
     delete: [
       param('id').trim().exists({ checkFalsy: true }),
       param('id').trim().exists({ checkFalsy: true }),
@@ -57,7 +64,9 @@ module.exports = (crowi) => {
     ],
     ],
     listChildren: [
     listChildren: [
       query('parentIds', 'parentIds must be an array').optional().isArray(),
       query('parentIds', 'parentIds must be an array').optional().isArray(),
-      query('includeGrandChildren', 'parentIds must be boolean').optional().isBoolean(),
+      query('includeGrandChildren', 'parentIds must be boolean')
+        .optional()
+        .isBoolean(),
     ],
     ],
     ancestorGroup: [
     ancestorGroup: [
       query('groupId', 'groupId must be a string').optional().isString(),
       query('groupId', 'groupId must be a string').optional().isString(),
@@ -137,27 +146,41 @@ module.exports = (crowi) => {
    *                      type: number
    *                      type: number
    *                      description: the number of items per page
    *                      description: the number of items per page
    */
    */
-  router.get('/', accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired, async(req, res) => {
-    const { query } = req;
-
-    try {
-      const page = query.page != null ? parseInt(query.page) : undefined;
-      const limit = query.limit != null ? parseInt(query.limit) : undefined;
-      const offset = query.offset != null ? parseInt(query.offset) : undefined;
-      const pagination = query.pagination != null ? query.pagination !== 'false' : undefined;
+  router.get(
+    '/',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
+      const { query } = req;
 
 
-      const result = await UserGroup.findWithPagination({
-        page, limit, offset, pagination,
-      });
-      const { docs: userGroups, totalDocs: totalUserGroups, limit: pagingLimit } = result;
-      return res.apiv3({ userGroups, totalUserGroups, pagingLimit });
-    }
-    catch (err) {
-      const msg = 'Error occurred in fetching user group list';
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(msg, 'user-group-list-fetch-failed'));
-    }
-  });
+      try {
+        const page = query.page != null ? parseInt(query.page) : undefined;
+        const limit = query.limit != null ? parseInt(query.limit) : undefined;
+        const offset =
+          query.offset != null ? parseInt(query.offset) : undefined;
+        const pagination =
+          query.pagination != null ? query.pagination !== 'false' : undefined;
+
+        const result = await UserGroup.findWithPagination({
+          page,
+          limit,
+          offset,
+          pagination,
+        });
+        const {
+          docs: userGroups,
+          totalDocs: totalUserGroups,
+          limit: pagingLimit,
+        } = result;
+        return res.apiv3({ userGroups, totalUserGroups, pagingLimit });
+      } catch (err) {
+        const msg = 'Error occurred in fetching user group list';
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'user-group-list-fetch-failed'));
+      }
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -190,23 +213,27 @@ module.exports = (crowi) => {
    *                        type: object
    *                        type: object
    *                      description: userGroup objects
    *                      description: userGroup objects
    */
    */
-  router.get('/ancestors',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+  router.get(
+    '/ancestors',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
     validator.ancestorGroup,
     validator.ancestorGroup,
-    async(req, res) => {
+    async (req, res) => {
       const { groupId } = req.query;
       const { groupId } = req.query;
 
 
       try {
       try {
         const userGroup = await UserGroup.findOne({ _id: { $eq: groupId } });
         const userGroup = await UserGroup.findOne({ _id: { $eq: groupId } });
-        const ancestorUserGroups = await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
+        const ancestorUserGroups =
+          await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
         return res.apiv3({ ancestorUserGroups });
         return res.apiv3({ ancestorUserGroups });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred while searching user groups';
         const msg = 'Error occurred while searching user groups';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -251,26 +278,33 @@ module.exports = (crowi) => {
    *                          type: object
    *                          type: object
    *                        description: Grandchild user group objects
    *                        description: Grandchild user group objects
    */
    */
-  router.get('/children',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+  router.get(
+    '/children',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
     validator.listChildren,
     validator.listChildren,
-    async(req, res) => {
+    async (req, res) => {
       try {
       try {
         const { parentIds, includeGrandChildren = false } = req.query;
         const { parentIds, includeGrandChildren = false } = req.query;
 
 
-        const userGroupsResult = await UserGroup.findChildrenByParentIds(parentIds, includeGrandChildren);
+        const userGroupsResult = await UserGroup.findChildrenByParentIds(
+          parentIds,
+          includeGrandChildren,
+        );
         return res.apiv3({
         return res.apiv3({
           childUserGroups: userGroupsResult.childUserGroups,
           childUserGroups: userGroupsResult.childUserGroups,
           grandChildUserGroups: userGroupsResult.grandChildUserGroups,
           grandChildUserGroups: userGroupsResult.grandChildUserGroups,
         });
         });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in fetching child user group list';
         const msg = 'Error occurred in fetching child user group list';
         logger.error(msg, err);
         logger.error(msg, err);
-        return res.apiv3Err(new ErrorV3(msg, 'child-user-group-list-fetch-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'child-user-group-list-fetch-failed'),
+        );
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -309,28 +343,39 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: A result of `UserGroup.createGroupByName`
    *                      description: A result of `UserGroup.createGroupByName`
    */
    */
-  router.post('/',
-    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    addActivity, validator.create, apiV3FormValidator,
-    async(req, res) => {
+  router.post(
+    '/',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    addActivity,
+    validator.create,
+    apiV3FormValidator,
+    async (req, res) => {
       const { name, description = '', parentId } = req.body;
       const { name, description = '', parentId } = req.body;
 
 
       try {
       try {
         const userGroupName = generalXssFilter.process(name);
         const userGroupName = generalXssFilter.process(name);
         const userGroupDescription = generalXssFilter.process(description);
         const userGroupDescription = generalXssFilter.process(description);
-        const userGroup = await UserGroup.createGroup(userGroupName, userGroupDescription, parentId);
-
-        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_CREATE };
+        const userGroup = await UserGroup.createGroup(
+          userGroupName,
+          userGroupDescription,
+          parentId,
+        );
+
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_USER_GROUP_CREATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ userGroup }, 201);
         return res.apiv3({ userGroup }, 201);
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in creating a user group';
         const msg = 'Error occurred in creating a user group';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-group-create-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-group-create-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -363,27 +408,35 @@ module.exports = (crowi) => {
    *                        type: object
    *                        type: object
    *                      description: userGroup objects
    *                      description: userGroup objects
    */
    */
-  router.get('/selectable-parent-groups',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+  router.get(
+    '/selectable-parent-groups',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
     validator.selectableGroups,
     validator.selectableGroups,
-    async(req, res) => {
+    async (req, res) => {
       const { groupId } = req.query;
       const { groupId } = req.query;
 
 
       try {
       try {
         const userGroup = await UserGroup.findOne({ _id: { $eq: groupId } });
         const userGroup = await UserGroup.findOne({ _id: { $eq: groupId } });
 
 
-        const descendantGroups = await UserGroup.findGroupsWithDescendantsRecursively([userGroup], []);
-        const descendantGroupIds = descendantGroups.map(userGroups => userGroups._id.toString());
+        const descendantGroups =
+          await UserGroup.findGroupsWithDescendantsRecursively([userGroup], []);
+        const descendantGroupIds = descendantGroups.map((userGroups) =>
+          userGroups._id.toString(),
+        );
 
 
-        const selectableParentGroups = await UserGroup.find({ _id: { $nin: [groupId, ...descendantGroupIds] } });
+        const selectableParentGroups = await UserGroup.find({
+          _id: { $nin: [groupId, ...descendantGroupIds] },
+        });
         return res.apiv3({ selectableParentGroups });
         return res.apiv3({ selectableParentGroups });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred while searching user groups';
         const msg = 'Error occurred while searching user groups';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -416,10 +469,13 @@ module.exports = (crowi) => {
    *                        type: object
    *                        type: object
    *                      description: userGroup objects
    *                      description: userGroup objects
    */
    */
-  router.get('/selectable-child-groups',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+  router.get(
+    '/selectable-child-groups',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
     validator.selectableGroups,
     validator.selectableGroups,
-    async(req, res) => {
+    async (req, res) => {
       const { groupId } = req.query;
       const { groupId } = req.query;
 
 
       try {
       try {
@@ -430,16 +486,22 @@ module.exports = (crowi) => {
           UserGroup.findGroupsWithDescendantsRecursively([userGroup], []),
           UserGroup.findGroupsWithDescendantsRecursively([userGroup], []),
         ]);
         ]);
 
 
-        const excludeUserGroupIds = [userGroup, ...ancestorGroups, ...descendantGroups].map(userGroups => userGroups._id.toString());
-        const selectableChildGroups = await UserGroup.find({ _id: { $nin: excludeUserGroupIds } });
+        const excludeUserGroupIds = [
+          userGroup,
+          ...ancestorGroups,
+          ...descendantGroups,
+        ].map((userGroups) => userGroups._id.toString());
+        const selectableChildGroups = await UserGroup.find({
+          _id: { $nin: excludeUserGroupIds },
+        });
         return res.apiv3({ selectableChildGroups });
         return res.apiv3({ selectableChildGroups });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred while searching user groups';
         const msg = 'Error occurred while searching user groups';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-search-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -470,22 +532,25 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: userGroup object
    *                      description: userGroup object
    */
    */
-  router.get('/:id',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
+  router.get(
+    '/:id',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
     validator.selectableGroups,
     validator.selectableGroups,
-    async(req, res) => {
+    async (req, res) => {
       const { id: groupId } = req.params;
       const { id: groupId } = req.params;
 
 
       try {
       try {
         const userGroup = await UserGroup.findById(groupId);
         const userGroup = await UserGroup.findById(groupId);
         return res.apiv3({ userGroup });
         return res.apiv3({ userGroup });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred while getting user group';
         const msg = 'Error occurred while getting user group';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-get-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-get-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -531,34 +596,51 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: A result of `UserGroup.removeCompletelyById`
    *                      description: A result of `UserGroup.removeCompletelyById`
    */
    */
-  router.delete('/:id',
-    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    validator.delete, apiV3FormValidator, addActivity,
-    async(req, res) => {
+  router.delete(
+    '/:id',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.delete,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
       const { id: deleteGroupId } = req.params;
       const { id: deleteGroupId } = req.params;
-      const { actionName, transferToUserGroupId, transferToUserGroupType } = req.query;
-
-      const transferToUserGroup = typeof transferToUserGroupId === 'string'
-        && (transferToUserGroupType === GroupType.userGroup || transferToUserGroupType === GroupType.externalUserGroup)
-        ? {
-          item: transferToUserGroupId,
-          type: transferToUserGroupType,
-        } : undefined;
+      const { actionName, transferToUserGroupId, transferToUserGroupType } =
+        req.query;
+
+      const transferToUserGroup =
+        typeof transferToUserGroupId === 'string' &&
+        (transferToUserGroupType === GroupType.userGroup ||
+          transferToUserGroupType === GroupType.externalUserGroup)
+          ? {
+              item: transferToUserGroupId,
+              type: transferToUserGroupType,
+            }
+          : undefined;
 
 
       try {
       try {
-        const userGroups = await crowi.userGroupService.removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferToUserGroup);
-
-        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE };
+        const userGroups =
+          await crowi.userGroupService.removeCompletelyByRootGroupId(
+            deleteGroupId,
+            actionName,
+            req.user,
+            transferToUserGroup,
+          );
+
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ userGroups });
         return res.apiv3({ userGroups });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred while deleting user groups';
         const msg = 'Error occurred while deleting user groups';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-delete-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-groups-delete-failed'));
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -607,30 +689,45 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: A result of `UserGroup.updateName`
    *                      description: A result of `UserGroup.updateName`
    */
    */
-  router.put('/:id',
-    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    validator.update, apiV3FormValidator, addActivity,
-    async(req, res) => {
+  router.put(
+    '/:id',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.update,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
       const {
       const {
-        name, description, parentId, forceUpdateParents = false,
+        name,
+        description,
+        parentId,
+        forceUpdateParents = false,
       } = req.body;
       } = req.body;
 
 
       try {
       try {
-        const userGroup = await crowi.userGroupService.updateGroup(id, name, description, parentId, forceUpdateParents);
-
-        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_UPDATE };
+        const userGroup = await crowi.userGroupService.updateGroup(
+          id,
+          name,
+          description,
+          parentId,
+          forceUpdateParents,
+        );
+
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_USER_GROUP_UPDATE,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
 
         return res.apiv3({ userGroup });
         return res.apiv3({ userGroup });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = 'Error occurred in updating a user group name';
         const msg = 'Error occurred in updating a user group name';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-group-update-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-group-update-failed'));
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -663,28 +760,34 @@ module.exports = (crowi) => {
    *                        $ref: '#/components/schemas/User'
    *                        $ref: '#/components/schemas/User'
    *                      description: user objects
    *                      description: user objects
    */
    */
-  router.get('/:id/users',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    async(req, res) => {
+  router.get(
+    '/:id/users',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
 
 
       try {
       try {
         const userGroup = await UserGroup.findById(id);
         const userGroup = await UserGroup.findById(id);
-        const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
+        const userGroupRelations =
+          await UserGroupRelation.findAllRelationForUserGroup(userGroup);
 
 
         const serializeUsers = userGroupRelations.map((userGroupRelation) => {
         const serializeUsers = userGroupRelations.map((userGroupRelation) => {
           return serializeUserSecurely(userGroupRelation.relatedUser);
           return serializeUserSecurely(userGroupRelation.relatedUser);
         });
         });
-        const users = serializeUsers.filter(user => user != null);
+        const users = serializeUsers.filter((user) => user != null);
 
 
         return res.apiv3({ users });
         return res.apiv3({ users });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = `Error occurred in fetching users for group: ${id}`;
         const msg = `Error occurred in fetching users for group: ${id}`;
         logger.error(msg, err);
         logger.error(msg, err);
-        return res.apiv3Err(new ErrorV3(msg, 'user-group-user-list-fetch-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'user-group-user-list-fetch-failed'),
+        );
       }
       }
-    });
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -737,21 +840,29 @@ module.exports = (crowi) => {
    *                        $ref: '#/components/schemas/User'
    *                        $ref: '#/components/schemas/User'
    *                      description: user objects
    *                      description: user objects
    */
    */
-  router.get('/:id/unrelated-users',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    async(req, res) => {
+  router.get(
+    '/:id/unrelated-users',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
-      const {
-        searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
-      } = req.query;
+      const { searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched } =
+        req.query;
 
 
       const queryOptions = {
       const queryOptions = {
-        searchWord, searchType, isAlsoNameSearched, isAlsoMailSearched,
+        searchWord,
+        searchType,
+        isAlsoNameSearched,
+        isAlsoMailSearched,
       };
       };
 
 
       try {
       try {
         const userGroup = await UserGroup.findById(id);
         const userGroup = await UserGroup.findById(id);
-        const users = await UserGroupRelation.findUserByNotRelatedGroup(userGroup, queryOptions);
+        const users = await UserGroupRelation.findUserByNotRelatedGroup(
+          userGroup,
+          queryOptions,
+        );
 
 
         // return email only this api
         // return email only this api
         const serializedUsers = users.map((user) => {
         const serializedUsers = users.map((user) => {
@@ -761,14 +872,15 @@ module.exports = (crowi) => {
           return serializedUser;
           return serializedUser;
         });
         });
         return res.apiv3({ users: serializedUsers });
         return res.apiv3({ users: serializedUsers });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = `Error occurred in fetching unrelated users for group: ${id}`;
         const msg = `Error occurred in fetching unrelated users for group: ${id}`;
         logger.error(msg, err);
         logger.error(msg, err);
-        return res.apiv3Err(new ErrorV3(msg, 'user-group-unrelated-user-list-fetch-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'user-group-unrelated-user-list-fetch-failed'),
+        );
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -809,10 +921,15 @@ module.exports = (crowi) => {
    *                      type: number
    *                      type: number
    *                      description: the number of relations created
    *                      description: the number of relations created
    */
    */
-  router.post('/:id/users/:username',
-    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    validator.users.post, apiV3FormValidator, addActivity,
-    async(req, res) => {
+  router.post(
+    '/:id/users/:username',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.users.post,
+    apiV3FormValidator,
+    addActivity,
+    async (req, res) => {
       const { id, username } = req.params;
       const { id, username } = req.params;
 
 
       try {
       try {
@@ -821,28 +938,42 @@ module.exports = (crowi) => {
           User.findUserByUsername(username),
           User.findUserByUsername(username),
         ]);
         ]);
 
 
-        const userGroups = await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
-        const userGroupIds = userGroups.map(g => g._id);
+        const userGroups =
+          await UserGroup.findGroupsWithAncestorsRecursively(userGroup);
+        const userGroupIds = userGroups.map((g) => g._id);
 
 
         // remove existing relations from list to create
         // remove existing relations from list to create
-        const existingRelations = await UserGroupRelation.find({ relatedGroup: { $in: userGroupIds }, relatedUser: user._id });
-        const existingGroupIds = existingRelations.map(r => r.relatedGroup);
-        const groupIdsToCreateRelation = excludeTestIdsFromTargetIds(userGroupIds, existingGroupIds);
-
-        const insertedRelations = await UserGroupRelation.createRelations(groupIdsToCreateRelation, user);
+        const existingRelations = await UserGroupRelation.find({
+          relatedGroup: { $in: userGroupIds },
+          relatedUser: user._id,
+        });
+        const existingGroupIds = existingRelations.map((r) => r.relatedGroup);
+        const groupIdsToCreateRelation = excludeTestIdsFromTargetIds(
+          userGroupIds,
+          existingGroupIds,
+        );
+
+        const insertedRelations = await UserGroupRelation.createRelations(
+          groupIdsToCreateRelation,
+          user,
+        );
         const serializedUser = serializeUserSecurely(user);
         const serializedUser = serializeUserSecurely(user);
 
 
-        const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_ADD_USER };
+        const parameters = {
+          action: SupportedAction.ACTION_ADMIN_USER_GROUP_ADD_USER,
+        };
         activityEvent.emit('update', res.locals.activity._id, parameters);
         activityEvent.emit('update', res.locals.activity._id, parameters);
-        return res.apiv3({ user: serializedUser, createdRelationCount: insertedRelations.length });
-      }
-      catch (err) {
+        return res.apiv3({
+          user: serializedUser,
+          createdRelationCount: insertedRelations.length,
+        });
+      } catch (err) {
         const msg = `Error occurred in adding the user "${username}" to group "${id}"`;
         const msg = `Error occurred in adding the user "${username}" to group "${id}"`;
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-group-add-user-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-group-add-user-failed'));
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -884,25 +1015,35 @@ module.exports = (crowi) => {
    *                      type: number
    *                      type: number
    *                      description: the number of groups from which the user was removed
    *                      description: the number of groups from which the user was removed
    */
    */
-  router.delete('/:id/users/:username',
-    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    validator.users.delete, apiV3FormValidator,
-    async(req, res) => {
+  router.delete(
+    '/:id/users/:username',
+    accessTokenParser([SCOPE.WRITE.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.users.delete,
+    apiV3FormValidator,
+    async (req, res) => {
       const { id: userGroupId, username } = req.params;
       const { id: userGroupId, username } = req.params;
 
 
       try {
       try {
-        const removedUserRes = await crowi.userGroupService.removeUserByUsername(userGroupId, username);
+        const removedUserRes =
+          await crowi.userGroupService.removeUserByUsername(
+            userGroupId,
+            username,
+          );
         const serializedUser = serializeUserSecurely(removedUserRes.user);
         const serializedUser = serializeUserSecurely(removedUserRes.user);
 
 
-        return res.apiv3({ user: serializedUser, deletedGroupsCount: removedUserRes.deletedGroupsCount });
-      }
-      catch (err) {
+        return res.apiv3({
+          user: serializedUser,
+          deletedGroupsCount: removedUserRes.deletedGroupsCount,
+        });
+      } catch (err) {
         const msg = 'Error occurred while removing the user from groups.';
         const msg = 'Error occurred while removing the user from groups.';
         logger.error(msg, err);
         logger.error(msg, err);
         return res.apiv3Err(new ErrorV3(msg, 'user-group-remove-user-failed'));
         return res.apiv3Err(new ErrorV3(msg, 'user-group-remove-user-failed'));
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -935,24 +1076,31 @@ module.exports = (crowi) => {
    *                        type: object
    *                        type: object
    *                      description: userGroupRelation objects
    *                      description: userGroupRelation objects
    */
    */
-  router.get('/:id/user-group-relations',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    async(req, res) => {
+  router.get(
+    '/:id/user-group-relations',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
 
 
       try {
       try {
         const userGroup = await UserGroup.findById(id);
         const userGroup = await UserGroup.findById(id);
-        const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
-        const serialized = userGroupRelations.map(relation => serializeUserGroupRelationSecurely(relation));
+        const userGroupRelations =
+          await UserGroupRelation.findAllRelationForUserGroup(userGroup);
+        const serialized = userGroupRelations.map((relation) =>
+          serializeUserGroupRelationSecurely(relation),
+        );
         return res.apiv3({ userGroupRelations: serialized });
         return res.apiv3({ userGroupRelations: serialized });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = `Error occurred in fetching user group relations for group: ${id}`;
         const msg = `Error occurred in fetching user group relations for group: ${id}`;
         logger.error(msg, err);
         logger.error(msg, err);
-        return res.apiv3Err(new ErrorV3(msg, 'user-group-user-group-relation-list-fetch-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'user-group-user-group-relation-list-fetch-failed'),
+        );
       }
       }
-    });
-
+    },
+  );
 
 
   /**
   /**
    * @swagger
    * @swagger
@@ -985,26 +1133,33 @@ module.exports = (crowi) => {
    *                        type: object
    *                        type: object
    *                      description: page objects
    *                      description: page objects
    */
    */
-  router.get('/:id/pages',
-    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]), loginRequiredStrictly, adminRequired,
-    validator.pages.get, apiV3FormValidator,
-    async(req, res) => {
+  router.get(
+    '/:id/pages',
+    accessTokenParser([SCOPE.READ.ADMIN.USER_GROUP_MANAGEMENT]),
+    loginRequiredStrictly,
+    adminRequired,
+    validator.pages.get,
+    apiV3FormValidator,
+    async (req, res) => {
       const { id } = req.params;
       const { id } = req.params;
       const { limit, offset } = req.query;
       const { limit, offset } = req.query;
 
 
       try {
       try {
-        const { docs, totalDocs } = await Page.paginate({
-          grant: Page.GRANT_USER_GROUP,
-          grantedGroups: {
-            $elemMatch: {
-              item: id,
+        const { docs, totalDocs } = await Page.paginate(
+          {
+            grant: Page.GRANT_USER_GROUP,
+            grantedGroups: {
+              $elemMatch: {
+                item: id,
+              },
             },
             },
           },
           },
-        }, {
-          offset,
-          limit,
-          populate: 'lastUpdateUser',
-        });
+          {
+            offset,
+            limit,
+            populate: 'lastUpdateUser',
+          },
+        );
 
 
         const current = offset / limit + 1;
         const current = offset / limit + 1;
 
 
@@ -1015,13 +1170,15 @@ module.exports = (crowi) => {
 
 
         // TODO: create a common moudule for paginated response
         // TODO: create a common moudule for paginated response
         return res.apiv3({ total: totalDocs, current, pages });
         return res.apiv3({ total: totalDocs, current, pages });
-      }
-      catch (err) {
+      } catch (err) {
         const msg = `Error occurred in fetching pages for group: ${id}`;
         const msg = `Error occurred in fetching pages for group: ${id}`;
         logger.error(msg, err);
         logger.error(msg, err);
-        return res.apiv3Err(new ErrorV3(msg, 'user-group-page-list-fetch-failed'));
+        return res.apiv3Err(
+          new ErrorV3(msg, 'user-group-page-list-fetch-failed'),
+        );
       }
       }
-    });
+    },
+  );
 
 
   return router;
   return router;
 };
 };

Разница между файлами не показана из-за своего большого размера
+ 402 - 224
apps/app/src/server/routes/apiv3/users.js


+ 0 - 1
biome.json

@@ -30,7 +30,6 @@
       "!packages/pdf-converter-client/specs",
       "!packages/pdf-converter-client/specs",
       "!apps/app/src/client",
       "!apps/app/src/client",
       "!apps/app/src/server/middlewares",
       "!apps/app/src/server/middlewares",
-      "!apps/app/src/server/routes/apiv3/*.js",
       "!apps/app/src/server/service"
       "!apps/app/src/server/service"
     ]
     ]
   },
   },

Некоторые файлы не были показаны из-за большого количества измененных файлов