Просмотр исходного кода

Merge branch 'slack-notification' of github.com:crowi/crowi into slack-notification

Sotaro KARASAWA 10 лет назад
Родитель
Сommit
42de66f2e0

+ 5 - 1
lib/events/page.js

@@ -10,10 +10,14 @@ function PageEvent(crowi) {
 }
 util.inherits(PageEvent, events.EventEmitter);
 
-PageEvent.prototype.onUpdated = function(page) {
+PageEvent.prototype.onCreate = function(context, page, user) {
   var User = this.crowi.model('User');
   var Page = this.crowi.model('Page');
 
 };
+PageEvent.prototype.onUpdate = function(context, page, user) {
+  var User = this.crowi.model('User');
+  var Page = this.crowi.model('Page');
+};
 
 module.exports = PageEvent;

+ 2 - 1
lib/form/revision.js

@@ -7,5 +7,6 @@ module.exports = form(
   field('pageForm.path').required(),
   field('pageForm.body').required().custom(function(value) { return value.replace(/\r/g, '\n'); }),
   field('pageForm.currentRevision'),
-  field('pageForm.grant').toInt().required()
+  field('pageForm.grant').toInt().required(),
+  field('pageForm.notify')
 );

+ 149 - 132
lib/models/page.js

@@ -30,11 +30,26 @@ module.exports = function(crowi) {
     liker: [{ type: ObjectId, ref: 'User', index: true }],
     seenUsers: [{ type: ObjectId, ref: 'User', index: true }],
     commentCount: { type: Number, default: 0 },
+    extented: {
+      type: String,
+      default: '{}',
+      get: function(data) {
+        try {
+          return JSON.parse(data);
+        } catch(e) {
+          return data;
+        }
+      },
+      set: function(data) {
+        return JSON.stringify(data);
+      }
+    },
     createdAt: { type: Date, default: Date.now },
     updatedAt: Date
   });
 
