notification-setting.js 16 KB

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