@open-mercato/webhooks 0.4.9-canary-8c762104f0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -0
- package/build.mjs +69 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/webhooks/acl.js +14 -0
- package/dist/modules/webhooks/acl.js.map +7 -0
- package/dist/modules/webhooks/api/openapi.js +8 -0
- package/dist/modules/webhooks/api/openapi.js.map +7 -0
- package/dist/modules/webhooks/api/webhook-deliveries/route.js +118 -0
- package/dist/modules/webhooks/api/webhook-deliveries/route.js.map +7 -0
- package/dist/modules/webhooks/api/webhooks/route.js +268 -0
- package/dist/modules/webhooks/api/webhooks/route.js.map +7 -0
- package/dist/modules/webhooks/backend/webhooks/[id]/page.meta.js +17 -0
- package/dist/modules/webhooks/backend/webhooks/[id]/page.meta.js.map +7 -0
- package/dist/modules/webhooks/backend/webhooks/create/page.meta.js +17 -0
- package/dist/modules/webhooks/backend/webhooks/create/page.meta.js.map +7 -0
- package/dist/modules/webhooks/backend/webhooks/page.meta.js +24 -0
- package/dist/modules/webhooks/backend/webhooks/page.meta.js.map +7 -0
- package/dist/modules/webhooks/data/entities.js +196 -0
- package/dist/modules/webhooks/data/entities.js.map +7 -0
- package/dist/modules/webhooks/data/validators.js +39 -0
- package/dist/modules/webhooks/data/validators.js.map +7 -0
- package/dist/modules/webhooks/events.js +18 -0
- package/dist/modules/webhooks/events.js.map +7 -0
- package/dist/modules/webhooks/index.js +14 -0
- package/dist/modules/webhooks/index.js.map +7 -0
- package/dist/modules/webhooks/setup.js +12 -0
- package/dist/modules/webhooks/setup.js.map +7 -0
- package/dist/modules/webhooks/subscribers/outbound-dispatch.js +67 -0
- package/dist/modules/webhooks/subscribers/outbound-dispatch.js.map +7 -0
- package/dist/modules/webhooks/workers/webhook-delivery.js +129 -0
- package/dist/modules/webhooks/workers/webhook-delivery.js.map +7 -0
- package/generated/entities/webhook_delivery_entity/index.ts +22 -0
- package/generated/entities/webhook_entity/index.ts +26 -0
- package/generated/entities.ids.generated.ts +12 -0
- package/generated/entity-fields-registry.ts +13 -0
- package/jest.config.cjs +20 -0
- package/package.json +77 -0
- package/src/index.ts +1 -0
- package/src/modules/webhooks/acl.ts +10 -0
- package/src/modules/webhooks/api/openapi.ts +5 -0
- package/src/modules/webhooks/api/webhook-deliveries/route.ts +131 -0
- package/src/modules/webhooks/api/webhooks/route.ts +288 -0
- package/src/modules/webhooks/backend/webhooks/[id]/page.meta.ts +13 -0
- package/src/modules/webhooks/backend/webhooks/[id]/page.tsx +262 -0
- package/src/modules/webhooks/backend/webhooks/create/page.meta.ts +13 -0
- package/src/modules/webhooks/backend/webhooks/create/page.tsx +75 -0
- package/src/modules/webhooks/backend/webhooks/page.meta.ts +20 -0
- package/src/modules/webhooks/backend/webhooks/page.tsx +206 -0
- package/src/modules/webhooks/data/entities.ts +157 -0
- package/src/modules/webhooks/data/validators.ts +40 -0
- package/src/modules/webhooks/events.ts +15 -0
- package/src/modules/webhooks/i18n/en.json +73 -0
- package/src/modules/webhooks/index.ts +12 -0
- package/src/modules/webhooks/setup.ts +10 -0
- package/src/modules/webhooks/subscribers/outbound-dispatch.ts +79 -0
- package/src/modules/webhooks/workers/webhook-delivery.ts +158 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/build.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as esbuild from 'esbuild'
|
|
2
|
+
import { glob } from 'glob'
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
|
|
9
|
+
const entryPoints = await glob('src/**/*.ts', {
|
|
10
|
+
cwd: __dirname,
|
|
11
|
+
ignore: ['**/__tests__/**', '**/*.test.ts'],
|
|
12
|
+
absolute: true,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
if (entryPoints.length === 0) {
|
|
16
|
+
console.error('No entry points found!')
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`Found ${entryPoints.length} entry points`)
|
|
21
|
+
|
|
22
|
+
const addJsExtension = {
|
|
23
|
+
name: 'add-js-extension',
|
|
24
|
+
setup(build) {
|
|
25
|
+
build.onEnd(async (result) => {
|
|
26
|
+
if (result.errors.length > 0) return
|
|
27
|
+
const outputFiles = await glob('dist/**/*.js', { cwd: __dirname, absolute: true })
|
|
28
|
+
for (const file of outputFiles) {
|
|
29
|
+
const fileDir = dirname(file)
|
|
30
|
+
let content = readFileSync(file, 'utf-8')
|
|
31
|
+
content = content.replace(
|
|
32
|
+
/from\s+["'](\.[^"']+)["']/g,
|
|
33
|
+
(match, path) => {
|
|
34
|
+
if (path.endsWith('.js') || path.endsWith('.json')) return match
|
|
35
|
+
const resolvedPath = join(fileDir, path)
|
|
36
|
+
if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
|
|
37
|
+
return `from "${path}/index.js"`
|
|
38
|
+
}
|
|
39
|
+
return `from "${path}.js"`
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
content = content.replace(
|
|
43
|
+
/import\s*\(\s*["'](\.[^"']+)["']\s*\)/g,
|
|
44
|
+
(match, path) => {
|
|
45
|
+
if (path.endsWith('.js') || path.endsWith('.json')) return match
|
|
46
|
+
const resolvedPath = join(fileDir, path)
|
|
47
|
+
if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
|
|
48
|
+
return `import("${path}/index.js")`
|
|
49
|
+
}
|
|
50
|
+
return `import("${path}.js")`
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
writeFileSync(file, content)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await esbuild.build({
|
|
60
|
+
entryPoints,
|
|
61
|
+
outdir: 'dist',
|
|
62
|
+
format: 'esm',
|
|
63
|
+
platform: 'node',
|
|
64
|
+
target: 'node18',
|
|
65
|
+
sourcemap: true,
|
|
66
|
+
plugins: [addJsExtension],
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
console.log('webhooks built successfully')
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const features = [
|
|
2
|
+
{ id: "webhooks.view", title: "View webhooks", module: "webhooks" },
|
|
3
|
+
{ id: "webhooks.create", title: "Create webhooks", module: "webhooks" },
|
|
4
|
+
{ id: "webhooks.edit", title: "Edit webhooks", module: "webhooks" },
|
|
5
|
+
{ id: "webhooks.delete", title: "Delete webhooks", module: "webhooks" },
|
|
6
|
+
{ id: "webhooks.test", title: "Test webhooks", module: "webhooks" },
|
|
7
|
+
{ id: "webhooks.deliveries.view", title: "View webhook deliveries", module: "webhooks" }
|
|
8
|
+
];
|
|
9
|
+
var acl_default = features;
|
|
10
|
+
export {
|
|
11
|
+
acl_default as default,
|
|
12
|
+
features
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=acl.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/webhooks/acl.ts"],
|
|
4
|
+
"sourcesContent": ["export const features = [\n { id: 'webhooks.view', title: 'View webhooks', module: 'webhooks' },\n { id: 'webhooks.create', title: 'Create webhooks', module: 'webhooks' },\n { id: 'webhooks.edit', title: 'Edit webhooks', module: 'webhooks' },\n { id: 'webhooks.delete', title: 'Delete webhooks', module: 'webhooks' },\n { id: 'webhooks.test', title: 'Test webhooks', module: 'webhooks' },\n { id: 'webhooks.deliveries.view', title: 'View webhook deliveries', module: 'webhooks' },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,iBAAiB,OAAO,iBAAiB,QAAQ,WAAW;AAAA,EAClE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,WAAW;AAAA,EACtE,EAAE,IAAI,iBAAiB,OAAO,iBAAiB,QAAQ,WAAW;AAAA,EAClE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,WAAW;AAAA,EACtE,EAAE,IAAI,iBAAiB,OAAO,iBAAiB,QAAQ,WAAW;AAAA,EAClE,EAAE,IAAI,4BAA4B,OAAO,2BAA2B,QAAQ,WAAW;AACzF;AAEA,IAAO,cAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/webhooks/api/openapi.ts"],
|
|
4
|
+
"sourcesContent": ["import { createCrudOpenApiFactory } from '@open-mercato/shared/lib/openapi/crud'\n\nexport const buildWebhooksCrudOpenApi = createCrudOpenApiFactory({\n defaultTag: 'Webhooks',\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,gCAAgC;AAElC,MAAM,2BAA2B,yBAAyB;AAAA,EAC/D,YAAY;AACd,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { WebhookDeliveryEntity } from "../../data/entities.js";
|
|
4
|
+
import { webhookDeliveryQuerySchema } from "../../data/validators.js";
|
|
5
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
+
function json(payload, init = { status: 200 }) {
|
|
7
|
+
return new Response(JSON.stringify(payload), {
|
|
8
|
+
...init,
|
|
9
|
+
headers: { "content-type": "application/json", ...init.headers || {} }
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
const deliveryItemSchema = z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
webhookId: z.string(),
|
|
15
|
+
eventType: z.string(),
|
|
16
|
+
messageId: z.string(),
|
|
17
|
+
status: z.string(),
|
|
18
|
+
responseStatus: z.number().nullable(),
|
|
19
|
+
errorMessage: z.string().nullable(),
|
|
20
|
+
attemptNumber: z.number(),
|
|
21
|
+
maxAttempts: z.number(),
|
|
22
|
+
targetUrl: z.string(),
|
|
23
|
+
durationMs: z.number().nullable(),
|
|
24
|
+
enqueuedAt: z.string(),
|
|
25
|
+
lastAttemptAt: z.string().nullable(),
|
|
26
|
+
deliveredAt: z.string().nullable(),
|
|
27
|
+
createdAt: z.string()
|
|
28
|
+
});
|
|
29
|
+
const deliveryCollectionResponseSchema = z.object({
|
|
30
|
+
items: z.array(deliveryItemSchema),
|
|
31
|
+
total: z.number().int().nonnegative(),
|
|
32
|
+
page: z.number().int().positive(),
|
|
33
|
+
pageSize: z.number().int().positive(),
|
|
34
|
+
totalPages: z.number().int().nonnegative()
|
|
35
|
+
});
|
|
36
|
+
const errorSchema = z.object({ error: z.string() });
|
|
37
|
+
const crud = makeCrudRoute({
|
|
38
|
+
metadata: {
|
|
39
|
+
GET: { requireAuth: true, requireFeatures: ["webhooks.deliveries.view"] }
|
|
40
|
+
},
|
|
41
|
+
orm: { entity: WebhookDeliveryEntity, orgField: "organizationId" },
|
|
42
|
+
list: { schema: webhookDeliveryQuerySchema },
|
|
43
|
+
hooks: {
|
|
44
|
+
beforeList: async (query, ctx) => {
|
|
45
|
+
const auth = ctx.auth;
|
|
46
|
+
const { translate } = await resolveTranslations();
|
|
47
|
+
if (!auth?.tenantId) throw json({ error: translate("webhooks.errors.tenantRequired", "Tenant context required") }, { status: 400 });
|
|
48
|
+
const page = query.page ?? 1;
|
|
49
|
+
const pageSize = Math.min(query.pageSize ?? 50, 100);
|
|
50
|
+
const em = ctx.container.resolve("em");
|
|
51
|
+
const qb = em.createQueryBuilder(WebhookDeliveryEntity, "d");
|
|
52
|
+
qb.where({ tenantId: auth.tenantId });
|
|
53
|
+
if (auth.orgId) {
|
|
54
|
+
qb.andWhere({ organizationId: auth.orgId });
|
|
55
|
+
}
|
|
56
|
+
if (query.webhookId) {
|
|
57
|
+
qb.andWhere({ webhookId: query.webhookId });
|
|
58
|
+
}
|
|
59
|
+
if (query.eventType) {
|
|
60
|
+
qb.andWhere({ eventType: query.eventType });
|
|
61
|
+
}
|
|
62
|
+
if (query.status) {
|
|
63
|
+
qb.andWhere({ status: query.status });
|
|
64
|
+
}
|
|
65
|
+
qb.orderBy({ createdAt: "desc" });
|
|
66
|
+
qb.limit(pageSize).offset((page - 1) * pageSize);
|
|
67
|
+
const [items, total] = await qb.getResultAndCount();
|
|
68
|
+
const payload = {
|
|
69
|
+
items: items.map((item) => ({
|
|
70
|
+
id: item.id,
|
|
71
|
+
webhookId: item.webhookId,
|
|
72
|
+
eventType: item.eventType,
|
|
73
|
+
messageId: item.messageId,
|
|
74
|
+
status: item.status,
|
|
75
|
+
responseStatus: item.responseStatus ?? null,
|
|
76
|
+
errorMessage: item.errorMessage ?? null,
|
|
77
|
+
attemptNumber: item.attemptNumber,
|
|
78
|
+
maxAttempts: item.maxAttempts,
|
|
79
|
+
targetUrl: item.targetUrl,
|
|
80
|
+
durationMs: item.durationMs ?? null,
|
|
81
|
+
enqueuedAt: item.enqueuedAt.toISOString(),
|
|
82
|
+
lastAttemptAt: item.lastAttemptAt?.toISOString() ?? null,
|
|
83
|
+
deliveredAt: item.deliveredAt?.toISOString() ?? null,
|
|
84
|
+
createdAt: item.createdAt.toISOString()
|
|
85
|
+
})),
|
|
86
|
+
total,
|
|
87
|
+
page,
|
|
88
|
+
pageSize,
|
|
89
|
+
totalPages: Math.ceil(total / pageSize)
|
|
90
|
+
};
|
|
91
|
+
throw json(payload);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const metadata = crud.metadata;
|
|
96
|
+
const GET = crud.GET;
|
|
97
|
+
const openApi = {
|
|
98
|
+
summary: "Webhook delivery logs",
|
|
99
|
+
description: "View delivery attempts for webhook endpoints.",
|
|
100
|
+
methods: {
|
|
101
|
+
GET: {
|
|
102
|
+
summary: "List delivery logs",
|
|
103
|
+
description: "Returns paginated webhook delivery attempts with filtering by webhook, event type, and status.",
|
|
104
|
+
query: webhookDeliveryQuerySchema,
|
|
105
|
+
responses: [{ status: 200, description: "Delivery log collection", schema: deliveryCollectionResponseSchema }],
|
|
106
|
+
errors: [
|
|
107
|
+
{ status: 400, description: "Tenant context missing", schema: errorSchema },
|
|
108
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema }
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
export {
|
|
114
|
+
GET,
|
|
115
|
+
metadata,
|
|
116
|
+
openApi
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/webhooks/api/webhook-deliveries/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { WebhookDeliveryEntity } from '../../data/entities'\nimport { webhookDeliveryQuerySchema } from '../../data/validators'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nfunction json(payload: unknown, init: ResponseInit = { status: 200 }) {\n return new Response(JSON.stringify(payload), {\n ...init,\n headers: { 'content-type': 'application/json', ...(init.headers || {}) },\n })\n}\n\nconst deliveryItemSchema = z.object({\n id: z.string(),\n webhookId: z.string(),\n eventType: z.string(),\n messageId: z.string(),\n status: z.string(),\n responseStatus: z.number().nullable(),\n errorMessage: z.string().nullable(),\n attemptNumber: z.number(),\n maxAttempts: z.number(),\n targetUrl: z.string(),\n durationMs: z.number().nullable(),\n enqueuedAt: z.string(),\n lastAttemptAt: z.string().nullable(),\n deliveredAt: z.string().nullable(),\n createdAt: z.string(),\n})\n\nconst deliveryCollectionResponseSchema = z.object({\n items: z.array(deliveryItemSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n totalPages: z.number().int().nonnegative(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nconst crud = makeCrudRoute<never, never, z.infer<typeof webhookDeliveryQuerySchema>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['webhooks.deliveries.view'] },\n },\n orm: { entity: WebhookDeliveryEntity, orgField: 'organizationId' },\n list: { schema: webhookDeliveryQuerySchema },\n hooks: {\n beforeList: async (query, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n\n const page = query.page ?? 1\n const pageSize = Math.min(query.pageSize ?? 50, 100)\n\n const em = ctx.container.resolve('em') as EntityManager\n const qb = em.createQueryBuilder(WebhookDeliveryEntity, 'd')\n qb.where({ tenantId: auth.tenantId })\n\n if (auth.orgId) {\n qb.andWhere({ organizationId: auth.orgId })\n }\n\n if (query.webhookId) {\n qb.andWhere({ webhookId: query.webhookId })\n }\n\n if (query.eventType) {\n qb.andWhere({ eventType: query.eventType })\n }\n\n if (query.status) {\n qb.andWhere({ status: query.status })\n }\n\n qb.orderBy({ createdAt: 'desc' })\n qb.limit(pageSize).offset((page - 1) * pageSize)\n const [items, total] = await qb.getResultAndCount()\n\n const payload = {\n items: items.map((item) => ({\n id: item.id,\n webhookId: item.webhookId,\n eventType: item.eventType,\n messageId: item.messageId,\n status: item.status,\n responseStatus: item.responseStatus ?? null,\n errorMessage: item.errorMessage ?? null,\n attemptNumber: item.attemptNumber,\n maxAttempts: item.maxAttempts,\n targetUrl: item.targetUrl,\n durationMs: item.durationMs ?? null,\n enqueuedAt: item.enqueuedAt.toISOString(),\n lastAttemptAt: item.lastAttemptAt?.toISOString() ?? null,\n deliveredAt: item.deliveredAt?.toISOString() ?? null,\n createdAt: item.createdAt.toISOString(),\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n }\n\n throw json(payload)\n },\n },\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Webhook delivery logs',\n description: 'View delivery attempts for webhook endpoints.',\n methods: {\n GET: {\n summary: 'List delivery logs',\n description: 'Returns paginated webhook delivery attempts with filtering by webhook, event type, and status.',\n query: webhookDeliveryQuerySchema,\n responses: [{ status: 200, description: 'Delivery log collection', schema: deliveryCollectionResponseSchema }],\n errors: [\n { status: 400, description: 'Tenant context missing', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { WebhookEntity } from "../../data/entities.js";
|
|
4
|
+
import { webhookCreateSchema, webhookUpdateSchema, webhookListQuerySchema } from "../../data/validators.js";
|
|
5
|
+
import { generateWebhookSecret } from "@open-mercato/shared/lib/webhooks";
|
|
6
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
|
+
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
|
|
8
|
+
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
|
|
9
|
+
function json(payload, init = { status: 200 }) {
|
|
10
|
+
return new Response(JSON.stringify(payload), {
|
|
11
|
+
...init,
|
|
12
|
+
headers: { "content-type": "application/json", ...init.headers || {} }
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const webhookListItemSchema = z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
name: z.string(),
|
|
18
|
+
description: z.string().nullable(),
|
|
19
|
+
url: z.string(),
|
|
20
|
+
subscribedEvents: z.array(z.string()),
|
|
21
|
+
httpMethod: z.string(),
|
|
22
|
+
isActive: z.boolean(),
|
|
23
|
+
deliveryStrategy: z.string(),
|
|
24
|
+
maxRetries: z.number(),
|
|
25
|
+
consecutiveFailures: z.number(),
|
|
26
|
+
lastSuccessAt: z.string().nullable(),
|
|
27
|
+
lastFailureAt: z.string().nullable(),
|
|
28
|
+
createdAt: z.string(),
|
|
29
|
+
updatedAt: z.string()
|
|
30
|
+
});
|
|
31
|
+
const webhookCollectionResponseSchema = z.object({
|
|
32
|
+
items: z.array(webhookListItemSchema),
|
|
33
|
+
total: z.number().int().nonnegative(),
|
|
34
|
+
page: z.number().int().positive(),
|
|
35
|
+
pageSize: z.number().int().positive(),
|
|
36
|
+
totalPages: z.number().int().nonnegative()
|
|
37
|
+
});
|
|
38
|
+
const webhookCreateResponseSchema = z.object({
|
|
39
|
+
id: z.string(),
|
|
40
|
+
name: z.string(),
|
|
41
|
+
url: z.string(),
|
|
42
|
+
secret: z.string(),
|
|
43
|
+
subscribedEvents: z.array(z.string()),
|
|
44
|
+
isActive: z.boolean()
|
|
45
|
+
});
|
|
46
|
+
const webhookDetailResponseSchema = webhookListItemSchema.extend({
|
|
47
|
+
customHeaders: z.record(z.string(), z.string()).nullable(),
|
|
48
|
+
strategyConfig: z.record(z.string(), z.unknown()).nullable(),
|
|
49
|
+
timeoutMs: z.number(),
|
|
50
|
+
rateLimitPerMinute: z.number(),
|
|
51
|
+
autoDisableThreshold: z.number(),
|
|
52
|
+
integrationId: z.string().nullable()
|
|
53
|
+
});
|
|
54
|
+
const deleteResponseSchema = z.object({ success: z.literal(true) });
|
|
55
|
+
const errorSchema = z.object({ error: z.string() });
|
|
56
|
+
const crud = makeCrudRoute({
|
|
57
|
+
metadata: {
|
|
58
|
+
GET: { requireAuth: true, requireFeatures: ["webhooks.view"] },
|
|
59
|
+
POST: { requireAuth: true, requireFeatures: ["webhooks.create"] },
|
|
60
|
+
PUT: { requireAuth: true, requireFeatures: ["webhooks.edit"] },
|
|
61
|
+
DELETE: { requireAuth: true, requireFeatures: ["webhooks.delete"] }
|
|
62
|
+
},
|
|
63
|
+
orm: { entity: WebhookEntity, orgField: "organizationId" },
|
|
64
|
+
list: { schema: webhookListQuerySchema },
|
|
65
|
+
create: {
|
|
66
|
+
schema: webhookCreateSchema,
|
|
67
|
+
mapToEntity: (input, ctx) => {
|
|
68
|
+
const scopedCtx = ctx;
|
|
69
|
+
const secret = scopedCtx.__webhookSecret;
|
|
70
|
+
if (!secret) throw new Error("Webhook secret not prepared");
|
|
71
|
+
return {
|
|
72
|
+
name: input.name,
|
|
73
|
+
description: input.description ?? null,
|
|
74
|
+
url: input.url,
|
|
75
|
+
secret,
|
|
76
|
+
subscribedEvents: input.subscribedEvents,
|
|
77
|
+
httpMethod: input.httpMethod ?? "POST",
|
|
78
|
+
customHeaders: input.customHeaders ?? null,
|
|
79
|
+
deliveryStrategy: input.deliveryStrategy ?? "http",
|
|
80
|
+
strategyConfig: input.strategyConfig ?? null,
|
|
81
|
+
maxRetries: input.maxRetries ?? 10,
|
|
82
|
+
timeoutMs: input.timeoutMs ?? 15e3,
|
|
83
|
+
rateLimitPerMinute: input.rateLimitPerMinute ?? 0,
|
|
84
|
+
autoDisableThreshold: input.autoDisableThreshold ?? 100,
|
|
85
|
+
integrationId: input.integrationId ?? null,
|
|
86
|
+
organizationId: ctx.auth?.orgId ?? "",
|
|
87
|
+
tenantId: ctx.auth?.tenantId ?? ""
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
response: (entity) => ({
|
|
91
|
+
id: entity.id,
|
|
92
|
+
name: entity.name,
|
|
93
|
+
url: entity.url,
|
|
94
|
+
secret: entity.__revealSecret ?? "***",
|
|
95
|
+
subscribedEvents: entity.subscribedEvents,
|
|
96
|
+
isActive: entity.isActive
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
update: {
|
|
100
|
+
schema: webhookUpdateSchema,
|
|
101
|
+
applyToEntity: (entity, input) => {
|
|
102
|
+
if (input.name !== void 0) entity.name = input.name;
|
|
103
|
+
if (input.description !== void 0) entity.description = input.description;
|
|
104
|
+
if (input.url !== void 0) entity.url = input.url;
|
|
105
|
+
if (input.subscribedEvents !== void 0) entity.subscribedEvents = input.subscribedEvents;
|
|
106
|
+
if (input.httpMethod !== void 0) entity.httpMethod = input.httpMethod;
|
|
107
|
+
if (input.customHeaders !== void 0) entity.customHeaders = input.customHeaders;
|
|
108
|
+
if (input.deliveryStrategy !== void 0) entity.deliveryStrategy = input.deliveryStrategy;
|
|
109
|
+
if (input.strategyConfig !== void 0) entity.strategyConfig = input.strategyConfig;
|
|
110
|
+
if (input.maxRetries !== void 0) entity.maxRetries = input.maxRetries;
|
|
111
|
+
if (input.timeoutMs !== void 0) entity.timeoutMs = input.timeoutMs;
|
|
112
|
+
if (input.rateLimitPerMinute !== void 0) entity.rateLimitPerMinute = input.rateLimitPerMinute;
|
|
113
|
+
if (input.autoDisableThreshold !== void 0) entity.autoDisableThreshold = input.autoDisableThreshold;
|
|
114
|
+
if (input.integrationId !== void 0) entity.integrationId = input.integrationId;
|
|
115
|
+
if (input.isActive !== void 0) entity.isActive = input.isActive;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
del: { idFrom: "query" },
|
|
119
|
+
hooks: {
|
|
120
|
+
beforeList: async (query, ctx) => {
|
|
121
|
+
const auth = ctx.auth;
|
|
122
|
+
const { translate } = await resolveTranslations();
|
|
123
|
+
if (!auth?.tenantId) throw json({ error: translate("webhooks.errors.tenantRequired", "Tenant context required") }, { status: 400 });
|
|
124
|
+
const page = Math.max(parseInt(query.page ?? "1", 10) || 1, 1);
|
|
125
|
+
const pageSize = Math.min(Math.max(parseInt(query.pageSize ?? "20", 10) || 20, 1), 100);
|
|
126
|
+
const search = (query.search ?? "").trim().toLowerCase();
|
|
127
|
+
const organizationIds = Array.isArray(ctx.organizationIds) ? ctx.organizationIds : null;
|
|
128
|
+
if (organizationIds && organizationIds.length === 0) {
|
|
129
|
+
throw json({ items: [], total: 0, page, pageSize, totalPages: 0 });
|
|
130
|
+
}
|
|
131
|
+
const em = ctx.container.resolve("em");
|
|
132
|
+
const qb = em.createQueryBuilder(WebhookEntity, "w");
|
|
133
|
+
qb.where({ deletedAt: null });
|
|
134
|
+
qb.andWhere({ tenantId: auth.tenantId });
|
|
135
|
+
if (organizationIds && organizationIds.length > 0) {
|
|
136
|
+
qb.andWhere({ organizationId: { $in: organizationIds } });
|
|
137
|
+
} else if (auth.orgId) {
|
|
138
|
+
qb.andWhere({ organizationId: auth.orgId });
|
|
139
|
+
}
|
|
140
|
+
if (search) {
|
|
141
|
+
const pattern = `%${escapeLikePattern(search)}%`;
|
|
142
|
+
qb.andWhere({ name: { $ilike: pattern } });
|
|
143
|
+
}
|
|
144
|
+
if (query.isActive !== void 0 && query.isActive !== "") {
|
|
145
|
+
const active = parseBooleanToken(query.isActive);
|
|
146
|
+
if (active !== null) {
|
|
147
|
+
qb.andWhere({ isActive: active });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
qb.orderBy({ createdAt: "desc" });
|
|
151
|
+
qb.limit(pageSize).offset((page - 1) * pageSize);
|
|
152
|
+
const [items, total] = await qb.getResultAndCount();
|
|
153
|
+
const payload = {
|
|
154
|
+
items: items.map((item) => ({
|
|
155
|
+
id: item.id,
|
|
156
|
+
name: item.name,
|
|
157
|
+
description: item.description ?? null,
|
|
158
|
+
url: item.url,
|
|
159
|
+
subscribedEvents: item.subscribedEvents,
|
|
160
|
+
httpMethod: item.httpMethod,
|
|
161
|
+
isActive: item.isActive,
|
|
162
|
+
deliveryStrategy: item.deliveryStrategy,
|
|
163
|
+
maxRetries: item.maxRetries,
|
|
164
|
+
consecutiveFailures: item.consecutiveFailures,
|
|
165
|
+
lastSuccessAt: item.lastSuccessAt?.toISOString() ?? null,
|
|
166
|
+
lastFailureAt: item.lastFailureAt?.toISOString() ?? null,
|
|
167
|
+
createdAt: item.createdAt.toISOString(),
|
|
168
|
+
updatedAt: item.updatedAt.toISOString()
|
|
169
|
+
})),
|
|
170
|
+
total,
|
|
171
|
+
page,
|
|
172
|
+
pageSize,
|
|
173
|
+
totalPages: Math.ceil(total / pageSize)
|
|
174
|
+
};
|
|
175
|
+
throw json(payload);
|
|
176
|
+
},
|
|
177
|
+
beforeCreate: async (input, ctx) => {
|
|
178
|
+
const auth = ctx.auth;
|
|
179
|
+
const { translate } = await resolveTranslations();
|
|
180
|
+
if (!auth?.tenantId) throw json({ error: translate("webhooks.errors.tenantRequired", "Tenant context required") }, { status: 400 });
|
|
181
|
+
if (!auth?.orgId) throw json({ error: translate("webhooks.errors.orgRequired", "Organization context required") }, { status: 400 });
|
|
182
|
+
const scopedCtx = ctx;
|
|
183
|
+
scopedCtx.__webhookSecret = generateWebhookSecret();
|
|
184
|
+
return input;
|
|
185
|
+
},
|
|
186
|
+
afterCreate: async (entity, ctx) => {
|
|
187
|
+
const scopedCtx = ctx;
|
|
188
|
+
if (scopedCtx.__webhookSecret) {
|
|
189
|
+
;
|
|
190
|
+
entity.__revealSecret = scopedCtx.__webhookSecret;
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
beforeDelete: async (id, ctx) => {
|
|
194
|
+
const auth = ctx.auth;
|
|
195
|
+
const { translate } = await resolveTranslations();
|
|
196
|
+
if (!auth?.tenantId) throw json({ error: translate("webhooks.errors.tenantRequired", "Tenant context required") }, { status: 400 });
|
|
197
|
+
const em = ctx.container.resolve("em");
|
|
198
|
+
const record = await em.findOne(WebhookEntity, { id, deletedAt: null, tenantId: auth.tenantId });
|
|
199
|
+
if (!record) throw json({ error: translate("webhooks.errors.notFound", "Webhook not found") }, { status: 404 });
|
|
200
|
+
const allowedIds = ctx.organizationScope?.allowedIds ?? null;
|
|
201
|
+
if (record.organizationId && Array.isArray(allowedIds) && allowedIds.length > 0) {
|
|
202
|
+
if (!allowedIds.includes(record.organizationId)) {
|
|
203
|
+
throw json({ error: translate("webhooks.errors.forbidden", "Forbidden") }, { status: 403 });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
const metadata = crud.metadata;
|
|
210
|
+
const GET = crud.GET;
|
|
211
|
+
const POST = crud.POST;
|
|
212
|
+
const PUT = crud.PUT;
|
|
213
|
+
const DELETE = crud.DELETE;
|
|
214
|
+
const openApi = {
|
|
215
|
+
summary: "Manage webhooks",
|
|
216
|
+
description: "CRUD operations for webhook endpoints following the Standard Webhooks specification.",
|
|
217
|
+
methods: {
|
|
218
|
+
GET: {
|
|
219
|
+
summary: "List webhooks",
|
|
220
|
+
description: "Returns paginated webhooks for the current tenant and organization.",
|
|
221
|
+
query: webhookListQuerySchema,
|
|
222
|
+
responses: [{ status: 200, description: "Webhook collection", schema: webhookCollectionResponseSchema }],
|
|
223
|
+
errors: [
|
|
224
|
+
{ status: 400, description: "Tenant context missing", schema: errorSchema },
|
|
225
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema }
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
POST: {
|
|
229
|
+
summary: "Create webhook",
|
|
230
|
+
description: "Creates a new webhook endpoint. A signing secret (whsec_ prefixed) is auto-generated and returned once.",
|
|
231
|
+
requestBody: { contentType: "application/json", schema: webhookCreateSchema, description: "Webhook configuration." },
|
|
232
|
+
responses: [{ status: 201, description: "Webhook created", schema: webhookCreateResponseSchema }],
|
|
233
|
+
errors: [
|
|
234
|
+
{ status: 400, description: "Invalid payload", schema: errorSchema },
|
|
235
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema }
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
PUT: {
|
|
239
|
+
summary: "Update webhook",
|
|
240
|
+
description: "Updates an existing webhook configuration.",
|
|
241
|
+
requestBody: { contentType: "application/json", schema: webhookUpdateSchema, description: "Fields to update." },
|
|
242
|
+
responses: [{ status: 200, description: "Webhook updated", schema: webhookDetailResponseSchema }],
|
|
243
|
+
errors: [
|
|
244
|
+
{ status: 400, description: "Invalid payload", schema: errorSchema },
|
|
245
|
+
{ status: 404, description: "Not found", schema: errorSchema }
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
DELETE: {
|
|
249
|
+
summary: "Delete webhook",
|
|
250
|
+
description: "Soft-deletes a webhook endpoint.",
|
|
251
|
+
query: z.object({ id: z.string().uuid().describe("Webhook ID to delete") }),
|
|
252
|
+
responses: [{ status: 200, description: "Deleted", schema: deleteResponseSchema }],
|
|
253
|
+
errors: [
|
|
254
|
+
{ status: 404, description: "Not found", schema: errorSchema },
|
|
255
|
+
{ status: 403, description: "Forbidden", schema: errorSchema }
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
export {
|
|
261
|
+
DELETE,
|
|
262
|
+
GET,
|
|
263
|
+
POST,
|
|
264
|
+
PUT,
|
|
265
|
+
metadata,
|
|
266
|
+
openApi
|
|
267
|
+
};
|
|
268
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/webhooks/api/webhooks/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { WebhookEntity } from '../../data/entities'\nimport { webhookCreateSchema, webhookUpdateSchema, webhookListQuerySchema } from '../../data/validators'\nimport { generateWebhookSecret } from '@open-mercato/shared/lib/webhooks'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport type { WebhookCreateInput, WebhookUpdateInput } from '../../data/validators'\n\ntype WebhookCrudCtx = CrudCtx & {\n __webhookSecret?: string\n}\n\nfunction json(payload: unknown, init: ResponseInit = { status: 200 }) {\n return new Response(JSON.stringify(payload), {\n ...init,\n headers: { 'content-type': 'application/json', ...(init.headers || {}) },\n })\n}\n\nconst webhookListItemSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n url: z.string(),\n subscribedEvents: z.array(z.string()),\n httpMethod: z.string(),\n isActive: z.boolean(),\n deliveryStrategy: z.string(),\n maxRetries: z.number(),\n consecutiveFailures: z.number(),\n lastSuccessAt: z.string().nullable(),\n lastFailureAt: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst webhookCollectionResponseSchema = z.object({\n items: z.array(webhookListItemSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n totalPages: z.number().int().nonnegative(),\n})\n\nconst webhookCreateResponseSchema = z.object({\n id: z.string(),\n name: z.string(),\n url: z.string(),\n secret: z.string(),\n subscribedEvents: z.array(z.string()),\n isActive: z.boolean(),\n})\n\nconst webhookDetailResponseSchema = webhookListItemSchema.extend({\n customHeaders: z.record(z.string(), z.string()).nullable(),\n strategyConfig: z.record(z.string(), z.unknown()).nullable(),\n timeoutMs: z.number(),\n rateLimitPerMinute: z.number(),\n autoDisableThreshold: z.number(),\n integrationId: z.string().nullable(),\n})\n\nconst deleteResponseSchema = z.object({ success: z.literal(true) })\nconst errorSchema = z.object({ error: z.string() })\n\nconst crud = makeCrudRoute<WebhookCreateInput, WebhookUpdateInput, z.infer<typeof webhookListQuerySchema>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['webhooks.view'] },\n POST: { requireAuth: true, requireFeatures: ['webhooks.create'] },\n PUT: { requireAuth: true, requireFeatures: ['webhooks.edit'] },\n DELETE: { requireAuth: true, requireFeatures: ['webhooks.delete'] },\n },\n orm: { entity: WebhookEntity, orgField: 'organizationId' },\n list: { schema: webhookListQuerySchema },\n create: {\n schema: webhookCreateSchema,\n mapToEntity: (input, ctx) => {\n const scopedCtx = ctx as WebhookCrudCtx\n const secret = scopedCtx.__webhookSecret\n if (!secret) throw new Error('Webhook secret not prepared')\n return {\n name: input.name,\n description: input.description ?? null,\n url: input.url,\n secret,\n subscribedEvents: input.subscribedEvents,\n httpMethod: input.httpMethod ?? 'POST',\n customHeaders: input.customHeaders ?? null,\n deliveryStrategy: input.deliveryStrategy ?? 'http',\n strategyConfig: input.strategyConfig ?? null,\n maxRetries: input.maxRetries ?? 10,\n timeoutMs: input.timeoutMs ?? 15000,\n rateLimitPerMinute: input.rateLimitPerMinute ?? 0,\n autoDisableThreshold: input.autoDisableThreshold ?? 100,\n integrationId: input.integrationId ?? null,\n organizationId: ctx.auth?.orgId ?? '',\n tenantId: ctx.auth?.tenantId ?? '',\n }\n },\n response: (entity) => ({\n id: entity.id,\n name: entity.name,\n url: entity.url,\n secret: (entity as WebhookEntity & { __revealSecret?: string }).__revealSecret ?? '***',\n subscribedEvents: entity.subscribedEvents,\n isActive: entity.isActive,\n }),\n },\n update: {\n schema: webhookUpdateSchema,\n applyToEntity: (entity, input) => {\n if (input.name !== undefined) entity.name = input.name\n if (input.description !== undefined) entity.description = input.description\n if (input.url !== undefined) entity.url = input.url\n if (input.subscribedEvents !== undefined) entity.subscribedEvents = input.subscribedEvents\n if (input.httpMethod !== undefined) entity.httpMethod = input.httpMethod\n if (input.customHeaders !== undefined) entity.customHeaders = input.customHeaders\n if (input.deliveryStrategy !== undefined) entity.deliveryStrategy = input.deliveryStrategy\n if (input.strategyConfig !== undefined) entity.strategyConfig = input.strategyConfig\n if (input.maxRetries !== undefined) entity.maxRetries = input.maxRetries\n if (input.timeoutMs !== undefined) entity.timeoutMs = input.timeoutMs\n if (input.rateLimitPerMinute !== undefined) entity.rateLimitPerMinute = input.rateLimitPerMinute\n if (input.autoDisableThreshold !== undefined) entity.autoDisableThreshold = input.autoDisableThreshold\n if (input.integrationId !== undefined) entity.integrationId = input.integrationId\n if (input.isActive !== undefined) entity.isActive = input.isActive\n },\n },\n del: { idFrom: 'query' },\n hooks: {\n beforeList: async (query, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n\n const page = Math.max(parseInt(query.page ?? '1', 10) || 1, 1)\n const pageSize = Math.min(Math.max(parseInt(query.pageSize ?? '20', 10) || 20, 1), 100)\n const search = (query.search ?? '').trim().toLowerCase()\n\n const organizationIds = Array.isArray(ctx.organizationIds) ? ctx.organizationIds : null\n if (organizationIds && organizationIds.length === 0) {\n throw json({ items: [], total: 0, page, pageSize, totalPages: 0 })\n }\n\n const em = ctx.container.resolve('em') as EntityManager\n const qb = em.createQueryBuilder(WebhookEntity, 'w')\n qb.where({ deletedAt: null })\n qb.andWhere({ tenantId: auth.tenantId })\n\n if (organizationIds && organizationIds.length > 0) {\n qb.andWhere({ organizationId: { $in: organizationIds } })\n } else if (auth.orgId) {\n qb.andWhere({ organizationId: auth.orgId })\n }\n\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n qb.andWhere({ name: { $ilike: pattern } })\n }\n\n if (query.isActive !== undefined && query.isActive !== '') {\n const active = parseBooleanToken(query.isActive)\n if (active !== null) {\n qb.andWhere({ isActive: active })\n }\n }\n\n qb.orderBy({ createdAt: 'desc' })\n qb.limit(pageSize).offset((page - 1) * pageSize)\n const [items, total] = await qb.getResultAndCount()\n\n const payload = {\n items: items.map((item) => ({\n id: item.id,\n name: item.name,\n description: item.description ?? null,\n url: item.url,\n subscribedEvents: item.subscribedEvents,\n httpMethod: item.httpMethod,\n isActive: item.isActive,\n deliveryStrategy: item.deliveryStrategy,\n maxRetries: item.maxRetries,\n consecutiveFailures: item.consecutiveFailures,\n lastSuccessAt: item.lastSuccessAt?.toISOString() ?? null,\n lastFailureAt: item.lastFailureAt?.toISOString() ?? null,\n createdAt: item.createdAt.toISOString(),\n updatedAt: item.updatedAt.toISOString(),\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n }\n\n throw json(payload)\n },\n beforeCreate: async (input, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n if (!auth?.orgId) throw json({ error: translate('webhooks.errors.orgRequired', 'Organization context required') }, { status: 400 })\n\n const scopedCtx = ctx as WebhookCrudCtx\n scopedCtx.__webhookSecret = generateWebhookSecret()\n\n return input\n },\n afterCreate: async (entity, ctx) => {\n const scopedCtx = ctx as WebhookCrudCtx\n if (scopedCtx.__webhookSecret) {\n ;(entity as WebhookEntity & { __revealSecret?: string }).__revealSecret = scopedCtx.__webhookSecret\n }\n },\n beforeDelete: async (id, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n\n const em = ctx.container.resolve('em') as EntityManager\n const record = await em.findOne(WebhookEntity, { id, deletedAt: null, tenantId: auth.tenantId })\n if (!record) throw json({ error: translate('webhooks.errors.notFound', 'Webhook not found') }, { status: 404 })\n\n const allowedIds = ctx.organizationScope?.allowedIds ?? null\n if (record.organizationId && Array.isArray(allowedIds) && allowedIds.length > 0) {\n if (!allowedIds.includes(record.organizationId)) {\n throw json({ error: translate('webhooks.errors.forbidden', 'Forbidden') }, { status: 403 })\n }\n }\n },\n },\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage webhooks',\n description: 'CRUD operations for webhook endpoints following the Standard Webhooks specification.',\n methods: {\n GET: {\n summary: 'List webhooks',\n description: 'Returns paginated webhooks for the current tenant and organization.',\n query: webhookListQuerySchema,\n responses: [{ status: 200, description: 'Webhook collection', schema: webhookCollectionResponseSchema }],\n errors: [\n { status: 400, description: 'Tenant context missing', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Create webhook',\n description: 'Creates a new webhook endpoint. A signing secret (whsec_ prefixed) is auto-generated and returned once.',\n requestBody: { contentType: 'application/json', schema: webhookCreateSchema, description: 'Webhook configuration.' },\n responses: [{ status: 201, description: 'Webhook created', schema: webhookCreateResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update webhook',\n description: 'Updates an existing webhook configuration.',\n requestBody: { contentType: 'application/json', schema: webhookUpdateSchema, description: 'Fields to update.' },\n responses: [{ status: 200, description: 'Webhook updated', schema: webhookDetailResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 404, description: 'Not found', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete webhook',\n description: 'Soft-deletes a webhook endpoint.',\n query: z.object({ id: z.string().uuid().describe('Webhook ID to delete') }),\n responses: [{ status: 200, description: 'Deleted', schema: deleteResponseSchema }],\n errors: [\n { status: 404, description: 'Not found', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const metadata = {
|
|
2
|
+
requireAuth: true,
|
|
3
|
+
requireFeatures: ["webhooks.edit"],
|
|
4
|
+
pageTitle: "Edit Webhook",
|
|
5
|
+
pageTitleKey: "webhooks.form.title.edit",
|
|
6
|
+
pageGroup: "Integrations",
|
|
7
|
+
pageGroupKey: "webhooks.nav.group",
|
|
8
|
+
navHidden: true,
|
|
9
|
+
breadcrumb: [
|
|
10
|
+
{ label: "Webhooks", labelKey: "webhooks.nav.title", href: "/backend/webhooks" },
|
|
11
|
+
{ label: "Edit", labelKey: "common.edit" }
|
|
12
|
+
]
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
metadata
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=page.meta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/webhooks/backend/webhooks/%5Bid%5D/page.meta.ts"],
|
|
4
|
+
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['webhooks.edit'],\n pageTitle: 'Edit Webhook',\n pageTitleKey: 'webhooks.form.title.edit',\n pageGroup: 'Integrations',\n pageGroupKey: 'webhooks.nav.group',\n navHidden: true,\n breadcrumb: [\n { label: 'Webhooks', labelKey: 'webhooks.nav.title', href: '/backend/webhooks' },\n { label: 'Edit', labelKey: 'common.edit' },\n ],\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,eAAe;AAAA,EACjC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,YAAY,UAAU,sBAAsB,MAAM,oBAAoB;AAAA,IAC/E,EAAE,OAAO,QAAQ,UAAU,cAAc;AAAA,EAC3C;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const metadata = {
|
|
2
|
+
requireAuth: true,
|
|
3
|
+
requireFeatures: ["webhooks.create"],
|
|
4
|
+
pageTitle: "Create Webhook",
|
|
5
|
+
pageTitleKey: "webhooks.form.title.create",
|
|
6
|
+
pageGroup: "Integrations",
|
|
7
|
+
pageGroupKey: "webhooks.nav.group",
|
|
8
|
+
navHidden: true,
|
|
9
|
+
breadcrumb: [
|
|
10
|
+
{ label: "Webhooks", labelKey: "webhooks.nav.title", href: "/backend/webhooks" },
|
|
11
|
+
{ label: "Create", labelKey: "common.create" }
|
|
12
|
+
]
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
metadata
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=page.meta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/webhooks/backend/webhooks/create/page.meta.ts"],
|
|
4
|
+
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['webhooks.create'],\n pageTitle: 'Create Webhook',\n pageTitleKey: 'webhooks.form.title.create',\n pageGroup: 'Integrations',\n pageGroupKey: 'webhooks.nav.group',\n navHidden: true,\n breadcrumb: [\n { label: 'Webhooks', labelKey: 'webhooks.nav.title', href: '/backend/webhooks' },\n { label: 'Create', labelKey: 'common.create' },\n ],\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,iBAAiB;AAAA,EACnC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,YAAY,UAAU,sBAAsB,MAAM,oBAAoB;AAAA,IAC/E,EAAE,OAAO,UAAU,UAAU,gBAAgB;AAAA,EAC/C;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|