-  pageEvent.on('updated', pageEvent.onUpdated);
+  pageEvent.on('create', pageEvent.onCreate);
+  pageEvent.on('update', pageEvent.onUpdate);
 
   pageSchema.methods.isPublic = function() {
     if (!this.grant || this.grant == GRANT_PUBLIC) {
@@ -97,21 +112,21 @@ module.exports = function(crowi) {
     var self = this,
       Page = self;
 
-    return new Promise(function(resolve, reject) {
-      var added = self.liker.addToSet(userData._id);
-      if (added.length > 0) {
-        self.save(function(err, data) {
-          if (err) {
-            return reject(err);
-          }
-          debug('liker updated!', added);
-          return resolve(data);
-        });
-      } else {
-        debug('liker not updated');
-        return reject(self);
-      }
-    });
+      return new Promise(function(resolve, reject) {
+        var added = self.liker.addToSet(userData._id);
+        if (added.length > 0) {
+          self.save(function(err, data) {
+            if (err) {
+              return reject(err);
+            }
+            debug('liker updated!', added);
+            return resolve(data);
+          });
+        } else {
+          debug('liker not updated');
+          return reject(self);
+        }
+      });
 
   };
 
@@ -119,21 +134,21 @@ module.exports = function(crowi) {
     var self = this,
       Page = self;
 
-    return new Promise(function(resolve, reject) {
-      var beforeCount = self.liker.length;
-      self.liker.pull(userData._id);
-      if (self.liker.length != beforeCount) {
-        self.save(function(err, data) {
-          if (err) {
-            return reject(err);
-          }
-          return resolve(data);
-        });
-      } else {
-        debug('liker not updated');
-        return reject(self);
-      }
-    });
+      return new Promise(function(resolve, reject) {
+        var beforeCount = self.liker.length;
+        self.liker.pull(userData._id);
+        if (self.liker.length != beforeCount) {
+          self.save(function(err, data) {
+            if (err) {
+              return reject(err);
+            }
+            return resolve(data);
+          });
+        } else {
+          debug('liker not updated');
+          return reject(self);
+        }
+      });
 
   };
 
@@ -141,35 +156,35 @@ module.exports = function(crowi) {
     var self = this,
       Page = self;
 
-    return this.seenUsers.some(function(seenUser) {
-      return seenUser.equals(userData._id);
-    });
+      return this.seenUsers.some(function(seenUser) {
+        return seenUser.equals(userData._id);
+      });
   };
 
   pageSchema.methods.seen = function(userData) {
     var self = this,
       Page = self;
 
-    if (this.isSeenUser(userData)) {
-      debug('seenUsers not updated');
-      return Promise.resolve(this);
-    }
-
-    return new Promise(function(resolve, reject) {
-      if (!userData || !userData._id) {
-        reject(new Error('User data is not valid'));
+      if (this.isSeenUser(userData)) {
+        debug('seenUsers not updated');
+        return Promise.resolve(this);
       }
 
-      var added = self.seenUsers.addToSet(userData);
-      self.save(function(err, data) {
-        if (err) {
-          return reject(err);
+      return new Promise(function(resolve, reject) {
+        if (!userData || !userData._id) {
+          reject(new Error('User data is not valid'));
         }
 
-        debug('seenUsers updated!', added);
-        return resolve(self);
+        var added = self.seenUsers.addToSet(userData);
+        self.save(function(err, data) {
+          if (err) {
+            return reject(err);
+          }
+
+          debug('seenUsers updated!', added);
+          return resolve(self);
+        });
       });
-    });
   };
 
   pageSchema.statics.populatePageData = function(pageData, revisionId) {
@@ -284,13 +299,13 @@ module.exports = function(crowi) {
     var forbiddenPages = [
       /\^|\$|\*|\+|\#/,
       /^\/_api\/.*/,
-      /^\/\-\/.*/,
-      /^\/_r\/.*/,
-      /^\/user\/[^\/]+\/(bookmarks|comments|activities|pages|recent-create|recent-edit)/, // reserved
-      /^http:\/\/.+$/, // avoid miss in renaming
-      /.+\/edit$/,
-      /.+\.md$/,
-      /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments).+/,
+/^\/\-\/.*/,
+/^\/_r\/.*/,
+/^\/user\/[^\/]+\/(bookmarks|comments|activities|pages|recent-create|recent-edit)/, // reserved
+/^http:\/\/.+$/, // avoid miss in renaming
+/.+\/edit$/,
+/.+\.md$/,
+/^\/(installer|register|login|logout|admin|me|files|trash|paste|comments).+/,
     ];
 
     var isCreatable = true;
@@ -313,13 +328,13 @@ module.exports = function(crowi) {
 
   pageSchema.statics.findUpdatedList = function(offset, limit, cb) {
     this
-      .find({})
-      .sort({updatedAt: -1})
-      .skip(offset)
-      .limit(limit)
-      .exec(function(err, data) {
-        cb(err, data);
-      });
+    .find({})
+    .sort({updatedAt: -1})
+    .skip(offset)
+    .limit(limit)
+    .exec(function(err, data) {
+      cb(err, data);
+    });
   };
 
   pageSchema.statics.findPageById = function(id) {
@@ -405,24 +420,24 @@ module.exports = function(crowi) {
 
     return new Promise(function(resolve, reject) {
       Page
-        .find({ _id: { $in: ids }, grant: GRANT_PUBLIC })
-        //.sort({createdAt: -1}) // TODO optionize
-        .skip(offset)
-        .limit(limit)
-        .populate('revision')
-        .exec(function(err, pages) {
+      .find({ _id: { $in: ids }, grant: GRANT_PUBLIC })
+      //.sort({createdAt: -1}) // TODO optionize
+      .skip(offset)
+      .limit(limit)
+      .populate('revision')
+      .exec(function(err, pages) {
+        if (err) {
+          return reject(err);
+        }
+
+        Page.populate(pages, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS}, function(err, data) {
           if (err) {
             return reject(err);
           }
 
-          Page.populate(pages, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS}, function(err, data) {
-            if (err) {
-              return reject(err);
-            }
-
-            return resolve(data);
-          });
+          return resolve(data);
         });
+      });
     });
   };
 
@@ -434,24 +449,24 @@ module.exports = function(crowi) {
 
     return new Promise(function(resolve, reject) {
       Page
-        .find({ creator: user._id, grant: GRANT_PUBLIC })
-        .sort({createdAt: -1})
-        .skip(offset)
-        .limit(limit)
-        .populate('revision')
-        .exec(function(err, pages) {
+      .find({ creator: user._id, grant: GRANT_PUBLIC })
+      .sort({createdAt: -1})
+      .skip(offset)
+      .limit(limit)
+      .populate('revision')
+      .exec(function(err, pages) {
+        if (err) {
+          return reject(err);
+        }
+
+        Page.populate(pages, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS}, function(err, data) {
           if (err) {
             return reject(err);
           }
 
-          Page.populate(pages, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS}, function(err, data) {
-            if (err) {
-              return reject(err);
-            }
-
-            return resolve(data);
-          });
+          return resolve(data);
         });
+      });
     });
   };
 
@@ -476,20 +491,20 @@ module.exports = function(crowi) {
     return new Promise(function(resolve, reject) {
       // FIXME: might be heavy
       var q = Page.find({
-          path: queryReg,
-          redirectTo: null,
-          $or: [
-            {grant: null},
-            {grant: GRANT_PUBLIC},
-            {grant: GRANT_RESTRICTED, grantedUsers: userData._id},
-            {grant: GRANT_SPECIFIED, grantedUsers: userData._id},
-            {grant: GRANT_OWNER, grantedUsers: userData._id},
-          ],
-        })
-        .populate('revision')
-        .sort(sortOpt)
-        .skip(opt.offset)
-        .limit(opt.limit);
+        path: queryReg,
+        redirectTo: null,
+        $or: [
+          {grant: null},
+          {grant: GRANT_PUBLIC},
+          {grant: GRANT_RESTRICTED, grantedUsers: userData._id},
+          {grant: GRANT_SPECIFIED, grantedUsers: userData._id},
+          {grant: GRANT_OWNER, grantedUsers: userData._id},
+        ],
+      })
+      .populate('revision')
+      .sort(sortOpt)
+      .skip(opt.offset)
+      .limit(opt.limit);
 
       q.exec(function(err, pages) {
         if (err) {
@@ -584,6 +599,7 @@ module.exports = function(crowi) {
           }
 
           resolve(data);
+          pageEvent.emit('update', data, user);
         });
       });
     });
@@ -597,41 +613,42 @@ module.exports = function(crowi) {
       , redirectTo = options.redirectTo || null;
 
     // force public
-    if (isPortalPath(path)) {
-      grant = GRANT_PUBLIC;
-    }
-
-    return new Promise(function(resolve, reject) {
-      Page.findOne({path: path}, function(err, pageData) {
-        if (pageData) {
-          return reject(new Error('Cannot create new page to existed path'));
-        }
+      if (isPortalPath(path)) {
+        grant = GRANT_PUBLIC;
+      }
 
-        var newPage = new Page();
-        newPage.path = path;
-        newPage.creator = user;
-        newPage.createdAt = Date.now();
-        newPage.updatedAt = Date.now();
-        newPage.redirectTo = redirectTo;
-        newPage.grant = grant;
-        newPage.grantedUsers = [];
-        newPage.grantedUsers.push(user);
-
-        newPage.save(function (err, newPage) {
-          if (err) {
-            return reject(err);
+      return new Promise(function(resolve, reject) {
+        Page.findOne({path: path}, function(err, pageData) {
+          if (pageData) {
+            return reject(new Error('Cannot create new page to existed path'));
           }
 
-          var newRevision = Revision.prepareRevision(newPage, body, user, {format: format});
-          Page.pushRevision(newPage, newRevision, user).then(function(data) {
-            resolve(data);
-          }).catch(function(err) {
-            debug('Push Revision Error on create page', err);
-            return reject(err);
+          var newPage = new Page();
+          newPage.path = path;
+          newPage.creator = user;
+          newPage.createdAt = Date.now();
+          newPage.updatedAt = Date.now();
+          newPage.redirectTo = redirectTo;
+          newPage.grant = grant;
+          newPage.grantedUsers = [];
+          newPage.grantedUsers.push(user);
+
+          newPage.save(function (err, newPage) {
+            if (err) {
+              return reject(err);
+            }
+
+            var newRevision = Revision.prepareRevision(newPage, body, user, {format: format});
+            Page.pushRevision(newPage, newRevision, user).then(function(data) {
+              resolve(data);
+              pageEvent.emit('create', data, user);
+            }).catch(function(err) {
+              debug('Push Revision Error on create page', err);
+              return reject(err);
+            });
           });
         });
       });
-    });
   };
 
   pageSchema.statics.rename = function(pageData, newPagePath, user, options) {

+ 58 - 0
lib/routes/admin.js

@@ -193,6 +193,64 @@ module.exports = function(crowi, app) {
     });
   };
 
+  actions.slackauthstart = function(req, res) {
+    var Botkit = require('botkit');
+    var controller = Botkit.slackbot();
+    controller.configureSlackApp({
+      clientId: '',
+      clientSecret: '',
+      redirectUri: 'http://localhost:3000/slackauth',
+      scopes: ['chat:write:bot']
+    });
+
+    return res.render('admin/slackauthstart', {
+      url: controller.getAuthorizeURL(),
+    });
+  };
+
+  actions.slackauth = function(req, res) {
+    debug(req.query.code);
+    var Botkit = require('botkit');
+    var controller = Botkit.slackbot({debug: true});
+    controller.configureSlackApp({
+      clientId: '',
+      clientSecret: '',
+      redirectUri: 'http://localhost:3000/slackauth',
+      scopes: ['chat:write:bot']
+    });
+
+    var bot = controller.spawn();
+    bot.api.oauth.access({code: req.query.code}, function (err, response) {
+      debug(err, response);
+    });
+    //
+    // access_token: '',
+    // scope: 'identify,chat:write:bot',
+    // team_name: '',
+    // team_id: '' }
+    //var bot = controller.spawn({token: ''});
+    //bot.api.chat.postMessage({
+    //  channel: '#xtest',
+    //  username: 'Crowi',
+    //  text: '/hoge/fuga/piyo is updated.',
+    //  attachments: [
+    //    {
+    //      color: '#263a3c',
+    //      author_name: '@sotarok',
+    //      author_link: 'http://localhost:3000/user/sotarok',
+    //      author_icon: 'https://crowi-strk-dev.s3.amazonaws.com/user/56c9dbf860ab5bc62647d84a.png',
+    //      title: "/hoge/fuga/piyo",
+    //      title_link: "http://localhost:3000/hoge/fuga/piyo",
+    //      text: '*# sotarok*\n\nshellに続き、初心者が頑張って理解していくノート的記事です。\nGitHubが常識と化してきた今日この頃、チャレンジしてみたものの、そもそもGitってなんなのかわからないと使いこなせない、っていうか挫折すると思います。私はしました。\nなので、起き上がってまずGitについて本当に一から理解を試みました。\n\n***\n  \n*## Gitって何?*\n\nGitとは、**[バージョン管理システム](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0)**のひとつです。\n        バージョン管理‥? :open_mouth: \nバージョンすなわち変更履歴の管理です。\n\n例えばなにかファイルの書き換えをするとき、元データを失わないためにどのような工夫をするでしょうか。ほとんどの場合、保存用にコピーを作成しておくというのが定番の方法だと思います。\nですが、誰しもこんな経験があるのでは。\n\n - 元データの保管が面倒\n  - \'meeting_3月.txt\'、\'meeting_最新.txt\'といった名前のデータが複数できてしまう\n   - チームで触っていて、最後に書き換えたのが誰なのかわからない\n    - 先輩も同時に編集していたのに、知らずにタッチの差で上書きしてしまった\n     - 書き換えたよ~と言われても(あるいは自分でも)、どこを換えたかわからない\n      - 何回か前の更新状態の方が良かった、戻したい…\n      \n      ある~~~!!\n      こんな事態を解消してくれるのが**Gitというバージョン管理システム**です。',
+    //      mrkdwn_in: ["text"],
+    //    },
+    //  ],
+    //});
+
+    //var code = req.query.);
+    res.render('admin/slackauth', {});
+  };
+
   actions.api = {};
   actions.api.appSetting = function(req, res) {
     var form = req.form.settingForm;

+ 3 - 0
lib/routes/index.js

@@ -52,6 +52,9 @@ module.exports = function(crowi, app) {
   app.post('/admin/user/:id/remove'     , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.remove);
   app.post('/admin/user/:id/removeCompletely' , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.removeCompletely);
 
+  app.get('/slackauthstart' , loginRequired(crowi, app) , middleware.adminRequired() , admin.slackauthstart);
+  app.get('/slackauth' , loginRequired(crowi, app) , middleware.adminRequired() , admin.slackauth);
+
   app.get('/me'                       , loginRequired(crowi, app) , me.index);
   app.get('/me/password'              , loginRequired(crowi, app) , me.password);
   app.get('/me/apiToken'              , loginRequired(crowi, app) , me.apiToken);

+ 5 - 0
lib/routes/page.js

@@ -213,6 +213,11 @@ module.exports = function(crowi, app) {
     var grant = pageForm.grant;
     var path = pageForm.path;
 
+    // TODO: make it pluggable
+    var notify = pageForm.notify || {};
+
+    debug('notify: ', notify);
+
     var redirectPath = encodeURI(path);
 
     // set to render

+ 7 - 3
lib/service/notification.js

@@ -1,11 +1,15 @@
+'use strict';
 
-function Notification() {
+function Notification ()
+{
 }
 
-Notification.prototype.loadModules = function() {
+Notification.prototype.noitfyByEmail = function()
+{
 };
 
-Notification.prototype.onPageUpdated = function(page) {
+Notification.prototype.noitfyByChat = function()
+{
 };
 
 module.exports = Notification;

+ 12 - 2
lib/views/_form.html

@@ -9,7 +9,7 @@
 </div>
 {% endif %}
 <div id="form-box" class="row">
-  <form action="/_/edit" id="page-form" method="post" class="col-md-6 {% if isUploadable() %}uploadable{% endif %}">
+  <form action="/_/edit" id="page-form" method="post" class="col-md-6 {% if isUploadable() %}uploadable{% endif %} page-form">
     <textarea name="pageForm[body]" class="form-control" id="form-body">{% if pageForm.body %}{{ pageForm.body }}{% elseif not page.revision.body %}# {{ path|path2name }}{% else %}{{ page.revision.body }}{% endif %}</textarea>
 
     <input type="hidden" name="pageForm[path]" value="{{ path }}">
@@ -20,7 +20,17 @@
         ファイルを追加 ...
       </button>#}
 
-      <div class="pull-right form-inline">
+      <div class="pull-right form-inline page-form-setting">
+        <span class="input-group extended-setting">
+          <span class="input-group-addon">
+            <label>
+              <i class="fa fa-slack"></i>
+              <input class="" type="checkbox" name="pageForm[notify][slack][on]" value="1">
+            </label>
+          </span>
+          <input class="form-control" type="text" name="pageForm[notify][slack][channel]" value="{{ page.extented.slack|default('') }}" placeholder="#slack-channel-name">
+        </span>
+
         {% if forceGrant %}
         <input type="hidden" name="pageForm[grant]" value="{{ forceGrant }}">
         {% else %}

+ 0 - 0
lib/views/admin/slackauth.html


+ 1 - 0
lib/views/admin/slackauthstart.html

@@ -0,0 +1 @@
+<a href="{{ url }}">auth</a>

+ 1 - 0
package.json

@@ -34,6 +34,7 @@
     "bluebird": "~3.0.5",
     "body-parser": "~1.14.1",
     "bootstrap-sass": "~3.3.6",
+    "botkit": "0.0.8",
     "browserify": "~12.0.1",
     "cli": "~0.6.0",
     "connect-flash": "~0.1.1",

+ 8 - 0
resource/css/_form.scss

@@ -76,6 +76,14 @@
             content: "Preview";
           }
         }
+
+        .page-form-setting {
+          .extended-setting {
+            label {
+              margin-bottom: 0;
+            }
+          }
+        }
       }
     }