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

Merge pull request #1491 from weseek/reactify-admin/enable-aws-setting

Reactify admin/enable aws setting
yusuketk 6 лет назад
Родитель
Сommit
ab38f36951

+ 43 - 68
src/client/js/components/Admin/App/AppSetting.jsx

@@ -8,6 +8,7 @@ import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 const logger = loggerFactory('growi:appSettings');
 
@@ -37,47 +38,35 @@ class AppSetting extends React.Component {
 
     return (
       <React.Fragment>
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label className="col-xs-3 control-label">{t('app_setting.Site Name')}</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[app:title]"
-                  type="text"
-                  name="title"
-                  defaultValue={adminAppContainer.state.title}
-                  onChange={(e) => { adminAppContainer.changeTitle(e.target.value) }}
-                  placeholder="GROWI"
-                />
-                <p className="help-block">{t('app_setting.sitename_change')}</p>
-              </div>
-            </div>
+        <div className="row md-5">
+          <label className="col-xs-3 control-label">{t('app_setting.Site Name')}</label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.title}
+              onChange={(e) => { adminAppContainer.changeTitle(e.target.value) }}
+              placeholder="GROWI"
+            />
+            <p className="help-block">{t('app_setting.sitename_change')}</p>
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label className="col-xs-3 control-label">{t('app_setting.Confidential name')}</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[app:confidential]"
-                  type="text"
-                  name="confidential"
-                  defaultValue={adminAppContainer.state.confidential}
-                  onChange={(e) => { adminAppContainer.changeConfidential(e.target.value) }}
-                  placeholder={t('app_setting.ex) internal use only')}
-                />
-                <p className="help-block">{t('app_setting.header_content')}</p>
-              </div>
-            </div>
+        <div className="row md-5">
+          <label className="col-xs-3 control-label">{t('app_setting.Confidential name')}</label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.confidential}
+              onChange={(e) => { adminAppContainer.changeConfidential(e.target.value) }}
+              placeholder={t('app_setting.ex) internal use only')}
+            />
+            <p className="help-block">{t('app_setting.header_content')}</p>
           </div>
         </div>
 
-        <div className="form-group">
+        <div className="row md-5">
           <label className="col-xs-3 control-label">{t('app_setting.Default Language for new users')}</label>
           <div className="col-xs-6">
             <div className="radio radio-primary radio-inline">
