ソースを参照

Merge branch 'imprv/reactify-admin' into reactify-admin-line-break

itizawa 6 年 前
コミット
89e75a2b31

+ 1 - 1
src/client/js/components/Admin/UserGroup/UserGroupTable.jsx

@@ -73,7 +73,7 @@ class UserGroupTable extends React.Component {
                       })}
                     </ul>
                   </td>
-                  <td>{dateFnsFormat(new Date(group.createdAt), 'YYYY-MM-DD')}</td>
+                  <td>{dateFnsFormat(new Date(group.createdAt), 'yyyy-MM-dd')}</td>
                   {this.props.isAclEnabled
                     ? (
                       <td>

+ 1 - 1
src/client/js/components/Admin/UserGroupDetail/UserGroupEditForm.jsx

@@ -73,7 +73,7 @@ class UserGroupEditForm extends React.Component {
               <input
                 type="text"
                 className="form-control"
-                value={dateFnsFormat(new Date(userGroupDetailContainer.state.userGroup.createdAt), 'YYYY-MM-DD')}
+                value={dateFnsFormat(new Date(userGroupDetailContainer.state.userGroup.createdAt), 'yyyy-MM-dd')}
                 disabled
               />
             </div>

+ 2 - 2
src/client/js/components/Admin/UserGroupDetail/UserGroupUserTable.jsx

@@ -60,8 +60,8 @@ class UserGroupUserTable extends React.Component {
                     <strong>{relatedUser.username}</strong>
                   </td>
                   <td>{relatedUser.name}</td>
-                  <td>{relatedUser.createdAt ? dateFnsFormat(new Date(relatedUser.createdAt), 'YYYY-MM-DD') : ''}</td>
-                  <td>{relatedUser.lastLoginAt ? dateFnsFormat(new Date(relatedUser.lastLoginAt), 'YYYY-MM-DD HH:mm:ss') : ''}</td>
+                  <td>{relatedUser.createdAt ? dateFnsFormat(new Date(relatedUser.createdAt), 'yyyy-MM-dd') : ''}</td>
+                  <td>{relatedUser.lastLoginAt ? dateFnsFormat(new Date(relatedUser.lastLoginAt), 'yyyy-MM-dd HH:mm:ss') : ''}</td>
                   <td>
                     <div className="btn-group admin-user-menu">
                       <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">

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

@@ -4,7 +4,7 @@ import { withTranslation } from 'react-i18next';
 
 import StatusActivateForm from './StatusActivateForm';
 import StatusSuspendedForm from './StatusSuspendedForm';
-import RemoveUserForm from './UserRemoveForm';
+import RemoveUserButton from './UserRemoveButton';
 import RemoveAdminForm from './RemoveAdminForm';
 import GiveAdminForm from './GiveAdminForm';
 
@@ -48,7 +48,7 @@ class UserMenu extends React.Component {
             <li>
               {(user.status === 1 || user.status === 3) && <StatusActivateForm user={user} />}
               {user.status === 2 && <StatusSuspendedForm user={user} />}
-              {(user.status === 1 || user.status === 3 || user.status === 5) && <RemoveUserForm user={user} />}
+              {(user.status === 1 || user.status === 3 || user.status === 5) && <RemoveUserButton user={user} removeUser={this.props.removeUser} />}
             </li>
             <li className="divider pl-0"></li>
             <li className="dropdown-header">{ t('user_management.administrator_menu') }</li>
@@ -73,6 +73,7 @@ UserMenu.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   user: PropTypes.object.isRequired,
+  removeUser: PropTypes.func.isRequired,
   onPasswordResetClicked: PropTypes.func.isRequired,
 };
 

+ 47 - 0
src/client/js/components/Admin/Users/UserRemoveButton.jsx

@@ -0,0 +1,47 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import AppContainer from '../../../services/AppContainer';
+
+class UserRemoveButton extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onClickDeleteBtn = this.onClickDeleteBtn.bind(this);
+  }
+
+  onClickDeleteBtn() {
+    this.props.removeUser(this.props.user);
+  }
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <a className="px-4" onClick={() => { this.onClickDeleteBtn() }}>
+        <i className="icon-fw icon-fire text-danger"></i> { t('Delete') }
+      </a>
+    );
+  }
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const UserRemoveButtonWrapper = (props) => {
+  return createSubscribedElement(UserRemoveButton, props, [AppContainer]);
+};
+
+UserRemoveButton.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+
+  user: PropTypes.object.isRequired,
+  removeUser: PropTypes.func.isRequired,
+};
+
+export default withTranslation()(UserRemoveButtonWrapper);

