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

Merge pull request #1331 from weseek/external-account-reflect-api

External account reflect api
Kaito Mishina 6 лет назад
Родитель
Сommit
598c832c66

+ 1 - 1
resource/locales/en-US/translation.json

@@ -705,7 +705,7 @@
     "send_new_password": "Please send the new password to the user.",
     "password_never_seen": "The temporary password can never be retrieved after this screen is closed.",
     "reset_password": "Reset Password",
-    "related_username": "Related user's <code>%s</code>",
+    "related_username": "Related user's ",
     "accept": "Accept",
     "deactivate_account":"Deactivate Account",
     "your_own":"You cannot deactivate your own account",

+ 1 - 1
resource/locales/ja/translation.json

@@ -689,7 +689,7 @@
     "send_new_password": "新規発行したパスワードを、対象ユーザーへ連絡してください。",
     "password_never_seen": "表示されたパスワードはこの画面を閉じると二度と表示できませんのでご注意ください。",
     "reset_password": "パスワードの再発行",
-    "related_username": "関連付けられているユーザーの <code>%s</code>",
+    "related_username": "関連付けられているユーザーの ",
     "accept": "承認する",
     "deactivate_account": "アカウント停止",
     "your_own": "自分自身のアカウントを停止することはできません",

+ 2 - 3
src/client/js/app.jsx

@@ -40,7 +40,7 @@ import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import MarkdownSetting from './components/Admin/MarkdownSetting/MarkDownSetting';
 import UserManagement from './components/Admin/UserManagement';
-import ManageExternalAccount from './components/Admin/Users/ManageExternalAccount';
+import ManageExternalAccount from './components/Admin/ManageExternalAccount';
 import UserGroupPage from './components/Admin/UserGroup/UserGroupPage';
 import Customize from './components/Admin/Customize/Customize';
 import ImportDataPage from './components/Admin/ImportDataPage';
@@ -111,7 +111,6 @@ let componentMappings = {
 
   'admin-full-text-search-management': <FullTextSearchManagement />,
   'admin-customize': <Customize />,
-  'admin-external-account-setting': <ManageExternalAccount />,
 
   'staff-credit': <StaffCredit />,
   'admin-importer': <ImportDataPage />,
@@ -172,7 +171,7 @@ if (adminUsersElem != null) {
   );
 }
 
