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

# Feature/196, 198, 199 Grouping users
* Fix user-group-relation management page.
** Imprv management page user-group name updatable.
** Imprv management page user-group picture uploadable.
* Prepare page-group-relation model.

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

+ 1 - 0
lib/models/index.js

@@ -2,6 +2,7 @@
 
 module.exports = {
   Page: require('./page'),
+  PageGroupRelation: require('./page-group-relation'),
   User: require('./user'),
   UserGroup: require('./user-group'),
   UserGroupRelation: require('./user-group-relation'),

+ 152 - 0
lib/models/page-group-relation.js

@@ -0,0 +1,152 @@
+module.exports = function(crowi) {
+  var debug = require('debug')('crowi:models:pageGroupRelation')
+    , mongoose = require('mongoose')
+    , mongoosePaginate = require('mongoose-paginate')
+    , ObjectId = mongoose.Schema.Types.ObjectId
+
+    , PAGE_ITEMS = 50
+
+    , pageGroupRelationSchema;
+
+  pageGroupRelationSchema = new mongoose.Schema({
+    pageGroupRelationId: String,
+    relatedGroup: { type: ObjectId, ref: 'UserGroup' },
+    targetPage: { type: ObjectId, ref: 'Page' },
+    createdAt: { type: Date, default: Date.now },
+  },{
+    toJSON: { getters: true },
+    toObject: { getters: true }
+  });
+  pageGroupRelationSchema.plugin(mongoosePaginate);
+
+  // すべてのグループ所属関係を取得
+  pageGroupRelationSchema.statics.findAllRelation = function() {
+    debug('findAllRelations is called');
+    var PageGroupRelation = this;
+
+    return new Promise(function(resolve, reject) {
+      PageGroupRelation
+        .find({ relatedGroup: group} )
+        .populate('targetPage')
+        .exec(function (err, pageGroupRelationData) {
+          if (err) {
+            return reject(err);
+          }
+
+          return resolve(pageGroupRelationData);
+        });
+    });
+  };
+
+  // 指定グループに対するすべてのグループ所属関係を取得
+  pageGroupRelationSchema.statics.findAllRelationForUserGroup = function (userGroup) {
+    debug('findAllRelation is called', userGroup);
+    var PageGroupRelation = this;
+
+    return new Promise(function (resolve, reject) {
+      PageGroupRelation
+        .find({ relatedGroup: userGroup })
+        .populate('targetPage')
+        .exec(function (err, pageGroupRelationData) {
+          if (err) {
+            return reject(err);
+          }
+          return resolve(pageGroupRelationData);
+        });
+    });
+  };
+
+  // 指定グループリストに対するすべてのグループ所属関係を取得
+  pageGroupRelationSchema.statics.findAllRelationForUserGroups = function (userGroups, callback) {
+    debug('findAllRelations is called', userGroups);
+    var PageGroupRelation = this;
+    var groupRelations = new Map();
+
+    return new Promise(function (resolve, reject) {
+      PageGroupRelation
+        .find({ relatedGroup: { $in: userGroups} })
+        .populate('targetPage')
+        .exec(function (err, pageGroupRelationData) {
+          if (err) {
+            return reject(err);
+          }
+          debug(pageGroupRelationData);
+          return resolve(pageGroupRelationData);
+        });
+    });
+  };
+
+  // ページネーション利用の検索
+  pageGroupRelationSchema.statics.findPageGroupRelationsWithPagination = function (userGroup, options, callback) {
+
+    this.paginate({ relatedGroup: userGroup }, { 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);
+    });
+  };
+
+  // 関係性の生成
+  pageGroupRelationSchema.statics.createRelation = function(userGroup, user, callback) {
+    var PageGroupRelation = this
+      , newPageGroupRelation = new PageGroupRelation();
+
+    if (userGroup == null || user == null) {
+      return callback(new Error('userGroup or user is null'));
+    }
+    newPageGroupRelation.relatedGroup = userGroup;
+    newPageGroupRelation.targetPage = user;
+    newPageGroupRelation.createdAt = Date.now();
+
+    debug('create new user-group-relation ', newPageGroupRelation);
+    newPageGroupRelation.save(function(err, pageGroupRelationData) {
+      return callback(err, pageGroupRelationData);
+    });
+  };
+
+  // グループに紐づく関係性の全削除
+  pageGroupRelationSchema.statics.removeAllByUserGroup = function (userGroup, callback) {
+
+    if (userGroup === null || userGroup === undefined) { return callback(null); }
+    var PageGroupRelation = this
+    var relations = PageGroupRelation.findAllRelation(userGroup);
+
+    // 関係性削除の実装
+    relations.array.forEach(relation => {
+      PageGroupRelation.removeById(relation.id, function(err) {
+        if (err) { return callback(err); }
+      });
+    });
+    return callback(null);
+  }
+
+  // ユーザグループの関係性を削除
+  pageGroupRelationSchema.statics.removeById = function (id, callback) {
+    var PageGroupRelation = this
+    PageGroupRelation.findById(id, function (err, relationData) {
+      if (err) {
+        debug('Error on find a removing user-group-relation', err);
+        return callback(err);
+      }
+      debug('relationData is ', relationData);
+      if (relationData == null || relationData == undefined) {
+        debug('Cannot find user group relation by id', id);
+        return callback(new Error('Cannot find user group relation by id'));
+      }
+
+      relationData.remove(function(err) {
+        if (err) {
+          return callback(err);
+        }
+        return callback(null);
+      });
+    });
+  }
+
+  pageGroupRelationSchema.statics.PAGE_ITEMS         = PAGE_ITEMS;
+
+  return mongoose.model('PageGroupRelation', pageGroupRelationSchema);
+};