@@ -105,43 +94,29 @@ class AppSetting extends React.Component {
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label className="col-xs-3 control-label">{t('app_setting.File Uploading')}</label>
-              <div className="col-xs-6">
-                <div className="checkbox checkbox-info">
-                  <input
-                    type="checkbox"
-                    id="cbFileUpload"
-                    name="fileUpload"
-                    checked={adminAppContainer.state.fileUpload}
-                    onChange={(e) => { adminAppContainer.changeFileUpload(e.target.checked) }}
-                  />
-                  <label htmlFor="cbFileUpload">{t('app_setting.enable_files_except_image')}</label>
-                </div>
-
-                <p className="help-block">
-                  {t('app_setting.enable_files_except_image')}
-                  <br />
-                  {t('app_setting.attach_enable')}
-                </p>
-              </div>
+        <div className="row md-5">
+          <label className="col-xs-3 control-label">{t('app_setting.File Uploading')}</label>
+          <div className="col-xs-6">
+            <div className="checkbox checkbox-info">
+              <input
+                type="checkbox"
+                id="cbFileUpload"
+                name="fileUpload"
+                checked={adminAppContainer.state.fileUpload}
+                onChange={(e) => { adminAppContainer.changeFileUpload(e.target.checked) }}
+              />
+              <label htmlFor="cbFileUpload">{t('app_setting.enable_files_except_image')}</label>
             </div>
-          </div>
-        </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <div className="col-xs-offset-3 col-xs-6">
-                <button type="submit" className="btn btn-primary" onClick={this.submitHandler}>
-                  {t('app_setting.Update')}
-                </button>
-              </div>
-            </div>
+            <p className="help-block">
+              {t('app_setting.enable_files_except_image')}
+              <br />
+              {t('app_setting.attach_enable')}
+            </p>
           </div>
         </div>
+
+        <AdminUpdateButtonRow onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 99 - 95
src/client/js/components/Admin/App/AwsSetting.jsx

@@ -1,136 +1,139 @@
 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';
+import AdminAppContainer from '../../../services/AdminAppContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
+const logger = loggerFactory('growi:appSettings');
 
 class AwsSetting extends React.Component {
 
   constructor(props) {
     super(props);
 
-    this.state = {
-    };
+    this.submitHandler = this.submitHandler.bind(this);
   }
 
+  async submitHandler() {
+    const { t, adminAppContainer } = this.props;
+
+    try {
+      await adminAppContainer.updateAwsSettingHandler();
+      toastSuccess(t('app_setting.updated_app_setting'));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
 
   render() {
-    const { t } = this.props;
+    const { t, adminAppContainer } = this.props;
 
     return (
       <React.Fragment>
-        <p className="well">{t('app_setting.AWS_access') }<br />
-          { t('app_setting.No_SMTP_setting') }<br />
+        <p className="well">
+          {t('app_setting.AWS_access')}
           <br />
-
-          <span className="text-danger"><i className="ti-unlink"></i> {t('app_setting.change_setting') }</span>
+          {t('app_setting.No_SMTP_setting')}
+          <br />
+          <br />
+          <span className="text-danger">
+            <i className="ti-unlink"></i>
+            {t('app_setting.change_setting')}
+          </span>
         </p>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label htmlFor="settingForm[app:region]" className="col-xs-3 control-label">{ t('app_setting.region') }</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[app:region]"
-                  type="text"
-                  name="settingForm[aws:region]"
-                  placeholder="例: ap-northeast-1"
-                  value="{{ getConfig('crowi', 'aws:region') | default('') }}"
-                />
-              </div>
-            </div>
+        <div className="row mb-5">
+          <label className="col-xs-3 control-label">
+            {t('app_setting.region')}
+          </label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              placeholder={`${t('eg')} ap-northeast-1`}
+              defaultValue={adminAppContainer.state.region}
+              onChange={(e) => {
+                adminAppContainer.changeRegion(e.target.value);
+              }}
+            />
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label htmlFor="settingForm[aws:customEndpoint]" className="col-xs-3 control-label">{ t('app_setting.custom endpoint') }</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[aws:customEndpoint]"
-                  type="text"
-                  name="settingForm[aws:customEndpoint]"
-                  placeholder="例: http://localhost:9000"
-                  value="{{ getConfig('crowi', 'aws:customEndpoint') | default('') }}"
-                />
-                <p className="help-block">{ t('app_setting.custom_endpoint_change') }</p>
-              </div>
-            </div>
+        <div className="row mb-5">
+          <label className="col-xs-3 control-label">
+            {t('app_setting.custom endpoint')}
+          </label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              type="text"
+              placeholder={`${t('eg')} http://localhost:9000`}
+              defaultValue={adminAppContainer.state.customEndpoint}
+              onChange={(e) => {
+                adminAppContainer.changeCustomEndpoint(e.target.value);
+              }}
+            />
+            <p className="help-block">{t('app_setting.custom_endpoint_change')}</p>
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label htmlFor="settingForm[aws:bucket]" className="col-xs-3 control-label">{t('app_setting.bucket name')}</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[aws:bucket]"
-                  type="text"
-                  name="settingForm[aws:bucket]"
-                  placeholder="例: crowi"
-                  value="{{ getConfig('crowi', 'aws:bucket') | default('') }}"
-                />
-              </div>
-            </div>
+        <div className="row mb-5">
+          <label className="col-xs-3 control-label">
+            {t('app_setting.bucket name')}
+          </label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              type="text"
+              placeholder={`${t('eg')} crowi`}
+              defaultValue={adminAppContainer.state.bucket}
+              onChange={(e) => {
+                adminAppContainer.changeBucket(e.target.value);
+              }}
+            />
           </div>
         </div>
 
-
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label htmlFor="settingForm[aws:accessKeyId]" className="col-xs-3 control-label">Access Key ID</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[aws:accessKeyId]"
-                  type="text"
-                  name="settingForm[aws:accessKeyId]"
-                  value="{{ getConfig('crowi', 'aws:accessKeyId') | default('') }}"
-                />
-              </div>
-            </div>
+        <div className="row mb-5">
+          <label className="col-xs-3 control-label">
+            Access Key ID
+          </label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.accessKeyId}
+              onChange={(e) => {
+                adminAppContainer.changeAccessKeyId(e.target.value);
+              }}
+            />
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label htmlFor="settingForm[aws:secretAccessKey]" className="col-xs-3 control-label">Secret Access Key</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[aws:secretAccessKey]"
-                  type="text"
-                  name="settingForm[aws:secretAccessKey]"
-                  value="{{ getConfig('crowi', 'aws:secretAccessKey') | default('') }}"
-                />
-              </div>
-            </div>
+        <div className="row mb-5">
+          <label className="col-xs-3 control-label">
+            Secret Access Key
+          </label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.secretKey}
+              onChange={(e) => {
+                adminAppContainer.changeSecretKey(e.target.value);
+              }}
+            />
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <div className="col-xs-offset-3 col-xs-6">
-                <input type="hidden" name="_csrf" value="{{ csrf() }}" />
-                <button type="submit" className="btn btn-primary">
-                  {t('app_setting.Update')}
-                </button>
-              </div>
-            </div>
-          </div>
-        </div>
+        <AdminUpdateButtonRow onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }
@@ -141,12 +144,13 @@ class AwsSetting extends React.Component {
  * Wrapper component for using unstated
  */
 const AwsSettingWrapper = (props) => {
-  return createSubscribedElement(AwsSetting, props, [AppContainer]);
+  return createSubscribedElement(AwsSetting, props, [AppContainer, AdminAppContainer]);
 };
 
 AwsSetting.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
 };
 
 export default withTranslation()(AwsSettingWrapper);

+ 48 - 76
src/client/js/components/Admin/App/MailSetting.jsx

@@ -8,6 +8,7 @@ import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 const logger = loggerFactory('growi:appSettings');
 
@@ -38,91 +39,62 @@ class MailSetting extends React.Component {
     return (
       <React.Fragment>
         <p className="well">{t('app_setting.SMTP_used')} {t('app_setting.SMTP_but_AWS')}<br />{t('app_setting.neihter_of')}</p>
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label htmlFor="settingForm[mail.from]" className="col-xs-3 control-label">{t('app_setting.From e-mail address')}</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  id="settingForm[mail.from]"
-                  type="text"
-                  name="settingForm[mail:from]"
-                  placeholder={`${t('eg')} mail@growi.org`}
-                  defaultValue={adminAppContainer.state.fromAddress}
-                  onChange={(e) => { adminAppContainer.changeFromAddress(e.target.value) }}
-                />
-              </div>
-            </div>
+        <div className="row mb-5">
+          <label className="col-xs-3 control-label">{t('app_setting.From e-mail address')}</label>
+          <div className="col-xs-6">
+            <input
+              className="form-control"
+              type="text"
+              placeholder={`${t('eg')} mail@growi.org`}
+              defaultValue={adminAppContainer.state.fromAddress}
+              onChange={(e) => { adminAppContainer.changeFromAddress(e.target.value) }}
+            />
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <label className="col-xs-3 control-label">{ t('app_setting.SMTP settings') }</label>
-              <div className="col-xs-4">
-                <label>{ t('app_setting.Host') }</label>
-                <input
-                  className="form-control"
-                  type="text"
-                  name="settingForm[mail:smtpHost]"
-                  defaultValue={adminAppContainer.state.smtpHost}
-                  onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
-                />
-              </div>
-              <div className="col-xs-2">
-                <label>{ t('app_setting.Port') }</label>
-                <input
-                  className="form-control"
-                  type="text"
-                  name="settingForm[mail:smtpPort]"
-                  defaultValue={adminAppContainer.state.smtpPort}
-                  onChange={(e) => { adminAppContainer.changeSmtpPort(e.target.value) }}
-                />
-              </div>
-            </div>
+        <div className="row mb-5">
+          <label className="col-xs-3 control-label">{ t('app_setting.SMTP settings') }</label>
+          <div className="col-xs-4">
+            <label>{ t('app_setting.Host') }</label>
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.smtpHost}
+              onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
+            />
           </div>
-        </div>
-
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <div className="col-xs-3 col-xs-offset-3">
-                <label>{ t('app_setting.User') }</label>
-                <input
-                  className="form-control"
-                  type="text"
-                  name="settingForm[mail:smtpUser]"
-                  defaultValue={adminAppContainer.state.SmtpUser}
-                  onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
-                />
-              </div>
-              <div className="col-xs-3">
-                <label>{ t('Password') }</label>
-                <input
-                  className="form-control"
-                  type="password"
-                  name="settingForm[mail:smtpPassword]"
-                  defaultValue={adminAppContainer.state.smtpPassword}
-                  onChange={(e) => { adminAppContainer.changeSmtpPassword(e.target.value) }}
-                />
-              </div>
-            </div>
+          <div className="col-xs-2">
+            <label>{ t('app_setting.Port') }</label>
+            <input
+              className="form-control"
+              defaultValue={adminAppContainer.state.smtpPort}
+              onChange={(e) => { adminAppContainer.changeSmtpPort(e.target.value) }}
+            />
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <div className="col-xs-offset-3 col-xs-6">
-                <button type="submit" className="btn btn-primary" onClick={this.submitHandler}>
-                  {t('app_setting.Update')}
-                </button>
-              </div>
-            </div>
+        <div className="row mb-5">
+          <div className="col-xs-3 col-xs-offset-3">
+            <label>{ t('app_setting.User') }</label>
+            <input
+              className="form-control"
+              type="text"
+              defaultValue={adminAppContainer.state.SmtpUser}
+              onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
+            />
+          </div>
+          <div className="col-xs-3">
+            <label>{ t('Password') }</label>
+            <input
+              className="form-control"
+              type="password"
+              defaultValue={adminAppContainer.state.smtpPassword}
+              onChange={(e) => { adminAppContainer.changeSmtpPassword(e.target.value) }}
+            />
           </div>
         </div>
+
+        <AdminUpdateButtonRow onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 2 - 11
src/client/js/components/Admin/App/SiteUrlSetting.jsx

@@ -8,6 +8,7 @@ import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 const logger = loggerFactory('growi:appSettings');
 
@@ -84,17 +85,7 @@ class SiteUrlSetting extends React.Component {
           </div>
         </div>
 
-        <div className="row">
-          <div className="col-md-12">
-            <div className="form-group">
-              <div className="col-xs-offset-3 col-xs-6">
-                <button type="submit" className="btn btn-primary" onClick={this.submitHandler}>
-                  {t('app_setting.Update')}
-                </button>
-              </div>
-            </div>
-          </div>
-        </div>
+        <AdminUpdateButtonRow onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null} />
       </React.Fragment>
     );
   }

+ 69 - 0
src/client/js/services/AdminAppContainer.js

@@ -18,6 +18,7 @@ export default class AdminAppContainer extends Container {
     this.appContainer = appContainer;
 
     this.state = {
+      retrieveError: null,
       title: '',
       confidential: '',
       globalLang: '',
@@ -30,6 +31,11 @@ export default class AdminAppContainer extends Container {
       smtpPort: '',
       smtpUser: '',
       smtpPassword: '',
+      region: '',
+      customEndpoint: '',
+      bucket: '',
+      accessKeyId: '',
+      secretKey: '',
     };
 
     this.changeTitle = this.changeTitle.bind(this);
@@ -42,9 +48,15 @@ export default class AdminAppContainer extends Container {
     this.changeSmtpPort = this.changeSmtpPort.bind(this);
     this.changeSmtpUser = this.changeSmtpUser.bind(this);
     this.changeSmtpPassword = this.changeSmtpPassword.bind(this);
+    this.changeRegion = this.changeRegion.bind(this);
+    this.changeCustomEndpoint = this.changeCustomEndpoint.bind(this);
+    this.changeBucket = this.changeBucket.bind(this);
+    this.changeAccessKeyId = this.changeAccessKeyId.bind(this);
+    this.changeSecretKey = this.changeSecretKey.bind(this);
     this.updateAppSettingHandler = this.updateAppSettingHandler.bind(this);
     this.updateSiteUrlSettingHandler = this.updateSiteUrlSettingHandler.bind(this);
     this.updateMailSettingHandler = this.updateMailSettingHandler.bind(this);
+    this.updateAwsSettingHandler = this.updateAwsSettingHandler.bind(this);
   }
 
   /**
@@ -75,6 +87,11 @@ export default class AdminAppContainer extends Container {
         smtpPort: appSettingsParams.smtpPort,
         smtpUser: appSettingsParams.smtpUser,
         smtpPassword: appSettingsParams.smtpPassword,
+        region: appSettingsParams.region,
+        customEndpoint: appSettingsParams.customEndpoint,
+        bucket: appSettingsParams.bucket,
+        accessKeyId: appSettingsParams.accessKeyId,
+        secretKey: appSettingsParams.secretKey,
       });
 
     }
@@ -155,6 +172,41 @@ export default class AdminAppContainer extends Container {
     this.setState({ smtpPassword });
   }
 
+  /**
+   * Change region
+   */
+  changeRegion(region) {
+    this.setState({ region });
+  }
+
+  /**
+   * Change custom endpoint
+   */
+  changeCustomEndpoint(customEndpoint) {
+    this.setState({ customEndpoint });
+  }
+
+  /**
+   * Change bucket name
+   */
+  changeBucket(bucket) {
+    this.setState({ bucket });
+  }
+
+  /**
+   * Change access key id
+   */
+  changeAccessKeyId(accessKeyId) {
+    this.setState({ accessKeyId });
+  }
+
+  /**
+   * Change secret key
+   */
+  changeSecretKey(secretKey) {
+    this.setState({ secretKey });
+  }
+
   /**
    * Update app setting
    * @memberOf AdminAppContainer
@@ -202,4 +254,21 @@ export default class AdminAppContainer extends Container {
     return mailSettingParams;
   }
 
+  /**
+   * Update AWS setting
+   * @memberOf AdminAppContainer
+   * @return {Array} Appearance
+   */
+  async updateAwsSettingHandler() {
+    const response = await this.appContainer.apiv3.put('/app-settings/aws-setting', {
+      region: this.state.region,
+      customEndpoint: this.state.customEndpoint,
+      bucket: this.state.bucket,
+      accessKeyId: this.state.accessKeyId,
+      secretKey: this.state.secretKey,
+    });
+    const { awsSettingParams } = response.data;
+    return awsSettingParams;
+  }
+
 }

+ 0 - 10
src/server/form/admin/app.js

@@ -1,10 +0,0 @@
-const form = require('express-form');
-
-const field = form.field;
-
-module.exports = form(
-  field('settingForm[app:title]').trim(),
-  field('settingForm[app:confidential]'),
-  field('settingForm[app:globalLang]'),
-  field('settingForm[app:fileUpload]').trim().toBooleanStrict(),
-);

+ 0 - 11
src/server/form/admin/aws.js

@@ -1,11 +0,0 @@
-const form = require('express-form');
-
-const field = form.field;
-
-module.exports = form(
-  field('settingForm[aws:region]', 'リージョン').trim().is(/^[a-z]+-[a-z]+-\d+$/, 'リージョンには、AWSリージョン名を入力してください。 例: ap-northeast-1'),
-  field('settingForm[aws:customEndpoint]', 'カスタムエンドポイント').trim().is(/^(https?:\/\/[^/]+|)$/, 'カスタムエンドポイントは、http(s)://で始まるURLを指定してください。また、末尾の/は不要です。'),
-  field('settingForm[aws:bucket]', 'バケット名').trim(),
-  field('settingForm[aws:accessKeyId]', 'Access Key Id').trim().is(/^[\da-zA-Z]+$/),
-  field('settingForm[aws:secretAccessKey]', 'Secret Access Key').trim(),
-);

+ 0 - 11
src/server/form/admin/mail.js

@@ -1,11 +0,0 @@
-const form = require('express-form');
-
-const field = form.field;
-
-module.exports = form(
-  field('settingForm[mail:from]', 'メールFrom').trim(),
-  field('settingForm[mail:smtpHost]', 'SMTPホスト').trim(),
-  field('settingForm[mail:smtpPort]', 'SMTPポート').trim().toInt(),
-  field('settingForm[mail:smtpUser]', 'SMTPユーザー').trim(),
-  field('settingForm[mail:smtpPassword]', 'SMTPパスワード').trim(),
-);

+ 0 - 7
src/server/form/admin/siteUrl.js

@@ -1,7 +0,0 @@
-const form = require('express-form');
-
-const field = form.field;
-
-module.exports = form(
-  field('settingForm[app:siteUrl]').trim().isUrl(),
-);

+ 0 - 4
src/server/form/index.js

@@ -11,10 +11,6 @@ module.exports = {
     apiToken: require('./me/apiToken'),
   },
   admin: {
-    app: require('./admin/app'),
-    siteUrl: require('./admin/siteUrl'),
-    mail: require('./admin/mail'),
-    aws: require('./admin/aws'),
     plugin: require('./admin/plugin'),
     securityGeneral: require('./admin/securityGeneral'),
     securityPassportLocal: require('./admin/securityPassportLocal'),

+ 96 - 21
src/server/routes/apiv3/app-settings.js

@@ -9,29 +9,8 @@ const express = require('express');
 const router = express.Router();
 
 const { body } = require('express-validator/check');
-
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
-const validator = {
-  appSetting: [
-    body('title').trim(),
-    body('confidential'),
-    body('globalLang').isIn(['en-US', 'ja']),
-    body('fileUpload').isBoolean(),
-  ],
-  siteUrlSetting: [
-    body('siteUrl').trim(),
-  ],
-  mailSetting: [
-    body('fromAddress').trim(),
-    body('smtpHost').trim(),
-    body('smtpPort').trim(),
-    body('smtpUser').trim(),
-    body('smtpPassword').trim(),
-  ],
-};
-
-
 /**
  * @swagger
  *  tags:
@@ -85,6 +64,23 @@ const validator = {
  *          smtpPassword:
  *            type: String
  *            description: password of client's smtp server
+ *      AwsSettingParams:
+ *        type: object
+ *          region:
+ *            type: String
+ *            description: region of AWS S3
+ *          customEndpoint:
+ *            type: String
+ *            description: custom endpoint of AWS S3
+ *          bucket:
+ *            type: String
+ *            description: AWS S3 bucket name
+ *          accessKeyId:
+ *            type: String
+ *            description: accesskey id for authentification of AWS
+ *          secretKey:
+ *            type: String
+ *            description: secret key for authentification of AWS
  */
 
 module.exports = (crowi) => {
@@ -96,6 +92,32 @@ module.exports = (crowi) => {
 
   const { ApiV3FormValidator } = crowi.middlewares;
 
+  const validator = {
+    appSetting: [
+      body('title').trim(),
+      body('confidential'),
+      body('globalLang').isIn(['en-US', 'ja']),
+      body('fileUpload').isBoolean(),
+    ],
+    siteUrlSetting: [
+      body('siteUrl').trim().isURL({ require_tld: false }),
+    ],
+    mailSetting: [
+      body('fromAddress').trim().isEmail(),
+      body('smtpHost').trim(),
+      body('smtpPort').trim().isPort(),
+      body('smtpUser').trim(),
+      body('smtpPassword').trim(),
+    ],
+    awsSetting: [
+      body('region').trim().matches(/^[a-z]+-[a-z]+-\d+$/).withMessage('リージョンには、AWSリージョン名を入力してください。 例: ap-northeast-1'),
+      body('customEndpoint').trim().matches(/^(https?:\/\/[^/]+|)$/).withMessage('カスタムエンドポイントは、http(s)://で始まるURLを指定してください。また、末尾の/は不要です。'),
+      body('bucket').trim(),
+      body('accessKeyId').trim().matches(/^[\da-zA-Z]+$/),
+      body('secretKey').trim(),
+    ],
+  };
+
   /**
    * @swagger
    *
@@ -127,6 +149,11 @@ module.exports = (crowi) => {
       smtpPort: crowi.configManager.getConfig('crowi', 'mail:smtpPort'),
       smtpUser: crowi.configManager.getConfig('crowi', 'mail:smtpUser'),
       smtpPassword: crowi.configManager.getConfig('crowi', 'mail:smtpPassword'),
+      region: crowi.configManager.getConfig('crowi', 'aws:region'),
+      customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
+      bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
+      accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
+      secretKey: crowi.configManager.getConfig('crowi', 'aws:secretKey'),
     };
     return res.apiv3({ appSettingsParams });
 
@@ -330,5 +357,53 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /app-settings/aws-setting:
+   *      put:
+   *        tags: [AppSettings]
+   *        description: Update aws setting
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/AwsSettingParams'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update aws setting
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/AwsSettingParams'
+   */
+  router.put('/aws-setting', loginRequiredStrictly, adminRequired, csrf, validator.awsSetting, ApiV3FormValidator, async(req, res) => {
+    const requestAwsSettingParams = {
+      'aws:region': req.body.region,
+      'aws:customEndpoint': req.body.customEndpoint,
+      'aws:bucket': req.body.bucket,
+      'aws:accessKeyId': req.body.accessKeyId,
+      'aws:secretKey': req.body.secretKey,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestAwsSettingParams);
+      const awsSettingParams = {
+        region: crowi.configManager.getConfig('crowi', 'aws:region'),
+        customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
+        bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
+        accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
+        secretKey: crowi.configManager.getConfig('crowi', 'aws:secretKey'),
+      };
+      return res.apiv3({ awsSettingParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating aws setting';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-awsSetting-failed'));
+    }
+
+  });
   return router;
 };

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

@@ -59,7 +59,6 @@ module.exports = function(crowi, app) {
 
   app.get('/admin'                          , loginRequiredStrictly , adminRequired , admin.index);
   app.get('/admin/app'                      , loginRequiredStrictly , adminRequired , admin.app.index);
-  app.post('/_api/admin/settings/aws'       , loginRequiredStrictly , adminRequired , csrf, form.admin.aws, admin.api.appSetting);
   app.post('/_api/admin/settings/plugin'    , loginRequiredStrictly , adminRequired , csrf, form.admin.plugin, admin.api.appSetting);
 
   // security admin