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

Merge commit '0ec5c41c1bb3679141dec6570a4183c4c9887820' into imprv/reactify-admin

itizawa 6 лет назад
Родитель
Сommit
87e1cd6286

+ 2 - 0
resource/locales/en-US/translation.json

@@ -715,6 +715,8 @@
     "current_users": "Current users:",
     "valid_email": "Valid email address is required",
     "existing_email": "The following emails already exist",
+    "give_user_admin": "Give {{username}} admin success",
+    "remove_user_admin": "Remove {{username}} admin success",
     "activate_user_success": "Activating {{username}} success",
     "deactivate_user_success": "Deactivating {{username}} success",
     "remove_user_success": "Removing {{username}} success"

+ 2 - 0
resource/locales/ja/translation.json

@@ -699,6 +699,8 @@
     "current_users": "現在のユーザー数:",
     "valid_email": "メールアドレスを入力してください。",
     "existing_email": "以下のEmailはすでに存在しています。",
+    "give_user_admin": "{{username}}を管理者に設定しました",
+    "remove_user_admin": "{{username}}を管理者から外しました",
     "activate_user_success": "{{username}}を有効化しました",
     "deactivate_user_success": "{{username}}を無効化しました",
     "remove_user_success": "{{username}}を削除しました"

+ 57 - 0
src/client/js/components/Admin/Users/GiveAdminButton.jsx

@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import AppContainer from '../../../services/AppContainer';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
+
+class GiveAdminButton extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onClickGiveAdminBtn = this.onClickGiveAdminBtn.bind(this);
+  }
+
+  async onClickGiveAdminBtn() {
+    const { t } = this.props;
+
+    try {
+      const username = await this.props.adminUsersContainer.giveUserAdmin(this.props.user._id);
+      toastSuccess(t('user_management.give_user_admin', { username }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <a className="px-4" onClick={() => { this.onClickGiveAdminBtn() }}>
+        <i className="icon-fw icon-user-following"></i> { t('user_management.give_admin_access') }
+      </a>
+    );
+  }
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const GiveAdminButtonWrapper = (props) => {
+  return createSubscribedElement(GiveAdminButton, props, [AppContainer, AdminUsersContainer]);
+};
+
+GiveAdminButton.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AppContainer).isRequired,
+
+  user: PropTypes.object.isRequired,
+};
+
+export default withTranslation()(GiveAdminButtonWrapper);

+ 0 - 56
src/client/js/components/Admin/Users/GiveAdminForm.jsx

@@ -1,56 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-
-import { createSubscribedElement } from '../../UnstatedUtils';
-import AppContainer from '../../../services/AppContainer';
-
-class AdminMenuForm extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-
-    };
-
-    this.handleSubmit = this.handleSubmit.bind(this);
-  }
-
-  // これは将来的にapiにするので。あとボタンにするとデザインがよくなかったので。
-  handleSubmit(event) {
-    $(event.currentTarget).parent().submit();
-  }
-
-  render() {
-    const { t, appContainer, user } = this.props;
-
-    return (
-      <a className="px-4">
-        <form action={`/admin/user/${user._id}/makeAdmin`} method="post">
-          <input type="hidden" name="_csrf" value={appContainer.csrfToken} />
-          <span onClick={this.handleSubmit}>
-            <i className="icon-fw icon-magic-wand"></i>{ t('user_management.give_admin_access') }
-          </span>
-        </form>
-      </a>
-    );
-  }
-
-}
-
-/**
- * Wrapper component for using unstated
- */
-const AdminMenuFormWrapper = (props) => {
-  return createSubscribedElement(AdminMenuForm, props, [AppContainer]);
-};
-
-AdminMenuForm.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  user: PropTypes.object.isRequired,
-};
-
-export default withTranslation()(AdminMenuFormWrapper);

+ 81 - 0
src/client/js/components/Admin/Users/RemoveAdminButton.jsx

