diff --git a/packages/cloud/src/libraries/services.ts b/packages/cloud/src/libraries/services.ts index 35965cc2c..ae29c31e9 100644 --- a/packages/cloud/src/libraries/services.ts +++ b/packages/cloud/src/libraries/services.ts @@ -16,6 +16,7 @@ import { RequestError } from '@withtyped/server'; import type { Queries } from '#src/queries/index.js'; import type { LogtoConnector } from '#src/utils/connector/index.js'; import { loadConnectorFactories } from '#src/utils/connector/index.js'; +import { jsonObjectGuard } from '#src/utils/guard.js'; export const serviceCountLimitForTenant = 100; @@ -107,11 +108,12 @@ export class ServicesLibrary { return sendMessage(data); } - async addLog(tenantId: string, type: ServiceLogType) { + async addLog(tenantId: string, type: ServiceLogType, payload?: unknown) { return this.queries.serviceLogs.insertLog({ id: generateStandardId(), type, tenantId, + payload: trySafe(() => jsonObjectGuard.parse(payload)), }); } diff --git a/packages/cloud/src/queries/service-logs.ts b/packages/cloud/src/queries/service-logs.ts index 5858346d3..4b296d2c7 100644 --- a/packages/cloud/src/queries/service-logs.ts +++ b/packages/cloud/src/queries/service-logs.ts @@ -1,14 +1,14 @@ import type { CreateServiceLog, ServiceLogType } from '@logto/schemas'; import type { PostgreSql } from '@withtyped/postgres'; import { sql } from '@withtyped/postgres'; -import type { Queryable } from '@withtyped/server'; +import type { JsonObject, Queryable } from '@withtyped/server'; import { insertInto } from '#src/utils/query.js'; export type ServiceLogsQueries = ReturnType; export const createServiceLogsQueries = (client: Queryable) => { - const insertLog = async (data: Omit) => + const insertLog = async (data: Omit & { payload?: JsonObject }) => client.query(insertInto(data, 'service_logs')); const countTenantLogs = async (tenantId: string, type: ServiceLogType) => { diff --git a/packages/cloud/src/routes/service.test.ts b/packages/cloud/src/routes/service.test.ts index 7530ba202..666df99ad 100644 --- a/packages/cloud/src/routes/service.test.ts +++ b/packages/cloud/src/routes/service.test.ts @@ -65,7 +65,9 @@ describe('POST /api/services/send-email', () => { async ({ status }) => { expect(status).toBe(201); expect(library.sendMessage).toBeCalledWith(ConnectorType.Email, mockSendMessagePayload); - expect(library.addLog).toBeCalledWith('tenantId', ServiceLogType.SendEmail); + expect(library.addLog).toBeCalledWith('tenantId', ServiceLogType.SendEmail, { + data: mockSendMessagePayload, + }); }, createHttpContext() ); @@ -125,7 +127,9 @@ describe('POST /api/services/send-sms', () => { async ({ status }) => { expect(status).toBe(201); expect(library.sendMessage).toBeCalledWith(ConnectorType.Sms, mockSendMessagePayload); - expect(library.addLog).toBeCalledWith('tenantId', ServiceLogType.SendSms); + expect(library.addLog).toBeCalledWith('tenantId', ServiceLogType.SendSms, { + data: mockSendMessagePayload, + }); }, createHttpContext() ); diff --git a/packages/cloud/src/routes/services.ts b/packages/cloud/src/routes/services.ts index 51c719358..8247e4a9c 100644 --- a/packages/cloud/src/routes/services.ts +++ b/packages/cloud/src/routes/services.ts @@ -29,7 +29,7 @@ export const servicesRoutes = (library: ServicesLibrary) => } await library.sendMessage(ConnectorType.Email, context.guarded.body.data); - await library.addLog(tenantId, ServiceLogType.SendEmail); + await library.addLog(tenantId, ServiceLogType.SendEmail, context.guarded.body); return next({ ...context, status: 201 }); } @@ -55,7 +55,7 @@ export const servicesRoutes = (library: ServicesLibrary) => } await library.sendMessage(ConnectorType.Sms, context.guarded.body.data); - await library.addLog(tenantId, ServiceLogType.SendSms); + await library.addLog(tenantId, ServiceLogType.SendSms, context.guarded.body); return next({ ...context, status: 201 }); } diff --git a/packages/cloud/src/utils/guard.ts b/packages/cloud/src/utils/guard.ts new file mode 100644 index 000000000..1298fe9f8 --- /dev/null +++ b/packages/cloud/src/utils/guard.ts @@ -0,0 +1,13 @@ +import type { Json } from '@withtyped/server'; +import { z } from 'zod'; + +/** + * `jsonGuard` copied from https://github.com/colinhacks/zod#json-type. + * Can be moved to @logto/shared if needed. + */ + +const jsonGuard: z.ZodType = z.lazy(() => + z.union([z.number(), z.boolean(), z.string(), z.null(), z.array(jsonGuard), z.record(jsonGuard)]) +); + +export const jsonObjectGuard = z.record(jsonGuard);