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

Merge pull request #9473 from weseek/support/new-config-manager

support: New config manager
Yuki Takei 1 год назад
Родитель
Сommit
e302f7e53c
100 измененных файлов с 857 добавлено и 840 удалено
  1. 0 1
      apps/app/package.json
  2. 0 3
      apps/app/public/static/locales/en_US/admin.json
  3. 0 3
      apps/app/public/static/locales/fr_FR/admin.json
  4. 0 3
      apps/app/public/static/locales/ja_JP/admin.json
  5. 0 3
      apps/app/public/static/locales/zh_CN/admin.json
  6. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeTitle.tsx
  7. 0 36
      apps/app/src/client/components/Admin/Security/FacebookSecuritySetting.jsx
  8. 0 8
      apps/app/src/client/components/Admin/Security/SecurityManagementContents.jsx
  9. 0 2
      apps/app/src/client/components/LoginForm/ExternalAuthButton.tsx
  10. 0 6
      apps/app/src/client/components/LoginForm/LoginForm.module.scss
  11. 0 6
      apps/app/src/client/components/Me/AssociateModal.tsx
  12. 1 1
      apps/app/src/components/ShareLinkPageView/ShareLinkAlert.tsx
  13. 27 27
      apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts
  14. 7 7
      apps/app/src/features/external-user-group/server/service/external-user-group-sync.ts
  15. 2 1
      apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.integ.ts
  16. 14 13
      apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.ts
  17. 12 10
      apps/app/src/features/external-user-group/server/service/ldap-user-group-sync.ts
  18. 2 2
      apps/app/src/features/openai/server/routes/middlewares/certify-ai-service.ts
  19. 16 5
      apps/app/src/features/openai/server/services/assistant/assistant.ts
  20. 1 1
      apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts
  21. 1 1
      apps/app/src/features/openai/server/services/client.ts
  22. 4 4
      apps/app/src/features/openai/server/services/cron/thread-deletion-cron.ts
  23. 4 4
      apps/app/src/features/openai/server/services/cron/vector-store-file-deletion-cron.ts
  24. 1 1
      apps/app/src/features/openai/server/services/is-ai-enabled.ts
  25. 3 3
      apps/app/src/features/openai/server/services/openai.ts
  26. 4 3
      apps/app/src/features/opentelemetry/server/start.ts
  27. 3 2
      apps/app/src/features/questionnaire/interfaces/condition.ts
  28. 7 30
      apps/app/src/features/questionnaire/interfaces/growi-info.ts
  29. 3 2
      apps/app/src/features/questionnaire/server/models/schema/condition.ts
  30. 7 2
      apps/app/src/features/questionnaire/server/models/schema/growi-info.ts
  31. 4 3
      apps/app/src/features/questionnaire/server/routes/apiv3/questionnaire.ts
  32. 3 3
      apps/app/src/features/questionnaire/server/service/questionnaire-cron.ts
  33. 9 7
      apps/app/src/features/questionnaire/server/service/questionnaire.ts
  34. 1 0
      apps/app/src/interfaces/activity.ts
  35. 13 1
      apps/app/src/interfaces/attachment.ts
  36. 3 2
      apps/app/src/interfaces/crowi-request.ts
  37. 17 0
      apps/app/src/interfaces/system.ts
  38. 38 0
      apps/app/src/migrations/19700101000000-foremost-1000-20241123211930-remove-ns-from-configs.js
  39. 2 6
      apps/app/src/migrations/20180927102719-init-serverurl.js
  40. 6 6
      apps/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js
  41. 3 6
      apps/app/src/migrations/20190618104011-add-config-app-installed.js
  42. 0 1
      apps/app/src/migrations/20200512005851-remove-behavior-type.js
  43. 0 1
      apps/app/src/migrations/20200827045151-remove-layout-setting.js
  44. 0 2
      apps/app/src/migrations/20200828024025-copy-aws-setting.js
  45. 0 3
      apps/app/src/migrations/20200901034313-update-mail-transmission.js
  46. 0 1
      apps/app/src/migrations/20200903080025-remove-timeline-type.js.js
  47. 0 5
      apps/app/src/migrations/20220311011114-convert-page-delete-config.js
  48. 0 1
      apps/app/src/migrations/20221014130200-remove-customize-is-saved-states-of-tab-changes.js
  49. 0 1
      apps/app/src/migrations/20230213090921-remove-presentation-configurations.js
  50. 48 44
      apps/app/src/pages/[[...path]].page.tsx
  51. 18 16
      apps/app/src/pages/_private-legacy-pages.page.tsx
  52. 19 17
      apps/app/src/pages/_search.page.tsx
  53. 1 1
      apps/app/src/pages/admin/ai-integration.page.tsx
  54. 2 2
      apps/app/src/pages/admin/audit-log.page.tsx
  55. 3 2
      apps/app/src/pages/admin/customize.page.tsx
  56. 1 1
      apps/app/src/pages/admin/data-transfer.page.tsx
  57. 5 5
      apps/app/src/pages/admin/index.page.tsx
  58. 1 1
      apps/app/src/pages/installer.page.tsx
  59. 10 11
      apps/app/src/pages/login/index.page.tsx
  60. 19 17
      apps/app/src/pages/me/[[...path]].page.tsx
  61. 28 23
      apps/app/src/pages/share/[[...path]].page.tsx
  62. 3 3
      apps/app/src/pages/tags.page.tsx
  63. 4 4
      apps/app/src/pages/trash.page.tsx
  64. 2 2
      apps/app/src/pages/user-activation.page.tsx
  65. 7 7
      apps/app/src/pages/utils/commons.ts
  66. 3 3
      apps/app/src/server/crowi/express-init.js
  67. 29 14
      apps/app/src/server/crowi/index.js
  68. 1 1
      apps/app/src/server/middlewares/certify-shared-page-attachment/validate-referer/retrieve-site-url.ts
  69. 1 1
      apps/app/src/server/middlewares/exclude-read-only-user.ts
  70. 2 4
      apps/app/src/server/models/config.ts
  71. 2 2
      apps/app/src/server/models/obsolete-page.js
  72. 1 1
      apps/app/src/server/models/page.ts
  73. 2 2
      apps/app/src/server/models/slack-app-integration.js
  74. 8 8
      apps/app/src/server/models/user.js
  75. 2 2
      apps/app/src/server/routes/admin.js
  76. 3 2
      apps/app/src/server/routes/apiv3/activity.ts
  77. 4 4
      apps/app/src/server/routes/apiv3/admin-home.ts
  78. 106 105
      apps/app/src/server/routes/apiv3/app-settings.js
  79. 1 1
      apps/app/src/server/routes/apiv3/attachment.js
  80. 53 52
      apps/app/src/server/routes/apiv3/customize-setting.js
  81. 3 3
      apps/app/src/server/routes/apiv3/forgot-password.js
  82. 3 3
      apps/app/src/server/routes/apiv3/g2g-transfer.ts
  83. 5 5
      apps/app/src/server/routes/apiv3/import.js
  84. 2 2
      apps/app/src/server/routes/apiv3/index.js
  85. 1 1
      apps/app/src/server/routes/apiv3/installer.ts
  86. 20 19
      apps/app/src/server/routes/apiv3/markdown-setting.js
  87. 7 6
      apps/app/src/server/routes/apiv3/notification-setting.js
  88. 2 2
      apps/app/src/server/routes/apiv3/page-listing.ts
  89. 1 1
      apps/app/src/server/routes/apiv3/page/create-page.ts
  90. 1 1
      apps/app/src/server/routes/apiv3/page/index.ts
  91. 4 4
      apps/app/src/server/routes/apiv3/pages/index.js
  92. 2 2
      apps/app/src/server/routes/apiv3/personal-setting.js
  93. 1 1
      apps/app/src/server/routes/apiv3/revisions.js
  94. 152 151
      apps/app/src/server/routes/apiv3/security-settings/index.js
  95. 1 1
      apps/app/src/server/routes/apiv3/share-links.js
  96. 8 7
      apps/app/src/server/routes/apiv3/slack-integration-legacy-settings.js
  97. 17 16
      apps/app/src/server/routes/apiv3/slack-integration-settings.js
  98. 12 10
      apps/app/src/server/routes/apiv3/slack-integration.js
  99. 1 1
      apps/app/src/server/routes/apiv3/staffs.js
  100. 6 6
      apps/app/src/server/routes/apiv3/user-activation.ts

+ 0 - 1
apps/app/package.json

@@ -21,7 +21,6 @@
     "dev:pre:styles": "pnpm run pre:styles --mode dev",
     "dev:pre:styles": "pnpm run pre:styles --mode dev",
     "dev:migrate-mongo": "cross-env NODE_ENV=development pnpm run ts-node node_modules/migrate-mongo/bin/migrate-mongo",
     "dev:migrate-mongo": "cross-env NODE_ENV=development pnpm run ts-node node_modules/migrate-mongo/bin/migrate-mongo",
     "dev:migrate": "pnpm run dev:migrate:status > tmp/cache/migration-status.out && pnpm run dev:migrate:up",
     "dev:migrate": "pnpm run dev:migrate:status > tmp/cache/migration-status.out && pnpm run dev:migrate:up",
-    "dev:migrate:create": "pnpm run dev:migrate-mongo create -f config/migrate-mongo-config.js",
     "dev:migrate:status": "pnpm run dev:migrate-mongo status -f config/migrate-mongo-config.js",
     "dev:migrate:status": "pnpm run dev:migrate-mongo status -f config/migrate-mongo-config.js",
     "dev:migrate:up": "pnpm run dev:migrate-mongo up -f config/migrate-mongo-config.js",
     "dev:migrate:up": "pnpm run dev:migrate-mongo up -f config/migrate-mongo-config.js",
     "dev:migrate:down": "pnpm run dev:migrate-mongo down -f config/migrate-mongo-config.js",
     "dev:migrate:down": "pnpm run dev:migrate-mongo down -f config/migrate-mongo-config.js",

+ 0 - 3
apps/app/public/static/locales/en_US/admin.json

@@ -184,9 +184,6 @@
         "register_5": "Copy and paste your ClientID and Client Secret above",
         "register_5": "Copy and paste your ClientID and Client Secret above",
         "updated_google": "Succeeded to update Google OAuth setting"
         "updated_google": "Succeeded to update Google OAuth setting"
       },
       },
-      "Facebook": {
-        "name": "Facebook OAuth"
-      },
       "GitHub": {
       "GitHub": {
         "enable_github": "Enable GitHub OAuth",
         "enable_github": "Enable GitHub OAuth",
         "name": "GitHub OAuth",
         "name": "GitHub OAuth",

+ 0 - 3
apps/app/public/static/locales/fr_FR/admin.json

@@ -184,9 +184,6 @@
         "register_5": "Copier l'ID client et Secret client ci-dessus",
         "register_5": "Copier l'ID client et Secret client ci-dessus",
         "updated_google": "Paramètres mis à jour"
         "updated_google": "Paramètres mis à jour"
       },
       },
-      "Facebook": {
-        "name": "Facebook OAuth"
-      },
       "GitHub": {
       "GitHub": {
         "enable_github": "Activer GitHub OAuth",
         "enable_github": "Activer GitHub OAuth",
         "name": "GitHub OAuth",
         "name": "GitHub OAuth",

+ 0 - 3
apps/app/public/static/locales/ja_JP/admin.json

@@ -193,9 +193,6 @@
         "register_5": "上記フォームにクライアントIDとクライアントシークレットを入力",
         "register_5": "上記フォームにクライアントIDとクライアントシークレットを入力",
         "updated_google": "Google OAuth を更新しました"
         "updated_google": "Google OAuth を更新しました"
       },
       },
-      "Facebook": {
-        "name": "Facebook OAuth"
-      },
       "GitHub": {
       "GitHub": {
         "enable_github": "GitHub OAuth を有効にする",
         "enable_github": "GitHub OAuth を有効にする",
         "name": "GitHub OAuth",
         "name": "GitHub OAuth",

+ 0 - 3
apps/app/public/static/locales/zh_CN/admin.json

@@ -193,9 +193,6 @@
 				"register_5": "Copy and paste your ClientID and Client Secret above",
 				"register_5": "Copy and paste your ClientID and Client Secret above",
 				"updated_google": "Succeeded to update Google OAuth setting"
 				"updated_google": "Succeeded to update Google OAuth setting"
 			},
 			},
-			"Facebook": {
-				"name": "Facebook OAuth"
-			},
 			"GitHub": {
 			"GitHub": {
 				"enable_github": "Enable GitHub OAuth",
 				"enable_github": "Enable GitHub OAuth",
 				"name": "GitHub OAuth",
 				"name": "GitHub OAuth",

+ 1 - 1
apps/app/src/client/components/Admin/Customize/CustomizeTitle.tsx

@@ -16,7 +16,7 @@ export const CustomizeTitle: FC = () => {
 
 
   const { data: customizeTitle } = useCustomizeTitle();
   const { data: customizeTitle } = useCustomizeTitle();
 
 
-  const [currentCustomizeTitle, setCrrentCustomizeTitle] = useState(customizeTitle);
+  const [currentCustomizeTitle, setCrrentCustomizeTitle] = useState(customizeTitle ?? '');
 
 
   const onClickSubmit = async() => {
   const onClickSubmit = async() => {
     try {
     try {

+ 0 - 36
apps/app/src/client/components/Admin/Security/FacebookSecuritySetting.jsx

@@ -1,36 +0,0 @@
-import React from 'react';
-
-import { withTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-
-import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-
-class FacebookSecurityManagement extends React.Component {
-
-  render() {
-    const { t } = this.props;
-    return (
-      <>
-        <h2 className="alert-anchor border-bottom">
-          Facebook OAuth { t('admin:security_settings.configuration') }
-        </h2>
-
-        <p className="card custom-card">(TBD)</p>
-      </>
-    );
-  }
-
-}
-
-
-FacebookSecurityManagement.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
-};
-
-const FacebookSecurityManagementWrapper = withUnstatedContainers(FacebookSecurityManagement, [AdminGeneralSecurityContainer]);
-
-export default withTranslation()(FacebookSecurityManagementWrapper);

+ 0 - 8
apps/app/src/client/components/Admin/Security/SecurityManagementContents.jsx

@@ -6,7 +6,6 @@ import { TabContent, TabPane } from 'reactstrap';
 
 
 import CustomNav from '../../CustomNavigation/CustomNav';
 import CustomNav from '../../CustomNavigation/CustomNav';
 
 
-// import FacebookSecuritySetting from './FacebookSecuritySetting';
 import GitHubSecuritySetting from './GitHubSecuritySetting';
 import GitHubSecuritySetting from './GitHubSecuritySetting';
 import GoogleSecuritySetting from './GoogleSecuritySetting';
 import GoogleSecuritySetting from './GoogleSecuritySetting';
 import LdapSecuritySetting from './LdapSecuritySetting';
 import LdapSecuritySetting from './LdapSecuritySetting';
@@ -53,10 +52,6 @@ const SecurityManagementContents = () => {
         Icon: () => <span className="growi-custom-icons align-bottom">github</span>,
         Icon: () => <span className="growi-custom-icons align-bottom">github</span>,
         i18n: 'GitHub',
         i18n: 'GitHub',
       },
       },
-      // passport_facebook: {
-      //   Icon: () => <span className="growi-custom-icons align-bottom">facebook</span>,
-      //   i18n: '(TBD) Facebook',
-      // },
     };
     };
   }, []);
   }, []);
 
 
@@ -114,9 +109,6 @@ const SecurityManagementContents = () => {
           <TabPane tabId="passport_github">
           <TabPane tabId="passport_github">
             {activeComponents.has('passport_github') && <GitHubSecuritySetting />}
             {activeComponents.has('passport_github') && <GitHubSecuritySetting />}
           </TabPane>
           </TabPane>
-          {/* <TabPane tabId="passport_facebook">
-            {activeComponents.has('passport_facebook') && <FacebookSecuritySetting />}
-          </TabPane> */}
         </TabContent>
         </TabContent>
       </div>
       </div>
     </div>
     </div>

+ 0 - 2
apps/app/src/client/components/LoginForm/ExternalAuthButton.tsx

@@ -6,7 +6,6 @@ import { useTranslation } from 'next-i18next';
 const authIcon = {
 const authIcon = {
   [IExternalAuthProviderType.google]: <span className="growi-custom-icons align-bottom">google</span>,
   [IExternalAuthProviderType.google]: <span className="growi-custom-icons align-bottom">google</span>,
   [IExternalAuthProviderType.github]: <span className="growi-custom-icons align-bottom">github</span>,
   [IExternalAuthProviderType.github]: <span className="growi-custom-icons align-bottom">github</span>,
-  [IExternalAuthProviderType.facebook]: <span className="growi-custom-icons align-bottom">facebook</span>,
   [IExternalAuthProviderType.oidc]: <span className="growi-custom-icons align-bottom">openid</span>,
   [IExternalAuthProviderType.oidc]: <span className="growi-custom-icons align-bottom">openid</span>,
   [IExternalAuthProviderType.saml]: <span className="material-symbols-outlined align-bottom">key</span>,
   [IExternalAuthProviderType.saml]: <span className="material-symbols-outlined align-bottom">key</span>,
 };
 };
@@ -14,7 +13,6 @@ const authIcon = {
 const authLabel = {
 const authLabel = {
   [IExternalAuthProviderType.google]: 'Google',
   [IExternalAuthProviderType.google]: 'Google',
   [IExternalAuthProviderType.github]: 'GitHub',
   [IExternalAuthProviderType.github]: 'GitHub',
-  [IExternalAuthProviderType.facebook]: 'Facebook',
   [IExternalAuthProviderType.oidc]: 'OIDC',
   [IExternalAuthProviderType.oidc]: 'OIDC',
   [IExternalAuthProviderType.saml]: 'SAML',
   [IExternalAuthProviderType.saml]: 'SAML',
 };
 };

+ 0 - 6
apps/app/src/client/components/LoginForm/LoginForm.module.scss

@@ -82,12 +82,6 @@
       --bs-btn-active-bg: #{rgba(#403D3E, 0.7)};
       --bs-btn-active-bg: #{rgba(#403D3E, 0.7)};
     }
     }
 
 