@@ -0,0 +1,81 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import AppContainer from '../../../services/AppContainer';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
+
+class RemoveAdminButton extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onClickRemoveAdminBtn = this.onClickRemoveAdminBtn.bind(this);
+  }
+
+  async onClickRemoveAdminBtn() {
+    const { t } = this.props;
+
+    try {
+      const username = await this.props.adminUsersContainer.removeUserAdmin(this.props.user._id);
+      toastSuccess(t('user_management.remove_user_admin', { username }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
+
+  renderRemoveAdminBtn() {
+    const { t } = this.props;
+
+    return (
+      <a className="px-4" onClick={() => { this.onClickRemoveAdminBtn() }}>
+        <i className="icon-fw icon-user-unfollow"></i> { t('user_management.remove_admin_access') }
+      </a>
+    );
+  }
+
+  renderRemoveAdminAlert() {
+    const { t } = this.props;
+
+    return (
+      <div className="px-4">
+        <i className="icon-fw icon-user-unfollow mb-2"></i>{ t('user_management.remove_admin_access') }
+        <p className="alert alert-danger">{ t('user_management.cannot_remove') }</p>
+      </div>
+    );
+  }
+
+  render() {
+    const { user } = this.props;
+    const me = this.props.appContainer.me;
+
+    return (
+      <Fragment>
+        {user.username !== me ? this.renderRemoveAdminBtn()
+          : this.renderRemoveAdminAlert()}
+      </Fragment>
+    );
+  }
+
+}
+
+/**
+* Wrapper component for using unstated
+*/
+const RemoveAdminButtonWrapper = (props) => {
+  return createSubscribedElement(RemoveAdminButton, props, [AppContainer, AdminUsersContainer]);
+};
+
+RemoveAdminButton.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AppContainer).isRequired,
+
+  user: PropTypes.object.isRequired,
+};
+
+export default withTranslation()(RemoveAdminButtonWrapper);

+ 0 - 68
src/client/js/components/Admin/Users/RemoveAdminForm.jsx

@@ -1,68 +0,0 @@
-import React, { Fragment } from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-
-import { createSubscribedElement } from '../../UnstatedUtils';
-import AppContainer from '../../../services/AppContainer';
-
-class RemoveAdminForm extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-    };
-
-    this.handleSubmit = this.handleSubmit.bind(this);
-  }
-
-  // これは将来的にapiにするので。あとボタンにするとデザインがよくなかったので。
-  handleSubmit(event) {
-    $(event.currentTarget).parent().submit();
-  }
-
-  render() {
-    const { t, user } = this.props;
-    const me = this.props.appContainer.me;
-
-    return (
-      <Fragment>
-        {user.username !== me
-          ? (
-            <a>
-              <form action={`/admin/user/${user._id}/removeFromAdmin`} method="post">
-                <input type="hidden" />
-                <span onClick={this.handleSubmit}>
-                  <i className="icon-fw icon-user-unfollow mb-2"></i>{ t('user_management.remove_admin_access') }
-                </span>
-              </form>
-            </a>
-          )
-          : (
-            <div className="px-4">
-              <i className="icon-fw icon-user-unfollow mb-2"></i>{ t('user_management.remove_admin_access') }
-              <p className="alert alert-danger">{ t('user_management.cannot_remove') }</p>
-            </div>
-          )
-        }
-      </Fragment>
-    );
-  }
-
-}
-
-/**
-* Wrapper component for using unstated
-*/
-const RemoveAdminFormWrapper = (props) => {
-  return createSubscribedElement(RemoveAdminForm, props, [AppContainer]);
-};
-
-RemoveAdminForm.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  user: PropTypes.object.isRequired,
-};
-
-export default withTranslation()(RemoveAdminFormWrapper);

+ 4 - 4
src/client/js/components/Admin/Users/UserMenu.jsx

@@ -5,8 +5,8 @@ import { withTranslation } from 'react-i18next';
 import StatusActivateButton from './StatusActivateButton';
 import StatusSuspendedButton from './StatusSuspendedButton';
 import RemoveUserButton from './UserRemoveButton';
