From 8fc5b78def666c4e2e43c327d35fb59d8958e8a1 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Tue, 9 May 2023 09:25:45 +0800 Subject: [PATCH] refactor(schemas): update hook schema (#3788) --- packages/core/src/libraries/hook.test.ts | 6 +- packages/core/src/libraries/hook.ts | 4 +- .../src/tests/api/hooks.test.ts | 1 - .../next-1683292832-update-hooks.ts | 105 ++++++++++++++++++ .../schemas/src/foundations/jsonb-types.ts | 22 ++-- packages/schemas/tables/hooks.sql | 24 ++-- 6 files changed, 133 insertions(+), 29 deletions(-) create mode 100644 packages/schemas/alterations/next-1683292832-update-hooks.ts diff --git a/packages/core/src/libraries/hook.test.ts b/packages/core/src/libraries/hook.test.ts index a516ae02b..723dde4c7 100644 --- a/packages/core/src/libraries/hook.test.ts +++ b/packages/core/src/libraries/hook.test.ts @@ -21,8 +21,12 @@ const url = 'https://logto.gg'; const hook: Hook = { tenantId: 'bar', id: 'foo', + name: 'hook_name', event: HookEvent.PostSignIn, - config: { headers: { bar: 'baz' }, url, retries: 3 }, + events: [HookEvent.PostSignIn], + signingKey: 'signing_key', + enabled: true, + config: { headers: { bar: 'baz' }, url }, createdAt: Date.now() / 1000, }; diff --git a/packages/core/src/libraries/hook.ts b/packages/core/src/libraries/hook.ts index 27d5d5248..fbee0cf53 100644 --- a/packages/core/src/libraries/hook.ts +++ b/packages/core/src/libraries/hook.ts @@ -78,7 +78,7 @@ export const createHookLibrary = (queries: Queries) => { } satisfies Omit; await Promise.all( - rows.map(async ({ config: { url, headers, retries }, id }) => { + rows.map(async ({ config: { url, headers }, id }) => { consoleLog.info(`\tTriggering hook ${id} due to ${hookEvent} event`); const json: HookEventPayload = { hookId: id, ...payload }; const logEntry = new LogEntry(`TriggerHook.${hookEvent}`); @@ -90,7 +90,7 @@ export const createHookLibrary = (queries: Queries) => { .post(url, { headers: { 'user-agent': 'Logto (https://logto.io)', ...headers }, json, - retry: { limit: retries }, + retry: { limit: 3 }, timeout: { request: 10_000 }, }) .then(async (response) => { diff --git a/packages/integration-tests/src/tests/api/hooks.test.ts b/packages/integration-tests/src/tests/api/hooks.test.ts index 5e0981fbd..0b00e7a92 100644 --- a/packages/integration-tests/src/tests/api/hooks.test.ts +++ b/packages/integration-tests/src/tests/api/hooks.test.ts @@ -13,7 +13,6 @@ const createPayload = (event: HookEvent, url = 'not_work_url'): Partial => config: { url, headers: { foo: 'bar' }, - retries: 3, }, }); diff --git a/packages/schemas/alterations/next-1683292832-update-hooks.ts b/packages/schemas/alterations/next-1683292832-update-hooks.ts new file mode 100644 index 000000000..62da0404f --- /dev/null +++ b/packages/schemas/alterations/next-1683292832-update-hooks.ts @@ -0,0 +1,105 @@ +import { generateStandardId } from '@logto/shared'; +import { sql } from 'slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +enum HookEvent { + PostRegister = 'PostRegister', + PostSignIn = 'PostSignIn', + PostResetPassword = 'PostResetPassword', +} + +type HookConfig = { + url: string; + headers?: Record; + retries?: number; +}; + +type Hook = { + tenantId: string; + id: string; + name: string; + event: HookEvent | null; + events: HookEvent[]; + config: HookConfig; + signingKey: string; + enabled: boolean; + createdAt: number; +}; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + alter table hooks + add column name varchar(256) not null default '', + add column events jsonb not null default '[]'::jsonb, + add column signing_key varchar(64) not null default '', + add column enabled boolean not null default true, + alter column event drop not null; + drop index hooks__event; + `); + }, + down: async (pool) => { + await pool.query(sql` + delete from hooks where enabled = false; + `); + + const { rows: hooks } = await pool.query(sql` + select * from hooks; + `); + + /* eslint-disable no-await-in-loop */ + for (const { id, tenantId, events, config } of hooks) { + const { retries, ...rest } = config; + + const updatedConfig = { + ...rest, + retries: retries ?? 3, + }; + + if (events.length === 0) { + await pool.query(sql` + update hooks + set config = ${JSON.stringify(updatedConfig)} + where id = ${id} and tenant_id = ${tenantId}; + `); + + continue; + } + + for (const [index, event] of events.entries()) { + if (index === 0) { + await pool.query(sql` + update hooks + set event = ${event}, + config = ${JSON.stringify(updatedConfig)} + where id = ${id} and tenant_id = ${tenantId}; + `); + + continue; + } + + // Create new hook when there are multiple events + const hookId = generateStandardId(); + + await pool.query(sql` + insert into hooks (id, tenant_id, event, config) + values (${hookId}, ${tenantId}, ${event}, ${JSON.stringify(updatedConfig)}); + `); + } + } + /* eslint-enable no-await-in-loop */ + + await pool.query(sql` + alter table hooks + alter column event set not null, + drop column name, + drop column events, + drop column signing_key, + drop column enabled; + create index hooks__event on hooks (tenant_id, event); + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index 1dceae5cb..109756d3d 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -200,23 +200,17 @@ export enum HookEvent { export const hookEventGuard: z.ZodType = z.nativeEnum(HookEvent); -export type HookConfig = { +export const hookEventsGuard = hookEventGuard.array(); + +export type HookEvents = z.infer; + +export const hookConfigGuard = z.object({ /** We don't need `type` since v1 only has web hook */ // type: 'web'; /** Method fixed to `POST` */ - url: string; - /** Additional headers that attach to the request */ - headers?: Record; - /** - * Retry times when hook response status >= 500. - * - * Must be less than or equal to `3`. Use `0` to disable retry. - **/ - retries: number; -}; - -export const hookConfigGuard: z.ZodType = z.object({ url: z.string(), + /** Additional headers that attach to the request */ headers: z.record(z.string()).optional(), - retries: z.number().gte(0).lte(3), }); + +export type HookConfig = z.infer; diff --git a/packages/schemas/tables/hooks.sql b/packages/schemas/tables/hooks.sql index 70122951b..c3e62a87e 100644 --- a/packages/schemas/tables/hooks.sql +++ b/packages/schemas/tables/hooks.sql @@ -1,13 +1,15 @@ create table hooks ( - tenant_id varchar(21) not null - references tenants (id) on update cascade on delete cascade, - id varchar(21) not null, - event varchar(128) /* @use HookEvent */ not null, - config jsonb /* @use HookConfig */ not null, - created_at timestamptz not null default(now()), - primary key (id) - ); + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + name varchar(256) not null default '', + event varchar(128) /* @use HookEvent */, + events jsonb /* @use HookEvents */ not null default '[]'::jsonb, + config jsonb /* @use HookConfig */ not null, + signing_key varchar(64) not null default '', + enabled boolean not null default true, + created_at timestamptz not null default(now()), + primary key (id) +); - create index hooks__id on hooks (tenant_id, id); - - create index hooks__event on hooks (tenant_id, event); +create index hooks__id on hooks (tenant_id, id);