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

# Feature/196, 198, 199 Grouping users
* Prepare group model.(Fix group model with reviewed feed back.)
* Fix routes and index for ↑.

Tatsuya Ise 8 лет назад
Родитель
Сommit
29855bf163

+ 1 - 1
lib/models/index.js

@@ -3,7 +3,7 @@
 module.exports = {
   Page: require('./page'),
   User: require('./user'),
-  Group: require('./group'),
+  UserGroup: require('./userGroup'),
   Revision: require('./revision'),
   Bookmark: require('./bookmark'),
   Comment: require('./comment'),

+ 205 - 0
lib/models/userGroup.js

@@ -0,0 +1,205 @@
+module.exports = function(crowi) {
+  var debug = require('debug')('crowi:models:userGroup')
+    , mongoose = require('mongoose')
+    , mongoosePaginate = require('mongoose-paginate')
+    , uniqueValidator = require('mongoose-unique-validator')
+    , ObjectId = mongoose.Schema.Types.ObjectId
+
+    , USER_GROUP_PUBLIC_FIELDS = '_id image name createdAt'
+
+    , PAGE_ITEMS = 50
+
+    , userGroupSchema;
+
+  userGroupSchema = new mongoose.Schema({
+    userGroupId: String,
+    image: String,
+    name: { type: String, required: true, unique: true, index: true },
+    createdAt: { type: Date, default: Date.now },
+  });
+  userGroupSchema.plugin(mongoosePaginate);
+  userGroupSchema.plugin(uniqueValidator);
+
+
+  // TBD: グループ画像の更新
+  // userGroupSchema.methods.updateImage = function(image, callback) {
+  //   this.image = image;
+  //   this.save(function(err, userGroupData) {
+  //     return callback(err, userGroupData);
+  //   });
+  // };
+
+  // TBD: グループ画像の削除
+  // userGroupSchema.methods.deleteImage = function(callback) {
+  //   return this.updateImage(null, callback);
+  // };
+
+  // グループ公開情報のフィルター
+  userGroupSchema.statics.filterToPublicFields = function(userGroup) {
+    debug('UserGroup is', typeof userGroup, userGroup);
+    if (typeof userGroup !== 'object' || !userGroup._id) {
+      return userGroup;
+    }
+
+    var filteredGroup = {};
+    var fields = USER_GROUP_PUBLIC_FIELDS.split(' ');
+    for (var i = 0; i < fields.length; i++) {
+      var key = fields[i];
+      if (userGroup[key]) {
+        filteredGroup[key] = userGroup[key];
+      }
+    }
+
+    return filteredGroup;
+  };
+
+  // TBD: グループ検索
+  // userGroupSchema.statics.findGroups = function(options, callback) {
+  //   var sort = options.sort || {createdAt: 1};
+
+  //   this.find()
+  //     .sort(sort)
+  //     .skip(options.skip || 0)
+  //     .limit(options.limit || 21)
+  //     .exec(function (err, userGroupData) {
+  //       callback(err, userGroupData);
+  //     });
+
+  // };
+
+  // すべてのグループを取得(オプション指定可)
+  userGroupSchema.statics.findAllGroups = function(option) {
+    debug('NoErrorOccured');
+
+    var UserGroup = this;
+    var option = option || {}
+      , sort = option.sort || {createdAt: -1}
+      , fields = option.fields || USER_GROUP_PUBLIC_FIELDS
+      ;
+
+    return new Promise(function(resolve, reject) {
+      UserGroup
+        .find()
+        .select(fields)
+        .sort(sort)
+        .exec(function (err, userGroupData) {
+          if (err) {
+            return reject(err);
+          }
+
+          return resolve(userGroupData);
+        });
+    });
+  };
+
+  // TBD: IDによるグループ検索
+  // userGroupSchema.statics.findGroupsByIds = function(ids, option) {
+  //   var UserGroup = this;
+  //   var option = option || {}
+  //     , sort = option.sort || {createdAt: -1}
+  //     , fields = option.fields || USER_GROUP_PUBLIC_FIELDS
+  //     ;
+
+  //   return new Promise(function(resolve, reject) {
+  //     UserGroup
+  //       .find({ _id: { $in: ids }})
+  //       .select(fields)
+  //       .sort(sort)
+  //       .exec(function (err, userGroupData) {
+  //         if (err) {
+  //           return reject(err);
+  //         }
+
+  //         return resolve(userGroupData);
+  //       });
+  //   });
+  // };
+
+  // ページネーション利用のグループ検索
+  userGroupSchema.statics.findUserGroupsWithPagination = function(options, callback) {
+    var sort = options.sort || {name: 1, createdAt: 1};
+
+    // return callback(err, null);
+    this.paginate({ page: options.page || 1, limit: options.limit || PAGE_ITEMS }, function(err, result) {
+      if (err) {
+        debug('Error on pagination:', err);
+        return callback(err, null);
+      }
+
+      return callback(err, result);
+    }, { sortBy : sort });
+  };
+
+  // TBD: グループ名によるグループ検索
+  // userGroupSchema.statics.findUserGroupByName = function(name) {
+  //   var UserGroup = this;
+  //   return new Promise(function(resolve, reject) {
+  //     UserGroup.findOne({name: name}, function (err, userGroupData) {
+  //       if (err) {
+  //         return reject(err);
+  //       }
+
+  //       return resolve(userGroupData);
+  //     });
+  //   });
+  // };
+
+  // TBD: 登録可能グループ名確認
+  // userGroupSchema.statics.isRegisterableName = function(name, callback) {
+  //   var UserGroup = this;
+  //   var userGroupnameUsable = true;
+
+  //   this.findOne({name: name}, function (err, userGroupData) {
+  //     if (userGroupData) {
+  //       userGroupnameUsable = false;
+  //     }
+  //     return callback(userGroupnameUsable);
+  //   });
+  // };
+
+  // TBD: グループの完全削除
+  // userGroupSchema.statics.removeCompletelyById = function(id, callback) {
+  //   var UserGroup = this;
+  //   UserGroup.findById(id, function (err, userGroupData) {
+  //     if (!userGroupData) {
+  //       return callback(err, null);
+  //     }
+
+  //     debug('Removing userGroup:', userGroupData);
+
+  //     userGroupData.remove(function(err) {
+  //       if (err) {
+  //         return callback(err, null);
+  //       }
+
+  //       return callback(null, 1);
+  //     });
+  //   });
+  // };
+
+  // TBD: グループ生成(名前が要る)
+  userGroupSchema.statics.createGroupByName = function(name, callback) {
+    var UserGroup = this
+      , newUserGroup = new UserGroup();
+
+    newUserGroup.name = name;
+    newUserGroup.createdAt = Date.now();
+
+    newUserGroup.save(function(err, userGroupData) {
+      return callback(err, userGroupData);
+    });
+  };
+
+  // TBD: グループ画像パスの生成
+  // userGroupSchema.statics.createGroupPictureFilePath = function(userGroup, name) {
+  //   var ext = '.' + name.match(/(.*)(?:\.([^.]+$))/)[2];
+
+  //   return 'userGroup/' + userGroup._id + ext;
+  // };
+
+
+  userGroupSchema.statics.USER_GROUP_PUBLIC_FIELDS = USER_GROUP_PUBLIC_FIELDS;
+  userGroupSchema.statics.PAGE_ITEMS         = PAGE_ITEMS;
+
+  return mongoose.model('UserGroup', userGroupSchema);
+};

+ 24 - 6
lib/routes/admin.js

@@ -5,7 +5,7 @@ module.exports = function(crowi, app) {
     , models = crowi.models
     , Page = models.Page
     , User = models.User
-    , Group = models.Group
+    , UserGroup = models.UserGroup
     , Config = models.Config
     , PluginUtils = require('../plugins/plugin-utils')
     , pluginUtils = new PluginUtils()
@@ -472,20 +472,38 @@ module.exports = function(crowi, app) {
     });
   }
 
