0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor!: add systems table for global configs

This commit is contained in:
Gao Sun 2023-01-29 22:20:31 +08:00
parent d0399eb8a4
commit c2c3faa248
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
8 changed files with 86 additions and 72 deletions

View file

@ -22,7 +22,7 @@ await mockEsmWithActual('./utils.js', () => ({
}));
const { getCurrentDatabaseAlterationTimestamp } = await mockEsmWithActual(
'../../../queries/logto-config.js',
'../../../queries/system.js',
() => ({
getCurrentDatabaseAlterationTimestamp: jest.fn(),
})

View file

@ -31,6 +31,7 @@ export const updateValueByKey = async <T extends LogtoConfigKey>(
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}
`
);

View file

@ -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);
});

View file

@ -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),

View file

@ -16,7 +16,7 @@ export const createLogtoConfigQueries = (pool: CommonQueryMethods) => {
const updateAdminConsoleConfig = async (value: Partial<AdminConsoleData>) =>
pool.one<Record<string, unknown>>(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}
`);

View file

@ -13,7 +13,8 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
'/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<T extends AuthedRouter>(
status: 200,
}),
async (ctx, next) => {
ctx.body = await updateAdminConsoleConfig(ctx.guard.body);
const { value } = await updateAdminConsoleConfig(ctx.guard.body);
ctx.body = value;
return next();
}

View file

@ -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);
`);
},
};

View file

@ -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;