瀏覽代碼

refactor GlobalNotificationService

mizozobu 6 年之前
父節點
當前提交
4a79f1b93e

+ 9 - 1
src/server/crowi/index.js

@@ -131,12 +131,16 @@ Crowi.prototype.initForTest = async function() {
   //   this.setupMailer(),
   //   this.setupMailer(),
   //   this.setupSlack(),
   //   this.setupSlack(),
   //   this.setupCsrf(),
   //   this.setupCsrf(),
-  //   this.setUpGlobalNotification(),
   //   this.setUpFileUpload(),
   //   this.setUpFileUpload(),
     this.setUpAcl(),
     this.setUpAcl(),
   //   this.setUpCustomize(),
   //   this.setUpCustomize(),
   //   this.setUpRestQiitaAPI(),
   //   this.setUpRestQiitaAPI(),
   ]);
   ]);
+
+  // globalNotification depends on slack and mailer
+  // await Promise.all([
+  //   this.setUpGlobalNotification(),
+  // ]);
 };
 };
 
 
 Crowi.prototype.isPageId = function(pageId) {
 Crowi.prototype.isPageId = function(pageId) {
@@ -270,6 +274,10 @@ Crowi.prototype.getMailer = function() {
   return this.mailer;
   return this.mailer;
 };
 };
 
 
+Crowi.prototype.getSlack = function() {
+  return this.slack;
+};
+
 Crowi.prototype.getInterceptorManager = function() {
 Crowi.prototype.getInterceptorManager = function() {
   return this.interceptorManager;
   return this.interceptorManager;
 };
 };

+ 14 - 2
src/server/models/GlobalNotificationSetting/index.js

@@ -11,6 +11,18 @@ const globalNotificationSettingSchema = new mongoose.Schema({
   triggerEvents: { type: [String] },
   triggerEvents: { type: [String] },
 });
 });
 
 
+/**
+ * global notifcation event master
+ */
+globalNotificationSettingSchema.EVENT = {
+  PAGE_CREATE: 'pageCreate',
+  PAGE_EDIT: 'pageEdit',
+  PAGE_DELETE: 'pageDelete',
+  PAGE_MOVE: 'pageMove',
+  PAGE_LIKE: 'pageLike',
+  COMMENT: 'comment',
+};
+
 /*
 /*
 * e.g. "/a/b/c" => ["/a/b/c", "/a/b", "/a", "/"]
 * e.g. "/a/b/c" => ["/a/b/c", "/a/b", "/a", "/"]
 */
 */
