notification-setting.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. import loggerFactory from '~/utils/logger';
  2. import { removeNullPropertyFromObject } from '~/utils/object-utils';
  3. // eslint-disable-next-line no-unused-vars
  4. const logger = loggerFactory('growi:routes:apiv3:notification-setting');
  5. const express = require('express');
  6. const router = express.Router();
  7. const { body } = require('express-validator');
  8. const ErrorV3 = require('../../models/vo/error-apiv3');
  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. return res.apiv3({ responseParams });
  170. }
  171. catch (err) {
  172. const msg = 'Error occurred in updating slack configuration';
  173. logger.error('Error', err);
  174. return res.apiv3Err(new ErrorV3(msg, 'update-slackConfiguration-failed'));
  175. }
  176. });
  177. /**
  178. * @swagger
  179. *
  180. * /notification-setting/user-notification:
  181. * post:
  182. * tags: [NotificationSetting]
  183. * description: add user notification setting
  184. * requestBody:
  185. * required: true
  186. * content:
  187. * application/json:
  188. * schema:
  189. * $ref: '#/components/schemas/UserNotificationParams'
  190. * responses:
  191. * 200:
  192. * description: Succeeded to add user notification setting
  193. * content:
  194. * application/json:
  195. * schema:
  196. * properties:
  197. * createdUser:
  198. * type: object
  199. * description: user who set notification
  200. * userNotifications:
  201. * type: object
  202. * description: user trigger notifications for updated
  203. */
  204. router.post('/user-notification', loginRequiredStrictly, adminRequired, csrf, validator.userNotification, apiV3FormValidator, async(req, res) => {
  205. const { pathPattern, channel } = req.body;
  206. const UpdatePost = crowi.model('UpdatePost');
  207. try {
  208. logger.info('notification.add', pathPattern, channel);
  209. const responseParams = {
  210. createdUser: await UpdatePost.create(pathPattern, channel, req.user),
  211. userNotifications: await UpdatePost.findAll(),
  212. };
  213. return res.apiv3({ responseParams }, 201);
  214. }
  215. catch (err) {
  216. const msg = 'Error occurred in updating user notification';
  217. logger.error('Error', err);
  218. return res.apiv3Err(new ErrorV3(msg, 'update-userNotification-failed'));
  219. }
  220. });
  221. /**
  222. * @swagger
  223. *
  224. * /notification-setting/user-notification/{id}:
  225. * delete:
  226. * tags: [NotificationSetting]
  227. * description: delete user trigger notification pattern
  228. * parameters:
  229. * - name: id
  230. * in: path
  231. * required: true
  232. * description: id of user trigger notification
  233. * schema:
  234. * type: string
  235. * responses:
  236. * 200:
  237. * description: Succeeded to delete user trigger notification pattern
  238. * content:
  239. * application/json:
  240. * schema:
  241. * properties:
  242. * deletedNotificaton:
  243. * type: object
  244. * description: deleted notification
  245. */
  246. router.delete('/user-notification/:id', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  247. const { id } = req.params;
  248. try {
  249. const deletedNotificaton = await UpdatePost.remove(id);
  250. return res.apiv3(deletedNotificaton);
  251. }
  252. catch (err) {
  253. const msg = 'Error occurred in delete user trigger notification';
  254. logger.error('Error', err);
  255. return res.apiv3Err(new ErrorV3(msg, 'delete-userTriggerNotification-failed'));
  256. }
  257. });
  258. /**
  259. * @swagger
  260. *
  261. * /notification-setting/global-notification:
  262. * post:
  263. * tags: [NotificationSetting]
  264. * description: add global notification
  265. * requestBody:
  266. * required: true
  267. * content:
  268. * application/json:
  269. * schema:
  270. * $ref: '#/components/schemas/GlobalNotificationParams'
  271. * responses:
  272. * 200:
  273. * description: Succeeded to add global notification
  274. * content:
  275. * application/json:
  276. * schema:
  277. * properties:
  278. * createdNotification:
  279. * type: object
  280. * description: notification param created
  281. */
  282. router.post('/global-notification', loginRequiredStrictly, adminRequired, csrf, validator.globalNotification, apiV3FormValidator, async(req, res) => {
  283. const {
  284. notifyToType, toEmail, slackChannels, triggerPath, triggerEvents,
  285. } = req.body;
  286. let notification;
  287. if (notifyToType === GlobalNotificationSetting.TYPE.MAIL) {
  288. notification = new GlobalNotificationMailSetting(crowi);
  289. notification.toEmail = toEmail;
  290. }
  291. if (notifyToType === GlobalNotificationSetting.TYPE.SLACK) {
  292. notification = new GlobalNotificationSlackSetting(crowi);
  293. notification.slackChannels = slackChannels;
  294. }
  295. notification.triggerPath = triggerPath;
  296. notification.triggerEvents = triggerEvents || [];
  297. try {
  298. const createdNotification = await notification.save();
  299. return res.apiv3({ createdNotification }, 201);
  300. }
  301. catch (err) {
  302. const msg = 'Error occurred in updating global notification';
  303. logger.error('Error', err);
  304. return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
  305. }
  306. });
  307. /**
  308. * @swagger
  309. *
  310. * /notification-setting/global-notification/{id}:
  311. * put:
  312. * tags: [NotificationSetting]
  313. * description: update global notification
  314. * parameters:
  315. * - name: id
  316. * in: path
  317. * required: true
  318. * description: global notification id for updated
  319. * schema:
  320. * type: string
  321. * requestBody:
  322. * required: true
  323. * content:
  324. * application/json:
  325. * schema:
  326. * $ref: '#/components/schemas/GlobalNotificationParams'
  327. * responses:
  328. * 200:
  329. * description: Succeeded to update global notification
  330. * content:
  331. * application/json:
  332. * schema:
  333. * properties:
  334. * createdNotification:
  335. * type: object
  336. * description: notification param updated
  337. */
  338. router.put('/global-notification/:id', loginRequiredStrictly, adminRequired, csrf, validator.globalNotification, apiV3FormValidator, async(req, res) => {
  339. const { id } = req.params;
  340. const {
  341. notifyToType, toEmail, slackChannels, triggerPath, triggerEvents,
  342. } = req.body;
  343. const models = {
  344. [GlobalNotificationSetting.TYPE.MAIL]: GlobalNotificationMailSetting,
  345. [GlobalNotificationSetting.TYPE.SLACK]: GlobalNotificationSlackSetting,
  346. };
  347. try {
  348. let setting = await GlobalNotificationSetting.findOne({ _id: id });
  349. setting = setting.toObject();
  350. // when switching from one type to another,
  351. // remove toEmail from slack setting and slackChannels from mail setting
  352. if (setting.__t !== notifyToType) {
  353. setting = models[setting.__t].hydrate(setting);
  354. setting.toEmail = undefined;
  355. setting.slackChannels = undefined;
  356. await setting.save();
  357. setting = setting.toObject();
  358. }
  359. if (notifyToType === GlobalNotificationSetting.TYPE.MAIL) {
  360. setting = GlobalNotificationMailSetting.hydrate(setting);
  361. setting.toEmail = toEmail;
  362. }
  363. if (notifyToType === GlobalNotificationSetting.TYPE.SLACK) {
  364. setting = GlobalNotificationSlackSetting.hydrate(setting);
  365. setting.slackChannels = slackChannels;
  366. }
  367. setting.__t = notifyToType;
  368. setting.triggerPath = triggerPath;
  369. setting.triggerEvents = triggerEvents || [];
  370. const createdNotification = await setting.save();
  371. return res.apiv3({ createdNotification });
  372. }
  373. catch (err) {
  374. const msg = 'Error occurred in updating global notification';
  375. logger.error('Error', err);
  376. return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed'));
  377. }
  378. });
  379. /**
  380. * @swagger
  381. *
  382. * /notification-setting/notify-for-page-grant:
  383. * put:
  384. * tags: [NotificationSetting]
  385. * description: Update settings for notify for page grant
  386. * requestBody:
  387. * required: true
  388. * content:
  389. * application/json:
  390. * schema:
  391. * $ref: '#/components/schemas/NotifyForPageGrant'
  392. * responses:
  393. * 200:
  394. * description: Succeeded to settings for notify for page grant
  395. * content:
  396. * application/json:
  397. * schema:
  398. * $ref: '#/components/schemas/NotifyForPageGrant'
  399. */
  400. router.put('/notify-for-page-grant', loginRequiredStrictly, adminRequired, csrf, validator.notifyForPageGrant, apiV3FormValidator, async(req, res) => {
  401. let requestParams = {
  402. 'notification:owner-page:isEnabled': req.body.isNotificationForOwnerPageEnabled,
  403. 'notification:group-page:isEnabled': req.body.isNotificationForGroupPageEnabled,
  404. };
  405. requestParams = removeNullPropertyFromObject(requestParams);
  406. try {
  407. await crowi.configManager.updateConfigsInTheSameNamespace('notification', requestParams);
  408. const responseParams = {
  409. isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification', 'notification:owner-page:isEnabled'),
  410. isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification', 'notification:group-page:isEnabled'),
  411. };
  412. return res.apiv3({ responseParams });
  413. }
  414. catch (err) {
  415. const msg = 'Error occurred in updating notify for page grant';
  416. logger.error('Error', err);
  417. return res.apiv3Err(new ErrorV3(msg, 'update-notify-for-page-grant-failed'));
  418. }
  419. });
  420. /**
  421. * @swagger
  422. *
  423. * /notification-setting/global-notification/{id}/enabled:
  424. * put:
  425. * tags: [NotificationSetting]
  426. * description: toggle enabled global notification
  427. * parameters:
  428. * - name: id
  429. * in: path
  430. * required: true
  431. * description: notification id for updated
  432. * schema:
  433. * type: string
  434. * requestBody:
  435. * required: true
  436. * content:
  437. * application/json:
  438. * schema:
  439. * properties:
  440. * isEnabled:
  441. * type: boolean
  442. * description: is notification enabled
  443. * responses:
  444. * 200:
  445. * description: Succeeded to delete global notification pattern
  446. * content:
  447. * application/json:
  448. * schema:
  449. * properties:
  450. * deletedNotificaton:
  451. * type: object
  452. * description: notification id for updated
  453. */
  454. router.put('/global-notification/:id/enabled', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  455. const { id } = req.params;
  456. const { isEnabled } = req.body;
  457. try {
  458. if (isEnabled) {
  459. await GlobalNotificationSetting.enable(id);
  460. }
  461. else {
  462. await GlobalNotificationSetting.disable(id);
  463. }
  464. return res.apiv3({ id });
  465. }
  466. catch (err) {
  467. const msg = 'Error occurred in toggle of global notification';
  468. logger.error('Error', err);
  469. return res.apiv3Err(new ErrorV3(msg, 'toggle-globalNotification-failed'));
  470. }
  471. });
  472. /**
  473. * @swagger
  474. *
  475. * /notification-setting/global-notification/{id}:
  476. * delete:
  477. * tags: [NotificationSetting]
  478. * description: delete global notification pattern
  479. * parameters:
  480. * - name: id
  481. * in: path
  482. * required: true
  483. * description: id of global notification
  484. * schema:
  485. * type: string
  486. * responses:
  487. * 200:
  488. * description: Succeeded to delete global notification pattern
  489. * content:
  490. * application/json:
  491. * schema:
  492. * properties:
  493. * deletedNotificaton:
  494. * type: object
  495. * description: deleted notification
  496. */
  497. router.delete('/global-notification/:id', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  498. const { id } = req.params;
  499. try {
  500. const deletedNotificaton = await GlobalNotificationSetting.findOneAndRemove({ _id: id });
  501. return res.apiv3(deletedNotificaton);
  502. }
  503. catch (err) {
  504. const msg = 'Error occurred in delete global notification';
  505. logger.error('Error', err);
  506. return res.apiv3Err(new ErrorV3(msg, 'delete-globalNotification-failed'));
  507. }
  508. });
  509. return router;
  510. };