Browse Source

Slack configuration screen on admin

tmp

Slack configuration screen on admin
Sotaro KARASAWA 10 years ago
parent
commit
40d21bf894

+ 19 - 0
lib/crowi/index.js

@@ -78,6 +78,8 @@ Crowi.prototype.init = function() {
     });
   }).then(function() {
     return self.setupMailer();
+  }).then(function() {
+    return self.setupSlack();
   }).then(function() {
     return self.buildServer();
   });
@@ -198,6 +200,23 @@ Crowi.prototype.setupMailer = function() {
   });
 };
 
+Crowi.prototype.setupSlack = function() {
+  var self = this;
+  var config = this.getConfig();
+  var Config = this.model('Config');
+
+  return new Promise(function(resolve, reject) {
+    if (!Config.hasSlackConfig(config)) {
+      self.slack = {};
+    } else {
+      self.slack = require('../util/slack')(self);
+    }
+
+    resolve();
+  });
+};
+
+
 
 Crowi.prototype.start = function(app) {
   var self = this

+ 10 - 0
lib/form/admin/slackSetting.js

@@ -0,0 +1,10 @@
+'use strict';
+
+var form = require('express-form')
+  , field = form.field;
+
+module.exports = form(
+  field('slackSetting[slack:clientId]', 'clientId').is(/(\d+)\.(\d+)/).required(),
+  field('slackSetting[slack:clientSecret]', 'clientSecret').required().is(/([0-9a-f]+)/)
+);
+

+ 1 - 0
lib/form/index.js

@@ -16,4 +16,5 @@ exports.admin = {
   google: require('./admin/google'),
   fb: require('./admin/fb'),
   userInvite: require('./admin/userInvite'),
+  slackSetting: require('./admin/slackSetting'),
 };

+ 31 - 2
lib/models/config.js

@@ -89,11 +89,14 @@ module.exports = function(crowi) {
 
   configSchema.statics.setupCofigFormData = function(ns, config)
   {
-    var defaultConfig;
+    var defaultConfig = {};
     if (ns === 'crowi') {
       defaultConfig  = getArrayForInstalling();
     }
-    Object.keys(config[ns]).forEach(function (key) {
+    if (!defaultConfig[ns]) {
+      defaultConfig[ns] = {};
+    }
+    Object.keys(config[ns] || {}).forEach(function (key) {
       if (config[ns][key]) {
         defaultConfig[key] = config[ns][key];
       }
@@ -176,6 +179,32 @@ module.exports = function(crowi) {
     return true;
   };
 
+  configSchema.statics.hasSlackConfig = function(config)
+  {
+    if (!config.notification) {
+      return false;
+    }
+    if (!config.notification['slack:clientId'] ||
+        !config.notification['slack:clientSecret']) {
+      return false;
+    }
+
+    return true;
+  };
+
+  configSchema.statics.hasSlackToken = function(config)
+  {
+    if (!this.hasSlackConfig(config)) {
+      return false;
+    }
+
+    if (!config.notification['slack:token']) {
+      return false;
+    }
+
+    return true;
+  };
+
   /*
   configSchema.statics.isInstalled = function(config)
   {

+ 111 - 30
lib/routes/admin.js

@@ -81,6 +81,89 @@ module.exports = function(crowi, app) {
   actions.app.settingUpdate = function(req, res) {
   };
 
+  // app.get('/admin/notification'               , admin.notification.index);
+  actions.notification = {};
+  actions.notification.index = function(req, res) {
+    var config = crowi.getConfig();
+    var slackSetting = Config.setupCofigFormData('notification', config);
+    var hasSlackConfig = Config.hasSlackConfig(config);
+    var hasSlackToken = Config.hasSlackToken(config);
+    var slack = crowi.slack;
+    var slackAuthUrl = '';
+
+    if (!Config.hasSlackConfig(req.config)) {
+      slackSetting['slack:clientId'] = '';
+      slackSetting['slack:clientSecret'] = '';
+    } else {
+      debug('slack is:', slack);
+      slackAuthUrl = slack.getAuthorizeURL();
+    }
+
+    if (req.session.slackSetting) {
+      slackSetting = req.session.slackSetting;
+      req.session.slackSetting = null;
+    }
+
+    return res.render('admin/notification', {
+      slackSetting,
+      hasSlackConfig,
+      hasSlackToken,
+      slackAuthUrl
+    });
+  };
+
+  // app.post('/admin/notification/slackSetting' , admin.notification.slackauth);
+  actions.notification.slackSetting = function(req, res) {
+    var slackSetting = req.form.slackSetting;
+
+    req.session.slackSetting = slackSetting;
+    if (req.form.isValid) {
+      Config.updateNamespaceByArray('notification', slackSetting, function(err, config) {
+        Config.updateConfigCache('notification', config);
+        req.session.slackSetting = null;
+
+        crowi.setupSlack().then(function() {
+          return res.redirect('/admin/notification');
+        });
+      });
+    } else {
+      req.flash('errorMessage', req.form.errors);
+      return res.redirect('/admin/notification');
+    }
+  };
+
+  // app.get('/admin/notification/slackAuth'     , admin.notification.slackauth);
+  actions.notification.slackAuth = function(req, res) {
+    var code = req.query.code;
+    var config = crowi.getConfig();
+
+    if (!code || !Config.hasSlackConfig(req.config)) {
+      return res.redirect('/admin/notification');
+    }
+
+    var slack = crowi.slack;
+    var bot = slack.createBot();
+    bot.api.oauth.access({code}, function(err, data) {
+      debug('oauth response', err, data);
+      if (!data.ok || !data.access_token) {
+        req.flash('errorMessage', ['Failed to fetch access_token. Please do connect again.']);
+        return res.redirect('/admin/notification');
+      } else {
+        Config.updateNamespaceByArray('notification', {'slack:token': data.access_token}, function(err, config) {
+          if (err) {
+            req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
+          } else {
+            Config.updateConfigCache('notification', config);
+            req.flash('successMessage', ['Successfully Connected!']);
+          }
+
+          slack.createBot();
+          return res.redirect('/admin/notification');
+        });
+      }
+    });
+  };
+
   actions.user = {};
   actions.user.index = function(req, res) {
     var page = parseInt(req.query.page) || 1;
@@ -193,44 +276,33 @@ 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']
-    });
+    //debug(req.query.code);
+    //var Botkit = require('botkit');
+    //var controller = Botkit.slackbot({debug: true});
+    //var config = crowi.getConfig();
+
+    //controller.configureSlackApp({
+    //  clientId: '2462768682.25922619424',
+    //  clientSecret: '2bbbc1c5d355e128ffd009a5d57629b9',
+    //  redirectUri: config.crowi['app:url'] + '/slackauth',
+    //  scopes: ['chat:write:bot']
+    //});
 
-    var bot = controller.spawn();
-    bot.api.oauth.access({code: req.query.code}, function (err, response) {
-      debug(err, response);
-    });
+    //var bot = controller.spawn();
+    //bot.api.oauth.access({code: req.query.code}, function (err, response) {
+    //  debug(err, response);
+    //});
+    //var accessToken = 'xoxp-2462768682-2462768686-25940861495-3e4cc9168a';
+    //var accessToken = 'xoxp-2462768682-2462768686-26103706022-46943fb5ec';
     //
     // access_token: '',
     // scope: 'identify,chat:write:bot',
     // team_name: '',
     // team_id: '' }
-    //var bot = controller.spawn({token: ''});
+    //var bot = controller.spawn({token: accessToken});
     //bot.api.chat.postMessage({
-    //  channel: '#xtest',
+    //  channel: '#xtest2',
     //  username: 'Crowi',
     //  text: '/hoge/fuga/piyo is updated.',
     //  attachments: [
@@ -277,6 +349,15 @@ module.exports = function(crowi, app) {
     }
   };
 
+  // app.post('/_api/admin/notifications.add'    , admin.api.notificationAdd);
+  actions.api.notificationAdd = function(req, res) {
+  };
+
+  // app.post('/_api/admin/notifications.remove' , admin.api.notificationRemove);
+  actions.api.notificationRemove = function(req, res) {
+  };
+
+
   function saveSetting(req, res, form)
   {
     Config.updateNamespaceByArray('crowi', form, function(err, config) {

+ 7 - 3
lib/routes/index.js

@@ -43,6 +43,13 @@ module.exports = function(crowi, app) {
   app.post('/_api/admin/settings/google', loginRequired(crowi, app) , middleware.adminRequired() , form.admin.google, admin.api.appSetting);
   app.post('/_api/admin/settings/fb'    , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.fb , admin.api.appSetting);
 
+  // notification admin
+  app.get('/admin/notification'              , loginRequired(crowi, app) , middleware.adminRequired() , admin.notification.index);
+  app.post('/admin/notification/slackSetting', loginRequired(crowi, app) , middleware.adminRequired() , form.admin.slackSetting, admin.notification.slackSetting);
+  app.get('/admin/notification/slackAuth'    , loginRequired(crowi, app) , middleware.adminRequired() , admin.notification.slackAuth);
+  app.post('/_api/admin/notification.add'    , loginRequired(crowi, app) , middleware.adminRequired() , admin.api.notificationAdd);
+  app.post('/_api/admin/notification.remove' , loginRequired(crowi, app) , middleware.adminRequired() , admin.api.notificationRemove);
+
   app.get('/admin/users'                , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.index);
   app.post('/admin/user/invite'         , form.admin.userInvite ,  loginRequired(crowi, app) , middleware.adminRequired() , admin.user.invite);
   app.post('/admin/user/:id/makeAdmin'  , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.makeAdmin);
@@ -52,9 +59,6 @@ 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);

+ 12 - 1
lib/service/notification.js

@@ -1,9 +1,20 @@
 'use strict';
 
-function Notification ()
+function Notification (crowi)
 {
+  this.crowi = crowi;
+  this.config = crowi.getConfig();
 }
 
+Notification.prototype.hasSlackConfig = function()
+{
+  if (!this.config.notification['slack']) {
+    return false;
+  }
+
+  //var config = ;
+};
+
 Notification.prototype.noitfyByEmail = function()
 {
 };

+ 75 - 0
lib/util/slack.js

@@ -0,0 +1,75 @@
+/**
+ * slack
+ */
+
+module.exports = function(crowi) {
+  'use strict';
+
+  var debug = require('debug')('crowi:util:slack'),
+    Config = crowi.model('Config'),
+    Botkit = require('botkit'),
+    slack = {};
+  slack.controller = undefined;
+
+  slack.createBot = function() {
+    var bot;
+    var config = crowi.getConfig();
+
+    if (!slack.controller) {
+      slack.configureSlackApp();
+    }
+
+    if (!slack.controller) {
+      return false;
+    }
+
+    if (Config.hasSlackToken(config)) {
+      bot = slack.controller.spawn({token: config.notification['slack:token']});
+    } else {
+      bot = slack.controller.spawn();
+    }
+
+    return bot;
+  };
+
+  slack.configureSlackApp = function ()
+  {
+    var config = crowi.getConfig();
+    if (Config.hasSlackConfig(config)) {
+      slack.controller = Botkit.slackbot();
+      slack.controller.configureSlackApp({
+        clientId: config.notification['slack:clientId'],
+        clientSecret: config.notification['slack:clientSecret'],
+        redirectUri: slack.getSlackAuthCallbackUrl(),
+        scopes: ['chat:write:bot']
+      });
+
+      return true;
+    }
+
+    return false;
+  }
+
+  // hmmm
+  slack.getSlackAuthCallbackUrl = function()
+  {
+    var config = crowi.getConfig();
+    // Web アクセスがきてないと app:url がセットされないので crowi.setupSlack 時にはできない
+    // cli, bot 系作るときに問題なりそう
+    return (config.crowi['app:url'] || '') + '/admin/notification/slackAuth';
+  }
+
+  slack.getAuthorizeURL = function () {
+    if (!slack.controller) {
+      slack.configureSlackApp();
+    }
+
+    if (!slack.controller) {
+      return '';
+    }
+
+    return slack.controller.getAuthorizeURL();
+  }
+
+  return slack;
+};

+ 1 - 1
lib/views/admin/app.html

@@ -29,6 +29,7 @@
       <ul class="nav nav-pills nav-stacked">
         <li><a href="/admin"><i class="fa fa-cube"></i> Wiki管理トップ</a></li>
         <li class="active"><a href="/admin/app"><i class="fa fa-gears"></i> アプリ設定</a></li>
+        <li><a href="/admin/notification"><i class="fa fa-bell"></i> 通知設定</a></li>
         <li><a href="/admin/users"><i class="fa fa-users"></i> ユーザー管理</a></li>
       </ul>
     </div>
@@ -43,7 +44,6 @@
             <input class="form-control" type="text" name="settingForm[app:title]" value="{{ settingForm['app:title'] }}">
 
             <p class="help-block">ヘッダーやHTMLタイトルに使用されるWikiの名前を変更できます。</p>
-            </p>
           </div>
         </div>
 

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

@@ -15,6 +15,7 @@
       <ul class="nav nav-pills nav-stacked">
         <li class="active"><a href="/admin"><i class="fa fa-cube"></i> Wiki管理トップ</a></li>
         <li><a href="/admin/app"><i class="fa fa-gears"></i> アプリ設定</a></li>
+        <li><a href="/admin/notification"><i class="fa fa-bell"></i> 通知設定</a></li>
         <li><a href="/admin/users"><i class="fa fa-users"></i> ユーザー管理</a></li>
       </ul>
     </div>

+ 127 - 0
lib/views/admin/notification.html

@@ -0,0 +1,127 @@
+{% extends '../layout/2column.html' %}
+
+{% block html_title %}通知設定 · {{ path }}{% endblock %}
+
+{% block content_head %}
+<header id="page-header">
+  <h1 class="title" id="">通知設定</h1>
+</header>
+{% endblock %}
+
+{% block content_main %}
+<div class="content-main">
+  <div class="row">
+    <div class="col-md-3">
+      <ul class="nav nav-pills nav-stacked">
+        <li><a href="/admin"><i class="fa fa-cube"></i> Wiki管理トップ</a></li>
+        <li><a href="/admin/app"><i class="fa fa-gears"></i> アプリ設定</a></li>
+        <li class="active"><a href="/admin/notification"><i class="fa fa-bell"></i> 通知設定</a></li>
+        <li><a href="/admin/users"><i class="fa fa-users"></i> ユーザー管理</a></li>
+      </ul>
+    </div>
+    <div class="col-md-9">
+
+      <ul class="nav nav-tabs">
+        <li class="active"><a href="#slack" data-toggle="tab"><i class="fa fa-slack"></i> Slack</a></li>
+      </ul>
+
+      <br>
+
+      {% set smessage = req.flash('successMessage') %}
+      {% if smessage.length %}
+      <div class="alert alert-success">
+        {% for e in smessage %}
+          {{ e }}<br>
+        {% endfor %}
+      </div>
+      {% endif %}
+
+      {% set emessage = req.flash('errorMessage') %}
+      {% if emessage.length %}
+      <div class="alert alert-danger">
+        {% for e in emessage %}
+        {{ e }}<br>
+        {% endfor %}
+      </div>
+      {% endif %}
+
+      <form action="/admin/notification/slackSetting" method="post" class="form-horizontal" id="appSettingForm" role="form">
+      <fieldset>
+        <legend>Slack App Configuration</legend>
+        <div class="form-group">
+          <label for="slackSetting[slack:clientId]" class="col-xs-3 control-label">clientId</label>
+          <div class="col-xs-6">
+            <input class="form-control" type="text" name="slackSetting[slack:clientId]" value="{{ slackSetting['slack:clientId'] }}">
+          </div>
+        </div>
+
+        <div class="form-group">
+          <label for="slackSetting[slack:clientSecret]" class="col-xs-3 control-label">clientSecret</label>
+          <div class="col-xs-6">
+            <input class="form-control" type="text" name="slackSetting[slack:clientSecret]" value="{{ slackSetting['slack:clientSecret'] }}">
+          </div>
+        </div>
+
+        <div class="form-group">
+          <div class="col-xs-offset-3 col-xs-6">
+            <button type="submit" class="btn btn-primary">Submit</button>
+          </div>
+        </div>
+      </fieldset>
+      </form>
+
+      {% if hasSlackConfig %}
+      <div class="text-center">
+        {% if hasSlackToken %}
+        <p>Crowi and Slack is already <strong>connected</strong>. You can re-connect to refresh and overwirte the token with your Slack account.</p>
+        <a class="btn btn-default" href="{{ slackAuthUrl }}">
+          <i class="fa fa-slack"></i> Reconnect to Slack
+        </a>
+        {% else %}
+        <p>Slack clientId and clientSecret is configured. Now, you can connect with Slack.</p>
+        <a class="btn btn-primary" href="{{ slackAuthUrl }}">
+          <i class="fa fa-slack"></i> Connect to Slack
+        </a>
+        {% endif %}
+      </div>
+      {% endif %}
+
+      {% if not hasSlackConfig %}
+      <h3>How to configure Slack app for Crowi</h3>
+      <p>
+      Register Crowi as a Slack application, the notification feature for Slack can be enabled.
+      </p>
+      <h4>1. Register Slack App</h4>
+      <p>
+      Create App from this link, and fill the form out as below:
+      </p>
+      <dl class="dl-horizontal">
+        <dt>App Name</dt> <dd><code>Crowi</code> </dd>
+        <dt>Icon</dt> <dd>Upload this image as the icon (Free to download and use it) =&gt; <img src=""></dd>
+        <dt>Short description</dt> <dd><code>Crowi's Slack Notification Integration</code> </dd>
+        <dt>Long description</dt> <dd><code>Crowi's Slack Notification Integration</code> </dd>
+      </dl>
+      <p>
+      and <strong>Save</strong> it.
+      </p>
+
+      <h4>2. Get <code>clientId</code> and <code>clientSecret</code></h4>
+      <h4>3. Configure Slack on this notification setting screen</h4>
+      {% endif %}
+
+
+
+    </div>
+  </div>
+
+</div>
+{% endblock content_main %}
+
+{% block content_footer %}
+{% endblock content_footer %}
+
+{% block footer %}
+{% endblock footer %}
+
+
+

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

@@ -29,6 +29,7 @@
       <ul class="nav nav-pills nav-stacked">
         <li><a href="/admin"><i class="fa fa-cube"></i> Wiki管理トップ</a></li>
         <li><a href="/admin/app"><i class="fa fa-gears"></i> アプリ設定</a></li>
+        <li><a href="/admin/notification"><i class="fa fa-bell"></i> 通知設定</a></li>
         <li class="active"><a href="/admin/users"><i class="fa fa-users"></i> ユーザー管理</a></li>
       </ul>
     </div>

+ 1 - 1
package.json

@@ -34,7 +34,7 @@
     "bluebird": "~3.0.5",
     "body-parser": "~1.14.1",
     "bootstrap-sass": "~3.3.6",
-    "botkit": "0.0.8",
+    "botkit": "git+https://github.com/sotarok/botkit.git#fix-undefined-function-callapiwithouttoken",
     "browserify": "~12.0.1",
     "cli": "~0.6.0",
     "connect-flash": "~0.1.1",