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

Add admin for notification settings

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

+ 13 - 0
gulpfile.js

@@ -61,6 +61,10 @@ var js = {
     'resource/js/crowi-form.js'
   ],
   formDist: dirs.jsDist + '/crowi-form.js',
+  adminSrc: [
+    'resource/js/crowi-admin.js'
+  ],
+  adminDist: dirs.jsDist + '/crowi-admin.js',
   clientWatch: ['resource/js/**/*.js'],
   watch: ['test/**/*.test.js', 'app.js', 'lib/**/*.js'],
   lint: ['app.js', 'lib/**/*.js'],
@@ -90,6 +94,10 @@ gulp.task('js:concat', ['js:browserify'], function() {
     .pipe(concat('crowi-reveal.js'))
     .pipe(gulp.dest(dirs.jsDist));
 
+  gulp.src(js.adminSrc)
+    .pipe(concat('crowi-admin.js'))
+    .pipe(gulp.dest(dirs.jsDist));
+
   gulp.src(js.formSrc)
     .pipe(concat('crowi-form.js'))
     .pipe(gulp.dest(dirs.jsDist));
@@ -110,6 +118,11 @@ gulp.task('js:min', ['js:concat'], function() {
     .pipe(rename({suffix: '.min'}))
     .pipe(gulp.dest(dirs.jsDist));
 
+  gulp.src(js.adminDist)
+    .pipe(uglify())
+    .pipe(rename({suffix: '.min'}))
+    .pipe(gulp.dest(dirs.jsDist));
+
   return gulp.src(js.dist)
     .pipe(uglify())
     .pipe(rename({suffix: '.min'}))

+ 1 - 1
lib/models/index.js

@@ -7,5 +7,5 @@ module.exports = {
   Bookmark: require('./bookmark'),
   Comment: require('./comment'),
   Attachment: require('./attachment'),
-  Notification: require('./notification'),
+  UpdatePost: require('./updatePost'),
 };

+ 0 - 97
lib/models/notification.js

@@ -1,97 +0,0 @@
-module.exports = function(crowi) {
-  var debug = require('debug')('crowi:models:notification')
-    , mongoose = require('mongoose')
-    , ObjectId = mongoose.Schema.Types.ObjectId
-  ;
-
-  // TODO: slack 以外の対応
-  notificationSchema = new mongoose.Schema({
-    pathPattern: { type: String, required: true },
-    patternPrefix:  { type: String, required: true },
-    patternPrefix2: { type: String, required: true },
-    channel: { type: String, required: true },
-    provider: { type: String, required: true },
-    creator: { type: ObjectId, ref: 'User', index: true  },
-    createdAt: { type: Date, default: Date.now }
-  });
-
-  function createPrefixesbyPathPattern (pathPattern)
-  {
-    return ['*', '*'];
-  }
-
-  notificationSchema.statics.findSettingsByPath = function(path)
-  {
-    var Notification = this;
-
-    return new Promise(function(resolve, reject) {
-    });
-  };
-
-  notificationSchema.statics.findAll = function(offset)
-  {
-    var Notification = this;
-    var offset = offset || 0;
-
-    return new Promise(function(resolve, reject) {
-      Notification
-        .find()
-        .sort({'createdAt': 1})
-        .populate('creator')
-        .exec(function(err, data) {
-          if (err) {
-            return reject(err);
-          }
-
-          if (data.length < 1) {
-            return resolve([]);
-          }
-
-          return resolve(data);
-        });
-    });
-  };
-
-  notificationSchema.statics.create = function(pathPatter, channel, user)
-  {
-    var Notification = this;
-    var provider = 'slack'; // now slack only
-
-    var notif = new Notification;
-    notif.pathPattern = pathPattern;
-    notif.channel = Notification.nomalizeChannelName(channel);
-    notif.provider = provider;
-    notif.creator = user;
-    notif.createdAt = Date.now();
-
-    return new Promise(function(resolve, reject) {
-      notif.save(function(err, data) {
-        if (err) {
-          debug('Error on saving notification.', err);
-          return reject(err);
-        }
-        debug('notification saved.', data);
-        return resolve(data);
-      });
-    });
-  };
-
-  notificationSchema.statics.remove = function(id)
-  {
-    var Notification = this;
-
-    return new Promise(function(resolve, reject) {
-      Notification.findOneAndRemove({_id: id}, function(err, data) {
-        if (err) {
-          debug('Notification.findOneAndRemove failed', err);
-          return reject(err);
-        }
-
-        return resolve(data);
-      });
-    });
-  };
-
-  return mongoose.model('Attachment', attachmentSchema);
-};
-

+ 152 - 0
lib/models/updatePost.js

@@ -0,0 +1,152 @@
+/**
+ * This is the setting for notify to 3rd party tool (like Slack).
+ */
+module.exports = function(crowi) {
+  var debug = require('debug')('crowi:models:updatePost')
+    , mongoose = require('mongoose')
+    , ObjectId = mongoose.Schema.Types.ObjectId
+  ;
+
+  // TODO: slack 以外の対応
+  updatePostSchema = new mongoose.Schema({
+    pathPattern: { type: String, required: true },
+    patternPrefix:  { type: String, required: true },
+    patternPrefix2: { type: String, required: true },
+    channel: { type: String, required: true },
+    provider: { type: String, required: true },
+    creator: { type: ObjectId, ref: 'User', index: true  },
+    createdAt: { type: Date, default: Date.now }
+  });
+
+  updatePostSchema.statics.normalizeChannelName = function(channel)
+  {
+    return channel.replace(/(#|,)/g, '');
+  }
+
+  updatePostSchema.statics.createPrefixesByPathPattern = function(pathPattern)
+  {
+    var patternPrefix = ['*', '*'];
+
+    // not begin with slash
+    if (!pathPattern.match(/^\/.+/)) {
+      return patternPrefix;
+    }
+
+    var pattern = pathPattern.split('/');
+    pattern.shift();
+    if (pattern[0] && pattern[0] != '*') {
+      patternPrefix[0] = pattern[0];
+    }
+
+    if (pattern[1] && pattern[1] != '*') {
+      patternPrefix[1] = pattern[1];
+    }
+    return patternPrefix;
+  }
+
+  updatePostSchema.statics.getRegExpByPattern = function(pattern)
+  {
+    var reg = pattern;
+    if (!reg.match(/^\/.*/)) {
+      reg = '/*' + reg + '*';
+    }
+    reg = '^' + reg;
+    reg = reg.replace(/(\*)/g, '.*');
+
+    return new RegExp(reg);
+  }
+
+  updatePostSchema.statics.findSettingsByPath = function(path)
+  {
+    var UpdatePost = this;
+    var prefixes = UpdatePost.createPrefixesByPathPattern(path);
+
+    return UpdatePost.find({$or: [
+      {patternPrefix: prefixes[0], patternPrefix2: prefixes[1]},
+      {patternPrefix: '*', patternPrefix2: '*'},
+      {patternPrefix: prefixes[0], patternPrefix2: '*'},
+      {patternPrefix: '*', patternPrefix2: prefixes[1]},
+    ]})
+    .then(function(settings) {
+      if (settings.length <= 0) {
+        return Promise.resolve(settings);
+      }
+
+      settings = settings.filter(function(setting) {
+        var patternRegex = UpdatePost.getRegExpByPattern(setting.pathPattern);
+        return pathPattern.test(path);
+      });
+
+      return Promise.resolve(settings);
+    });
+  };
+
+  updatePostSchema.statics.findAll = function(offset)
+  {
+    var UpdatePost = this;
+    var offset = offset || 0;
+
+    return new Promise(function(resolve, reject) {
+      UpdatePost
+        .find()
+        .sort({'createdAt': 1})
+        .populate('creator')
+        .exec(function(err, data) {
+          if (err) {
+            return reject(err);
+          }
+
+          if (data.length < 1) {
+            return resolve([]);
+          }
+
+          return resolve(data);
+        });
+    });
+  };
+
+  updatePostSchema.statics.create = function(pathPattern, channel, user)
+  {
+    var UpdatePost = this;
+    var provider = 'slack'; // now slack only
+
+    var prefixes = UpdatePost.createPrefixesByPathPattern(pathPattern);
+    var notif = new UpdatePost;
+    notif.pathPattern = pathPattern;
+    notif.patternPrefix = prefixes[0];
+    notif.patternPrefix2 = prefixes[1];
+    notif.channel = UpdatePost.normalizeChannelName(channel);
+    notif.provider = provider;
+    notif.creator = user;
+    notif.createdAt = Date.now();
+
+    return new Promise(function(resolve, reject) {
+      notif.save(function(err, doc) {
+       if (err) {
+         return reject(err);
+       }
+
+       return resolve(doc);
+      });
+    });
+  };
+
+  updatePostSchema.statics.remove = function(id)
+  {
+    var UpdatePost = this;
+
+    return new Promise(function(resolve, reject) {
+      UpdatePost.findOneAndRemove({_id: id}, function(err, data) {
+        if (err) {
+          debug('UpdatePost.findOneAndRemove failed', err);
+          return reject(err);
+        }
+
+        return resolve(data);
+      });
+    });
+  };
+
+  return mongoose.model('UpdatePost', updatePostSchema);
+};
+

+ 41 - 7
lib/routes/admin.js

@@ -6,6 +6,7 @@ module.exports = function(crowi, app) {
     , Page = models.Page
     , User = models.User
     , Config = models.Config
+    , ApiResponse = require('../util/apiResponse')
 
     , MAX_PAGE_LIST = 5
     , actions = {};
@@ -84,6 +85,7 @@ module.exports = function(crowi, app) {
   actions.notification = {};
   actions.notification.index = function(req, res) {
     var config = crowi.getConfig();
+    var UpdatePost = crowi.model('UpdatePost');
     var slackSetting = Config.setupCofigFormData('notification', config);
     var hasSlackConfig = Config.hasSlackConfig(config);
     var hasSlackToken = Config.hasSlackToken(config);
@@ -102,11 +104,15 @@ module.exports = function(crowi, app) {
       req.session.slackSetting = null;
     }
 
-    return res.render('admin/notification', {
-      slackSetting,
-      hasSlackConfig,
-      hasSlackToken,
-      slackAuthUrl
+    UpdatePost.findAll()
+    .then(function(settings) {
+      return res.render('admin/notification', {
+        settings,
+        slackSetting,
+        hasSlackConfig,
+        hasSlackToken,
+        slackAuthUrl
+      });
     });
   };
 
@@ -254,11 +260,12 @@ module.exports = function(crowi, app) {
     });
   };
 
-  actions.user.remove= function(req, res) {
+  actions.user.remove = function(req, res) {
     // 未実装
     return res.redirect('/admin/users');
   };
 
+  // これやったときの relation の挙動未確認
   actions.user.removeCompletely = function(req, res) {
     // ユーザーの物理削除
     var id = req.params.id;
@@ -349,12 +356,39 @@ module.exports = function(crowi, app) {
 
   // app.post('/_api/admin/notifications.add'    , admin.api.notificationAdd);
   actions.api.notificationAdd = function(req, res) {
+    var UpdatePost = crowi.model('UpdatePost');
+    var pathPattern = req.body.pathPattern;
+    var channel = req.body.channel;
+
+    debug('notification.add', pathPattern, channel);
+    UpdatePost.create(pathPattern, channel, req.user)
+    .then(function(doc) {
+      debug('Successfully save updatePost', doc);
+
+      // fixme: うーん
+      doc.creator = doc.creator._id.toString();
+      return res.json(ApiResponse.success({updatePost: doc}));
+    }).catch(function(err) {
+      debug('Failed to save updatePost', err);
+      return res.json(ApiResponse.error());
+    });
   };
 
   // app.post('/_api/admin/notifications.remove' , admin.api.notificationRemove);
   actions.api.notificationRemove = function(req, res) {
-  };
+    var UpdatePost = crowi.model('UpdatePost');
+    var id = req.body.id;
 
+    UpdatePost.remove(id)
+    .then(function() {
+      debug('Successfully remove updatePost');
+
+      return res.json(ApiResponse.success({}));
+    }).catch(function(err) {
+      debug('Failed to remove updatePost', err);
+      return res.json(ApiResponse.error());
+    });
+  };
 
   function saveSetting(req, res, form)
   {

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

@@ -1,4 +1,4 @@
-{% extends '../layout/2column.html' %}
+{% extends '../layout/admin.html' %}
 
 {% block html_title %}アプリ設定 · {% endblock %}
 
@@ -328,7 +328,3 @@
 {% block content_footer %}
 {% endblock content_footer %}
 
-{% block footer %}
-{% endblock footer %}
-
-

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

@@ -1,4 +1,4 @@
-{% extends '../layout/2column.html' %}
+{% extends '../layout/admin.html' %}
 
 {% block html_title %}Wiki管理 · {{ path }}{% endblock %}
 
@@ -35,7 +35,3 @@
 {% block content_footer %}
 {% endblock content_footer %}
 
-{% block footer %}
-{% endblock footer %}
-
-

+ 12 - 13
lib/views/admin/notification.html

@@ -1,11 +1,13 @@
-{% extends '../layout/2column.html' %}
+{% extends '../layout/admin.html' %}
 
 {% block html_title %}通知設定 · {{ path }}{% endblock %}
 
 {% block content_head %}
-<header id="page-header">
-  <h1 class="title" id="">通知設定</h1>
-</header>
+<div class="header-wrap">
+  <header id="page-header">
+    <h1 class="title" id="">通知設定</h1>
+  </header>
+</div>
 {% endblock %}
 
 {% block content_main %}
@@ -95,7 +97,7 @@
           <th>Channel</th>
           <th>Operation</th>
         </thead>
-        <tbody>
+        <tbody class="admin-notif-list">
           <form id="slackNotificationForm">
           <tr>
             <td>
@@ -116,17 +118,17 @@
           </tr>
           </form>
 
-          {% for notif in notifs %}
-          <tr>
+          {% for notif in settings %}
+          <tr class="admin-notif-row" data-updatepost-id="{{ notif._id.toString() }}">
             <td>
-              {{ notif.pattern }}
+              {{ notif.pathPattern }}
             </td>
             <td>
               {{ notif.channel }}
             </td>
             <td>
-              <form>
-                <input type="hidden" name="id" value="">
+              <form class="admin-remove-updatepost">
+                <input type="hidden" name="id" value="{{ notif._id.toString() }}">
                 <input type="submit" value="Delete" class="btn btn-default">
               </form>
             </td>
@@ -172,8 +174,5 @@
 {% block content_footer %}
 {% endblock content_footer %}
 
-{% block footer %}
-{% endblock footer %}
-
 
 

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

@@ -1,4 +1,4 @@
-{% extends '../layout/2column.html' %}
+{% extends '../layout/admin.html' %}
 
 {% block html_title %}ユーザー管理 · {% endblock %}
 
@@ -216,8 +216,4 @@
 {% block content_footer %}
 {% endblock content_footer %}
 
-{% block footer %}
-{% endblock footer %}
-
-
 

+ 6 - 0
lib/views/layout/admin.html

@@ -0,0 +1,6 @@
+{% extends '2column.html' %}
+
+{% block footer %}
+  <script src="/js/crowi-admin{% if env  == 'production' %}.min{% endif %}.js"></script>
+{% endblock footer %}
+

+ 24 - 0
resource/js/crowi-admin.js

@@ -0,0 +1,24 @@
+$(function() {
+  var UpdatePost = {};
+
+  $('#slackNotificationForm').on('submit', function(e) {
+    $.post('/_api/admin/notification.add', $(this).serialize(), function(res) {
+      if (res.ok) {
+        // TODO Fix
+        location.reload();
+      }
+    });
+
+    return false;
+  });
+
+  $('form.admin-remove-updatepost').on('submit', function(e) {
+    $.post('/_api/admin/notification.remove', $(this).serialize(), function(res) {
+      if (res.ok) {
+        // TODO Fix
+        location.reload();
+      }
+    });
+    return false;
+  });
+});

+ 54 - 0
test/models/updatePost.test.js

@@ -0,0 +1,54 @@
+var chai = require('chai')
+  , expect = chai.expect
+  , sinon = require('sinon')
+  , sinonChai = require('sinon-chai')
+  , Promise = require('bluebird')
+  , utils = require('../utils.js')
+  ;
+chai.use(sinonChai);
+
+describe('UpdatePost', function () {
+  var UpdatePost = utils.models.UpdatePost,
+    conn   = utils.mongoose.connection;
+
+  describe('.createPrefixesByPathPattern', function () {
+    context('with a path', function() {
+      it('should return right patternPrfixes', function(done) {
+        expect(UpdatePost.createPrefixesByPathPattern('/*')).to.deep.equal(['*', '*']);
+        expect(UpdatePost.createPrefixesByPathPattern('/user/*/日報*')).to.deep.equal(['user', '*']);
+        expect(UpdatePost.createPrefixesByPathPattern('/project/hoge/*')).to.deep.equal(['project', 'hoge']);
+        expect(UpdatePost.createPrefixesByPathPattern('/*/MTG/*')).to.deep.equal(['*', 'MTG']);
+        expect(UpdatePost.createPrefixesByPathPattern('自己紹介')).to.deep.equal(['*', '*']);
+        expect(UpdatePost.createPrefixesByPathPattern('/user/aoi/メモ/2016/02/10/xxx')).to.deep.equal(['user', 'aoi']);
+
+        done();
+      });
+    });
+  });
+
+  describe('.getRegExpByPattern', function () {
+    context('with a pattern', function() {
+      it('should return true', function(done) {
+        expect(UpdatePost.getRegExpByPattern('/*')).to.deep.equal(/^\/.*/);
+        expect(UpdatePost.getRegExpByPattern('/user/*/日報*')).to.deep.equal(/^\/user\/.*\/日報.*/);
+        expect(UpdatePost.getRegExpByPattern('/project/hoge/*')).to.deep.equal(/^\/project\/hoge\/.*/);
+        expect(UpdatePost.getRegExpByPattern('/*/MTG/*')).to.deep.equal(/^\/.*\/MTG\/.*/);
+        expect(UpdatePost.getRegExpByPattern('自己紹介')).to.deep.equal(/^\/.*自己紹介.*/);
+        expect(UpdatePost.getRegExpByPattern('\/user\/aoi\/メモ\/2016\/02\/10\/xxx')).to.deep.equal(/^\/user\/aoi\/メモ\/2016\/02\/10\/xxx/);
+        done();
+      });
+    });
+  });
+
+  describe('.normalizeChannelName', function () {
+    context('with a channel name', function() {
+      it('should return true', function(done) {
+        expect(UpdatePost.normalizeChannelName('#pj-hoge')).to.be.equal('pj-hoge');
+        expect(UpdatePost.normalizeChannelName('pj-hoge')).to.be.equal('pj-hoge');
+
+        done();
+      });
+    });
+  });
+});
+

+ 1 - 0
test/utils.js

@@ -47,6 +47,7 @@ models.Page     = require(MODEL_DIR + '/page.js')(crowi);
 models.User     = require(MODEL_DIR + '/user.js')(crowi);
 models.Config   = require(MODEL_DIR + '/config.js')(crowi);
 models.Revision = require(MODEL_DIR + '/revision.js')(crowi);
+models.UpdatePost = require(MODEL_DIR + '/updatePost.js')(crowi);
 
 crowi.models = models;