mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
Merge pull request #2891 from logto-io/gao-log-5124-core-library-factory-hook
refactor(core): migrate hook library to factory mode
This commit is contained in:
commit
1f293292b8
15 changed files with 180 additions and 139 deletions
|
@ -21,7 +21,7 @@
|
|||
"start": "NODE_ENV=production node build/index.js",
|
||||
"test:only": "NODE_OPTIONS=--experimental-vm-modules jest",
|
||||
"test": "pnpm build:test && pnpm test:only",
|
||||
"test:ci": "pnpm test:only --coverage --silent --maxWorkers=50%",
|
||||
"test:ci": "pnpm test:only --coverage --silent --maxWorkers=75%",
|
||||
"test:report": "codecov -F core"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -4,54 +4,64 @@ import { createMockUtils } from '@logto/shared/esm';
|
|||
import type { InferModelType } from '@withtyped/server';
|
||||
import { got } from 'got';
|
||||
|
||||
import modelRouters from '#src/model-routers/index.js';
|
||||
import { MockQueryClient } from '#src/test-utils/query-client.js';
|
||||
import type { ModelRouters } from '#src/model-routers/index.js';
|
||||
|
||||
import type { Interaction } from './hook.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm, mockEsmDefault } = createMockUtils(jest);
|
||||
const { mockEsmDefault, mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const nanoIdMock = 'mockId';
|
||||
await mockEsmWithActual('@logto/core-kit', () => ({
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
buildIdGenerator: () => () => nanoIdMock,
|
||||
generateStandardId: () => nanoIdMock,
|
||||
}));
|
||||
|
||||
const { createModelRouters } = await import('#src/model-routers/index.js');
|
||||
const { MockQueryClient } = await import('#src/test-utils/query-client.js');
|
||||
const { MockQueries } = await import('#src/test-utils/tenant.js');
|
||||
|
||||
const queryClient = new MockQueryClient();
|
||||
const queryFunction = jest.fn();
|
||||
|
||||
const url = 'https://logto.gg';
|
||||
const hook: InferModelType<typeof modelRouters.hook.model> = {
|
||||
const hook: InferModelType<ModelRouters['hook']['model']> = {
|
||||
id: 'foo',
|
||||
event: HookEvent.PostSignIn,
|
||||
config: { headers: { bar: 'baz' }, url, retries: 3 },
|
||||
createdAt: new Date(),
|
||||
};
|
||||
const readAll = jest
|
||||
.spyOn(modelRouters.hook.client, 'readAll')
|
||||
.mockResolvedValue({ rows: [hook], rowCount: 1 });
|
||||
|
||||
const post = jest
|
||||
.spyOn(got, 'post')
|
||||
// @ts-expect-error for testing
|
||||
.mockImplementation(jest.fn(async () => ({ statusCode: 200, body: '{"message":"ok"}' })));
|
||||
|
||||
const nanoIdMock = 'mockId';
|
||||
mockEsm('@logto/core-kit', () => ({
|
||||
generateStandardId: () => nanoIdMock,
|
||||
}));
|
||||
|
||||
const { insertLog } = mockEsm('#src/queries/log.js', () => ({
|
||||
insertLog: jest.fn(),
|
||||
}));
|
||||
|
||||
mockEsm('#src/queries/user.js', () => ({
|
||||
findUserById: () => ({ id: 'user_id', username: 'user', extraField: 'not_ok' }),
|
||||
}));
|
||||
mockEsm('#src/queries/application.js', () => ({
|
||||
findApplicationById: () => ({ id: 'app_id', extraField: 'not_ok' }),
|
||||
}));
|
||||
const insertLog = jest.fn();
|
||||
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
mockEsmDefault('#src/env-set/create-query-client.js', () => () => queryClient);
|
||||
jest.spyOn(queryClient, 'query').mockImplementation(queryFunction);
|
||||
|
||||
const { triggerInteractionHooksIfNeeded } = await import('./hook.js');
|
||||
const { createHookLibrary } = await import('./hook.js');
|
||||
const modelRouters = createModelRouters(new MockQueryClient());
|
||||
const { triggerInteractionHooksIfNeeded } = createHookLibrary(
|
||||
new MockQueries({
|
||||
// @ts-expect-error
|
||||
users: { findUserById: () => ({ id: 'user_id', username: 'user', extraField: 'not_ok' }) },
|
||||
applications: {
|
||||
// @ts-expect-error
|
||||
findApplicationById: async () => ({ id: 'app_id', extraField: 'not_ok' }),
|
||||
},
|
||||
logs: { insertLog },
|
||||
}),
|
||||
modelRouters
|
||||
);
|
||||
|
||||
const readAll = jest
|
||||
.spyOn(modelRouters.hook.client, 'readAll')
|
||||
.mockResolvedValue({ rows: [hook], rowCount: 1 });
|
||||
|
||||
describe('triggerInteractionHooksIfNeeded()', () => {
|
||||
afterEach(() => {
|
||||
|
|
|
@ -8,10 +8,8 @@ import { got, HTTPError } from 'got';
|
|||
import type Provider from 'oidc-provider';
|
||||
|
||||
import { LogEntry } from '#src/middleware/koa-audit-log.js';
|
||||
import modelRouters from '#src/model-routers/index.js';
|
||||
import { findApplicationById } from '#src/queries/application.js';
|
||||
import { insertLog } from '#src/queries/log.js';
|
||||
import { findUserById } from '#src/queries/user.js';
|
||||
import type { ModelRouters } from '#src/model-routers/index.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
||||
const parseResponse = ({ statusCode, body }: Response) => ({
|
||||
statusCode,
|
||||
|
@ -27,80 +25,90 @@ const eventToHook: Record<InteractionEvent, HookEvent> = {
|
|||
|
||||
export type Interaction = Awaited<ReturnType<Provider['interactionDetails']>>;
|
||||
|
||||
export const triggerInteractionHooksIfNeeded = async (
|
||||
event: InteractionEvent,
|
||||
details?: Interaction,
|
||||
userAgent?: string
|
||||
) => {
|
||||
const userId = details?.result?.login?.accountId;
|
||||
const sessionId = details?.jti;
|
||||
const applicationId = details?.params.client_id;
|
||||
export const createHookLibrary = (queries: Queries, { hook }: ModelRouters) => {
|
||||
const {
|
||||
applications: { findApplicationById },
|
||||
logs: { insertLog },
|
||||
users: { findUserById },
|
||||
} = queries;
|
||||
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
const triggerInteractionHooksIfNeeded = async (
|
||||
event: InteractionEvent,
|
||||
details?: Interaction,
|
||||
userAgent?: string
|
||||
) => {
|
||||
const userId = details?.result?.login?.accountId;
|
||||
const sessionId = details?.jti;
|
||||
const applicationId = details?.params.client_id;
|
||||
|
||||
const hookEvent = eventToHook[event];
|
||||
const { rows } = await modelRouters.hook.client.readAll();
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [user, application] = await Promise.all([
|
||||
trySafe(findUserById(userId)),
|
||||
trySafe(async () =>
|
||||
conditional(typeof applicationId === 'string' && (await findApplicationById(applicationId)))
|
||||
),
|
||||
]);
|
||||
const hookEvent = eventToHook[event];
|
||||
const { rows } = await hook.client.readAll();
|
||||
|
||||
const payload = {
|
||||
event: hookEvent,
|
||||
interactionEvent: event,
|
||||
createdAt: new Date().toISOString(),
|
||||
sessionId,
|
||||
userAgent,
|
||||
userId,
|
||||
user: user && pick(user, ...userInfoSelectFields),
|
||||
application: application && pick(application, 'id', 'type', 'name', 'description'),
|
||||
} satisfies Omit<HookEventPayload, 'hookId'>;
|
||||
const [user, application] = await Promise.all([
|
||||
trySafe(findUserById(userId)),
|
||||
trySafe(async () =>
|
||||
conditional(typeof applicationId === 'string' && (await findApplicationById(applicationId)))
|
||||
),
|
||||
]);
|
||||
|
||||
await Promise.all(
|
||||
rows
|
||||
.filter(({ event }) => event === hookEvent)
|
||||
.map(async ({ config: { url, headers, retries }, id }) => {
|
||||
console.log(`\tTriggering hook ${id} due to ${hookEvent} event`);
|
||||
const json: HookEventPayload = { hookId: id, ...payload };
|
||||
const logEntry = new LogEntry(`TriggerHook.${hookEvent}`);
|
||||
const payload = {
|
||||
event: hookEvent,
|
||||
interactionEvent: event,
|
||||
createdAt: new Date().toISOString(),
|
||||
sessionId,
|
||||
userAgent,
|
||||
userId,
|
||||
user: user && pick(user, ...userInfoSelectFields),
|
||||
application: application && pick(application, 'id', 'type', 'name', 'description'),
|
||||
} satisfies Omit<HookEventPayload, 'hookId'>;
|
||||
|
||||
logEntry.append({ json, hookId: id });
|
||||
await Promise.all(
|
||||
rows
|
||||
.filter(({ event }) => event === hookEvent)
|
||||
.map(async ({ config: { url, headers, retries }, id }) => {
|
||||
console.log(`\tTriggering hook ${id} due to ${hookEvent} event`);
|
||||
const json: HookEventPayload = { hookId: id, ...payload };
|
||||
const logEntry = new LogEntry(`TriggerHook.${hookEvent}`);
|
||||
|
||||
// Trigger web hook and log response
|
||||
await got
|
||||
.post(url, {
|
||||
headers: { 'user-agent': 'Logto (https://logto.io)', ...headers },
|
||||
json,
|
||||
retry: { limit: retries },
|
||||
timeout: { request: 10_000 },
|
||||
})
|
||||
.then(async (response) => {
|
||||
logEntry.append({
|
||||
response: parseResponse(response),
|
||||
});
|
||||
})
|
||||
.catch(async (error) => {
|
||||
logEntry.append({
|
||||
result: LogResult.Error,
|
||||
response: conditional(error instanceof HTTPError && parseResponse(error.response)),
|
||||
error: conditional(error instanceof Error && String(error)),
|
||||
logEntry.append({ json, hookId: id });
|
||||
|
||||
// Trigger web hook and log response
|
||||
await got
|
||||
.post(url, {
|
||||
headers: { 'user-agent': 'Logto (https://logto.io)', ...headers },
|
||||
json,
|
||||
retry: { limit: retries },
|
||||
timeout: { request: 10_000 },
|
||||
})
|
||||
.then(async (response) => {
|
||||
logEntry.append({
|
||||
response: parseResponse(response),
|
||||
});
|
||||
})
|
||||
.catch(async (error) => {
|
||||
logEntry.append({
|
||||
result: LogResult.Error,
|
||||
response: conditional(error instanceof HTTPError && parseResponse(error.response)),
|
||||
error: conditional(error instanceof Error && String(error)),
|
||||
});
|
||||
});
|
||||
|
||||
console.log(
|
||||
`\tHook ${id} ${logEntry.payload.result === LogResult.Success ? 'succeeded' : 'failed'}`
|
||||
);
|
||||
|
||||
await insertLog({
|
||||
id: generateStandardId(),
|
||||
key: logEntry.key,
|
||||
payload: logEntry.payload,
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
console.log(
|
||||
`\tHook ${id} ${logEntry.payload.result === LogResult.Success ? 'succeeded' : 'failed'}`
|
||||
);
|
||||
|
||||
await insertLog({
|
||||
id: generateStandardId(),
|
||||
key: logEntry.key,
|
||||
payload: logEntry.payload,
|
||||
});
|
||||
})
|
||||
);
|
||||
return { triggerInteractionHooksIfNeeded };
|
||||
};
|
||||
|
|
|
@ -3,24 +3,26 @@ import { LogResult } from '@logto/schemas';
|
|||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
|
||||
|
||||
import type { WithLogContext, LogPayload } from './koa-audit-log.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
||||
const { insertLog } = mockEsm('#src/queries/log.js', () => ({
|
||||
insertLog: jest.fn(),
|
||||
}));
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const nanoIdMock = 'mockId';
|
||||
mockEsm('@logto/core-kit', () => ({
|
||||
await mockEsmWithActual('@logto/core-kit', () => ({
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
buildIdGenerator: () => () => nanoIdMock,
|
||||
generateStandardId: () => nanoIdMock,
|
||||
}));
|
||||
|
||||
const { default: RequestError } = await import('#src/errors/RequestError/index.js');
|
||||
const { MockQueries } = await import('#src/test-utils/tenant.js');
|
||||
const { createContextWithRouteParameters } = await import('#src/utils/test-utils.js');
|
||||
|
||||
const insertLog = jest.fn();
|
||||
const queries = new MockQueries({ logs: { insertLog } });
|
||||
|
||||
const koaLog = await pickDefault(import('./koa-audit-log.js'));
|
||||
|
||||
describe('koaAuditLog middleware', () => {
|
||||
|
@ -51,7 +53,7 @@ describe('koaAuditLog middleware', () => {
|
|||
log.append(mockPayload);
|
||||
log.append(additionalMockPayload);
|
||||
};
|
||||
await koaLog()(ctx, next);
|
||||
await koaLog(queries)(ctx, next);
|
||||
|
||||
expect(insertLog).toBeCalledWith({
|
||||
id: nanoIdMock,
|
||||
|
@ -82,7 +84,7 @@ describe('koaAuditLog middleware', () => {
|
|||
const log2 = ctx.createLog(logKey);
|
||||
log2.append(mockPayload);
|
||||
};
|
||||
await koaLog()(ctx, next);
|
||||
await koaLog(queries)(ctx, next);
|
||||
|
||||
const basePayload = {
|
||||
...mockPayload,
|
||||
|
@ -116,7 +118,7 @@ describe('koaAuditLog middleware', () => {
|
|||
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping, @typescript-eslint/no-empty-function
|
||||
const next = async () => {};
|
||||
await koaLog()(ctx, next);
|
||||
await koaLog(queries)(ctx, next);
|
||||
expect(insertLog).not.toBeCalled();
|
||||
});
|
||||
|
||||
|
@ -136,7 +138,7 @@ describe('koaAuditLog middleware', () => {
|
|||
log.append(mockPayload);
|
||||
throw error;
|
||||
};
|
||||
await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
|
||||
await expect(koaLog(queries)(ctx, next)).rejects.toMatchError(error);
|
||||
|
||||
expect(insertLog).toBeCalledWith({
|
||||
id: nanoIdMock,
|
||||
|
@ -172,7 +174,7 @@ describe('koaAuditLog middleware', () => {
|
|||
log2.append(mockPayload);
|
||||
throw error;
|
||||
};
|
||||
await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
|
||||
await expect(koaLog(queries)(ctx, next)).rejects.toMatchError(error);
|
||||
|
||||
expect(insertLog).toHaveBeenCalledTimes(2);
|
||||
expect(insertLog).toBeCalledWith({
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { Context, MiddlewareType } from 'koa';
|
|||
import type { IRouterParamContext } from 'koa-router';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { insertLog } from '#src/queries/log.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
||||
const removeUndefinedKeys = (object: Record<string, unknown>) =>
|
||||
Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined));
|
||||
|
@ -93,11 +93,9 @@ export type WithLogContext<ContextT extends IRouterParamContext = IRouterParamCo
|
|||
* @see {@link LogKey} for all available log keys, and {@link LogResult} for result enums.
|
||||
* @see {@link LogContextPayload} for the basic type suggestion of log data.
|
||||
*/
|
||||
export default function koaAuditLog<
|
||||
StateT,
|
||||
ContextT extends IRouterParamContext,
|
||||
ResponseBodyT
|
||||
>(): MiddlewareType<StateT, WithLogContext<ContextT>, ResponseBodyT> {
|
||||
export default function koaAuditLog<StateT, ContextT extends IRouterParamContext, ResponseBodyT>({
|
||||
logs: { insertLog },
|
||||
}: Queries): MiddlewareType<StateT, WithLogContext<ContextT>, ResponseBodyT> {
|
||||
return async (ctx, next) => {
|
||||
const entries: LogEntry[] = [];
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Hooks } from '@logto/schemas/models';
|
||||
import { createModelRouter } from '@withtyped/postgres';
|
||||
import type { QueryClient } from '@withtyped/server';
|
||||
|
||||
import envSet from '#src/env-set/index.js';
|
||||
export type ModelRouters = ReturnType<typeof createModelRouters>;
|
||||
|
||||
const modelRouters = {
|
||||
hook: createModelRouter(Hooks, envSet.queryClient).withCrud(),
|
||||
};
|
||||
|
||||
export default modelRouters;
|
||||
export const createModelRouters = (queryClient: QueryClient) => ({
|
||||
hook: createModelRouter(Hooks, queryClient).withCrud(),
|
||||
});
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { MockQueries } from '#src/test-utils/tenant.js';
|
||||
|
||||
import initOidc from './init.js';
|
||||
|
||||
describe('oidc provider init', () => {
|
||||
it('init should not throw', async () => {
|
||||
expect(() => initOidc()).not.toThrow();
|
||||
expect(() => initOidc(new MockQueries())).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,10 +13,8 @@ import { addOidcEventListeners } from '#src/event-listeners/index.js';
|
|||
import koaAuditLog from '#src/middleware/koa-audit-log.js';
|
||||
import postgresAdapter from '#src/oidc/adapter.js';
|
||||
import { isOriginAllowed, validateCustomClientMetadata } from '#src/oidc/utils.js';
|
||||
import { findApplicationById } from '#src/queries/application.js';
|
||||
import { findResourceByIndicator } from '#src/queries/resource.js';
|
||||
import { findUserById } from '#src/queries/user.js';
|
||||
import { routes } from '#src/routes/consts.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import { claimToUserKey, getUserClaims } from './scope.js';
|
||||
|
@ -24,7 +22,12 @@ import { claimToUserKey, getUserClaims } from './scope.js';
|
|||
// Temporarily removed 'EdDSA' since it's not supported by browser yet
|
||||
const supportedSigningAlgs = Object.freeze(['RS256', 'PS256', 'ES256', 'ES384', 'ES512'] as const);
|
||||
|
||||
export default function initOidc(): Provider {
|
||||
export default function initOidc(queries: Queries): Provider {
|
||||
const {
|
||||
applications: { findApplicationById },
|
||||
resources: { findResourceByIndicator },
|
||||
users: { findUserById },
|
||||
} = queries;
|
||||
const {
|
||||
issuer,
|
||||
cookieKeys,
|
||||
|
@ -208,7 +211,7 @@ export default function initOidc(): Provider {
|
|||
addOidcEventListeners(oidc);
|
||||
|
||||
// Provide audit log context for event listeners
|
||||
oidc.use(koaAuditLog());
|
||||
oidc.use(koaAuditLog(queries));
|
||||
|
||||
return oidc;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import type { MiddlewareType } from 'koa';
|
|||
import koaBody from 'koa-body';
|
||||
|
||||
import LogtoRequestError from '#src/errors/RequestError/index.js';
|
||||
import modelRouters from '#src/model-routers/index.js';
|
||||
|
||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||
|
||||
|
@ -23,6 +22,8 @@ const errorHandler: MiddlewareType = async (_, next) => {
|
|||
}
|
||||
};
|
||||
|
||||
export default function hookRoutes<T extends AuthedRouter>(...[router]: RouterInitArgs<T>) {
|
||||
export default function hookRoutes<T extends AuthedRouter>(
|
||||
...[router, { modelRouters }]: RouterInitArgs<T>
|
||||
) {
|
||||
router.all('/hooks/(.*)?', koaBody(), errorHandler, koaAdapter(modelRouters.hook.routes()));
|
||||
}
|
||||
|
|
|
@ -45,12 +45,12 @@ export type RouterContext<T> = T extends Router<unknown, infer Context> ? Contex
|
|||
export default function interactionRoutes<T extends AnonymousRouter>(
|
||||
...[anonymousRouter, tenant]: RouterInitArgs<T>
|
||||
) {
|
||||
const { provider } = tenant;
|
||||
const { provider, queries } = tenant;
|
||||
const router =
|
||||
// @ts-expect-error for good koa types
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
(anonymousRouter as Router<unknown, WithInteractionDetailsContext<RouterContext<T>>>).use(
|
||||
koaAuditLog(),
|
||||
koaAuditLog(queries),
|
||||
koaInteractionDetails(provider)
|
||||
);
|
||||
|
||||
|
@ -280,7 +280,7 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
router.post(
|
||||
`${interactionPrefix}/submit`,
|
||||
koaInteractionSie(),
|
||||
koaInteractionHooks(provider),
|
||||
koaInteractionHooks(tenant),
|
||||
async (ctx, next) => {
|
||||
const { interactionDetails, createLog } = ctx;
|
||||
const interactionStorage = getInteractionStorage(interactionDetails.result);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { trySafe } from '@logto/shared';
|
||||
import type { MiddlewareType } from 'koa';
|
||||
import type { IRouterParamContext } from 'koa-router';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
import { triggerInteractionHooksIfNeeded } from '#src/libraries/hook.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
|
||||
import { getInteractionStorage } from '../utils/interaction.js';
|
||||
import type { WithInteractionDetailsContext } from './koa-interaction-details.js';
|
||||
|
@ -12,7 +11,12 @@ export default function koaInteractionHooks<
|
|||
StateT,
|
||||
ContextT extends WithInteractionDetailsContext<IRouterParamContext>,
|
||||
ResponseT
|
||||
>(provider: Provider): MiddlewareType<StateT, ContextT, ResponseT> {
|
||||
>({
|
||||
provider,
|
||||
libraries: {
|
||||
hooks: { triggerInteractionHooksIfNeeded },
|
||||
},
|
||||
}: TenantContext): MiddlewareType<StateT, ContextT, ResponseT> {
|
||||
return async (ctx, next) => {
|
||||
const { event } = getInteractionStorage(ctx.interactionDetails.result);
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { createConnectorLibrary } from '#src/libraries/connector.js';
|
||||
import { createHookLibrary } from '#src/libraries/hook.js';
|
||||
import { createPhraseLibrary } from '#src/libraries/phrase.js';
|
||||
import { createResourceLibrary } from '#src/libraries/resource.js';
|
||||
import { createSignInExperienceLibrary } from '#src/libraries/sign-in-experience/index.js';
|
||||
import { createUserLibrary } from '#src/libraries/user.js';
|
||||
import type { ModelRouters } from '#src/model-routers/index.js';
|
||||
|
||||
import type Queries from './Queries.js';
|
||||
|
||||
|
@ -12,6 +14,7 @@ export default class Libraries {
|
|||
signInExperiences = createSignInExperienceLibrary(this.queries, this.connectors);
|
||||
phrases = createPhraseLibrary(this.queries);
|
||||
resources = createResourceLibrary(this.queries);
|
||||
hooks = createHookLibrary(this.queries, this.modelRouters);
|
||||
|
||||
constructor(public readonly queries: Queries) {}
|
||||
constructor(private readonly queries: Queries, private readonly modelRouters: ModelRouters) {}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ import koaSlonikErrorHandler from '#src/middleware/koa-slonik-error-handler.js';
|
|||
import koaSpaProxy from '#src/middleware/koa-spa-proxy.js';
|
||||
import koaSpaSessionGuard from '#src/middleware/koa-spa-session-guard.js';
|
||||
import koaWelcomeProxy from '#src/middleware/koa-welcome-proxy.js';
|
||||
import type { ModelRouters } from '#src/model-routers/index.js';
|
||||
import { createModelRouters } from '#src/model-routers/index.js';
|
||||
import initOidc from '#src/oidc/init.js';
|
||||
import initRouter from '#src/routes/init.js';
|
||||
|
||||
|
@ -27,6 +29,7 @@ export default class Tenant implements TenantContext {
|
|||
public readonly provider: Provider;
|
||||
public readonly queries: Queries;
|
||||
public readonly libraries: Libraries;
|
||||
public readonly modelRouters: ModelRouters;
|
||||
|
||||
public readonly app: Koa;
|
||||
|
||||
|
@ -35,16 +38,18 @@ export default class Tenant implements TenantContext {
|
|||
}
|
||||
|
||||
constructor(public id: string) {
|
||||
const modelRouters = createModelRouters(envSet.queryClient);
|
||||
const queries = new Queries(envSet.pool);
|
||||
const libraries = new Libraries(queries);
|
||||
const libraries = new Libraries(queries, modelRouters);
|
||||
|
||||
this.modelRouters = modelRouters;
|
||||
this.queries = queries;
|
||||
this.libraries = libraries;
|
||||
|
||||
// Init app
|
||||
const app = new Koa();
|
||||
|
||||
const provider = initOidc();
|
||||
const provider = initOidc(queries);
|
||||
app.use(mount('/oidc', provider.app));
|
||||
|
||||
app.use(koaLogger());
|
||||
|
@ -54,7 +59,7 @@ export default class Tenant implements TenantContext {
|
|||
app.use(koaConnectorErrorHandler());
|
||||
app.use(koaI18next());
|
||||
|
||||
const apisApp = initRouter({ provider, queries, libraries });
|
||||
const apisApp = initRouter({ provider, queries, libraries, modelRouters });
|
||||
app.use(mount('/api', apisApp));
|
||||
|
||||
app.use(mount('/', koaRootProxy()));
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import type Provider from 'oidc-provider';
|
||||
|
||||
import type { ModelRouters } from '#src/model-routers/index.js';
|
||||
|
||||
import type Libraries from './Libraries.js';
|
||||
import type Queries from './Queries.js';
|
||||
|
||||
|
@ -7,4 +9,5 @@ export default abstract class TenantContext {
|
|||
public abstract readonly provider: Provider;
|
||||
public abstract readonly queries: Queries;
|
||||
public abstract readonly libraries: Libraries;
|
||||
public abstract readonly modelRouters: ModelRouters;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { createMockPool, createMockQueryResult } from 'slonik';
|
||||
|
||||
import { createModelRouters } from '#src/model-routers/index.js';
|
||||
import Libraries from '#src/tenants/Libraries.js';
|
||||
import Queries from '#src/tenants/Queries.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
|
||||
import type { GrantMock } from './oidc-provider.js';
|
||||
import { createMockProvider } from './oidc-provider.js';
|
||||
import { MockQueryClient } from './query-client.js';
|
||||
|
||||
export class MockQueries extends Queries {
|
||||
constructor(queriesOverride?: Partial2<Queries>) {
|
||||
|
@ -44,6 +46,7 @@ export type Partial2<T> = { [key in keyof T]?: Partial<T[key]> };
|
|||
export class MockTenant implements TenantContext {
|
||||
public queries: Queries;
|
||||
public libraries: Libraries;
|
||||
public modelRouters = createModelRouters(new MockQueryClient());
|
||||
|
||||
constructor(
|
||||
public provider = createMockProvider(),
|
||||
|
@ -51,7 +54,7 @@ export class MockTenant implements TenantContext {
|
|||
librariesOverride?: Partial2<Libraries>
|
||||
) {
|
||||
this.queries = new MockQueries(queriesOverride);
|
||||
this.libraries = new Libraries(this.queries);
|
||||
this.libraries = new Libraries(this.queries, this.modelRouters);
|
||||
this.setPartial('libraries', librariesOverride);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue