/* eslint-disable no-unused-vars */ import { SupportedAction } from '~/interfaces/activity'; import loggerFactory from '~/utils/logger'; import { generateAddActivityMiddleware } from '../../middlewares/add-activity'; import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator'; const logger = loggerFactory('growi:routes:apiv3:customize-setting'); const express = require('express'); const router = express.Router(); const { body, query } = require('express-validator'); const ErrorV3 = require('../../models/vo/error-apiv3'); /** * @swagger * tags: * name: CustomizeSetting */ /** * @swagger * * components: * schemas: * CustomizeLayout: * description: CustomizeLayout * type: object * properties: * isContainerFluid: * type: boolean * CustomizeTheme: * description: CustomizeTheme * type: object * properties: * themeType: * type: string * CustomizeFunction: * description: CustomizeFunction * type: object * properties: * isEnabledTimeline: * type: boolean * isSavedStatesOfTabChanges: * type: boolean * isEnabledAttachTitleHeader: * type: boolean * pageLimitationS: * type: number * pageLimitationM: * type: number * isEnabledStaleNotification: * type: boolean * isAllReplyShown: * type: boolean * isSearchScopeChildrenAsDefault: * type: boolean * CustomizeHighlight: * description: CustomizeHighlight * type: object * properties: * styleName: * type: string * styleBorder: * type: boolean * CustomizeTitle: * description: CustomizeTitle * type: object * properties: * customizeTitle: * type: string * CustomizeHeader: * description: CustomizeHeader * type: object * properties: * customizeHeader: * type: string * CustomizeCss: * description: CustomizeCss * type: object * properties: * customizeCss: * type: string * CustomizeScript: * description: CustomizeScript * type: object * properties: * customizeScript: * type: string */ module.exports = (crowi) => { const loginRequiredStrictly = require('../../middlewares/login-required')(crowi); const adminRequired = require('../../middlewares/admin-required')(crowi); const csrf = require('../../middlewares/csrf')(crowi); const addActivity = generateAddActivityMiddleware(crowi); const activityEvent = crowi.event('activity'); const { customizeService } = crowi; const validator = { layout: [ body('isContainerFluid').isBoolean(), ], themeAssetPath: [ query('themeName').isString(), ], theme: [ body('themeType').isString(), ], sidebar: [ body('isSidebarDrawerMode').isBoolean(), body('isSidebarClosedAtDockMode').isBoolean(), ], function: [ body('isEnabledTimeline').isBoolean(), body('isSavedStatesOfTabChanges').isBoolean(), body('isEnabledAttachTitleHeader').isBoolean(), body('pageLimitationS').isInt().isInt({ min: 1, max: 1000 }), body('pageLimitationM').isInt().isInt({ min: 1, max: 1000 }), body('pageLimitationL').isInt().isInt({ min: 1, max: 1000 }), body('pageLimitationXL').isInt().isInt({ min: 1, max: 1000 }), body('isEnabledStaleNotification').isBoolean(), body('isAllReplyShown').isBoolean(), body('isSearchScopeChildrenAsDefault').isBoolean(), ], customizeTitle: [ body('customizeTitle').isString(), ], customizeHeader: [ body('customizeHeader').isString(), ], highlight: [ body('highlightJsStyle').isString().isIn([ 'github', 'github-gist', 'atom-one-light', 'xcode', 'vs', 'atom-one-dark', 'hybrid', 'monokai', 'tomorrow-night', 'vs2015', ]), body('highlightJsStyleBorder').isBoolean(), ], customizeCss: [ body('customizeCss').isString(), ], customizeScript: [ body('customizeScript').isString(), ], }; /** * @swagger * * /customize-setting: * get: * tags: [CustomizeSetting] * operationId: getCustomizeSetting * summary: /customize-setting * description: Get customize parameters * responses: * 200: * description: params of customize * content: * application/json: * schema: * properties: * customizeParams: * type: object * description: customize params */ router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => { const customizeParams = { themeType: await crowi.configManager.getConfig('crowi', 'customize:theme'), isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'), isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'), isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'), pageLimitationS: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS'), pageLimitationM: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM'), pageLimitationL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationL'), pageLimitationXL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL'), isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'), isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'), isSearchScopeChildrenAsDefault: await crowi.configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault'), styleName: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'), styleBorder: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'), customizeTitle: await crowi.configManager.getConfig('crowi', 'customize:title'), customizeHeader: await crowi.configManager.getConfig('crowi', 'customize:header'), customizeCss: await crowi.configManager.getConfig('crowi', 'customize:css'), customizeScript: await crowi.configManager.getConfig('crowi', 'customize:script'), }; return res.apiv3({ customizeParams }); }); /** * @swagger * * /customize-setting/layout: * get: * tags: [CustomizeSetting] * operationId: getLayoutCustomizeSetting * summary: /customize-setting/layout * description: Get layout * responses: * 200: * description: Succeeded to get layout * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeLayout' */ router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => { try { const isContainerFluid = await crowi.configManager.getConfig('crowi', '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 * * /customize-setting/layout: * put: * tags: [CustomizeSetting] * operationId: updateLayoutCustomizeSetting * summary: /customize-setting/layout * description: Update layout * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeLayout' * responses: * 200: * description: Succeeded to update layout * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeLayout' */ router.put('/layout', loginRequiredStrictly, adminRequired, csrf, addActivity, validator.layout, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:isContainerFluid': req.body.isContainerFluid, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams); const customizedParams = { isContainerFluid: await crowi.configManager.getConfig('crowi', 'customize:isContainerFluid'), }; const parameters = { action: SupportedAction.ACTION_ADMIN_LAYOUT_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating layout'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-layout-failed')); } }); /** * @swagger * * /customize-setting/theme/asset-path: * put: * tags: [CustomizeSetting] * operationId: getThemeAssetPath * summary: /customize-setting/theme/asset-path * description: Get theme asset path * parameters: * - name: themeName * in: query * required: true * schema: * type: string * responses: * 200: * description: Succeeded to get theme asset path * content: * application/json: * schema: * properties: * assetPath: * type: string */ router.get('/theme/asset-path', loginRequiredStrictly, adminRequired, validator.themeAssetPath, apiV3FormValidator, async(req, res) => { const { themeName } = req.query; const webpackAssetKey = `styles/theme-${themeName}.css`; const assetPath = res.locals.webpack_asset(webpackAssetKey); if (assetPath == null) { return res.apiv3Err(new ErrorV3(`The asset for '${webpackAssetKey}' is undefined.`, 'invalid-asset')); } return res.apiv3({ assetPath }); }); /** * @swagger * * /customize-setting/theme: * put: * tags: [CustomizeSetting] * operationId: updateThemeCustomizeSetting * summary: /customize-setting/theme * description: Update theme * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeTheme' * responses: * 200: * description: Succeeded to update theme * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeTheme' */ router.put('/theme', loginRequiredStrictly, adminRequired, csrf, validator.theme, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:theme': req.body.themeType, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams); const customizedParams = { themeType: await crowi.configManager.getConfig('crowi', 'customize:theme'), }; return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating theme'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-theme-failed')); } }); // sidebar router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => { try { const isSidebarDrawerMode = await crowi.configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'); const isSidebarClosedAtDockMode = await crowi.configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'); return res.apiv3({ isSidebarDrawerMode, isSidebarClosedAtDockMode }); } catch (err) { const msg = 'Error occurred in getting sidebar'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'get-sidebar-failed')); } }); router.put('/sidebar', loginRequiredStrictly, adminRequired, csrf, validator.sidebar, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:isSidebarDrawerMode': req.body.isSidebarDrawerMode, 'customize:isSidebarClosedAtDockMode': req.body.isSidebarClosedAtDockMode, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams); const customizedParams = { isSidebarDrawerMode: await crowi.configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'), isSidebarClosedAtDockMode: await crowi.configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'), }; return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating sidebar'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-sidebar-failed')); } }); /** * @swagger * * /customize-setting/function: * put: * tags: [CustomizeSetting] * operationId: updateFunctionCustomizeSetting * summary: /customize-setting/function * description: Update function * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeFunction' * responses: * 200: * description: Succeeded to update function * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeFunction' */ router.put('/function', loginRequiredStrictly, adminRequired, csrf, validator.function, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:isEnabledTimeline': req.body.isEnabledTimeline, 'customize:isSavedStatesOfTabChanges': req.body.isSavedStatesOfTabChanges, 'customize:isEnabledAttachTitleHeader': req.body.isEnabledAttachTitleHeader, 'customize:showPageLimitationS': req.body.pageLimitationS, 'customize:showPageLimitationM': req.body.pageLimitationM, 'customize:showPageLimitationL': req.body.pageLimitationL, 'customize:showPageLimitationXL': req.body.pageLimitationXL, 'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification, 'customize:isAllReplyShown': req.body.isAllReplyShown, 'customize:isSearchScopeChildrenAsDefault': req.body.isSearchScopeChildrenAsDefault, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams); const customizedParams = { isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'), isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'), isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'), pageLimitationS: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS'), pageLimitationM: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM'), pageLimitationL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationL'), pageLimitationXL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL'), isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'), isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'), isSearchScopeChildrenAsDefault: await crowi.configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault'), }; return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating function'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-function-failed')); } }); /** * @swagger * * /customize-setting/highlight: * put: * tags: [CustomizeSetting] * operationId: updateHighlightCustomizeSetting * summary: /customize-setting/highlight * description: Update highlight * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeHighlight' * responses: * 200: * description: Succeeded to update highlight * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeHighlight' */ router.put('/highlight', loginRequiredStrictly, adminRequired, csrf, validator.highlight, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:highlightJsStyle': req.body.highlightJsStyle, 'customize:highlightJsStyleBorder': req.body.highlightJsStyleBorder, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams); const customizedParams = { styleName: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'), styleBorder: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'), }; return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating highlight'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-highlight-failed')); } }); /** * @swagger * * /customize-setting/customizeTitle: * put: * tags: [CustomizeSetting] * operationId: updateCustomizeTitleCustomizeSetting * summary: /customize-setting/customizeTitle * description: Update customizeTitle * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeTitle' * responses: * 200: * description: Succeeded to update customizeTitle * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeTitle' */ router.put('/customize-title', loginRequiredStrictly, adminRequired, csrf, validator.customizeTitle, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:title': req.body.customizeTitle, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true); crowi.customizeService.publishUpdatedMessage(); const customizedParams = { customizeTitle: await crowi.configManager.getConfig('crowi', 'customize:title'), }; customizeService.initCustomTitle(); return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating customizeTitle'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-customizeTitle-failed')); } }); /** * @swagger * * /customize-setting/customizeHeader: * put: * tags: [CustomizeSetting] * operationId: updateCustomizeHeaderCustomizeSetting * summary: /customize-setting/customizeHeader * description: Update customizeHeader * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeHeader' * responses: * 200: * description: Succeeded to update customize header * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeHeader' */ router.put('/customize-header', loginRequiredStrictly, adminRequired, csrf, validator.customizeHeader, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:header': req.body.customizeHeader, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams); const customizedParams = { customizeHeader: await crowi.configManager.getConfig('crowi', 'customize:header'), }; return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating customizeHeader'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-customizeHeader-failed')); } }); /** * @swagger * * /customize-setting/customizeCss: * put: * tags: [CustomizeSetting] * operationId: updateCustomizeCssCustomizeSetting * summary: /customize-setting/customizeCss * description: Update customizeCss * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeCss' * responses: * 200: * description: Succeeded to update customize css * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeCss' */ router.put('/customize-css', loginRequiredStrictly, adminRequired, csrf, validator.customizeCss, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:css': req.body.customizeCss, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true); crowi.customizeService.publishUpdatedMessage(); const customizedParams = { customizeCss: await crowi.configManager.getConfig('crowi', 'customize:css'), }; customizeService.initCustomCss(); return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating customizeCss'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-customizeCss-failed')); } }); /** * @swagger * * /customize-setting/customizeScript: * put: * tags: [CustomizeSetting] * operationId: updateCustomizeScriptCustomizeSetting * summary: /customize-setting/customizeScript * description: Update customizeScript * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeScript' * responses: * 200: * description: Succeeded to update customize script * content: * application/json: * schema: * $ref: '#/components/schemas/CustomizeScript' */ router.put('/customize-script', loginRequiredStrictly, adminRequired, csrf, validator.customizeScript, apiV3FormValidator, async(req, res) => { const requestParams = { 'customize:script': req.body.customizeScript, }; try { await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams); const customizedParams = { customizeScript: await crowi.configManager.getConfig('crowi', 'customize:script'), }; return res.apiv3({ customizedParams }); } catch (err) { const msg = 'Error occurred in updating customizeScript'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-customizeScript-failed')); } }); return router; };