-const adminExternalAccountsElem = document.getElementById('admin-external-account');
+const adminExternalAccountsElem = document.getElementById('admin-external-account-setting');
 if (adminExternalAccountsElem != null) {
   const adminExternalAccountsContainer = new AdminExternalAccountsContainer(appContainer);
   ReactDOM.render(

+ 80 - 0
src/client/js/components/Admin/ManageExternalAccount.jsx

@@ -0,0 +1,80 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import PaginationWrapper from '../PaginationWrapper';
+
+import { createSubscribedElement } from '../UnstatedUtils';
+import AppContainer from '../../services/AppContainer';
+import AdminExternalAccountsContainer from '../../services/AdminExternalAccountsContainer';
+import ExternalAccountTable from './Users/ExternalAccountTable';
+import { toastError } from '../../util/apiNotification';
+
+
+class ManageExternalAccount extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.xss = window.xss;
+    this.handleExternalAccountPage = this.handleExternalAccountPage.bind(this);
+  }
+
+  componentWillMount() {
+    this.handleExternalAccountPage(1);
+  }
+
+  async handleExternalAccountPage(selectedPage) {
+    try {
+      await this.props.adminExternalAccountsContainer.retrieveExternalAccountsByPagingNum(selectedPage);
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
+  render() {
+    const { t, adminExternalAccountsContainer } = this.props;
+
+    const pager = (
+      <div className="pull-right">
+        <PaginationWrapper
+          activePage={adminExternalAccountsContainer.state.activePage}
+          changePage={this.handleExternalAccountPage}
+          totalItemsCount={adminExternalAccountsContainer.state.totalAccounts}
+          pagingLimit={adminExternalAccountsContainer.state.pagingLimit}
+        />
+      </div>
+    );
+    return (
+      <Fragment>
+        <p>
+          <a className="btn btn-default" href="/admin/users">
+            <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
+            { t('user_management.back_to_user_management') }
+          </a>
+        </p>
+
+        <h2>{ t('user_management.external_account_list') }</h2>
+
+        { pager }
+        <ExternalAccountTable />
+        { pager }
+
+      </Fragment>
+    );
+  }
+
+}
+
+ManageExternalAccount.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminExternalAccountsContainer: PropTypes.instanceOf(AdminExternalAccountsContainer).isRequired,
+};
+
+const ManageExternalAccountWrapper = (props) => {
+  return createSubscribedElement(ManageExternalAccount, props, [AppContainer, AdminExternalAccountsContainer]);
+};
+
+
+export default withTranslation()(ManageExternalAccountWrapper);

+ 131 - 0
src/client/js/components/Admin/Users/ExternalAccountTable.jsx

@@ -0,0 +1,131 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import dateFnsFormat from 'date-fns/format';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import AppContainer from '../../../services/AppContainer';
+import AdminExternalAccountsContainer from '../../../services/AdminExternalAccountsContainer';
+
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+
+class ExternalAccountTable extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+
+    };
+    this.xss = window.xss;
+    this.removeExtenalAccount = this.removeExtenalAccount.bind(this);
+  }
+
+  // remove external-account
+  async removeExtenalAccount(externalAccountId) {
+    try {
+      const externalAccountName = await this.props.adminExternalAccountsContainer.removeExternal(externalAccountId);
+      toastSuccess(`Removed "${this.xss.process(externalAccountName)}"`);
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
+
+  render() {
+    const { t, adminExternalAccountsContainer } = this.props;
+    return (
+      <Fragment>
+        <table className="table table-bordered table-user-list">
+          <thead>
+            <tr>
+              <th width="120px">{ t('user_management.authentication_provider') }</th>
+              <th><code>accountId</code></th>
+              <th>{ t('user_management.related_username') }<code>username</code></th>
+              <th>
+                { t('user_management.password_setting') }
+                <div
+                  className="text-muted"
+                  data-toggle="popover"
+                  data-placement="top"
+                  data-trigger="hover focus"
+                  tabIndex="0"
+                  role="button"
+                  data-animation="false"
+                  data-html="true"
+                  data-content={t('user_management.password_setting_help')}
+                >
+                  <small>
+                    <i className="icon-question" aria-hidden="true"></i>
+                  </small>
+                </div>
+              </th>
+              <th width="100px">{ t('Created') }</th>
+              <th width="70px"></th>
+            </tr>
+          </thead>
+          <tbody>
+            {adminExternalAccountsContainer.state.externalAccounts.map((ea) => {
+              return (
+                <tr key={ea._id}>
+                  <td>{ea.providerType}</td>
+                  <td>
+                    <strong>{ea.accountId}</strong>
+                  </td>
+                  <td>
+                    <strong>{ ea.user.username }</strong>
+                  </td>
+                  <td>
+                    { ea.user.password
+                      ? (
+                        <span className="label label-info">
+                          { t('user_management.set') }
+                        </span>
+                      )
+                      : (
+                        <span className="label label-warning">
+                          { t('user_management.unset') }
+                        </span>
+                      )
+                    }
+                  </td>
+                  <td>{dateFnsFormat(new Date(ea.createdAt), 'yyyy-MM-dd')}</td>
+                  <td>
+                    <div className="btn-group admin-user-menu">
+                      <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
+                        <i className="icon-settings"></i> <span className="caret"></span>
+                      </button>
+                      <ul className="dropdown-menu" role="menu">
+                        <li className="dropdown-header">{ t('user_management.edit_menu') }</li>
+                        <li>
+                          <a onClick={() => { return this.removeExtenalAccount(ea._id) }}>
+                            <i className="icon-fw icon-fire text-danger"></i> { t('Delete') }
+                          </a>
+                        </li>
+                      </ul>
+                    </div>
+                  </td>
+                </tr>
+              );
+            })}
+          </tbody>
+        </table>
+      </Fragment>
+    );
+  }
+
+}
+
+ExternalAccountTable.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminExternalAccountsContainer: PropTypes.instanceOf(AdminExternalAccountsContainer).isRequired,
+};
+
+const ExternalAccountTableWrapper = (props) => {
+  return createSubscribedElement(ExternalAccountTable, props, [AppContainer, AdminExternalAccountsContainer]);
+};
+
+
+export default withTranslation()(ExternalAccountTableWrapper);

