customize-setting.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  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. * operationId: getCustomizeSetting
  252. * summary: /customize-setting
  253. * description: Get customize parameters
  254. * responses:
  255. * 200:
  256. * description: params of customize
  257. * content:
  258. * application/json:
  259. * schema:
  260. * properties:
  261. * customizeParams:
  262. * type: object
  263. * description: customize params
  264. * $ref: '#/components/schemas/CustomizeSetting'
  265. */
  266. router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
  267. const customizeParams = {
  268. isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
  269. isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
  270. pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
  271. pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
  272. pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
  273. pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
  274. isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
  275. isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
  276. showPageSideAuthors: await configManager.getConfig('customize:showPageSideAuthors'),
  277. isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
  278. isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
  279. styleName: await configManager.getConfig('customize:highlightJsStyle'),
  280. styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
  281. customizeTitle: await configManager.getConfig('customize:title'),
  282. customizeScript: await configManager.getConfig('customize:script'),
  283. customizeCss: await configManager.getConfig('customize:css'),
  284. customizeNoscript: await configManager.getConfig('customize:noscript'),
  285. };
  286. return res.apiv3({ customizeParams });
  287. });
  288. /**
  289. * @swagger
  290. *
  291. * /customize-setting/layout:
  292. * get:
  293. * tags: [CustomizeSetting]
  294. * security:
  295. * - cookieAuth: []
  296. * operationId: getLayoutCustomizeSetting
  297. * summary: /customize-setting/layout
  298. * description: Get layout
  299. * responses:
  300. * 200:
  301. * description: Succeeded to get layout
  302. * content:
  303. * application/json:
  304. * schema:
  305. * $ref: '#/components/schemas/CustomizeLayout'
  306. */
  307. router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => {
  308. try {
  309. const isContainerFluid = await configManager.getConfig('customize:isContainerFluid');
  310. return res.apiv3({ isContainerFluid });
  311. }
  312. catch (err) {
  313. const msg = 'Error occurred in getting layout';
  314. logger.error('Error', err);
  315. return res.apiv3Err(new ErrorV3(msg, 'get-layout-failed'));
  316. }
  317. });
  318. /**
  319. * @swagger
  320. *
  321. * /customize-setting/layout:
  322. * put:
  323. * tags: [CustomizeSetting]
  324. * operationId: updateLayoutCustomizeSetting
  325. * summary: /customize-setting/layout
  326. * description: Update layout
  327. * requestBody:
  328. * required: true
  329. * content:
  330. * application/json:
  331. * schema:
  332. * $ref: '#/components/schemas/CustomizeLayout'
  333. * responses:
  334. * 200:
  335. * description: Succeeded to update layout
  336. * content:
  337. * application/json:
  338. * schema:
  339. * type: object
  340. * properties:
  341. * customizedParams:
  342. * type: object
  343. * description: customized params
  344. * $ref: '#/components/schemas/CustomizeLayout'
  345. */
  346. router.put('/layout', loginRequiredStrictly, adminRequired, addActivity, validator.layout, apiV3FormValidator, async(req, res) => {
  347. const requestParams = {
  348. 'customize:isContainerFluid': req.body.isContainerFluid,
  349. };
  350. try {
  351. await configManager.updateConfigs(requestParams);
  352. const customizedParams = {
  353. isContainerFluid: await configManager.getConfig('customize:isContainerFluid'),
  354. };
  355. const parameters = { action: SupportedAction.ACTION_ADMIN_LAYOUT_UPDATE };
  356. activityEvent.emit('update', res.locals.activity._id, parameters);
  357. return res.apiv3({ customizedParams });
  358. }
  359. catch (err) {
  360. const msg = 'Error occurred in updating layout';
  361. logger.error('Error', err);
  362. return res.apiv3Err(new ErrorV3(msg, 'update-layout-failed'));
  363. }
  364. });
  365. /**
  366. * @swagger
  367. *
  368. * /customize-setting/theme:
  369. * get:
  370. * tags: [CustomizeSetting]
  371. * security:
  372. * - cookieAuth: []
  373. * operationId: getThemeCustomizeSetting
  374. * summary: /customize-setting/theme
  375. * description: Get theme
  376. * responses:
  377. * 200:
  378. * description: Succeeded to get layout
  379. * content:
  380. * application/json:
  381. * schema:
  382. * type: object
  383. * properties:
  384. * currentTheme:
  385. * type: string
  386. * description: The current theme name.
  387. * pluginThemesMetadatas:
  388. * type: array
  389. * description: Metadata for available plugin themes.
  390. * items:
  391. * $ref: '#/components/schemas/ThemesMetadata'
  392. */
  393. router.get('/theme', loginRequiredStrictly, async(req, res) => {
  394. try {
  395. const currentTheme = await configManager.getConfig('customize:theme');
  396. // retrieve plugin manifests
  397. const themePlugins = await GrowiPlugin.findEnabledPluginsByType(GrowiPluginType.Theme);
  398. const pluginThemesMetadatas = themePlugins
  399. .map(themePlugin => themePlugin.meta.themes)
  400. .flat();
  401. return res.apiv3({ currentTheme, pluginThemesMetadatas });
  402. }
  403. catch (err) {
  404. const msg = 'Error occurred in getting theme';
  405. logger.error('Error', err);
  406. return res.apiv3Err(new ErrorV3(msg, 'get-theme-failed'));
  407. }
  408. });
  409. /**
  410. * @swagger
  411. *
  412. * /customize-setting/theme:
  413. * put:
  414. * tags: [CustomizeSetting]
  415. * security:
  416. * - cookieAuth: []
  417. * operationId: updateThemeCustomizeSetting
  418. * summary: /customize-setting/theme
  419. * description: Update theme
  420. * requestBody:
  421. * required: true
  422. * content:
  423. * application/json:
  424. * schema:
  425. * $ref: '#/components/schemas/CustomizeTheme'
  426. * responses:
  427. * 200:
  428. * description: Succeeded to update theme
  429. * content:
  430. * application/json:
  431. * schema:
  432. * type: object
  433. * properties:
  434. * customizedParams:
  435. * $ref: '#/components/schemas/CustomizeTheme'
  436. */
  437. router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
  438. const requestParams = {
  439. 'customize:theme': req.body.theme,
  440. };
  441. try {
  442. await configManager.updateConfigs(requestParams);
  443. const customizedParams = {
  444. theme: await configManager.getConfig('customize:theme'),
  445. };
  446. customizeService.initGrowiTheme();
  447. const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
  448. activityEvent.emit('update', res.locals.activity._id, parameters);
  449. return res.apiv3({ customizedParams });
  450. }
  451. catch (err) {
  452. const msg = 'Error occurred in updating theme';
  453. logger.error('Error', err);
  454. return res.apiv3Err(new ErrorV3(msg, 'update-theme-failed'));
  455. }
  456. });
  457. /**
  458. * @swagger
  459. *
  460. * /customize-setting/sidebar:
  461. * get:
  462. * tags: [CustomizeSetting]
  463. * security:
  464. * - cookieAuth: []
  465. * operationId: getCustomeSettingSidebar
  466. * summary: /customize-setting/sidebar
  467. * description: Get sidebar
  468. * responses:
  469. * 200:
  470. * description: Succeeded to get sidebar
  471. * content:
  472. * application/json:
  473. * schema:
  474. * $ref: '#/components/schemas/CustomizeSidebar'
  475. */
  476. router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
  477. try {
  478. const isSidebarCollapsedMode = await configManager.getConfig('customize:isSidebarCollapsedMode');
  479. const isSidebarClosedAtDockMode = await configManager.getConfig('customize:isSidebarClosedAtDockMode');
  480. return res.apiv3({ isSidebarCollapsedMode, isSidebarClosedAtDockMode });
  481. }
  482. catch (err) {
  483. const msg = 'Error occurred in getting sidebar';
  484. logger.error('Error', err);
  485. return res.apiv3Err(new ErrorV3(msg, 'get-sidebar-failed'));
  486. }
  487. });
  488. /**
  489. * @swagger
  490. *
  491. * /customize-setting/sidebar:
  492. * put:
  493. * tags: [CustomizeSetting]
  494. * security:
  495. * - cookieAuth: []
  496. * operationId: updateCustomizeSettingSidebar
  497. * summary: /customize-setting/sidebar
  498. * description: Update sidebar
  499. * requestBody:
  500. * required: true
  501. * content:
  502. * application/json:
  503. * schema:
  504. * $ref: '#/components/schemas/CustomizeSidebar'
  505. * responses:
  506. * 200:
  507. * description: Succeeded to update sidebar
  508. * content:
  509. * application/json:
  510. * schema:
  511. * type: object
  512. * properties:
  513. * customizedParams:
  514. * $ref: '#/components/schemas/CustomizeSidebar'
  515. */
  516. router.put('/sidebar', loginRequiredStrictly, adminRequired, validator.sidebar, apiV3FormValidator, addActivity, async(req, res) => {
  517. const requestParams = {
  518. 'customize:isSidebarCollapsedMode': req.body.isSidebarCollapsedMode,
  519. 'customize:isSidebarClosedAtDockMode': req.body.isSidebarClosedAtDockMode,
  520. };
  521. try {
  522. await configManager.updateConfigs(requestParams);
  523. const customizedParams = {
  524. isSidebarCollapsedMode: await configManager.getConfig('customize:isSidebarCollapsedMode'),
  525. isSidebarClosedAtDockMode: await configManager.getConfig('customize:isSidebarClosedAtDockMode'),
  526. };
  527. activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SIDEBAR_UPDATE });
  528. return res.apiv3({ customizedParams });
  529. }
  530. catch (err) {
  531. const msg = 'Error occurred in updating sidebar';
  532. logger.error('Error', err);
  533. return res.apiv3Err(new ErrorV3(msg, 'update-sidebar-failed'));
  534. }
  535. });
  536. /**
  537. * @swagger
  538. *
  539. * /customize-setting/function:
  540. * put:
  541. * tags: [CustomizeSetting]
  542. * security:
  543. * - cookieAuth: []
  544. * operationId: updateFunctionCustomizeSetting
  545. * summary: /customize-setting/function
  546. * description: Update function
  547. * requestBody:
  548. * required: true
  549. * content:
  550. * application/json:
  551. * schema:
  552. * $ref: '#/components/schemas/CustomizeFunction'
  553. * responses:
  554. * 200:
  555. * description: Succeeded to update function
  556. * content:
  557. * application/json:
  558. * schema:
  559. * type: object
  560. * properties:
  561. * customizedParams:
  562. * $ref: '#/components/schemas/CustomizeFunction'
  563. */
  564. router.put('/function', loginRequiredStrictly, adminRequired, addActivity, validator.function, apiV3FormValidator, async(req, res) => {
  565. const requestParams = {
  566. 'customize:isEnabledTimeline': req.body.isEnabledTimeline,
  567. 'customize:isEnabledAttachTitleHeader': req.body.isEnabledAttachTitleHeader,
  568. 'customize:showPageLimitationS': req.body.pageLimitationS,
  569. 'customize:showPageLimitationM': req.body.pageLimitationM,
  570. 'customize:showPageLimitationL': req.body.pageLimitationL,
  571. 'customize:showPageLimitationXL': req.body.pageLimitationXL,
  572. 'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification,
  573. 'customize:isAllReplyShown': req.body.isAllReplyShown,
  574. 'customize:isSearchScopeChildrenAsDefault': req.body.isSearchScopeChildrenAsDefault,
  575. 'customize:showPageSideAuthors': req.body.showPageSideAuthors,
  576. };
  577. try {
  578. await configManager.updateConfigs(requestParams);
  579. const customizedParams = {
  580. isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
  581. isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
  582. pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
  583. pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
  584. pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
  585. pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
  586. isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
  587. isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
  588. isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
  589. showPageSideAuthors: await configManager.getConfig('customize:showPageSideAuthors'),
  590. };
  591. const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
  592. activityEvent.emit('update', res.locals.activity._id, parameters);
  593. return res.apiv3({ customizedParams });
  594. }
  595. catch (err) {
  596. const msg = 'Error occurred in updating function';
  597. logger.error('Error', err);
  598. return res.apiv3Err(new ErrorV3(msg, 'update-function-failed'));
  599. }
  600. });
  601. /**
  602. * @swagger
  603. *
  604. * /customize-setting/presentation:
  605. * put:
  606. * tags: [CustomizeSetting]
  607. * security:
  608. * - cookieAuth: []
  609. * operationId: updatePresentationCustomizeSetting
  610. * summary: /customize-setting/presentation
  611. * description: Update presentation
  612. * requestBody:
  613. * required: true
  614. * content:
  615. * application/json:
  616. * schema:
  617. * $ref: '#/components/schemas/CustomizePresentation'
  618. * responses:
  619. * 200:
  620. * description: Succeeded to update presentation
  621. * content:
  622. * application/json:
  623. * schema:
  624. * type: object
  625. * properties:
  626. * customizedParams:
  627. * $ref: '#/components/schemas/CustomizePresentation'
  628. */
  629. router.put('/presentation', loginRequiredStrictly, adminRequired, addActivity, validator.CustomizePresentation, apiV3FormValidator, async(req, res) => {
  630. const requestParams = {
  631. 'customize:isEnabledMarp': req.body.isEnabledMarp,
  632. };
  633. try {
  634. await configManager.updateConfigs(requestParams);
  635. const customizedParams = {
  636. isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
  637. };
  638. const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
  639. activityEvent.emit('update', res.locals.activity._id, parameters);
  640. return res.apiv3({ customizedParams });
  641. }
  642. catch (err) {
  643. const msg = 'Error occurred in updating presentaion';
  644. logger.error('Error', err);
  645. return res.apiv3Err(new ErrorV3(msg, 'update-presentation-failed'));
  646. }
  647. });
  648. /**
  649. * @swagger
  650. *
  651. * /customize-setting/highlight:
  652. * put:
  653. * tags: [CustomizeSetting]
  654. * security:
  655. * - cookieAuth: []
  656. * operationId: updateHighlightCustomizeSetting
  657. * summary: /customize-setting/highlight
  658. * description: Update highlight
  659. * requestBody:
  660. * required: true
  661. * content:
  662. * application/json:
  663. * schema:
  664. * $ref: '#/components/schemas/CustomizeHighlight'
  665. * responses:
  666. * 200:
  667. * description: Succeeded to update highlight
  668. * content:
  669. * application/json:
  670. * schema:
  671. * type: object
  672. * properties:
  673. * customizedParams:
  674. * $ref: '#/components/schemas/CustomizeHighlightResponse'
  675. */
  676. router.put('/highlight', loginRequiredStrictly, adminRequired, addActivity, validator.highlight, apiV3FormValidator, async(req, res) => {
  677. const requestParams = {
  678. 'customize:highlightJsStyle': req.body.highlightJsStyle,
  679. 'customize:highlightJsStyleBorder': req.body.highlightJsStyleBorder,
  680. };
  681. try {
  682. await configManager.updateConfigs(requestParams);
  683. const customizedParams = {
  684. styleName: await configManager.getConfig('customize:highlightJsStyle'),
  685. styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
  686. };
  687. const parameters = { action: SupportedAction.ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE };
  688. activityEvent.emit('update', res.locals.activity._id, parameters);
  689. return res.apiv3({ customizedParams });
  690. }
  691. catch (err) {
  692. const msg = 'Error occurred in updating highlight';
  693. logger.error('Error', err);
  694. return res.apiv3Err(new ErrorV3(msg, 'update-highlight-failed'));
  695. }
  696. });
  697. /**
  698. * @swagger
  699. *
  700. * /customize-setting/customizeTitle:
  701. * put:
  702. * tags: [CustomizeSetting]
  703. * security:
  704. * - cookieAuth: []
  705. * operationId: updateCustomizeTitleCustomizeSetting
  706. * summary: /customize-setting/customizeTitle
  707. * description: Update title
  708. * requestBody:
  709. * required: true
  710. * content:
  711. * application/json:
  712. * schema:
  713. * $ref: '#/components/schemas/CustomizeTitle'
  714. * responses:
  715. * 200:
  716. * description: Succeeded to update customizeTitle
  717. * content:
  718. * application/json:
  719. * schema:
  720. * type: object
  721. * properties:
  722. * customizedParams:
  723. * $ref: '#/components/schemas/CustomizeTitle'
  724. */
  725. router.put('/customize-title', loginRequiredStrictly, adminRequired, addActivity, validator.customizeTitle, apiV3FormValidator, async(req, res) => {
  726. const requestParams = {
  727. 'customize:title': req.body.customizeTitle,
  728. };
  729. try {
  730. await configManager.updateConfigs(requestParams, { skipPubsub: true });
  731. crowi.customizeService.publishUpdatedMessage();
  732. const customizedParams = {
  733. customizeTitle: await configManager.getConfig('customize:title'),
  734. };
  735. customizeService.initCustomTitle();
  736. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_TITLE_UPDATE };
  737. activityEvent.emit('update', res.locals.activity._id, parameters);
  738. return res.apiv3({ customizedParams });
  739. }
  740. catch (err) {
  741. const msg = 'Error occurred in updating customizeTitle';
  742. logger.error('Error', err);
  743. return res.apiv3Err(new ErrorV3(msg, 'update-customizeTitle-failed'));
  744. }
  745. });
  746. /**
  747. * @swagger
  748. *
  749. * /customize-setting/customize-noscript:
  750. * put:
  751. * tags: [CustomizeSetting]
  752. * security:
  753. * - cookieAuth: []
  754. * operationId: updateCustomizeNoscriptCustomizeSetting
  755. * summary: /customize-setting/customize-noscript
  756. * description: Update noscript
  757. * requestBody:
  758. * required: true
  759. * content:
  760. * application/json:
  761. * schema:
  762. * $ref: '#/components/schemas/CustomizeNoscript'
  763. * responses:
  764. * 200:
  765. * description: Succeeded to update customize header
  766. * content:
  767. * application/json:
  768. * schema:
  769. * type: object
  770. * properties:
  771. * customizedParams:
  772. * $ref: '#/components/schemas/CustomizeNoscript'
  773. */
  774. router.put('/customize-noscript', loginRequiredStrictly, adminRequired, addActivity, validator.customizeNoscript, apiV3FormValidator, async(req, res) => {
  775. const requestParams = {
  776. 'customize:noscript': req.body.customizeNoscript,
  777. };
  778. try {
  779. await configManager.updateConfigs(requestParams);
  780. const customizedParams = {
  781. customizeNoscript: await configManager.getConfig('customize:noscript'),
  782. };
  783. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_NOSCRIPT_UPDATE };
  784. activityEvent.emit('update', res.locals.activity._id, parameters);
  785. return res.apiv3({ customizedParams });
  786. }
  787. catch (err) {
  788. const msg = 'Error occurred in updating customizeNoscript';
  789. logger.error('Error', err);
  790. return res.apiv3Err(new ErrorV3(msg, 'update-customizeNoscript-failed'));
  791. }
  792. });
  793. /**
  794. * @swagger
  795. *
  796. * /customize-setting/customize-css:
  797. * put:
  798. * tags: [CustomizeSetting]
  799. * security:
  800. * - cookieAuth: []
  801. * operationId: updateCustomizeCssCustomizeSetting
  802. * summary: /customize-setting/customize-css
  803. * description: Update customize css
  804. * requestBody:
  805. * required: true
  806. * content:
  807. * application/json:
  808. * schema:
  809. * $ref: '#/components/schemas/CustomizeCss'
  810. * responses:
  811. * 200:
  812. * description: Succeeded to update customize css
  813. * content:
  814. * application/json:
  815. * schema:
  816. * type: object
  817. * properties:
  818. * customizedParams:
  819. * $ref: '#/components/schemas/CustomizeCss'
  820. */
  821. router.put('/customize-css', loginRequiredStrictly, adminRequired, addActivity, validator.customizeCss, apiV3FormValidator, async(req, res) => {
  822. const requestParams = {
  823. 'customize:css': req.body.customizeCss,
  824. };
  825. try {
  826. await configManager.updateConfigs(requestParams, { skipPubsub: true });
  827. crowi.customizeService.publishUpdatedMessage();
  828. const customizedParams = {
  829. customizeCss: await configManager.getConfig('customize:css'),
  830. };
  831. customizeService.initCustomCss();
  832. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_CSS_UPDATE };
  833. activityEvent.emit('update', res.locals.activity._id, parameters);
  834. return res.apiv3({ customizedParams });
  835. }
  836. catch (err) {
  837. const msg = 'Error occurred in updating customizeCss';
  838. logger.error('Error', err);
  839. return res.apiv3Err(new ErrorV3(msg, 'update-customizeCss-failed'));
  840. }
  841. });
  842. /**
  843. * @swagger
  844. *
  845. * /customize-setting/customize-script:
  846. * put:
  847. * tags: [CustomizeSetting]
  848. * security:
  849. * - cookieAuth: []
  850. * operationId: updateCustomizeScriptCustomizeSetting
  851. * summary: /customize-setting/customize-script
  852. * description: Update customize script
  853. * requestBody:
  854. * required: true
  855. * content:
  856. * application/json:
  857. * schema:
  858. * $ref: '#/components/schemas/CustomizeScript'
  859. * responses:
  860. * 200:
  861. * description: Succeeded to update customize script
  862. * content:
  863. * application/json:
  864. * schema:
  865. * type: object
  866. * properties:
  867. * customizedParams:
  868. * $ref: '#/components/schemas/CustomizeScript'
  869. */
  870. router.put('/customize-script', loginRequiredStrictly, adminRequired, addActivity, validator.customizeScript, apiV3FormValidator, async(req, res) => {
  871. const requestParams = {
  872. 'customize:script': req.body.customizeScript,
  873. };
  874. try {
  875. await configManager.updateConfigs(requestParams);
  876. const customizedParams = {
  877. customizeScript: await configManager.getConfig('customize:script'),
  878. };
  879. const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE };
  880. activityEvent.emit('update', res.locals.activity._id, parameters);
  881. return res.apiv3({ customizedParams });
  882. }
  883. catch (err) {
  884. const msg = 'Error occurred in updating customizeScript';
  885. logger.error('Error', err);
  886. return res.apiv3Err(new ErrorV3(msg, 'update-customizeScript-failed'));
  887. }
  888. });
  889. /**
  890. * @swagger
  891. *
  892. * /customize-setting/customize-logo:
  893. * put:
  894. * tags: [CustomizeSetting]
  895. * security:
  896. * - cookieAuth: []
  897. * operationId: updateCustomizeLogoCustomizeSetting
  898. * summary: /customize-setting/customize-logo
  899. * description: Update customize logo
  900. * requestBody:
  901. * required: true
  902. * content:
  903. * application/json:
  904. * schema:
  905. * $ref: '#/components/schemas/CustomizeLogo'
  906. * responses:
  907. * 200:
  908. * description: Succeeded to update customize logo
  909. * content:
  910. * application/json:
  911. * schema:
  912. * type: object
  913. * properties:
  914. * customizedParams:
  915. * $ref: '#/components/schemas/CustomizeLogo'
  916. */
  917. router.put('/customize-logo', loginRequiredStrictly, adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
  918. const {
  919. isDefaultLogo,
  920. } = req.body;
  921. const requestParams = {
  922. 'customize:isDefaultLogo': isDefaultLogo,
  923. };
  924. try {
  925. await configManager.updateConfigs(requestParams);
  926. const customizedParams = {
  927. isDefaultLogo: await configManager.getConfig('customize:isDefaultLogo'),
  928. };
  929. return res.apiv3({ customizedParams });
  930. }
  931. catch (err) {
  932. const msg = 'Error occurred in updating customizeLogo';
  933. logger.error('Error', err);
  934. return res.apiv3Err(new ErrorV3(msg, 'update-customizeLogo-failed'));
  935. }
  936. });
  937. /**
  938. * @swagger
  939. *
  940. * /customize-setting/upload-brand-logo:
  941. * put:
  942. * tags: [CustomizeSetting]
  943. * security:
  944. * - cookieAuth: []
  945. * operationId: uploadBrandLogoCustomizeSetting
  946. * summary: /customize-setting/upload-brand-logo
  947. * description: Upload brand logo
  948. * requestBody:
  949. * required: true
  950. * content:
  951. * multipart/form-data:
  952. * schema:
  953. * type: object
  954. * properties:
  955. * file:
  956. * format: binary
  957. * responses:
  958. * 200:
  959. * description: Succeeded to upload brand logo
  960. * content:
  961. * application/json:
  962. * schema:
  963. * type: object
  964. * properties:
  965. * attachment:
  966. * allOf:
  967. * - $ref: '#/components/schemas/Attachment'
  968. * - type: object
  969. * properties:
  970. * creator:
  971. * type: string
  972. * page: {}
  973. * temporaryUrlExpiredAt: {}
  974. * temporaryUrlCached: {}
  975. */
  976. router.post('/upload-brand-logo', loginRequiredStrictly, adminRequired,
  977. uploads.single('file'), validator.logo, apiV3FormValidator, async(req, res) => {
  978. if (req.file == null) {
  979. return res.apiv3Err(new ErrorV3('File error.', 'upload-brand-logo-failed'));
  980. }
  981. if (req.user == null) {
  982. return res.apiv3Err(new ErrorV3('param "user" must be set.', 'upload-brand-logo-failed'));
  983. }
  984. const file = req.file;
  985. // check type
  986. const acceptableFileType = /image\/.+/;
  987. if (!file.mimetype.match(acceptableFileType)) {
  988. const msg = 'File type error. Only image files is allowed to set as user picture.';
  989. return res.apiv3Err(new ErrorV3(msg, 'upload-brand-logo-failed'));
  990. }
  991. // Check if previous attachment exists and remove it
  992. const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });
  993. if (attachments != null) {
  994. await attachmentService.removeAllAttachments(attachments);
  995. }
  996. let attachment;
  997. try {
  998. attachment = await attachmentService.createAttachment(file, req.user, null, AttachmentType.BRAND_LOGO);
  999. }
  1000. catch (err) {
  1001. logger.error(err);
  1002. return res.apiv3Err(new ErrorV3(err.message, 'upload-brand-logo-failed'));
  1003. }
  1004. attachment.toObject({ virtuals: true });
  1005. return res.apiv3({ attachment });
  1006. });
  1007. /**
  1008. * @swagger
  1009. *
  1010. * /customize-setting/delete-brand-logo:
  1011. * delete:
  1012. * tags: [CustomizeSetting]
  1013. * security:
  1014. * - cookieAuth: []
  1015. * operationId: deleteBrandLogoCustomizeSetting
  1016. * summary: /customize-setting/delete-brand-logo
  1017. * description: Delete brand logo
  1018. * responses:
  1019. * 200:
  1020. * description: Succeeded to delete brand logo
  1021. * content:
  1022. * application/json:
  1023. * schema:
  1024. * additionalProperties: false
  1025. */
  1026. router.delete('/delete-brand-logo', loginRequiredStrictly, adminRequired, async(req, res) => {
  1027. const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });
  1028. if (attachments == null) {
  1029. return res.apiv3Err(new ErrorV3('attachment not found', 'delete-brand-logo-failed'));
  1030. }
  1031. try {
  1032. await attachmentService.removeAllAttachments(attachments);
  1033. }
  1034. catch (err) {
  1035. logger.error(err);
  1036. return res.status(500).apiv3Err(new ErrorV3('Error while deleting logo', 'delete-brand-logo-failed'));
  1037. }
  1038. return res.apiv3({});
  1039. });
  1040. return router;
  1041. };