-    .btn-auth-facebook {
-      --bs-btn-bg: #{rgba(#29487d, 0.4)};
-      --bs-btn-hover-bg: #{rgba(#29487d, 0.9)};
-      --bs-btn-active-bg: #{rgba(#29487d, 0.9)};
-    }
-
     .btn-auth-oidc {
     .btn-auth-oidc {
       --bs-btn-bg: #{rgba(#835B1A, 0.4)};
       --bs-btn-bg: #{rgba(#835B1A, 0.4)};
       --bs-btn-hover-bg: #{rgba(#835B1A, 0.8)};
       --bs-btn-hover-bg: #{rgba(#835B1A, 0.8)};

+ 0 - 6
apps/app/src/client/components/Me/AssociateModal.tsx

@@ -80,12 +80,6 @@ const AssociateModal = (props: Props): JSX.Element => {
             >
             >
               <span className="growi-custom-icons">google</span> (TBD) Google OAuth
               <span className="growi-custom-icons">google</span> (TBD) Google OAuth
             </NavLink>
             </NavLink>
-            {/* <NavLink
-              className={`${activeTab === 4 ? 'active' : ''} d-flex gap-1 align-items-center`}
-              onClick={() => setActiveTab(4)}
-            >
-              <span className="growi-custom-icons">facebook</span> (TBD) Facebook
-            </NavLink> */}
           </Nav>
           </Nav>
           <TabContent activeTab={activeTab}>
           <TabContent activeTab={activeTab}>
             <TabPane tabId={1}>
             <TabPane tabId={1}>

+ 1 - 1
apps/app/src/components/ShareLinkPageView/ShareLinkAlert.tsx

@@ -42,7 +42,7 @@ const ShareLinkAlert: FC<Props> = (props: Props) => {
   return (
   return (
     <p className={`alert alert-${alertColor} px-4 d-edit-none`}>
     <p className={`alert alert-${alertColor} px-4 d-edit-none`}>
       <span className="material-symbols-outlined me-1">link</span>
       <span className="material-symbols-outlined me-1">link</span>
-      {(expiredAt === null ? <span>{t('page_page.notice.no_deadline')}</span>
+      {(expiredAt == null ? <span>{t('page_page.notice.no_deadline')}</span>
       // eslint-disable-next-line react/no-danger
       // eslint-disable-next-line react/no-danger
         : <span dangerouslySetInnerHTML={{ __html: t('page_page.notice.expiration', { expiredAt }) }} />
         : <span dangerouslySetInnerHTML={{ __html: t('page_page.notice.expiration', { expiredAt }) }} />
       )}
       )}

+ 27 - 27
apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts

@@ -216,14 +216,14 @@ module.exports = (crowi: Crowi): Router => {
 
 
   router.get('/ldap/sync-settings', loginRequiredStrictly, adminRequired, (req: AuthorizedRequest, res: ApiV3Response) => {
   router.get('/ldap/sync-settings', loginRequiredStrictly, adminRequired, (req: AuthorizedRequest, res: ApiV3Response) => {
     const settings = {
     const settings = {
-      ldapGroupSearchBase: configManager?.getConfig('crowi', 'external-user-group:ldap:groupSearchBase'),
-      ldapGroupMembershipAttribute: configManager?.getConfig('crowi', 'external-user-group:ldap:groupMembershipAttribute'),
-      ldapGroupMembershipAttributeType: configManager?.getConfig('crowi', 'external-user-group:ldap:groupMembershipAttributeType'),
-      ldapGroupChildGroupAttribute: configManager?.getConfig('crowi', 'external-user-group:ldap:groupChildGroupAttribute'),
-      autoGenerateUserOnLdapGroupSync: configManager?.getConfig('crowi', 'external-user-group:ldap:autoGenerateUserOnGroupSync'),
-      preserveDeletedLdapGroups: configManager?.getConfig('crowi', 'external-user-group:ldap:preserveDeletedGroups'),
-      ldapGroupNameAttribute: configManager?.getConfig('crowi', 'external-user-group:ldap:groupNameAttribute'),
-      ldapGroupDescriptionAttribute: configManager?.getConfig('crowi', 'external-user-group:ldap:groupDescriptionAttribute'),
+      ldapGroupSearchBase: configManager.getConfig('external-user-group:ldap:groupSearchBase'),
+      ldapGroupMembershipAttribute: configManager.getConfig('external-user-group:ldap:groupMembershipAttribute'),
+      ldapGroupMembershipAttributeType: configManager.getConfig('external-user-group:ldap:groupMembershipAttributeType'),
+      ldapGroupChildGroupAttribute: configManager.getConfig('external-user-group:ldap:groupChildGroupAttribute'),
+      autoGenerateUserOnLdapGroupSync: configManager.getConfig('external-user-group:ldap:autoGenerateUserOnGroupSync'),
+      preserveDeletedLdapGroups: configManager.getConfig('external-user-group:ldap:preserveDeletedGroups'),
+      ldapGroupNameAttribute: configManager.getConfig('external-user-group:ldap:groupNameAttribute'),
+      ldapGroupDescriptionAttribute: configManager.getConfig('external-user-group:ldap:groupDescriptionAttribute'),
     };
     };
 
 
     return res.apiv3(settings);
     return res.apiv3(settings);
@@ -231,14 +231,14 @@ module.exports = (crowi: Crowi): Router => {
 
 
   router.get('/keycloak/sync-settings', loginRequiredStrictly, adminRequired, (req: AuthorizedRequest, res: ApiV3Response) => {
   router.get('/keycloak/sync-settings', loginRequiredStrictly, adminRequired, (req: AuthorizedRequest, res: ApiV3Response) => {
     const settings = {
     const settings = {
-      keycloakHost: configManager?.getConfig('crowi', 'external-user-group:keycloak:host'),
-      keycloakGroupRealm: configManager?.getConfig('crowi', 'external-user-group:keycloak:groupRealm'),
-      keycloakGroupSyncClientRealm: configManager?.getConfig('crowi', 'external-user-group:keycloak:groupSyncClientRealm'),
-      keycloakGroupSyncClientID: configManager?.getConfig('crowi', 'external-user-group:keycloak:groupSyncClientID'),
-      keycloakGroupSyncClientSecret: configManager?.getConfig('crowi', 'external-user-group:keycloak:groupSyncClientSecret'),
-      autoGenerateUserOnKeycloakGroupSync: configManager?.getConfig('crowi', 'external-user-group:keycloak:autoGenerateUserOnGroupSync'),
-      preserveDeletedKeycloakGroups: configManager?.getConfig('crowi', 'external-user-group:keycloak:preserveDeletedGroups'),
-      keycloakGroupDescriptionAttribute: configManager?.getConfig('crowi', 'external-user-group:keycloak:groupDescriptionAttribute'),
+      keycloakHost: configManager.getConfig('external-user-group:keycloak:host'),
+      keycloakGroupRealm: configManager.getConfig('external-user-group:keycloak:groupRealm'),
+      keycloakGroupSyncClientRealm: configManager.getConfig('external-user-group:keycloak:groupSyncClientRealm'),
+      keycloakGroupSyncClientID: configManager.getConfig('external-user-group:keycloak:groupSyncClientID'),
+      keycloakGroupSyncClientSecret: configManager.getConfig('external-user-group:keycloak:groupSyncClientSecret'),
+      autoGenerateUserOnKeycloakGroupSync: configManager.getConfig('external-user-group:keycloak:autoGenerateUserOnGroupSync'),
+      preserveDeletedKeycloakGroups: configManager.getConfig('external-user-group:keycloak:preserveDeletedGroups'),
+      keycloakGroupDescriptionAttribute: configManager.getConfig('external-user-group:keycloak:groupDescriptionAttribute'),
     };
     };
 
 
     return res.apiv3(settings);
     return res.apiv3(settings);
@@ -269,7 +269,7 @@ module.exports = (crowi: Crowi): Router => {
     }
     }
 
 
     try {
     try {
-      await configManager.updateConfigsInTheSameNamespace('crowi', params, true);
+      await configManager.updateConfigs(params, { skipPubsub: true });
       return res.apiv3({}, 204);
       return res.apiv3({}, 204);
     }
     }
     catch (err) {
     catch (err) {
@@ -301,7 +301,7 @@ module.exports = (crowi: Crowi): Router => {
       };
       };
 
 
       try {
       try {
-        await configManager.updateConfigsInTheSameNamespace('crowi', params, true);
+        await configManager.updateConfigs(params, { skipPubsub: true });
         return res.apiv3({}, 204);
         return res.apiv3({}, 204);
       }
       }
       catch (err) {
       catch (err) {
@@ -319,7 +319,7 @@ module.exports = (crowi: Crowi): Router => {
       );
       );
     }
     }
 
 
-    const isLdapEnabled = await configManager.getConfig('crowi', 'security:passport-ldap:isEnabled');
+    const isLdapEnabled = await configManager.getConfig('security:passport-ldap:isEnabled');
     if (!isLdapEnabled) {
     if (!isLdapEnabled) {
       return res.apiv3Err(
       return res.apiv3Err(
         new ErrorV3('Authentication using ldap is not set', 'external_user_group.ldap.auth_not_set'), 422,
         new ErrorV3('Authentication using ldap is not set', 'external_user_group.ldap.auth_not_set'), 422,
@@ -349,25 +349,25 @@ module.exports = (crowi: Crowi): Router => {
     }
     }
 
 
     const getAuthProviderType = () => {
     const getAuthProviderType = () => {
-      let kcHost = configManager?.getConfig('crowi', 'external-user-group:keycloak:host');
+      let kcHost = configManager.getConfig('external-user-group:keycloak:host');
       if (kcHost?.endsWith('/')) {
       if (kcHost?.endsWith('/')) {
         kcHost = kcHost.slice(0, -1);
         kcHost = kcHost.slice(0, -1);
       }
       }
-      const kcGroupRealm = configManager?.getConfig('crowi', 'external-user-group:keycloak:groupRealm');
+      const kcGroupRealm = configManager.getConfig('external-user-group:keycloak:groupRealm');
 
 
       // starts with kcHost, contains kcGroupRealm in path
       // starts with kcHost, contains kcGroupRealm in path
       // see: https://regex101.com/r/3ihDmf/1
       // see: https://regex101.com/r/3ihDmf/1
       const regex = new RegExp(`^${kcHost}/.*/${kcGroupRealm}(/|$).*`);
       const regex = new RegExp(`^${kcHost}/.*/${kcGroupRealm}(/|$).*`);
 
 
-      const isOidcEnabled = configManager.getConfig('crowi', 'security:passport-oidc:isEnabled');
-      const oidcIssuerHost = configManager.getConfig('crowi', 'security:passport-oidc:issuerHost');
+      const isOidcEnabled = configManager.getConfig('security:passport-oidc:isEnabled');
+      const oidcIssuerHost = configManager.getConfig('security:passport-oidc:issuerHost');
 
 
-      if (isOidcEnabled && regex.test(oidcIssuerHost)) return 'oidc';
+      if (isOidcEnabled && oidcIssuerHost != null && regex.test(oidcIssuerHost)) return 'oidc';
 
 
-      const isSamlEnabled = configManager.getConfig('crowi', 'security:passport-saml:isEnabled');
-      const samlEntryPoint = configManager.getConfig('crowi', 'security:passport-saml:entryPoint');
+      const isSamlEnabled = configManager.getConfig('security:passport-saml:isEnabled');
+      const samlEntryPoint = configManager.getConfig('security:passport-saml:entryPoint');
 
 
-      if (isSamlEnabled && regex.test(samlEntryPoint)) return 'saml';
+      if (isSamlEnabled && samlEntryPoint != null && regex.test(samlEntryPoint)) return 'saml';
 
 
       return null;
       return null;
     };
     };

+ 7 - 7
apps/app/src/features/external-user-group/server/service/external-user-group-sync.ts

@@ -1,17 +1,17 @@
-import type { IUserHasId } from '@growi/core';
+import type { IExternalAuthProviderType, IUserHasId } from '@growi/core';
 
 
 import { SocketEventName } from '~/interfaces/websocket';
 import { SocketEventName } from '~/interfaces/websocket';
 import ExternalAccount from '~/server/models/external-account';
 import ExternalAccount from '~/server/models/external-account';
 import S2sMessage from '~/server/models/vo/s2s-message';
 import S2sMessage from '~/server/models/vo/s2s-message';
-import { S2sMessagingService } from '~/server/service/s2s-messaging/base';
-import { S2sMessageHandlable } from '~/server/service/s2s-messaging/handlable';
+import type { S2sMessagingService } from '~/server/service/s2s-messaging/base';
+import type { S2sMessageHandlable } from '~/server/service/s2s-messaging/handlable';
 import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 import { batchProcessPromiseAll } from '~/utils/promise';
 import { batchProcessPromiseAll } from '~/utils/promise';
 
 
 import { configManager } from '../../../../server/service/config-manager';
 import { configManager } from '../../../../server/service/config-manager';
 import { externalAccountService } from '../../../../server/service/external-account';
 import { externalAccountService } from '../../../../server/service/external-account';
-import {
+import type {
   ExternalGroupProviderType, ExternalUserGroupTreeNode, ExternalUserInfo, IExternalUserGroupHasId,
   ExternalGroupProviderType, ExternalUserGroupTreeNode, ExternalUserInfo, IExternalUserGroupHasId,
 } from '../../interfaces/external-user-group';
 } from '../../interfaces/external-user-group';
 import ExternalUserGroup from '../models/external-user-group';
 import ExternalUserGroup from '../models/external-user-group';
@@ -37,7 +37,7 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
 
 
   groupProviderType: ExternalGroupProviderType; // name of external service that contains user group info (e.g: ldap, keycloak)
   groupProviderType: ExternalGroupProviderType; // name of external service that contains user group info (e.g: ldap, keycloak)
 
 
-  authProviderType: string | null; // auth provider type (e.g: ldap, oidc). Has to be set before syncExternalUserGroups execution.
+  authProviderType: IExternalAuthProviderType | null; // auth provider type (e.g: ldap, oidc). Has to be set before syncExternalUserGroups execution.
 
 
   socketIoService: any;
   socketIoService: any;
 
 
@@ -93,7 +93,7 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
     if (this.authProviderType == null) throw new Error('auth provider type is not set');
     if (this.authProviderType == null) throw new Error('auth provider type is not set');
     if (this.syncStatus.isExecutingSync) throw new Error('External user group sync is already being executed');
     if (this.syncStatus.isExecutingSync) throw new Error('External user group sync is already being executed');
 
 
-    const preserveDeletedLdapGroups: boolean = configManager?.getConfig('crowi', `external-user-group:${this.groupProviderType}:preserveDeletedGroups`);
+    const preserveDeletedLdapGroups = configManager.getConfig(`external-user-group:${this.groupProviderType}:preserveDeletedGroups`);
     const existingExternalUserGroupIds: string[] = [];
     const existingExternalUserGroupIds: string[] = [];
 
 
     const socket = this.socketIoService?.getAdminSocket();
     const socket = this.socketIoService?.getAdminSocket();
@@ -183,7 +183,7 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
     const authProviderType = this.authProviderType;
     const authProviderType = this.authProviderType;
     if (authProviderType == null) throw new Error('auth provider type is not set');
     if (authProviderType == null) throw new Error('auth provider type is not set');
 
 
-    const autoGenerateUserOnGroupSync = configManager?.getConfig('crowi', `external-user-group:${this.groupProviderType}:autoGenerateUserOnGroupSync`);
+    const autoGenerateUserOnGroupSync = configManager.getConfig(`external-user-group:${this.groupProviderType}:autoGenerateUserOnGroupSync`);
 
 
     const getExternalAccount = async() => {
     const getExternalAccount = async() => {
       if (autoGenerateUserOnGroupSync && externalAccountService != null) {
       if (autoGenerateUserOnGroupSync && externalAccountService != null) {

+ 2 - 1
apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.integ.ts

@@ -146,7 +146,8 @@ describe('KeycloakUserGroupSyncService.generateExternalUserGroupTrees', () => {
   };
   };
 
 
   beforeAll(async() => {
   beforeAll(async() => {
-    await configManager.updateConfigsInTheSameNamespace('crowi', configParams, true);
+    await configManager.loadConfigs();
+    await configManager.updateConfigs(configParams, { skipPubsub: true });
     keycloakUserGroupSyncService = new KeycloakUserGroupSyncService(null, null);
     keycloakUserGroupSyncService = new KeycloakUserGroupSyncService(null, null);
     keycloakUserGroupSyncService.init('oidc');
     keycloakUserGroupSyncService.init('oidc');
   });
   });

+ 14 - 13
apps/app/src/features/external-user-group/server/service/keycloak-user-group-sync.ts

@@ -1,13 +1,14 @@
 import KeycloakAdminClient from '@keycloak/keycloak-admin-client';
 import KeycloakAdminClient from '@keycloak/keycloak-admin-client';
-import GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation';
-import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';
+import type GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation';
+import type UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';
 
 
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
-import { S2sMessagingService } from '~/server/service/s2s-messaging/base';
+import type { S2sMessagingService } from '~/server/service/s2s-messaging/base';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 import { batchProcessPromiseAll } from '~/utils/promise';
 import { batchProcessPromiseAll } from '~/utils/promise';
 
 
-import { ExternalGroupProviderType, ExternalUserGroupTreeNode, ExternalUserInfo } from '../../interfaces/external-user-group';
+import type { ExternalUserGroupTreeNode, ExternalUserInfo } from '../../interfaces/external-user-group';
+import { ExternalGroupProviderType } from '../../interfaces/external-user-group';
 
 
 import ExternalUserGroupSyncService from './external-user-group-sync';
 import ExternalUserGroupSyncService from './external-user-group-sync';
 
 
@@ -22,9 +23,9 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
 
 
   kcAdminClient: KeycloakAdminClient;
   kcAdminClient: KeycloakAdminClient;
 
 
-  realm: string; // realm that contains the groups
+  realm: string | undefined; // realm that contains the groups
 
 
-  groupDescriptionAttribute: string; // attribute to map to group description
+  groupDescriptionAttribute: string | undefined; // attribute to map to group description
 
 
   isInitialized = false;
   isInitialized = false;
 
 
@@ -34,10 +35,10 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
   }
   }
 
 
   init(authProviderType: 'oidc' | 'saml'): void {
   init(authProviderType: 'oidc' | 'saml'): void {
-    const kcHost = configManager?.getConfig('crowi', 'external-user-group:keycloak:host');
-    const kcGroupRealm = configManager?.getConfig('crowi', 'external-user-group:keycloak:groupRealm');
-    const kcGroupSyncClientRealm = configManager?.getConfig('crowi', 'external-user-group:keycloak:groupSyncClientRealm');
-    const kcGroupDescriptionAttribute = configManager?.getConfig('crowi', 'external-user-group:keycloak:groupDescriptionAttribute');
+    const kcHost = configManager.getConfig('external-user-group:keycloak:host');
+    const kcGroupRealm = configManager.getConfig('external-user-group:keycloak:groupRealm');
+    const kcGroupSyncClientRealm = configManager.getConfig('external-user-group:keycloak:groupSyncClientRealm');
+    const kcGroupDescriptionAttribute = configManager.getConfig('external-user-group:keycloak:groupDescriptionAttribute');
 
 
     this.kcAdminClient = new KeycloakAdminClient({ baseUrl: kcHost, realmName: kcGroupSyncClientRealm });
     this.kcAdminClient = new KeycloakAdminClient({ baseUrl: kcHost, realmName: kcGroupSyncClientRealm });
     this.realm = kcGroupRealm;
     this.realm = kcGroupRealm;
@@ -70,12 +71,12 @@ export class KeycloakUserGroupSyncService extends ExternalUserGroupSyncService {
    * Authenticate to group sync client using client credentials grant type
    * Authenticate to group sync client using client credentials grant type
    */
    */
   private async auth(): Promise<void> {
   private async auth(): Promise<void> {
-    const kcGroupSyncClientID: string = configManager.getConfig('crowi', 'external-user-group:keycloak:groupSyncClientID');
-    const kcGroupSyncClientSecret: string = configManager.getConfig('crowi', 'external-user-group:keycloak:groupSyncClientSecret');
+    const kcGroupSyncClientID = configManager.getConfig('external-user-group:keycloak:groupSyncClientID');
+    const kcGroupSyncClientSecret = configManager.getConfig('external-user-group:keycloak:groupSyncClientSecret');
 
 
     await this.kcAdminClient.auth({
     await this.kcAdminClient.auth({
       grantType: 'client_credentials',
       grantType: 'client_credentials',
-      clientId: kcGroupSyncClientID,
+      clientId: kcGroupSyncClientID ?? '',
       clientSecret: kcGroupSyncClientSecret,
       clientSecret: kcGroupSyncClientSecret,
     });
     });
   }
   }

+ 12 - 10
apps/app/src/features/external-user-group/server/service/ldap-user-group-sync.ts

@@ -1,12 +1,14 @@
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
-import { ldapService, SearchResultEntry } from '~/server/service/ldap';
-import PassportService from '~/server/service/passport';
-import { S2sMessagingService } from '~/server/service/s2s-messaging/base';
+import type { SearchResultEntry } from '~/server/service/ldap';
+import { ldapService } from '~/server/service/ldap';
+import type PassportService from '~/server/service/passport';
+import type { S2sMessagingService } from '~/server/service/s2s-messaging/base';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 import { batchProcessPromiseAll } from '~/utils/promise';
 import { batchProcessPromiseAll } from '~/utils/promise';
 
 
+import type { ExternalUserGroupTreeNode, ExternalUserInfo } from '../../interfaces/external-user-group';
 import {
 import {
-  ExternalGroupProviderType, ExternalUserGroupTreeNode, ExternalUserInfo, LdapGroupMembershipAttributeType,
+  ExternalGroupProviderType, LdapGroupMembershipAttributeType,
 } from '../../interfaces/external-user-group';
 } from '../../interfaces/external-user-group';
 
 
 import ExternalUserGroupSyncService from './external-user-group-sync';
 import ExternalUserGroupSyncService from './external-user-group-sync';
@@ -47,11 +49,11 @@ export class LdapUserGroupSyncService extends ExternalUserGroupSyncService {
   }
   }
 
 
   override async generateExternalUserGroupTrees(): Promise<ExternalUserGroupTreeNode[]> {
   override async generateExternalUserGroupTrees(): Promise<ExternalUserGroupTreeNode[]> {
-    const groupChildGroupAttribute: string = configManager.getConfig('crowi', 'external-user-group:ldap:groupChildGroupAttribute');
-    const groupMembershipAttribute: string = configManager.getConfig('crowi', 'external-user-group:ldap:groupMembershipAttribute');
-    const groupNameAttribute: string = configManager.getConfig('crowi', 'external-user-group:ldap:groupNameAttribute');
-    const groupDescriptionAttribute: string = configManager.getConfig('crowi', 'external-user-group:ldap:groupDescriptionAttribute');
-    const groupBase: string = ldapService.getGroupSearchBase();
+    const groupChildGroupAttribute = configManager.getConfig('external-user-group:ldap:groupChildGroupAttribute');
+    const groupMembershipAttribute = configManager.getConfig('external-user-group:ldap:groupMembershipAttribute');
+    const groupNameAttribute = configManager.getConfig('external-user-group:ldap:groupNameAttribute');
+    const groupDescriptionAttribute = configManager.getConfig('external-user-group:ldap:groupDescriptionAttribute');
+    const groupBase = ldapService.getGroupSearchBase();
 
 
     const groupEntries = await ldapService.searchGroupDir();
     const groupEntries = await ldapService.searchGroupDir();
 
 
@@ -117,7 +119,7 @@ export class LdapUserGroupSyncService extends ExternalUserGroupSyncService {
   }
   }
 
 
   private async getUserInfo(userId: string): Promise<ExternalUserInfo | null> {
   private async getUserInfo(userId: string): Promise<ExternalUserInfo | null> {
-    const groupMembershipAttributeType = configManager?.getConfig('crowi', 'external-user-group:ldap:groupMembershipAttributeType');
+    const groupMembershipAttributeType = configManager.getConfig('external-user-group:ldap:groupMembershipAttributeType');
     const attrMapUsername = this.passportService.getLdapAttrNameMappedToUsername();
     const attrMapUsername = this.passportService.getLdapAttrNameMappedToUsername();
     const attrMapName = this.passportService.getLdapAttrNameMappedToName();
     const attrMapName = this.passportService.getLdapAttrNameMappedToName();
     const attrMapMail = this.passportService.getLdapAttrNameMappedToMail();
     const attrMapMail = this.passportService.getLdapAttrNameMappedToMail();

+ 2 - 2
apps/app/src/features/openai/server/routes/middlewares/certify-ai-service.ts

@@ -8,7 +8,7 @@ import { OpenaiServiceTypes } from '../../../interfaces/ai';
 const logger = loggerFactory('growi:middlewares:certify-ai-service');
 const logger = loggerFactory('growi:middlewares:certify-ai-service');
 
 
 export const certifyAiService = (req: Request, res: Response & { apiv3Err }, next: NextFunction): void => {
 export const certifyAiService = (req: Request, res: Response & { apiv3Err }, next: NextFunction): void => {
-  const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
+  const aiEnabled = configManager.getConfig('app:aiEnabled');
 
 
   if (!aiEnabled) {
   if (!aiEnabled) {
     const message = 'AI_ENABLED is not true';
     const message = 'AI_ENABLED is not true';
@@ -16,7 +16,7 @@ export const certifyAiService = (req: Request, res: Response & { apiv3Err }, nex
     return res.apiv3Err(message, 403);
     return res.apiv3Err(message, 403);
   }
   }
 
 
-  const openaiServiceType = configManager.getConfig('crowi', 'openai:serviceType');
+  const openaiServiceType = configManager.getConfig('openai:serviceType');
   if (openaiServiceType == null || !OpenaiServiceTypes.includes(openaiServiceType)) {
   if (openaiServiceType == null || !OpenaiServiceTypes.includes(openaiServiceType)) {
     const message = 'AI_SERVICE_TYPE is missing or contains an invalid value';
     const message = 'AI_SERVICE_TYPE is missing or contains an invalid value';
     logger.error(message);
     logger.error(message);

+ 16 - 5
apps/app/src/features/openai/server/services/assistant/assistant.ts

@@ -15,9 +15,20 @@ const AssistantDefaultModelMap: Record<AssistantType, OpenAI.Chat.ChatModel> = {
   [AssistantType.CHAT]: 'gpt-4o-mini',
   [AssistantType.CHAT]: 'gpt-4o-mini',
 };
 };
 
 
+const isValidChatModel = (model: string): model is OpenAI.Chat.ChatModel => {
+  return model.startsWith('gpt-');
+};
+
 const getAssistantModelByType = (type: AssistantType): OpenAI.Chat.ChatModel => {
 const getAssistantModelByType = (type: AssistantType): OpenAI.Chat.ChatModel => {
-  const configKey = `openai:assistantModel:${type.toLowerCase()}`;
-  return configManager.getConfig('crowi', configKey) ?? AssistantDefaultModelMap[type];
+  const configValue = type === AssistantType.SEARCH
+    ? undefined // TODO: add the value for 'openai:assistantModel:search' to config-definition.ts
+    : configManager.getConfig('openai:assistantModel:chat');
+
+  if (typeof configValue === 'string' && isValidChatModel(configValue)) {
+    return configValue;
+  }
+
+  return AssistantDefaultModelMap[type];
 };
 };
 
 
 type AssistantType = typeof AssistantType[keyof typeof AssistantType];
 type AssistantType = typeof AssistantType[keyof typeof AssistantType];
@@ -45,7 +56,7 @@ const findAssistantByName = async(assistantName: string): Promise<OpenAI.Beta.As
 };
 };
 
 
 const getOrCreateAssistant = async(type: AssistantType, nameSuffix?: string): Promise<OpenAI.Beta.Assistant> => {
 const getOrCreateAssistant = async(type: AssistantType, nameSuffix?: string): Promise<OpenAI.Beta.Assistant> => {
-  const appSiteUrl = configManager.getConfig('crowi', 'app:siteUrl');
+  const appSiteUrl = configManager.getConfig('app:siteUrl');
   const assistantName = `GROWI ${type} Assistant for ${appSiteUrl}${nameSuffix != null ? ` ${nameSuffix}` : ''}`;
   const assistantName = `GROWI ${type} Assistant for ${appSiteUrl}${nameSuffix != null ? ` ${nameSuffix}` : ''}`;
   const assistantModel = getAssistantModelByType(type);
   const assistantModel = getAssistantModelByType(type);
 
 
@@ -57,7 +68,7 @@ const getOrCreateAssistant = async(type: AssistantType, nameSuffix?: string): Pr
       }));
       }));
 
 
   // update instructions
   // update instructions
-  const instructions = configManager.getConfig('crowi', 'openai:chatAssistantInstructions');
+  const instructions = configManager.getConfig('openai:chatAssistantInstructions');
   openaiClient.beta.assistants.update(assistant.id, {
   openaiClient.beta.assistants.update(assistant.id, {
     instructions,
     instructions,
     model: assistantModel,
     model: assistantModel,
@@ -75,7 +86,7 @@ const getOrCreateAssistant = async(type: AssistantType, nameSuffix?: string): Pr
 
 
 //   searchAssistant = await getOrCreateAssistant(AssistantType.SEARCH);
 //   searchAssistant = await getOrCreateAssistant(AssistantType.SEARCH);
 //   openaiClient.beta.assistants.update(searchAssistant.id, {
 //   openaiClient.beta.assistants.update(searchAssistant.id, {
-//     instructions: configManager.getConfig('crowi', 'openai:searchAssistantInstructions'),
+//     instructions: configManager.getConfig('openai:searchAssistantInstructions'),
 //     tools: [{ type: 'file_search' }],
 //     tools: [{ type: 'file_search' }],
 //   });
 //   });
 
 

+ 1 - 1
apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts

@@ -13,7 +13,7 @@ export class OpenaiClientDelegator implements IOpenaiClientDelegator {
 
 
   constructor() {
   constructor() {
     // Retrieve OpenAI related values from environment variables
     // Retrieve OpenAI related values from environment variables
-    const apiKey = configManager.getConfig('crowi', 'openai:apiKey');
+    const apiKey = configManager.getConfig('openai:apiKey');
 
 
     const isValid = [apiKey].every(value => value != null);
     const isValid = [apiKey].every(value => value != null);
     if (!isValid) {
     if (!isValid) {

+ 1 - 1
apps/app/src/features/openai/server/services/client.ts

@@ -3,5 +3,5 @@ import OpenAI from 'openai';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 
 
 export const openaiClient = new OpenAI({
 export const openaiClient = new OpenAI({
-  apiKey: configManager?.getConfig('crowi', 'openai:apiKey'), // This is the default and can be omitted
+  apiKey: configManager.getConfig('openai:apiKey'), // This is the default and can be omitted
 });
 });

+ 4 - 4
apps/app/src/features/openai/server/services/cron/thread-deletion-cron.ts

@@ -37,10 +37,10 @@ export class ThreadDeletionCronService {
     }
     }
 
 
     this.openaiService = openaiService;
     this.openaiService = openaiService;
-    this.threadDeletionCronExpression = configManager.getConfig('crowi', 'openai:threadDeletionCronExpression');
-    this.threadDeletionCronMaxMinutesUntilRequest = configManager.getConfig('crowi', 'app:openaiThreadDeletionCronMaxMinutesUntilRequest');
-    this.threadDeletionBarchSize = configManager.getConfig('crowi', 'openai:threadDeletionBarchSize');
-    this.threadDeletionApiCallInterval = configManager.getConfig('crowi', 'openai:threadDeletionApiCallInterval');
+    this.threadDeletionCronExpression = configManager.getConfig('openai:threadDeletionCronExpression');
+    this.threadDeletionCronMaxMinutesUntilRequest = configManager.getConfig('app:openaiThreadDeletionCronMaxMinutesUntilRequest');
+    this.threadDeletionBarchSize = configManager.getConfig('openai:threadDeletionBarchSize');
+    this.threadDeletionApiCallInterval = configManager.getConfig('openai:threadDeletionApiCallInterval');
 
 
     this.cronJob?.stop();
     this.cronJob?.stop();
     this.cronJob = this.generateCronJob();
     this.cronJob = this.generateCronJob();

+ 4 - 4
apps/app/src/features/openai/server/services/cron/vector-store-file-deletion-cron.ts

@@ -36,10 +36,10 @@ export class VectorStoreFileDeletionCronService {
     }
     }
 
 
     this.openaiService = openaiService;
     this.openaiService = openaiService;
-    this.vectorStoreFileDeletionCronExpression = configManager.getConfig('crowi', 'openai:vectorStoreFileDeletionCronExpression');
-    this.vectorStoreFileDeletionCronMaxMinutesUntilRequest = configManager.getConfig('crowi', 'app:openaiVectorStoreFileDeletionCronMaxMinutesUntilRequest');
-    this.vectorStoreFileDeletionBarchSize = configManager.getConfig('crowi', 'openai:vectorStoreFileDeletionBarchSize');
-    this.vectorStoreFileDeletionApiCallInterval = configManager.getConfig('crowi', 'openai:vectorStoreFileDeletionApiCallInterval');
+    this.vectorStoreFileDeletionCronExpression = configManager.getConfig('openai:vectorStoreFileDeletionCronExpression');
+    this.vectorStoreFileDeletionCronMaxMinutesUntilRequest = configManager.getConfig('app:openaiVectorStoreFileDeletionCronMaxMinutesUntilRequest');
+    this.vectorStoreFileDeletionBarchSize = configManager.getConfig('openai:vectorStoreFileDeletionBarchSize');
+    this.vectorStoreFileDeletionApiCallInterval = configManager.getConfig('openai:vectorStoreFileDeletionApiCallInterval');
 
 
     this.cronJob?.stop();
     this.cronJob?.stop();
     this.cronJob = this.generateCronJob();
     this.cronJob = this.generateCronJob();

+ 1 - 1
apps/app/src/features/openai/server/services/is-ai-enabled.ts

@@ -1,3 +1,3 @@
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 
 
-export const isAiEnabled = (): boolean => configManager.getConfig('crowi', 'app:aiEnabled');
+export const isAiEnabled = (): boolean => configManager.getConfig('app:aiEnabled');

+ 3 - 3
apps/app/src/features/openai/server/services/openai.ts

@@ -49,7 +49,7 @@ export interface IOpenaiService {
 class OpenaiService implements IOpenaiService {
 class OpenaiService implements IOpenaiService {
 
 
   private get client() {
   private get client() {
-    const openaiServiceType = configManager.getConfig('crowi', 'openai:serviceType');
+    const openaiServiceType = configManager.getConfig('openai:serviceType');
     return getClient({ openaiServiceType });
     return getClient({ openaiServiceType });
   }
   }
 
 
@@ -363,8 +363,8 @@ export const getOpenaiService = (): IOpenaiService | undefined => {
     return instance;
     return instance;
   }
   }
 
 
-  const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
-  const openaiServiceType = configManager.getConfig('crowi', 'openai:serviceType');
+  const aiEnabled = configManager.getConfig('app:aiEnabled');
+  const openaiServiceType = configManager.getConfig('openai:serviceType');
   if (aiEnabled && openaiServiceType != null && OpenaiServiceTypes.includes(openaiServiceType)) {
   if (aiEnabled && openaiServiceType != null && OpenaiServiceTypes.includes(openaiServiceType)) {
     instance = new OpenaiService();
     instance = new OpenaiService();
     return instance;
     return instance;

+ 4 - 3
apps/app/src/features/opentelemetry/server/start.ts

@@ -14,7 +14,7 @@ let sdkInstance: NodeSDK;
  * Since otel library sees it.
  * Since otel library sees it.
  */
  */
 function overwriteSdkDisabled(): void {
 function overwriteSdkDisabled(): void {
-  const instrumentationEnabled = configManager.getConfig('crowi', 'otel:enabled');
+  const instrumentationEnabled = configManager.getConfig('otel:enabled');
 
 
   if (instrumentationEnabled && (
   if (instrumentationEnabled && (
     process.env.OTEL_SDK_DISABLED === 'true'
     process.env.OTEL_SDK_DISABLED === 'true'
@@ -44,7 +44,7 @@ export const startInstrumentation = async(version: string): Promise<void> => {
 
 
   overwriteSdkDisabled();
   overwriteSdkDisabled();
 
 
-  const instrumentationEnabled = configManager.getConfig('crowi', 'otel:enabled');
+  const instrumentationEnabled = configManager.getConfig('otel:enabled');
   if (instrumentationEnabled) {
   if (instrumentationEnabled) {
 
 
     logger.info(`GROWI now collects anonymous telemetry.
     logger.info(`GROWI now collects anonymous telemetry.
@@ -65,7 +65,8 @@ For more information, see https://docs.growi.org/en/admin-guide/telemetry.html.
     const { NodeSDK } = await import('@opentelemetry/sdk-node');
     const { NodeSDK } = await import('@opentelemetry/sdk-node');
     const { generateNodeSDKConfiguration } = await import('./node-sdk-configuration');
     const { generateNodeSDKConfiguration } = await import('./node-sdk-configuration');
 
 
-    const serviceInstanceId = configManager.getConfig('crowi', 'otel:serviceInstanceId');
+    const serviceInstanceId = configManager.getConfig('otel:serviceInstanceId')
+      ?? 'generated-appSiteUrlHashed'; // TODO: generated appSiteUrlHashed
 
 
     sdkInstance = new NodeSDK(generateNodeSDKConfiguration(serviceInstanceId, version));
     sdkInstance = new NodeSDK(generateNodeSDKConfiguration(serviceInstanceId, version));
     sdkInstance.start();
     sdkInstance.start();

+ 3 - 2
apps/app/src/features/questionnaire/interfaces/condition.ts

@@ -1,7 +1,8 @@
 import type { HasObjectId } from '@growi/core';
 import type { HasObjectId } from '@growi/core';
 
 
-import { GrowiServiceType } from './growi-info';
-import { UserType } from './user-info';
+import type { GrowiServiceType } from '~/interfaces/system';
+
+import type { UserType } from './user-info';
 
 
 
 
 interface UserCondition {
 interface UserCondition {

+ 7 - 30
apps/app/src/features/questionnaire/interfaces/growi-info.ts

@@ -1,37 +1,14 @@
-import * as os from 'node:os';
+import type * as os from 'node:os';
 
 
 import { IExternalAuthProviderType } from '@growi/core';
 import { IExternalAuthProviderType } from '@growi/core';
 
 
-export const GrowiServiceType = {
-  cloud: 'cloud',
-  privateCloud: 'private-cloud',
-  onPremise: 'on-premise',
-  others: 'others',
-} as const;
-export const GrowiWikiType = { open: 'open', closed: 'closed' } as const;
-export const GrowiAttachmentType = {
-  aws: 'aws',
-  gcs: 'gcs',
-  gcp: 'gcp',
-  azure: 'azure',
-  gridfs: 'gridfs',
-  mongo: 'mongo',
-  mongodb: 'mongodb',
-  local: 'local',
-  none: 'none',
-} as const;
-export const GrowiDeploymentType = {
-  officialHelmChart: 'official-helm-chart',
-  growiDockerCompose: 'growi-docker-compose',
-  node: 'node',
-  others: 'others',
-} as const;
-export const GrowiExternalAuthProviderType = IExternalAuthProviderType;
+import type { AttachmentMethodType } from '~/interfaces/attachment';
+import type { GrowiDeploymentType, GrowiServiceType } from '~/interfaces/system';
 
 
-export type GrowiServiceType = typeof GrowiServiceType[keyof typeof GrowiServiceType]
+export const GrowiWikiType = { open: 'open', closed: 'closed' } as const;
 type GrowiWikiType = typeof GrowiWikiType[keyof typeof GrowiWikiType]
 type GrowiWikiType = typeof GrowiWikiType[keyof typeof GrowiWikiType]
-export type GrowiAttachmentType = typeof GrowiAttachmentType[keyof typeof GrowiAttachmentType]
-export type GrowiDeploymentType = typeof GrowiDeploymentType[keyof typeof GrowiDeploymentType]
+
+export const GrowiExternalAuthProviderType = IExternalAuthProviderType;
 export type GrowiExternalAuthProviderType = typeof GrowiExternalAuthProviderType[keyof typeof GrowiExternalAuthProviderType]
 export type GrowiExternalAuthProviderType = typeof GrowiExternalAuthProviderType[keyof typeof GrowiExternalAuthProviderType]
 
 
 interface IGrowiOSInfo {
 interface IGrowiOSInfo {
@@ -51,7 +28,7 @@ export interface IGrowiInfo {
   currentUsersCount: number
   currentUsersCount: number
   currentActiveUsersCount: number
   currentActiveUsersCount: number
   wikiType: GrowiWikiType
   wikiType: GrowiWikiType
-  attachmentType: GrowiAttachmentType
+  attachmentType: AttachmentMethodType
   activeExternalAccountTypes?: GrowiExternalAuthProviderType[]
   activeExternalAccountTypes?: GrowiExternalAuthProviderType[]
   osInfo?: IGrowiOSInfo
   osInfo?: IGrowiOSInfo
   deploymentType?: GrowiDeploymentType
   deploymentType?: GrowiDeploymentType

+ 3 - 2
apps/app/src/features/questionnaire/server/models/schema/condition.ts

@@ -1,7 +1,8 @@
 import { Schema } from 'mongoose';
 import { Schema } from 'mongoose';
 
 
-import { ICondition } from '../../../interfaces/condition';
-import { GrowiServiceType } from '../../../interfaces/growi-info';
+import { GrowiServiceType } from '~/interfaces/system';
+
+import type { ICondition } from '../../../interfaces/condition';
 import { UserType } from '../../../interfaces/user-info';
 import { UserType } from '../../../interfaces/user-info';
 
 
 const conditionSchema = new Schema<ICondition>({
 const conditionSchema = new Schema<ICondition>({

+ 7 - 2
apps/app/src/features/questionnaire/server/models/schema/growi-info.ts

@@ -1,9 +1,14 @@
 import { Schema } from 'mongoose';
 import { Schema } from 'mongoose';
 
 
+import { AttachmentMethodType } from '~/interfaces/attachment';
+import { GrowiDeploymentType, GrowiServiceType } from '~/interfaces/system';
+
+import type { IGrowiInfo } from '../../../interfaces/growi-info';
 import {
 import {
-  GrowiAttachmentType, GrowiDeploymentType, GrowiExternalAuthProviderType, GrowiServiceType, GrowiWikiType, IGrowiInfo,
+  GrowiExternalAuthProviderType, GrowiWikiType,
 } from '../../../interfaces/growi-info';
 } from '../../../interfaces/growi-info';
 
 
+
 export const growiInfoSchema = new Schema<IGrowiInfo>({
 export const growiInfoSchema = new Schema<IGrowiInfo>({
   version: { type: String, required: true },
   version: { type: String, required: true },
   appSiteUrl: { type: String },
   appSiteUrl: { type: String },
@@ -14,7 +19,7 @@ export const growiInfoSchema = new Schema<IGrowiInfo>({
   currentUsersCount: { type: Number, required: true },
   currentUsersCount: { type: Number, required: true },
   currentActiveUsersCount: { type: Number, required: true },
   currentActiveUsersCount: { type: Number, required: true },
   wikiType: { type: String, required: true, enum: Object.values(GrowiWikiType) },
   wikiType: { type: String, required: true, enum: Object.values(GrowiWikiType) },
-  attachmentType: { type: String, required: true, enum: Object.values(GrowiAttachmentType) },
+  attachmentType: { type: String, required: true, enum: Object.values(AttachmentMethodType) },
   activeExternalAccountTypes: [{ type: String, enum: Object.values(GrowiExternalAuthProviderType) }],
   activeExternalAccountTypes: [{ type: String, enum: Object.values(GrowiExternalAuthProviderType) }],
   osInfo: {
   osInfo: {
     type: { type: String },
     type: { type: String },

+ 4 - 3
apps/app/src/features/questionnaire/server/routes/apiv3/questionnaire.ts

@@ -6,6 +6,7 @@ import { body, validationResult } from 'express-validator';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
+import { configManager } from '~/server/service/config-manager';
 import axios from '~/utils/axios';
 import axios from '~/utils/axios';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -74,13 +75,13 @@ module.exports = (crowi: Crowi): Router => {
   });
   });
 
 
   router.get('/is-enabled', accessTokenParser, loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
   router.get('/is-enabled', accessTokenParser, loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    const isEnabled = crowi.configManager!.getConfig('crowi', 'questionnaire:isQuestionnaireEnabled');
+    const isEnabled = configManager.getConfig('questionnaire:isQuestionnaireEnabled');
     return res.apiv3({ isEnabled });
     return res.apiv3({ isEnabled });
   });
   });
 
 
   router.post('/proactive/answer', accessTokenParser, loginRequired, validators.proactiveAnswer, async(req: AuthorizedRequest, res: ApiV3Response) => {
   router.post('/proactive/answer', accessTokenParser, loginRequired, validators.proactiveAnswer, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const sendQuestionnaireAnswer = async() => {
     const sendQuestionnaireAnswer = async() => {
-      const questionnaireServerOrigin = crowi.configManager?.getConfig('crowi', 'app:questionnaireServerOrigin');
+      const questionnaireServerOrigin = crowi.configManager.getConfig('app:questionnaireServerOrigin');
       const growiInfo = await crowi.questionnaireService!.getGrowiInfo();
       const growiInfo = await crowi.questionnaireService!.getGrowiInfo();
       const userInfo = crowi.questionnaireService!.getUserInfo(req.user ?? null, growiInfo.appSiteUrlHashed);
       const userInfo = crowi.questionnaireService!.getUserInfo(req.user ?? null, growiInfo.appSiteUrlHashed);
 
 
@@ -126,7 +127,7 @@ module.exports = (crowi: Crowi): Router => {
 
 
   router.put('/answer', accessTokenParser, loginRequired, validators.answer, async(req: AuthorizedRequest, res: ApiV3Response) => {
   router.put('/answer', accessTokenParser, loginRequired, validators.answer, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const sendQuestionnaireAnswer = async(user: IUserHasId, answers: IAnswer[]) => {
     const sendQuestionnaireAnswer = async(user: IUserHasId, answers: IAnswer[]) => {
-      const questionnaireServerOrigin = crowi.configManager?.getConfig('crowi', 'app:questionnaireServerOrigin');
+      const questionnaireServerOrigin = crowi.configManager.getConfig('app:questionnaireServerOrigin');
       const growiInfo = await crowi.questionnaireService!.getGrowiInfo();
       const growiInfo = await crowi.questionnaireService!.getGrowiInfo();
       const userInfo = crowi.questionnaireService!.getUserInfo(user, growiInfo.appSiteUrlHashed);
       const userInfo = crowi.questionnaireService!.getUserInfo(user, growiInfo.appSiteUrlHashed);
 
 

+ 3 - 3
apps/app/src/features/questionnaire/server/service/questionnaire-cron.ts

@@ -38,8 +38,8 @@ class QuestionnaireCronService {
   sleep = (msec: number): Promise<void> => new Promise(resolve => setTimeout(resolve, msec));
   sleep = (msec: number): Promise<void> => new Promise(resolve => setTimeout(resolve, msec));
 
 
   startCron(): void {
   startCron(): void {
-    const cronSchedule = this.crowi.configManager?.getConfig('crowi', 'app:questionnaireCronSchedule');
-    const maxHoursUntilRequest = this.crowi.configManager?.getConfig('crowi', 'app:questionnaireCronMaxHoursUntilRequest');
+    const cronSchedule = this.crowi.configManager.getConfig('app:questionnaireCronSchedule');
+    const maxHoursUntilRequest = this.crowi.configManager.getConfig('app:questionnaireCronMaxHoursUntilRequest');
 
 
     const maxSecondsUntilRequest = maxHoursUntilRequest * 60 * 60;
     const maxSecondsUntilRequest = maxHoursUntilRequest * 60 * 60;
 
 
@@ -53,7 +53,7 @@ class QuestionnaireCronService {
   }
   }
 
 
   async executeJob(): Promise<void> {
   async executeJob(): Promise<void> {
-    const questionnaireServerOrigin = this.crowi.configManager?.getConfig('crowi', 'app:questionnaireServerOrigin');
+    const questionnaireServerOrigin = this.crowi.configManager.getConfig('app:questionnaireServerOrigin');
 
 
     const fetchQuestionnaireOrders = async(): Promise<IQuestionnaireOrder[]> => {
     const fetchQuestionnaireOrders = async(): Promise<IQuestionnaireOrder[]> => {
       const response = await axios.get(`${questionnaireServerOrigin}/questionnaire-order/index`);
       const response = await axios.get(`${questionnaireServerOrigin}/questionnaire-order/index`);

+ 9 - 7
apps/app/src/features/questionnaire/server/service/questionnaire.ts

@@ -3,6 +3,8 @@ import * as os from 'node:os';
 
 
 import type { IUserHasId } from '@growi/core';
 import type { IUserHasId } from '@growi/core';
 
 
+import { AttachmentMethodType } from '~/interfaces/attachment';
+import { GrowiDeploymentType, GrowiServiceType } from '~/interfaces/system';
 import type { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
 import type { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import { Config } from '~/server/models/config';
 import { Config } from '~/server/models/config';
@@ -11,7 +13,7 @@ import loggerFactory from '~/utils/logger';
 
 
 import type { IGrowiInfo } from '../../interfaces/growi-info';
 import type { IGrowiInfo } from '../../interfaces/growi-info';
 import {
 import {
-  GrowiWikiType, GrowiExternalAuthProviderType, GrowiServiceType, GrowiAttachmentType, GrowiDeploymentType,
+  GrowiWikiType, GrowiExternalAuthProviderType,
 } from '../../interfaces/growi-info';
 } from '../../interfaces/growi-info';
 import { StatusType } from '../../interfaces/questionnaire-answer-status';
 import { StatusType } from '../../interfaces/questionnaire-answer-status';
 import { type IUserInfo, UserType } from '../../interfaces/user-info';
 import { type IUserInfo, UserType } from '../../interfaces/user-info';
@@ -57,16 +59,16 @@ class QuestionnaireService {
     const wikiType = isGuestAllowedToRead ? GrowiWikiType.open : GrowiWikiType.closed;
     const wikiType = isGuestAllowedToRead ? GrowiWikiType.open : GrowiWikiType.closed;
 
 
     const activeExternalAccountTypes: GrowiExternalAuthProviderType[] = Object.values(GrowiExternalAuthProviderType).filter((type) => {
     const activeExternalAccountTypes: GrowiExternalAuthProviderType[] = Object.values(GrowiExternalAuthProviderType).filter((type) => {
-      return this.crowi.configManager.getConfig('crowi', `security:passport-${type}:isEnabled`);
+      return this.crowi.configManager.getConfig(`security:passport-${type}:isEnabled`);
     });
     });
 
 
-    const typeStr = this.crowi.configManager.getConfig('crowi', 'app:serviceType');
+    const typeStr = this.crowi.configManager.getConfig('app:serviceType');
     const type = Object.values(GrowiServiceType).includes(typeStr) ? typeStr : null;
     const type = Object.values(GrowiServiceType).includes(typeStr) ? typeStr : null;
 
 
-    const attachmentTypeStr = this.crowi.configManager.getConfig('crowi', 'app:fileUploadType');
-    const attachmentType = Object.values(GrowiAttachmentType).includes(attachmentTypeStr) ? attachmentTypeStr : null;
+    const attachmentTypeStr = this.crowi.configManager.getConfig('app:fileUploadType');
+    const attachmentType = Object.values(AttachmentMethodType).includes(attachmentTypeStr) ? attachmentTypeStr : null;
 
 
-    const deploymentTypeStr = this.crowi.configManager.getConfig('crowi', 'app:deploymentType');
+    const deploymentTypeStr = this.crowi.configManager.getConfig('app:deploymentType');
     const deploymentType = Object.values(GrowiDeploymentType).includes(deploymentTypeStr) ? deploymentTypeStr : null;
     const deploymentType = Object.values(GrowiDeploymentType).includes(deploymentTypeStr) ? deploymentTypeStr : null;
 
 
     return {
     return {
@@ -77,7 +79,7 @@ class QuestionnaireService {
         arch: os.arch(),
         arch: os.arch(),
         totalmem: os.totalmem(),
         totalmem: os.totalmem(),
       },
       },
-      appSiteUrl: this.crowi.configManager.getConfig('crowi', 'questionnaire:isAppSiteUrlHashed') ? null : appSiteUrl,
+      appSiteUrl: this.crowi.configManager.getConfig('questionnaire:isAppSiteUrlHashed') ? null : appSiteUrl,
       appSiteUrlHashed,
       appSiteUrlHashed,
       installedAt,
       installedAt,
       installedAtByOldestUser,
       installedAtByOldestUser,

+ 1 - 0
apps/app/src/interfaces/activity.ts

@@ -365,6 +365,7 @@ export const ActionGroupSize = {
   Medium: 'MEDIUM',
   Medium: 'MEDIUM',
   Large: 'LARGE',
   Large: 'LARGE',
 } as const;
 } as const;
+export type ActionGroupSize = typeof ActionGroupSize[keyof typeof ActionGroupSize];
 
 
 export const SmallActionGroup = {
 export const SmallActionGroup = {
   ACTION_USER_LOGIN_WITH_LOCAL,
   ACTION_USER_LOGIN_WITH_LOCAL,

+ 13 - 1
apps/app/src/interfaces/attachment.ts

@@ -1,7 +1,19 @@
-import type { IAttachmentHasId } from '@growi/core';
+import type { IAttachmentHasId } from '@growi/core/dist/interfaces';
 
 
 import type { PaginateResult } from './mongoose-utils';
 import type { PaginateResult } from './mongoose-utils';
 
 
+export const AttachmentMethodType = {
+  aws: 'aws',
+  gcs: 'gcs',
+  gcp: 'gcp',
+  azure: 'azure',
+  gridfs: 'gridfs',
+  mongo: 'mongo',
+  mongodb: 'mongodb',
+  local: 'local',
+  none: 'none',
+} as const;
+export type AttachmentMethodType = typeof AttachmentMethodType[keyof typeof AttachmentMethodType]
 
 
 export type IResAttachmentList = {
 export type IResAttachmentList = {
   paginateResult: PaginateResult<IAttachmentHasId>
   paginateResult: PaginateResult<IAttachmentHasId>

+ 3 - 2
apps/app/src/interfaces/crowi-request.ts

@@ -2,13 +2,14 @@ import type { IUser } from '@growi/core';
 import type { Request } from 'express';
 import type { Request } from 'express';
 import type { HydratedDocument } from 'mongoose';
 import type { HydratedDocument } from 'mongoose';
 
 
+import type Crowi from '~/server/crowi';
+
 
 
 export interface CrowiProperties {
 export interface CrowiProperties {
 
 
   user?: HydratedDocument<IUser>,
   user?: HydratedDocument<IUser>,
 
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  crowi: any,
+  crowi: Crowi,
 
 
   session: any,
   session: any,
 
 

+ 17 - 0
apps/app/src/interfaces/system.ts

@@ -0,0 +1,17 @@
+export const GrowiServiceType = {
+  cloud: 'cloud',
+  privateCloud: 'private-cloud',
+  onPremise: 'on-premise',
+  others: 'others',
+} as const;
+
+export const GrowiDeploymentType = {
+  officialHelmChart: 'official-helm-chart',
+  growiDockerCompose: 'growi-docker-compose',
+  node: 'node',
+  others: 'others',
+} as const;
+
+export type GrowiServiceType = typeof GrowiServiceType[keyof typeof GrowiServiceType]
+
+export type GrowiDeploymentType = typeof GrowiDeploymentType[keyof typeof GrowiDeploymentType]

+ 38 - 0
apps/app/src/migrations/19700101000000-foremost-1000-20241123211930-remove-ns-from-configs.js

@@ -0,0 +1,38 @@
+import mongoose from 'mongoose';
+
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
+
+
+const logger = loggerFactory('growi:migrate:remove-ns-from-configs');
+
+async function dropIndexIfExists(db, collectionName, indexName) {
+  // check existence of the collection
+  const items = await db.listCollections({ name: collectionName }, { nameOnly: true }).toArray();
+  if (items.length === 0) {
+    return;
+  }
+
+  const collection = await db.collection(collectionName);
+  if (await collection.indexExists(indexName)) {
+    await collection.dropIndex(indexName);
+  }
+}
+
+module.exports = {
+  async up(db) {
+    logger.info('Apply migration');
+    await mongoose.connect(getMongoUri(), mongoOptions);
+
+    // drop index
+    await dropIndexIfExists(db, 'configs', 'ns_1_key_1');
+
+    // create index
+    const collection = await db.collection('configs');
+    await collection.createIndex({ key: 1 }, { unique: true });
+  },
+
+  async down() {
+    // No rollback
+  },
+};

+ 2 - 6
apps/app/src/migrations/20180927102719-init-serverurl.js

@@ -24,7 +24,6 @@ module.exports = {
 
 
     // find 'app:siteUrl'
     // find 'app:siteUrl'
     const siteUrlConfig = await Config.findOne({
     const siteUrlConfig = await Config.findOne({
-      ns: 'crowi',
       key: 'app:siteUrl',
       key: 'app:siteUrl',
     });
     });
     // exit if exists
     // exit if exists
@@ -35,7 +34,6 @@ module.exports = {
 
 
     // find all callbackUrls
     // find all callbackUrls
     const configs = await Config.find({
     const configs = await Config.find({
-      ns: 'crowi',
       $or: [
       $or: [
         { key: 'security:passport-github:callbackUrl' },
         { key: 'security:passport-github:callbackUrl' },
         { key: 'security:passport-google:callbackUrl' },
         { key: 'security:passport-google:callbackUrl' },
@@ -63,11 +61,10 @@ module.exports = {
     }
     }
 
 
     if (siteUrl != null) {
     if (siteUrl != null) {
-      const ns = 'crowi';
       const key = 'app:siteUrl';
       const key = 'app:siteUrl';
       await Config.findOneAndUpdate(
       await Config.findOneAndUpdate(
-        { ns, key },
-        { ns, key, value: JSON.stringify(siteUrl) },
+        { key },
+        { key, value: JSON.stringify(siteUrl) },
         { upsert: true },
         { upsert: true },
       );
       );
       logger.info('Migration has successfully applied');
       logger.info('Migration has successfully applied');
@@ -80,7 +77,6 @@ module.exports = {
 
 
     // remote 'app:siteUrl'
     // remote 'app:siteUrl'
     await Config.findOneAndDelete({
     await Config.findOneAndDelete({
-      ns: 'crowi',
       key: 'app:siteUrl',
       key: 'app:siteUrl',
     });
     });
 
 

+ 6 - 6
apps/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js

@@ -15,18 +15,18 @@ module.exports = {
     // enable passport and delete configs for crowi classic auth
     // enable passport and delete configs for crowi classic auth
     await Promise.all([
     await Promise.all([
       Config.findOneAndUpdate(
       Config.findOneAndUpdate(
-        { ns: 'crowi', key: 'security:isEnabledPassport' },
-        { ns: 'crowi', key: 'security:isEnabledPassport', value: JSON.stringify(true) },
+        { key: 'security:isEnabledPassport' },
+        { key: 'security:isEnabledPassport', value: JSON.stringify(true) },
         { upsert: true },
         { upsert: true },
       ),
       ),
       Config.findOneAndUpdate(
       Config.findOneAndUpdate(
-        { ns: 'crowi', key: 'google:clientId' },
-        { ns: 'crowi', key: 'google:clientId', value: JSON.stringify(null) },
+        { key: 'google:clientId' },
+        { key: 'google:clientId', value: JSON.stringify(null) },
         { upsert: true },
         { upsert: true },
       ),
       ),
       Config.findOneAndUpdate(
       Config.findOneAndUpdate(
-        { ns: 'crowi', key: 'google:clientSecret' },
-        { ns: 'crowi', key: 'google:clientSecret', value: JSON.stringify(null) },
+        { key: 'google:clientSecret' },
+        { key: 'google:clientSecret', value: JSON.stringify(null) },
         { upsert: true },
         { upsert: true },
       ),
       ),
     ]);
     ]);

+ 3 - 6
apps/app/src/migrations/20190618104011-add-config-app-installed.js

@@ -9,9 +9,9 @@ const logger = loggerFactory('growi:migrate:add-config-app-installed');
 
 
 /**
 /**
  * BEFORE
  * BEFORE
- *   - Config document { ns: 'crowi', key: 'app:installed' } does not exist
+ *   - Config document { key: 'app:installed' } does not exist
  * AFTER
  * AFTER
- *   - Config document { ns: 'crowi', key: 'app:installed' } is created
+ *   - Config document { key: 'app:installed' } is created
  *     - value will be true if one or more users exist
  *     - value will be true if one or more users exist
  *     - value will be false if no users exist
  *     - value will be false if no users exist
  */
  */
@@ -23,9 +23,8 @@ module.exports = {
 
 
     const User = userModelFactory();
     const User = userModelFactory();
 
 
-    // find 'app:siteUrl'
+    // find 'app:installed'
     const appInstalled = await Config.findOne({
     const appInstalled = await Config.findOne({
-      ns: 'crowi',
       key: 'app:installed',
       key: 'app:installed',
     });
     });
     // exit if exists
     // exit if exists
@@ -38,7 +37,6 @@ module.exports = {
 
 
     if (userCount > 0) {
     if (userCount > 0) {
       await Config.create({
       await Config.create({
-        ns: 'crowi',
         key: 'app:installed',
         key: 'app:installed',
         value: true,
         value: true,
       });
       });
@@ -53,7 +51,6 @@ module.exports = {
 
 
     // remote 'app:siteUrl'
     // remote 'app:siteUrl'
     await Config.findOneAndDelete({
     await Config.findOneAndDelete({
-      ns: 'crowi',
       key: 'app:installed',
       key: 'app:installed',
     });
     });
 
 

+ 0 - 1
apps/app/src/migrations/20200512005851-remove-behavior-type.js

@@ -23,7 +23,6 @@ module.exports = {
     await mongoose.connect(getMongoUri(), mongoOptions);
     await mongoose.connect(getMongoUri(), mongoOptions);
 
 
     const insertConfig = new Config({
     const insertConfig = new Config({
-      ns: 'crowi',
       key: 'customize:behavior',
       key: 'customize:behavior',
       value: JSON.stringify('growi'),
       value: JSON.stringify('growi'),
     });
     });

+ 0 - 1
apps/app/src/migrations/20200827045151-remove-layout-setting.js

@@ -45,7 +45,6 @@ module.exports = {
     const insertLayoutType = (theme.value === '"kibela"') ? 'kibela' : 'growi';
     const insertLayoutType = (theme.value === '"kibela"') ? 'kibela' : 'growi';
 
 
     const insertConfig = new Config({
     const insertConfig = new Config({
-      ns: 'crowi',
       key: 'customize:layout',
       key: 'customize:layout',
       value: JSON.stringify(insertLayoutType),
       value: JSON.stringify(insertLayoutType),
     });
     });

+ 0 - 2
apps/app/src/migrations/20200828024025-copy-aws-setting.js

@@ -25,7 +25,6 @@ module.exports = {
           insertOne: {
           insertOne: {
             document: {
             document: {
               key: 'mail:sesAccessKeyId',
               key: 'mail:sesAccessKeyId',
-              ns: 'crowi',
               value: accessKeyId.value,
               value: accessKeyId.value,
             },
             },
           },
           },
@@ -39,7 +38,6 @@ module.exports = {
           insertOne: {
           insertOne: {
             document: {
             document: {
               key: 'mail:sesSecretAccessKey',
               key: 'mail:sesSecretAccessKey',
-              ns: 'crowi',
               value: secretAccessKey.value,
               value: secretAccessKey.value,
             },
             },
           },
           },

+ 0 - 3
apps/app/src/migrations/20200901034313-update-mail-transmission.js

@@ -13,11 +13,9 @@ module.exports = {
     await mongoose.connect(getMongoUri(), mongoOptions);
     await mongoose.connect(getMongoUri(), mongoOptions);
 
 
     const sesAccessKeyId = await Config.findOne({
     const sesAccessKeyId = await Config.findOne({
-      ns: 'crowi',
       key: 'mail:sesAccessKeyId',
       key: 'mail:sesAccessKeyId',
     });
     });
     const transmissionMethod = await Config.findOne({
     const transmissionMethod = await Config.findOne({
-      ns: 'crowi',
       key: 'mail:transmissionMethod',
       key: 'mail:transmissionMethod',
     });
     });
 
 
@@ -47,7 +45,6 @@ module.exports = {
 
 
     // remote 'mail:transmissionMethod'
     // remote 'mail:transmissionMethod'
     await Config.findOneAndDelete({
     await Config.findOneAndDelete({
-      ns: 'crowi',
       key: 'mail:transmissionMethod',
       key: 'mail:transmissionMethod',
     });
     });
 
 

+ 0 - 1
apps/app/src/migrations/20200903080025-remove-timeline-type.js.js

@@ -24,7 +24,6 @@ module.exports = {
     await mongoose.connect(getMongoUri(), mongoOptions);
     await mongoose.connect(getMongoUri(), mongoOptions);
 
 
     const insertConfig = new Config({
     const insertConfig = new Config({
-      ns: 'crowi',
       key: 'customize:isEnabledTimeline',
       key: 'customize:isEnabledTimeline',
       value: true,
       value: true,
     });
     });

+ 0 - 5
apps/app/src/migrations/20220311011114-convert-page-delete-config.js

@@ -15,7 +15,6 @@ module.exports = {
     await mongoose.connect(getMongoUri(), mongoOptions);
     await mongoose.connect(getMongoUri(), mongoOptions);
 
 
     const isNewConfigExists = await Config.count({
     const isNewConfigExists = await Config.count({
-      ns: 'crowi',
       key: 'security:pageDeletionAuthority',
       key: 'security:pageDeletionAuthority',
     }) > 0;
     }) > 0;
 
 
@@ -26,7 +25,6 @@ module.exports = {
     }
     }
 
 
     const oldConfig = await Config.findOne({
     const oldConfig = await Config.findOne({
-      ns: 'crowi',
       key: 'security:pageCompleteDeletionAuthority',
       key: 'security:pageCompleteDeletionAuthority',
     });
     });
 
 
@@ -37,17 +35,14 @@ module.exports = {
       await Config.insertMany(
       await Config.insertMany(
         [
         [
           {
           {
-            ns: 'crowi',
             key: 'security:pageDeletionAuthority',
             key: 'security:pageDeletionAuthority',
             value: oldValue,
             value: oldValue,
           },
           },
           {
           {
-            ns: 'crowi',
             key: 'security:pageRecursiveDeletionAuthority',
             key: 'security:pageRecursiveDeletionAuthority',
             value: `"${PageRecursiveDeleteConfigValue.Inherit}"`,
             value: `"${PageRecursiveDeleteConfigValue.Inherit}"`,
           },
           },
           {
           {
-            ns: 'crowi',
             key: 'security:pageRecursiveCompleteDeletionAuthority',
             key: 'security:pageRecursiveCompleteDeletionAuthority',
             value: `"${PageRecursiveDeleteCompConfigValue.Inherit}"`,
             value: `"${PageRecursiveDeleteCompConfigValue.Inherit}"`,
           },
           },

+ 0 - 1
apps/app/src/migrations/20221014130200-remove-customize-is-saved-states-of-tab-changes.js

@@ -23,7 +23,6 @@ module.exports = {
     await mongoose.connect(getMongoUri(), mongoOptions);
     await mongoose.connect(getMongoUri(), mongoOptions);
 
 
     const insertConfig = new Config({
     const insertConfig = new Config({
-      ns: 'crowi',
       key: 'customize:isSavedStatesOfTabChanges',
       key: 'customize:isSavedStatesOfTabChanges',
       value: false,
       value: false,
     });
     });

+ 0 - 1
apps/app/src/migrations/20230213090921-remove-presentation-configurations.js

@@ -24,7 +24,6 @@ module.exports = {
     await mongoose.connect(getMongoUri(), mongoOptions);
     await mongoose.connect(getMongoUri(), mongoOptions);
 
 
     const insertConfig = new Config({
     const insertConfig = new Config({
-      ns: 'crowi',
       key: 'markdown:presentation:pageBreakSeparator',
       key: 'markdown:presentation:pageBreakSeparator',
       value: 2,
       value: 2,
     });
     });

+ 48 - 44
apps/app/src/pages/[[...path]].page.tsx

@@ -45,7 +45,6 @@ import {
   useIsUploadAllFileAllowed, useIsUploadEnabled,
   useIsUploadAllFileAllowed, useIsUploadEnabled,
   useElasticsearchMaxBodyLengthToIndex,
   useElasticsearchMaxBodyLengthToIndex,
   useIsLocalAccountRegistrationEnabled,
   useIsLocalAccountRegistrationEnabled,
-  useIsRomUserAllowedToComment,
   useIsAiEnabled,
   useIsAiEnabled,
 } from '~/stores-universal/context';
 } from '~/stores-universal/context';
 import { useEditingMarkdown } from '~/stores/editor';
 import { useEditingMarkdown } from '~/stores/editor';
@@ -175,7 +174,6 @@ type Props = CommonProps & {
   isAclEnabled: boolean,
   isAclEnabled: boolean,
   // hasSlackConfig: boolean,
   // hasSlackConfig: boolean,
   drawioUri: string | null,
   drawioUri: string | null,
-  noCdn: string,
   // highlightJsStyle: string,
   // highlightJsStyle: string,
   isAllReplyShown: boolean,
   isAllReplyShown: boolean,
   isContainerFluid: boolean,
   isContainerFluid: boolean,
@@ -233,7 +231,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   // useIsMailerSetup(props.isMailerSetup);
   // useIsMailerSetup(props.isMailerSetup);
   useIsAclEnabled(props.isAclEnabled);
   useIsAclEnabled(props.isAclEnabled);
   // useHasSlackConfig(props.hasSlackConfig);
   // useHasSlackConfig(props.hasSlackConfig);
-  // useNoCdn(props.noCdn);
   useDefaultIndentSize(props.adminPreferredIndentSize);
   useDefaultIndentSize(props.adminPreferredIndentSize);
   useIsIndentSizeForced(props.isIndentSizeForced);
   useIsIndentSizeForced(props.isIndentSizeForced);
   useDisableLinkSharing(props.disableLinkSharing);
   useDisableLinkSharing(props.disableLinkSharing);
@@ -475,24 +472,29 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
     }
     }
   }
   }
 
 
-  const pageWithMeta: IPageToShowRevisionWithMeta | null = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
-  const page = pageWithMeta?.data as unknown as PageDocument;
+  const pageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
+  const { data: page, meta } = pageWithMeta ?? {};
 
 
   // add user to seen users
   // add user to seen users
   if (page != null && user != null) {
   if (page != null && user != null) {
     await page.seen(user);
     await page.seen(user);
   }
   }
 
 
+  props.pageWithMeta = null;
+
   // populate & check if the revision is latest
   // populate & check if the revision is latest
   if (page != null) {
   if (page != null) {
     page.initLatestRevisionField(revisionId);
     page.initLatestRevisionField(revisionId);
     props.isLatestRevision = page.isLatestRevision();
     props.isLatestRevision = page.isLatestRevision();
-    const ssrMaxRevisionBodyLength = configManager.getConfig('crowi', 'app:ssrMaxRevisionBodyLength');
+    const ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
     props.skipSSR = await skipSSR(page, ssrMaxRevisionBodyLength);
     props.skipSSR = await skipSSR(page, ssrMaxRevisionBodyLength);
-    await page.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
-  }
+    const populatedPage = await page.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
 
 
-  props.pageWithMeta = pageWithMeta;
+    props.pageWithMeta = {
+      data: populatedPage,
+      meta,
+    };
+  }
 }
 }
 
 
 async function injectRoutingInformation(context: GetServerSidePropsContext, props: Props): Promise<void> {
 async function injectRoutingInformation(context: GetServerSidePropsContext, props: Props): Promise<void> {
@@ -557,64 +559,66 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { crowi } = req;
   const {
   const {
-    searchService, configManager, aclService,
+    configManager, searchService, aclService, fileUploadService,
+    slackIntegrationService, passportService,
   } = crowi;
   } = crowi;
 
 
-  props.aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
+  props.aiEnabled = configManager.getConfig('app:aiEnabled');
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
-  props.elasticsearchMaxBodyLengthToIndex = configManager.getConfig('crowi', 'app:elasticsearchMaxBodyLengthToIndex');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.elasticsearchMaxBodyLengthToIndex = configManager.getConfig('app:elasticsearchMaxBodyLengthToIndex');
 
 
-  props.isRomUserAllowedToComment = configManager.getConfig('crowi', 'security:isRomUserAllowedToComment');
+  props.isRomUserAllowedToComment = configManager.getConfig('security:isRomUserAllowedToComment');
 
 
-  props.isSlackConfigured = crowi.slackIntegrationService.isSlackConfigured;
+  props.isSlackConfigured = slackIntegrationService.isSlackConfigured;
   // props.isMailerSetup = mailService.isMailerSetup;
   // props.isMailerSetup = mailService.isMailerSetup;
   props.isAclEnabled = aclService.isAclEnabled();
   props.isAclEnabled = aclService.isAclEnabled();
   // props.hasSlackConfig = slackNotificationService.hasSlackConfig();
   // props.hasSlackConfig = slackNotificationService.hasSlackConfig();
-  props.drawioUri = configManager.getConfig('crowi', 'app:drawioUri');
-  props.noCdn = configManager.getConfig('crowi', 'app:noCdn');
-  // props.highlightJsStyle = configManager.getConfig('crowi', 'customize:highlightJsStyle');
-  props.isAllReplyShown = configManager.getConfig('crowi', 'customize:isAllReplyShown');
-  props.isContainerFluid = configManager.getConfig('crowi', 'customize:isContainerFluid');
-  props.isEnabledStaleNotification = configManager.getConfig('crowi', 'customize:isEnabledStaleNotification');
-  props.disableLinkSharing = configManager.getConfig('crowi', 'security:disableLinkSharing');
-  props.isUploadAllFileAllowed = crowi.fileUploadService.getFileUploadEnabled();
-  props.isUploadEnabled = crowi.fileUploadService.getIsUploadable();
+  props.drawioUri = configManager.getConfig('app:drawioUri');
+  // props.highlightJsStyle = configManager.getConfig('customize:highlightJsStyle');
+  props.isAllReplyShown = configManager.getConfig('customize:isAllReplyShown');
+  props.isContainerFluid = configManager.getConfig('customize:isContainerFluid');
+  props.isEnabledStaleNotification = configManager.getConfig('customize:isEnabledStaleNotification');
+  props.disableLinkSharing = configManager.getConfig('security:disableLinkSharing');
+  props.isUploadAllFileAllowed = fileUploadService.getFileUploadEnabled();
+  props.isUploadEnabled = fileUploadService.getIsUploadable();
 
 
-  props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup
-  && configManager.getConfig('crowi', 'security:registrationMode') !== RegistrationMode.CLOSED;
+  props.isLocalAccountRegistrationEnabled = passportService.isLocalStrategySetup
+  && configManager.getConfig('security:registrationMode') !== RegistrationMode.CLOSED;
 
 
-  props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
-  props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
+  props.adminPreferredIndentSize = configManager.getConfig('markdown:adminPreferredIndentSize');
+  props.isIndentSizeForced = configManager.getConfig('markdown:isIndentSizeForced');
 
 
-  props.isEnabledAttachTitleHeader = configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader');
+  props.isEnabledAttachTitleHeader = configManager.getConfig('customize:isEnabledAttachTitleHeader');
 
 
   props.sidebarConfig = {
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
+    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
   };
   };
 
 
   props.rendererConfig = {
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
-    isEnabledMarp: configManager.getConfig('crowi', 'customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
-    isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
+    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
+    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
 
-    drawioUri: configManager.getConfig('crowi', 'app:drawioUri'),
-    plantumlUri: configManager.getConfig('crowi', 'app:plantumlUri'),
+    drawioUri: configManager.getConfig('app:drawioUri'),
+    plantumlUri: configManager.getConfig('app:plantumlUri'),
 
 
     // XSS Options
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
-    sanitizeType: configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
-    customAttrWhitelist: JSON.parse(crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes')),
-    customTagWhitelist: crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
-    highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
+    customTagWhitelist: configManager.getConfig('markdown:rehypeSanitize:tagNames'),
+    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
+      : undefined,
+    highlightJsStyleBorder: configManager.getConfig('customize:highlightJsStyleBorder'),
   };
   };
 
 
-  props.ssrMaxRevisionBodyLength = configManager.getConfig('crowi', 'app:ssrMaxRevisionBodyLength');
+  props.ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
 }
 }
 
 
 /**
 /**

+ 18 - 16
apps/app/src/pages/_private-legacy-pages.page.tsx

@@ -94,30 +94,32 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
-  props.isEnabledMarp = configManager.getConfig('crowi', 'customize:isEnabledMarp');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.isEnabledMarp = configManager.getConfig('customize:isEnabledMarp');
 
 
   props.sidebarConfig = {
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
+    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
   };
   };
 
 
   props.rendererConfig = {
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
-    isEnabledMarp: configManager.getConfig('crowi', 'customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
-    isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
+    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
+    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
 
-    drawioUri: configManager.getConfig('crowi', 'app:drawioUri'),
-    plantumlUri: configManager.getConfig('crowi', 'app:plantumlUri'),
+    drawioUri: configManager.getConfig('app:drawioUri'),
+    plantumlUri: configManager.getConfig('app:plantumlUri'),
 
 
     // XSS Options
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
-    sanitizeType: configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
-    customAttrWhitelist: JSON.parse(crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes')),
-    customTagWhitelist: crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
-    highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
+    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
+    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
+      : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
   };
   };
 }
 }
 
 

+ 19 - 17
apps/app/src/pages/_search.page.tsx

@@ -121,33 +121,35 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
-  props.isContainerFluid = configManager.getConfig('crowi', 'customize:isContainerFluid');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.isContainerFluid = configManager.getConfig('customize:isContainerFluid');
 
 
   props.sidebarConfig = {
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
+    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
   };
   };
 
 
   props.rendererConfig = {
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
-    isEnabledMarp: configManager.getConfig('crowi', 'customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
-    isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
+    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
+    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
 
-    drawioUri: configManager.getConfig('crowi', 'app:drawioUri'),
-    plantumlUri: configManager.getConfig('crowi', 'app:plantumlUri'),
+    drawioUri: configManager.getConfig('app:drawioUri'),
+    plantumlUri: configManager.getConfig('app:plantumlUri'),
 
 
     // XSS Options
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
-    sanitizeType: configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
-    customAttrWhitelist: JSON.parse(crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes')),
-    customTagWhitelist: crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
-    highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
+    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
+    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
+      : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
   };
   };
 
 
-  props.showPageLimitationL = configManager.getConfig('crowi', 'customize:showPageLimitationL');
+  props.showPageLimitationL = configManager.getConfig('customize:showPageLimitationL');
 }
 }
 
 
 /**
 /**

+ 1 - 1
apps/app/src/pages/admin/ai-integration.page.tsx

@@ -50,7 +50,7 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   const { crowi } = req;
   const { crowi } = req;
   const { configManager } = crowi;
   const { configManager } = crowi;
 
 
-  props.aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
+  props.aiEnabled = configManager.getConfig('app:aiEnabled');
 };
 };
 
 
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {

+ 2 - 2
apps/app/src/pages/admin/audit-log.page.tsx

@@ -57,8 +57,8 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   const { crowi } = req;
   const { crowi } = req;
   const { activityService } = crowi;
   const { activityService } = crowi;
 
 
-  props.auditLogEnabled = crowi.configManager.getConfig('crowi', 'app:auditLogEnabled');
-  props.activityExpirationSeconds = crowi.configManager.getConfig('crowi', 'app:activityExpirationSeconds');
+  props.auditLogEnabled = crowi.configManager.getConfig('app:auditLogEnabled');
+  props.activityExpirationSeconds = crowi.configManager.getConfig('app:activityExpirationSeconds');
   props.auditLogAvailableActions = activityService.getAvailableActions(false);
   props.auditLogAvailableActions = activityService.getAvailableActions(false);
 };
 };
 
 

+ 3 - 2
apps/app/src/pages/admin/customize.page.tsx

@@ -12,6 +12,7 @@ import { Provider } from 'unstated';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
+import { configManager } from '~/server/service/config-manager';
 import { useCustomizeTitle, useCurrentUser, useIsCustomizedLogoUploaded } from '~/stores-universal/context';
 import { useCustomizeTitle, useCurrentUser, useIsCustomizedLogoUploaded } from '~/stores-universal/context';
 
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
@@ -22,7 +23,7 @@ const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenP
 
 
 
 
 type Props = CommonProps & {
 type Props = CommonProps & {
-  customizeTitle: string,
+  customizeTitle?: string,
   isCustomizedLogoUploaded: boolean,
   isCustomizedLogoUploaded: boolean,
 };
 };
 
 
@@ -66,7 +67,7 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { crowi } = req;
 
 
-  props.customizeTitle = crowi.configManager.getConfig('crowi', 'customize:title');
+  props.customizeTitle = crowi.configManager.getConfig('customize:title');
   props.isCustomizedLogoUploaded = await crowi.attachmentService.isBrandLogoExist();
   props.isCustomizedLogoUploaded = await crowi.attachmentService.isBrandLogoExist();
 };
 };
 
 

+ 1 - 1
apps/app/src/pages/admin/data-transfer.page.tsx

@@ -60,7 +60,7 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { crowi } = req;
 
 
-  props.growiCloudUri = await crowi.configManager.getConfig('crowi', 'app:growiCloudUri');
+  props.growiCloudUri = await crowi.configManager.getConfig('app:growiCloudUri');
 };
 };
 
 
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {

+ 5 - 5
apps/app/src/pages/admin/index.page.tsx

@@ -25,12 +25,12 @@ const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenP
 
 
 
 
 type Props = CommonProps & {
 type Props = CommonProps & {
-  growiCloudUri: string,
-  growiAppIdForGrowiCloud: number,
+  growiCloudUri?: string,
+  growiAppIdForGrowiCloud?: number,
 };
 };
 
 
 
 
-const AdminHomepage: NextPage<Props> = (props) => {
+const AdminHomepage: NextPage<Props> = (props: Props) => {
   useCurrentUser(props.currentUser ?? null);
   useCurrentUser(props.currentUser ?? null);
   useGrowiCloudUri(props.growiCloudUri);
   useGrowiCloudUri(props.growiCloudUri);
   useGrowiAppIdForGrowiCloud(props.growiAppIdForGrowiCloud);
   useGrowiAppIdForGrowiCloud(props.growiAppIdForGrowiCloud);
@@ -71,8 +71,8 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { crowi } = req;
 
 
-  props.growiCloudUri = await crowi.configManager.getConfig('crowi', 'app:growiCloudUri');
-  props.growiAppIdForGrowiCloud = await crowi.configManager.getConfig('crowi', 'app:growiAppIdForCloud');
+  props.growiCloudUri = crowi.configManager.getConfig('app:growiCloudUri');
+  props.growiAppIdForGrowiCloud = crowi.configManager.getConfig('app:growiAppIdForCloud');
 };
 };
 
 
 
 

+ 1 - 1
apps/app/src/pages/installer.page.tsx

@@ -79,7 +79,7 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
   const { crowi } = req;
   const { crowi } = req;
   const { configManager } = crowi;
   const { configManager } = crowi;
 
 
-  props.minPasswordLength = configManager.getConfig('crowi', 'app:minPasswordLength');
+  props.minPasswordLength = configManager.getConfig('app:minPasswordLength');
 }
 }
 
 
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {

+ 10 - 11
apps/app/src/pages/login/index.page.tsx

@@ -95,11 +95,10 @@ function injectEnabledStrategies(context: GetServerSidePropsContext, props: Prop
   } = crowi;
   } = crowi;
 
 
   props.enabledExternalAuthType = [
   props.enabledExternalAuthType = [
-    configManager.getConfig('crowi', 'security:passport-google:isEnabled') === true ? IExternalAuthProviderType.google : undefined,
-    configManager.getConfig('crowi', 'security:passport-github:isEnabled') === true ? IExternalAuthProviderType.github : undefined,
-    // configManager.getConfig('crowi', 'security:passport-facebook:isEnabled') ?? IExternalAuthProviderType.facebook : undefined,
-    configManager.getConfig('crowi', 'security:passport-saml:isEnabled') === true ? IExternalAuthProviderType.saml : undefined,
-    configManager.getConfig('crowi', 'security:passport-oidc:isEnabled') === true ? IExternalAuthProviderType.oidc : undefined,
+    configManager.getConfig('security:passport-google:isEnabled') === true ? IExternalAuthProviderType.google : undefined,
+    configManager.getConfig('security:passport-github:isEnabled') === true ? IExternalAuthProviderType.github : undefined,
+    configManager.getConfig('security:passport-saml:isEnabled') === true ? IExternalAuthProviderType.saml : undefined,
+    configManager.getConfig('security:passport-oidc:isEnabled') === true ? IExternalAuthProviderType.oidc : undefined,
 
 
   ]
   ]
     .filter((authType): authType is Exclude<typeof authType, undefined> => authType != null);
     .filter((authType): authType is Exclude<typeof authType, undefined> => authType != null);
@@ -114,15 +113,15 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
     passportService,
     passportService,
   } = crowi;
   } = crowi;
 
 
-  props.isPasswordResetEnabled = crowi.configManager.getConfig('crowi', 'security:passport-local:isPasswordResetEnabled');
+  props.isPasswordResetEnabled = configManager.getConfig('security:passport-local:isPasswordResetEnabled');
   props.isMailerSetup = mailService.isMailerSetup;
   props.isMailerSetup = mailService.isMailerSetup;
   props.isLocalStrategySetup = passportService.isLocalStrategySetup;
   props.isLocalStrategySetup = passportService.isLocalStrategySetup;
   props.isLdapStrategySetup = passportService.isLdapStrategySetup;
   props.isLdapStrategySetup = passportService.isLdapStrategySetup;
-  props.isLdapSetupFailed = configManager.getConfig('crowi', 'security:passport-ldap:isEnabled') && !props.isLdapStrategySetup;
-  props.registrationWhitelist = configManager.getConfig('crowi', 'security:registrationWhitelist');
-  props.isEmailAuthenticationEnabled = configManager.getConfig('crowi', 'security:passport-local:isEmailAuthenticationEnabled');
-  props.registrationMode = configManager.getConfig('crowi', 'security:registrationMode');
-  props.minPasswordLength = configManager.getConfig('crowi', 'app:minPasswordLength');
+  props.isLdapSetupFailed = configManager.getConfig('security:passport-ldap:isEnabled') && !props.isLdapStrategySetup;
+  props.registrationWhitelist = configManager.getConfig('security:registrationWhitelist');
+  props.isEmailAuthenticationEnabled = configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled');
+  props.registrationMode = configManager.getConfig('security:registrationMode');
+  props.minPasswordLength = configManager.getConfig('app:minPasswordLength');
 }
 }
 
 
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {

+ 19 - 17
apps/app/src/pages/me/[[...path]].page.tsx

@@ -173,33 +173,35 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
 
 
-  props.registrationWhitelist = configManager.getConfig('crowi', 'security:registrationWhitelist');
+  props.registrationWhitelist = configManager.getConfig('security:registrationWhitelist');
 
 
-  props.showPageLimitationXL = crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL');
+  props.showPageLimitationXL = crowi.configManager.getConfig('customize:showPageLimitationXL');
 
 
   props.sidebarConfig = {
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
+    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
   };
   };
 
 
   props.rendererConfig = {
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
-    isEnabledMarp: configManager.getConfig('crowi', 'customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
-    isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
+    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
+    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
 
-    drawioUri: configManager.getConfig('crowi', 'app:drawioUri'),
-    plantumlUri: configManager.getConfig('crowi', 'app:plantumlUri'),
+    drawioUri: configManager.getConfig('app:drawioUri'),
+    plantumlUri: configManager.getConfig('app:plantumlUri'),
 
 
     // XSS Options
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
-    sanitizeType: configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
-    customAttrWhitelist: JSON.parse(crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes')),
-    customTagWhitelist: crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
-    highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
+    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
+    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
+      : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
   };
   };
 }
 }
 
 

+ 28 - 23
apps/app/src/pages/share/[[...path]].page.tsx

@@ -18,7 +18,7 @@ import type { CrowiRequest } from '~/interfaces/crowi-request';
 import { RegistrationMode } from '~/interfaces/registration-mode';
 import { RegistrationMode } from '~/interfaces/registration-mode';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
-import type { PageDocument } from '~/server/models/page';
+import type { PageDocument, PageModel } from '~/server/models/page';
 import ShareLink from '~/server/models/share-link';
 import ShareLink from '~/server/models/share-link';
 import {
 import {
   useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
   useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
@@ -159,37 +159,39 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   const { crowi } = req;
   const { crowi } = req;
   const { configManager, searchService } = crowi;
   const { configManager, searchService } = crowi;
 
 
-  props.disableLinkSharing = configManager.getConfig('crowi', 'security:disableLinkSharing');
+  props.disableLinkSharing = configManager.getConfig('security:disableLinkSharing');
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
 
 
-  props.drawioUri = configManager.getConfig('crowi', 'app:drawioUri');
+  props.drawioUri = configManager.getConfig('app:drawioUri');
 
 
   props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup
   props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup
-    && configManager.getConfig('crowi', 'security:registrationMode') !== RegistrationMode.CLOSED;
+    && configManager.getConfig('security:registrationMode') !== RegistrationMode.CLOSED;
 
 
   props.rendererConfig = {
   props.rendererConfig = {
     isSharedPage: true,
     isSharedPage: true,
-    isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
-    isEnabledMarp: configManager.getConfig('crowi', 'customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
-    isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
+    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
+    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
 
-    drawioUri: configManager.getConfig('crowi', 'app:drawioUri'),
-    plantumlUri: configManager.getConfig('crowi', 'app:plantumlUri'),
+    drawioUri: configManager.getConfig('app:drawioUri'),
+    plantumlUri: configManager.getConfig('app:plantumlUri'),
 
 
     // XSS Options
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
-    sanitizeType: configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
-    customAttrWhitelist: JSON.parse(crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes')),
-    customTagWhitelist: crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
-    highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
+    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
+    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
+      : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
   };
   };
 
 
-  props.ssrMaxRevisionBodyLength = configManager.getConfig('crowi', 'app:ssrMaxRevisionBodyLength');
+  props.ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
 }
 }
 
 
 async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
 async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
@@ -232,13 +234,16 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
       props.shareLink = shareLink.toObject();
       props.shareLink = shareLink.toObject();
 
 
       // retrieve Page
       // retrieve Page
-      const Page = crowi.model('Page');
+      const Page = crowi.model('Page') as PageModel;
       const relatedPage = await Page.findOne({ _id: getIdForRef(shareLink.relatedPage) });
       const relatedPage = await Page.findOne({ _id: getIdForRef(shareLink.relatedPage) });
       // determine whether skip SSR
       // determine whether skip SSR
-      const ssrMaxRevisionBodyLength = crowi.configManager.getConfig('crowi', 'app:ssrMaxRevisionBodyLength');
-      props.skipSSR = await skipSSR(relatedPage, ssrMaxRevisionBodyLength);
-      // populate
-      props.shareLinkRelatedPage = await relatedPage.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
+      const ssrMaxRevisionBodyLength = crowi.configManager.getConfig('app:ssrMaxRevisionBodyLength');
+
+      if (relatedPage != null) {
+        props.skipSSR = await skipSSR(relatedPage, ssrMaxRevisionBodyLength);
+        // populate
+        props.shareLinkRelatedPage = await relatedPage.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
+      }
     }
     }
   }
   }
   catch (err) {
   catch (err) {

+ 3 - 3
apps/app/src/pages/tags.page.tsx

@@ -152,11 +152,11 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
 
 
   props.sidebarConfig = {
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
+    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
   };
   };
 
 
 }
 }

+ 4 - 4
apps/app/src/pages/trash.page.tsx

@@ -117,12 +117,12 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
-  props.showPageLimitationXL = crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.showPageLimitationXL = crowi.configManager.getConfig('customize:showPageLimitationXL');
 
 
   props.sidebarConfig = {
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
+    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
   };
   };
 
 
 }
 }

+ 2 - 2
apps/app/src/pages/user-activation.page.tsx

@@ -81,8 +81,8 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   if (typeof context.query.errorCode === 'string') {
   if (typeof context.query.errorCode === 'string') {
     props.errorCode = context.query.errorCode as UserActivationErrorCode;
     props.errorCode = context.query.errorCode as UserActivationErrorCode;
   }
   }
-  props.registrationMode = req.crowi.configManager.getConfig('crowi', 'security:registrationMode');
-  props.isEmailAuthenticationEnabled = req.crowi.configManager.getConfig('crowi', 'security:passport-local:isEmailAuthenticationEnabled');
+  props.registrationMode = req.crowi.configManager.getConfig('security:registrationMode');
+  props.isEmailAuthenticationEnabled = req.crowi.configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled');
 
 
   await injectNextI18NextConfigurations(context, props, ['translation']);
   await injectNextI18NextConfigurations(context, props, ['translation']);
 
 

+ 7 - 7
apps/app/src/pages/utils/commons.ts

@@ -22,7 +22,7 @@ export type CommonProps = {
   namespacesRequired: string[], // i18next
   namespacesRequired: string[], // i18next
   currentPathname: string,
   currentPathname: string,
   appTitle: string,
   appTitle: string,
-  siteUrl: string,
+  siteUrl: string | undefined,
   confidential: string,
   confidential: string,
   customTitleTemplate: string,
   customTitleTemplate: string,
   csrfToken: string,
   csrfToken: string,
@@ -31,7 +31,7 @@ export type CommonProps = {
   isMaintenanceMode: boolean,
   isMaintenanceMode: boolean,
   redirectDestination: string | null,
   redirectDestination: string | null,
   isDefaultLogo: boolean,
   isDefaultLogo: boolean,
-  growiCloudUri: string,
+  growiCloudUri: string | undefined,
   isAccessDeniedForNonAdminUser?: boolean,
   isAccessDeniedForNonAdminUser?: boolean,
   currentUser?: IUserHasId,
   currentUser?: IUserHasId,
   forcedColorScheme?: ColorScheme,
   forcedColorScheme?: ColorScheme,
@@ -74,7 +74,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   }
   }
 
 
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
-  const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
+  const isDefaultLogo = crowi.configManager.getConfig('customize:isDefaultLogo') || !isCustomizedLogoUploaded;
   const forcedColorScheme = crowi.customizeService.forcedColorScheme;
   const forcedColorScheme = crowi.customizeService.forcedColorScheme;
 
 
   // retrieve UserUISett ings
   // retrieve UserUISett ings
@@ -87,18 +87,18 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
     namespacesRequired: ['translation'],
     namespacesRequired: ['translation'],
     currentPathname,
     currentPathname,
     appTitle: appService.getAppTitle(),
     appTitle: appService.getAppTitle(),
-    siteUrl: configManager.getConfig('crowi', 'app:siteUrl'), // DON'T USE appService.getSiteUrl()
+    siteUrl: configManager.getConfig('app:siteUrl'), // DON'T USE appService.getSiteUrl()
     confidential: appService.getAppConfidential() || '',
     confidential: appService.getAppConfidential() || '',
     customTitleTemplate: customizeService.customTitleTemplate,
     customTitleTemplate: customizeService.customTitleTemplate,
     csrfToken: req.csrfToken(),
     csrfToken: req.csrfToken(),
-    isContainerFluid: configManager.getConfig('crowi', 'customize:isContainerFluid') ?? false,
+    isContainerFluid: configManager.getConfig('customize:isContainerFluid') ?? false,
     growiVersion: crowi.version,
     growiVersion: crowi.version,
     isMaintenanceMode,
     isMaintenanceMode,
     redirectDestination,
     redirectDestination,
     currentUser,
     currentUser,
     isDefaultLogo,
     isDefaultLogo,
     forcedColorScheme,
     forcedColorScheme,
-    growiCloudUri: configManager.getConfig('crowi', 'app:growiCloudUri'),
+    growiCloudUri: configManager.getConfig('app:growiCloudUri'),
     userUISettings: userUISettings?.toObject?.() ?? userUISettings,
     userUISettings: userUISettings?.toObject?.() ?? userUISettings,
   };
   };
 
 
@@ -122,7 +122,7 @@ export const getLangAtServerSide = (req: CrowiRequest): Lang => {
   const { configManager } = req.crowi;
   const { configManager } = req.crowi;
 
 
   return user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
   return user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
-    : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US) ?? Lang.en_US;
+    : (user.lang ?? configManager.getConfig('app:globalLang') ?? Lang.en_US) ?? Lang.en_US;
 };
 };
 
 
 // use this function to get locale for html lang attribute
 // use this function to get locale for html lang attribute

+ 3 - 3
apps/app/src/server/crowi/express-init.js

@@ -39,9 +39,9 @@ module.exports = function(crowi, app) {
 
 
   const { configManager } = crowi;
   const { configManager } = crowi;
 
 
-  const trustProxyBool = configManager.getConfig('crowi', 'security:trustProxyBool');
-  const trustProxyCsv = configManager.getConfig('crowi', 'security:trustProxyCsv');
-  const trustProxyHops = configManager.getConfig('crowi', 'security:trustProxyHops');
+  const trustProxyBool = configManager.getConfig('security:trustProxyBool');
+  const trustProxyCsv = configManager.getConfig('security:trustProxyCsv');
+  const trustProxyHops = configManager.getConfig('security:trustProxyHops');
 
 
   const trustProxy = trustProxyBool ?? trustProxyCsv ?? trustProxyHops;
   const trustProxy = trustProxyBool ?? trustProxyCsv ?? trustProxyHops;
 
 

+ 29 - 14
apps/app/src/server/crowi/index.js

@@ -59,21 +59,36 @@ class Crowi {
    */
    */
   accessTokenParser;
   accessTokenParser;
 
 
+  /** @type {import('../service/config-manager').IConfigManagerForApp} */
+  configManager;
+
+  /** @type {import('../service/acl').AclService} */
+  aclService;
+
   /** @type {AppService} */
   /** @type {AppService} */
   appService;
   appService;
 
 
+  /** @type {FileUploader} */
+  fileUploadService;
+
   /** @type {import('../service/page').IPageService} */
   /** @type {import('../service/page').IPageService} */
   pageService;
   pageService;
 
 
-  /** @type UserNotificationService */
-  userNotificationService;
+  /** @type {PassportService} */
+  passportService;
 
 
-  /** @type {FileUploader} */
-  fileUploadService;
+  /** @type {SearchService} */
+  searchService;
+
+  /** @type {SlackIntegrationService} */
+  slackIntegrationService;
 
 
   /** @type {SocketIoService} */
   /** @type {SocketIoService} */
   socketIoService;
   socketIoService;
 
 
+  /** @type UserNotificationService */
+  userNotificationService;
+
   constructor() {
   constructor() {
     this.version = pkg.version;
     this.version = pkg.version;
 
 
@@ -258,7 +273,7 @@ Crowi.prototype.setupDatabase = function() {
 
 
 Crowi.prototype.setupSessionConfig = async function() {
 Crowi.prototype.setupSessionConfig = async function() {
   const session = require('express-session');
   const session = require('express-session');
-  const sessionMaxAge = this.configManager.getConfig('crowi', 'security:sessionMaxAge') || 2592000000; // default: 30days
+  const sessionMaxAge = this.configManager.getConfig('security:sessionMaxAge') || 2592000000; // default: 30days
   const redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null;
   const redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null;
   const uid = require('uid-safe').sync;
   const uid = require('uid-safe').sync;
 
 
@@ -398,8 +413,8 @@ Crowi.prototype.setupMailer = async function() {
 };
 };
 
 
 Crowi.prototype.autoInstall = async function() {
 Crowi.prototype.autoInstall = async function() {
-  const isInstalled = this.configManager.getConfig('crowi', 'app:installed');
-  const username = this.configManager.getConfig('crowi', 'autoInstall:adminUsername');
+  const isInstalled = this.configManager.getConfig('app:installed');
+  const username = this.configManager.getConfig('autoInstall:adminUsername');
 
 
   if (isInstalled || username == null) {
   if (isInstalled || username == null) {
     return;
     return;
@@ -409,14 +424,14 @@ Crowi.prototype.autoInstall = async function() {
 
 
   const firstAdminUserToSave = {
   const firstAdminUserToSave = {
     username,
     username,
-    name: this.configManager.getConfig('crowi', 'autoInstall:adminName'),
-    email: this.configManager.getConfig('crowi', 'autoInstall:adminEmail'),
-    password: this.configManager.getConfig('crowi', 'autoInstall:adminPassword'),
+    name: this.configManager.getConfig('autoInstall:adminName'),
+    email: this.configManager.getConfig('autoInstall:adminEmail'),
+    password: this.configManager.getConfig('autoInstall:adminPassword'),
     admin: true,
     admin: true,
   };
   };
-  const globalLang = this.configManager.getConfig('crowi', 'autoInstall:globalLang');
-  const allowGuestMode = this.configManager.getConfig('crowi', 'autoInstall:allowGuestMode');
-  const serverDate = this.configManager.getConfig('crowi', 'autoInstall:serverDate');
+  const globalLang = this.configManager.getConfig('autoInstall:globalLang');
+  const allowGuestMode = this.configManager.getConfig('autoInstall:allowGuestMode');
+  const serverDate = this.configManager.getConfig('autoInstall:serverDate');
 
 
   const installerService = new InstallerService(this);
   const installerService = new InstallerService(this);
 
 
@@ -614,7 +629,7 @@ Crowi.prototype.setUpApp = async function() {
     this.appService = new AppService(this);
     this.appService = new AppService(this);
 
 
     // add as a message handler
     // add as a message handler
-    const isInstalled = this.configManager.getConfig('crowi', 'app:installed');
+    const isInstalled = this.configManager.getConfig('app:installed');
     if (this.s2sMessagingService != null && !isInstalled) {
     if (this.s2sMessagingService != null && !isInstalled) {
       this.s2sMessagingService.addMessageHandler(this.appService);
       this.s2sMessagingService.addMessageHandler(this.appService);
     }
     }

+ 1 - 1
apps/app/src/server/middlewares/certify-shared-page-attachment/validate-referer/retrieve-site-url.ts

@@ -6,7 +6,7 @@ const logger = loggerFactory('growi:middlewares:certify-shared-page-attachment:v
 
 
 
 
 export const retrieveSiteUrl = (): URL | null => {
 export const retrieveSiteUrl = (): URL | null => {
-  const siteUrlString = configManager.getConfig('crowi', 'app:siteUrl');
+  const siteUrlString = configManager.getConfig('app:siteUrl');
   if (siteUrlString == null) {
   if (siteUrlString == null) {
     logger.warn("Verification referer does not work because 'Site URL' is NOT set. All of attachments in share link page is invisible.");
     logger.warn("Verification referer does not work because 'Site URL' is NOT set. All of attachments in share link page is invisible.");
     return null;
     return null;

+ 1 - 1
apps/app/src/server/middlewares/exclude-read-only-user.ts

@@ -30,7 +30,7 @@ export const excludeReadOnlyUser = (req: Request, res: Response & { apiv3Err },
 export const excludeReadOnlyUserIfCommentNotAllowed = (req: Request, res: Response & { apiv3Err }, next: () => NextFunction): NextFunction => {
 export const excludeReadOnlyUserIfCommentNotAllowed = (req: Request, res: Response & { apiv3Err }, next: () => NextFunction): NextFunction => {
   const user = req.user;
   const user = req.user;
 
 
-  const isRomUserAllowedToComment = configManager.getConfig('crowi', 'security:isRomUserAllowedToComment');
+  const isRomUserAllowedToComment = configManager.getConfig('security:isRomUserAllowedToComment');
 
 
   if (user == null) {
   if (user == null) {
     logger.warn('req.user is null');
     logger.warn('req.user is null');

+ 2 - 4
apps/app/src/server/models/config.ts

@@ -22,15 +22,13 @@ interface ModelMethods { any }
 
 
 
 
 const schema = new Schema<IConfig>({
 const schema = new Schema<IConfig>({
-  ns: { type: String, required: true },
-  key: { type: String, required: true },
+  ns: { type: String },
+  key: { type: String, required: true, unique: true },
   value: { type: String, required: true },
   value: { type: String, required: true },
 }, {
 }, {
   timestamps: true,
   timestamps: true,
 });
 });
 
 
-// define unique compound index
-schema.index({ ns: 1, key: 1 }, { unique: true });
 schema.plugin(uniqueValidator);
 schema.plugin(uniqueValidator);
 
 
 /**
 /**

+ 2 - 2
apps/app/src/server/models/obsolete-page.js

@@ -513,8 +513,8 @@ export const getPageSchema = (crowi) => {
     validateCrowi();
     validateCrowi();
 
 
     // determine User condition
     // determine User condition
-    const hidePagesRestrictedByOwner = crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner');
-    const hidePagesRestrictedByGroup = crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByGroup');
+    const hidePagesRestrictedByOwner = crowi.configManager.getConfig('security:list-policy:hideRestrictedByOwner');
+    const hidePagesRestrictedByGroup = crowi.configManager.getConfig('security:list-policy:hideRestrictedByGroup');
 
 
     // determine UserGroup condition
     // determine UserGroup condition
     const userGroups = user != null ? [
     const userGroups = user != null ? [

+ 1 - 1
apps/app/src/server/models/page.ts

@@ -51,7 +51,7 @@ export interface PageDocument extends IPage, Document<Types.ObjectId> {
   [x:string]: any // for obsolete methods
   [x:string]: any // for obsolete methods
   getLatestRevisionBodyLength(): Promise<number | null | undefined>
   getLatestRevisionBodyLength(): Promise<number | null | undefined>
   calculateAndUpdateLatestRevisionBodyLength(this: PageDocument): Promise<void>
   calculateAndUpdateLatestRevisionBodyLength(this: PageDocument): Promise<void>
-  populateDataToShowRevision(shouldExcludeBody?: boolean): Promise<IPagePopulatedToShowRevision>
+  populateDataToShowRevision(shouldExcludeBody?: boolean): Promise<IPagePopulatedToShowRevision & PageDocument>
 }
 }
 
 
 type TargetAndAncestorsResult = {
 type TargetAndAncestorsResult = {

+ 2 - 2
apps/app/src/server/models/slack-app-integration.js

@@ -38,8 +38,8 @@ class SlackAppIntegration {
     let generateTokens;
     let generateTokens;
 
 
     // get salt strings
     // get salt strings
-    const saltForGtoP = this.crowi.configManager.getConfig('crowi', 'slackbot:withProxy:saltForGtoP');
-    const saltForPtoG = this.crowi.configManager.getConfig('crowi', 'slackbot:withProxy:saltForPtoG');
+    const saltForGtoP = this.crowi.configManager.getConfig('slackbot:withProxy:saltForGtoP');
+    const saltForPtoG = this.crowi.configManager.getConfig('slackbot:withProxy:saltForPtoG');
 
 
     do {
     do {
       generateTokens = this.generateAccessTokens(saltForGtoP, saltForPtoG);
       generateTokens = this.generateAccessTokens(saltForGtoP, saltForPtoG);

+ 8 - 8
apps/app/src/server/models/user.js

@@ -101,13 +101,13 @@ const factory = (crowi) => {
   function decideUserStatusOnRegistration() {
   function decideUserStatusOnRegistration() {
     validateCrowi();
     validateCrowi();
 
 
-    const isInstalled = configManager.getConfig('crowi', 'app:installed');
+    const isInstalled = configManager.getConfig('app:installed');
     if (!isInstalled) {
     if (!isInstalled) {
       return STATUS_ACTIVE; // is this ok?
       return STATUS_ACTIVE; // is this ok?
     }
     }
 
 
     // status decided depends on registrationMode
     // status decided depends on registrationMode
-    const registrationMode = configManager.getConfig('crowi', 'security:registrationMode');
+    const registrationMode = configManager.getConfig('security:registrationMode');
     switch (registrationMode) {
     switch (registrationMode) {
       case aclService.labels.SECURITY_REGISTRATION_MODE_OPEN:
       case aclService.labels.SECURITY_REGISTRATION_MODE_OPEN:
         return STATUS_ACTIVE;
         return STATUS_ACTIVE;
@@ -278,7 +278,7 @@ const factory = (crowi) => {
     this.name = name;
     this.name = name;
     this.username = username;
     this.username = username;
     this.status = STATUS_ACTIVE;
     this.status = STATUS_ACTIVE;
-    this.isEmailPublished = configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser');
+    this.isEmailPublished = configManager.getConfig('customize:isEmailPublishedForNewUser');
 
 
     this.save((err, userData) => {
     this.save((err, userData) => {
       userEvent.emit('activated', userData);
       userEvent.emit('activated', userData);
@@ -371,7 +371,7 @@ const factory = (crowi) => {
   userSchema.statics.isEmailValid = function(email, callback) {
   userSchema.statics.isEmailValid = function(email, callback) {
     validateCrowi();
     validateCrowi();
 
 
-    const whitelist = configManager.getConfig('crowi', 'security:registrationWhitelist');
+    const whitelist = configManager.getConfig('security:registrationWhitelist');
 
 
     if (Array.isArray(whitelist) && whitelist.length > 0) {
     if (Array.isArray(whitelist) && whitelist.length > 0) {
       return whitelist.some((allowedEmail) => {
       return whitelist.some((allowedEmail) => {
@@ -480,7 +480,7 @@ const factory = (crowi) => {
   };
   };
 
 
   userSchema.statics.isUserCountExceedsUpperLimit = async function() {
   userSchema.statics.isUserCountExceedsUpperLimit = async function() {
-    const userUpperLimit = configManager.getConfig('crowi', 'security:userUpperLimit');
+    const userUpperLimit = configManager.getConfig('security:userUpperLimit');
 
 
     const activeUsers = await this.countActiveUsers();
     const activeUsers = await this.countActiveUsers();
     if (userUpperLimit <= activeUsers) {
     if (userUpperLimit <= activeUsers) {
@@ -576,7 +576,7 @@ const factory = (crowi) => {
     newUser.setPassword(password);
     newUser.setPassword(password);
     newUser.status = STATUS_INVITED;
     newUser.status = STATUS_INVITED;
 
 
-    const globalLang = configManager.getConfig('crowi', 'app:globalLang');
+    const globalLang = configManager.getConfig('app:globalLang');
     if (globalLang != null) {
     if (globalLang != null) {
       newUser.lang = globalLang;
       newUser.lang = globalLang;
     }
     }
@@ -651,9 +651,9 @@ const factory = (crowi) => {
     }
     }
 
 
     // Default email show/hide is up to the administrator
     // Default email show/hide is up to the administrator
-    newUser.isEmailPublished = configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser');
+    newUser.isEmailPublished = configManager.getConfig('customize:isEmailPublishedForNewUser');
 
 
-    const globalLang = configManager.getConfig('crowi', 'app:globalLang');
+    const globalLang = configManager.getConfig('app:globalLang');
     if (globalLang != null) {
     if (globalLang != null) {
       newUser.lang = globalLang;
       newUser.lang = globalLang;
     }
     }

+ 2 - 2
apps/app/src/server/routes/admin.js

@@ -105,7 +105,7 @@ module.exports = function(crowi, app) {
       return res.json(ApiResponse.error('esa.io form is blank'));
       return res.json(ApiResponse.error('esa.io form is blank'));
     }
     }
 
 
-    await configManager.updateConfigsInTheSameNamespace('crowi', form);
+    await configManager.updateConfigs(form);
     importer.initializeEsaClient(); // let it run in the back aftert res
     importer.initializeEsaClient(); // let it run in the back aftert res
     const parameters = { action: SupportedAction.ACTION_ADMIN_ESA_DATA_UPDATED };
     const parameters = { action: SupportedAction.ACTION_ADMIN_ESA_DATA_UPDATED };
     activityEvent.emit('update', res.locals.activity._id, parameters);
     activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -127,7 +127,7 @@ module.exports = function(crowi, app) {
       return res.json(ApiResponse.error('Qiita form is blank'));
       return res.json(ApiResponse.error('Qiita form is blank'));
     }
     }
 
 
-    await configManager.updateConfigsInTheSameNamespace('crowi', form);
+    await configManager.updateConfigs(form);
     importer.initializeQiitaClient(); // let it run in the back aftert res
     importer.initializeQiitaClient(); // let it run in the back aftert res
     const parameters = { action: SupportedAction.ACTION_ADMIN_QIITA_DATA_UPDATED };
     const parameters = { action: SupportedAction.ACTION_ADMIN_QIITA_DATA_UPDATED };
     activityEvent.emit('update', res.locals.activity._id, parameters);
     activityEvent.emit('update', res.locals.activity._id, parameters);

+ 3 - 2
apps/app/src/server/routes/apiv3/activity.ts

@@ -7,6 +7,7 @@ import { query } from 'express-validator';
 import type { IActivity, ISearchFilter } from '~/interfaces/activity';
 import type { IActivity, ISearchFilter } from '~/interfaces/activity';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import Activity from '~/server/models/activity';
 import Activity from '~/server/models/activity';
+import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import type Crowi from '../../crowi';
 import type Crowi from '../../crowi';
@@ -34,14 +35,14 @@ module.exports = (crowi: Crowi): Router => {
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, validator.list, apiV3FormValidator, async(req: Request, res: ApiV3Response) => {
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, validator.list, apiV3FormValidator, async(req: Request, res: ApiV3Response) => {
-    const auditLogEnabled = crowi.configManager?.getConfig('crowi', 'app:auditLogEnabled') || false;
+    const auditLogEnabled = configManager.getConfig('app:auditLogEnabled');
     if (!auditLogEnabled) {
     if (!auditLogEnabled) {
       const msg = 'AuditLog is not enabled';
       const msg = 'AuditLog is not enabled';
       logger.error(msg);
       logger.error(msg);
       return res.apiv3Err(msg, 405);
       return res.apiv3Err(msg, 405);
     }
     }
 
 
-    const limit = req.query.limit || await crowi.configManager?.getConfig('crowi', 'customize:showPageLimitationS') || 10;
+    const limit = req.query.limit || configManager.getConfig('customize:showPageLimitationS');
     const offset = req.query.offset || 1;
     const offset = req.query.offset || 1;
 
 
     const query = {};
     const query = {};

+ 4 - 4
apps/app/src/server/routes/apiv3/admin-home.ts

@@ -1,4 +1,4 @@
-import ConfigLoader from '../../service/config-loader';
+import { configManager } from '~/server/service/config-manager';
 
 
 const express = require('express');
 const express = require('express');
 
 
@@ -91,9 +91,9 @@ module.exports = (crowi) => {
       nodeVersion: runtimeVersions.node ?? '-',
       nodeVersion: runtimeVersions.node ?? '-',
       npmVersion: runtimeVersions.npm ?? '-',
       npmVersion: runtimeVersions.npm ?? '-',
       pnpmVersion: runtimeVersions.pnpm ?? '-',
       pnpmVersion: runtimeVersions.pnpm ?? '-',
-      envVars: await ConfigLoader.getEnvVarsForDisplay(true),
-      isV5Compatible: crowi.configManager.getConfig('crowi', 'app:isV5Compatible'),
-      isMaintenanceMode: crowi.configManager.getConfig('crowi', 'app:isMaintenanceMode'),
+      envVars: configManager.getManagedEnvVars(),
+      isV5Compatible: configManager.getConfig('app:isV5Compatible'),
+      isMaintenanceMode: configManager.getConfig('app:isMaintenanceMode'),
     };
     };
 
 
     return res.apiv3({ adminHomeParams });
     return res.apiv3({ adminHomeParams });

+ 106 - 105
apps/app/src/server/routes/apiv3/app-settings.js

@@ -1,3 +1,4 @@
+import { ConfigSource } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
 
 
@@ -5,6 +6,7 @@ import { i18n } from '^/config/next-i18next.config';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
+import { configManager } from '~/server/service/config-manager';
 import { getTranslation } from '~/server/service/i18next';
 import { getTranslation } from '~/server/service/i18next';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -430,66 +432,66 @@ module.exports = (crowi) => {
    */
    */
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
     const appSettingsParams = {
     const appSettingsParams = {
-      title: crowi.configManager.getConfig('crowi', 'app:title'),
-      confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
-      globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
-      isEmailPublishedForNewUser: crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser'),
-      fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
-      isV5Compatible: crowi.configManager.getConfig('crowi', 'app:isV5Compatible'),
-      siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
-      siteUrlUseOnlyEnvVars: crowi.configManager.getConfig('crowi', 'app:siteUrl:useOnlyEnvVars'),
-      envSiteUrl: crowi.configManager.getConfigFromEnvVars('crowi', 'app:siteUrl'),
+      title: configManager.getConfig('app:title'),
+      confidential: configManager.getConfig('app:confidential'),
+      globalLang: configManager.getConfig('app:globalLang'),
+      isEmailPublishedForNewUser: configManager.getConfig('customize:isEmailPublishedForNewUser'),
+      fileUpload: configManager.getConfig('app:fileUpload'),
+      isV5Compatible: configManager.getConfig('app:isV5Compatible'),
+      siteUrl: configManager.getConfig('app:siteUrl'),
+      siteUrlUseOnlyEnvVars: configManager.getConfig('env:useOnlyEnvVars:app:siteUrl'),
+      envSiteUrl: configManager.getConfig('app:siteUrl', ConfigSource.env),
       isMailerSetup: crowi.mailService.isMailerSetup,
       isMailerSetup: crowi.mailService.isMailerSetup,
-      fromAddress: crowi.configManager.getConfig('crowi', 'mail:from'),
-
-      transmissionMethod: crowi.configManager.getConfig('crowi', 'mail:transmissionMethod'),
-      smtpHost: crowi.configManager.getConfig('crowi', 'mail:smtpHost'),
-      smtpPort: crowi.configManager.getConfig('crowi', 'mail:smtpPort'),
-      smtpUser: crowi.configManager.getConfig('crowi', 'mail:smtpUser'),
-      smtpPassword: crowi.configManager.getConfig('crowi', 'mail:smtpPassword'),
-      sesAccessKeyId: crowi.configManager.getConfig('crowi', 'mail:sesAccessKeyId'),
-      sesSecretAccessKey: crowi.configManager.getConfig('crowi', 'mail:sesSecretAccessKey'),
-
-      fileUploadType: crowi.configManager.getConfig('crowi', 'app:fileUploadType'),
-      envFileUploadType: crowi.configManager.getConfigFromEnvVars('crowi', 'app:fileUploadType'),
-      useOnlyEnvVarForFileUploadType: crowi.configManager.getConfig('crowi', 'app:useOnlyEnvVarForFileUploadType'),
-
-      s3Region: crowi.configManager.getConfig('crowi', 'aws:s3Region'),
-      s3CustomEndpoint: crowi.configManager.getConfig('crowi', 'aws:s3CustomEndpoint'),
-      s3Bucket: crowi.configManager.getConfig('crowi', 'aws:s3Bucket'),
-      s3AccessKeyId: crowi.configManager.getConfig('crowi', 'aws:s3AccessKeyId'),
-      s3ReferenceFileWithRelayMode: crowi.configManager.getConfig('crowi', 'aws:referenceFileWithRelayMode'),
-
-      gcsUseOnlyEnvVars: crowi.configManager.getConfig('crowi', 'gcs:useOnlyEnvVarsForSomeOptions'),
-      gcsApiKeyJsonPath: crowi.configManager.getConfig('crowi', 'gcs:apiKeyJsonPath'),
-      gcsBucket: crowi.configManager.getConfig('crowi', 'gcs:bucket'),
-      gcsUploadNamespace: crowi.configManager.getConfig('crowi', 'gcs:uploadNamespace'),
-      gcsReferenceFileWithRelayMode: crowi.configManager.getConfig('crowi', 'gcs:referenceFileWithRelayMode'),
-
-      envGcsApiKeyJsonPath: crowi.configManager.getConfigFromEnvVars('crowi', 'gcs:apiKeyJsonPath'),
-      envGcsBucket: crowi.configManager.getConfigFromEnvVars('crowi', 'gcs:bucket'),
-      envGcsUploadNamespace: crowi.configManager.getConfigFromEnvVars('crowi', 'gcs:uploadNamespace'),
-
-      azureUseOnlyEnvVars: crowi.configManager.getConfig('crowi', 'azure:useOnlyEnvVarsForSomeOptions'),
-      azureTenantId: crowi.configManager.getConfigFromDB('crowi', 'azure:tenantId'),
-      azureClientId: crowi.configManager.getConfigFromDB('crowi', 'azure:clientId'),
-      azureClientSecret: crowi.configManager.getConfigFromDB('crowi', 'azure:clientSecret'),
-      azureStorageAccountName: crowi.configManager.getConfigFromDB('crowi', 'azure:storageAccountName'),
-      azureStorageContainerName: crowi.configManager.getConfigFromDB('crowi', 'azure:storageContainerName'),
-      azureReferenceFileWithRelayMode: crowi.configManager.getConfig('crowi', 'azure:referenceFileWithRelayMode'),
-
-      envAzureTenantId: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:tenantId'),
-      envAzureClientId: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:clientId'),
-      envAzureClientSecret: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:clientSecret'),
-      envAzureStorageAccountName: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:storageAccountName'),
-      envAzureStorageContainerName: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:storageContainerName'),
-
-      isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
-
-      isQuestionnaireEnabled: crowi.configManager.getConfig('crowi', 'questionnaire:isQuestionnaireEnabled'),
-      isAppSiteUrlHashed: crowi.configManager.getConfig('crowi', 'questionnaire:isAppSiteUrlHashed'),
-
-      isMaintenanceMode: crowi.configManager.getConfig('crowi', 'app:isMaintenanceMode'),
+      fromAddress: configManager.getConfig('mail:from'),
+
+      transmissionMethod: configManager.getConfig('mail:transmissionMethod'),
+      smtpHost: configManager.getConfig('mail:smtpHost'),
+      smtpPort: configManager.getConfig('mail:smtpPort'),
+      smtpUser: configManager.getConfig('mail:smtpUser'),
+      smtpPassword: configManager.getConfig('mail:smtpPassword'),
+      sesAccessKeyId: configManager.getConfig('mail:sesAccessKeyId'),
+      sesSecretAccessKey: configManager.getConfig('mail:sesSecretAccessKey'),
+
+      fileUploadType: configManager.getConfig('app:fileUploadType'),
+      envFileUploadType: configManager.getConfig('app:fileUploadType', ConfigSource.env),
+      useOnlyEnvVarForFileUploadType: configManager.getConfig('env:useOnlyEnvVars:app:fileUploadType'),
+
+      s3Region: configManager.getConfig('aws:s3Region'),
+      s3CustomEndpoint: configManager.getConfig('aws:s3CustomEndpoint'),
+      s3Bucket: configManager.getConfig('aws:s3Bucket'),
+      s3AccessKeyId: configManager.getConfig('aws:s3AccessKeyId'),
+      s3ReferenceFileWithRelayMode: configManager.getConfig('aws:referenceFileWithRelayMode'),
+
+      gcsUseOnlyEnvVars: configManager.getConfig('env:useOnlyEnvVars:gcs'),
+      gcsApiKeyJsonPath: configManager.getConfig('gcs:apiKeyJsonPath'),
+      gcsBucket: configManager.getConfig('gcs:bucket'),
+      gcsUploadNamespace: configManager.getConfig('gcs:uploadNamespace'),
+      gcsReferenceFileWithRelayMode: configManager.getConfig('gcs:referenceFileWithRelayMode'),
+
+      envGcsApiKeyJsonPath: configManager.getConfig('gcs:apiKeyJsonPath', ConfigSource.env),
+      envGcsBucket: configManager.getConfig('gcs:bucket', ConfigSource.env),
+      envGcsUploadNamespace: configManager.getConfig('gcs:uploadNamespace', ConfigSource.env),
+
+      azureUseOnlyEnvVars: configManager.getConfig('env:useOnlyEnvVars:azure'),
+      azureTenantId: configManager.getConfig('azure:tenantId', ConfigSource.db),
+      azureClientId: configManager.getConfig('azure:clientId', ConfigSource.db),
+      azureClientSecret: configManager.getConfig('azure:clientSecret', ConfigSource.db),
+      azureStorageAccountName: configManager.getConfig('azure:storageAccountName', ConfigSource.db),
+      azureStorageContainerName: configManager.getConfig('azure:storageContainerName', ConfigSource.db),
+      azureReferenceFileWithRelayMode: configManager.getConfig('azure:referenceFileWithRelayMode'),
+
+      envAzureTenantId: configManager.getConfig('azure:tenantId', ConfigSource.env),
+      envAzureClientId: configManager.getConfig('azure:clientId', ConfigSource.env),
+      envAzureClientSecret: configManager.getConfig('azure:clientSecret', ConfigSource.env),
+      envAzureStorageAccountName: configManager.getConfig('azure:storageAccountName', ConfigSource.env),
+      envAzureStorageContainerName: configManager.getConfig('azure:storageContainerName', ConfigSource.env),
+
+      isEnabledPlugins: configManager.getConfig('plugin:isEnabledPlugins'),
+
+      isQuestionnaireEnabled: configManager.getConfig('questionnaire:isQuestionnaireEnabled'),
+      isAppSiteUrlHashed: configManager.getConfig('questionnaire:isAppSiteUrlHashed'),
+
+      isMaintenanceMode: configManager.getConfig('app:isMaintenanceMode'),
     };
     };
     return res.apiv3({ appSettingsParams });
     return res.apiv3({ appSettingsParams });
 
 
@@ -535,13 +537,13 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestAppSettingParams);
+      await configManager.updateConfigs(requestAppSettingParams);
       const appSettingParams = {
       const appSettingParams = {
-        title: crowi.configManager.getConfig('crowi', 'app:title'),
-        confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
-        globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
-        isEmailPublishedForNewUser: crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser'),
-        fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
+        title: configManager.getConfig('app:title'),
+        confidential: configManager.getConfig('app:confidential'),
+        globalLang: configManager.getConfig('app:globalLang'),
+        isEmailPublishedForNewUser: configManager.getConfig('customize:isEmailPublishedForNewUser'),
+        fileUpload: configManager.getConfig('app:fileUpload'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_APP_SETTINGS_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_APP_SETTINGS_UPDATE };
@@ -592,7 +594,7 @@ module.exports = (crowi) => {
    */
    */
   router.put('/site-url-setting', loginRequiredStrictly, adminRequired, addActivity, validator.siteUrlSetting, apiV3FormValidator, async(req, res) => {
   router.put('/site-url-setting', loginRequiredStrictly, adminRequired, addActivity, validator.siteUrlSetting, apiV3FormValidator, async(req, res) => {
 
 
-    const useOnlyEnvVars = crowi.configManager.getConfig('crowi', 'app:siteUrl:useOnlyEnvVars');
+    const useOnlyEnvVars = configManager.getConfig('env:useOnlyEnvVars:app:siteUrl');
 
 
     if (useOnlyEnvVars) {
     if (useOnlyEnvVars) {
       const msg = 'Updating the Site URL is prohibited on this system.';
       const msg = 'Updating the Site URL is prohibited on this system.';
@@ -604,9 +606,9 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestSiteUrlSettingParams);
+      await configManager.updateConfigs(requestSiteUrlSettingParams);
       const siteUrlSettingParams = {
       const siteUrlSettingParams = {
-        siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
+        siteUrl: configManager.getConfig('app:siteUrl'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_SITE_URL_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_SITE_URL_UPDATE };
@@ -642,21 +644,21 @@ module.exports = (crowi) => {
    */
    */
   async function sendTestEmail(destinationAddress) {
   async function sendTestEmail(destinationAddress) {
 
 
-    const { configManager, mailService } = crowi;
+    const { mailService } = crowi;
 
 
     if (!mailService.isMailerSetup) {
     if (!mailService.isMailerSetup) {
       throw Error('mailService is not setup');
       throw Error('mailService is not setup');
     }
     }
 
 
-    const fromAddress = configManager.getConfig('crowi', 'mail:from');
+    const fromAddress = configManager.getConfig('mail:from');
     if (fromAddress == null) {
     if (fromAddress == null) {
       throw Error('fromAddress is not setup');
       throw Error('fromAddress is not setup');
     }
     }
 
 
-    const smtpHost = configManager.getConfig('crowi', 'mail:smtpHost');
-    const smtpPort = configManager.getConfig('crowi', 'mail:smtpPort');
-    const smtpUser = configManager.getConfig('crowi', 'mail:smtpUser');
-    const smtpPassword = configManager.getConfig('crowi', 'mail:smtpPassword');
+    const smtpHost = configManager.getConfig('mail:smtpHost');
+    const smtpPort = configManager.getConfig('mail:smtpPort');
+    const smtpUser = configManager.getConfig('mail:smtpUser');
+    const smtpPassword = configManager.getConfig('mail:smtpPassword');
 
 
     const option = {
     const option = {
       host: smtpHost,
       host: smtpHost,
@@ -687,25 +689,24 @@ module.exports = (crowi) => {
 
 
   const updateMailSettinConfig = async function(requestMailSettingParams) {
   const updateMailSettinConfig = async function(requestMailSettingParams) {
     const {
     const {
-      configManager,
       mailService,
       mailService,
     } = crowi;
     } = crowi;
 
 
     // update config without publishing S2sMessage
     // update config without publishing S2sMessage
-    await configManager.updateConfigsInTheSameNamespace('crowi', requestMailSettingParams, true);
+    await configManager.updateConfigs(requestMailSettingParams, { skipPubsub: true });
 
 
     await mailService.initialize();
     await mailService.initialize();
     mailService.publishUpdatedMessage();
     mailService.publishUpdatedMessage();
 
 
     return {
     return {
       isMailerSetup: mailService.isMailerSetup,
       isMailerSetup: mailService.isMailerSetup,
-      fromAddress: configManager.getConfig('crowi', 'mail:from'),
-      smtpHost: configManager.getConfig('crowi', 'mail:smtpHost'),
-      smtpPort: configManager.getConfig('crowi', 'mail:smtpPort'),
-      smtpUser: configManager.getConfig('crowi', 'mail:smtpUser'),
-      smtpPassword: configManager.getConfig('crowi', 'mail:smtpPassword'),
-      sesAccessKeyId: configManager.getConfig('crowi', 'mail:sesAccessKeyId'),
-      sesSecretAccessKey: configManager.getConfig('crowi', 'mail:sesSecretAccessKey'),
+      fromAddress: configManager.getConfig('mail:from'),
+      smtpHost: configManager.getConfig('mail:smtpHost'),
+      smtpPort: configManager.getConfig('mail:smtpPort'),
+      smtpUser: configManager.getConfig('mail:smtpUser'),
+      smtpPassword: configManager.getConfig('mail:smtpPassword'),
+      sesAccessKeyId: configManager.getConfig('mail:sesAccessKeyId'),
+      sesSecretAccessKey: configManager.getConfig('mail:sesSecretAccessKey'),
     };
     };
   };
   };
 
 
@@ -912,42 +913,42 @@ module.exports = (crowi) => {
     }
     }
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true);
+      await configManager.updateConfigs(requestParams, { skipPubsub: true });
 
 
       const s3SecretAccessKey = req.body.s3SecretAccessKey;
       const s3SecretAccessKey = req.body.s3SecretAccessKey;
       if (fileUploadType === 'aws' && s3SecretAccessKey != null && s3SecretAccessKey.trim() !== '') {
       if (fileUploadType === 'aws' && s3SecretAccessKey != null && s3SecretAccessKey.trim() !== '') {
-        await crowi.configManager.updateConfigsInTheSameNamespace('crowi', { 'aws:s3SecretAccessKey': s3SecretAccessKey }, true);
+        await configManager.updateConfigs({ 'aws:s3SecretAccessKey': s3SecretAccessKey }, { skipPubsub: true });
       }
       }
 
 
       await crowi.setUpFileUpload(true);
       await crowi.setUpFileUpload(true);
       crowi.fileUploaderSwitchService.publishUpdatedMessage();
       crowi.fileUploaderSwitchService.publishUpdatedMessage();
 
 
       const responseParams = {
       const responseParams = {
-        fileUploadType: crowi.configManager.getConfig('crowi', 'app:fileUploadType'),
+        fileUploadType: configManager.getConfig('app:fileUploadType'),
       };
       };
 
 
       if (fileUploadType === 'gcs') {
       if (fileUploadType === 'gcs') {
-        responseParams.gcsApiKeyJsonPath = crowi.configManager.getConfig('crowi', 'gcs:apiKeyJsonPath');
-        responseParams.gcsBucket = crowi.configManager.getConfig('crowi', 'gcs:bucket');
-        responseParams.gcsUploadNamespace = crowi.configManager.getConfig('crowi', 'gcs:uploadNamespace');
-        responseParams.gcsReferenceFileWithRelayMode = crowi.configManager.getConfig('crowi', 'gcs:referenceFileWithRelayMode ');
+        responseParams.gcsApiKeyJsonPath = configManager.getConfig('gcs:apiKeyJsonPath');
+        responseParams.gcsBucket = configManager.getConfig('gcs:bucket');
+        responseParams.gcsUploadNamespace = configManager.getConfig('gcs:uploadNamespace');
+        responseParams.gcsReferenceFileWithRelayMode = configManager.getConfig('gcs:referenceFileWithRelayMode ');
       }
       }
 
 
       if (fileUploadType === 'aws') {
       if (fileUploadType === 'aws') {
-        responseParams.s3Region = crowi.configManager.getConfig('crowi', 'aws:s3Region');
-        responseParams.s3CustomEndpoint = crowi.configManager.getConfig('crowi', 'aws:s3CustomEndpoint');
-        responseParams.s3Bucket = crowi.configManager.getConfig('crowi', 'aws:s3Bucket');
-        responseParams.s3AccessKeyId = crowi.configManager.getConfig('crowi', 'aws:s3AccessKeyId');
-        responseParams.s3ReferenceFileWithRelayMode = crowi.configManager.getConfig('crowi', 'aws:referenceFileWithRelayMode');
+        responseParams.s3Region = configManager.getConfig('aws:s3Region');
+        responseParams.s3CustomEndpoint = configManager.getConfig('aws:s3CustomEndpoint');
+        responseParams.s3Bucket = configManager.getConfig('aws:s3Bucket');
+        responseParams.s3AccessKeyId = configManager.getConfig('aws:s3AccessKeyId');
+        responseParams.s3ReferenceFileWithRelayMode = configManager.getConfig('aws:referenceFileWithRelayMode');
       }
       }
 
 
       if (fileUploadType === 'azure') {
       if (fileUploadType === 'azure') {
-        responseParams.azureTenantId = crowi.configManager.getConfig('crowi', 'azure:tenantId');
-        responseParams.azureClientId = crowi.configManager.getConfig('crowi', 'azure:clientId');
-        responseParams.azureClientSecret = crowi.configManager.getConfig('crowi', 'azure:clientSecret');
-        responseParams.azureStorageAccountName = crowi.configManager.getConfig('crowi', 'azure:storageAccountName');
-        responseParams.azureStorageContainerName = crowi.configManager.getConfig('crowi', 'azure:storageContainerName');
-        responseParams.azureReferenceFileWithRelayMode = crowi.configManager.getConfig('crowi', 'azure:referenceFileWithRelayMode');
+        responseParams.azureTenantId = configManager.getConfig('azure:tenantId');
+        responseParams.azureClientId = configManager.getConfig('azure:clientId');
+        responseParams.azureClientSecret = configManager.getConfig('azure:clientSecret');
+        responseParams.azureStorageAccountName = configManager.getConfig('azure:storageAccountName');
+        responseParams.azureStorageContainerName = configManager.getConfig('azure:storageContainerName');
+        responseParams.azureReferenceFileWithRelayMode = configManager.getConfig('azure:referenceFileWithRelayMode');
       }
       }
       const parameters = { action: SupportedAction.ACTION_ADMIN_FILE_UPLOAD_CONFIG_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_FILE_UPLOAD_CONFIG_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -1000,11 +1001,11 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true);
+      await configManager.updateConfigs(requestParams, { skipPubsub: true });
 
 
       const responseParams = {
       const responseParams = {
-        isQuestionnaireEnabled: crowi.configManager.getConfig('crowi', 'questionnaire:isQuestionnaireEnabled'),
-        isAppSiteUrlHashed: crowi.configManager.getConfig('crowi', 'questionnaire:isAppSiteUrlHashed'),
+        isQuestionnaireEnabled: configManager.getConfig('questionnaire:isQuestionnaireEnabled'),
+        isAppSiteUrlHashed: configManager.getConfig('questionnaire:isAppSiteUrlHashed'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_QUESTIONNAIRE_SETTINGS_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_QUESTIONNAIRE_SETTINGS_UPDATE };
@@ -1049,7 +1050,7 @@ module.exports = (crowi) => {
       return res.apiv3Err(new ErrorV3('GROWI is not maintenance mode. To import data, please activate the maintenance mode first.', 'not_maintenance_mode'));
       return res.apiv3Err(new ErrorV3('GROWI is not maintenance mode. To import data, please activate the maintenance mode first.', 'not_maintenance_mode'));
     }
     }
 
 
-    const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
+    const isV5Compatible = configManager.getConfig('app:isV5Compatible');
 
 
     try {
     try {
       if (!isV5Compatible) {
       if (!isV5Compatible) {

+ 1 - 1
apps/app/src/server/routes/apiv3/attachment.js

@@ -199,7 +199,7 @@ module.exports = (crowi) => {
    */
    */
   router.get('/list', accessTokenParser, loginRequired, validator.retrieveAttachments, apiV3FormValidator, async(req, res) => {
   router.get('/list', accessTokenParser, loginRequired, validator.retrieveAttachments, apiV3FormValidator, async(req, res) => {
 
 
-    const limit = req.query.limit || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS') || 10;
+    const limit = req.query.limit || await crowi.configManager.getConfig('customize:showPageLimitationS') || 10;
     const pageNumber = req.query.pageNumber || 1;
     const pageNumber = req.query.pageNumber || 1;
     const offset = (pageNumber - 1) * limit;
     const offset = (pageNumber - 1) * limit;
 
 

+ 53 - 52
apps/app/src/server/routes/apiv3/customize-setting.js

@@ -10,6 +10,7 @@ import { GrowiPlugin } from '~/features/growi-plugin/server/models';
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { Attachment } from '~/server/models/attachment';
 import { Attachment } from '~/server/models/attachment';
+import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -169,22 +170,22 @@ module.exports = (crowi) => {
    */
    */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
     const customizeParams = {
     const customizeParams = {
-      isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
-      isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
-      pageLimitationS: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS'),
-      pageLimitationM: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM'),
-      pageLimitationL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationL'),
-      pageLimitationXL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL'),
-      isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
-      isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'),
-      isSearchScopeChildrenAsDefault: await crowi.configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault'),
-      isEnabledMarp: await crowi.configManager.getConfig('crowi', 'customize:isEnabledMarp'),
-      styleName: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
-      styleBorder: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
-      customizeTitle: await crowi.configManager.getConfig('crowi', 'customize:title'),
-      customizeScript: await crowi.configManager.getConfig('crowi', 'customize:script'),
-      customizeCss: await crowi.configManager.getConfig('crowi', 'customize:css'),
-      customizeNoscript: await crowi.configManager.getConfig('crowi', 'customize:noscript'),
+      isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
+      isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
+      pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
+      pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
+      pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
+      pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
+      isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
+      isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
+      isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
+      isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
+      styleName: await configManager.getConfig('customize:highlightJsStyle'),
+      styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
+      customizeTitle: await configManager.getConfig('customize:title'),
+      customizeScript: await configManager.getConfig('customize:script'),
+      customizeCss: await configManager.getConfig('customize:css'),
+      customizeNoscript: await configManager.getConfig('customize:noscript'),
     };
     };
 
 
     return res.apiv3({ customizeParams });
     return res.apiv3({ customizeParams });
@@ -210,7 +211,7 @@ module.exports = (crowi) => {
   router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     try {
     try {
-      const isContainerFluid = await crowi.configManager.getConfig('crowi', 'customize:isContainerFluid');
+      const isContainerFluid = await configManager.getConfig('customize:isContainerFluid');
       return res.apiv3({ isContainerFluid });
       return res.apiv3({ isContainerFluid });
     }
     }
     catch (err) {
     catch (err) {
@@ -249,9 +250,9 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        isContainerFluid: await crowi.configManager.getConfig('crowi', 'customize:isContainerFluid'),
+        isContainerFluid: await configManager.getConfig('customize:isContainerFluid'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_LAYOUT_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_LAYOUT_UPDATE };
@@ -269,7 +270,7 @@ module.exports = (crowi) => {
   router.get('/theme', loginRequiredStrictly, async(req, res) => {
   router.get('/theme', loginRequiredStrictly, async(req, res) => {
 
 
     try {
     try {
-      const currentTheme = await crowi.configManager.getConfig('crowi', 'customize:theme');
+      const currentTheme = await configManager.getConfig('customize:theme');
 
 
       // retrieve plugin manifests
       // retrieve plugin manifests
       const themePlugins = await GrowiPlugin.findEnabledPluginsByType(GrowiPluginType.Theme);
       const themePlugins = await GrowiPlugin.findEnabledPluginsByType(GrowiPluginType.Theme);
@@ -316,9 +317,9 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        theme: await crowi.configManager.getConfig('crowi', 'customize:theme'),
+        theme: await configManager.getConfig('customize:theme'),
       };
       };
       customizeService.initGrowiTheme();
       customizeService.initGrowiTheme();
       const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
@@ -336,8 +337,8 @@ module.exports = (crowi) => {
   router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     try {
     try {
-      const isSidebarCollapsedMode = await crowi.configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode');
-      const isSidebarClosedAtDockMode = await crowi.configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode');
+      const isSidebarCollapsedMode = await configManager.getConfig('customize:isSidebarCollapsedMode');
+      const isSidebarClosedAtDockMode = await configManager.getConfig('customize:isSidebarClosedAtDockMode');
       return res.apiv3({ isSidebarCollapsedMode, isSidebarClosedAtDockMode });
       return res.apiv3({ isSidebarCollapsedMode, isSidebarClosedAtDockMode });
     }
     }
     catch (err) {
     catch (err) {
@@ -354,10 +355,10 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        isSidebarCollapsedMode: await crowi.configManager.getConfig('crowi', 'customize:isSidebarCollapsedMode'),
-        isSidebarClosedAtDockMode: await crowi.configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+        isSidebarCollapsedMode: await configManager.getConfig('customize:isSidebarCollapsedMode'),
+        isSidebarClosedAtDockMode: await configManager.getConfig('customize:isSidebarClosedAtDockMode'),
       };
       };
 
 
       activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SIDEBAR_UPDATE });
       activityEvent.emit('update', res.locals.activity._id, { action: SupportedAction.ACTION_ADMIN_SIDEBAR_UPDATE });
@@ -408,17 +409,17 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
-        isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
-        pageLimitationS: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS'),
-        pageLimitationM: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM'),
-        pageLimitationL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationL'),
-        pageLimitationXL: await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL'),
-        isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
-        isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'),
-        isSearchScopeChildrenAsDefault: await crowi.configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault'),
+        isEnabledTimeline: await configManager.getConfig('customize:isEnabledTimeline'),
+        isEnabledAttachTitleHeader: await configManager.getConfig('customize:isEnabledAttachTitleHeader'),
+        pageLimitationS: await configManager.getConfig('customize:showPageLimitationS'),
+        pageLimitationM: await configManager.getConfig('customize:showPageLimitationM'),
+        pageLimitationL: await configManager.getConfig('customize:showPageLimitationL'),
+        pageLimitationXL: await configManager.getConfig('customize:showPageLimitationXL'),
+        isEnabledStaleNotification: await configManager.getConfig('customize:isEnabledStaleNotification'),
+        isAllReplyShown: await configManager.getConfig('customize:isAllReplyShown'),
+        isSearchScopeChildrenAsDefault: await configManager.getConfig('customize:isSearchScopeChildrenAsDefault'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -438,9 +439,9 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        isEnabledMarp: await crowi.configManager.getConfig('crowi', 'customize:isEnabledMarp'),
+        isEnabledMarp: await configManager.getConfig('customize:isEnabledMarp'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -483,10 +484,10 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        styleName: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
-        styleBorder: await crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+        styleName: await configManager.getConfig('customize:highlightJsStyle'),
+        styleBorder: await configManager.getConfig('customize:highlightJsStyleBorder'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -528,11 +529,11 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true);
+      await configManager.updateConfigs(requestParams, { skipPubsub: true });
       crowi.customizeService.publishUpdatedMessage();
       crowi.customizeService.publishUpdatedMessage();
 
 
       const customizedParams = {
       const customizedParams = {
-        customizeTitle: await crowi.configManager.getConfig('crowi', 'customize:title'),
+        customizeTitle: await configManager.getConfig('customize:title'),
       };
       };
       customizeService.initCustomTitle();
       customizeService.initCustomTitle();
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_TITLE_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_TITLE_UPDATE };
@@ -574,9 +575,9 @@ module.exports = (crowi) => {
       'customize:noscript': req.body.customizeNoscript,
       'customize:noscript': req.body.customizeNoscript,
     };
     };
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        customizeNoscript: await crowi.configManager.getConfig('crowi', 'customize:noscript'),
+        customizeNoscript: await configManager.getConfig('customize:noscript'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_NOSCRIPT_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_NOSCRIPT_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -617,11 +618,11 @@ module.exports = (crowi) => {
       'customize:css': req.body.customizeCss,
       'customize:css': req.body.customizeCss,
     };
     };
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true);
+      await configManager.updateConfigs(requestParams, { skipPubsub: true });
       crowi.customizeService.publishUpdatedMessage();
       crowi.customizeService.publishUpdatedMessage();
 
 
       const customizedParams = {
       const customizedParams = {
-        customizeCss: await crowi.configManager.getConfig('crowi', 'customize:css'),
+        customizeCss: await configManager.getConfig('customize:css'),
       };
       };
       customizeService.initCustomCss();
       customizeService.initCustomCss();
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_CSS_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_CSS_UPDATE };
@@ -663,9 +664,9 @@ module.exports = (crowi) => {
       'customize:script': req.body.customizeScript,
       'customize:script': req.body.customizeScript,
     };
     };
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        customizeScript: await crowi.configManager.getConfig('crowi', 'customize:script'),
+        customizeScript: await configManager.getConfig('customize:script'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -688,9 +689,9 @@ module.exports = (crowi) => {
       'customize:isDefaultLogo': isDefaultLogo,
       'customize:isDefaultLogo': isDefaultLogo,
     };
     };
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      await configManager.updateConfigs(requestParams);
       const customizedParams = {
       const customizedParams = {
-        isDefaultLogo: await crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo'),
+        isDefaultLogo: await configManager.getConfig('customize:isDefaultLogo'),
       };
       };
       return res.apiv3({ customizedParams });
       return res.apiv3({ customizedParams });
     }
     }

+ 3 - 3
apps/app/src/server/routes/apiv3/forgot-password.js

@@ -30,7 +30,7 @@ module.exports = (crowi) => {
 
 
   const activityEvent = crowi.event('activity');
   const activityEvent = crowi.event('activity');
 
 
-  const minPasswordLength = configManager.getConfig('crowi', 'app:minPasswordLength');
+  const minPasswordLength = configManager.getConfig('app:minPasswordLength');
 
 
   const validator = {
   const validator = {
     password: [
     password: [
@@ -71,7 +71,7 @@ module.exports = (crowi) => {
 
 
   router.post('/', checkPassportStrategyMiddleware, validator.email, apiV3FormValidator, addActivity, async(req, res) => {
   router.post('/', checkPassportStrategyMiddleware, validator.email, apiV3FormValidator, addActivity, async(req, res) => {
     const { email } = req.body;
     const { email } = req.body;
-    const locale = configManager.getConfig('crowi', 'app:globalLang');
+    const locale = configManager.getConfig('app:globalLang');
     const appUrl = appService.getSiteUrl();
     const appUrl = appService.getSiteUrl();
 
 
     try {
     try {
@@ -107,7 +107,7 @@ module.exports = (crowi) => {
   router.put('/', checkPassportStrategyMiddleware, injectResetOrderByTokenMiddleware, validator.password, apiV3FormValidator, addActivity, async(req, res) => {
   router.put('/', checkPassportStrategyMiddleware, injectResetOrderByTokenMiddleware, validator.password, apiV3FormValidator, addActivity, async(req, res) => {
     const { passwordResetOrder } = req;
     const { passwordResetOrder } = req;
     const { email } = passwordResetOrder;
     const { email } = passwordResetOrder;
-    const grobalLang = configManager.getConfig('crowi', 'app:globalLang');
+    const grobalLang = configManager.getConfig('app:globalLang');
     const i18n = grobalLang || req.language;
     const i18n = grobalLang || req.language;
     const { newPassword } = req.body;
     const { newPassword } = req.body;
 
 

+ 3 - 3
apps/app/src/server/routes/apiv3/g2g-transfer.ts

@@ -82,7 +82,7 @@ module.exports = (crowi: Crowi): Router => {
     }),
     }),
   });
   });
 
 
-  const isInstalled = crowi.configManager?.getConfig('crowi', 'app:installed');
+  const isInstalled = configManager.getConfig('app:installed');
 
 
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
@@ -104,7 +104,7 @@ module.exports = (crowi: Crowi): Router => {
       return;
       return;
     }
     }
 
 
-    if (crowi.configManager?.getConfig('crowi', 'app:siteUrl') != null || req.body.appSiteUrl != null) {
+    if (configManager.getConfig('app:siteUrl') != null || req.body.appSiteUrl != null) {
       next();
       next();
       return;
       return;
     }
     }
@@ -270,7 +270,7 @@ module.exports = (crowi: Crowi): Router => {
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   receiveRouter.post('/generate-key', accessTokenParser, adminRequiredIfInstalled, appSiteUrlRequiredIfNotInstalled, async(req: Request, res: ApiV3Response) => {
   receiveRouter.post('/generate-key', accessTokenParser, adminRequiredIfInstalled, appSiteUrlRequiredIfNotInstalled, async(req: Request, res: ApiV3Response) => {
-    const appSiteUrl = req.body.appSiteUrl ?? crowi.configManager?.getConfig('crowi', 'app:siteUrl');
+    const appSiteUrl = req.body.appSiteUrl ?? configManager.getConfig('app:siteUrl');
 
 
     let appSiteUrlOrigin: string;
     let appSiteUrlOrigin: string;
     try {
     try {

+ 5 - 5
apps/app/src/server/routes/apiv3/import.js

@@ -118,10 +118,10 @@ export default function route(crowi) {
   router.get('/', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
   router.get('/', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
     try {
     try {
       const importSettingsParams = {
       const importSettingsParams = {
-        esaTeamName: await crowi.configManager.getConfig('crowi', 'importer:esa:team_name'),
-        esaAccessToken: await crowi.configManager.getConfig('crowi', 'importer:esa:access_token'),
-        qiitaTeamName: await crowi.configManager.getConfig('crowi', 'importer:qiita:team_name'),
-        qiitaAccessToken: await crowi.configManager.getConfig('crowi', 'importer:qiita:access_token'),
+        esaTeamName: await crowi.configManager.getConfig('importer:esa:team_name'),
+        esaAccessToken: await crowi.configManager.getConfig('importer:esa:access_token'),
+        qiitaTeamName: await crowi.configManager.getConfig('importer:qiita:team_name'),
+        qiitaAccessToken: await crowi.configManager.getConfig('importer:qiita:access_token'),
       };
       };
       return res.apiv3({
       return res.apiv3({
         importSettingsParams,
         importSettingsParams,
@@ -201,7 +201,7 @@ export default function route(crowi) {
     const { fileName, collections, options } = req.body;
     const { fileName, collections, options } = req.body;
 
 
     // pages collection can only be imported by upsert if isV5Compatible is true
     // pages collection can only be imported by upsert if isV5Compatible is true
-    const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
+    const isV5Compatible = crowi.configManager.getConfig('app:isV5Compatible');
     const isImportPagesCollection = collections.includes('pages');
     const isImportPagesCollection = collections.includes('pages');
     if (isV5Compatible && isImportPagesCollection) {
     if (isV5Compatible && isImportPagesCollection) {
       /** @type {ImportOptionForPages} */
       /** @type {ImportOptionForPages} */

+ 2 - 2
apps/app/src/server/routes/apiv3/index.js

@@ -22,8 +22,8 @@ const routerForAdmin = express.Router();
 const routerForAuth = express.Router();
 const routerForAuth = express.Router();
 
 
 module.exports = (crowi, app) => {
 module.exports = (crowi, app) => {
-  const isInstalled = crowi.configManager.getConfig('crowi', 'app:installed');
-  const minPasswordLength = crowi.configManager.getConfig('crowi', 'app:minPasswordLength');
+  const isInstalled = crowi.configManager.getConfig('app:installed');
+  const minPasswordLength = crowi.configManager.getConfig('app:minPasswordLength');
 
 
   // add custom functions to express response
   // add custom functions to express response
   require('./response')(express, crowi);
   require('./response')(express, crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/installer.ts

@@ -27,7 +27,7 @@ module.exports = (crowi: Crowi): Router => {
 
 
   const router = express.Router();
   const router = express.Router();
 
 
-  const minPasswordLength = configManager.getConfig('crowi', 'app:minPasswordLength');
+  const minPasswordLength = configManager.getConfig('app:minPasswordLength');
 
 
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.post('/', registerRules(minPasswordLength), registerValidation, addActivity, async(req: FormRequest, res: ApiV3Response) => {
   router.post('/', registerRules(minPasswordLength), registerValidation, addActivity, async(req: FormRequest, res: ApiV3Response) => {

+ 20 - 19
apps/app/src/server/routes/apiv3/markdown-setting.js

@@ -1,6 +1,7 @@
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
+import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -110,14 +111,14 @@ module.exports = (crowi) => {
    */
    */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
     const markdownParams = {
     const markdownParams = {
-      isEnabledLinebreaks: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
-      isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
-      adminPreferredIndentSize: await crowi.configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
-      isIndentSizeForced: await crowi.configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
-      isEnabledXss: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
-      xssOption: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
-      tagWhitelist: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
-      attrWhitelist: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes'),
+      isEnabledLinebreaks: await crowi.configManager.getConfig('markdown:isEnabledLinebreaks'),
+      isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+      adminPreferredIndentSize: await crowi.configManager.getConfig('markdown:adminPreferredIndentSize'),
+      isIndentSizeForced: await crowi.configManager.getConfig('markdown:isIndentSizeForced'),
+      isEnabledXss: await crowi.configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+      xssOption: await crowi.configManager.getConfig('markdown:rehypeSanitize:option'),
+      tagWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
+      attrWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:attributes'),
     };
     };
 
 
     return res.apiv3({ markdownParams });
     return res.apiv3({ markdownParams });
@@ -154,10 +155,10 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('markdown', requestLineBreakParams);
+      await configManager.updateConfigs(requestLineBreakParams);
       const lineBreaksParams = {
       const lineBreaksParams = {
-        isEnabledLinebreaks: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
-        isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
+        isEnabledLinebreaks: await crowi.configManager.getConfig('markdown:isEnabledLinebreaks'),
+        isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_LINE_BREAK_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_LINE_BREAK_UPDATE };
@@ -181,10 +182,10 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('markdown', requestIndentParams);
+      await configManager.updateConfigs(requestIndentParams);
       const indentParams = {
       const indentParams = {
-        adminPreferredIndentSize: await crowi.configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
-        isIndentSizeForced: await crowi.configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+        adminPreferredIndentSize: await crowi.configManager.getConfig('markdown:adminPreferredIndentSize'),
+        isIndentSizeForced: await crowi.configManager.getConfig('markdown:isIndentSizeForced'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_INDENT_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_INDENT_UPDATE };
@@ -245,12 +246,12 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('markdown', reqestXssParams);
+      await configManager.updateConfigs(reqestXssParams);
       const xssParams = {
       const xssParams = {
-        isEnabledXss: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
-        xssOption: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'),
-        tagWhitelist: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'),
-        attrWhitelist: await crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes'),
+        isEnabledXss: await crowi.configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+        xssOption: await crowi.configManager.getConfig('markdown:rehypeSanitize:option'),
+        tagWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
+        attrWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:attributes'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_XSS_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_XSS_UPDATE };

+ 7 - 6
apps/app/src/server/routes/apiv3/notification-setting.js

@@ -2,6 +2,7 @@ import { ErrorV3 } from '@growi/core/dist/models';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import { GlobalNotificationSettingType } from '~/server/models/GlobalNotificationSetting';
 import { GlobalNotificationSettingType } from '~/server/models/GlobalNotificationSetting';
+import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';
 
 
@@ -121,11 +122,11 @@ module.exports = (crowi) => {
       // status of slack intagration
       // status of slack intagration
       isSlackbotConfigured: crowi.slackIntegrationService.isSlackbotConfigured,
       isSlackbotConfigured: crowi.slackIntegrationService.isSlackbotConfigured,
       isSlackLegacyConfigured: crowi.slackIntegrationService.isSlackLegacyConfigured,
       isSlackLegacyConfigured: crowi.slackIntegrationService.isSlackLegacyConfigured,
-      currentBotType: await crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
+      currentBotType: await crowi.configManager.getConfig('slackbot:currentBotType'),
 
 
       userNotifications: await UpdatePost.findAll(),
       userNotifications: await UpdatePost.findAll(),
-      isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification', 'notification:owner-page:isEnabled'),
-      isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification', 'notification:group-page:isEnabled'),
+      isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification:owner-page:isEnabled'),
+      isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification:group-page:isEnabled'),
       globalNotifications: await GlobalNotificationSetting.findAll(),
       globalNotifications: await GlobalNotificationSetting.findAll(),
     };
     };
     return res.apiv3({ notificationParams });
     return res.apiv3({ notificationParams });
@@ -424,10 +425,10 @@ module.exports = (crowi) => {
     requestParams = removeNullPropertyFromObject(requestParams);
     requestParams = removeNullPropertyFromObject(requestParams);
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('notification', requestParams);
+      await configManager.updateConfigs(requestParams);
       const responseParams = {
       const responseParams = {
-        isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification', 'notification:owner-page:isEnabled'),
-        isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification', 'notification:group-page:isEnabled'),
+        isNotificationForOwnerPageEnabled: await crowi.configManager.getConfig('notification:owner-page:isEnabled'),
+        isNotificationForGroupPageEnabled: await crowi.configManager.getConfig('notification:group-page:isEnabled'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE };

+ 2 - 2
apps/app/src/server/routes/apiv3/page-listing.ts

@@ -105,8 +105,8 @@ const routerFactory = (crowi: Crowi): Router => {
 
 
     const pageService = crowi.pageService;
     const pageService = crowi.pageService;
 
 
-    const hideRestrictedByOwner = await configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner');
-    const hideRestrictedByGroup = await configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByGroup');
+    const hideRestrictedByOwner = await configManager.getConfig('security:list-policy:hideRestrictedByOwner');
+    const hideRestrictedByGroup = await configManager.getConfig('security:list-policy:hideRestrictedByGroup');
 
 
     try {
     try {
       const pages = await pageService.findChildrenByParentPathOrIdAndViewer(
       const pages = await pageService.findChildrenByParentPathOrIdAndViewer(

+ 1 - 1
apps/app/src/server/routes/apiv3/page/create-page.ts

@@ -135,7 +135,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
     let tags: string[] = _tags ?? [];
     let tags: string[] = _tags ?? [];
 
 
     if (_body == null) {
     if (_body == null) {
-      const isEnabledAttachTitleHeader = await configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader');
+      const isEnabledAttachTitleHeader = await configManager.getConfig('customize:isEnabledAttachTitleHeader');
       if (isEnabledAttachTitleHeader) {
       if (isEnabledAttachTitleHeader) {
         body += `${attachTitleHeader(path)}\n`;
         body += `${attachTitleHeader(path)}\n`;
       }
       }

+ 1 - 1
apps/app/src/server/routes/apiv3/page/index.ts

@@ -884,7 +884,7 @@ module.exports = (crowi) => {
       const { pageId } = req.params;
       const { pageId } = req.params;
       const { expandContentWidth } = req.body;
       const { expandContentWidth } = req.body;
 
 
-      const isContainerFluidBySystem = configManager.getConfig('crowi', 'customize:isContainerFluid');
+      const isContainerFluidBySystem = configManager.getConfig('customize:isContainerFluid');
 
 
       try {
       try {
         const updateQuery = expandContentWidth === isContainerFluidBySystem
         const updateQuery = expandContentWidth === isContainerFluidBySystem

+ 4 - 4
apps/app/src/server/routes/apiv3/pages/index.js

@@ -139,8 +139,8 @@ module.exports = (crowi) => {
     const offset = parseInt(req.query.offset) || 0;
     const offset = parseInt(req.query.offset) || 0;
     const includeWipPage = req.query.includeWipPage === 'true'; // Need validation using express-validator
     const includeWipPage = req.query.includeWipPage === 'true'; // Need validation using express-validator
 
 
-    const hideRestrictedByOwner = await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner');
-    const hideRestrictedByGroup = await crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByGroup');
+    const hideRestrictedByOwner = await crowi.configManager.getConfig('security:list-policy:hideRestrictedByOwner');
+    const hideRestrictedByGroup = await crowi.configManager.getConfig('security:list-policy:hideRestrictedByGroup');
 
 
     /**
     /**
     * @type {import('~/server/models/page').FindRecentUpdatedPagesOption}
     * @type {import('~/server/models/page').FindRecentUpdatedPagesOption}
@@ -426,7 +426,7 @@ module.exports = (crowi) => {
   router.get('/list', accessTokenParser, loginRequired, validator.displayList, apiV3FormValidator, async(req, res) => {
   router.get('/list', accessTokenParser, loginRequired, validator.displayList, apiV3FormValidator, async(req, res) => {
 
 
     const { path } = req.query;
     const { path } = req.query;
-    const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS') || 10;
+    const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('customize:showPageLimitationS') || 10;
     const page = req.query.page || 1;
     const page = req.query.page || 1;
     const offset = (page - 1) * limit;
     const offset = (page - 1) * limit;
 
 
@@ -719,7 +719,7 @@ module.exports = (crowi) => {
 
 
   router.get('/v5-migration-status', accessTokenParser, loginRequired, async(req, res) => {
   router.get('/v5-migration-status', accessTokenParser, loginRequired, async(req, res) => {
     try {
     try {
-      const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
+      const isV5Compatible = crowi.configManager.getConfig('app:isV5Compatible');
       const migratablePagesCount = req.user != null ? await crowi.pageService.countPagesCanNormalizeParentByUser(req.user) : null; // null check since not using loginRequiredStrictly
       const migratablePagesCount = req.user != null ? await crowi.pageService.countPagesCanNormalizeParentByUser(req.user) : null; // null check since not using loginRequiredStrictly
       return res.apiv3({ isV5Compatible, migratablePagesCount });
       return res.apiv3({ isV5Compatible, migratablePagesCount });
     }
     }

+ 2 - 2
apps/app/src/server/routes/apiv3/personal-setting.js

@@ -74,7 +74,7 @@ module.exports = (crowi) => {
 
 
   const activityEvent = crowi.event('activity');
   const activityEvent = crowi.event('activity');
 
 
-  const minPasswordLength = crowi.configManager.getConfig('crowi', 'app:minPasswordLength');
+  const minPasswordLength = crowi.configManager.getConfig('app:minPasswordLength');
 
 
   const validator = {
   const validator = {
     personal: [
     personal: [
@@ -189,7 +189,7 @@ module.exports = (crowi) => {
     try {
     try {
       const user = await User.findUserByUsername(username);
       const user = await User.findUserByUsername(username);
       const isPasswordSet = user.isPasswordSet();
       const isPasswordSet = user.isPasswordSet();
-      const minPasswordLength = crowi.configManager.getConfig('crowi', 'app:minPasswordLength');
+      const minPasswordLength = crowi.configManager.getConfig('app:minPasswordLength');
       return res.apiv3({ isPasswordSet, minPasswordLength });
       return res.apiv3({ isPasswordSet, minPasswordLength });
     }
     }
     catch (err) {
     catch (err) {

+ 1 - 1
apps/app/src/server/routes/apiv3/revisions.js

@@ -87,7 +87,7 @@ module.exports = (crowi) => {
 
 
   router.get('/list', certifySharedPage, accessTokenParser, loginRequired, validator.retrieveRevisions, apiV3FormValidator, async(req, res) => {
   router.get('/list', certifySharedPage, accessTokenParser, loginRequired, validator.retrieveRevisions, apiV3FormValidator, async(req, res) => {
     const pageId = req.query.pageId;
     const pageId = req.query.pageId;
-    const limit = req.query.limit || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationS') || 10;
+    const limit = req.query.limit || await crowi.configManager.getConfig('customize:showPageLimitationS') || 10;
     const { isSharedPage } = req;
     const { isSharedPage } = req;
     const offset = req.query.offset || 0;
     const offset = req.query.offset || 0;
 
 

+ 152 - 151
apps/app/src/server/routes/apiv3/security-settings/index.js

@@ -1,3 +1,4 @@
+import { ConfigSource } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import xss from 'xss';
 import xss from 'xss';
 
 
@@ -319,7 +320,7 @@ module.exports = (crowi) => {
     const { passportService } = crowi;
     const { passportService } = crowi;
 
 
     // update config without publishing S2sMessage
     // update config without publishing S2sMessage
-    await configManager.updateConfigsInTheSameNamespace('crowi', params, true);
+    await configManager.updateConfigs(params, { skipPubsub: true });
 
 
     await passportService.setupStrategyById(authId);
     await passportService.setupStrategyById(authId);
     passportService.publishUpdatedMessage(authId);
     passportService.publishUpdatedMessage(authId);
@@ -348,106 +349,106 @@ module.exports = (crowi) => {
     const securityParams = {
     const securityParams = {
       generalSetting: {
       generalSetting: {
         restrictGuestMode: crowi.aclService.getGuestModeValue(),
         restrictGuestMode: crowi.aclService.getGuestModeValue(),
-        pageDeletionAuthority: await configManager.getConfig('crowi', 'security:pageDeletionAuthority'),
-        pageCompleteDeletionAuthority: await configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority'),
-        pageRecursiveDeletionAuthority: await configManager.getConfig('crowi', 'security:pageRecursiveDeletionAuthority'),
-        pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('crowi', 'security:pageRecursiveCompleteDeletionAuthority'),
+        pageDeletionAuthority: await configManager.getConfig('security:pageDeletionAuthority'),
+        pageCompleteDeletionAuthority: await configManager.getConfig('security:pageCompleteDeletionAuthority'),
+        pageRecursiveDeletionAuthority: await configManager.getConfig('security:pageRecursiveDeletionAuthority'),
+        pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('security:pageRecursiveCompleteDeletionAuthority'),
         isAllGroupMembershipRequiredForPageCompleteDeletion:
         isAllGroupMembershipRequiredForPageCompleteDeletion:
-        await configManager.getConfig('crowi', 'security:isAllGroupMembershipRequiredForPageCompleteDeletion'),
-        hideRestrictedByOwner: await configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),
-        hideRestrictedByGroup: await configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByGroup'),
-        isUsersHomepageDeletionEnabled: await configManager.getConfig('crowi', 'security:user-homepage-deletion:isEnabled'),
+        await configManager.getConfig('security:isAllGroupMembershipRequiredForPageCompleteDeletion'),
+        hideRestrictedByOwner: await configManager.getConfig('security:list-policy:hideRestrictedByOwner'),
+        hideRestrictedByGroup: await configManager.getConfig('security:list-policy:hideRestrictedByGroup'),
+        isUsersHomepageDeletionEnabled: await configManager.getConfig('security:user-homepage-deletion:isEnabled'),
         isForceDeleteUserHomepageOnUserDeletion:
         isForceDeleteUserHomepageOnUserDeletion:
-        await configManager.getConfig('crowi', 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
-        isRomUserAllowedToComment: await configManager.getConfig('crowi', 'security:isRomUserAllowedToComment'),
-        wikiMode: await configManager.getConfig('crowi', 'security:wikiMode'),
-        sessionMaxAge: await configManager.getConfig('crowi', 'security:sessionMaxAge'),
+        await configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
+        isRomUserAllowedToComment: await configManager.getConfig('security:isRomUserAllowedToComment'),
+        wikiMode: await configManager.getConfig('security:wikiMode'),
+        sessionMaxAge: await configManager.getConfig('security:sessionMaxAge'),
       },
       },
       shareLinkSetting: {
       shareLinkSetting: {
-        disableLinkSharing: await configManager.getConfig('crowi', 'security:disableLinkSharing'),
+        disableLinkSharing: await configManager.getConfig('security:disableLinkSharing'),
       },
       },
       localSetting: {
       localSetting: {
-        useOnlyEnvVarsForSomeOptions: await configManager.getConfig('crowi', 'security:passport-local:useOnlyEnvVarsForSomeOptions'),
-        registrationMode: await configManager.getConfig('crowi', 'security:registrationMode'),
-        registrationWhitelist: await configManager.getConfig('crowi', 'security:registrationWhitelist'),
-        isPasswordResetEnabled: await configManager.getConfig('crowi', 'security:passport-local:isPasswordResetEnabled'),
-        isEmailAuthenticationEnabled: await configManager.getConfig('crowi', 'security:passport-local:isEmailAuthenticationEnabled'),
+        useOnlyEnvVarsForSomeOptions: await configManager.getConfig('env:useOnlyEnvVars:security:passport-local'),
+        registrationMode: await configManager.getConfig('security:registrationMode'),
+        registrationWhitelist: await configManager.getConfig('security:registrationWhitelist'),
+        isPasswordResetEnabled: await configManager.getConfig('security:passport-local:isPasswordResetEnabled'),
+        isEmailAuthenticationEnabled: await configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled'),
       },
       },
       generalAuth: {
       generalAuth: {
-        isLocalEnabled: await configManager.getConfig('crowi', 'security:passport-local:isEnabled'),
-        isLdapEnabled: await configManager.getConfig('crowi', 'security:passport-ldap:isEnabled'),
-        isSamlEnabled: await configManager.getConfig('crowi', 'security:passport-saml:isEnabled'),
-        isOidcEnabled: await configManager.getConfig('crowi', 'security:passport-oidc:isEnabled'),
-        isGoogleEnabled: await configManager.getConfig('crowi', 'security:passport-google:isEnabled'),
-        isGitHubEnabled: await configManager.getConfig('crowi', 'security:passport-github:isEnabled'),
+        isLocalEnabled: await configManager.getConfig('security:passport-local:isEnabled'),
+        isLdapEnabled: await configManager.getConfig('security:passport-ldap:isEnabled'),
+        isSamlEnabled: await configManager.getConfig('security:passport-saml:isEnabled'),
+        isOidcEnabled: await configManager.getConfig('security:passport-oidc:isEnabled'),
+        isGoogleEnabled: await configManager.getConfig('security:passport-google:isEnabled'),
+        isGitHubEnabled: await configManager.getConfig('security:passport-github:isEnabled'),
       },
       },
       ldapAuth: {
       ldapAuth: {
-        serverUrl: await configManager.getConfig('crowi', 'security:passport-ldap:serverUrl'),
-        isUserBind: await configManager.getConfig('crowi', 'security:passport-ldap:isUserBind'),
-        ldapBindDN: await configManager.getConfig('crowi', 'security:passport-ldap:bindDN'),
-        ldapBindDNPassword: await configManager.getConfig('crowi', 'security:passport-ldap:bindDNPassword'),
-        ldapSearchFilter: await configManager.getConfig('crowi', 'security:passport-ldap:searchFilter'),
-        ldapAttrMapUsername: await configManager.getConfig('crowi', 'security:passport-ldap:attrMapUsername'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'),
-        ldapAttrMapMail: await configManager.getConfig('crowi', 'security:passport-ldap:attrMapMail'),
-        ldapAttrMapName: await configManager.getConfig('crowi', 'security:passport-ldap:attrMapName'),
-        ldapGroupSearchBase: await configManager.getConfig('crowi', 'security:passport-ldap:groupSearchBase'),
-        ldapGroupSearchFilter: await configManager.getConfig('crowi', 'security:passport-ldap:groupSearchFilter'),
-        ldapGroupDnProperty: await configManager.getConfig('crowi', 'security:passport-ldap:groupDnProperty'),
+        serverUrl: await configManager.getConfig('security:passport-ldap:serverUrl'),
+        isUserBind: await configManager.getConfig('security:passport-ldap:isUserBind'),
+        ldapBindDN: await configManager.getConfig('security:passport-ldap:bindDN'),
+        ldapBindDNPassword: await configManager.getConfig('security:passport-ldap:bindDNPassword'),
+        ldapSearchFilter: await configManager.getConfig('security:passport-ldap:searchFilter'),
+        ldapAttrMapUsername: await configManager.getConfig('security:passport-ldap:attrMapUsername'),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'),
+        ldapAttrMapMail: await configManager.getConfig('security:passport-ldap:attrMapMail'),
+        ldapAttrMapName: await configManager.getConfig('security:passport-ldap:attrMapName'),
+        ldapGroupSearchBase: await configManager.getConfig('security:passport-ldap:groupSearchBase'),
+        ldapGroupSearchFilter: await configManager.getConfig('security:passport-ldap:groupSearchFilter'),
+        ldapGroupDnProperty: await configManager.getConfig('security:passport-ldap:groupDnProperty'),
       },
       },
       samlAuth: {
       samlAuth: {
         missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
         missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
-        useOnlyEnvVarsForSomeOptions: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:useOnlyEnvVarsForSomeOptions'),
-        samlEntryPoint: await configManager.getConfigFromDB('crowi', 'security:passport-saml:entryPoint'),
-        samlEnvVarEntryPoint: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:entryPoint'),
-        samlIssuer: await configManager.getConfigFromDB('crowi', 'security:passport-saml:issuer'),
-        samlEnvVarIssuer: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:issuer'),
-        samlCert: await configManager.getConfigFromDB('crowi', 'security:passport-saml:cert'),
-        samlEnvVarCert: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:cert'),
-        samlAttrMapId: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapId'),
-        samlEnvVarAttrMapId: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapId'),
-        samlAttrMapUsername: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapUsername'),
-        samlEnvVarAttrMapUsername: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapUsername'),
-        samlAttrMapMail: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapMail'),
-        samlEnvVarAttrMapMail: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapMail'),
-        samlAttrMapFirstName: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapFirstName'),
-        samlEnvVarAttrMapFirstName: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapFirstName'),
-        samlAttrMapLastName: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapLastName'),
-        samlEnvVarAttrMapLastName: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapLastName'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
-        samlABLCRule: await configManager.getConfigFromDB('crowi', 'security:passport-saml:ABLCRule'),
-        samlEnvVarABLCRule: await configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:ABLCRule'),
+        useOnlyEnvVarsForSomeOptions: await configManager.getConfig('env:useOnlyEnvVars:security:passport-saml', ConfigSource.env),
+        samlEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.db),
+        samlEnvVarEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.env),
+        samlIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.db),
+        samlEnvVarIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.env),
+        samlCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.db),
+        samlEnvVarCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.env),
+        samlAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.db),
+        samlEnvVarAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.env),
+        samlAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.db),
+        samlEnvVarAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.env),
+        samlAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.db),
+        samlEnvVarAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.env),
+        samlAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.db),
+        samlEnvVarAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.env),
+        samlAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.db),
+        samlEnvVarAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.env),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
+        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
+        samlABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule', ConfigSource.db),
+        samlEnvVarABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule', ConfigSource.env),
       },
       },
       oidcAuth: {
       oidcAuth: {
-        oidcProviderName: await configManager.getConfig('crowi', 'security:passport-oidc:providerName'),
-        oidcIssuerHost: await configManager.getConfig('crowi', 'security:passport-oidc:issuerHost'),
-        oidcAuthorizationEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:authorizationEndpoint'),
-        oidcTokenEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:tokenEndpoint'),
-        oidcRevocationEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:revocationEndpoint'),
-        oidcIntrospectionEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:introspectionEndpoint'),
-        oidcUserInfoEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:userInfoEndpoint'),
-        oidcEndSessionEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:endSessionEndpoint'),
-        oidcRegistrationEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:registrationEndpoint'),
-        oidcJWKSUri: await configManager.getConfig('crowi', 'security:passport-oidc:jwksUri'),
-        oidcClientId: await configManager.getConfig('crowi', 'security:passport-oidc:clientId'),
-        oidcClientSecret: await configManager.getConfig('crowi', 'security:passport-oidc:clientSecret'),
-        oidcAttrMapId: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapId'),
-        oidcAttrMapUserName: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapUserName'),
-        oidcAttrMapName: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapName'),
-        oidcAttrMapEmail: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapMail'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-oidc:isSameEmailTreatedAsIdenticalUser'),
+        oidcProviderName: await configManager.getConfig('security:passport-oidc:providerName'),
+        oidcIssuerHost: await configManager.getConfig('security:passport-oidc:issuerHost'),
+        oidcAuthorizationEndpoint: await configManager.getConfig('security:passport-oidc:authorizationEndpoint'),
+        oidcTokenEndpoint: await configManager.getConfig('security:passport-oidc:tokenEndpoint'),
+        oidcRevocationEndpoint: await configManager.getConfig('security:passport-oidc:revocationEndpoint'),
+        oidcIntrospectionEndpoint: await configManager.getConfig('security:passport-oidc:introspectionEndpoint'),
+        oidcUserInfoEndpoint: await configManager.getConfig('security:passport-oidc:userInfoEndpoint'),
+        oidcEndSessionEndpoint: await configManager.getConfig('security:passport-oidc:endSessionEndpoint'),
+        oidcRegistrationEndpoint: await configManager.getConfig('security:passport-oidc:registrationEndpoint'),
+        oidcJWKSUri: await configManager.getConfig('security:passport-oidc:jwksUri'),
+        oidcClientId: await configManager.getConfig('security:passport-oidc:clientId'),
+        oidcClientSecret: await configManager.getConfig('security:passport-oidc:clientSecret'),
+        oidcAttrMapId: await configManager.getConfig('security:passport-oidc:attrMapId'),
+        oidcAttrMapUserName: await configManager.getConfig('security:passport-oidc:attrMapUserName'),
+        oidcAttrMapName: await configManager.getConfig('security:passport-oidc:attrMapName'),
+        oidcAttrMapEmail: await configManager.getConfig('security:passport-oidc:attrMapMail'),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'),
+        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameEmailTreatedAsIdenticalUser'),
       },
       },
       googleOAuth: {
       googleOAuth: {
-        googleClientId: await configManager.getConfig('crowi', 'security:passport-google:clientId'),
-        googleClientSecret: await configManager.getConfig('crowi', 'security:passport-google:clientSecret'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-google:isSameEmailTreatedAsIdenticalUser'),
+        googleClientId: await configManager.getConfig('security:passport-google:clientId'),
+        googleClientSecret: await configManager.getConfig('security:passport-google:clientSecret'),
+        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-google:isSameEmailTreatedAsIdenticalUser'),
       },
       },
       githubOAuth: {
       githubOAuth: {
-        githubClientId: await configManager.getConfig('crowi', 'security:passport-github:clientId'),
-        githubClientSecret: await configManager.getConfig('crowi', 'security:passport-github:clientSecret'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-github:isSameUsernameTreatedAsIdenticalUser'),
+        githubClientId: await configManager.getConfig('security:passport-github:clientId'),
+        githubClientSecret: await configManager.getConfig('security:passport-github:clientSecret'),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-github:isSameUsernameTreatedAsIdenticalUser'),
       },
       },
     };
     };
     return res.apiv3({ securityParams });
     return res.apiv3({ securityParams });
@@ -510,7 +511,7 @@ module.exports = (crowi) => {
       await updateAndReloadStrategySettings(authId, enableParams);
       await updateAndReloadStrategySettings(authId, enableParams);
 
 
       const responseParams = {
       const responseParams = {
-        [`security:passport-${authId}:isEnabled`]: await configManager.getConfig('crowi', `security:passport-${authId}:isEnabled`),
+        [`security:passport-${authId}:isEnabled`]: await configManager.getConfig(`security:passport-${authId}:isEnabled`),
       };
       };
       switch (authId) {
       switch (authId) {
         case 'local':
         case 'local':
@@ -646,28 +647,28 @@ module.exports = (crowi) => {
       return res.apiv3Err(new ErrorV3('Delete config values are not correct.', 'delete_config_not_normalized'));
       return res.apiv3Err(new ErrorV3('Delete config values are not correct.', 'delete_config_not_normalized'));
     }
     }
 
 
-    const wikiMode = await configManager.getConfig('crowi', 'security:wikiMode');
+    const wikiMode = await configManager.getConfig('security:wikiMode');
     if (wikiMode === 'private' || wikiMode === 'public') {
     if (wikiMode === 'private' || wikiMode === 'public') {
       logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
       logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
       delete updateData['security:restrictGuestMode'];
       delete updateData['security:restrictGuestMode'];
     }
     }
     try {
     try {
-      await configManager.updateConfigsInTheSameNamespace('crowi', updateData);
+      await configManager.updateConfigs(updateData);
       const securitySettingParams = {
       const securitySettingParams = {
-        sessionMaxAge: await configManager.getConfig('crowi', 'security:sessionMaxAge'),
-        restrictGuestMode: await configManager.getConfig('crowi', 'security:restrictGuestMode'),
-        pageDeletionAuthority: await configManager.getConfig('crowi', 'security:pageDeletionAuthority'),
-        pageCompleteDeletionAuthority: await configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority'),
-        pageRecursiveDeletionAuthority: await configManager.getConfig('crowi', 'security:pageRecursiveDeletionAuthority'),
-        pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('crowi', 'security:pageRecursiveCompleteDeletionAuthority'),
+        sessionMaxAge: await configManager.getConfig('security:sessionMaxAge'),
+        restrictGuestMode: await configManager.getConfig('security:restrictGuestMode'),
+        pageDeletionAuthority: await configManager.getConfig('security:pageDeletionAuthority'),
+        pageCompleteDeletionAuthority: await configManager.getConfig('security:pageCompleteDeletionAuthority'),
+        pageRecursiveDeletionAuthority: await configManager.getConfig('security:pageRecursiveDeletionAuthority'),
+        pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('security:pageRecursiveCompleteDeletionAuthority'),
         isAllGroupMembershipRequiredForPageCompleteDeletion:
         isAllGroupMembershipRequiredForPageCompleteDeletion:
-        await configManager.getConfig('crowi', 'security:isAllGroupMembershipRequiredForPageCompleteDeletion'),
-        hideRestrictedByOwner: await configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner'),
-        hideRestrictedByGroup: await configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByGroup'),
-        isUsersHomepageDeletionEnabled: await configManager.getConfig('crowi', 'security:user-homepage-deletion:isEnabled'),
+        await configManager.getConfig('security:isAllGroupMembershipRequiredForPageCompleteDeletion'),
+        hideRestrictedByOwner: await configManager.getConfig('security:list-policy:hideRestrictedByOwner'),
+        hideRestrictedByGroup: await configManager.getConfig('security:list-policy:hideRestrictedByGroup'),
+        isUsersHomepageDeletionEnabled: await configManager.getConfig('security:user-homepage-deletion:isEnabled'),
         isForceDeleteUserHomepageOnUserDeletion:
         isForceDeleteUserHomepageOnUserDeletion:
-        await configManager.getConfig('crowi', 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
-        isRomUserAllowedToComment: await configManager.getConfig('crowi', 'security:isRomUserAllowedToComment'),
+        await configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'),
+        isRomUserAllowedToComment: await configManager.getConfig('security:isRomUserAllowedToComment'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_SECURITY_SETTINGS_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_SECURITY_SETTINGS_UPDATE };
@@ -708,9 +709,9 @@ module.exports = (crowi) => {
       'security:disableLinkSharing': req.body.disableLinkSharing,
       'security:disableLinkSharing': req.body.disableLinkSharing,
     };
     };
     try {
     try {
-      await configManager.updateConfigsInTheSameNamespace('crowi', updateData);
+      await configManager.updateConfigs(updateData);
       const securitySettingParams = {
       const securitySettingParams = {
-        disableLinkSharing: configManager.getConfig('crowi', 'security:disableLinkSharing'),
+        disableLinkSharing: configManager.getConfig('security:disableLinkSharing'),
       };
       };
       // eslint-disable-next-line max-len
       // eslint-disable-next-line max-len
       const parameters = { action: updateData['security:disableLinkSharing'] ? SupportedAction.ACTION_ADMIN_REJECT_SHARE_LINK : SupportedAction.ACTION_ADMIN_PERMIT_SHARE_LINK };
       const parameters = { action: updateData['security:disableLinkSharing'] ? SupportedAction.ACTION_ADMIN_REJECT_SHARE_LINK : SupportedAction.ACTION_ADMIN_PERMIT_SHARE_LINK };
@@ -829,10 +830,10 @@ module.exports = (crowi) => {
       await updateAndReloadStrategySettings('local', requestParams);
       await updateAndReloadStrategySettings('local', requestParams);
 
 
       const localSettingParams = {
       const localSettingParams = {
-        registrationMode: await configManager.getConfig('crowi', 'security:registrationMode'),
-        registrationWhitelist: await configManager.getConfig('crowi', 'security:registrationWhitelist'),
-        isPasswordResetEnabled: await configManager.getConfig('crowi', 'security:passport-local:isPasswordResetEnabled'),
-        isEmailAuthenticationEnabled: await configManager.getConfig('crowi', 'security:passport-local:isEmailAuthenticationEnabled'),
+        registrationMode: await configManager.getConfig('security:registrationMode'),
+        registrationWhitelist: await configManager.getConfig('security:registrationWhitelist'),
+        isPasswordResetEnabled: await configManager.getConfig('security:passport-local:isPasswordResetEnabled'),
+        isEmailAuthenticationEnabled: await configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -886,18 +887,18 @@ module.exports = (crowi) => {
       await updateAndReloadStrategySettings('ldap', requestParams);
       await updateAndReloadStrategySettings('ldap', requestParams);
 
 
       const securitySettingParams = {
       const securitySettingParams = {
-        serverUrl: await configManager.getConfig('crowi', 'security:passport-ldap:serverUrl'),
-        isUserBind: await configManager.getConfig('crowi', 'security:passport-ldap:isUserBind'),
-        ldapBindDN: await configManager.getConfig('crowi', 'security:passport-ldap:bindDN'),
-        ldapBindDNPassword: await configManager.getConfig('crowi', 'security:passport-ldap:bindDNPassword'),
-        ldapSearchFilter: await configManager.getConfig('crowi', 'security:passport-ldap:searchFilter'),
-        ldapAttrMapUsername: await configManager.getConfig('crowi', 'security:passport-ldap:attrMapUsername'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'),
-        ldapAttrMapMail: await configManager.getConfig('crowi', 'security:passport-ldap:attrMapMail'),
-        ldapAttrMapName: await configManager.getConfig('crowi', 'security:passport-ldap:attrMapName'),
-        ldapGroupSearchBase: await configManager.getConfig('crowi', 'security:passport-ldap:groupSearchBase'),
-        ldapGroupSearchFilter: await configManager.getConfig('crowi', 'security:passport-ldap:groupSearchFilter'),
-        ldapGroupDnProperty: await configManager.getConfig('crowi', 'security:passport-ldap:groupDnProperty'),
+        serverUrl: await configManager.getConfig('security:passport-ldap:serverUrl'),
+        isUserBind: await configManager.getConfig('security:passport-ldap:isUserBind'),
+        ldapBindDN: await configManager.getConfig('security:passport-ldap:bindDN'),
+        ldapBindDNPassword: await configManager.getConfig('security:passport-ldap:bindDNPassword'),
+        ldapSearchFilter: await configManager.getConfig('security:passport-ldap:searchFilter'),
+        ldapAttrMapUsername: await configManager.getConfig('security:passport-ldap:attrMapUsername'),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'),
+        ldapAttrMapMail: await configManager.getConfig('security:passport-ldap:attrMapMail'),
+        ldapAttrMapName: await configManager.getConfig('security:passport-ldap:attrMapName'),
+        ldapGroupSearchBase: await configManager.getConfig('security:passport-ldap:groupSearchBase'),
+        ldapGroupSearchFilter: await configManager.getConfig('security:passport-ldap:groupSearchFilter'),
+        ldapGroupDnProperty: await configManager.getConfig('security:passport-ldap:groupDnProperty'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_LDAP_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_LDAP_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -941,7 +942,7 @@ module.exports = (crowi) => {
     for (const configKey of crowi.passportService.mandatoryConfigKeysForSaml) {
     for (const configKey of crowi.passportService.mandatoryConfigKeysForSaml) {
       const key = configKey.replace('security:passport-saml:', '');
       const key = configKey.replace('security:passport-saml:', '');
       const formValue = req.body[key];
       const formValue = req.body[key];
-      if (configManager.getConfigFromEnvVars('crowi', configKey) === null && formValue == null) {
+      if (configManager.getConfig(configKey, ConfigSource.env) == null && formValue == null) {
         const formItemName = t(`security_settings.form_item_name.${key}`);
         const formItemName = t(`security_settings.form_item_name.${key}`);
         invalidValues.push(t('input_validation.message.required', { param: formItemName }));
         invalidValues.push(t('input_validation.message.required', { param: formItemName }));
       }
       }
@@ -981,17 +982,17 @@ module.exports = (crowi) => {
 
 
       const securitySettingParams = {
       const securitySettingParams = {
         missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
         missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
-        samlEntryPoint: await configManager.getConfigFromDB('crowi', 'security:passport-saml:entryPoint'),
-        samlIssuer: await configManager.getConfigFromDB('crowi', 'security:passport-saml:issuer'),
-        samlCert: await configManager.getConfigFromDB('crowi', 'security:passport-saml:cert'),
-        samlAttrMapId: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapId'),
-        samlAttrMapUsername: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapUsername'),
-        samlAttrMapMail: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapMail'),
-        samlAttrMapFirstName: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapFirstName'),
-        samlAttrMapLastName: await configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapLastName'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
-        samlABLCRule: await configManager.getConfig('crowi', 'security:passport-saml:ABLCRule'),
+        samlEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.db),
+        samlIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.db),
+        samlCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.db),
+        samlAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.db),
+        samlAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.db),
+        samlAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.db),
+        samlAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.db),
+        samlAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.db),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
+        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
+        samlABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -1051,24 +1052,24 @@ module.exports = (crowi) => {
       await updateAndReloadStrategySettings('oidc', requestParams);
       await updateAndReloadStrategySettings('oidc', requestParams);
 
 
       const securitySettingParams = {
       const securitySettingParams = {
-        oidcProviderName: await configManager.getConfig('crowi', 'security:passport-oidc:providerName'),
-        oidcIssuerHost: await configManager.getConfig('crowi', 'security:passport-oidc:issuerHost'),
-        oidcAuthorizationEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:authorizationEndpoint'),
-        oidcTokenEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:tokenEndpoint'),
-        oidcRevocationEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:revocationEndpoint'),
-        oidcIntrospectionEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:introspectionEndpoint'),
-        oidcUserInfoEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:userInfoEndpoint'),
-        oidcEndSessionEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:endSessionEndpoint'),
-        oidcRegistrationEndpoint: await configManager.getConfig('crowi', 'security:passport-oidc:registrationEndpoint'),
-        oidcJWKSUri: await configManager.getConfig('crowi', 'security:passport-oidc:jwksUri'),
-        oidcClientId: await configManager.getConfig('crowi', 'security:passport-oidc:clientId'),
-        oidcClientSecret: await configManager.getConfig('crowi', 'security:passport-oidc:clientSecret'),
-        oidcAttrMapId: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapId'),
-        oidcAttrMapUserName: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapUserName'),
-        oidcAttrMapName: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapName'),
-        oidcAttrMapEmail: await configManager.getConfig('crowi', 'security:passport-oidc:attrMapMail'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-oidc:isSameEmailTreatedAsIdenticalUser'),
+        oidcProviderName: await configManager.getConfig('security:passport-oidc:providerName'),
+        oidcIssuerHost: await configManager.getConfig('security:passport-oidc:issuerHost'),
+        oidcAuthorizationEndpoint: await configManager.getConfig('security:passport-oidc:authorizationEndpoint'),
+        oidcTokenEndpoint: await configManager.getConfig('security:passport-oidc:tokenEndpoint'),
+        oidcRevocationEndpoint: await configManager.getConfig('security:passport-oidc:revocationEndpoint'),
+        oidcIntrospectionEndpoint: await configManager.getConfig('security:passport-oidc:introspectionEndpoint'),
+        oidcUserInfoEndpoint: await configManager.getConfig('security:passport-oidc:userInfoEndpoint'),
+        oidcEndSessionEndpoint: await configManager.getConfig('security:passport-oidc:endSessionEndpoint'),
+        oidcRegistrationEndpoint: await configManager.getConfig('security:passport-oidc:registrationEndpoint'),
+        oidcJWKSUri: await configManager.getConfig('security:passport-oidc:jwksUri'),
+        oidcClientId: await configManager.getConfig('security:passport-oidc:clientId'),
+        oidcClientSecret: await configManager.getConfig('security:passport-oidc:clientSecret'),
+        oidcAttrMapId: await configManager.getConfig('security:passport-oidc:attrMapId'),
+        oidcAttrMapUserName: await configManager.getConfig('security:passport-oidc:attrMapUserName'),
+        oidcAttrMapName: await configManager.getConfig('security:passport-oidc:attrMapName'),
+        oidcAttrMapEmail: await configManager.getConfig('security:passport-oidc:attrMapMail'),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'),
+        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameEmailTreatedAsIdenticalUser'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_OIDC_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_OIDC_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -1114,9 +1115,9 @@ module.exports = (crowi) => {
       await updateAndReloadStrategySettings('google', requestParams);
       await updateAndReloadStrategySettings('google', requestParams);
 
 
       const securitySettingParams = {
       const securitySettingParams = {
-        googleClientId: await configManager.getConfig('crowi', 'security:passport-google:clientId'),
-        googleClientSecret: await configManager.getConfig('crowi', 'security:passport-google:clientSecret'),
-        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-google:isSameEmailTreatedAsIdenticalUser'),
+        googleClientId: await configManager.getConfig('security:passport-google:clientId'),
+        googleClientSecret: await configManager.getConfig('security:passport-google:clientSecret'),
+        isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-google:isSameEmailTreatedAsIdenticalUser'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -1161,9 +1162,9 @@ module.exports = (crowi) => {
       await updateAndReloadStrategySettings('github', requestParams);
       await updateAndReloadStrategySettings('github', requestParams);
 
 
       const securitySettingParams = {
       const securitySettingParams = {
-        githubClientId: await configManager.getConfig('crowi', 'security:passport-github:clientId'),
-        githubClientSecret: await configManager.getConfig('crowi', 'security:passport-github:clientSecret'),
-        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('crowi', 'security:passport-github:isSameUsernameTreatedAsIdenticalUser'),
+        githubClientId: await configManager.getConfig('security:passport-github:clientId'),
+        githubClientSecret: await configManager.getConfig('security:passport-github:clientSecret'),
+        isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-github:isSameUsernameTreatedAsIdenticalUser'),
       };
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GITHUB_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GITHUB_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);

+ 1 - 1
apps/app/src/server/routes/apiv3/share-links.js

@@ -33,7 +33,7 @@ module.exports = (crowi) => {
    * middleware to limit link sharing
    * middleware to limit link sharing
    */
    */
   const linkSharingRequired = (req, res, next) => {
   const linkSharingRequired = (req, res, next) => {
-    const isLinkSharingDisabled = crowi.configManager.getConfig('crowi', 'security:disableLinkSharing');
+    const isLinkSharingDisabled = crowi.configManager.getConfig('security:disableLinkSharing');
     logger.debug(`isLinkSharingDisabled: ${isLinkSharingDisabled}`);
     logger.debug(`isLinkSharingDisabled: ${isLinkSharingDisabled}`);
 
 
     if (isLinkSharingDisabled) {
     if (isLinkSharingDisabled) {

+ 8 - 7
apps/app/src/server/routes/apiv3/slack-integration-legacy-settings.js

@@ -3,6 +3,7 @@ import express from 'express';
 import { body } from 'express-validator';
 import { body } from 'express-validator';
 
 
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
+import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -69,9 +70,9 @@ module.exports = (crowi) => {
 
 
     const slackIntegrationParams = {
     const slackIntegrationParams = {
       isSlackbotConfigured: crowi.slackIntegrationService.isSlackbotConfigured,
       isSlackbotConfigured: crowi.slackIntegrationService.isSlackbotConfigured,
-      webhookUrl: await crowi.configManager.getConfig('notification', 'slack:incomingWebhookUrl'),
-      isIncomingWebhookPrioritized: await crowi.configManager.getConfig('notification', 'slack:isIncomingWebhookPrioritized'),
-      slackToken: await crowi.configManager.getConfig('notification', 'slack:token'),
+      webhookUrl: await crowi.configManager.getConfig('slack:incomingWebhookUrl'),
+      isIncomingWebhookPrioritized: await crowi.configManager.getConfig('slack:isIncomingWebhookPrioritized'),
+      slackToken: await crowi.configManager.getConfig('slack:token'),
     };
     };
     return res.apiv3({ slackIntegrationParams });
     return res.apiv3({ slackIntegrationParams });
   });
   });
@@ -106,11 +107,11 @@ module.exports = (crowi) => {
     };
     };
 
 
     try {
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('notification', requestParams);
+      await configManager.updateConfigs(requestParams);
       const responseParams = {
       const responseParams = {
-        webhookUrl: await crowi.configManager.getConfig('notification', 'slack:incomingWebhookUrl'),
-        isIncomingWebhookPrioritized: await crowi.configManager.getConfig('notification', 'slack:isIncomingWebhookPrioritized'),
-        slackToken: await crowi.configManager.getConfig('notification', 'slack:token'),
+        webhookUrl: await crowi.configManager.getConfig('slack:incomingWebhookUrl'),
+        isIncomingWebhookPrioritized: await crowi.configManager.getConfig('slack:isIncomingWebhookPrioritized'),
+        slackToken: await crowi.configManager.getConfig('slack:token'),
       };
       };
 
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE };

+ 17 - 16
apps/app/src/server/routes/apiv3/slack-integration-settings.js

@@ -1,3 +1,4 @@
+import { ConfigSource } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import {
 import {
   SlackbotType, REQUEST_TIMEOUT_FOR_GTOP,
   SlackbotType, REQUEST_TIMEOUT_FOR_GTOP,
@@ -99,7 +100,7 @@ module.exports = (crowi) => {
   async function updateSlackBotSettings(params) {
   async function updateSlackBotSettings(params) {
     const { configManager } = crowi;
     const { configManager } = crowi;
     // update config without publishing S2sMessage
     // update config without publishing S2sMessage
-    return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
+    return configManager.updateConfigs(params, { skipPubsub: true });
   }
   }
 
 
   async function resetAllBotSettings(initializedType) {
   async function resetAllBotSettings(initializedType) {
@@ -171,17 +172,17 @@ module.exports = (crowi) => {
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     const { configManager, slackIntegrationService } = crowi;
     const { configManager, slackIntegrationService } = crowi;
-    const currentBotType = configManager.getConfig('crowi', 'slackbot:currentBotType');
+    const currentBotType = configManager.getConfig('slackbot:currentBotType');
 
 
     // retrieve settings
     // retrieve settings
     const settings = {};
     const settings = {};
     if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
     if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
-      settings.slackSigningSecretEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:withoutProxy:signingSecret');
-      settings.slackBotTokenEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:withoutProxy:botToken');
-      settings.slackSigningSecret = configManager.getConfig('crowi', 'slackbot:withoutProxy:signingSecret');
-      settings.slackBotToken = configManager.getConfig('crowi', 'slackbot:withoutProxy:botToken');
-      settings.commandPermission = configManager.getConfig('crowi', 'slackbot:withoutProxy:commandPermission');
-      settings.eventActionsPermission = configManager.getConfig('crowi', 'slackbot:withoutProxy:eventActionsPermission');
+      settings.slackSigningSecretEnvVars = configManager.getConfig('slackbot:withoutProxy:signingSecret', ConfigSource.env);
+      settings.slackBotTokenEnvVars = configManager.getConfig('slackbot:withoutProxy:botToken', ConfigSource.env);
+      settings.slackSigningSecret = configManager.getConfig('slackbot:withoutProxy:signingSecret');
+      settings.slackBotToken = configManager.getConfig('slackbot:withoutProxy:botToken');
+      settings.commandPermission = configManager.getConfig('slackbot:withoutProxy:commandPermission');
+      settings.eventActionsPermission = configManager.getConfig('slackbot:withoutProxy:eventActionsPermission');
     }
     }
     else {
     else {
       settings.proxyServerUri = slackIntegrationService.proxyUriForCurrentType;
       settings.proxyServerUri = slackIntegrationService.proxyUriForCurrentType;
@@ -280,7 +281,7 @@ module.exports = (crowi) => {
     }
     }
 
 
     // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
     // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
-    const slackBotTypeParam = { slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType') };
+    const slackBotTypeParam = { slackBotType: crowi.configManager.getConfig('slackbot:currentBotType') };
     return res.apiv3({ slackBotTypeParam });
     return res.apiv3({ slackBotTypeParam });
   };
   };
 
 
@@ -368,7 +369,7 @@ module.exports = (crowi) => {
    *             description: Succeeded to put CustomBotWithoutProxy setting.
    *             description: Succeeded to put CustomBotWithoutProxy setting.
    */
    */
   router.put('/without-proxy/update-settings', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
   router.put('/without-proxy/update-settings', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
-    const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
+    const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
       const msg = 'Not CustomBotWithoutProxy';
       const msg = 'Not CustomBotWithoutProxy';
       return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
       return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
@@ -409,7 +410,7 @@ module.exports = (crowi) => {
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.put('/without-proxy/update-permissions', loginRequiredStrictly, adminRequired, addActivity, validator.updatePermissionsWithoutProxy, async(req, res) => {
   router.put('/without-proxy/update-permissions', loginRequiredStrictly, adminRequired, addActivity, validator.updatePermissionsWithoutProxy, async(req, res) => {
-    const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
+    const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
       const msg = 'Not CustomBotWithoutProxy';
       const msg = 'Not CustomBotWithoutProxy';
       return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
       return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
@@ -699,7 +700,7 @@ module.exports = (crowi) => {
    */
    */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.post('/slack-app-integrations/:id/relation-test', loginRequiredStrictly, adminRequired, addActivity, validator.relationTest, apiV3FormValidator, async(req, res) => {
   router.post('/slack-app-integrations/:id/relation-test', loginRequiredStrictly, adminRequired, addActivity, validator.relationTest, apiV3FormValidator, async(req, res) => {
-    const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
+    const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
     if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
       const msg = 'Not Proxy Type';
       const msg = 'Not Proxy Type';
       return res.apiv3Err(new ErrorV3(msg, 'not-proxy-type'), 400);
       return res.apiv3Err(new ErrorV3(msg, 'not-proxy-type'), 400);
@@ -741,7 +742,7 @@ module.exports = (crowi) => {
     }
     }
 
 
     const { channel } = req.body;
     const { channel } = req.body;
-    const appSiteURL = crowi.configManager.getConfig('crowi', 'app:siteUrl');
+    const appSiteURL = crowi.configManager.getConfig('app:siteUrl');
     try {
     try {
       await sendSuccessMessage(slackBotToken, channel, appSiteURL);
       await sendSuccessMessage(slackBotToken, channel, appSiteURL);
     }
     }
@@ -776,20 +777,20 @@ module.exports = (crowi) => {
    *             description: Succeeded to connect to slack work space.
    *             description: Succeeded to connect to slack work space.
    */
    */
   router.post('/without-proxy/test', loginRequiredStrictly, adminRequired, addActivity, validator.slackChannel, apiV3FormValidator, async(req, res) => {
   router.post('/without-proxy/test', loginRequiredStrictly, adminRequired, addActivity, validator.slackChannel, apiV3FormValidator, async(req, res) => {
-    const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
+    const currentBotType = crowi.configManager.getConfig('slackbot:currentBotType');
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
     if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
       const msg = 'Select Without Proxy Type';
       const msg = 'Select Without Proxy Type';
       return res.apiv3Err(new ErrorV3(msg, 'select-not-proxy-type'), 400);
       return res.apiv3Err(new ErrorV3(msg, 'select-not-proxy-type'), 400);
     }
     }
 
 
-    const slackBotToken = crowi.configManager.getConfig('crowi', 'slackbot:withoutProxy:botToken');
+    const slackBotToken = crowi.configManager.getConfig('slackbot:withoutProxy:botToken');
     const status = await getConnectionStatus(slackBotToken);
     const status = await getConnectionStatus(slackBotToken);
     if (status.error != null) {
     if (status.error != null) {
       return res.apiv3Err(new ErrorV3(`Error occured while getting connection. ${status.error}`, 'send-message-failed'));
       return res.apiv3Err(new ErrorV3(`Error occured while getting connection. ${status.error}`, 'send-message-failed'));
     }
     }
 
 
     const { channel } = req.body;
     const { channel } = req.body;
-    const appSiteURL = crowi.configManager.getConfig('crowi', 'app:siteUrl');
+    const appSiteURL = crowi.configManager.getConfig('app:siteUrl');
     try {
     try {
       await sendSuccessMessage(slackBotToken, channel, appSiteURL);
       await sendSuccessMessage(slackBotToken, channel, appSiteURL);
     }
     }

+ 12 - 10
apps/app/src/server/routes/apiv3/slack-integration.js

@@ -9,6 +9,7 @@ import { parseSlashCommand } from '@growi/slack/dist/utils/slash-command-parser'
 import createError from 'http-errors';
 import createError from 'http-errors';
 
 
 import { SlackCommandHandlerError } from '~/server/models/vo/slack-command-handler-error';
 import { SlackCommandHandlerError } from '~/server/models/vo/slack-command-handler-error';
+import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 
@@ -23,9 +24,10 @@ const SlackAppIntegration = mongoose.model('SlackAppIntegration');
 const { handleError } = require('../../service/slack-command-handler/error-handler');
 const { handleError } = require('../../service/slack-command-handler/error-handler');
 const { checkPermission } = require('../../util/slack-integration');
 const { checkPermission } = require('../../util/slack-integration');
 
 
+/** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
 
 
-  const { configManager, slackIntegrationService } = crowi;
+  const { slackIntegrationService } = crowi;
 
 
   // Check if the access token is correct
   // Check if the access token is correct
   async function verifyAccessTokenFromProxy(req, res, next) {
   async function verifyAccessTokenFromProxy(req, res, next) {
@@ -115,7 +117,7 @@ module.exports = (crowi) => {
     }
     }
 
 
     // without proxy
     // without proxy
-    commandPermission = configManager.getConfig('crowi', 'slackbot:withoutProxy:commandPermission');
+    commandPermission = configManager.getConfig('slackbot:withoutProxy:commandPermission');
 
 
     const isPermitted = checkPermission(commandPermission, growiCommand.growiCommandType, fromChannel);
     const isPermitted = checkPermission(commandPermission, growiCommand.growiCommandType, fromChannel);
     if (isPermitted) {
     if (isPermitted) {
@@ -162,7 +164,7 @@ module.exports = (crowi) => {
     }
     }
 
 
     // without proxy
     // without proxy
-    commandPermission = configManager.getConfig('crowi', 'slackbot:withoutProxy:commandPermission');
+    commandPermission = configManager.getConfig('slackbot:withoutProxy:commandPermission');
 
 
     const isPermitted = checkPermission(commandPermission, callbacIdkOrActionId, fromChannel);
     const isPermitted = checkPermission(commandPermission, callbacIdkOrActionId, fromChannel);
     if (isPermitted) {
     if (isPermitted) {
@@ -182,7 +184,7 @@ module.exports = (crowi) => {
   }
   }
 
 
   const addSigningSecretToReq = (req, res, next) => {
   const addSigningSecretToReq = (req, res, next) => {
-    req.slackSigningSecret = configManager.getConfig('crowi', 'slackbot:withoutProxy:signingSecret');
+    req.slackSigningSecret = configManager.getConfig('slackbot:withoutProxy:signingSecret');
     return next();
     return next();
   };
   };
 
 
@@ -210,7 +212,7 @@ module.exports = (crowi) => {
   };
   };
 
 
   function getRespondUtil(responseUrl) {
   function getRespondUtil(responseUrl) {
-    const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType; // can be null
+    const proxyUri = slackIntegrationService.proxyUriForCurrentType ?? null; // can be null
 
 
     const appSiteUrl = crowi.appService.getSiteUrl();
     const appSiteUrl = crowi.appService.getSiteUrl();
     if (appSiteUrl == null || appSiteUrl === '') {
     if (appSiteUrl == null || appSiteUrl === '') {
@@ -280,7 +282,7 @@ module.exports = (crowi) => {
     }
     }
 
 
     try {
     try {
-      await crowi.slackIntegrationService.handleCommandRequest(growiCommand, client, body, respondUtil);
+      await slackIntegrationService.handleCommandRequest(growiCommand, client, body, respondUtil);
     }
     }
     catch (err) {
     catch (err) {
       return handleError(err, responseUrl);
       return handleError(err, responseUrl);
@@ -352,10 +354,10 @@ module.exports = (crowi) => {
       const respondUtil = getRespondUtil(responseUrl);
       const respondUtil = getRespondUtil(responseUrl);
       switch (type) {
       switch (type) {
         case 'block_actions':
         case 'block_actions':
-          await crowi.slackIntegrationService.handleBlockActionsRequest(client, interactionPayload, interactionPayloadAccessor, respondUtil);
+          await slackIntegrationService.handleBlockActionsRequest(client, interactionPayload, interactionPayloadAccessor, respondUtil);
           break;
           break;
         case 'view_submission':
         case 'view_submission':
-          await crowi.slackIntegrationService.handleViewSubmissionRequest(client, interactionPayload, interactionPayloadAccessor, respondUtil);
+          await slackIntegrationService.handleViewSubmissionRequest(client, interactionPayload, interactionPayloadAccessor, respondUtil);
           break;
           break;
         default:
         default:
           break;
           break;
@@ -397,7 +399,7 @@ module.exports = (crowi) => {
     try {
     try {
       const client = await slackIntegrationService.generateClientForCustomBotWithoutProxy();
       const client = await slackIntegrationService.generateClientForCustomBotWithoutProxy();
       // convert permission object to map
       // convert permission object to map
-      const permission = new Map(Object.entries(crowi.configManager.getConfig('crowi', 'slackbot:withoutProxy:eventActionsPermission')));
+      const permission = new Map(Object.entries(crowi.configManager.getConfig('slackbot:withoutProxy:eventActionsPermission')));
 
 
       await crowi.slackIntegrationService.handleEventsRequest(client, growiBotEvent, permission);
       await crowi.slackIntegrationService.handleEventsRequest(client, growiBotEvent, permission);
 
 
@@ -431,7 +433,7 @@ module.exports = (crowi) => {
       const client = await slackIntegrationService.generateClientBySlackAppIntegration(slackAppIntegration);
       const client = await slackIntegrationService.generateClientBySlackAppIntegration(slackAppIntegration);
       const { permissionsForSlackEventActions } = slackAppIntegration;
       const { permissionsForSlackEventActions } = slackAppIntegration;
 
 
-      await crowi.slackIntegrationService.handleEventsRequest(client, growiBotEvent, permissionsForSlackEventActions, data);
+      await slackIntegrationService.handleEventsRequest(client, growiBotEvent, permissionsForSlackEventActions, data);
 
 
       return res.apiv3({});
       return res.apiv3({});
     }
     }

+ 1 - 1
apps/app/src/server/routes/apiv3/staffs.js

@@ -25,7 +25,7 @@ module.exports = (crowi) => {
 
 
   router.get('/', async(req, res) => {
   router.get('/', async(req, res) => {
     const now = new Date();
     const now = new Date();
-    const growiCloudUri = await crowi.configManager.getConfig('crowi', 'app:growiCloudUri');
+    const growiCloudUri = await crowi.configManager.getConfig('app:growiCloudUri');
 
 
     if (growiCloudUri != null && (expiredAt == null || isAfter(now, expiredAt))) {
     if (growiCloudUri != null && (expiredAt == null || isAfter(now, expiredAt))) {
       const url = new URL('_api/staffCredit', growiCloudUri);
       const url = new URL('_api/staffCredit', growiCloudUri);

+ 6 - 6
apps/app/src/server/routes/apiv3/user-activation.ts

@@ -83,12 +83,12 @@ export const completeRegistrationAction = (crowi) => {
     }
     }
 
 
     // error when registration is not allowed
     // error when registration is not allowed
-    if (configManager.getConfig('crowi', 'security:registrationMode') === aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED) {
+    if (configManager.getConfig('security:registrationMode') === aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED) {
       return res.apiv3Err(new ErrorV3('Registration closed', 'registration-failed'), 403);
       return res.apiv3Err(new ErrorV3('Registration closed', 'registration-failed'), 403);
     }
     }
 
 
     // error when email authentication is disabled
     // error when email authentication is disabled
-    if (configManager.getConfig('crowi', 'security:passport-local:isEmailAuthenticationEnabled') !== true) {
+    if (configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled') !== true) {
       return res.apiv3Err(new ErrorV3('Email authentication configuration is disabled', 'registration-failed'), 403);
       return res.apiv3Err(new ErrorV3('Email authentication configuration is disabled', 'registration-failed'), 403);
     }
     }
 
 
@@ -138,13 +138,13 @@ export const completeRegistrationAction = (crowi) => {
 
 
         userRegistrationOrder.revokeOneTimeToken();
         userRegistrationOrder.revokeOneTimeToken();
 
 
-        if (configManager.getConfig('crowi', 'security:registrationMode') === aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED) {
+        if (configManager.getConfig('security:registrationMode') === aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED) {
           const isMailerSetup = mailService.isMailerSetup ?? false;
           const isMailerSetup = mailService.isMailerSetup ?? false;
 
 
           if (isMailerSetup) {
           if (isMailerSetup) {
             const admins = await User.findAdmins();
             const admins = await User.findAdmins();
             const appTitle = appService.getAppTitle();
             const appTitle = appService.getAppTitle();
-            const locale = configManager.getConfig('crowi', 'app:globalLang');
+            const locale = configManager.getConfig('app:globalLang');
             const template = path.join(crowi.localeDir, `${locale}/admin/userWaitingActivation.ejs`);
             const template = path.join(crowi.localeDir, `${locale}/admin/userWaitingActivation.ejs`);
             const url = appService.getSiteUrl();
             const url = appService.getSiteUrl();
 
 
@@ -218,7 +218,7 @@ async function makeRegistrationEmailToken(email, crowi) {
     throw Error('mailService is not setup');
     throw Error('mailService is not setup');
   }
   }
 
 
-  const locale = configManager.getConfig('crowi', 'app:globalLang');
+  const locale = configManager.getConfig('app:globalLang');
   const appUrl = appService.getSiteUrl();
   const appUrl = appService.getSiteUrl();
 
 
   const userRegistrationOrder = await UserRegistrationOrder.createUserRegistrationOrder(email);
   const userRegistrationOrder = await UserRegistrationOrder.createUserRegistrationOrder(email);
@@ -248,7 +248,7 @@ export const registerAction = (crowi) => {
     const registerForm = req.body.registerForm || {};
     const registerForm = req.body.registerForm || {};
     const email = registerForm.email;
     const email = registerForm.email;
     const isRegisterableEmail = await User.isRegisterableEmail(email);
     const isRegisterableEmail = await User.isRegisterableEmail(email);
-    const registrationMode = configManager.getConfig('crowi', 'security:registrationMode') as RegistrationMode;
+    const registrationMode = configManager.getConfig('security:registrationMode');
     const isEmailValid = await User.isEmailValid(email);
     const isEmailValid = await User.isEmailValid(email);
 
 
     if (registrationMode === RegistrationMode.CLOSED) {
     if (registrationMode === RegistrationMode.CLOSED) {

Некоторые файлы не были показаны из-за большого количества измененных файлов