-import RemoveAdminForm from './RemoveAdminForm';
-import GiveAdminForm from './GiveAdminForm';
+import RemoveAdminButton from './RemoveAdminButton';
+import GiveAdminButton from './GiveAdminButton';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
@@ -54,8 +54,8 @@ class UserMenu extends React.Component {
             <li className="divider pl-0"></li>
             <li className="dropdown-header">{ t('user_management.administrator_menu') }</li>
             <li>
-              {user.status === 2 && user.admin === true && <RemoveAdminForm user={user} />}
-              {user.status === 2 && user.admin === false && <GiveAdminForm user={user} />}
+              {user.status === 2 && user.admin === true && <RemoveAdminButton user={user} />}
+              {user.status === 2 && user.admin === false && <GiveAdminButton user={user} />}
             </li>
           </ul>
         </div>

+ 24 - 0
src/client/js/services/AdminUsersContainer.js

@@ -89,6 +89,30 @@ export default class AdminUsersContainer extends Container {
     await this.setState({ isUserInviteModalShown: !this.state.isUserInviteModalShown });
   }
 
+  /**
+   * Give user admin
+   * @memberOf AdminUsersContainer
+   * @param {string} userId
+   * @return {string} username
+   */
+  async giveUserAdmin(userId) {
+    const response = await this.appContainer.apiv3.put(`/users/${userId}/giveAdmin`);
+    const { username } = response.data.userData;
+    return username;
+  }
+
+  /**
+   * Remove user admin
+   * @memberOf AdminUsersContainer
+   * @param {string} userId
+   * @return {string} username
+   */
+  async removeUserAdmin(userId) {
+    const response = await this.appContainer.apiv3.put(`/users/${userId}/removeAdmin`);
+    const { username } = response.data.userData;
+    return username;
+  }
+
   /**
    * Activate user
    * @memberOf AdminUsersContainer

+ 4 - 8
src/server/models/user.js

@@ -285,20 +285,16 @@ module.exports = function(crowi) {
     });
   };
 
-  userSchema.methods.removeFromAdmin = function(callback) {
+  userSchema.methods.removeFromAdmin = async function() {
     debug('Remove from admin', this);
     this.admin = 0;
-    this.save((err, userData) => {
-      return callback(err, userData);
-    });
+    return this.save();
   };
 
-  userSchema.methods.makeAdmin = function(callback) {
+  userSchema.methods.makeAdmin = async function() {
     debug('Admin', this);
     this.admin = 1;
-    this.save((err, userData) => {
-      return callback(err, userData);
-    });
+    return this.save();
   };
 
   userSchema.methods.asyncMakeAdmin = async function(callback) {

+ 0 - 32
src/server/routes/admin.js

@@ -459,38 +459,6 @@ module.exports = function(crowi, app) {
     });
   };
 
-  actions.user.makeAdmin = function(req, res) {
-    const id = req.params.id;
-    User.findById(id, (err, userData) => {
-      userData.makeAdmin((err, userData) => {
-        if (err === null) {
-          req.flash('successMessage', `${userData.name}さんのアカウントを管理者に設定しました。`);
-        }
-        else {
-          req.flash('errorMessage', '更新に失敗しました。');
-          debug(err, userData);
-        }
-        return res.redirect('/admin/users');
-      });
-    });
-  };
-
-  actions.user.removeFromAdmin = function(req, res) {
-    const id = req.params.id;
-    User.findById(id, (err, userData) => {
-      userData.removeFromAdmin((err, userData) => {
-        if (err === null) {
-          req.flash('successMessage', `${userData.name}さんのアカウントを管理者から外しました。`);
-        }
-        else {
-          req.flash('errorMessage', '更新に失敗しました。');
-          debug(err, userData);
-        }
-        return res.redirect('/admin/users');
-      });
-    });
-  };
-
   // これやったときの relation の挙動未確認
   actions.user.removeCompletely = function(req, res) {
     // ユーザーの物理削除

+ 78 - 0
src/server/routes/apiv3/users.js

@@ -119,6 +119,84 @@ module.exports = (crowi) => {
       return res.apiv3Err(new ErrorV3(err));
     }
   });
+  /**
+   * @swagger
+   *
+   *  paths:
+   *    /_api/v3/users/{id}/giveAdmin:
+   *      put:
+   *        tags: [Users]
+   *        description: Give user admin
+   *        parameters:
+   *          - name: id
+   *            in: path
+   *            required: true
+   *            description: id of user for admin
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Give user admin success
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    userData:
+   *                      type: object
+   *                      description: data of admin user
+   */
+  router.put('/:id/giveAdmin', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
+    const { id } = req.params;
+
+    try {
+      const userData = await User.findById(id);
+      await userData.makeAdmin();
+      return res.apiv3({ userData });
+    }
+    catch (err) {
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(err));
+    }
+  });
+  /**
+   * @swagger
+   *
+   *  paths:
+   *    /_api/v3/users/{id}/removeAdmin:
+   *      put:
+   *        tags: [Users]
+   *        description: Remove user admin
+   *        parameters:
+   *          - name: id
+   *            in: path
+   *            required: true
+   *            description: id of user for removing admin
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Remove user admin success
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    userData:
+   *                      type: object
+   *                      description: data of removed admin user
+   */
+  router.put('/:id/removeAdmin', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
+    const { id } = req.params;
+
+    try {
+      const userData = await User.findById(id);
+      await userData.removeFromAdmin();
+      return res.apiv3({ userData });
+    }
+    catch (err) {
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(err));
+    }
+  });
   /**
    * @swagger
    *

+ 0 - 2
src/server/routes/index.js

@@ -129,8 +129,6 @@ module.exports = function(crowi, app) {
   app.post('/admin/global-notification/:id/remove', loginRequiredStrictly , adminRequired , admin.globalNotification.remove);
 
   app.get('/admin/users'                , loginRequiredStrictly , adminRequired , admin.user.index);
-  app.post('/admin/user/:id/makeAdmin'  , loginRequiredStrictly , adminRequired , csrf, admin.user.makeAdmin);
-  app.post('/admin/user/:id/removeFromAdmin', loginRequiredStrictly , adminRequired , admin.user.removeFromAdmin);
   app.post('/admin/user/:id/removeCompletely' , loginRequiredStrictly , adminRequired , csrf, admin.user.removeCompletely);
   // new route patterns from here:
   app.post('/_api/admin/users.resetPassword'  , loginRequiredStrictly , adminRequired , csrf, admin.user.resetPassword);