From c2c3faa248d66fe27fdd486beeb81da4c39e976a Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Sun, 29 Jan 2023 22:20:31 +0800 Subject: [PATCH] refactor!: add systems table for global configs --- .../database/alteration/index.test.ts | 2 +- packages/cli/src/queries/logto-config.ts | 3 +- packages/cli/src/queries/system.test.ts | 59 +++++++++++-------- .../core/src/database/update-where.test.ts | 2 +- packages/core/src/queries/logto-config.ts | 2 +- packages/core/src/routes/logto-config.ts | 6 +- ...87042-drop-settings-and-create-systems.ts} | 45 +++++++++++++- .../next-1674987042.2-create-systems-table.ts | 39 ------------ 8 files changed, 86 insertions(+), 72 deletions(-) rename packages/schemas/alterations/{next-1674987042.1-merge-settings-into-configs.ts => next-1674987042-drop-settings-and-create-systems.ts} (57%) delete mode 100644 packages/schemas/alterations/next-1674987042.2-create-systems-table.ts diff --git a/packages/cli/src/commands/database/alteration/index.test.ts b/packages/cli/src/commands/database/alteration/index.test.ts index 9e99f1bf3..db289c21a 100644 --- a/packages/cli/src/commands/database/alteration/index.test.ts +++ b/packages/cli/src/commands/database/alteration/index.test.ts @@ -22,7 +22,7 @@ await mockEsmWithActual('./utils.js', () => ({ })); const { getCurrentDatabaseAlterationTimestamp } = await mockEsmWithActual( - '../../../queries/logto-config.js', + '../../../queries/system.js', () => ({ getCurrentDatabaseAlterationTimestamp: jest.fn(), }) diff --git a/packages/cli/src/queries/logto-config.ts b/packages/cli/src/queries/logto-config.ts index 2de144cde..61dc9978d 100644 --- a/packages/cli/src/queries/logto-config.ts +++ b/packages/cli/src/queries/logto-config.ts @@ -31,6 +31,7 @@ export const updateValueByKey = async ( sql` insert into ${table} (${fields.key}, ${fields.value}) values (${key}, ${sql.jsonb(value)}) - on conflict (${fields.key}) do update set ${fields.value}=excluded.${fields.value} + on conflict (${fields.tenantId}, ${fields.key}) + do update set ${fields.value}=excluded.${fields.value} ` ); diff --git a/packages/cli/src/queries/system.test.ts b/packages/cli/src/queries/system.test.ts index b1a53a5d0..be41df767 100644 --- a/packages/cli/src/queries/system.test.ts +++ b/packages/cli/src/queries/system.test.ts @@ -17,10 +17,11 @@ const pool = createMockPool({ }); const { table, fields } = convertToIdentifiers(Systems); const timestamp = 1_663_923_776; +const systemsTableExists = async () => createMockQueryResult([{ regclass: true }]); describe('getCurrentDatabaseAlterationTimestamp()', () => { it('returns 0 if query failed (table not found)', async () => { - mockQuery.mockRejectedValueOnce({ code: '42P01' }); + mockQuery.mockImplementationOnce(systemsTableExists).mockRejectedValueOnce({ code: '42P01' }); await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0); }); @@ -30,12 +31,14 @@ describe('getCurrentDatabaseAlterationTimestamp()', () => { select * from ${table} where ${fields.key}=$1 `; - mockQuery.mockImplementationOnce(async (sql, values) => { - expectSqlAssert(sql, expectSql.sql); - expect(values).toEqual([AlterationStateKey.AlterationState]); + mockQuery + .mockImplementationOnce(systemsTableExists) + .mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([AlterationStateKey.AlterationState]); - return createMockQueryResult([]); - }); + return createMockQueryResult([]); + }); await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0); }); @@ -45,12 +48,14 @@ describe('getCurrentDatabaseAlterationTimestamp()', () => { select * from ${table} where ${fields.key}=$1 `; - mockQuery.mockImplementationOnce(async (sql, values) => { - expectSqlAssert(sql, expectSql.sql); - expect(values).toEqual([AlterationStateKey.AlterationState]); + mockQuery + .mockImplementationOnce(systemsTableExists) + .mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([AlterationStateKey.AlterationState]); - return createMockQueryResult([{ value: 'some_value' }]); - }); + return createMockQueryResult([{ value: 'some_value' }]); + }); await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0); }); @@ -60,13 +65,15 @@ describe('getCurrentDatabaseAlterationTimestamp()', () => { select * from ${table} where ${fields.key}=$1 `; - mockQuery.mockImplementationOnce(async (sql, values) => { - expectSqlAssert(sql, expectSql.sql); - expect(values).toEqual([AlterationStateKey.AlterationState]); + mockQuery + .mockImplementationOnce(systemsTableExists) + .mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([AlterationStateKey.AlterationState]); - // @ts-expect-error createMockQueryResult doesn't support jsonb - return createMockQueryResult([{ value: { timestamp, updatedAt: 'now' } }]); - }); + // @ts-expect-error createMockQueryResult doesn't support jsonb + return createMockQueryResult([{ value: { timestamp, updatedAt: 'now' } }]); + }); await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toEqual(timestamp); }); @@ -90,15 +97,17 @@ describe('updateDatabaseTimestamp()', () => { }); it('sends upsert sql with timestamp and updatedAt', async () => { - mockQuery.mockImplementationOnce(async (sql, values) => { - expectSqlAssert(sql, expectSql.sql); - expect(values).toEqual([ - AlterationStateKey.AlterationState, - JSON.stringify({ timestamp, updatedAt }), - ]); + mockQuery + .mockImplementationOnce(systemsTableExists) + .mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([ + AlterationStateKey.AlterationState, + JSON.stringify({ timestamp, updatedAt }), + ]); - return createMockQueryResult([]); - }); + return createMockQueryResult([]); + }); await updateDatabaseTimestamp(pool, timestamp); }); diff --git a/packages/core/src/database/update-where.test.ts b/packages/core/src/database/update-where.test.ts index e9397ed2d..0943fe456 100644 --- a/packages/core/src/database/update-where.test.ts +++ b/packages/core/src/database/update-where.test.ts @@ -52,7 +52,7 @@ describe('buildUpdateWhere()', () => { it('return query with jsonb partial update if input data type is jsonb', async () => { const pool = createTestPool( - 'update "applications"\nset\n"custom_client_metadata"=\ncoalesce("custom_client_metadata",\'{}\'::jsonb)|| $1\nwhere "id"=$2\nreturning *', + 'update "applications"\nset\n"custom_client_metadata"=\ncoalesce("custom_client_metadata",\'{}\'::jsonb) || $1\nwhere "id"=$2\nreturning *', (_, [customClientMetadata, id]) => ({ id: String(id), customClientMetadata: String(customClientMetadata), diff --git a/packages/core/src/queries/logto-config.ts b/packages/core/src/queries/logto-config.ts index 3f92492b1..110293a1b 100644 --- a/packages/core/src/queries/logto-config.ts +++ b/packages/core/src/queries/logto-config.ts @@ -16,7 +16,7 @@ export const createLogtoConfigQueries = (pool: CommonQueryMethods) => { const updateAdminConsoleConfig = async (value: Partial) => pool.one>(sql` update ${table} - set ${fields.value}=coalesce(${fields.value},'{}'::jsonb) || ${sql.jsonb(value)} + set ${fields.value} = coalesce(${fields.value},'{}'::jsonb) || ${sql.jsonb(value)} where ${fields.key} = ${AdminConsoleConfigKey.AdminConsole} returning ${fields.value} `); diff --git a/packages/core/src/routes/logto-config.ts b/packages/core/src/routes/logto-config.ts index c37027bbf..7f6bbc3b0 100644 --- a/packages/core/src/routes/logto-config.ts +++ b/packages/core/src/routes/logto-config.ts @@ -13,7 +13,8 @@ export default function logtoConfigRoutes( '/configs/admin-console', koaGuard({ response: adminConsoleDataGuard, status: 200 }), async (ctx, next) => { - ctx.body = await getAdminConsoleConfig(); + const { value } = await getAdminConsoleConfig(); + ctx.body = value; return next(); } @@ -27,7 +28,8 @@ export default function logtoConfigRoutes( status: 200, }), async (ctx, next) => { - ctx.body = await updateAdminConsoleConfig(ctx.guard.body); + const { value } = await updateAdminConsoleConfig(ctx.guard.body); + ctx.body = value; return next(); } diff --git a/packages/schemas/alterations/next-1674987042.1-merge-settings-into-configs.ts b/packages/schemas/alterations/next-1674987042-drop-settings-and-create-systems.ts similarity index 57% rename from packages/schemas/alterations/next-1674987042.1-merge-settings-into-configs.ts rename to packages/schemas/alterations/next-1674987042-drop-settings-and-create-systems.ts index f993e1789..6fa64e66e 100644 --- a/packages/schemas/alterations/next-1674987042.1-merge-settings-into-configs.ts +++ b/packages/schemas/alterations/next-1674987042-drop-settings-and-create-systems.ts @@ -4,6 +4,7 @@ import type { AlterationScript } from '../lib/types/alteration.js'; const alteration: AlterationScript = { up: async (pool) => { + /* Drop settings table */ await pool.query(sql` insert into _logto_configs (key, value) select 'adminConsole', admin_console from settings @@ -22,8 +23,45 @@ const alteration: AlterationScript = { alter column tenant_id drop default; `); await pool.query(sql`drop table settings cascade;`); + + /* Create systems table */ + 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; + alter table logto_configs + drop constraint _logto_configs_pkey, + add primary key (tenant_id, key); + alter table logto_configs + rename constraint _logto_configs_tenant_id_fkey to logto_configs_tenant_id_fkey; + `); + + 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) => { + /* Drop systems table */ + 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; + `); + + /* Restore settings table */ await pool.query(sql` create table settings ( tenant_id varchar(21) not null @@ -52,9 +90,12 @@ const alteration: AlterationScript = { `); await pool.query(sql` + drop trigger set_tenant_id on _logto_configs; + alter table _logto_configs - drop column tenant_id, - drop trigger set_tenant_id; + drop constraint logto_configs_pkey, + drop column tenant_id cascade, + add primary key (key); `); }, }; diff --git a/packages/schemas/alterations/next-1674987042.2-create-systems-table.ts b/packages/schemas/alterations/next-1674987042.2-create-systems-table.ts deleted file mode 100644 index c5bdd5931..000000000 --- a/packages/schemas/alterations/next-1674987042.2-create-systems-table.ts +++ /dev/null @@ -1,39 +0,0 @@ -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;