+ 0 - 78
src/client/js/components/Admin/Users/ManageExternalAccount.jsx

@@ -1,78 +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 ManageExternalAccount extends React.Component {
-
-  constructor(props) {
-    super(props);
-    // TODO GW-417
-    this.state = {
-    };
-  }
-
-
-  render() {
-    const { t } = this.props;
-
-    return (
-      <Fragment>
-        <p>
-          <a className="btn btn-default" href="/admin/users">
-            <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
-            { t('user_management.back_to_user_management') }
-          </a>
-        </p>
-
-        <h2>{ t('user_management.external_account_list') }</h2>
-
-        <table className="table table-bordered table-user-list">
-          <thead>
-            <tr>
-              <th width="120px">{ t('user_management.authentication_provider') }</th>
-              <th><code>accountId</code></th>
-              <th>{ t('user_management.related_username', 'username') }</th>
-              <th>
-                { t('user_management.password_setting') }
-                <div
-                  className="text-muted"
-                  data-toggle="popover"
-                  data-placement="top"
-                  data-trigger="hover focus"
-                  tabIndex="0"
-                  role="button"
-                  data-animation="false"
-                  data-html="true"
-                  data-content="<small>{{ t('user_management.password_setting_help') }}</small>"
-                >
-                  <small>
-                    <i className="icon-question" aria-hidden="true"></i>
-                  </small>
-                </div>
-              </th>
-              <th width="100px">{ t('Created') }</th>
-              <th width="70px"></th>
-            </tr>
-          </thead>
-          {/* TODO GW-417 */}
-        </table>
-      </Fragment>
-    );
-  }
-
-}
-
-const ManageExternalAccountWrapper = (props) => {
-  return createSubscribedElement(ManageExternalAccount, props, [AppContainer]);
-};
-
-ManageExternalAccount.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-};
-
-export default withTranslation()(ManageExternalAccountWrapper);

+ 6 - 22
src/client/js/services/AdminExternalAccountsContainer.js

@@ -4,13 +4,13 @@ import loggerFactory from '@alias/logger';
 
 
 // eslint-disable-next-line no-unused-vars
-const logger = loggerFactory('growi:services:UserGroupDetailContainer');
+const logger = loggerFactory('growi:services:AdminexternalaccountsContainer');
 
 /**
- * Service container for admin users page (Users.jsx)
+ * Service container for admin external-accounts page (ManageExternalAccountsContainer.jsx)
  * @extends {Container} unstated Container
  */
