import { ErrorV3 } from '@growi/core/dist/models'; import { SupportedAction } from '~/interfaces/activity'; import { GlobalNotificationSettingType } from '~/server/models'; import loggerFactory from '~/utils/logger'; import { removeNullPropertyFromObject } from '~/utils/object-utils'; import { generateAddActivityMiddleware } from '../../middlewares/add-activity'; import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator'; import UpdatePost from '../../models/update-post'; // eslint-disable-next-line no-unused-vars const logger = loggerFactory('growi:routes:apiv3:notification-setting'); const express = require('express'); const router = express.Router(); const { body } = require('express-validator'); const validator = { userNotification: [ body('pathPattern').isString().trim(), body('channel').isString().trim(), ], globalNotification: [ body('triggerPath').isString().trim().not() .isEmpty(), body('notifyType').isString().trim().isIn(['mail', 'slack']), body('toEmail').trim().custom((value, { req }) => { return (req.body.notifyType === 'mail') ? (!!value && value.match(/.+@.+\..+/)) : true; }), body('slackChannels').trim().custom((value, { req }) => { return (req.body.notifyType === 'slack') ? !!value : true; }), ], notifyForPageGrant: [ body('isNotificationForOwnerPageEnabled').if(value => value != null).isBoolean(), body('isNotificationForGroupPageEnabled').if(value => value != null).isBoolean(), ], }; /** * @swagger * tags: * name: NotificationSetting */ /** * @swagger * * components: * schemas: * UserNotificationParams: * type: object * properties: * pathPattern: * type: string * description: path name of wiki * channel: * type: string * description: slack channel name without '#' * NotifyForPageGrant: * type: object * properties: * isNotificationForOwnerPageEnabled: * type: string * description: Whether to notify on owner page * isNotificationForGroupPageEnabled: * type: string * description: Whether to notify on group page * GlobalNotificationParams: * type: object * properties: * notifyType: * type: string * description: What is type for notify * toEmail: * type: string * description: email for notify * slackChannels: * type: string * description: channels for notify * triggerPath: * type: string * description: trigger path for notify * triggerEvents: * type: array * items: * type: string * description: trigger events for notify */ module.exports = (crowi) => { const loginRequiredStrictly = require('../../middlewares/login-required')(crowi); const adminRequired = require('../../middlewares/admin-required')(crowi); const addActivity = generateAddActivityMiddleware(crowi); const activityEvent = crowi.event('activity'); const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting'); const GlobalNotificationMailSetting = crowi.models.GlobalNotificationMailSetting; const GlobalNotificationSlackSetting = crowi.models.GlobalNotificationSlackSetting; /** * @swagger * * /notification-setting/: * get: * tags: [NotificationSetting] * description: Get notification paramators * responses: * 200: * description: params of notification * content: * application/json: * schema: * properties: * notificationParams: * type: object * description: notification params */ router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => { const notificationParams = { // status of slack intagration isSlackbotConfigured: crowi.slackIntegrationService.isSlackbotConfigured, isSlackLegacyConfigured: crowi.slackIntegrationService.isSlackLegacyConfigured, currentBotType: await crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'), userNotifications: await UpdatePost.findAll(), isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification', 'notification:owner-page:isEnabled'), isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification', 'notification:group-page:isEnabled'), globalNotifications: await GlobalNotificationSetting.findAll(), }; return res.apiv3({ notificationParams }); }); /** * @swagger * * /notification-setting/user-notification: * post: * tags: [NotificationSetting] * description: add user notification setting * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/UserNotificationParams' * responses: * 200: * description: Succeeded to add user notification setting * content: * application/json: * schema: * properties: * createdUser: * type: object * description: user who set notification * userNotifications: * type: object * description: user trigger notifications for updated */ // eslint-disable-next-line max-len router.post('/user-notification', loginRequiredStrictly, adminRequired, addActivity, validator.userNotification, apiV3FormValidator, async(req, res) => { const { pathPattern, channel } = req.body; try { logger.info('notification.add', pathPattern, channel); const responseParams = { createdUser: await UpdatePost.createUpdatePost(pathPattern, channel, req.user), userNotifications: await UpdatePost.findAll(), }; const parameters = { action: SupportedAction.ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ responseParams }, 201); } catch (err) { const msg = 'Error occurred in updating user notification'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-userNotification-failed')); } }); /** * @swagger * * /notification-setting/user-notification/{id}: * delete: * tags: [NotificationSetting] * description: delete user trigger notification pattern * parameters: * - name: id * in: path * required: true * description: id of user trigger notification * schema: * type: string * responses: * 200: * description: Succeeded to delete user trigger notification pattern * content: * application/json: * schema: * properties: * deletedNotificaton: * type: object * description: deleted notification */ router.delete('/user-notification/:id', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => { const { id } = req.params; try { const deletedNotificaton = await UpdatePost.findOneAndRemove({ _id: id }); const parameters = { action: SupportedAction.ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3(deletedNotificaton); } catch (err) { const msg = 'Error occurred in delete user trigger notification'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'delete-userTriggerNotification-failed')); } }); router.get('/global-notification/:id', loginRequiredStrictly, adminRequired, validator.globalNotification, async(req, res) => { const notificationSettingId = req.params.id; let globalNotification; if (notificationSettingId) { try { globalNotification = await GlobalNotificationSetting.findOne({ _id: notificationSettingId }); } catch (err) { logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`); } } return res.apiv3({ globalNotification }); }); /** * @swagger * * /notification-setting/global-notification: * post: * tags: [NotificationSetting] * description: add global notification * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/GlobalNotificationParams' * responses: * 200: * description: Succeeded to add global notification * content: * application/json: * schema: * properties: * createdNotification: * type: object * description: notification param created */ // eslint-disable-next-line max-len router.post('/global-notification', loginRequiredStrictly, adminRequired, addActivity, validator.globalNotification, apiV3FormValidator, async(req, res) => { const { notifyType, toEmail, slackChannels, triggerPath, triggerEvents, } = req.body; let notification; if (notifyType === GlobalNotificationSettingType.MAIL) { notification = new GlobalNotificationMailSetting(crowi); notification.toEmail = toEmail; } if (notifyType === GlobalNotificationSettingType.SLACK) { notification = new GlobalNotificationSlackSetting(crowi); notification.slackChannels = slackChannels; } notification.triggerPath = triggerPath; notification.triggerEvents = triggerEvents || []; try { const createdNotification = await notification.save(); const parameters = { action: SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ createdNotification }, 201); } catch (err) { const msg = 'Error occurred in updating global notification'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed')); } }); /** * @swagger * * /notification-setting/global-notification/{id}: * put: * tags: [NotificationSetting] * description: update global notification * parameters: * - name: id * in: path * required: true * description: global notification id for updated * schema: * type: string * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/GlobalNotificationParams' * responses: * 200: * description: Succeeded to update global notification * content: * application/json: * schema: * properties: * createdNotification: * type: object * description: notification param updated */ // eslint-disable-next-line max-len router.put('/global-notification/:id', loginRequiredStrictly, adminRequired, addActivity, validator.globalNotification, apiV3FormValidator, async(req, res) => { const { id } = req.params; const { notifyType, toEmail, slackChannels, triggerPath, triggerEvents, } = req.body; const models = { [GlobalNotificationSettingType.MAIL]: GlobalNotificationMailSetting, [GlobalNotificationSettingType.SLACK]: GlobalNotificationSlackSetting, }; try { let setting = await GlobalNotificationSetting.findOne({ _id: id }); setting = setting.toObject(); // when switching from one type to another, // remove toEmail from slack setting and slackChannels from mail setting if (setting.__t !== notifyType) { setting = models[setting.__t].hydrate(setting); setting.toEmail = undefined; setting.slackChannels = undefined; await setting.save(); setting = setting.toObject(); } if (notifyType === GlobalNotificationSettingType.MAIL) { setting = GlobalNotificationMailSetting.hydrate(setting); setting.toEmail = toEmail; } if (notifyType === GlobalNotificationSettingType.SLACK) { setting = GlobalNotificationSlackSetting.hydrate(setting); setting.slackChannels = slackChannels; } setting.__t = notifyType; setting.triggerPath = triggerPath; setting.triggerEvents = triggerEvents || []; const createdNotification = await setting.save(); const parameters = { action: SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ createdNotification }); } catch (err) { const msg = 'Error occurred in updating global notification'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'post-globalNotification-failed')); } }); /** * @swagger * * /notification-setting/notify-for-page-grant: * put: * tags: [NotificationSetting] * description: Update settings for notify for page grant * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/NotifyForPageGrant' * responses: * 200: * description: Succeeded to settings for notify for page grant * content: * application/json: * schema: * $ref: '#/components/schemas/NotifyForPageGrant' */ // eslint-disable-next-line max-len router.put('/notify-for-page-grant', loginRequiredStrictly, adminRequired, addActivity, validator.notifyForPageGrant, apiV3FormValidator, async(req, res) => { let requestParams = { 'notification:owner-page:isEnabled': req.body.isNotificationForOwnerPageEnabled, 'notification:group-page:isEnabled': req.body.isNotificationForGroupPageEnabled, }; requestParams = removeNullPropertyFromObject(requestParams); try { await crowi.configManager.updateConfigsInTheSameNamespace('notification', requestParams); const responseParams = { isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification', 'notification:owner-page:isEnabled'), isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification', 'notification:group-page:isEnabled'), }; const parameters = { action: SupportedAction.ACTION_ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ responseParams }); } catch (err) { const msg = 'Error occurred in updating notify for page grant'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-notify-for-page-grant-failed')); } }); /** * @swagger * * /notification-setting/global-notification/{id}/enabled: * put: * tags: [NotificationSetting] * description: toggle enabled global notification * parameters: * - name: id * in: path * required: true * description: notification id for updated * schema: * type: string * requestBody: * required: true * content: * application/json: * schema: * properties: * isEnabled: * type: boolean * description: is notification enabled * responses: * 200: * description: Succeeded to delete global notification pattern * content: * application/json: * schema: * properties: * deletedNotificaton: * type: object * description: notification id for updated */ router.put('/global-notification/:id/enabled', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => { const { id } = req.params; const { isEnabled } = req.body; try { if (isEnabled) { await GlobalNotificationSetting.enable(id); } else { await GlobalNotificationSetting.disable(id); } const parameters = { action: isEnabled ? SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ENABLED : SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DISABLED, }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ id }); } catch (err) { const msg = 'Error occurred in toggle of global notification'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'toggle-globalNotification-failed')); } }); /** * @swagger * * /notification-setting/global-notification/{id}: * delete: * tags: [NotificationSetting] * description: delete global notification pattern * parameters: * - name: id * in: path * required: true * description: id of global notification * schema: * type: string * responses: * 200: * description: Succeeded to delete global notification pattern * content: * application/json: * schema: * properties: * deletedNotificaton: * type: object * description: deleted notification */ router.delete('/global-notification/:id', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => { const { id } = req.params; try { const deletedNotificaton = await GlobalNotificationSetting.findOneAndRemove({ _id: id }); const parameters = { action: SupportedAction.ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DELETE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3(deletedNotificaton); } catch (err) { const msg = 'Error occurred in delete global notification'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'delete-globalNotification-failed')); } }); return router; };