customize-setting.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083
  1. /* eslint-disable no-unused-vars */
  2. import { GrowiPluginType } from '@growi/core';
  3. import { ErrorV3 } from '@growi/core/dist/models';
  4. import express from 'express';
  5. import { body } from 'express-validator';
  6. import multer from 'multer';
  7. import { GrowiPlugin } from '~/features/growi-plugin/server/models';
  8. import { SupportedAction } from '~/interfaces/activity';
  9. import { AttachmentType } from '~/server/interfaces/attachment';
  10. import { Attachment } from '~/server/models/attachment';
  11. import { configManager } from '~/server/service/config-manager';
  12. import loggerFactory from '~/utils/logger';
  13. import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
  14. import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
  15. const logger = loggerFactory('growi:routes:apiv3:customize-setting');
  16. const router = express.Router();
  17. /**
  18. * @swagger
  19. *
  20. * components:
  21. * schemas:
  22. * CustomizeLayout:
  23. * description: CustomizeLayout
  24. * type: object
  25. * properties:
  26. * isContainerFluid:
  27. * type: boolean
  28. * CustomizeTheme:
  29. * description: CustomizeTheme
  30. * type: object
  31. * properties:
  32. * theme:
  33. * type: string
  34. * CustomizeFunction:
  35. * description: CustomizeFunction
  36. * type: object
  37. * properties:
  38. * isEnabledTimeline:
  39. * type: boolean
  40. * isEnabledAttachTitleHeader:
  41. * type: boolean
  42. * pageLimitationS:
  43. * type: number
  44. * pageLimitationM:
  45. * type: number
  46. * isEnabledStaleNotification:
  47. * type: boolean
  48. * isAllReplyShown:
  49. * type: boolean
  50. * isSearchScopeChildrenAsDefault:
  51. * type: boolean
  52. * CustomizeHighlight:
  53. * description: CustomizeHighlight
  54. * type: object
  55. * properties:
  56. * highlightJsStyle:
  57. * type: string
  58. * highlightJsStyleBorder:
  59. * type: boolean
  60. * CustomizeHighlightResponse:
  61. * description: CustomizeHighlight Response
  62. * type: object
  63. * properties:
  64. * styleName:
  65. * type: string
  66. * styleBorder:
  67. * type: boolean
  68. * CustomizeTitle:
  69. * description: CustomizeTitle
  70. * type: object
  71. * properties:
  72. * customizeTitle:
  73. * type: string
  74. * CustomizeNoscript:
  75. * description: CustomizeNoscript
  76. * type: object
  77. * properties:
  78. * customizeNoscript:
  79. * type: string
  80. * CustomizeCss:
  81. * description: CustomizeCss
  82. * type: object
  83. * properties:
  84. * customizeCss:
  85. * type: string
  86. * CustomizeScript:
  87. * description: CustomizeScript
  88. * type: object
  89. * properties:
  90. * customizeScript:
  91. * type: string
  92. * CustomizeSetting:
  93. * description: Customize Setting
  94. * type: object
  95. * properties:
  96. * isEnabledTimeline:
  97. * type: boolean
  98. * isEnabledAttachTitleHeader:
  99. * type: boolean
  100. * pageLimitationS:
  101. * type: number
  102. * pageLimitationM:
  103. * type: number
  104. * pageLimitationL:
  105. * type: number
  106. * pageLimitationXL:
  107. * type: number
  108. * isEnabledStaleNotification:
  109. * type: boolean
  110. * isAllReplyShown:
  111. * type: boolean
  112. * isSearchScopeChildrenAsDefault:
  113. * type: boolean
  114. * isEnabledMarp:
  115. * type: boolean
  116. * styleName:
  117. * type: string
  118. * styleBorder:
  119. * type: string
  120. * customizeTitle:
  121. * type: string
  122. * customizeScript:
  123. * type: string
  124. * customizeCss:
  125. * type: string
  126. * customizeNoscript:
  127. * type: string
  128. * ThemesMetadata:
  129. * type: object
  130. * properties:
  131. * name:
  132. * type: string
  133. * description: The name of the plugin theme.
  134. * manifestKey:
  135. * type: string
  136. * description: Path to the theme manifest file.
  137. * schemeType:
  138. * type: string
  139. * description: The color scheme type (e.g., light or dark).
  140. * lightBg:
  141. * type: string
  142. * description: Light mode background color (hex).
  143. * darkBg:
  144. * type: string
  145. * description: Dark mode background color (hex).
  146. * lightSidebar:
  147. * type: string
  148. * description: Light mode sidebar color (hex).
  149. * darkSidebar:
  150. * type: string
  151. * description: Dark mode sidebar color (hex).
  152. * lightIcon:
  153. * type: string
  154. * description: Light mode icon color (hex).
  155. * darkIcon:
  156. * type: string
  157. * description: Dark mode icon color (hex).
  158. * createBtn:
  159. * type: string
  160. * description: Color of the create button (hex).
  161. * CustomizeSidebar:
  162. * description: Customize Sidebar
  163. * type: object
  164. * properties:
  165. * isSidebarCollapsedMode:
  166. * type: boolean
  167. * description: The flag whether sidebar is collapsed mode or not.
  168. * isSidebarClosedAtDockMode:
  169. * type: boolean
  170. * description: The flag whether sidebar is closed at dock mode or not.
  171. * CustomizePresentation:
  172. * description: Customize Presentation
  173. * type: object
  174. * properties:
  175. * isEnabledMarp:
  176. * type: boolean
  177. * description: The flag whether Marp is enabled or not.
  178. * CustomizeLogo:
  179. * description: Customize Logo
  180. * type: object
  181. * properties:
  182. * isDefaultLogo:
  183. * type: boolean
  184. * description: The flag whether the logo is default or not.
  185. */
  186. /** @param {import('~/server/crowi').default} crowi Crowi instance */
  187. module.exports = (crowi) => {
  188. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  189. const adminRequired = require('../../middlewares/admin-required')(crowi);
  190. const addActivity = generateAddActivityMiddleware(crowi);
  191. const activityEvent = crowi.event('activity');
  192. const { customizeService, attachmentService } = crowi;
  193. const uploads = multer({ dest: `${crowi.tmpDir}uploads` });
  194. const validator = {
  195. layout: [
  196. body('isContainerFluid').isBoolean(),
  197. ],
  198. theme: [
  199. body('theme').isString(),
  200. ],
  201. sidebar: [
  202. body('isSidebarCollapsedMode').isBoolean(),
  203. body('isSidebarClosedAtDockMode').optional().isBoolean(),
  204. ],
  205. function: [
  206. body('isEnabledTimeline').isBoolean(),
  207. body('isEnabledAttachTitleHeader').isBoolean(),
  208. body('pageLimitationS').isInt().isInt({ min: 1, max: 1000 }),
  209. body('pageLimitationM').isInt().isInt({ min: 1, max: 1000 }),
  210. body('pageLimitationL').isInt().isInt({ min: 1, max: 1000 }),
  211. body('pageLimitationXL').isInt().isInt({ min: 1, max: 1000 }),
  212. body('isEnabledStaleNotification').isBoolean(),
  213. body('isAllReplyShown').isBoolean(),
  214. body('isSearchScopeChildrenAsDefault').isBoolean(),
  215. body('showPageSideAuthors').isBoolean(),
  216. ],
  217. CustomizePresentation: [
  218. body('isEnabledMarp').isBoolean(),
  219. ],
  220. customizeTitle: [
  221. body('customizeTitle').isString(),
  222. ],
  223. highlight: [
  224. body('highlightJsStyle').isString().isIn([
  225. 'github', 'github-gist', 'atom-one-light', 'xcode', 'vs', 'atom-one-dark', 'hybrid', 'monokai', 'tomorrow-night', 'vs2015',
  226. ]),
  227. body('highlightJsStyleBorder').isBoolean(),
  228. ],
  229. customizeScript: [
  230. body('customizeScript').isString(),
  231. ],
  232. customizeCss: [
  233. body('customizeCss').isString(),
  234. ],
  235. customizeNoscript: [
  236. body('customizeNoscript').isString(),
  237. ],
  238. logo: [
  239. body('isDefaultLogo').isBoolean().optional({ nullable: true }),
  240. body('customizedLogoSrc').isString().optional({ nullable: true }),
  241. ],
  242. };
  243. /**
  244. * @swagger
  245. *
  246. * /customize-setting:
  247. * get:
  248. * tags: [CustomizeSetting]
  249. * security:
  250. * - cookieAuth: []
  251. * summary: /customize-setting
  252. * description: Get customize parameters
  253. * responses:
  254. * 200:
  255. * description: params of customize
  256. * content:
  257. * application/json:
  258. * schema:
  259. * properties:
  260. * customizeParams:
  261. * type: object
  262. * description: customize params
  263. * $ref: '#/components/schemas/CustomizeSetting'
  264. */
  265. router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
  266. const customizeParams = {
  267. isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
  268. isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
  269. pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
  270. pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
  271. pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
  272. pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
  273. isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
  274. isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
  275. showPageSideAuthors: await configManager.getConfig('customize:showPageSideAuthors'),
  276. isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
  277. isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
  278. styleName: await configManager.getConfig('customize:highlightJsStyle'),
  279. styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
  280. customizeTitle: await configManager.getConfig('customize:title'),
  281. customizeScript: await configManager.getConfig('customize:script'),
  282. customizeCss: await configManager.getConfig('customize:css'),
  283. customizeNoscript: await configManager.getConfig('customize:noscript'),
  284. };
  285. return res.apiv3({ customizeParams });
  286. });
  287. /**
  288. * @swagger
  289. *
  290. * /customize-setting/layout:
  291. * get:
  292. * tags: [CustomizeSetting]
  293. * security:
  294. * - cookieAuth: []
  295. * summary: /customize-setting/layout
  296. * description: Get layout
  297. * responses:
  298. * 200:
  299. * description: Succeeded to get layout
  300. * content:
  301. * application/json:
  302. * schema:
  303. * $ref: '#/components/schemas/CustomizeLayout'
  304. */
  305. router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => {
  306. try {
  307. const isContainerFluid = await configManager.getConfig('customize:isContainerFluid');
  308. return res.apiv3({ isContainerFluid });
  309. }
  310. catch (err) {
  311. const msg = 'Error occurred in getting layout';
  312. logger.error('Error', err);
  313. return res.apiv3Err(new ErrorV3(msg, 'get-layout-failed'));
  314. }
  315. });
  316. /**
  317. * @swagger
  318. *
  319. * /customize-setting/layout:
  320. * put:
  321. * tags: [CustomizeSetting]
  322. * summary: /customize-setting/layout
  323. * description: Update layout
  324. * requestBody:
  325. * required: true
  326. * content:
  327. * application/json:
  328. * schema:
  329. * $ref: '#/components/schemas/CustomizeLayout'
  330. * responses:
  331. * 200:
  332. * description: Succeeded to update layout
  333. * content:
  334. * application/json:
  335. * schema:
  336. * type: object
  337. * properties:
  338. * customizedParams:
  339. * type: object
  340. * description: customized params
  341. * $ref: '#/components/schemas/CustomizeLayout'
  342. */
  343. router.put('/layout', loginRequiredStrictly, adminRequired, addActivity, validator.layout, apiV3FormValidator, async(req, res) => {
  344. const requestParams = {
  345. 'customize:isContainerFluid': req.body.isContainerFluid,
  346. };
  347. try {
  348. await configManager.updateConfigs(requestParams);
  349. const customizedParams = {
  350. isContainerFluid: await configManager.getConfig('customize:isContainerFluid'),
  351. };
  352. const parameters = { action: SupportedAction.ACTION_ADMIN_LAYOUT_UPDATE };
  353. activityEvent.emit('update', res.locals.activity._id, parameters);
  354. return res.apiv3({ customizedParams });
  355. }
  356. catch (err) {
  357. const msg = 'Error occurred in updating layout';
  358. logger.error('Error', err);
  359. return res.apiv3Err(new ErrorV3(msg, 'update-layout-failed'));
  360. }
  361. });
  362. /**
  363. * @swagger
  364. *
  365. * /customize-setting/theme:
  366. * get:
  367. * tags: [CustomizeSetting]
  368. * security:
  369. * - cookieAuth: []
  370. * summary: /customize-setting/theme
  371. * description: Get theme
  372. * responses:
  373. * 200:
  374. * description: Succeeded to get layout
  375. * content:
  376. * application/json:
  377. * schema:
  378. * type: object
  379. * properties:
  380. * currentTheme:
  381. * type: string
  382. * description: The current theme name.
  383. * pluginThemesMetadatas:
  384. * type: array
  385. * description: Metadata for available plugin themes.
  386. * items:
  387. * $ref: '#/components/schemas/ThemesMetadata'
  388. */
  389. router.get('/theme', loginRequiredStrictly, async(req, res) => {
  390. try {
  391. const currentTheme = await configManager.getConfig('customize:theme');
  392. // retrieve plugin manifests
  393. const themePlugins = await GrowiPlugin.findEnabledPluginsByType(GrowiPluginType.Theme);
  394. const pluginThemesMetadatas = themePlugins
  395. .map(themePlugin => themePlugin.meta.themes)
  396. .flat();
  397. return res.apiv3({ currentTheme, pluginThemesMetadatas });
  398. }
  399. catch (err) {
  400. const msg = 'Error occurred in getting theme';
  401. logger.error('Error', err);
  402. return res.apiv3Err(new ErrorV3(msg, 'get-theme-failed'));
  403. }
  404. });
  405. /**
  406. * @swagger
  407. *
  408. * /customize-setting/theme:
  409. * put:
  410. * tags: [CustomizeSetting]
  411. * security:
  412. * - cookieAuth: []
  413. * summary: /customize-setting/theme
  414. * description: Update theme
  415. * requestBody:
  416. * required: true
  417. * content:
  418. * application/json:
  419. * schema:
  420. * $ref: '#/components/schemas/CustomizeTheme'
  421. * responses:
  422. * 200:
  423. * description: Succeeded to update theme
  424. * content:
  425. * application/json:
  426. * schema:
  427. * type: object
  428. * properties:
  429. * customizedParams:
  430. * $ref: '#/components/schemas/CustomizeTheme'
  431. */
  432. router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
  433. const requestParams = {
  434. 'customize:theme': req.body.theme,
  435. };
  436. try {
  437. await configManager.updateConfigs(requestParams);
  438. const customizedParams = {
  439. theme: await configManager.getConfig('customize:theme'),
  440. };
  441. customizeService.initGrowiTheme();
  442. const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
  443. activityEvent.emit('update', res.locals.activity._id, parameters);
  444. return res.apiv3({ customizedParams });
  445. }
  446. catch (err) {
  447. const msg = 'Error occurred in updating theme';
  448. logger.error('Error', err);
  449. return res.apiv3Err(new ErrorV3(msg, 'update-theme-failed'));
  450. }
  451. });
  452. /**
  453. * @swagger
  454. *
  455. * /customize-setting/sidebar:
  456. * get:
  457. * tags: [CustomizeSetting]
  458. * security:
  459. * - cookieAuth: []
  460. * summary: /customize-setting/sidebar
  461. * description: Get sidebar
  462. * responses:
  463. * 200:
  464. * description: Succeeded to get sidebar
  465. * content:
  466. * application/json:
  467. * schema:
  468. * $ref: '#/components/schemas/CustomizeSidebar'
  469. */
  470. router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
  471. try {
  472. const isSidebarCollapsedMode = await configManager.getConfig('customize:isSidebarCollapsedMode');
  473. const isSidebarClosedAtDockMode = await configManager.getConfig('customize:isSidebarClosedAtDockMode');
  474. return res.apiv3({ isSidebarCollapsedMode, isSidebarClosedAtDockMode });
  475. }
  476. catch (err) {
  477. const msg = 'Error occurred in getting sidebar';
  478. logger.error('Error', err);
  479. return res.apiv3Err(new ErrorV3(msg, 'get-sidebar-failed'));
  480. }
  481. });
  482. /**
  483. * @swagger
  484. *
  485. * /customize-setting/sidebar:
  486. * put:
  487. * tags: [CustomizeSetting]
  488. * security:
  489. * - cookieAuth: []
  490. * summary: /customize-setting/sidebar
  491. * description: Update sidebar
  492. * requestBody:
  493. * required: true
  494. * content:
  495. * application/json:
  496. * schema:
  497. * $ref: '#/components/schemas/CustomizeSidebar'
  498. * responses:
  499. * 200:
  500. * description: Succeeded to update sidebar
  501. * content:
  502. * application/json:
  503. * schema:
  504. * type: object
  505. * properties:
  506. * customizedParams:
  507. * $ref: '#/components/schemas/CustomizeSidebar'
  508. */
  509. router.put('/sidebar', loginRequiredStrictly, adminRequired, validator.sidebar, apiV3FormValidator, addActivity, async(req, res) => {
  510. const requestParams = {
  511. 'customize:isSidebarCollapsedMode': req.body.isSidebarCollapsedMode,
  512. 'customize:isSidebarClosedAtDockMode': req.body.isSidebarClosedAtDockMode,
  513. };
  514. try {
  515. await configManager.updateConfigs(requestParams);
  516. const customizedParams = {
  517. isSidebarCollapsedMode: await configManager.getConfig('customize:isSidebarCollapsedMode'),
  518. isSidebarClosedAtDockMode: await configManager.getConfig('customize:isSidebarClosedAtDockMode'),
  519. };
  520. activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SIDEBAR_UPDATE });
  521. return res.apiv3({ customizedParams });
  522. }
  523. catch (err) {
  524. const msg = 'Error occurred in updating sidebar';
  525. logger.error('Error', err);
  526. return res.apiv3Err(new ErrorV3(msg, 'update-sidebar-failed'));
  527. }
  528. });
  529. /**
  530. * @swagger
  531. *
  532. * /customize-setting/function:
  533. * put:
  534. * tags: [CustomizeSetting]
  535. * security:
  536. * - cookieAuth: []
  537. * summary: /customize-setting/function
  538. * description: Update function
  539. * requestBody:
  540. * required: true
  541. * content:
  542. * application/json:
  543. * schema:
  544. * $ref: '#/components/schemas/CustomizeFunction'
  545. * responses:
  546. * 200:
  547. * description: Succeeded to update function
  548. * content:
  549. * application/json:
  550. * schema:
  551. * type: object
  552. * properties:
  553. * customizedParams:
  554. * $ref: '#/components/schemas/CustomizeFunction'
  555. */
  556. router.put('/function', loginRequiredStrictly, adminRequired, addActivity, validator.function, apiV3FormValidator, async(req, res) => {
  557. const requestParams = {
  558. 'customize:isEnabledTimeline': req.body.isEnabledTimeline,
  559. 'customize:isEnabledAttachTitleHeader': req.body.isEnabledAttachTitleHeader,
  560. 'customize:showPageLimitationS': req.body.pageLimitationS,
  561. 'customize:showPageLimitationM': req.body.pageLimitationM,
  562. 'customize:showPageLimitationL': req.body.pageLimitationL,
  563. 'customize:showPageLimitationXL': req.body.pageLimitationXL,
  564. 'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification,
  565. 'customize:isAllReplyShown': req.body.isAllReplyShown,
  566. 'customize:isSearchScopeChildrenAsDefault': req.body.isSearchScopeChildrenAsDefault,
  567. 'customize:showPageSideAuthors': req.body.showPageSideAuthors,
  568. };
  569. try {
  570. await configManager.updateConfigs(requestParams);
  571. const customizedParams = {
  572. isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
  573. isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
  574. pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
  575. pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
  576. pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
  577. pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
  578. isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
  579. isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
  580. isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
  581. showPageSideAuthors: await configManager.getConfig('customize:showPageSideAuthors'),
  582. };
  583. const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
  584. activityEvent.emit('update', res.locals.activity._id, parameters);
  585. return res.apiv3({ customizedParams });
  586. }
  587. catch (err) {
  588. const msg = 'Error occurred in updating function';
  589. logger.error('Error', err);
  590. return res.apiv3Err(new ErrorV3(msg, 'update-function-failed'));
  591. }
  592. });
  593. /**
  594. * @swagger
  595. *
  596. * /customize-setting/presentation:
  597. * put:
  598. * tags: [CustomizeSetting]
  599. * security:
  600. * - cookieAuth: []
  601. * summary: /customize-setting/presentation
  602. * description: Update presentation
  603. * requestBody:
  604. * required: true
  605. * content:
  606. * application/json:
  607. * schema:
  608. * $ref: '#/components/schemas/CustomizePresentation'
  609. * responses:
  610. * 200:
  611. * description: Succeeded to update presentation
  612. * content:
  613. * application/json:
  614. * schema:
  615. * type: object
  616. * properties:
  617. * customizedParams:
  618. * $ref: '#/components/schemas/CustomizePresentation'
  619. */
  620. router.put('/presentation', loginRequiredStrictly, adminRequired, addActivity, validator.CustomizePresentation, apiV3FormValidator, async(req, res) => {
  621. const requestParams = {
  622. 'customize:isEnabledMarp': req.body.isEnabledMarp,
  623. };
  624. try {
  625. await configManager.updateConfigs(requestParams);
  626. const customizedParams = {
  627. isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
  628. };
  629. const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
  630. activityEvent.emit('update', res.locals.activity._id, parameters);
  631. return res.apiv3({ customizedParams });
  632. }
  633. catch (err) {
  634. const msg = 'Error occurred in updating presentaion';
  635. logger.error('Error', err);
  636. return res.apiv3Err(new ErrorV3(msg, 'update-presentation-failed'));
  637. }
  638. });
  639. /**
  640. * @swagger
  641. *
  642. * /customize-setting/highlight:
  643. * put:
  644. * tags: [CustomizeSetting]
  645. * security:
  646. * - cookieAuth: []
  647. * summary: /customize-setting/highlight
  648. * description: Update highlight
  649. * requestBody:
  650. * required: true
  651. * content:
  652. * application/json:
  653. * schema:
  654. * $ref: '#/components/schemas/CustomizeHighlight'
  655. * responses:
  656. * 200:
  657. * description: Succeeded to update highlight
  658. * content:
  659. * application/json:
  660. * schema:
  661. * type: object
  662. * properties:
  663. * customizedParams:
  664. * $ref: '#/components/schemas/CustomizeHighlightResponse'
  665. */
  666. router.put('/highlight', loginRequiredStrictly, adminRequired, addActivity, validator.highlight, apiV3FormValidator, async(req, res) => {
  667. const requestParams = {
  668. 'customize:highlightJsStyle': req.body.highlightJsStyle,
  669. 'customize:highlightJsStyleBorder': req.body.highlightJsStyleBorder,
  670. };
  671. try {
  672. await configManager.updateConfigs(requestParams);
  673. const customizedParams = {
  674. styleName: await configManager.getConfig('customize:highlightJsStyle'),
  675. styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
  676. };
  677. const parameters = { action: SupportedAction.ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE };
  678. activityEvent.emit('update', res.locals.activity._id, parameters);
  679. return res.apiv3({ customizedParams });
  680. }
  681. catch (err) {
  682. const msg = 'Error occurred in updating highlight';
  683. logger.error('Error', err);
  684. return res.apiv3Err(new ErrorV3(msg, 'update-highlight-failed'));
  685. }
  686. });
  687. /**
  688. * @swagger
  689. *
  690. * /customize-setting/customizeTitle:
  691. * put:
  692. * tags: [CustomizeSetting]
  693. * security:
  694. * - cookieAuth: []
  695. * summary: /customize-setting/customizeTitle
  696. * description: Update title
  697. * requestBody:
  698. * required: true
  699. * content:
  700. * application/json:
  701. * schema:
  702. * $ref: '#/components/schemas/CustomizeTitle'
  703. * responses:
  704. * 200:
  705. * description: Succeeded to update customizeTitle
  706. * content:
  707. * application/json:
  708. * schema:
  709. * type: object
  710. * properties:
  711. * customizedParams:
  712. * $ref: '#/components/schemas/CustomizeTitle'
  713. */
  714. router.put('/customize-title', loginRequiredStrictly, adminRequired, addActivity, validator.customizeTitle, apiV3FormValidator, async(req, res) => {
  715. const requestParams = {
  716. 'customize:title': req.body.customizeTitle,
  717. };
  718. try {
  719. await configManager.updateConfigs(requestParams, { skipPubsub: true });
  720. crowi.customizeService.publishUpdatedMessage();
  721. const customizedParams = {
  722. customizeTitle: await configManager.getConfig('customize:title'),
  723. };
  724. customizeService.initCustomTitle();
  725. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_TITLE_UPDATE };
  726. activityEvent.emit('update', res.locals.activity._id, parameters);
  727. return res.apiv3({ customizedParams });
  728. }
  729. catch (err) {
  730. const msg = 'Error occurred in updating customizeTitle';
  731. logger.error('Error', err);
  732. return res.apiv3Err(new ErrorV3(msg, 'update-customizeTitle-failed'));
  733. }
  734. });
  735. /**
  736. * @swagger
  737. *
  738. * /customize-setting/customize-noscript:
  739. * put:
  740. * tags: [CustomizeSetting]
  741. * security:
  742. * - cookieAuth: []
  743. * summary: /customize-setting/customize-noscript
  744. * description: Update noscript
  745. * requestBody:
  746. * required: true
  747. * content:
  748. * application/json:
  749. * schema:
  750. * $ref: '#/components/schemas/CustomizeNoscript'
  751. * responses:
  752. * 200:
  753. * description: Succeeded to update customize header
  754. * content:
  755. * application/json:
  756. * schema:
  757. * type: object
  758. * properties:
  759. * customizedParams:
  760. * $ref: '#/components/schemas/CustomizeNoscript'
  761. */
  762. router.put('/customize-noscript', loginRequiredStrictly, adminRequired, addActivity, validator.customizeNoscript, apiV3FormValidator, async(req, res) => {
  763. const requestParams = {
  764. 'customize:noscript': req.body.customizeNoscript,
  765. };
  766. try {
  767. await configManager.updateConfigs(requestParams);
  768. const customizedParams = {
  769. customizeNoscript: await configManager.getConfig('customize:noscript'),
  770. };
  771. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_NOSCRIPT_UPDATE };
  772. activityEvent.emit('update', res.locals.activity._id, parameters);
  773. return res.apiv3({ customizedParams });
  774. }
  775. catch (err) {
  776. const msg = 'Error occurred in updating customizeNoscript';
  777. logger.error('Error', err);
  778. return res.apiv3Err(new ErrorV3(msg, 'update-customizeNoscript-failed'));
  779. }
  780. });
  781. /**
  782. * @swagger
  783. *
  784. * /customize-setting/customize-css:
  785. * put:
  786. * tags: [CustomizeSetting]
  787. * security:
  788. * - cookieAuth: []
  789. * summary: /customize-setting/customize-css
  790. * description: Update customize css
  791. * requestBody:
  792. * required: true
  793. * content:
  794. * application/json:
  795. * schema:
  796. * $ref: '#/components/schemas/CustomizeCss'
  797. * responses:
  798. * 200:
  799. * description: Succeeded to update customize css
  800. * content:
  801. * application/json:
  802. * schema:
  803. * type: object
  804. * properties:
  805. * customizedParams:
  806. * $ref: '#/components/schemas/CustomizeCss'
  807. */
  808. router.put('/customize-css', loginRequiredStrictly, adminRequired, addActivity, validator.customizeCss, apiV3FormValidator, async(req, res) => {
  809. const requestParams = {
  810. 'customize:css': req.body.customizeCss,
  811. };
  812. try {
  813. await configManager.updateConfigs(requestParams, { skipPubsub: true });
  814. crowi.customizeService.publishUpdatedMessage();
  815. const customizedParams = {
  816. customizeCss: await configManager.getConfig('customize:css'),
  817. };
  818. customizeService.initCustomCss();
  819. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_CSS_UPDATE };
  820. activityEvent.emit('update', res.locals.activity._id, parameters);
  821. return res.apiv3({ customizedParams });
  822. }
  823. catch (err) {
  824. const msg = 'Error occurred in updating customizeCss';
  825. logger.error('Error', err);
  826. return res.apiv3Err(new ErrorV3(msg, 'update-customizeCss-failed'));
  827. }
  828. });
  829. /**
  830. * @swagger
  831. *
  832. * /customize-setting/customize-script:
  833. * put:
  834. * tags: [CustomizeSetting]
  835. * security:
  836. * - cookieAuth: []
  837. * summary: /customize-setting/customize-script
  838. * description: Update customize script
  839. * requestBody:
  840. * required: true
  841. * content:
  842. * application/json:
  843. * schema:
  844. * $ref: '#/components/schemas/CustomizeScript'
  845. * responses:
  846. * 200:
  847. * description: Succeeded to update customize script
  848. * content:
  849. * application/json:
  850. * schema:
  851. * type: object
  852. * properties:
  853. * customizedParams:
  854. * $ref: '#/components/schemas/CustomizeScript'
  855. */
  856. router.put('/customize-script', loginRequiredStrictly, adminRequired, addActivity, validator.customizeScript, apiV3FormValidator, async(req, res) => {
  857. const requestParams = {
  858. 'customize:script': req.body.customizeScript,
  859. };
  860. try {
  861. await configManager.updateConfigs(requestParams);
  862. const customizedParams = {
  863. customizeScript: await configManager.getConfig('customize:script'),
  864. };
  865. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE };
  866. activityEvent.emit('update', res.locals.activity._id, parameters);
  867. return res.apiv3({ customizedParams });
  868. }
  869. catch (err) {
  870. const msg = 'Error occurred in updating customizeScript';
  871. logger.error('Error', err);
  872. return res.apiv3Err(new ErrorV3(msg, 'update-customizeScript-failed'));
  873. }
  874. });
  875. /**
  876. * @swagger
  877. *
  878. * /customize-setting/customize-logo:
  879. * put:
  880. * tags: [CustomizeSetting]
  881. * security:
  882. * - cookieAuth: []
  883. * summary: /customize-setting/customize-logo
  884. * description: Update customize logo
  885. * requestBody:
  886. * required: true
  887. * content:
  888. * application/json:
  889. * schema:
  890. * $ref: '#/components/schemas/CustomizeLogo'
  891. * responses:
  892. * 200:
  893. * description: Succeeded to update customize logo
  894. * content:
  895. * application/json:
  896. * schema:
  897. * type: object
  898. * properties:
  899. * customizedParams:
  900. * $ref: '#/components/schemas/CustomizeLogo'
  901. */
  902. router.put('/customize-logo', loginRequiredStrictly, adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
  903. const {
  904. isDefaultLogo,
  905. } = req.body;
  906. const requestParams = {
  907. 'customize:isDefaultLogo': isDefaultLogo,
  908. };
  909. try {
  910. await configManager.updateConfigs(requestParams);
  911. const customizedParams = {
  912. isDefaultLogo: await configManager.getConfig('customize:isDefaultLogo'),
  913. };
  914. return res.apiv3({ customizedParams });
  915. }
  916. catch (err) {
  917. const msg = 'Error occurred in updating customizeLogo';
  918. logger.error('Error', err);
  919. return res.apiv3Err(new ErrorV3(msg, 'update-customizeLogo-failed'));
  920. }
  921. });
  922. /**
  923. * @swagger
  924. *
  925. * /customize-setting/upload-brand-logo:
  926. * put:
  927. * tags: [CustomizeSetting]
  928. * security:
  929. * - cookieAuth: []
  930. * summary: /customize-setting/upload-brand-logo
  931. * description: Upload brand logo
  932. * requestBody:
  933. * required: true
  934. * content:
  935. * multipart/form-data:
  936. * schema:
  937. * type: object
  938. * properties:
  939. * file:
  940. * format: binary
  941. * responses:
  942. * 200:
  943. * description: Succeeded to upload brand logo
  944. * content:
  945. * application/json:
  946. * schema:
  947. * type: object
  948. * properties:
  949. * attachment:
  950. * allOf:
  951. * - $ref: '#/components/schemas/Attachment'
  952. * - type: object
  953. * properties:
  954. * creator:
  955. * type: string
  956. * page: {}
  957. * temporaryUrlExpiredAt: {}
  958. * temporaryUrlCached: {}
  959. */
  960. router.post('/upload-brand-logo', loginRequiredStrictly, adminRequired,
  961. uploads.single('file'), validator.logo, apiV3FormValidator, async(req, res) => {
  962. if (req.file == null) {
  963. return res.apiv3Err(new ErrorV3('File error.', 'upload-brand-logo-failed'));
  964. }
  965. if (req.user == null) {
  966. return res.apiv3Err(new ErrorV3('param "user" must be set.', 'upload-brand-logo-failed'));
  967. }
  968. const file = req.file;
  969. // check type
  970. const acceptableFileType = /image\/.+/;
  971. if (!file.mimetype.match(acceptableFileType)) {
  972. const msg = 'File type error. Only image files is allowed to set as user picture.';
  973. return res.apiv3Err(new ErrorV3(msg, 'upload-brand-logo-failed'));
  974. }
  975. // Check if previous attachment exists and remove it
  976. const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });
  977. if (attachments != null) {
  978. await attachmentService.removeAllAttachments(attachments);
  979. }
  980. let attachment;
  981. try {
  982. attachment = await attachmentService.createAttachment(file, req.user, null, AttachmentType.BRAND_LOGO);
  983. }
  984. catch (err) {
  985. logger.error(err);
  986. return res.apiv3Err(new ErrorV3(err.message, 'upload-brand-logo-failed'));
  987. }
  988. attachment.toObject({ virtuals: true });
  989. return res.apiv3({ attachment });
  990. });
  991. /**
  992. * @swagger
  993. *
  994. * /customize-setting/delete-brand-logo:
  995. * delete:
  996. * tags: [CustomizeSetting]
  997. * security:
  998. * - cookieAuth: []
  999. * summary: /customize-setting/delete-brand-logo
  1000. * description: Delete brand logo
  1001. * responses:
  1002. * 200:
  1003. * description: Succeeded to delete brand logo
  1004. * content:
  1005. * application/json:
  1006. * schema:
  1007. * additionalProperties: false
  1008. */
  1009. router.delete('/delete-brand-logo', loginRequiredStrictly, adminRequired, async(req, res) => {
  1010. const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });
  1011. if (attachments == null) {
  1012. return res.apiv3Err(new ErrorV3('attachment not found', 'delete-brand-logo-failed'));
  1013. }
  1014. try {
  1015. await attachmentService.removeAllAttachments(attachments);
  1016. }
  1017. catch (err) {
  1018. logger.error(err);
  1019. return res.status(500).apiv3Err(new ErrorV3('Error while deleting logo', 'delete-brand-logo-failed'));
  1020. }
  1021. return res.apiv3({});
  1022. });
  1023. return router;
  1024. };