-  actions.group = {};
-  actions.group.index = function (req, res) {
+  actions.userGroup = {};
+  actions.userGroup.index = function (req, res) {
     var page = parseInt(req.query.page) || 1;
 
-    Group.findGroupsWithPagination({ page: page }, function (err, result) {
+    UserGroup.findUserGroupsWithPagination({ page: page }, function (err, result) {
       const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
 
-      return res.render('admin/groups', {
-        groups: result.docs,
+      return res.render('admin/user_groups', {
+        userGroups: result.docs,
         pager: pager
       });
     });
   };
 
+  // actions.userGroup.create = function (req, res) {
+  //   var form = req.form.createGroupForm;
+  //   // var toSendEmail = form.sendEmail || false;
+  //   // if (req.form.isValid) {
+  //     User.createUsersByInvitation(form.userGroupName, function (err, newUserGroup) {
+  //       if (err) {
+  //         req.flash('errorMessage', req.form.errors.join('\n'));
+  //       } else {
+  //         req.flash('createdUserGroup', newUserGroup);
+  //       }
+  //       return res.redirect('/admin/user_groups');
+  //     });
+  //   // } else {
+  //   //   req.flash('errorMessage', req.form.errors.join('\n'));
+  //   //   return res.redirect('/admin/user_groups');
+  //   // }
+  // };
+
   actions.api = {};
   actions.api.appSetting = function(req, res) {
     var form = req.form.settingForm;

+ 2 - 1
lib/routes/index.js

@@ -101,7 +101,8 @@ module.exports = function(crowi, app) {
   app.post('/_api/admin/users.resetPassword'  , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.resetPassword);
 
   // groups admin
-  app.get('/admin/groups', loginRequired(crowi, app), middleware.adminRequired(), admin.group.index);
+  app.get('/admin/user-groups'          , loginRequired(crowi, app), middleware.adminRequired(), admin.userGroup.index);
+  // app.post('/admin/user-groups/create'  , form.admin.userGroupCreate, loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroup.create);
 
   app.get('/me'                       , loginRequired(crowi, app) , me.index);
   app.get('/me/password'              , loginRequired(crowi, app) , me.password);

+ 121 - 0
lib/views/admin/user_groups.html

@@ -0,0 +1,121 @@
+{% extends '../layout/admin.html' %}
+
+{% block html_title %}グループ管理 · {% endblock %}
+
+{% block content_head %}
+<div class="header-wrap">
+  <header id="page-header">
+    <h1 class="title" id="">グループ管理</h1>
+  </header>
+</div>
+{% endblock %}
+
+{% block content_main %}
+<div class="content-main">
+  {% set smessage = req.flash('successMessage') %}
+  {% if smessage.length %}
+  <div class="alert alert-success">
+    {{ smessage }}
+  </div>
+  {% endif %}
+
+  {% set emessage = req.flash('errorMessage') %}
+  {% if emessage.length %}
+  <div class="alert alert-danger">
+    {{ emessage }}
+  </div>
+  {% endif %}
+
+  <div class="row">
+    <div class="col-md-3">
+      {% include './widget/menu.html' with {current: 'user_group'} %}
+    </div>
+
+    <div class="col-md-9">
+      <p>
+        <button  data-toggle="collapse" class="btn btn-default" href="#createGroupForm">新規グループの作成</button>
+      </p>
+      <form role="form" action="/admin/group/create" method="post">
+        <div id="createGroupForm" class="collapse">
+          <div class="form-group">
+            <label for="createGroupForm[userGroupName]">グループ名</label>
+            <textarea class="form-control" name="createGroupForm[userGroupName]" placeholder="例: Group1"></textarea>
+          </div>
+          <button type="submit" class="btn btn-primary">作成する</button>
+        </div>
+        <input type="hidden" name="_csrf" value="{{ csrf() }}">
+      </form>
+
+      {% set createdGroup = req.flash('createdGroup') %}
+      {% if createdGroup.length %}
+      <div class="modal fade in" id="createdGroupModal">
+        <div class="modal-dialog">
+          <div class="modal-content">
+
+            <div class="modal-header">
+              <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+              <h4 class="modal-title">グループを作成しました</h4>
+            </div>
+
+            <div class="modal-body">
+              <p>
+                作成したにユーザを追加してください
+              </p>
+
+              <pre>{% for cGroup in createdGroup %}{{ cGroup.name }}<br>{% endfor %}</pre>
+            </div>
+
+          </div><!-- /.modal-content -->
+        </div><!-- /.modal-dialog -->
+      </div><!-- /.modal -->
+      {% endif %}
+
+      <h2>グループ一覧</h2>
+
+      <table class="table table-hover table-striped table-bordered table-user-list">
+        <thead>
+          <tr>
+            <th width="100px">#</th>
+            <th>名前</th>
+            <th width="100px">作成日</th>
+            <th width="90px">操作</th>
+          </tr>
+        </thead>
+        <tbody>
+          {% for sGroup in userGroups %}
+          <tr>
+            <td>
+              <img src="{{ sGroup|picture }}" class="picture picture-rounded" />
+            </td>
+            <td>{{ sGroup.name }}</td>
+            <td>{{ sGroup.createdAt|date('Y-m-d', sGroup.createdAt.getTimezoneOffset()) }}</td>
+            <td>
+              <div class="btn-group admin-group-menu">
+                <button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
+                  編集
+                  <span class="caret"></span>
+                </button>
+                <ul class="dropdown-menu" role="menu">
+                  <li class="dropdown-header">編集メニュー</li>
+                  <li>
+                    <a href="">編集</a>
+                  </li>
+                </ul>
+              </div>
+            </td>
+          </tr>
+          {% endfor %}
+        </tbody>
+      </table>
+
+      {% include '../widget/pager.html' with {path: "/admin/groups", pager: pager} %}
+
+    </div>
+  </div>
+</div>
+{% endblock content_main %}
+
+{% block content_footer %}
+{% endblock content_footer %}
+
+

+ 2 - 1
lib/views/admin/widget/menu.html

@@ -8,7 +8,8 @@
   <li class="{% if current == 'markdown'%}active{% endif %}"><a href="/admin/markdown"><i class="fa fa-pencil"></i> Markdown設定</a></li>
   <li class="{% if current == 'customize'%}active{% endif %}"><a href="/admin/customize"><i class="fa fa-object-group"></i> カスタマイズ</a></li>
   <li class="{% if current == 'notification'%}active{% endif %}"><a href="/admin/notification"><i class="fa fa-bell"></i> 通知設定</a></li>
-  <li class="{% if current == 'user'%}active{% endif %}"><a href="/admin/users"><i class="fa fa-users"></i> ユーザー管理</a></li>
+  <li class="{% if current == 'user'%}active{% endif %}"><a href="/admin/users"><i class="fa fa-user"></i> ユーザー管理</a></li>
+  <li class="{% if current == 'user_group'%}active{% endif %}"><a href="/admin/user-groups"><i class="fa fa-users"></i> グループ管理</a></li>
   {% if searchConfigured() %}
   <li class="{% if current == 'search'%}active{% endif %}"><a href="/admin/search"><i class="fa fa-search"></i> 検索管理</a></li>
   {% endif %}