From d0399eb8a4e4c7d9c5535d2a66d584efd486facb Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Sun, 29 Jan 2023 19:42:19 +0800 Subject: [PATCH] refactor!: merge settings into logto configs table --- .../src/commands/database/alteration/index.ts | 2 +- .../cli/src/commands/database/seed/index.ts | 6 +- packages/cli/src/queries/logto-config.ts | 41 ++---------- .../{logto-config.test.ts => system.test.ts} | 6 +- packages/cli/src/queries/system.ts | 67 +++++++++++++++++++ .../src/components/AppContent/index.tsx | 6 +- packages/console/src/hooks/use-configs.ts | 36 ++++++++++ packages/console/src/hooks/use-settings.ts | 40 ----------- .../components/CreateForm/index.tsx | 6 +- .../Connectors/components/Guide/index.tsx | 6 +- packages/console/src/pages/GetStarted/hook.ts | 34 +++++----- .../components/Welcome/GuideModal.tsx | 8 +-- .../src/pages/SignInExperience/index.tsx | 16 ++--- packages/core/src/__mocks__/index.ts | 27 ++++---- packages/core/src/database/update-where.ts | 2 +- packages/core/src/queries/logto-config.ts | 25 +++++++ packages/core/src/queries/setting.test.ts | 64 ------------------ packages/core/src/queries/setting.ts | 24 ------- packages/core/src/routes/init.ts | 4 +- packages/core/src/routes/logto-config.test.ts | 39 +++++++++++ packages/core/src/routes/logto-config.ts | 35 ++++++++++ packages/core/src/routes/setting.test.ts | 44 ------------ packages/core/src/routes/setting.ts | 32 --------- packages/core/src/tenants/Queries.ts | 4 +- ...674987042.1-merge-settings-into-configs.ts | 62 +++++++++++++++++ .../next-1674987042.2-create-systems-table.ts | 39 +++++++++++ .../schemas/src/foundations/jsonb-types.ts | 12 ---- packages/schemas/src/seeds/index.ts | 2 +- packages/schemas/src/seeds/logto-config.ts | 22 ++++++ packages/schemas/src/seeds/setting.ts | 21 ------ packages/schemas/src/types/logto-config.ts | 37 +++++++++- packages/schemas/tables/logto_configs.sql | 10 +++ packages/schemas/tables/settings.sql | 13 ---- .../{_logto_configs.sql => systems.sql} | 2 +- 34 files changed, 440 insertions(+), 354 deletions(-) rename packages/cli/src/queries/{logto-config.test.ts => system.test.ts} (95%) create mode 100644 packages/cli/src/queries/system.ts create mode 100644 packages/console/src/hooks/use-configs.ts delete mode 100644 packages/console/src/hooks/use-settings.ts create mode 100644 packages/core/src/queries/logto-config.ts delete mode 100644 packages/core/src/queries/setting.test.ts delete mode 100644 packages/core/src/queries/setting.ts create mode 100644 packages/core/src/routes/logto-config.test.ts create mode 100644 packages/core/src/routes/logto-config.ts delete mode 100644 packages/core/src/routes/setting.test.ts delete mode 100644 packages/core/src/routes/setting.ts create mode 100644 packages/schemas/alterations/next-1674987042.1-merge-settings-into-configs.ts create mode 100644 packages/schemas/alterations/next-1674987042.2-create-systems-table.ts create mode 100644 packages/schemas/src/seeds/logto-config.ts delete mode 100644 packages/schemas/src/seeds/setting.ts create mode 100644 packages/schemas/tables/logto_configs.sql delete mode 100644 packages/schemas/tables/settings.sql rename packages/schemas/tables/{_logto_configs.sql => systems.sql} (80%) diff --git a/packages/cli/src/commands/database/alteration/index.ts b/packages/cli/src/commands/database/alteration/index.ts index 28c69e7bf..317c386b0 100644 --- a/packages/cli/src/commands/database/alteration/index.ts +++ b/packages/cli/src/commands/database/alteration/index.ts @@ -8,7 +8,7 @@ import { createPoolFromConfig } from '../../../database.js'; import { getCurrentDatabaseAlterationTimestamp, updateDatabaseTimestamp, -} from '../../../queries/logto-config.js'; +} from '../../../queries/system.js'; import { log } from '../../../utilities.js'; import type { AlterationFile } from './type.js'; import { getAlterationFiles, getTimestampFromFilename } from './utils.js'; diff --git a/packages/cli/src/commands/database/seed/index.ts b/packages/cli/src/commands/database/seed/index.ts index 64b201398..87a46d3c3 100644 --- a/packages/cli/src/commands/database/seed/index.ts +++ b/packages/cli/src/commands/database/seed/index.ts @@ -7,7 +7,7 @@ import { LogtoOidcConfigKey, managementResource, defaultSignInExperience, - createDefaultSetting, + createDefaultAdminConsoleConfig, createDemoAppApplication, defaultRole, managementResourceScope, @@ -26,9 +26,9 @@ import { createPoolAndDatabaseIfNeeded, insertInto } from '../../../database.js' import { getRowsByKeys, doesConfigsTableExist, - updateDatabaseTimestamp, updateValueByKey, } from '../../../queries/logto-config.js'; +import { updateDatabaseTimestamp } from '../../../queries/system.js'; import { getPathInModule, log, oraPromise } from '../../../utilities.js'; import { getLatestAlterationTimestamp } from '../alteration/index.js'; import { getAlterationDirectory } from '../alteration/utils.js'; @@ -89,7 +89,7 @@ const seedTables = async (connection: DatabaseTransactionConnection, latestTimes await Promise.all([ connection.query(insertInto(managementResource, 'resources')), connection.query(insertInto(managementResourceScope, 'scopes')), - connection.query(insertInto(createDefaultSetting(), 'settings')), + connection.query(insertInto(createDefaultAdminConsoleConfig(), 'logto_configs')), connection.query(insertInto(defaultSignInExperience, 'sign_in_experiences')), connection.query(insertInto(createDemoAppApplication(generateStandardId()), 'applications')), connection.query(insertInto(defaultRole, 'roles')), diff --git a/packages/cli/src/queries/logto-config.ts b/packages/cli/src/queries/logto-config.ts index d207b690e..2de144cde 100644 --- a/packages/cli/src/queries/logto-config.ts +++ b/packages/cli/src/queries/logto-config.ts @@ -1,10 +1,10 @@ -import type { AlterationState, LogtoConfig, LogtoConfigKey } from '@logto/schemas'; -import { logtoConfigGuards, LogtoConfigs, AlterationStateKey } from '@logto/schemas'; +import type { LogtoConfig, LogtoConfigKey, logtoConfigGuards } from '@logto/schemas'; +import { LogtoConfigs } from '@logto/schemas'; import { convertToIdentifiers } from '@logto/shared'; import type { Nullable } from '@silverhand/essentials'; -import type { CommonQueryMethods, DatabaseTransactionConnection } from 'slonik'; +import type { CommonQueryMethods } from 'slonik'; import { sql } from 'slonik'; -import { z } from 'zod'; +import type { z } from 'zod'; const { table, fields } = convertToIdentifiers(LogtoConfigs); @@ -34,36 +34,3 @@ export const updateValueByKey = async ( on conflict (${fields.key}) do update set ${fields.value}=excluded.${fields.value} ` ); - -export const getCurrentDatabaseAlterationTimestamp = async (pool: CommonQueryMethods) => { - try { - const result = await pool.maybeOne( - sql`select * from ${table} where ${fields.key}=${AlterationStateKey.AlterationState}` - ); - const parsed = logtoConfigGuards[AlterationStateKey.AlterationState].safeParse(result?.value); - - return (parsed.success && parsed.data.timestamp) || 0; - } catch (error: unknown) { - const result = z.object({ code: z.string() }).safeParse(error); - - // Relation does not exist, treat as 0 - // https://www.postgresql.org/docs/14/errcodes-appendix.html - if (result.success && result.data.code === '42P01') { - return 0; - } - - throw error; - } -}; - -export const updateDatabaseTimestamp = async ( - connection: DatabaseTransactionConnection, - timestamp: number -) => { - const value: AlterationState = { - timestamp, - updatedAt: new Date().toISOString(), - }; - - return updateValueByKey(connection, AlterationStateKey.AlterationState, value); -}; diff --git a/packages/cli/src/queries/logto-config.test.ts b/packages/cli/src/queries/system.test.ts similarity index 95% rename from packages/cli/src/queries/logto-config.test.ts rename to packages/cli/src/queries/system.test.ts index 4498fbccb..b1a53a5d0 100644 --- a/packages/cli/src/queries/logto-config.test.ts +++ b/packages/cli/src/queries/system.test.ts @@ -1,10 +1,10 @@ -import { AlterationStateKey, LogtoConfigs } from '@logto/schemas'; +import { AlterationStateKey, Systems } from '@logto/schemas'; import { convertToIdentifiers } from '@logto/shared'; import { createMockPool, createMockQueryResult, sql } from 'slonik'; import type { QueryType } from '../test-utilities.js'; import { expectSqlAssert } from '../test-utilities.js'; -import { updateDatabaseTimestamp, getCurrentDatabaseAlterationTimestamp } from './logto-config.js'; +import { updateDatabaseTimestamp, getCurrentDatabaseAlterationTimestamp } from './system.js'; const { jest } = import.meta; @@ -15,7 +15,7 @@ const pool = createMockPool({ return mockQuery(sql, values); }, }); -const { table, fields } = convertToIdentifiers(LogtoConfigs); +const { table, fields } = convertToIdentifiers(Systems); const timestamp = 1_663_923_776; describe('getCurrentDatabaseAlterationTimestamp()', () => { diff --git a/packages/cli/src/queries/system.ts b/packages/cli/src/queries/system.ts new file mode 100644 index 000000000..f4b2716ec --- /dev/null +++ b/packages/cli/src/queries/system.ts @@ -0,0 +1,67 @@ +import type { AlterationState, System } from '@logto/schemas'; +import { Systems, logtoConfigGuards, AlterationStateKey } from '@logto/schemas'; +import { convertToIdentifiers } from '@logto/shared'; +import type { Nullable } from '@silverhand/essentials'; +import type { CommonQueryMethods, DatabaseTransactionConnection } from 'slonik'; +import { sql } from 'slonik'; +import { z } from 'zod'; + +const { fields } = convertToIdentifiers(Systems); + +const doesTableExist = async (pool: CommonQueryMethods, table: string) => { + const { rows } = await pool.query<{ regclass: Nullable }>( + sql`select to_regclass(${table}) as regclass` + ); + + return Boolean(rows[0]?.regclass); +}; + +export const doesSystemsTableExist = async (pool: CommonQueryMethods) => + doesTableExist(pool, Systems.table); + +const getAlterationStateTable = async (pool: CommonQueryMethods) => + (await doesSystemsTableExist(pool)) + ? sql.identifier([Systems.table]) + : sql.identifier(['_logto_configs']); // Fall back to the old config table + +export const getCurrentDatabaseAlterationTimestamp = async (pool: CommonQueryMethods) => { + const table = await getAlterationStateTable(pool); + + try { + const result = await pool.maybeOne( + sql`select * from ${table} where ${fields.key}=${AlterationStateKey.AlterationState}` + ); + const parsed = logtoConfigGuards[AlterationStateKey.AlterationState].safeParse(result?.value); + + return (parsed.success && parsed.data.timestamp) || 0; + } catch (error: unknown) { + const result = z.object({ code: z.string() }).safeParse(error); + + // Relation does not exist, treat as 0 + // https://www.postgresql.org/docs/14/errcodes-appendix.html + if (result.success && result.data.code === '42P01') { + return 0; + } + + throw error; + } +}; + +export const updateDatabaseTimestamp = async ( + connection: DatabaseTransactionConnection, + timestamp: number +) => { + const table = await getAlterationStateTable(connection); + const value: AlterationState = { + timestamp, + updatedAt: new Date().toISOString(), + }; + + await connection.query( + sql` + insert into ${table} (${fields.key}, ${fields.value}) + values (${AlterationStateKey.AlterationState}, ${sql.jsonb(value)}) + on conflict (${fields.key}) do update set ${fields.value}=excluded.${fields.value} + ` + ); +}; diff --git a/packages/console/src/components/AppContent/index.tsx b/packages/console/src/components/AppContent/index.tsx index 78b34b1fe..dc37de3d1 100644 --- a/packages/console/src/components/AppContent/index.tsx +++ b/packages/console/src/components/AppContent/index.tsx @@ -7,8 +7,8 @@ import { Outlet, useHref, useLocation, useNavigate } from 'react-router-dom'; import AppError from '@/components/AppError'; import AppLoading from '@/components/AppLoading'; import SessionExpired from '@/components/SessionExpired'; +import useConfigs from '@/hooks/use-configs'; import useScroll from '@/hooks/use-scroll'; -import useSettings from '@/hooks/use-settings'; import useUserPreferences from '@/hooks/use-user-preferences'; import Sidebar, { getPath } from './components/Sidebar'; @@ -20,8 +20,8 @@ const AppContent = () => { const { isAuthenticated, isLoading: isLogtoLoading, error, signIn } = useLogto(); const href = useHref('/callback'); const { isLoading: isPreferencesLoading } = useUserPreferences(); - const { isLoading: isSettingsLoading } = useSettings(); - const isLoading = isPreferencesLoading || isSettingsLoading; + const { isLoading: isConfigsLoading } = useConfigs(); + const isLoading = isPreferencesLoading || isConfigsLoading; const location = useLocation(); const navigate = useNavigate(); diff --git a/packages/console/src/hooks/use-configs.ts b/packages/console/src/hooks/use-configs.ts new file mode 100644 index 000000000..9d3df4d28 --- /dev/null +++ b/packages/console/src/hooks/use-configs.ts @@ -0,0 +1,36 @@ +import { useLogto } from '@logto/react'; +import type { AdminConsoleData } from '@logto/schemas'; +import useSWR from 'swr'; + +import type { RequestError } from './use-api'; +import useApi from './use-api'; + +const useConfigs = () => { + const { isAuthenticated, error: authError } = useLogto(); + const shouldFetch = isAuthenticated && !authError; + const { + data: configs, + error, + mutate, + } = useSWR(shouldFetch && '/api/configs/admin-console'); + const api = useApi(); + + const updateConfigs = async (json: Partial) => { + const updatedConfigs = await api + .patch('/api/configs/admin-console', { + json, + }) + .json(); + void mutate(updatedConfigs); + }; + + return { + isLoading: !configs && !error, + configs, + error, + mutate, + updateConfigs, + }; +}; + +export default useConfigs; diff --git a/packages/console/src/hooks/use-settings.ts b/packages/console/src/hooks/use-settings.ts deleted file mode 100644 index c17481899..000000000 --- a/packages/console/src/hooks/use-settings.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useLogto } from '@logto/react'; -import type { AdminConsoleConfig, Setting } from '@logto/schemas'; -import useSWR from 'swr'; - -import type { RequestError } from './use-api'; -import useApi from './use-api'; - -const useSettings = () => { - const { isAuthenticated, error: authError } = useLogto(); - const shouldFetch = isAuthenticated && !authError; - const { - data: settings, - error, - mutate, - } = useSWR(shouldFetch && '/api/settings'); - const api = useApi(); - - const updateSettings = async (delta: Partial) => { - const updatedSettings = await api - .patch('/api/settings', { - json: { - adminConsole: { - ...delta, - }, - }, - }) - .json(); - void mutate(updatedSettings); - }; - - return { - isLoading: !settings && !error, - settings: settings?.adminConsole, - error, - mutate, - updateSettings, - }; -}; - -export default useSettings; diff --git a/packages/console/src/pages/Applications/components/CreateForm/index.tsx b/packages/console/src/pages/Applications/components/CreateForm/index.tsx index ba5eee512..b8664f79b 100644 --- a/packages/console/src/pages/Applications/components/CreateForm/index.tsx +++ b/packages/console/src/pages/Applications/components/CreateForm/index.tsx @@ -11,7 +11,7 @@ import ModalLayout from '@/components/ModalLayout'; import RadioGroup, { Radio } from '@/components/RadioGroup'; import TextInput from '@/components/TextInput'; import useApi from '@/hooks/use-api'; -import useSettings from '@/hooks/use-settings'; +import useConfigs from '@/hooks/use-configs'; import * as modalStyles from '@/scss/modal.module.scss'; import { applicationTypeI18nKey } from '@/types/applications'; @@ -30,7 +30,7 @@ type Props = { }; const CreateForm = ({ onClose }: Props) => { - const { updateSettings } = useSettings(); + const { updateConfigs } = useConfigs(); const [createdApp, setCreatedApp] = useState(); const [isGetStartedModalOpen, setIsGetStartedModalOpen] = useState(false); const { @@ -58,7 +58,7 @@ const CreateForm = ({ onClose }: Props) => { const createdApp = await api.post('/api/applications', { json: data }).json(); setCreatedApp(createdApp); setIsGetStartedModalOpen(true); - void updateSettings({ applicationCreated: true }); + void updateConfigs({ applicationCreated: true }); }); return ( diff --git a/packages/console/src/pages/Connectors/components/Guide/index.tsx b/packages/console/src/pages/Connectors/components/Guide/index.tsx index 890b7b502..2ab100810 100644 --- a/packages/console/src/pages/Connectors/components/Guide/index.tsx +++ b/packages/console/src/pages/Connectors/components/Guide/index.tsx @@ -16,7 +16,7 @@ import IconButton from '@/components/IconButton'; import Markdown from '@/components/Markdown'; import { ConnectorsTabs } from '@/consts/page-tabs'; import useApi from '@/hooks/use-api'; -import useSettings from '@/hooks/use-settings'; +import useConfigs from '@/hooks/use-configs'; import SenderTester from '@/pages/ConnectorDetails/components/SenderTester'; import { safeParseJson } from '@/utilities/json'; @@ -33,7 +33,7 @@ type Props = { const Guide = ({ connector, onClose }: Props) => { const api = useApi(); const navigate = useNavigate(); - const { updateSettings } = useSettings(); + const { updateConfigs } = useConfigs(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { id: connectorId, type: connectorType, name, readme, isStandard } = connector; const { language } = i18next; @@ -89,7 +89,7 @@ const Guide = ({ connector, onClose }: Props) => { }) .json(); - await updateSettings({ + await updateConfigs({ ...conditional(!isSocialConnector && { passwordlessConfigured: true }), ...conditional(isSocialConnector && { socialSignInConfigured: true }), }); diff --git a/packages/console/src/pages/GetStarted/hook.ts b/packages/console/src/pages/GetStarted/hook.ts index a7f446860..6aabccf30 100644 --- a/packages/console/src/pages/GetStarted/hook.ts +++ b/packages/console/src/pages/GetStarted/hook.ts @@ -19,8 +19,8 @@ import SocialDark from '@/assets/images/social-dark.svg'; import Social from '@/assets/images/social.svg'; import { ConnectorsTabs } from '@/consts/page-tabs'; import { RequestError } from '@/hooks/use-api'; +import useConfigs from '@/hooks/use-configs'; import useDocumentationUrl from '@/hooks/use-documentation-url'; -import useSettings from '@/hooks/use-settings'; import { useTheme } from '@/hooks/use-theme'; type GetStartedMetadata = { @@ -36,7 +36,7 @@ type GetStartedMetadata = { const useGetStartedMetadata = () => { const documentationUrl = useDocumentationUrl(); - const { settings, updateSettings } = useSettings(); + const { configs, updateConfigs } = useConfigs(); const theme = useTheme(); const isLightMode = theme === AppearanceMode.LightMode; const { data: demoApp, error } = useSWR( @@ -63,10 +63,10 @@ const useGetStartedMetadata = () => { subtitle: 'get_started.card1_subtitle', icon: isLightMode ? CheckDemo : CheckDemoDark, buttonText: 'general.check_out', - isComplete: settings?.demoChecked, + isComplete: configs?.demoChecked, isHidden: hideDemo, onClick: async () => { - void updateSettings({ demoChecked: true }); + void updateConfigs({ demoChecked: true }); window.open('/demo-app', '_blank'); }, }, @@ -76,7 +76,7 @@ const useGetStartedMetadata = () => { subtitle: 'get_started.card2_subtitle', icon: isLightMode ? CreateApp : CreateAppDark, buttonText: 'general.create', - isComplete: settings?.applicationCreated, + isComplete: configs?.applicationCreated, onClick: () => { navigate('/applications/create'); }, @@ -87,7 +87,7 @@ const useGetStartedMetadata = () => { subtitle: 'get_started.card3_subtitle', icon: isLightMode ? Customize : CustomizeDark, buttonText: 'general.customize', - isComplete: settings?.signInExperienceCustomized, + isComplete: configs?.signInExperienceCustomized, onClick: () => { navigate('/sign-in-experience'); }, @@ -98,7 +98,7 @@ const useGetStartedMetadata = () => { subtitle: 'get_started.card4_subtitle', icon: isLightMode ? Passwordless : PasswordlessDark, buttonText: 'general.set_up', - isComplete: settings?.passwordlessConfigured, + isComplete: configs?.passwordlessConfigured, onClick: () => { navigate(`/connectors/${ConnectorsTabs.Passwordless}`); }, @@ -109,7 +109,7 @@ const useGetStartedMetadata = () => { subtitle: 'get_started.card5_subtitle', icon: isLightMode ? Social : SocialDark, buttonText: 'general.add', - isComplete: settings?.socialSignInConfigured, + isComplete: configs?.socialSignInConfigured, onClick: () => { navigate(`/connectors/${ConnectorsTabs.Social}`); }, @@ -120,9 +120,9 @@ const useGetStartedMetadata = () => { subtitle: 'get_started.card6_subtitle', icon: isLightMode ? FurtherReadings : FurtherReadingsDark, buttonText: 'general.check_out', - isComplete: settings?.furtherReadingsChecked, + isComplete: configs?.furtherReadingsChecked, onClick: () => { - void updateSettings({ furtherReadingsChecked: true }); + void updateConfigs({ furtherReadingsChecked: true }); window.open(`${documentationUrl}/docs/tutorials/get-started/further-readings/`, '_blank'); }, }, @@ -134,13 +134,13 @@ const useGetStartedMetadata = () => { hideDemo, isLightMode, navigate, - settings?.applicationCreated, - settings?.demoChecked, - settings?.furtherReadingsChecked, - settings?.passwordlessConfigured, - settings?.signInExperienceCustomized, - settings?.socialSignInConfigured, - updateSettings, + configs?.applicationCreated, + configs?.demoChecked, + configs?.furtherReadingsChecked, + configs?.passwordlessConfigured, + configs?.signInExperienceCustomized, + configs?.socialSignInConfigured, + updateConfigs, ]); return { diff --git a/packages/console/src/pages/SignInExperience/components/Welcome/GuideModal.tsx b/packages/console/src/pages/SignInExperience/components/Welcome/GuideModal.tsx index 7c70c7c08..a04645ac0 100644 --- a/packages/console/src/pages/SignInExperience/components/Welcome/GuideModal.tsx +++ b/packages/console/src/pages/SignInExperience/components/Welcome/GuideModal.tsx @@ -12,7 +12,7 @@ import CardTitle from '@/components/CardTitle'; import IconButton from '@/components/IconButton'; import Spacer from '@/components/Spacer'; import useApi from '@/hooks/use-api'; -import useSettings from '@/hooks/use-settings'; +import useConfigs from '@/hooks/use-configs'; import useUserPreferences from '@/hooks/use-user-preferences'; import * as modalStyles from '@/scss/modal.module.scss'; @@ -34,7 +34,7 @@ type Props = { const GuideModal = ({ isOpen, onClose }: Props) => { const { data } = useSWR('/api/sign-in-exp'); const { data: preferences, update: updatePreferences } = useUserPreferences(); - const { updateSettings } = useSettings(); + const { updateConfigs } = useConfigs(); const methods = useForm(); const { reset, @@ -68,7 +68,7 @@ const GuideModal = ({ isOpen, onClose }: Props) => { api.patch('/api/sign-in-exp', { json: signInExperienceParser.toRemoteModel(formData), }), - updateSettings({ signInExperienceCustomized: true }), + updateConfigs({ signInExperienceCustomized: true }), ]); onClose(); @@ -76,7 +76,7 @@ const GuideModal = ({ isOpen, onClose }: Props) => { const onSkip = async () => { setIsLoading(true); - await updateSettings({ signInExperienceCustomized: true }); + await updateConfigs({ signInExperienceCustomized: true }); setIsLoading(false); onClose(); }; diff --git a/packages/console/src/pages/SignInExperience/index.tsx b/packages/console/src/pages/SignInExperience/index.tsx index 59fe30438..efe8c5b93 100644 --- a/packages/console/src/pages/SignInExperience/index.tsx +++ b/packages/console/src/pages/SignInExperience/index.tsx @@ -15,7 +15,7 @@ import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal'; import { SignInExperiencePage } from '@/consts/page-tabs'; import type { RequestError } from '@/hooks/use-api'; import useApi from '@/hooks/use-api'; -import useSettings from '@/hooks/use-settings'; +import useConfigs from '@/hooks/use-configs'; import useUiLanguages from '@/hooks/use-ui-languages'; import Preview from './components/Preview'; @@ -40,7 +40,7 @@ const SignInExperience = () => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { tab } = useParams(); const { data, error, mutate } = useSWR('/api/sign-in-exp'); - const { settings, error: settingsError, updateSettings, mutate: mutateSettings } = useSettings(); + const { configs, error: configsError, updateConfigs, mutate: mutateConfigs } = useConfigs(); const { error: languageError, isLoading: isLoadingLanguages } = useUiLanguages(); const [dataToCompare, setDataToCompare] = useState(); @@ -79,7 +79,7 @@ const SignInExperience = () => { .json(); void mutate(updatedData); setDataToCompare(undefined); - await updateSettings({ signInExperienceCustomized: true }); + await updateConfigs({ signInExperienceCustomized: true }); toast.success(t('general.saved')); }; @@ -100,23 +100,23 @@ const SignInExperience = () => { await saveData(); }); - if ((!settings && !settingsError) || (!data && !error) || isLoadingLanguages) { + if ((!configs && !configsError) || (!data && !error) || isLoadingLanguages) { return ; } - if (!settings && settingsError) { - return
{settingsError.body?.message ?? settingsError.message}
; + if (!configs && configsError) { + return
{configsError.body?.message ?? configsError.message}
; } if (languageError) { return
{languageError.body?.message ?? languageError.message}
; } - if (!settings?.signInExperienceCustomized) { + if (!configs?.signInExperienceCustomized) { return ( { - void mutateSettings(); + void mutateConfigs(); void mutate(); }} /> diff --git a/packages/core/src/__mocks__/index.ts b/packages/core/src/__mocks__/index.ts index c1d316b6e..96c450a55 100644 --- a/packages/core/src/__mocks__/index.ts +++ b/packages/core/src/__mocks__/index.ts @@ -1,5 +1,12 @@ import { VerificationCodeType } from '@logto/connector-kit'; -import type { Application, Passcode, Resource, Role, Scope, Setting } from '@logto/schemas'; +import type { + AdminConsoleData, + Application, + Passcode, + Resource, + Role, + Scope, +} from '@logto/schemas'; import { ApplicationType } from '@logto/schemas'; export * from './connector.js'; @@ -65,17 +72,13 @@ export const mockRole: Role = { description: 'admin', }; -export const mockSetting: Setting = { - tenantId: 'fake_tenant', - id: 'foo setting', - adminConsole: { - demoChecked: false, - applicationCreated: false, - signInExperienceCustomized: false, - passwordlessConfigured: false, - socialSignInConfigured: false, - furtherReadingsChecked: false, - }, +export const mockAdminConsoleData: AdminConsoleData = { + demoChecked: false, + applicationCreated: false, + signInExperienceCustomized: false, + passwordlessConfigured: false, + socialSignInConfigured: false, + furtherReadingsChecked: false, }; export const mockPasscode: Passcode = { diff --git a/packages/core/src/database/update-where.ts b/packages/core/src/database/update-where.ts index e705b8152..0051d0e76 100644 --- a/packages/core/src/database/update-where.ts +++ b/packages/core/src/database/update-where.ts @@ -48,7 +48,7 @@ export const buildUpdateWhereWithPool = */ return sql` ${fields[key]}= - coalesce(${fields[key]},'{}'::jsonb)|| ${convertToPrimitiveOrSql(key, value)} + coalesce(${fields[key]},'{}'::jsonb) || ${convertToPrimitiveOrSql(key, value)} `; } diff --git a/packages/core/src/queries/logto-config.ts b/packages/core/src/queries/logto-config.ts new file mode 100644 index 000000000..3f92492b1 --- /dev/null +++ b/packages/core/src/queries/logto-config.ts @@ -0,0 +1,25 @@ +import type { AdminConsoleData } from '@logto/schemas'; +import { AdminConsoleConfigKey, LogtoConfigs } from '@logto/schemas'; +import { convertToIdentifiers } from '@logto/shared'; +import type { CommonQueryMethods } from 'slonik'; +import { sql } from 'slonik'; + +const { table, fields } = convertToIdentifiers(LogtoConfigs); + +export const createLogtoConfigQueries = (pool: CommonQueryMethods) => { + const getAdminConsoleConfig = async () => + pool.one>(sql` + select ${fields.value} from ${table} + where ${fields.key} = ${AdminConsoleConfigKey.AdminConsole} + `); + + const updateAdminConsoleConfig = async (value: Partial) => + pool.one>(sql` + update ${table} + set ${fields.value}=coalesce(${fields.value},'{}'::jsonb) || ${sql.jsonb(value)} + where ${fields.key} = ${AdminConsoleConfigKey.AdminConsole} + returning ${fields.value} + `); + + return { getAdminConsoleConfig, updateAdminConsoleConfig }; +}; diff --git a/packages/core/src/queries/setting.test.ts b/packages/core/src/queries/setting.test.ts deleted file mode 100644 index fef26b91d..000000000 --- a/packages/core/src/queries/setting.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Settings } from '@logto/schemas'; -import { convertToIdentifiers } from '@logto/shared'; -import { createMockPool, createMockQueryResult, sql } from 'slonik'; - -import { mockSetting } from '#src/__mocks__/index.js'; -import type { QueryType } from '#src/utils/test-utils.js'; -import { expectSqlAssert } from '#src/utils/test-utils.js'; - -const { jest } = import.meta; - -const mockQuery: jest.MockedFunction = jest.fn(); - -const pool = createMockPool({ - query: async (sql, values) => { - return mockQuery(sql, values); - }, -}); - -const { defaultSettingId, createSettingQueries } = await import('./setting.js'); -const { getSetting, updateSetting } = createSettingQueries(pool); - -describe('setting query', () => { - const { table, fields } = convertToIdentifiers(Settings); - const databaseValue = { ...mockSetting, adminConsole: JSON.stringify(mockSetting.adminConsole) }; - - it('getSetting', async () => { - const expectSql = sql` - select ${sql.join(Object.values(fields), sql`, `)} - from ${table} - where ${fields.id}=$1 - `; - - mockQuery.mockImplementationOnce(async (sql, values) => { - expectSqlAssert(sql, expectSql.sql); - expect(values).toEqual([defaultSettingId]); - - return createMockQueryResult([databaseValue]); - }); - - await expect(getSetting()).resolves.toEqual(databaseValue); - }); - - it('updateSetting', async () => { - const { adminConsole } = mockSetting; - - const expectSql = sql` - update ${table} - set - ${fields.adminConsole}= - coalesce("admin_console",'{}'::jsonb)|| $1 - where ${fields.id}=$2 - returning * - `; - - mockQuery.mockImplementationOnce(async (sql, values) => { - expectSqlAssert(sql, expectSql.sql); - expect(values).toEqual([JSON.stringify(adminConsole), defaultSettingId]); - - return createMockQueryResult([databaseValue]); - }); - - await expect(updateSetting({ adminConsole })).resolves.toEqual(databaseValue); - }); -}); diff --git a/packages/core/src/queries/setting.ts b/packages/core/src/queries/setting.ts deleted file mode 100644 index 43f6e575d..000000000 --- a/packages/core/src/queries/setting.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Setting, CreateSetting } from '@logto/schemas'; -import { Settings } from '@logto/schemas'; -import type { OmitAutoSetFields } from '@logto/shared'; -import type { CommonQueryMethods } from 'slonik'; - -import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js'; -import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; - -export const defaultSettingId = 'default'; - -export const createSettingQueries = (pool: CommonQueryMethods) => { - const getSetting = async () => - buildFindEntityByIdWithPool(pool)(Settings)(defaultSettingId); - - const updateSetting = async (setting: Partial>) => { - return buildUpdateWhereWithPool(pool)(Settings, true)({ - set: setting, - where: { id: defaultSettingId }, - jsonbMode: 'merge', - }); - }; - - return { getSetting, updateSetting }; -}; diff --git a/packages/core/src/routes/init.ts b/packages/core/src/routes/init.ts index 289ca4c17..a80f0c064 100644 --- a/packages/core/src/routes/init.ts +++ b/packages/core/src/routes/init.ts @@ -15,12 +15,12 @@ import dashboardRoutes from './dashboard.js'; import hookRoutes from './hook.js'; import interactionRoutes from './interaction/index.js'; import logRoutes from './log.js'; +import logtoConfigRoutes from './logto-config.js'; import phraseRoutes from './phrase.js'; import resourceRoutes from './resource.js'; import roleRoutes from './role.js'; import roleScopeRoutes from './role.scope.js'; import samlAssertionHandlerRoutes from './saml-assertion-handler.js'; -import settingRoutes from './setting.js'; import signInExperiencesRoutes from './sign-in-experience/index.js'; import statusRoutes from './status.js'; import swaggerRoutes from './swagger.js'; @@ -35,7 +35,7 @@ const createRouters = (tenant: TenantContext) => { const managementRouter: AuthedRouter = new Router(); managementRouter.use(koaAuth(tenant.envSet, managementResourceScope.name)); applicationRoutes(managementRouter, tenant); - settingRoutes(managementRouter, tenant); + logtoConfigRoutes(managementRouter, tenant); connectorRoutes(managementRouter, tenant); resourceRoutes(managementRouter, tenant); signInExperiencesRoutes(managementRouter, tenant); diff --git a/packages/core/src/routes/logto-config.test.ts b/packages/core/src/routes/logto-config.test.ts new file mode 100644 index 000000000..4b3dc4229 --- /dev/null +++ b/packages/core/src/routes/logto-config.test.ts @@ -0,0 +1,39 @@ +import type { AdminConsoleData } from '@logto/schemas'; +import { pickDefault } from '@logto/shared/esm'; + +import { mockAdminConsoleData } from '#src/__mocks__/index.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; +import { createRequester } from '#src/utils/test-utils.js'; + +const logtoConfigs = { + getAdminConsoleConfig: async () => ({ value: mockAdminConsoleData }), + updateAdminConsoleConfig: async (data: Partial) => ({ + value: { + ...mockAdminConsoleData, + ...data, + }, + }), +}; + +const settingRoutes = await pickDefault(import('./logto-config.js')); + +describe('configs routes', () => { + const roleRequester = createRequester({ + authedRoutes: settingRoutes, + tenantContext: new MockTenant(undefined, { logtoConfigs }), + }); + + it('GET /configs/admin-console', async () => { + const response = await roleRequester.get('/configs/admin-console'); + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockAdminConsoleData); + }); + + it('PATCH /configs/admin-console', async () => { + const demoChecked = !mockAdminConsoleData.demoChecked; + const response = await roleRequester.patch('/configs/admin-console').send({ demoChecked }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ ...mockAdminConsoleData, demoChecked }); + }); +}); diff --git a/packages/core/src/routes/logto-config.ts b/packages/core/src/routes/logto-config.ts new file mode 100644 index 000000000..c37027bbf --- /dev/null +++ b/packages/core/src/routes/logto-config.ts @@ -0,0 +1,35 @@ +import { adminConsoleDataGuard } from '@logto/schemas'; + +import koaGuard from '#src/middleware/koa-guard.js'; + +import type { AuthedRouter, RouterInitArgs } from './types.js'; + +export default function logtoConfigRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { getAdminConsoleConfig, updateAdminConsoleConfig } = queries.logtoConfigs; + + router.get( + '/configs/admin-console', + koaGuard({ response: adminConsoleDataGuard, status: 200 }), + async (ctx, next) => { + ctx.body = await getAdminConsoleConfig(); + + return next(); + } + ); + + router.patch( + '/configs/admin-console', + koaGuard({ + body: adminConsoleDataGuard.partial(), + response: adminConsoleDataGuard, + status: 200, + }), + async (ctx, next) => { + ctx.body = await updateAdminConsoleConfig(ctx.guard.body); + + return next(); + } + ); +} diff --git a/packages/core/src/routes/setting.test.ts b/packages/core/src/routes/setting.test.ts deleted file mode 100644 index 216b5fb1b..000000000 --- a/packages/core/src/routes/setting.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Setting, CreateSetting } from '@logto/schemas'; -import { pickDefault } from '@logto/shared/esm'; - -import { mockSetting } from '#src/__mocks__/index.js'; -import { MockTenant } from '#src/test-utils/tenant.js'; -import { createRequester } from '#src/utils/test-utils.js'; - -const settings = { - getSetting: async (): Promise => mockSetting, - updateSetting: async (data: Partial): Promise => ({ - ...mockSetting, - ...data, - }), -}; - -const settingRoutes = await pickDefault(import('./setting.js')); - -describe('settings routes', () => { - const roleRequester = createRequester({ - authedRoutes: settingRoutes, - tenantContext: new MockTenant(undefined, { settings }), - }); - - it('GET /settings', async () => { - const response = await roleRequester.get('/settings'); - expect(response.status).toEqual(200); - const { id, ...rest } = mockSetting; - expect(response.body).toEqual(rest); - }); - - it('PATCH /settings', async () => { - const { adminConsole } = mockSetting; - - const response = await roleRequester.patch('/settings').send({ - adminConsole, - }); - - expect(response.status).toEqual(200); - expect(response.body).toEqual({ - tenantId: 'fake_tenant', - adminConsole, - }); - }); -}); diff --git a/packages/core/src/routes/setting.ts b/packages/core/src/routes/setting.ts deleted file mode 100644 index 042bad2cd..000000000 --- a/packages/core/src/routes/setting.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Settings } from '@logto/schemas'; - -import koaGuard from '#src/middleware/koa-guard.js'; - -import type { AuthedRouter, RouterInitArgs } from './types.js'; - -export default function settingRoutes( - ...[router, { queries }]: RouterInitArgs -) { - const { getSetting, updateSetting } = queries.settings; - - router.get('/settings', async (ctx, next) => { - const { id, ...rest } = await getSetting(); - ctx.body = rest; - - return next(); - }); - - router.patch( - '/settings', - koaGuard({ - body: Settings.createGuard.omit({ id: true }).deepPartial(), - }), - async (ctx, next) => { - const { body: partialSettings } = ctx.guard; - const { id, ...rest } = await updateSetting(partialSettings); - ctx.body = rest; - - return next(); - } - ); -} diff --git a/packages/core/src/tenants/Queries.ts b/packages/core/src/tenants/Queries.ts index bc13db007..95507f858 100644 --- a/packages/core/src/tenants/Queries.ts +++ b/packages/core/src/tenants/Queries.ts @@ -5,13 +5,13 @@ import { createApplicationsRolesQueries } from '#src/queries/applications-roles. import { createConnectorQueries } from '#src/queries/connector.js'; import { createCustomPhraseQueries } from '#src/queries/custom-phrase.js'; import { createLogQueries } from '#src/queries/log.js'; +import { createLogtoConfigQueries } from '#src/queries/logto-config.js'; import { createOidcModelInstanceQueries } from '#src/queries/oidc-model-instance.js'; import { createPasscodeQueries } from '#src/queries/passcode.js'; import { createResourceQueries } from '#src/queries/resource.js'; import { createRolesScopesQueries } from '#src/queries/roles-scopes.js'; import { createRolesQueries } from '#src/queries/roles.js'; import { createScopeQueries } from '#src/queries/scope.js'; -import { createSettingQueries } from '#src/queries/setting.js'; import { createSignInExperienceQueries } from '#src/queries/sign-in-experience.js'; import { createUserQueries } from '#src/queries/user.js'; import { createUsersRolesQueries } from '#src/queries/users-roles.js'; @@ -27,7 +27,7 @@ export default class Queries { rolesScopes = createRolesScopesQueries(this.pool); roles = createRolesQueries(this.pool); scopes = createScopeQueries(this.pool); - settings = createSettingQueries(this.pool); + logtoConfigs = createLogtoConfigQueries(this.pool); signInExperiences = createSignInExperienceQueries(this.pool); users = createUserQueries(this.pool); usersRoles = createUsersRolesQueries(this.pool); diff --git a/packages/schemas/alterations/next-1674987042.1-merge-settings-into-configs.ts b/packages/schemas/alterations/next-1674987042.1-merge-settings-into-configs.ts new file mode 100644 index 000000000..f993e1789 --- /dev/null +++ b/packages/schemas/alterations/next-1674987042.1-merge-settings-into-configs.ts @@ -0,0 +1,62 @@ +import { sql } from 'slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + insert into _logto_configs (key, value) + select 'adminConsole', admin_console from settings + where id='default'; + `); + await pool.query(sql` + alter table _logto_configs + add column tenant_id varchar(21) not null default 'default' + references tenants (id) on update cascade on delete cascade; + + create trigger set_tenant_id before insert on _logto_configs + for each row execute procedure set_tenant_id(); + `); + await pool.query(sql` + alter table _logto_configs + alter column tenant_id drop default; + `); + await pool.query(sql`drop table settings cascade;`); + }, + down: async (pool) => { + await pool.query(sql` + create table settings ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + admin_console jsonb not null, + primary key (id) + ); + + create index settings__id + on settings (tenant_id, id); + + create trigger set_tenant_id before insert on settings + for each row execute procedure set_tenant_id(); + `); + + await pool.query(sql` + insert into settings (id, admin_console) + select 'default', value from _logto_configs + where key='adminConsole'; + `); + + await pool.query(sql` + delete from _logto_configs + where key='adminConsole'; + `); + + await pool.query(sql` + alter table _logto_configs + drop column tenant_id, + drop trigger set_tenant_id; + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/alterations/next-1674987042.2-create-systems-table.ts b/packages/schemas/alterations/next-1674987042.2-create-systems-table.ts new file mode 100644 index 000000000..c5bdd5931 --- /dev/null +++ b/packages/schemas/alterations/next-1674987042.2-create-systems-table.ts @@ -0,0 +1,39 @@ +import { sql } from 'slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + create table systems ( + key varchar(256) not null, + value jsonb not null default '{}'::jsonb, + primary key (key) + ); + + alter table _logto_configs rename to logto_configs; + `); + + await pool.query(sql` + insert into systems (key, value) + select key, value from logto_configs + where key='alterationState'; + `); + + await pool.query(sql` + delete from logto_configs + where key='alterationState'; + `); + }, + down: async (pool) => { + await pool.query(sql` + insert into _logto_configs (key, value) + select key, value from systems + where key='alterationState'; + drop table systems; + alter table logto_configs rename to _logto_configs; + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index d9593f616..479bc9d38 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -163,18 +163,6 @@ export enum AppearanceMode { DarkMode = 'dark', } -export const adminConsoleConfigGuard = z.object({ - // Get started challenges - demoChecked: z.boolean(), - applicationCreated: z.boolean(), - signInExperienceCustomized: z.boolean(), - passwordlessConfigured: z.boolean(), - socialSignInConfigured: z.boolean(), - furtherReadingsChecked: z.boolean(), -}); - -export type AdminConsoleConfig = z.infer; - /** * Phrases */ diff --git a/packages/schemas/src/seeds/index.ts b/packages/schemas/src/seeds/index.ts index 63017e9de..bd3603f18 100644 --- a/packages/schemas/src/seeds/index.ts +++ b/packages/schemas/src/seeds/index.ts @@ -1,6 +1,6 @@ export * from './application.js'; export * from './resource.js'; -export * from './setting.js'; +export * from './logto-config.js'; export * from './sign-in-experience.js'; export * from './roles.js'; export * from './scope.js'; diff --git a/packages/schemas/src/seeds/logto-config.ts b/packages/schemas/src/seeds/logto-config.ts new file mode 100644 index 000000000..95da2ea10 --- /dev/null +++ b/packages/schemas/src/seeds/logto-config.ts @@ -0,0 +1,22 @@ +import { CreateLogtoConfig } from '../db-entries/index.js'; +import { AppearanceMode } from '../foundations/index.js'; +import type { AdminConsoleData } from '../types/index.js'; +import { AdminConsoleConfigKey } from '../types/index.js'; + +export const createDefaultAdminConsoleConfig = (): Readonly<{ + key: AdminConsoleConfigKey; + value: AdminConsoleData; +}> => + Object.freeze({ + key: AdminConsoleConfigKey.AdminConsole, + value: { + language: 'en', + appearanceMode: AppearanceMode.SyncWithSystem, + demoChecked: false, + applicationCreated: false, + signInExperienceCustomized: false, + passwordlessConfigured: false, + socialSignInConfigured: false, + furtherReadingsChecked: false, + }, + } satisfies CreateLogtoConfig); diff --git a/packages/schemas/src/seeds/setting.ts b/packages/schemas/src/seeds/setting.ts deleted file mode 100644 index 24d543b49..000000000 --- a/packages/schemas/src/seeds/setting.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { CreateSetting } from '../db-entries/index.js'; -import { AppearanceMode } from '../foundations/index.js'; -import { defaultTenantId } from './tenant.js'; - -export const defaultSettingId = 'default'; - -export const createDefaultSetting = (): Readonly => - Object.freeze({ - tenantId: defaultTenantId, - id: defaultSettingId, - adminConsole: { - language: 'en', - appearanceMode: AppearanceMode.SyncWithSystem, - demoChecked: false, - applicationCreated: false, - signInExperienceCustomized: false, - passwordlessConfigured: false, - socialSignInConfigured: false, - furtherReadingsChecked: false, - }, - }); diff --git a/packages/schemas/src/types/logto-config.ts b/packages/schemas/src/types/logto-config.ts index 580345c01..ff7d46d87 100644 --- a/packages/schemas/src/types/logto-config.ts +++ b/packages/schemas/src/types/logto-config.ts @@ -39,17 +39,48 @@ export const logtoOidcConfigGuard: Readonly<{ [LogtoOidcConfigKey.CookieKeys]: z.string().array(), }); +// Admin console config +export const adminConsoleDataGuard = z.object({ + // Get started challenges + demoChecked: z.boolean(), + applicationCreated: z.boolean(), + signInExperienceCustomized: z.boolean(), + passwordlessConfigured: z.boolean(), + socialSignInConfigured: z.boolean(), + furtherReadingsChecked: z.boolean(), +}); + +export type AdminConsoleData = z.infer; + +export enum AdminConsoleConfigKey { + AdminConsole = 'adminConsole', +} + +export type AdminConsoleConfigType = { + [AdminConsoleConfigKey.AdminConsole]: AdminConsoleData; +}; + +export const adminConsoleConfigGuard: Readonly<{ + [key in AdminConsoleConfigKey]: ZodType; +}> = Object.freeze({ + [AdminConsoleConfigKey.AdminConsole]: adminConsoleDataGuard, +}); + // Summary -export type LogtoConfigKey = AlterationStateKey | LogtoOidcConfigKey; -export type LogtoConfigType = AlterationStateType | LogtoOidcConfigType; -export type LogtoConfigGuard = typeof alterationStateGuard & typeof logtoOidcConfigGuard; +export type LogtoConfigKey = AlterationStateKey | LogtoOidcConfigKey | AdminConsoleConfigKey; +export type LogtoConfigType = AlterationStateType | LogtoOidcConfigType | AdminConsoleConfigType; +export type LogtoConfigGuard = typeof alterationStateGuard & + typeof logtoOidcConfigGuard & + typeof adminConsoleConfigGuard; export const logtoConfigKeys: readonly LogtoConfigKey[] = Object.freeze([ ...Object.values(AlterationStateKey), ...Object.values(LogtoOidcConfigKey), + ...Object.values(AdminConsoleConfigKey), ]); export const logtoConfigGuards: LogtoConfigGuard = Object.freeze({ ...alterationStateGuard, ...logtoOidcConfigGuard, + ...adminConsoleConfigGuard, }); diff --git a/packages/schemas/tables/logto_configs.sql b/packages/schemas/tables/logto_configs.sql new file mode 100644 index 000000000..316df6a59 --- /dev/null +++ b/packages/schemas/tables/logto_configs.sql @@ -0,0 +1,10 @@ +create table logto_configs ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + key varchar(256) not null, + value jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb, + primary key (tenant_id, key) +); + +create trigger set_tenant_id before insert on logto_configs + for each row execute procedure set_tenant_id(); diff --git a/packages/schemas/tables/settings.sql b/packages/schemas/tables/settings.sql deleted file mode 100644 index 731b33ffd..000000000 --- a/packages/schemas/tables/settings.sql +++ /dev/null @@ -1,13 +0,0 @@ -create table settings ( - tenant_id varchar(21) not null - references tenants (id) on update cascade on delete cascade, - id varchar(21) not null, - admin_console jsonb /* @use AdminConsoleConfig */ not null, - primary key (id) -); - -create index settings__id - on settings (tenant_id, id); - -create trigger set_tenant_id before insert on settings - for each row execute procedure set_tenant_id(); diff --git a/packages/schemas/tables/_logto_configs.sql b/packages/schemas/tables/systems.sql similarity index 80% rename from packages/schemas/tables/_logto_configs.sql rename to packages/schemas/tables/systems.sql index 36645672b..62a5b25b1 100644 --- a/packages/schemas/tables/_logto_configs.sql +++ b/packages/schemas/tables/systems.sql @@ -1,4 +1,4 @@ -create table _logto_configs ( +create table systems ( key varchar(256) not null, value jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb, primary key (key)