+ 0 - 56
src/client/js/components/Admin/Users/UserRemoveForm.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 UserRemoveForm 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}/remove`} method="post">
-          <input type="hidden" name="_csrf" value={appContainer.csrfToken} />
-          <span onClick={this.handleSubmit}>
-            <i className="icon-fw icon-fire text-danger"></i> { t('Delete') }
-          </span>
-        </form>
-      </a>
-    );
-  }
-
-}
-
-/**
- * Wrapper component for using unstated
- */
-const UserRemoveFormWrapper = (props) => {
-  return createSubscribedElement(UserRemoveForm, props, [AppContainer]);
-};
-
-UserRemoveForm.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  user: PropTypes.object.isRequired,
-};
-
-export default withTranslation()(UserRemoveFormWrapper);

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

@@ -94,12 +94,12 @@ class UserTable extends React.Component {
                   </td>
                   <td>{user.name}</td>
                   <td>{user.email}</td>
-                  <td>{dateFnsFormat(new Date(user.createdAt), 'YYYY-MM-DD')}</td>
+                  <td>{dateFnsFormat(new Date(user.createdAt), 'yyyy-MM-dd')}</td>
                   <td>
-                    { user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'YYYY-MM-DD HH:mm')}</span> }
+                    { user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'yyyy-MM-dd HH:mm')}</span> }
                   </td>
                   <td>
-                    <UserMenu user={user} onPasswordResetClicked={this.props.onPasswordResetClicked} />
+                    <UserMenu user={user} onPasswordResetClicked={this.props.onPasswordResetClicked} removeUser={this.props.removeUser} />
                   </td>
                 </tr>
               );
@@ -121,6 +121,7 @@ UserTable.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   users: PropTypes.array.isRequired,
+  removeUser: PropTypes.func.isRequired,
   onPasswordResetClicked: PropTypes.func.isRequired,
 };
 

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

@@ -2,6 +2,8 @@ import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+
 import PasswordResetModal from './PasswordResetModal';
 import PaginationWrapper from '../../PaginationWrapper';
 import InviteUserControl from './InviteUserControl';
@@ -23,6 +25,7 @@ class UserPage extends React.Component {
       isPasswordResetModalShown: false,
     };
 
+    this.removeUser = this.removeUser.bind(this);
     this.showPasswordResetModal = this.showPasswordResetModal.bind(this);
     this.hidePasswordResetModal = this.hidePasswordResetModal.bind(this);
   }
@@ -37,6 +40,20 @@ class UserPage extends React.Component {
     });
   }
 
+  async removeUser(user) {
+
+    const { appContainer } = this.props;
+
+    try {
+      const response = await appContainer.apiv3.delete(`/users/${user._id}/remove`);
+      const { username } = response.data.userData;
+      toastSuccess(`Delete ${username} success`);
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
   /**
    * passwordリセットモーダルが開き、userが渡される
    * @param {object} user
@@ -76,14 +93,14 @@ class UserPage extends React.Component {
         <UserTable
           users={this.state.users}
           onPasswordResetClicked={this.showPasswordResetModal}
+          removeUser={this.removeUser}
         />
         <PaginationWrapper
           activePage={this.state.activePage}
-          changePage={this.handlePage}
-          totalItemsCount={this.state.totalUsers}
+          changePage={this.handlePage} // / TODO GW-314 create function
+          totalItemsCount={this.state.users.length} // TODO GW-314 props.userTotalCount
           pagingLimit={this.state.pagingLimit}
-        >
-        </PaginationWrapper>
+        />
       </Fragment>
     );
   }

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

@@ -332,7 +332,7 @@ module.exports = function(crowi) {
     });
   };
 
-  userSchema.methods.statusDelete = function(callback) {
+  userSchema.methods.statusDelete = async function() {
     debug('Delete User', this);
 
     const now = new Date();
@@ -346,9 +346,7 @@ module.exports = function(crowi) {
     this.googleId = null;
     this.isGravatarEnabled = false;
     this.image = null;
-    this.save((err, userData) => {
-      return callback(err, userData);
-    });
+    return this.save();
   };
 
   userSchema.methods.updateGoogleId = function(googleId, callback) {

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

@@ -4,7 +4,6 @@ module.exports = function(crowi, app) {
   const logger = require('@alias/logger')('growi:routes:admin');
 
   const models = crowi.models;
-  const Page = models.Page;
   const User = models.User;
   const ExternalAccount = models.ExternalAccount;
   const UserGroup = models.UserGroup;
@@ -532,43 +531,6 @@ module.exports = function(crowi, app) {
     });
   };
 
-  actions.user.remove = function(req, res) {
-    const id = req.params.id;
-    let username = '';
-
-    return new Promise((resolve, reject) => {
-      User.findById(id, (err, userData) => {
-        username = userData.username;
-        return resolve(userData);
-      });
-    })
-      .then((userData) => {
-        return new Promise((resolve, reject) => {
-          userData.statusDelete((err, userData) => {
-            if (err) {
-              reject(err);
-            }
-            resolve(userData);
-          });
-        });
-      })
-      .then((userData) => {
-      // remove all External Accounts
-        return ExternalAccount.remove({ user: userData }).then(() => { return userData });
-      })
-      .then((userData) => {
-        return Page.removeByPath(`/user/${username}`).then(() => { return userData });
-      })
-      .then((userData) => {
-        req.flash('successMessage', `${username} さんのアカウントを削除しました`);
-        return res.redirect('/admin/users');
-      })
-      .catch((err) => {
-        req.flash('errorMessage', '削除に失敗しました。');
-        return res.redirect('/admin/users');
-      });
-  };
-
   // これやったときの relation の挙動未確認
   actions.user.removeCompletely = function(req, res) {
     // ユーザーの物理削除

+ 0 - 2
src/server/routes/apiv3/user-group-relation.js

@@ -25,8 +25,6 @@ module.exports = (crowi) => {
    *      get:
    *        tags: [UserGroupRelation]
    *        description: Gets the user group relations
-   *        produces:
-   *          - application/json
    *        responses:
    *          200:
    *            description: user group relations are fetched

+ 23 - 46
src/server/routes/apiv3/user-group.js

@@ -43,8 +43,6 @@ module.exports = (crowi) => {
    *      get:
    *        tags: [UserGroup]
    *        description: Get usergroups
-   *        produces:
-   *          - application/json
    *        responses:
    *          200:
    *            description: usergroups are fetched
@@ -83,8 +81,6 @@ module.exports = (crowi) => {
    *      post:
    *        tags: [UserGroup]
    *        description: Adds userGroup
-   *        produces:
-   *          - application/json
    *        requestBody:
    *          required: true
    *          content:
@@ -131,18 +127,17 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}:
+   *    /_api/v3/user-groups/{id}:
    *      delete:
    *        tags: [UserGroup]
    *        description: Deletes userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
-   *          - name: deleteGroupId
+   *          - name: id
    *            in: path
+   *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
+   *              type: string
    *          - name: actionName
    *            in: query
    *            description: name of action
@@ -152,7 +147,7 @@ module.exports = (crowi) => {
    *            in: query
    *            description: userGroup id that will be transferred to
    *            schema:
-   *              type: ObjectId
+   *              type: string
    *        responses:
    *          200:
    *            description: userGroup is removed
@@ -192,19 +187,17 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}:
+   *    /_api/v3/user-groups/{id}:
    *      put:
    *        tags: [UserGroup]
    *        description: Update userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: id
    *            in: path
    *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
+   *              type: string
    *        responses:
    *          200:
    *            description: userGroup is updated
@@ -249,18 +242,17 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}/users:
+   *    /_api/v3/user-groups/{id}/users:
    *      get:
    *        tags: [UserGroup]
    *        description: Get users related to the userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: id
    *            in: path
+   *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
+   *              type: string
    *        responses:
    *          200:
    *            description: users are fetched
@@ -298,18 +290,17 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}/unrelated-users:
+   *    /_api/v3/user-groups/{id}/unrelated-users:
    *      get:
    *        tags: [UserGroup]
    *        description: Get users unrelated to the userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: id
    *            in: path
+   *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
+   *              type: string
    *        responses:
    *          200:
    *            description: users are fetched
@@ -348,22 +339,16 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}/users:
+   *    /_api/v3/user-groups/{id}/users:
    *      post:
    *        tags: [UserGroup]
    *        description: Add a user to the userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: id
    *            in: path
+   *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
-   *          - name: username
-   *            in: path
-   *            description: id of user
-   *            schema:
    *              type: string
    *        responses:
    *          200:
@@ -413,22 +398,16 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}/users:
+   *    /_api/v3/user-groups/{id}/users:
    *      delete:
    *        tags: [UserGroup]
    *        description: remove a user from the userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: id
    *            in: path
+   *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
-   *          - name: username
-   *            in: path
-   *            description: id of user
-   *            schema:
    *              type: string
    *        responses:
    *          200:
@@ -479,18 +458,17 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}/user-group-relations:
+   *    /_api/v3/user-groups/{id}/user-group-relations:
    *      get:
    *        tags: [UserGroup]
    *        description: Get the user group relations for the userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: id
    *            in: path
+   *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
+   *              type: string
    *        responses:
    *          200:
    *            description: user group relations are fetched
@@ -532,18 +510,17 @@ module.exports = (crowi) => {
    * @swagger
    *
    *  paths:
-   *    /_api/v3/user-groups/{:id}/pages:
+   *    /_api/v3/user-groups/{id}/pages:
    *      get:
    *        tags: [UserGroup]
    *        description: Get closed pages for the userGroup
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: id
    *            in: path
+   *            required: true
    *            description: id of userGroup
    *            schema:
-   *              type: ObjectId
+   *              type: string
    *        responses:
    *          200:
    *            description: pages are fetched

+ 56 - 12
src/server/routes/apiv3/users.js

@@ -1,6 +1,6 @@
 const loggerFactory = require('@alias/logger');
 
-const logger = loggerFactory('growi:routes:apiv3:user-group'); // eslint-disable-line no-unused-vars
+const logger = loggerFactory('growi:routes:apiv3:user-group');
 
 const express = require('express');
 
@@ -11,6 +11,13 @@ const { isEmail } = require('validator');
 
 const validator = {};
 
+/**
+ * @swagger
+ *  tags:
+ *    name: Users
+ */
+
+
 module.exports = (crowi) => {
   const loginRequiredStrictly = require('../../middleware/login-required')(crowi);
   const adminRequired = require('../../middleware/admin-required')(crowi);
@@ -19,6 +26,8 @@ module.exports = (crowi) => {
   const {
     ErrorV3,
     User,
+    Page,
+    ExternalAccount,
   } = crowi.models;
 
   const { ApiV3FormValidator } = crowi.middlewares;
@@ -42,14 +51,12 @@ module.exports = (crowi) => {
    *      post:
    *        tags: [Users]
    *        description: Create new users and send Emails
-   *        produces:
-   *          - application/json
    *        parameters:
    *          - name: shapedEmailList
    *            in: query
    *            description: Invitation emailList
    *            schema:
-   *              type: array
+   *              type: object
    *          - name: sendEmail
    *            in: query
    *            description: Whether to send mail
@@ -63,16 +70,10 @@ module.exports = (crowi) => {
    *                schema:
    *                  properties:
    *                    createdUserList:
-   *                      type: array
-   *                      email:
-   *                        type: string
-   *                      password:
-   *                        type: string
+   *                      type: object
    *                      description: Users successfully created
    *                    existingEmailList:
-   *                      type: array
-   *                      email:
-   *                        type: string
+   *                      type: object
    *                      description: Users email that already exists
    */
   router.post('/invite', loginRequiredStrictly, adminRequired, csrf, validator.inviteEmail, ApiV3FormValidator, async(req, res) => {
@@ -81,6 +82,49 @@ module.exports = (crowi) => {
       return res.apiv3({ emailList });
     }
     catch (err) {
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(err));
+    }
+  });
+  /**
+   * @swagger
+   *
+   *  paths:
+   *    /_api/v3/users/{id}/remove:
+   *      delete:
+   *        tags: [Users]
+   *        description: Delete user
+   *        parameters:
+   *          - name: id
+   *            in: path
+   *            required: true
+   *            description: id of delete user
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Deleting user success
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    userData:
+   *                      type: object
+   *                      description: data of delete user
+   */
+  router.delete('/:id/remove', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
+    const { id } = req.params;
+
+    try {
+      const userData = await User.findById(id);
+      await userData.statusDelete();
+      await ExternalAccount.remove({ user: userData });
+      await Page.removeByPath(`/user/${userData.username}`);
+
+      return res.apiv3({ userData });
+    }
+    catch (err) {
+      logger.error('Error', err);
       return res.apiv3Err(new ErrorV3(err));
     }
   });

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

@@ -133,7 +133,6 @@ module.exports = function(crowi, app) {
   app.post('/admin/user/:id/removeFromAdmin', loginRequiredStrictly , adminRequired , admin.user.removeFromAdmin);
   app.post('/admin/user/:id/activate'   , loginRequiredStrictly , adminRequired , csrf, admin.user.activate);
   app.post('/admin/user/:id/suspend'    , loginRequiredStrictly , adminRequired , csrf, admin.user.suspend);
-  app.post('/admin/user/:id/remove'     , loginRequiredStrictly , adminRequired , csrf, admin.user.remove);
   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);

+ 0 - 1
src/server/util/middlewares.js

@@ -1,7 +1,6 @@
 // don't add any more middlewares to this file.
 // all new middlewares should be an independent file under /server/routes/middlewares
 // eslint-disable-next-line no-unused-vars
-
 const logger = require('@alias/logger')('growi:lib:middlewares');
 
 const { formatDistanceStrict } = require('date-fns');