-export default class AdminExternalAccountContainer extends Container {
+export default class AdminExternalAccountsContainer extends Container {
 
   constructor(appContainer) {
     super();
@@ -18,7 +18,7 @@ export default class AdminExternalAccountContainer extends Container {
     this.appContainer = appContainer;
 
     this.state = {
-      exteranalAccounts: [],
+      externalAccounts: [],
       totalAccounts: 0,
       activePage: 1,
       pagingLimit: Infinity,
@@ -33,21 +33,6 @@ export default class AdminExternalAccountContainer extends Container {
     return 'AdminExternalAccountsContainer';
   }
 
-  /**
-   * update external-account
-   *
-   * @memberOf AdminExternalAccountsContainer
-   * @param {object} param update param for external account
-   * @return {object} response object
-   */
-  async updateExternalAccount(param) {
-    const res = await this.appContainer.apiv3.put(`/users/external-account/${this.state.externalAccounts._id}`, param);
-    const { exteranalAccounts } = res.data;
-
-    await this.setState({ exteranalAccounts });
-
-    return res;
-  }
 
   /**
    * syncExternalAccounts of selectedPage
@@ -59,13 +44,12 @@ export default class AdminExternalAccountContainer extends Container {
     const params = { page: selectedPage };
     const { data } = await this.appContainer.apiv3.get('/users/external-accounts', params);
 
-
     if (data.paginateResult == null) {
       throw new Error('data must conclude \'paginateResult\' property.');
     }
-    const { docs: exteranalAccounts, totalDocs: totalAccounts, limit: pagingLimit } = data.paginateResult;
+    const { docs: externalAccounts, totalDocs: totalAccounts, limit: pagingLimit } = data.paginateResult;
     this.setState({
-      exteranalAccounts,
+      externalAccounts,
       totalAccounts,
       pagingLimit,
       activePage: selectedPage,

+ 23 - 0
src/server/models/external-account.js

@@ -149,6 +149,29 @@ class ExternalAccount {
     return this.create({ providerType, accountId, user: user._id });
   }
 
+  /**
+   * find all entities with pagination
+   *
+   * @see https://github.com/edwardhotchkiss/mongoose-paginate
+   *
+   * @static
+   * @param {any} opts mongoose-paginate options object
+   * @returns {Promise<any>} mongoose-paginate result object
+   * @memberof ExternalAccount
+   */
+  static findAllWithPagination(opts) {
+    const query = {};
+    const options = Object.assign({ populate: 'user' }, opts);
+    if (options.sort == null) {
+      options.sort = { accountId: 1, createdAt: 1 };
+    }
+    if (options.limit == null) {
+      options.limit = ExternalAccount.DEFAULT_LIMIT;
+    }
+
+    return this.paginate(query, options);
+  }
+
 }
 
 module.exports = function(crowi) {

+ 7 - 17
src/server/routes/apiv3/users.js

@@ -354,23 +354,14 @@ module.exports = (crowi) => {
    */
   router.get('/external-accounts/', loginRequiredStrictly, adminRequired, async(req, res) => {
     const page = parseInt(req.query.page) || 1;
-
     try {
-      const paginateResult = await ExternalAccount.paginate(
-        { status: { $ne: ExternalAccount.STATUS_DELETED } },
-        {
-          sort: { status: 1, username: 1, createdAt: 1 },
-          page,
-          limit: PAGE_ITEMS,
-        },
-      );
+      const paginateResult = await ExternalAccount.findAllWithPagination({ page });
       return res.apiv3({ paginateResult });
     }
     catch (err) {
-      const msg = 'Error occurred in fetching external-account list';
+      const msg = 'Error occurred in fetching external-account list  ';
       logger.error(msg, err);
-      const errMsg = Object.assign(msg, err.message);
-      return res.apiv3Err(new ErrorV3(errMsg, 'external-account-list-fetch-failed'));
+      return res.apiv3Err(new ErrorV3(msg + err.message, 'external-account-list-fetch-failed'), 500);
     }
   });
 
@@ -403,18 +394,17 @@ module.exports = (crowi) => {
    */
 
   router.delete('/external-accounts/:id/remove', loginRequiredStrictly, adminRequired, ApiV3FormValidator, async(req, res) => {
-    const { id: deleteExtenralAccountId } = req.params.id;
+    const { id } = req.params;
 
     try {
-      const externalAccount = await ExternalAccount.findByIdAndRemove(deleteExtenralAccountId);
+      const externalAccount = await ExternalAccount.findByIdAndRemove(id);
 
       return res.apiv3({ externalAccount });
     }
     catch (err) {
-      const msg = 'Error occurred in deleting a external account';
+      const msg = 'Error occurred in deleting a external account  ';
       logger.error(msg, err);
-      const errMsg = Object.assign(msg, err.message);
-      return res.apiv3Err(new ErrorV3(errMsg, 'extenral-account-delete-failed'));
+      return res.apiv3Err(new ErrorV3(msg + err.message, 'extenral-account-delete-failed'));
     }
   });
   return router;