@@ -92,12 +104,13 @@ class GlobalNotificationSetting {
    * @param {string} path
    * @param {string} path
    * @param {string} event
    * @param {string} event
    */
    */
-  static async findSettingByPathAndEvent(path, event) {
+  static async findSettingByPathAndEvent(event, path, type) {
     const pathsToMatch = generatePathsToMatch(path);
     const pathsToMatch = generatePathsToMatch(path);
 
 
     const settings = await this.find({
     const settings = await this.find({
       triggerPath: { $in: pathsToMatch },
       triggerPath: { $in: pathsToMatch },
       triggerEvents: event,
       triggerEvents: event,
+      __t: type,
       isEnabled: true,
       isEnabled: true,
     })
     })
       .sort({ triggerPath: 1 });
       .sort({ triggerPath: 1 });
@@ -107,7 +120,6 @@ class GlobalNotificationSetting {
 
 
 }
 }
 
 
-
 module.exports = {
 module.exports = {
   class: GlobalNotificationSetting,
   class: GlobalNotificationSetting,
   schema: globalNotificationSettingSchema,
   schema: globalNotificationSettingSchema,

+ 9 - 2
src/server/routes/comment.js

@@ -3,6 +3,7 @@ module.exports = function(crowi, app) {
   const Comment = crowi.model('Comment');
   const Comment = crowi.model('Comment');
   const User = crowi.model('User');
   const User = crowi.model('User');
   const Page = crowi.model('Page');
   const Page = crowi.model('Page');
+  const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
   const ApiResponse = require('../util/apiResponse');
   const ApiResponse = require('../util/apiResponse');
   const globalNotificationService = crowi.getGlobalNotificationService();
   const globalNotificationService = crowi.getGlobalNotificationService();
   const { body } = require('express-validator/check');
   const { body } = require('express-validator/check');
@@ -107,11 +108,15 @@ module.exports = function(crowi, app) {
       return res.json(ApiResponse.error('Current user is not accessible to this page.'));
       return res.json(ApiResponse.error('Current user is not accessible to this page.'));
     }
     }
 
 
-    const createdComment = await Comment.create(pageId, req.user._id, revisionId, comment, position, isMarkdown, replyTo)
+    let createdComment = await Comment.create(pageId, req.user._id, revisionId, comment, position, isMarkdown, replyTo)
       .catch((err) => {
       .catch((err) => {
         return res.json(ApiResponse.error(err));
         return res.json(ApiResponse.error(err));
       });
       });
 
 
+    createdComment = await createdComment
+      .populate('creator')
+      .execPopulate();
+
     // update page
     // update page
     const page = await Page.findOneAndUpdate({ _id: pageId }, {
     const page = await Page.findOneAndUpdate({ _id: pageId }, {
       lastUpdateUser: req.user,
       lastUpdateUser: req.user,
@@ -123,7 +128,9 @@ module.exports = function(crowi, app) {
     const path = page.path;
     const path = page.path;
 
 
     // global notification
     // global notification
-    globalNotificationService.notifyComment(createdComment, path);
+    globalNotificationService.fire(GlobalNotificationSetting.schema.EVENT.COMMENT, path, req.user, {
+      comment: createdComment,
+    });
 
 
     // slack notification
     // slack notification
     if (slackNotificationForm.isSlackEnabled) {
     if (slackNotificationForm.isSlackEnabled) {

+ 8 - 5
src/server/routes/page.js

@@ -11,6 +11,7 @@ module.exports = function(crowi, app) {
   const Bookmark = crowi.model('Bookmark');
   const Bookmark = crowi.model('Bookmark');
   const PageTagRelation = crowi.model('PageTagRelation');
   const PageTagRelation = crowi.model('PageTagRelation');
   const UpdatePost = crowi.model('UpdatePost');
   const UpdatePost = crowi.model('UpdatePost');
+  const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
 
 
   const ApiResponse = require('../util/apiResponse');
   const ApiResponse = require('../util/apiResponse');
   const getToday = require('../util/getToday');
   const getToday = require('../util/getToday');
@@ -607,7 +608,7 @@ module.exports = function(crowi, app) {
 
 
     // global notification
     // global notification
     try {
     try {
-      await globalNotificationService.notifyPageCreate(createdPage);
+      await globalNotificationService.fire(GlobalNotificationSetting.schema.EVENT.PAGE_CREATE, createdPage.path, req.user);
     }
     }
     catch (err) {
     catch (err) {
       logger.error(err);
       logger.error(err);
@@ -694,7 +695,7 @@ module.exports = function(crowi, app) {
 
 
     // global notification
     // global notification
     try {
     try {
-      await globalNotificationService.notifyPageEdit(page);
+      await globalNotificationService.fire(GlobalNotificationSetting.schema.EVENT.PAGE_EDIT, page.path, req.user);
     }
     }
     catch (err) {
     catch (err) {
       logger.error(err);
       logger.error(err);
@@ -862,7 +863,7 @@ module.exports = function(crowi, app) {
 
 
     try {
     try {
       // global notification
       // global notification
-      globalNotificationService.notifyPageLike(page, req.user);
+      await globalNotificationService.fire(GlobalNotificationSetting.schema.EVENT.PAGE_LIKE, page.path, req.user);
     }
     }
     catch (err) {
     catch (err) {
       logger.error('Like failed', err);
       logger.error('Like failed', err);
@@ -999,7 +1000,7 @@ module.exports = function(crowi, app) {
     res.json(ApiResponse.success(result));
     res.json(ApiResponse.success(result));
 
 
     // global notification
     // global notification
-    return globalNotificationService.notifyPageDelete(page, req.user);
+    await globalNotificationService.fire(GlobalNotificationSetting.schema.EVENT.PAGE_DELETE, page.path, req.user);
   };
   };
 
 
   /**
   /**
@@ -1104,7 +1105,9 @@ module.exports = function(crowi, app) {
     res.json(ApiResponse.success(result));
     res.json(ApiResponse.success(result));
 
 
     // global notification
     // global notification
-    globalNotificationService.notifyPageMove(page, req.body.path, req.user);
+    globalNotificationService.fire(GlobalNotificationSetting.schema.EVENT.PAGE_MOVE, page.path, req.user, {
+      oldPath: req.body.path,
+    });
 
 
     return page;
     return page;
   };
   };

+ 0 - 172
src/server/service/global-notification.js

@@ -1,172 +0,0 @@
-const logger = require('@alias/logger')('growi:service:GlobalNotification');
-const nodePath = require('path');
-/**
- * the service class of GlobalNotificationSetting
- */
-class GlobalNotificationService {
-
-  constructor(crowi) {
-    this.crowi = crowi;
-    this.mailer = crowi.getMailer();
-    this.slack = crowi.slack;
-    this.GlobalNotification = crowi.model('GlobalNotificationSetting');
-    this.User = crowi.model('User');
-    this.appTitle = crowi.appService.getAppTitle();
-  }
-
-  notifyByMail(notification, mailOption) {
-    this.mailer.send(Object.assign(mailOption, { to: notification.toEmail }));
-  }
-
-  notifyBySlack(notification, slackOption) {
-    this.slack.sendGlobalNotification(notification, slackOption);
-  }
-
-  fire(notifications, option) {
-    notifications.forEach((notification) => {
-      if (notification.__t === 'mail') {
-        this.notifyByMail(notification, option);
-      }
-      else if (notification.__t === 'slack') {
-        this.notifyBySlack(notification, option);
-      }
-    });
-  }
-
-  /**
-   * send notification at page creation
-   * @memberof GlobalNotification
-   * @param {obejct} page
-   */
-  async notifyPageCreate(page) {
-    const notifications = await this.GlobalNotification.findSettingByPathAndEvent(page.path, 'pageCreate');
-    const lang = 'en-US'; // FIXME
-    const option = {
-      subject: `#pageCreate - ${page.creator.username} created ${page.path}`,
-      template: nodePath.join(this.crowi.localeDir, `${lang}/notifications/pageCreate.txt`),
-      vars: {
-        appTitle: this.appTitle,
-        path: page.path,
-        username: page.creator.username,
-      },
-    };
-
-    logger.debug('notifyPageCreate', option);
-
-    this.fire(notifications, option);
-  }
-
-  /**
-   * send notification at page edit
-   * @memberof GlobalNotification
-   * @param {obejct} page
-   */
-  async notifyPageEdit(page) {
-    const notifications = await this.GlobalNotification.findSettingByPathAndEvent(page.path, 'pageEdit');
-    const lang = 'en-US'; // FIXME
-    const option = {
-      subject: `#pageEdit - ${page.creator.username} edited ${page.path}`,
-      template: nodePath.join(this.crowi.localeDir, `${lang}/notifications/pageEdit.txt`),
-      vars: {
-        appTitle: this.appTitle,
-        path: page.path,
-        username: page.creator.username,
-      },
-    };
-
-    logger.debug('notifyPageEdit', option);
-
-    this.fire(notifications, option);
-  }
-
-  /**
-   * send notification at page deletion
-   * @memberof GlobalNotification
-   * @param {obejct} page
-   */
-  async notifyPageDelete(page, triggeredBy) {
-    const notifications = await this.GlobalNotification.findSettingByPathAndEvent(page.path, 'pageDelete');
-    const lang = 'en-US'; // FIXME
-    const option = {
-      subject: `#pageDelete - ${triggeredBy.username} deleted ${page.path}`, // FIXME
-      template: nodePath.join(this.crowi.localeDir, `${lang}/notifications/pageDelete.txt`),
-      vars: {
-        appTitle: this.appTitle,
-        path: page.path,
-        username: triggeredBy.username,
-      },
-    };
-
-    this.fire(notifications, option);
-  }
-
-  /**
-   * send notification at page move
-   * @memberof GlobalNotification
-   * @param {obejct} page
-   */
-  async notifyPageMove(page, oldPagePath, triggeredBy) {
-    const notifications = await this.GlobalNotification.findSettingByPathAndEvent(page.path, 'pageMove');
-    const lang = 'en-US'; // FIXME
-    const option = {
-      subject: `#pageMove - ${triggeredBy.username} moved ${page.path} to ${page.path}`, // FIXME
-      template: nodePath.join(this.crowi.localeDir, `${lang}/notifications/pageMove.txt`),
-      vars: {
-        appTitle: this.appTitle,
-        oldPath: oldPagePath,
-        newPath: page.path,
-        username: triggeredBy.username,
-      },
-    };
-
-    this.fire(notifications, option);
-  }
-
-  /**
-   * send notification at page like
-   * @memberof GlobalNotification
-   * @param {obejct} page
-   */
-  async notifyPageLike(page, triggeredBy) {
-    const notifications = await this.GlobalNotification.findSettingByPathAndEvent(page.path, 'pageLike');
-    const lang = 'en-US'; // FIXME
-    const option = {
-      subject: `#pageLike - ${triggeredBy.username} liked ${page.path}`,
-      template: nodePath.join(this.crowi.localeDir, `${lang}/notifications/pageLike.txt`),
-      vars: {
-        appTitle: this.appTitle,
-        path: page.path,
-        username: triggeredBy.username,
-      },
-    };
-
-    this.fire(notifications, option);
-  }
-
-  /**
-   * send notification at page comment
-   * @memberof GlobalNotification
-   * @param {obejct} page
-   * @param {obejct} comment
-   */
-  async notifyComment(comment, path) {
-    const notifications = await this.GlobalNotification.findSettingByPathAndEvent(path, 'comment');
-    const lang = 'en-US'; // FIXME
-    const triggeredBy = await this.User.findOne({ _id: comment.creator });
-    const option = {
-      subject: `#comment - ${triggeredBy.username} commented on ${path}`,
-      template: nodePath.join(this.crowi.localeDir, `${lang}/notifications/comment.txt`),
-      vars: {
-        appTitle: this.appTitle,
-        path,
-        username: triggeredBy.username,
-        comment: comment.comment,
-      },
-    };
-
-    this.fire(notifications, option);
-  }
-
-}
-
-module.exports = GlobalNotificationService;

+ 33 - 0
src/server/service/global-notification/global-notification-mail.js

@@ -0,0 +1,33 @@
+const logger = require('@alias/logger')('growi:service:GlobalNotificationMailService'); // eslint-disable-line no-unused-vars
+
+/**
+ * sub service class of GlobalNotificationSetting
+ */
+class GlobalNotificationMailService {
+
+  constructor(crowi) {
+    this.crowi = crowi;
+    this.type = 'mail';
+    this.mailer = crowi.getMailer();
+  }
+
+  /**
+   * send mail global notification
+   *
+   * @memberof GlobalNotificationMailService
+   * @param {string} event
+   * @param {string} path
+   * @param {obejct} option
+   */
+  async fire(event, path, option) {
+    const GlobalNotification = this.crowi.model('GlobalNotificationSetting');
+    const notifications = await GlobalNotification.findSettingByPathAndEvent(event, path, this.type);
+
+    await Promise.all(notifications.map((notification) => {
+      return this.mailer.send({ ...option, to: notification.toEmail });
+    }));
+  }
+
+}
+
+module.exports = GlobalNotificationMailService;

+ 33 - 0
src/server/service/global-notification/global-notification-slack.js

@@ -0,0 +1,33 @@
+const logger = require('@alias/logger')('growi:service:GlobalNotificationSlackService'); // eslint-disable-line no-unused-vars
+
+/**
+ * sub service class of GlobalNotificationSetting
+ */
+class GlobalNotificationSlackService {
+
+  constructor(crowi) {
+    this.crowi = crowi;
+    this.type = 'slack';
+    this.slack = crowi.getSlack();
+  }
+
+  /**
+   * send slack global notification
+   *
+   * @memberof GlobalNotificationSlackService
+   * @param {string} event
+   * @param {string} path
+   * @param {obejct} option
+   */
+  async fire(event, path, option) {
+    const GlobalNotification = this.crowi.model('GlobalNotificationSetting');
+    const notifications = await GlobalNotification.findSettingByPathAndEvent(event, path, this.type);
+
+    await Promise.all(notifications.map((notification) => {
+      return this.slack.sendGlobalNotification({ ...option, slackChannels: notification.slackChannels });
+    }));
+  }
+
+}
+
+module.exports = GlobalNotificationSlackService;

+ 123 - 0
src/server/service/global-notification/index.js

@@ -0,0 +1,123 @@
+const logger = require('@alias/logger')('growi:service:GlobalNotificationService');
+const nodePath = require('path');
+const GloabalNotificationSlack = require('./global-notification-slack');
+const GloabalNotificationMail = require('./global-notification-mail');
+
+/**
+ * service class of GlobalNotificationSetting
+ */
+class GlobalNotificationService {
+
+  constructor(crowi) {
+    this.crowi = crowi;
+    this.defaultLang = 'en-US'; // TODO: get defaultLang from app global config
+    this.event = crowi.model('GlobalNotificationSetting').schema.EVENT;
+
+    this.gloabalNotificationMail = new GloabalNotificationMail(crowi);
+    this.gloabalNotificationSlack = new GloabalNotificationSlack(crowi);
+  }
+
+  /**
+   * fire global notification
+   *
+   * @memberof GlobalNotificationService
+   * @param {string} event event name triggered
+   * @param {string} path path triggered the event
+   * @param {User} triggeredBy user triggered the event
+   * @param {object} vars event specific vars
+   */
+  async fire(event, path, triggeredBy, vars = {}) {
+    const option = await this.generateOption(event, path, triggeredBy, vars);
+
+    logger.debug(`global notficatoin event ${event} was triggered`);
+
+    await Promise.all([
+      this.gloabalNotificationMail.fire(event, path, option),
+      this.gloabalNotificationSlack.fire(event, path, option),
+    ]);
+  }
+
+  /**
+   * fire global notification
+   *
+   * @memberof GlobalNotificationService
+   *
+   * @param {string} event event name triggered
+   * @param {string} path path triggered the event
+   * @param {User} triggeredBy user triggered the event
+   * @param {{ comment: Comment, oldPath: string }} _ event specific vars
+   *
+   * @return  {{ subject: string, template: string, vars: object }}
+   */
+  async generateOption(event, path, triggeredBy, { comment, oldPath }) {
+    // validate for all events
+    if (event == null || path == null || triggeredBy == null) {
+      throw new Error(`invalid vars supplied to generateOption for event ${event}`);
+    }
+
+    const template = nodePath.join(this.crowi.localeDir, `${this.defaultLang}/notifications/${event}.txt`);
+    let subject;
+    let vars = {
+      appTitle: this.crowi.appService.getAppTitle(),
+      path,
+      username: triggeredBy.username,
+    };
+
+    switch (event) {
+      case this.event.PAGE_CREATE:
+        subject = `#${event} - ${triggeredBy.username} created ${path}`;
+        break;
+
+      case this.event.PAGE_EDIT:
+        subject = `#${event} - ${triggeredBy.username} edited ${path}`;
+        break;
+
+      case this.event.PAGE_DELETE:
+        subject = `#${event} - ${triggeredBy.username} deleted ${path}`;
+        break;
+
+      case this.event.PAGE_MOVE:
+        // validate for page move
+        if (oldPath == null) {
+          throw new Error(`invalid vars supplied to generateOption for event ${event}`);
+        }
+
+        subject = `#${event} - ${triggeredBy.username} moved ${oldPath} to ${path}`;
+        vars = {
+          ...vars,
+          oldPath,
+          newPath: path,
+        };
+        break;
+
+      case this.event.PAGE_LIKE:
+        subject = `#${event} - ${triggeredBy.username} liked ${path}`;
+        break;
+
+      case this.event.COMMENT:
+        // validate for comment
+        if (comment == null) {
+          throw new Error(`invalid vars supplied to generateOption for event ${event}`);
+        }
+
+        subject = `#${event} - ${triggeredBy.username} commented on ${path}`;
+        vars = {
+          ...vars,
+          comment: comment.comment,
+        };
+        break;
+
+      default:
+        throw new Error(`unknown global notificaiton event: ${event}`);
+    }
+
+    return {
+      subject,
+      template,
+      vars,
+    };
+  }
+
+}
+
+module.exports = GlobalNotificationService;

+ 7 - 7
src/server/util/slack.js

@@ -172,18 +172,18 @@ module.exports = function(crowi) {
 
 
   /**
   /**
    * For GlobalNotification
    * For GlobalNotification
-   * @param {GlobalNotification} notification
+   * @param {{ template: string, vars: object, slackChannels: string }} option
   */
   */
-  const prepareSlackMessageForGlobalNotification = async(notification, config) => {
+  const prepareSlackMessageForGlobalNotification = async(option) => {
     const appTitle = crowi.appService.getAppTitle();
     const appTitle = crowi.appService.getAppTitle();
-    const templateVars = config.vars || {};
+    const templateVars = option.vars || {};
     const output = await promisify(swig.renderFile)(
     const output = await promisify(swig.renderFile)(
-      config.template,
+      option.template,
       templateVars,
       templateVars,
     );
     );
 
 
     const message = {
     const message = {
-      channel: `#${notification.slackChannels}`,
+      channel: `#${option.slackChannels}`,
       username: appTitle,
       username: appTitle,
       text: output,
       text: output,
     };
     };
@@ -227,8 +227,8 @@ module.exports = function(crowi) {
     return slackPost(messageObj);
     return slackPost(messageObj);
   };
   };
 
 
-  slack.sendGlobalNotification = async(notification, config) => {
-    const messageObj = await prepareSlackMessageForGlobalNotification(notification, config);
+  slack.sendGlobalNotification = async(option) => {
+    const messageObj = await prepareSlackMessageForGlobalNotification(option);
 
 
     return slackPost(messageObj);
     return slackPost(messageObj);
   };
   };