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

Merge branch 'imprv/reactify-admin' into reactify-admin-user

WESEEK Kaito 6 лет назад
Родитель
Сommit
c79a97b704

+ 5 - 1
CHANGES.md

@@ -1,6 +1,10 @@
 # CHANGES
 
-## 3.5.16-RC
+## 3.5.17-RC
+
+* 
+
+## 3.5.16
 
 * Fix: Full Text Search doesn't work after when building indices
     * Introduced by 3.5.12

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "3.5.16-RC",
+  "version": "3.5.17-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 115 - 0
src/client/js/components/Admin/MarkdownSetting/LineBreakSetting.jsx

@@ -0,0 +1,115 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+
+import AppContainer from '../../../services/AppContainer';
+
+const logger = loggerFactory('growi:importer');
+
+class LineBreakSetting extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    const { appContainer } = this.props;
+
+    this.state = {
+      isEnabledLinebreaks: appContainer.config.isEnabledLinebreaks,
+      isEnabledLinebreaksInComments: appContainer.config.isEnabledLinebreaksInComments,
+    };
+    this.onChangeEnableLineBreaks = this.onChangeEnableLineBreaks.bind(this);
+    this.onChangeEnableLineBreaksInComments = this.onChangeEnableLineBreaksInComments.bind(this);
+    this.changeLineBreakSettings = this.changeLineBreakSettings.bind(this);
+  }
+
+
+  onChangeEnableLineBreaks() {
+    this.setState({ isEnabledLinebreaks: !this.state.isEnabledLinebreaks });
+  }
+
+  onChangeEnableLineBreaksInComments() {
+    this.setState({ isEnabledLinebreaksInComments: !this.state.isEnabledLinebreaksInComments });
+  }
+
+  async changeLineBreakSettings() {
+    const { appContainer } = this.props;
+    const params = {
+      isEnabledLinebreaks: this.state.isEnabledLinebreaks,
+      isEnabledLinebreaksInComments: this.state.isEnabledLinebreaksInComments,
+    };
+    try {
+      await appContainer.apiPost('/admin/markdown/lineBreaksSetting', { params });
+      toastSuccess('Success change line braek setting');
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <React.Fragment>
+        <div className="row my-3">
+          <div className="form-group">
+            <legend>{ t('markdown_setting.line_break_setting') }</legend>
+            <p className="well">{ t('markdown_setting.line_break_setting_desc') }</p>
+            <fieldset className="row">
+              <div className="form-group">
+                <div className="col-xs-4 text-right">
+                  <div className="checkbox checkbox-success" onChange={this.onChangeEnableLineBreaks}>
+                    <input type="checkbox" name="isEnabledLinebreaks" checked={this.state.isEnabledLinebreaks} />
+                    <label>
+                      { t('markdown_setting.Enable Line Break') }
+                    </label>
+                    <p className="help-block">{ t('markdown_setting.Enable Line Break desc') }</p>
+                  </div>
+                </div>
+              </div>
+            </fieldset>
+            <fieldset className="row">
+              <div className="form-group my-3">
+                <div className="col-xs-4 text-right">
+                  <div className="checkbox checkbox-success" onChange={this.onChangeEnableLineBreaksInComments}>
+                    <input type="checkbox" name="isEnabledLinebreaksInComments" checked={this.state.isEnabledLinebreaksInComments} />
+                    <label>
+                      { t('markdown_setting.Enable Line Break for comment') }
+                    </label>
+                    <p className="help-block">{ t('markdown_setting.Enable Line Break for comment desc') }</p>
+                  </div>
+                </div>
+              </div>
+            </fieldset>
+          </div>
+          <div className="form-group my-3">
+            <div className="col-xs-offset-4 col-xs-5">
+              <button type="submit" className="btn btn-primary" onClick={this.changeLineBreakSettings}>{ t('Update') }</button>
+            </div>
+          </div>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const LineBreakSettingWrapper = (props) => {
+  return createSubscribedElement(LineBreakSetting, props, [AppContainer]);
+};
+
+LineBreakSetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+};
+
+export default withTranslation()(LineBreakSettingWrapper);

+ 4 - 36
src/client/js/components/Admin/MarkdownSetting/MarkDownSetting.jsx

@@ -6,6 +6,7 @@ import { withTranslation } from 'react-i18next';
 import { createSubscribedElement } from '../../UnstatedUtils';
 
 import AppContainer from '../../../services/AppContainer';
+import LineBreakSetting from './LineBreakSetting';
 import XssForm from './XssForm';
 
 class MarkdownSetting extends React.Component {
@@ -13,11 +14,7 @@ class MarkdownSetting extends React.Component {
   constructor(props) {
     super(props);
 
-    const { appContainer } = this.props;
-
     this.state = {
-      isEnabledLinebreaks: appContainer.config.isEnabledLinebreaks,
-      isEnabledLinebreaksInComments: appContainer.config.isEnabledLinebreaksInComments,
       // TODO GW-220 get correct BreakOption value
       pageBreakOption: 1,
       // TODO GW-258 get correct custom regular expression
@@ -41,38 +38,9 @@ class MarkdownSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <div className="row my-3">
-          <div className="form-group">
-            <legend>{ t('markdown_setting.line_break_setting') }</legend>
-            <p className="well">{ t('markdown_setting.line_break_setting_desc') }</p>
-            <fieldset className="row">
-              <div className="form-group">
-                <label className="col-xs-4 control-label text-right">
-                  { t('markdown_setting.Enable Line Break') }
-                </label>
-                <div className="col-xs-5">
-                  <input type="checkbox" name="isEnabledLinebreaks" checked={this.state.isEnabledLinebreaks} onChange={this.handleInputChange} />
-                  <p className="help-block">{ t('markdown_setting.Enable Line Break desc') }</p>
-                </div>
-              </div>
-            </fieldset>
-            <fieldset className="row">
-              <div className="form-group my-3">
-                <label className="col-xs-4 control-label text-right">
-                  { t('markdown_setting.Enable Line Break for comment') }
-                </label>
-                <div className="col-xs-5">
-                  <input type="checkbox" name="isEnabledLinebreaksInComments" checked={this.state.isEnabledLinebreaksInComments} onChange={this.handleInputChange} />
-                  <p className="help-block">{ t('markdown_setting.Enable Line Break for comment desc') }</p>
-                </div>
-              </div>
-            </fieldset>
-          </div>
-          <div className="form-group my-3">
-            <div className="col-xs-offset-4 col-xs-5">
-              <button type="submit" className="btn btn-primary">{ t('Update') }</button>
-            </div>
-          </div>
+        <div>
+          {/* Line Break Setting */}
+          <LineBreakSetting />
         </div>
 
         <div className="row my-3">

+ 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) {

+ 7 - 45
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;
@@ -132,17 +131,17 @@ module.exports = function(crowi, app) {
 
   // app.post('/admin/markdown/lineBreaksSetting' , admin.markdown.lineBreaksSetting);
   actions.markdown.lineBreaksSetting = async function(req, res) {
-    const markdownSetting = req.form.markdownSetting;
 
-    if (req.form.isValid) {
-      await configManager.updateConfigsInTheSameNamespace('markdown', markdownSetting);
-      req.flash('successMessage', ['Successfully updated!']);
+    const array = req.body.params;
+
+    try {
+      await configManager.updateConfigsInTheSameNamespace('markdown', array);
+      return res.json(ApiResponse.success());
     }
-    else {
-      req.flash('errorMessage', req.form.errors);
+    catch (err) {
+      return res.json(ApiResponse.error(err));
     }
 
-    return res.redirect('/admin/markdown');
   };
 
   // app.post('/admin/markdown/presentationSetting' , admin.markdown.presentationSetting);
@@ -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));
     }
   });

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

@@ -92,7 +92,7 @@ module.exports = function(crowi, app) {
 
   // markdown admin
   app.get('/admin/markdown'                   , loginRequiredStrictly , adminRequired , admin.markdown.index);
-  app.post('/admin/markdown/lineBreaksSetting', loginRequiredStrictly , adminRequired , csrf, form.admin.markdown, admin.markdown.lineBreaksSetting); // change form name
+  app.post('/_api/admin/markdown/lineBreaksSetting', loginRequiredStrictly , adminRequired , csrf, form.admin.markdown, admin.markdown.lineBreaksSetting); // change form name
   app.post('/admin/markdown/xss-setting'      , loginRequiredStrictly , adminRequired , csrf, form.admin.markdownXss, admin.markdown.xssSetting);
   app.post('/admin/markdown/presentationSetting', loginRequiredStrictly , adminRequired , csrf, form.admin.markdownPresentation, admin.markdown.presentationSetting);
 
@@ -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');