notification-setting.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. const loggerFactory = require('@alias/logger');
  2. // eslint-disable-next-line no-unused-vars
  3. const logger = loggerFactory('growi:routes:apiv3:notification-setting');
  4. const express = require('express');
  5. const router = express.Router();
  6. const { body } = require('express-validator');
  7. const ErrorV3 = require('../../models/vo/error-apiv3');
  8. const removeNullPropertyFromObject = require('../../../lib/util/removeNullPropertyFromObject');
  9. const validator = {
  10. slackConfiguration: [
  11. body('webhookUrl').if(value => value != null).isString().trim(),
  12. body('isIncomingWebhookPrioritized').isBoolean(),
  13. body('slackToken').if(value => value != null).isString().trim(),
  14. ],
  15. userNotification: [
  16. body('pathPattern').isString().trim(),
  17. body('channel').isString().trim(),
  18. ],
  19. globalNotification: [
  20. body('triggerPath').isString().trim().not()
  21. .isEmpty(),
  22. body('notifyToType').isString().trim().isIn(['mail', 'slack']),
  23. body('toEmail').trim().custom((value, { req }) => {
  24. return (req.body.notifyToType === 'mail') ? (!!value && value.match(/.+@.+\..+/)) : true;
  25. }),
  26. body('slackChannels').trim().custom((value, { req }) => {
  27. return (req.body.notifyToType === 'slack') ? !!value : true;
  28. }),
  29. ],
  30. notifyForPageGrant: [
  31. body('isNotificationForOwnerPageEnabled').if(value => value != null).isBoolean(),
  32. body('isNotificationForGroupPageEnabled').if(value => value != null).isBoolean(),
  33. ],
  34. };
  35. /**
  36. * @swagger
  37. * tags:
  38. * name: NotificationSetting
  39. */
  40. /**
  41. * @swagger
  42. *
  43. * components:
  44. * schemas:
  45. * SlackConfigurationParams:
  46. * type: object
  47. * properties:
  48. * webhookUrl:
  49. * type: string
  50. * description: incoming webhooks url
  51. * isIncomingWebhookPrioritized:
  52. * type: boolean
  53. * description: use incoming webhooks even if Slack App settings are enabled
  54. * slackToken:
  55. * type: string
  56. * description: OAuth access token
  57. * UserNotificationParams:
  58. * type: object
  59. * properties:
  60. * pathPattern:
  61. * type: string
  62. * description: path name of wiki
  63. * channel:
  64. * type: string
  65. * description: slack channel name without '#'
  66. * NotifyForPageGrant:
  67. * type: object
  68. * properties:
  69. * isNotificationForOwnerPageEnabled:
  70. * type: string
  71. * description: Whether to notify on owner page
  72. * isNotificationForGroupPageEnabled:
  73. * type: string
  74. * description: Whether to notify on group page
  75. * GlobalNotificationParams:
  76. * type: object
  77. * properties:
  78. * notifyToType:
  79. * type: string
  80. * description: What is type for notify
  81. * toEmail:
  82. * type: string
  83. * description: email for notify
  84. * slackChannels:
  85. * type: string
  86. * description: channels for notify
  87. * triggerPath:
  88. * type: string
  89. * description: trigger path for notify
  90. * triggerEvents:
  91. * type: array
  92. * items:
  93. * type: string
  94. * description: trigger events for notify
  95. */
  96. module.exports = (crowi) => {
  97. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  98. const adminRequired = require('../../middlewares/admin-required')(crowi);
  99. const csrf = require('../../middlewares/csrf')(crowi);
  100. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  101. const UpdatePost = crowi.model('UpdatePost');
  102. const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
  103. const GlobalNotificationMailSetting = crowi.models.GlobalNotificationMailSetting;
  104. const GlobalNotificationSlackSetting = crowi.models.GlobalNotificationSlackSetting;
  105. /**
  106. * @swagger
  107. *
  108. * /notification-setting/:
  109. * get:
  110. * tags: [NotificationSetting]
  111. * description: Get notification paramators
  112. * responses:
  113. * 200:
  114. * description: params of notification
  115. * content:
  116. * application/json:
  117. * schema:
  118. * properties:
  119. * notificationParams:
  120. * type: object
  121. * description: notification params
  122. */
  123. router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
  124. const notificationParams = {
  125. webhookUrl: await crowi.configManager.getConfig('notification', 'slack:incomingWebhookUrl'),
  126. isIncomingWebhookPrioritized: await crowi.configManager.getConfig('notification', 'slack:isIncomingWebhookPrioritized'),
  127. slackToken: await crowi.configManager.getConfig('notification', 'slack:token'),
  128. userNotifications: await UpdatePost.findAll(),
  129. isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification', 'notification:owner-page:isEnabled'),
  130. isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification', 'notification:group-page:isEnabled'),
  131. globalNotifications: await GlobalNotificationSetting.findAll(),
  132. };
  133. return res.apiv3({ notificationParams });
  134. });
  135. /**
  136. * @swagger
  137. *
  138. * /notification-setting/slack-configuration:
  139. * put:
  140. * tags: [NotificationSetting]
  141. * description: Update slack configuration setting
  142. * requestBody:
  143. * required: true
  144. * content:
  145. * application/json:
  146. * schema:
  147. * $ref: '#/components/schemas/SlackConfigurationParams'
  148. * responses:
  149. * 200:
  150. * description: Succeeded to update slack configuration setting
  151. * content:
  152. * application/json:
  153. * schema:
  154. * $ref: '#/components/schemas/SlackConfigurationParams'
  155. */
  156. router.put('/slack-configuration', loginRequiredStrictly, adminRequired, csrf, validator.slackConfiguration, apiV3FormValidator, async(req, res) => {
  157. const requestParams = {
  158. 'slack:incomingWebhookUrl': req.body.webhookUrl,
  159. 'slack:isIncomingWebhookPrioritized': req.body.isIncomingWebhookPrioritized,
  160. 'slack:token': req.body.slackToken,
  161. };
  162. try {
  163. await crowi.configManager.updateConfigsInTheSameNamespace('notification', requestParams);
  164. const responseParams = {
  165. webhookUrl: await crowi.configManager.getConfig('notification', 'slack:incomingWebhookUrl'),
  166. isIncomingWebhookPrioritized: await crowi.configManager.getConfig('notification', 'slack:isIncomingWebhookPrioritized'),
  167. slackToken: await crowi.configManager.getConfig('notification', 'slack:token'),
  168. };
  169. await crowi.setupSlack();
  170. return res.apiv3({ responseParams });
  171. }
  172. catch (err) {
  173. const msg = 'Error occurred in updating slack configuration';
  174. logger.error('Error', err);
  175. return res.apiv3Err(new ErrorV3(msg, 'update-slackConfiguration-failed'));
  176. }
  177. });
  178. /**
  179. * @swagger
  180. *
  181. * /notification-setting/user-notification:
  182. * post:
  183. * tags: [NotificationSetting]
  184. * description: add user notification setting
  185. * requestBody:
  186. * required: true
  187. * content:
  188. * application/json:
  189. * schema:
  190. * $ref: '#/components/schemas/UserNotificationParams'
  191. * responses:
  192. * 200:
  193. * description: Succeeded to add user notification setting
  194. * content:
  195. * application/json:
  196. * schema:
  197. * properties:
  198. * createdUser:
  199. * type: object
  200. * description: user who set notification
  201. * userNotifications:
  202. * type: object
  203. * description: user trigger notifications for updated
  204. */
  205. router.post('/user-notification', loginRequiredStrictly, adminRequired, csrf, validator.userNotification, apiV3FormValidator, async(req, res) => {
  206. const { pathPattern, channel } = req.body;
  207. const UpdatePost = crowi.model('UpdatePost');
  208. try {
  209. logger.info('notification.add', pathPattern, channel);
  210. const responseParams = {
  211. createdUser: await UpdatePost.create(pathPattern, channel, req.user),
  212. userNotifications: await UpdatePost.findAll(),
  213. };
  214. return res.apiv3({ responseParams });
  215. }
  216. catch (err) {
  217. const msg = 'Error occurred in updating user notification';
  218. logger.error('Error', err);
  219. return res.apiv3Err(new ErrorV3(msg, 'update-userNotification-failed'));
  220. }
  221. });
  222. /**
  223. * @swagger
  224. *
  225. * /notification-setting/user-notification/{id}:
  226. * delete:
  227. * tags: [NotificationSetting]
  228. * description: delete user trigger notification pattern
  229. * parameters:
  230. * - name: id
  231. * in: path
  232. * required: true
  233. * description: id of user trigger notification
  234. * schema:
  235. * type: string
  236. * responses:
  237. * 200:
  238. * description: Succeeded to delete user trigger notification pattern
  239. * content:
  240. * application/json:
  241. * schema:
  242. * properties:
  243. * deletedNotificaton:
  244. * type: object
  245. * description: deleted notification
  246. */
  247. router.delete('/user-notification/:id', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  248. const { id } = req.params;
  249. try {
  250. const deletedNotificaton = await UpdatePost.remove(id);
  251. return res.apiv3(deletedNotificaton);
  252. }
  253. catch (err) {
  254. const msg = 'Error occurred in delete user trigger notification';
  255. logger.error('Error', err);
  256. return res.apiv3Err(new ErrorV3(msg, 'delete-userTriggerNotification-failed'));
  257. }
  258. });
  259. /**
  260. * @swagger
  261. *
  262. * /notification-setting/global-notification:
  263. * post:
  264. * tags: [NotificationSetting]
  265. * description: add global notification
  266. * requestBody:
  267. * required: true
  268. * content:
  269. * application/json:
  270. * schema:
  271. * $ref: '#/components/schemas/GlobalNotificationParams'
  272. * responses:
  273. * 200:
  274. * description: Succeeded to add global notification
  275. * content:
  276. * application/json:
  277. * schema:
  278. * properties:
  279. * createdNotification:
  280. * type: object
  281. * description: notification param created
  282. */
  283. router.post('/global-notification', loginRequiredStrictly, adminRequired, csrf, validator.globalNotification, apiV3FormValidator, async(req, res) => {
  284. const {
  285. notifyToType, toEmail, slackChannels, triggerPath, triggerEvents,
  286. } = req.body;
  287. let notification;
  288. if (notifyToType === GlobalNotificationSetting.TYPE.MAIL) {
  289. notification = new GlobalNotificationMailSetting(crowi);
  290. notification.toEmail = toEmail;
  291. }
  292. if (notifyToType === GlobalNotificationSetting.TYPE.SLACK) {
  293. notification = new GlobalNotificationSlackSetting(crowi);
  294. notification.slackChannels = slackChannels;
  295. }
  296. notification.triggerPath = triggerPath;
  297. notification.triggerEvents = triggerEvents || [];
  298. try {
  299. const createdNotification = await notification.save();
  300. return res.apiv3({ createdNotification });
  301. }
  302. catch (err) {
  303. const msg = 'Error occurred in updating global notification';
  304. logger.error('Error', err);
  305. return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
  306. }
  307. });
  308. /**
  309. * @swagger
  310. *
  311. * /notification-setting/global-notification/{id}:
  312. * put:
  313. * tags: [NotificationSetting]
  314. * description: update global notification
  315. * parameters:
  316. * - name: id
  317. * in: path
  318. * required: true
  319. * description: global notification id for updated
  320. * schema:
  321. * type: string
  322. * requestBody:
  323. * required: true
  324. * content:
  325. * application/json:
  326. * schema:
  327. * $ref: '#/components/schemas/GlobalNotificationParams'
  328. * responses:
  329. * 200:
  330. * description: Succeeded to update global notification
  331. * content:
  332. * application/json:
  333. * schema:
  334. * properties:
  335. * createdNotification:
  336. * type: object
  337. * description: notification param updated
  338. */
  339. router.put('/global-notification/:id', loginRequiredStrictly, adminRequired, csrf, validator.globalNotification, apiV3FormValidator, async(req, res) => {
  340. const { id } = req.params;
  341. const {
  342. notifyToType, toEmail, slackChannels, triggerPath, triggerEvents,
  343. } = req.body;
  344. const models = {
  345. [GlobalNotificationSetting.TYPE.MAIL]: GlobalNotificationMailSetting,
  346. [GlobalNotificationSetting.TYPE.SLACK]: GlobalNotificationSlackSetting,
  347. };
  348. try {
  349. let setting = await GlobalNotificationSetting.findOne({ _id: id });
  350. setting = setting.toObject();
  351. // when switching from one type to another,
  352. // remove toEmail from slack setting and slackChannels from mail setting
  353. if (setting.__t !== notifyToType) {
  354. setting = models[setting.__t].hydrate(setting);
  355. setting.toEmail = undefined;
  356. setting.slackChannels = undefined;
  357. await setting.save();
  358. setting = setting.toObject();
  359. }
  360. if (notifyToType === GlobalNotificationSetting.TYPE.MAIL) {
  361. setting = GlobalNotificationMailSetting.hydrate(setting);
  362. setting.toEmail = toEmail;
  363. }
  364. if (notifyToType === GlobalNotificationSetting.TYPE.SLACK) {
  365. setting = GlobalNotificationSlackSetting.hydrate(setting);
  366. setting.slackChannels = slackChannels;
  367. }
  368. setting.__t = notifyToType;
  369. setting.triggerPath = triggerPath;
  370. setting.triggerEvents = triggerEvents || [];
  371. const createdNotification = await setting.save();
  372. return res.apiv3({ createdNotification });
  373. }
  374. catch (err) {
  375. const msg = 'Error occurred in updating global notification';
  376. logger.error('Error', err);
  377. return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
  378. }
  379. });
  380. /**
  381. * @swagger
  382. *
  383. * /notification-setting/notify-for-page-grant:
  384. * put:
  385. * tags: [NotificationSetting]
  386. * description: Update settings for notify for page grant
  387. * requestBody:
  388. * required: true
  389. * content:
  390. * application/json:
  391. * schema:
  392. * $ref: '#/components/schemas/NotifyForPageGrant'
  393. * responses:
  394. * 200:
  395. * description: Succeeded to settings for notify for page grant
  396. * content:
  397. * application/json:
  398. * schema:
  399. * $ref: '#/components/schemas/NotifyForPageGrant'
  400. */
  401. router.put('/notify-for-page-grant', loginRequiredStrictly, adminRequired, csrf, validator.notifyForPageGrant, apiV3FormValidator, async(req, res) => {
  402. let requestParams = {
  403. 'notification:owner-page:isEnabled': req.body.isNotificationForOwnerPageEnabled,
  404. 'notification:group-page:isEnabled': req.body.isNotificationForGroupPageEnabled,
  405. };
  406. requestParams = removeNullPropertyFromObject(requestParams);
  407. try {
  408. await crowi.configManager.updateConfigsInTheSameNamespace('notification', requestParams);
  409. const responseParams = {
  410. isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification', 'notification:owner-page:isEnabled'),
  411. isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification', 'notification:group-page:isEnabled'),
  412. };
  413. return res.apiv3({ responseParams });
  414. }
  415. catch (err) {
  416. const msg = 'Error occurred in updating notify for page grant';
  417. logger.error('Error', err);
  418. return res.apiv3Err(new ErrorV3(msg, 'update-notify-for-page-grant-failed'));
  419. }
  420. });
  421. /**
  422. * @swagger
  423. *
  424. * /notification-setting/global-notification/{id}/enabled:
  425. * put:
  426. * tags: [NotificationSetting]
  427. * description: toggle enabled global notification
  428. * parameters:
  429. * - name: id
  430. * in: path
  431. * required: true
  432. * description: notification id for updated
  433. * schema:
  434. * type: string
  435. * requestBody:
  436. * required: true
  437. * content:
  438. * application/json:
  439. * schema:
  440. * properties:
  441. * isEnabled:
  442. * type: boolean
  443. * description: is notification enabled
  444. * responses:
  445. * 200:
  446. * description: Succeeded to delete global notification pattern
  447. * content:
  448. * application/json:
  449. * schema:
  450. * properties:
  451. * deletedNotificaton:
  452. * type: object
  453. * description: notification id for updated
  454. */
  455. router.put('/global-notification/:id/enabled', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  456. const { id } = req.params;
  457. const { isEnabled } = req.body;
  458. try {
  459. if (isEnabled) {
  460. await GlobalNotificationSetting.enable(id);
  461. }
  462. else {
  463. await GlobalNotificationSetting.disable(id);
  464. }
  465. return res.apiv3({ id });
  466. }
  467. catch (err) {
  468. const msg = 'Error occurred in toggle of global notification';
  469. logger.error('Error', err);
  470. return res.apiv3Err(new ErrorV3(msg, 'toggle-globalNotification-failed'));
  471. }
  472. });
  473. /**
  474. * @swagger
  475. *
  476. * /notification-setting/global-notification/{id}:
  477. * delete:
  478. * tags: [NotificationSetting]
  479. * description: delete global notification pattern
  480. * parameters:
  481. * - name: id
  482. * in: path
  483. * required: true
  484. * description: id of global notification
  485. * schema:
  486. * type: string
  487. * responses:
  488. * 200:
  489. * description: Succeeded to delete global notification pattern
  490. * content:
  491. * application/json:
  492. * schema:
  493. * properties:
  494. * deletedNotificaton:
  495. * type: object
  496. * description: deleted notification
  497. */
  498. router.delete('/global-notification/:id', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  499. const { id } = req.params;
  500. try {
  501. const deletedNotificaton = await GlobalNotificationSetting.findOneAndRemove({ _id: id });
  502. return res.apiv3(deletedNotificaton);
  503. }
  504. catch (err) {
  505. const msg = 'Error occurred in delete global notification';
  506. logger.error('Error', err);
  507. return res.apiv3Err(new ErrorV3(msg, 'delete-globalNotification-failed'));
  508. }
  509. });
  510. return router;
  511. };