+ 2 - 0
lib/models/page.js

@@ -7,6 +7,7 @@ module.exports = function(crowi) {
     , GRANT_RESTRICTED = 2
     , GRANT_SPECIFIED = 3
     , GRANT_OWNER = 4
+    , GRANT_USER_GROUP = 5
     , PAGE_GRANT_ERROR = 1
 
     , STATUS_WIP        = 'wip'
@@ -338,6 +339,7 @@ module.exports = function(crowi) {
     grantLabels[GRANT_PUBLIC]     = 'Public'; // 公開
     grantLabels[GRANT_RESTRICTED] = 'Anyone with the link'; // リンクを知っている人のみ
     //grantLabels[GRANT_SPECIFIED]  = 'Specified users only'; // 特定ユーザーのみ
+    grantLabels[GRANT_USER_GROUP] = 'Only inside the group'; // 特定グループのみ
     grantLabels[GRANT_OWNER]      = 'Just me'; // 自分のみ
 
     return grantLabels;

+ 17 - 8
lib/models/user-group.js

@@ -19,7 +19,7 @@ module.exports = function(crowi) {
   userGroupSchema.plugin(mongoosePaginate);
 
 
-  // TBD: グループ画像の更新
+  // グループ画像の更新
   userGroupSchema.methods.updateImage = function(image, callback) {
     this.image = image;
     this.save(function(err, userGroupData) {
@@ -27,11 +27,18 @@ module.exports = function(crowi) {
     });
   };
 
-  // TBD: グループ画像の削除
+  // グループ画像の削除
   userGroupSchema.methods.deleteImage = function(callback) {
     return this.updateImage(null, callback);
   };
 
+  // グループ画像パスの生成
+  userGroupSchema.statics.createUserGroupPictureFilePath = function (userGroup, name) {
+    var ext = '.' + name.match(/(.*)(?:\.([^.]+$))/)[2];
+
+    return 'userGroup/' + userGroup._id + ext;
+  };
+
   // すべてのグループを取得(オプション指定可)
   userGroupSchema.statics.findAllGroups = function(option) {
     var UserGroup = this;
@@ -84,6 +91,7 @@ module.exports = function(crowi) {
 
     this.findOne({name: name}, function (err, userGroupData) {
       if (userGroupData) {
+        debug(userGroupData);
         userGroupnameUsable = false;
       }
       return callback(userGroupnameUsable);
@@ -123,14 +131,15 @@ module.exports = function(crowi) {
     });
   };
 
-  // TBD: グループ画像パスの生成
-  userGroupSchema.statics.createGroupPictureFilePath = function(userGroup, name) {
-    var ext = '.' + name.match(/(.*)(?:\.([^.]+$))/)[2];
-
-    return 'userGroup/' + userGroup._id + ext;
+  // グループ名の更新
+  userGroupSchema.methods.updateName = function(name, callback) {
+    // 名前を設定して更新
+    this.name = name;
+    this.save(function (err, userGroupData) {
+      return callback(err, this.name);
+    });
   };
 
-
   // userGroupSchema.statics.USER_GROUP_PUBLIC_FIELDS = USER_GROUP_PUBLIC_FIELDS;
   userGroupSchema.statics.PAGE_ITEMS         = PAGE_ITEMS;
 

+ 120 - 48
lib/routes/admin.js

@@ -2,8 +2,10 @@ module.exports = function(crowi, app) {
   'use strict';
 
   var debug = require('debug')('crowi:routes:admin')
+    , fs = require('fs')
     , models = crowi.models
     , Page = models.Page
+    , PageGroupRelation = models.PageGroupRelation
     , User = models.User
     , UserGroup = models.UserGroup
     , UserGroupRelation = models.UserGroupRelation
@@ -510,27 +512,35 @@ module.exports = function(crowi, app) {
         if (userGroup) {
           UserGroupRelation.findAllRelationForUserGroup(userGroup)
           .then(function (relations) {
-            User.findAllUsers(null)
-              .then(function (users) {
-                debug('users', users);
-                users = users.filter( function(user) {
-                  var relation = relations.find( function(relation) {
-                    return relation.relatedUser._id.toString() == user._id.toString();
+            PageGroupRelation.findAllRelationForUserGroup(userGroup)
+            .then(function (pageRelations) {
+              User.findAllUsers(null)
+                .then(function (users) {
+                  debug('users', users);
+                  users = users.filter( function(user) {
+                    var relation = relations.find( function(relation) {
+                      return relation.relatedUser._id.toString() == user._id.toString();
+                    });
+                    return relation == null || relation == undefined;
                   });
-                  return relation == null || relation == undefined;
-                });
-                debug('users', users);
-                debug('user-group-detail succeed', relations);
-                return res.render('admin/user-group-detail', {
-                  userGroup: userGroup,
-                  userGroupRelations: relations,
-                  notRelatedusers: users
+                  debug('users', users);
+                  debug('user-group-detail succeed', relations);
+                  return res.render('admin/user-group-detail', {
+                    userGroup: userGroup,
+                    userGroupRelations: relations,
+                    pageGroupRelations: pageRelations,
+                    notRelatedusers: users
+                  });
+                })
+                .catch(function(err) {
+                  debug('Error on find all relations', err);
+                  return res.json(ApiResponse.error('Error'));
                 });
-              })
-              .catch(function(err) {
-                debug('Error on find all relations', err);
-                return res.json(ApiResponse.error('Error'));
-              });
+            })
+            .catch(function (err) {
+              debug('Error on find all relations', err);
+              return res.json(ApiResponse.error('Error'));
+            });
           }).catch(function (err) {
             debug('Error on find all relations', err);
             return res.json(ApiResponse.error('Error'));
@@ -575,11 +585,47 @@ module.exports = function(crowi, app) {
     }
   };
 
+  //
+  actions.userGroup.update = function (req, res) {
+
+    var userGroupId = req.params.userGroupId;
+    var name = req.body.name;
+
+    UserGroup.findById(userGroupId, function (err, userGroupData) {
+      if (!userGroupData) {
+        req.flash('errorMessage', 'グループの検索に失敗しました。');
+        return res.redirect('/admin/user-groups');
+      }
+
+      // 名前存在チェック
+      UserGroup.isRegisterableName(name, function (isRegisterableName) {
+        // 既に存在するグループ名に更新しようとした場合はエラー
+        if (!isRegisterableName) {
+          req.flash('errorMessage', 'グループ名が既に存在します。');
+          return res.redirect('/admin/user-group-detail/' + userGroupData.name);
+        }
+
+        userGroupData.updateName(name, function (err, updatedName) {
+          if (err) {
+            req.flash('errorMessage', 'グループ名の更新に失敗しました。');
+            return res.redirect('/admin/user-group-detail/' + userGroupData.name);
+          }
+          else {
+            req.flash('successMessage', 'グループ名を更新しました。');
+            return res.redirect('/admin/user-group-detail/' + name);
+          }
+        });
+      });
+    });
+  };
+
   actions.userGroup.uploadGroupPicture = function (req, res) {
     var fileUploader = require('../util/fileUploader')(crowi, app);
     //var storagePlugin = new pluginService('storage');
     //var storage = require('../service/storage').StorageService(config);
 
+    var userGroupId = req.params.userGroupId;
+
     var tmpFile = req.file || null;
     if (!tmpFile) {
       return res.json({
@@ -588,43 +634,69 @@ module.exports = function(crowi, app) {
       });
     }
 
-    var tmpPath = tmpFile.path;
-    var filePath = User.createUserPictureFilePath(req.user, tmpFile.filename + tmpFile.originalname);
-    var acceptableFileType = /image\/.+/;
-
-    if (!tmpFile.mimetype.match(acceptableFileType)) {
-      return res.json({
-        'status': false,
-        'message': 'File type error. Only image files is allowed to set as user picture.',
-      });
-    }
+    UserGroup.findById(userGroupId, function (err, userGroupData) {
+      if (!userGroupData) {
+        return res.json({
+          'status': false,
+          'message': 'UserGroup error.'
+        });
+      }
 
-    var tmpFileStream = fs.createReadStream(tmpPath, { flags: 'r', encoding: null, fd: null, mode: '0666', autoClose: true });
+      var tmpPath = tmpFile.path;
+      var filePath = UserGroup.createUserGroupPictureFilePath(userGroupData, tmpFile.filename + tmpFile.originalname);
+      var acceptableFileType = /image\/.+/;
 
-    fileUploader.uploadFile(filePath, tmpFile.mimetype, tmpFileStream, {})
-      .then(function (data) {
-        var imageUrl = fileUploader.generateUrl(filePath);
-        req.userGroup.updateImage(imageUrl, function (err, data) {
-          fs.unlink(tmpPath, function (err) {
-            if (err) {
-              debug('Error while deleting tmp file.', err);
-            }
+      if (!tmpFile.mimetype.match(acceptableFileType)) {
+        return res.json({
+          'status': false,
+          'message': 'File type error. Only image files is allowed to set as user picture.',
+        });
+      }
 
-            return res.json({
-              'status': true,
-              'url': imageUrl,
-              'message': '',
+      var tmpFileStream = fs.createReadStream(tmpPath, { flags: 'r', encoding: null, fd: null, mode: '0666', autoClose: true });
+
+      fileUploader.uploadFile(filePath, tmpFile.mimetype, tmpFileStream, {})
+        .then(function (data) {
+          var imageUrl = fileUploader.generateUrl(filePath);
+          userGroupData.updateImage(imageUrl, function (err, data) {
+            fs.unlink(tmpPath, function (err) {
+              if (err) {
+                debug('Error while deleting tmp file.', err);
+              }
+
+              return res.json({
+                'status': true,
+                'url': imageUrl,
+                'message': '',
+              });
             });
           });
-        });
-      }).catch(function (err) {
-        debug('Uploading error', err);
+        }).catch(function (err) {
+          debug('Uploading error', err);
 
-        return res.json({
-          'status': false,
-          'message': 'Error while uploading to ',
+          return res.json({
+            'status': false,
+            'message': 'Error while uploading to ',
+          });
         });
+    });
+
+  };
+
+  actions.userGroup.deletePicture = function (req, res) {
+
+    var userGroupId = req.params.userGroupId;
+
+    UserGroup.findById(userGroupId, function (err, userGroupData) {
+      if (!userGroupData) {
+        req.flash('errorMessage', 'Error while deleting group picture');
+      }
+
+      userGroupData.deleteImage(function (err, data) {
+        req.flash('successMessage', 'Deleted group picture');
       });
+      return res.redirect('/admin/user-group-detail/' + userGroupData.name);
+    });
   };
 
   // app.post('/_api/admin/user-group/delete' , admin.userGroup.removeCompletely);

+ 3 - 0
lib/routes/index.js

@@ -104,7 +104,10 @@ module.exports = function(crowi, app) {
   app.get('/admin/user-groups'             , loginRequired(crowi, app), middleware.adminRequired(), admin.userGroup.index);
   app.get('/admin/user-group-detail/:name'          , loginRequired(crowi, app), middleware.adminRequired(), admin.userGroup.detail);
   app.post('/admin/user-group/create'      , form.admin.userGroupCreate, loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroup.create);
+  app.post('/admin/user-group/:userGroupId/update', loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroup.update);
+  app.post('/admin/user-group/:userGroupId/picture/delete', loginRequired(crowi, app), admin.userGroup.deletePicture);
   app.post('/_api/admin/user-group.remove' , loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroup.removeCompletely);
+  app.post('/_api/admin/user-group/:userGroupId/picture/upload', loginRequired(crowi, app), uploads.single('userGroupPicture'), admin.userGroup.uploadGroupPicture);
 
   // user-group-relations admin
   app.post('/admin/user-group-relation/create', loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroupRelation.create)

+ 76 - 1
lib/views/admin/user-group-detail.html

@@ -85,7 +85,82 @@
         <!-- /.modal-dialog -->
       </div>
 
-      <h2>ユーザー一覧({{userGroup.name}} グループ)</h2>
+      <div class="form-box">
+        <form action="/admin/user-group/{{userGroup.id}}/update" method="post" class="form-horizontal" role="form">
+          <fieldset>
+            <legend>基本情報</legend>
+            <div class="form-group">
+              <label for="name" class="col-sm-2 control-label">{{ t('Name') }}</label>
+              <div class="col-sm-4">
+                <input class="form-control" type="text" name="name" value="{{ userGroup.name }}" required>
+              </div>
+            </div>
+            <div class="form-group">
+              <label class="col-sm-2 control-label">{{ t('Created') }}</label>
+              <div class="col-sm-4">
+                <input class="form-control" type="text" disabled value="{{userGroup.createdAt|date('Y-m-d', sRelation.relatedUser.createdAt.getTimezoneOffset()) }}">
+              </div>
+            </div>
+            <div class="form-group">
+              <div class="col-sm-offset-2 col-sm-10">
+                <input type="hidden" name="_csrf" value="{{ csrf() }}">
+                <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
+              </div>
+            </div>
+          </fieldset>
+        </form>
+      </div>
+
+      <div class="form-box">
+        <fieldset>
+          <legend>グループ画像の設定</legend>
+          <div class="form-group col-sm-8">
+            <h4>
+              {{ t('Upload Image') }}
+            </h4>
+            <div class="form-group">
+              <div id="pictureUploadFormMessage"></div>
+              <label for="" class="col-sm-4 control-label">
+                {{ t('Current Image') }}
+              </label>
+              <div class="col-sm-8">
+                <p>
+                  <img src="{{ userGroup|uploadedpicture }}" width="64" id="settingUserPicture">
+                  <br>
+                </p>
+                <p>
+                  {% if userGroup.image %}
+                  <form action="/admin/user-group/{{userGroup.id}}/picture/delete" method="post" class="form-horizontal" role="form" onsubmit="return window.confirm('{{ t('Delete this image?') }}');">
+                    <button type="submit" class="btn btn-danger">{{ t('Delete Image') }}</button>
+                  </form>
+                  {% endif %}
+                </p>
+              </div>
+            </div><!-- /.form-group -->
+
+            <div class="form-group">
+              <label for="" class="col-sm-4 control-label">
+                {{ t('Upload new image') }}
+              </label>
+              <div class="col-sm-8">
+                {% if isUploadable() %}
+                <form action="/_api/admin/user-group/{{userGroup.id}}/picture/upload" id="pictureUploadForm" method="post" class="form-horizontal" role="form" enctype="multipart/form-data">
+                  <input name="userGroupPicture" type="file" accept="image/*">
+                  <div id="pictureUploadFormProgress">
+                  </div>
+                </form>
+                {% else %} * {{ t('page_me.form_help.profile_image1') }}
+                <br> * {{ t('page_me.form_help.profile_image2') }}
+                <br> {% endif %}
+              </div>
+            </div><!-- /.form-group -->
+
+          </div><!-- /.col-sm- -->
+
+        </fieldset>
+      </div><!-- /.form-box -->
+
+      <legend>ユーザー一覧</legend>
 
       <table class="table table-hover table-striped table-bordered table-user-list">
         <thead>

+ 32 - 0
resource/js/legacy/crowi-admin.js

@@ -73,4 +73,36 @@ $(function() {
      });
   });
 
+
+  $("#pictureUploadForm input[name=userGroupPicture]").on('change', function () {
+    var $form = $('#pictureUploadForm');
+    var fd = new FormData($form[0]);
+    if ($(this).val() == '') {
+      return false;
+    }
+
+    $('#pictureUploadFormProgress').html('<img src="/images/loading_s.gif"> アップロード中...');
+    $.ajax($form.attr("action"), {
+      type: 'post',
+      processData: false,
+      contentType: false,
+      data: fd,
+      dataType: 'json',
+      success: function (data) {
+        if (data.status) {
+          $('#settingUserPicture').attr('src', data.url + '?time=' + (new Date()));
+          $('#pictureUploadFormMessage')
+            .addClass('alert alert-success')
+            .html('変更しました');
+        } else {
+          $('#pictureUploadFormMessage')
+            .addClass('alert alert-danger')
+            .html('変更中にエラーが発生しました。');
+        }
+        $('#pictureUploadFormProgress').html('');
+      }
+    });
+    return false;
+  });
+
 });