@orthacms/webhooks-server 0.5.0
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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/lib/application/dto/list-deliveries-query.dto.d.ts +8 -0
- package/dist/lib/application/dto/list-deliveries-query.dto.d.ts.map +1 -0
- package/dist/lib/application/dto/list-deliveries-query.dto.js +64 -0
- package/dist/lib/application/dto/save-webhook-endpoint.dto.d.ts +23 -0
- package/dist/lib/application/dto/save-webhook-endpoint.dto.d.ts.map +1 -0
- package/dist/lib/application/dto/save-webhook-endpoint.dto.js +127 -0
- package/dist/lib/application/webhook-endpoints.service.d.ts +82 -0
- package/dist/lib/application/webhook-endpoints.service.d.ts.map +1 -0
- package/dist/lib/application/webhook-endpoints.service.js +213 -0
- package/dist/lib/docs/describe-webhooks-api.d.ts +16 -0
- package/dist/lib/docs/describe-webhooks-api.d.ts.map +1 -0
- package/dist/lib/docs/describe-webhooks-api.js +170 -0
- package/dist/lib/docs/webhook-schemas.d.ts +21 -0
- package/dist/lib/docs/webhook-schemas.d.ts.map +1 -0
- package/dist/lib/docs/webhook-schemas.js +360 -0
- package/dist/lib/domain/errors/index.d.ts +3 -0
- package/dist/lib/domain/errors/index.d.ts.map +1 -0
- package/dist/lib/domain/errors/index.js +7 -0
- package/dist/lib/domain/errors/webhook-delivery-not-found.error.d.ts +12 -0
- package/dist/lib/domain/errors/webhook-delivery-not-found.error.d.ts.map +1 -0
- package/dist/lib/domain/errors/webhook-delivery-not-found.error.js +19 -0
- package/dist/lib/domain/errors/webhook-endpoint-not-found.error.d.ts +6 -0
- package/dist/lib/domain/errors/webhook-endpoint-not-found.error.d.ts.map +1 -0
- package/dist/lib/domain/errors/webhook-endpoint-not-found.error.js +13 -0
- package/dist/lib/domain/webhook-secret.d.ts +29 -0
- package/dist/lib/domain/webhook-secret.d.ts.map +1 -0
- package/dist/lib/domain/webhook-secret.js +34 -0
- package/dist/lib/domain/webhook-views.d.ts +85 -0
- package/dist/lib/domain/webhook-views.d.ts.map +1 -0
- package/dist/lib/domain/webhook-views.js +2 -0
- package/dist/lib/http/controllers/webhook-deliveries.controller.d.ts +28 -0
- package/dist/lib/http/controllers/webhook-deliveries.controller.d.ts.map +1 -0
- package/dist/lib/http/controllers/webhook-deliveries.controller.js +110 -0
- package/dist/lib/http/controllers/webhook-endpoints.controller.d.ts +61 -0
- package/dist/lib/http/controllers/webhook-endpoints.controller.d.ts.map +1 -0
- package/dist/lib/http/controllers/webhook-endpoints.controller.js +213 -0
- package/dist/lib/http/controllers/webhook-events.controller.d.ts +13 -0
- package/dist/lib/http/controllers/webhook-events.controller.d.ts.map +1 -0
- package/dist/lib/http/controllers/webhook-events.controller.js +38 -0
- package/dist/lib/infrastructure/event-mapping.d.ts +13 -0
- package/dist/lib/infrastructure/event-mapping.d.ts.map +1 -0
- package/dist/lib/infrastructure/event-mapping.js +69 -0
- package/dist/lib/infrastructure/purge/webhook-workspaces.purger.d.ts +42 -0
- package/dist/lib/infrastructure/purge/webhook-workspaces.purger.d.ts.map +1 -0
- package/dist/lib/infrastructure/purge/webhook-workspaces.purger.js +66 -0
- package/dist/lib/infrastructure/schema/index.d.ts +3 -0
- package/dist/lib/infrastructure/schema/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/schema/index.js +8 -0
- package/dist/lib/infrastructure/schema/webhook-deliveries.d.ts +332 -0
- package/dist/lib/infrastructure/schema/webhook-deliveries.d.ts.map +1 -0
- package/dist/lib/infrastructure/schema/webhook-deliveries.js +92 -0
- package/dist/lib/infrastructure/schema/webhook-endpoints.d.ts +330 -0
- package/dist/lib/infrastructure/schema/webhook-endpoints.d.ts.map +1 -0
- package/dist/lib/infrastructure/schema/webhook-endpoints.js +94 -0
- package/dist/lib/infrastructure/webhook-delivery.repository.d.ts +99 -0
- package/dist/lib/infrastructure/webhook-delivery.repository.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-delivery.repository.js +255 -0
- package/dist/lib/infrastructure/webhook-delivery.worker.d.ts +72 -0
- package/dist/lib/infrastructure/webhook-delivery.worker.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-delivery.worker.js +216 -0
- package/dist/lib/infrastructure/webhook-endpoint.repository.d.ts +88 -0
- package/dist/lib/infrastructure/webhook-endpoint.repository.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-endpoint.repository.js +306 -0
- package/dist/lib/infrastructure/webhook-fanout.subscriber.d.ts +36 -0
- package/dist/lib/infrastructure/webhook-fanout.subscriber.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-fanout.subscriber.js +83 -0
- package/dist/lib/infrastructure/webhook-http.client.d.ts +76 -0
- package/dist/lib/infrastructure/webhook-http.client.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-http.client.js +225 -0
- package/dist/lib/types/webhooks-config.d.ts +64 -0
- package/dist/lib/types/webhooks-config.d.ts.map +1 -0
- package/dist/lib/types/webhooks-config.js +24 -0
- package/dist/lib/utils/webhooks-plugin.d.ts +35 -0
- package/dist/lib/utils/webhooks-plugin.d.ts.map +1 -0
- package/dist/lib/utils/webhooks-plugin.js +90 -0
- package/dist/lib/webhooks.module.d.ts +18 -0
- package/dist/lib/webhooks.module.d.ts.map +1 -0
- package/dist/lib/webhooks.module.js +72 -0
- package/dist/lib/webhooks.tokens.d.ts +5 -0
- package/dist/lib/webhooks.tokens.d.ts.map +1 -0
- package/dist/lib/webhooks.tokens.js +9 -0
- package/migrations/0000_init_webhooks.sql +53 -0
- package/migrations/meta/0000_snapshot.json +424 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +45 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One configured destination for outgoing webhooks.
|
|
3
|
+
*
|
|
4
|
+
* Global rather than workspace-scoped: an endpoint spans whichever workspaces
|
|
5
|
+
* it was pointed at, holds a signing secret, and is deployment configuration
|
|
6
|
+
* rather than editorial content — the same reasoning that puts API tokens in
|
|
7
|
+
* the directory section instead of inside a workspace.
|
|
8
|
+
*
|
|
9
|
+
* **The secret is stored in the clear.** That is a decision, not an oversight:
|
|
10
|
+
* an API token is only ever *verified*, so a hash is enough, but a webhook
|
|
11
|
+
* secret is used to *sign*, and a signing key that cannot be read back cannot
|
|
12
|
+
* sign anything. What is done instead is to show it exactly once on mint, never
|
|
13
|
+
* return it from the API again (`secretHint` is what the UI shows), and let a
|
|
14
|
+
* deployment encrypt the column at rest.
|
|
15
|
+
*
|
|
16
|
+
* `createdBy` is a plain uuid with no FK, like `alarm_rules.createdBy`: the
|
|
17
|
+
* person who added the endpoint may be deleted and the endpoint must outlive
|
|
18
|
+
* them.
|
|
19
|
+
*/
|
|
20
|
+
export declare const webhookEndpoints: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
21
|
+
name: "webhook_endpoints";
|
|
22
|
+
schema: undefined;
|
|
23
|
+
columns: {
|
|
24
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
25
|
+
name: "id";
|
|
26
|
+
tableName: "webhook_endpoints";
|
|
27
|
+
dataType: "string";
|
|
28
|
+
columnType: "PgUUID";
|
|
29
|
+
data: string;
|
|
30
|
+
driverParam: string;
|
|
31
|
+
notNull: true;
|
|
32
|
+
hasDefault: true;
|
|
33
|
+
isPrimaryKey: true;
|
|
34
|
+
isAutoincrement: false;
|
|
35
|
+
hasRuntimeDefault: false;
|
|
36
|
+
enumValues: undefined;
|
|
37
|
+
baseColumn: never;
|
|
38
|
+
identity: undefined;
|
|
39
|
+
generated: undefined;
|
|
40
|
+
}, {}, {}>;
|
|
41
|
+
name: import("drizzle-orm/pg-core").PgColumn<{
|
|
42
|
+
name: "name";
|
|
43
|
+
tableName: "webhook_endpoints";
|
|
44
|
+
dataType: "string";
|
|
45
|
+
columnType: "PgText";
|
|
46
|
+
data: string;
|
|
47
|
+
driverParam: string;
|
|
48
|
+
notNull: true;
|
|
49
|
+
hasDefault: false;
|
|
50
|
+
isPrimaryKey: false;
|
|
51
|
+
isAutoincrement: false;
|
|
52
|
+
hasRuntimeDefault: false;
|
|
53
|
+
enumValues: [string, ...string[]];
|
|
54
|
+
baseColumn: never;
|
|
55
|
+
identity: undefined;
|
|
56
|
+
generated: undefined;
|
|
57
|
+
}, {}, {}>;
|
|
58
|
+
url: import("drizzle-orm/pg-core").PgColumn<{
|
|
59
|
+
name: "url";
|
|
60
|
+
tableName: "webhook_endpoints";
|
|
61
|
+
dataType: "string";
|
|
62
|
+
columnType: "PgText";
|
|
63
|
+
data: string;
|
|
64
|
+
driverParam: string;
|
|
65
|
+
notNull: true;
|
|
66
|
+
hasDefault: false;
|
|
67
|
+
isPrimaryKey: false;
|
|
68
|
+
isAutoincrement: false;
|
|
69
|
+
hasRuntimeDefault: false;
|
|
70
|
+
enumValues: [string, ...string[]];
|
|
71
|
+
baseColumn: never;
|
|
72
|
+
identity: undefined;
|
|
73
|
+
generated: undefined;
|
|
74
|
+
}, {}, {}>;
|
|
75
|
+
secret: import("drizzle-orm/pg-core").PgColumn<{
|
|
76
|
+
name: "secret";
|
|
77
|
+
tableName: "webhook_endpoints";
|
|
78
|
+
dataType: "string";
|
|
79
|
+
columnType: "PgText";
|
|
80
|
+
data: string;
|
|
81
|
+
driverParam: string;
|
|
82
|
+
notNull: true;
|
|
83
|
+
hasDefault: false;
|
|
84
|
+
isPrimaryKey: false;
|
|
85
|
+
isAutoincrement: false;
|
|
86
|
+
hasRuntimeDefault: false;
|
|
87
|
+
enumValues: [string, ...string[]];
|
|
88
|
+
baseColumn: never;
|
|
89
|
+
identity: undefined;
|
|
90
|
+
generated: undefined;
|
|
91
|
+
}, {}, {}>;
|
|
92
|
+
secretHint: import("drizzle-orm/pg-core").PgColumn<{
|
|
93
|
+
name: "secret_hint";
|
|
94
|
+
tableName: "webhook_endpoints";
|
|
95
|
+
dataType: "string";
|
|
96
|
+
columnType: "PgText";
|
|
97
|
+
data: string;
|
|
98
|
+
driverParam: string;
|
|
99
|
+
notNull: true;
|
|
100
|
+
hasDefault: false;
|
|
101
|
+
isPrimaryKey: false;
|
|
102
|
+
isAutoincrement: false;
|
|
103
|
+
hasRuntimeDefault: false;
|
|
104
|
+
enumValues: [string, ...string[]];
|
|
105
|
+
baseColumn: never;
|
|
106
|
+
identity: undefined;
|
|
107
|
+
generated: undefined;
|
|
108
|
+
}, {}, {}>;
|
|
109
|
+
enabled: import("drizzle-orm/pg-core").PgColumn<{
|
|
110
|
+
name: "enabled";
|
|
111
|
+
tableName: "webhook_endpoints";
|
|
112
|
+
dataType: "boolean";
|
|
113
|
+
columnType: "PgBoolean";
|
|
114
|
+
data: boolean;
|
|
115
|
+
driverParam: boolean;
|
|
116
|
+
notNull: true;
|
|
117
|
+
hasDefault: true;
|
|
118
|
+
isPrimaryKey: false;
|
|
119
|
+
isAutoincrement: false;
|
|
120
|
+
hasRuntimeDefault: false;
|
|
121
|
+
enumValues: undefined;
|
|
122
|
+
baseColumn: never;
|
|
123
|
+
identity: undefined;
|
|
124
|
+
generated: undefined;
|
|
125
|
+
}, {}, {}>;
|
|
126
|
+
eventKinds: import("drizzle-orm/pg-core").PgColumn<{
|
|
127
|
+
name: "event_kinds";
|
|
128
|
+
tableName: "webhook_endpoints";
|
|
129
|
+
dataType: "json";
|
|
130
|
+
columnType: "PgJsonb";
|
|
131
|
+
data: unknown;
|
|
132
|
+
driverParam: unknown;
|
|
133
|
+
notNull: true;
|
|
134
|
+
hasDefault: true;
|
|
135
|
+
isPrimaryKey: false;
|
|
136
|
+
isAutoincrement: false;
|
|
137
|
+
hasRuntimeDefault: false;
|
|
138
|
+
enumValues: undefined;
|
|
139
|
+
baseColumn: never;
|
|
140
|
+
identity: undefined;
|
|
141
|
+
generated: undefined;
|
|
142
|
+
}, {}, {}>;
|
|
143
|
+
contentTypes: import("drizzle-orm/pg-core").PgColumn<{
|
|
144
|
+
name: "content_types";
|
|
145
|
+
tableName: "webhook_endpoints";
|
|
146
|
+
dataType: "json";
|
|
147
|
+
columnType: "PgJsonb";
|
|
148
|
+
data: unknown;
|
|
149
|
+
driverParam: unknown;
|
|
150
|
+
notNull: true;
|
|
151
|
+
hasDefault: true;
|
|
152
|
+
isPrimaryKey: false;
|
|
153
|
+
isAutoincrement: false;
|
|
154
|
+
hasRuntimeDefault: false;
|
|
155
|
+
enumValues: undefined;
|
|
156
|
+
baseColumn: never;
|
|
157
|
+
identity: undefined;
|
|
158
|
+
generated: undefined;
|
|
159
|
+
}, {}, {}>;
|
|
160
|
+
allWorkspaces: import("drizzle-orm/pg-core").PgColumn<{
|
|
161
|
+
name: "all_workspaces";
|
|
162
|
+
tableName: "webhook_endpoints";
|
|
163
|
+
dataType: "boolean";
|
|
164
|
+
columnType: "PgBoolean";
|
|
165
|
+
data: boolean;
|
|
166
|
+
driverParam: boolean;
|
|
167
|
+
notNull: true;
|
|
168
|
+
hasDefault: true;
|
|
169
|
+
isPrimaryKey: false;
|
|
170
|
+
isAutoincrement: false;
|
|
171
|
+
hasRuntimeDefault: false;
|
|
172
|
+
enumValues: undefined;
|
|
173
|
+
baseColumn: never;
|
|
174
|
+
identity: undefined;
|
|
175
|
+
generated: undefined;
|
|
176
|
+
}, {}, {}>;
|
|
177
|
+
headers: import("drizzle-orm/pg-core").PgColumn<{
|
|
178
|
+
name: "headers";
|
|
179
|
+
tableName: "webhook_endpoints";
|
|
180
|
+
dataType: "json";
|
|
181
|
+
columnType: "PgJsonb";
|
|
182
|
+
data: unknown;
|
|
183
|
+
driverParam: unknown;
|
|
184
|
+
notNull: true;
|
|
185
|
+
hasDefault: true;
|
|
186
|
+
isPrimaryKey: false;
|
|
187
|
+
isAutoincrement: false;
|
|
188
|
+
hasRuntimeDefault: false;
|
|
189
|
+
enumValues: undefined;
|
|
190
|
+
baseColumn: never;
|
|
191
|
+
identity: undefined;
|
|
192
|
+
generated: undefined;
|
|
193
|
+
}, {}, {}>;
|
|
194
|
+
disabledReason: import("drizzle-orm/pg-core").PgColumn<{
|
|
195
|
+
name: "disabled_reason";
|
|
196
|
+
tableName: "webhook_endpoints";
|
|
197
|
+
dataType: "string";
|
|
198
|
+
columnType: "PgText";
|
|
199
|
+
data: string;
|
|
200
|
+
driverParam: string;
|
|
201
|
+
notNull: false;
|
|
202
|
+
hasDefault: false;
|
|
203
|
+
isPrimaryKey: false;
|
|
204
|
+
isAutoincrement: false;
|
|
205
|
+
hasRuntimeDefault: false;
|
|
206
|
+
enumValues: [string, ...string[]];
|
|
207
|
+
baseColumn: never;
|
|
208
|
+
identity: undefined;
|
|
209
|
+
generated: undefined;
|
|
210
|
+
}, {}, {}>;
|
|
211
|
+
consecutiveFailures: import("drizzle-orm/pg-core").PgColumn<{
|
|
212
|
+
name: "consecutive_failures";
|
|
213
|
+
tableName: "webhook_endpoints";
|
|
214
|
+
dataType: "number";
|
|
215
|
+
columnType: "PgInteger";
|
|
216
|
+
data: number;
|
|
217
|
+
driverParam: string | number;
|
|
218
|
+
notNull: true;
|
|
219
|
+
hasDefault: true;
|
|
220
|
+
isPrimaryKey: false;
|
|
221
|
+
isAutoincrement: false;
|
|
222
|
+
hasRuntimeDefault: false;
|
|
223
|
+
enumValues: undefined;
|
|
224
|
+
baseColumn: never;
|
|
225
|
+
identity: undefined;
|
|
226
|
+
generated: undefined;
|
|
227
|
+
}, {}, {}>;
|
|
228
|
+
createdBy: import("drizzle-orm/pg-core").PgColumn<{
|
|
229
|
+
name: "created_by";
|
|
230
|
+
tableName: "webhook_endpoints";
|
|
231
|
+
dataType: "string";
|
|
232
|
+
columnType: "PgUUID";
|
|
233
|
+
data: string;
|
|
234
|
+
driverParam: string;
|
|
235
|
+
notNull: false;
|
|
236
|
+
hasDefault: false;
|
|
237
|
+
isPrimaryKey: false;
|
|
238
|
+
isAutoincrement: false;
|
|
239
|
+
hasRuntimeDefault: false;
|
|
240
|
+
enumValues: undefined;
|
|
241
|
+
baseColumn: never;
|
|
242
|
+
identity: undefined;
|
|
243
|
+
generated: undefined;
|
|
244
|
+
}, {}, {}>;
|
|
245
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
246
|
+
name: "created_at";
|
|
247
|
+
tableName: "webhook_endpoints";
|
|
248
|
+
dataType: "date";
|
|
249
|
+
columnType: "PgTimestamp";
|
|
250
|
+
data: Date;
|
|
251
|
+
driverParam: string;
|
|
252
|
+
notNull: true;
|
|
253
|
+
hasDefault: true;
|
|
254
|
+
isPrimaryKey: false;
|
|
255
|
+
isAutoincrement: false;
|
|
256
|
+
hasRuntimeDefault: false;
|
|
257
|
+
enumValues: undefined;
|
|
258
|
+
baseColumn: never;
|
|
259
|
+
identity: undefined;
|
|
260
|
+
generated: undefined;
|
|
261
|
+
}, {}, {}>;
|
|
262
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
263
|
+
name: "updated_at";
|
|
264
|
+
tableName: "webhook_endpoints";
|
|
265
|
+
dataType: "date";
|
|
266
|
+
columnType: "PgTimestamp";
|
|
267
|
+
data: Date;
|
|
268
|
+
driverParam: string;
|
|
269
|
+
notNull: true;
|
|
270
|
+
hasDefault: true;
|
|
271
|
+
isPrimaryKey: false;
|
|
272
|
+
isAutoincrement: false;
|
|
273
|
+
hasRuntimeDefault: false;
|
|
274
|
+
enumValues: undefined;
|
|
275
|
+
baseColumn: never;
|
|
276
|
+
identity: undefined;
|
|
277
|
+
generated: undefined;
|
|
278
|
+
}, {}, {}>;
|
|
279
|
+
};
|
|
280
|
+
dialect: "pg";
|
|
281
|
+
}>;
|
|
282
|
+
/**
|
|
283
|
+
* The workspaces an endpoint subscribes to, when it does not take them all.
|
|
284
|
+
*
|
|
285
|
+
* No FK on `workspaceId`: the `workspaces` table belongs to another plugin, and
|
|
286
|
+
* cross-plugin foreign keys are not how this codebase scopes rows — the same
|
|
287
|
+
* convention as `api_token_workspaces` and the generated content tables.
|
|
288
|
+
*/
|
|
289
|
+
export declare const webhookEndpointWorkspaces: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
290
|
+
name: "webhook_endpoint_workspaces";
|
|
291
|
+
schema: undefined;
|
|
292
|
+
columns: {
|
|
293
|
+
endpointId: import("drizzle-orm/pg-core").PgColumn<{
|
|
294
|
+
name: "endpoint_id";
|
|
295
|
+
tableName: "webhook_endpoint_workspaces";
|
|
296
|
+
dataType: "string";
|
|
297
|
+
columnType: "PgUUID";
|
|
298
|
+
data: string;
|
|
299
|
+
driverParam: string;
|
|
300
|
+
notNull: true;
|
|
301
|
+
hasDefault: false;
|
|
302
|
+
isPrimaryKey: false;
|
|
303
|
+
isAutoincrement: false;
|
|
304
|
+
hasRuntimeDefault: false;
|
|
305
|
+
enumValues: undefined;
|
|
306
|
+
baseColumn: never;
|
|
307
|
+
identity: undefined;
|
|
308
|
+
generated: undefined;
|
|
309
|
+
}, {}, {}>;
|
|
310
|
+
workspaceId: import("drizzle-orm/pg-core").PgColumn<{
|
|
311
|
+
name: "workspace_id";
|
|
312
|
+
tableName: "webhook_endpoint_workspaces";
|
|
313
|
+
dataType: "string";
|
|
314
|
+
columnType: "PgUUID";
|
|
315
|
+
data: string;
|
|
316
|
+
driverParam: string;
|
|
317
|
+
notNull: true;
|
|
318
|
+
hasDefault: false;
|
|
319
|
+
isPrimaryKey: false;
|
|
320
|
+
isAutoincrement: false;
|
|
321
|
+
hasRuntimeDefault: false;
|
|
322
|
+
enumValues: undefined;
|
|
323
|
+
baseColumn: never;
|
|
324
|
+
identity: undefined;
|
|
325
|
+
generated: undefined;
|
|
326
|
+
}, {}, {}>;
|
|
327
|
+
};
|
|
328
|
+
dialect: "pg";
|
|
329
|
+
}>;
|
|
330
|
+
//# sourceMappingURL=webhook-endpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-endpoints.d.ts","sourceRoot":"","sources":["../../../../src/lib/infrastructure/schema/webhook-endpoints.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyD5B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAarC,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webhookEndpointWorkspaces = exports.webhookEndpoints = void 0;
|
|
4
|
+
const pg_core_1 = require("drizzle-orm/pg-core");
|
|
5
|
+
/**
|
|
6
|
+
* One configured destination for outgoing webhooks.
|
|
7
|
+
*
|
|
8
|
+
* Global rather than workspace-scoped: an endpoint spans whichever workspaces
|
|
9
|
+
* it was pointed at, holds a signing secret, and is deployment configuration
|
|
10
|
+
* rather than editorial content — the same reasoning that puts API tokens in
|
|
11
|
+
* the directory section instead of inside a workspace.
|
|
12
|
+
*
|
|
13
|
+
* **The secret is stored in the clear.** That is a decision, not an oversight:
|
|
14
|
+
* an API token is only ever *verified*, so a hash is enough, but a webhook
|
|
15
|
+
* secret is used to *sign*, and a signing key that cannot be read back cannot
|
|
16
|
+
* sign anything. What is done instead is to show it exactly once on mint, never
|
|
17
|
+
* return it from the API again (`secretHint` is what the UI shows), and let a
|
|
18
|
+
* deployment encrypt the column at rest.
|
|
19
|
+
*
|
|
20
|
+
* `createdBy` is a plain uuid with no FK, like `alarm_rules.createdBy`: the
|
|
21
|
+
* person who added the endpoint may be deleted and the endpoint must outlive
|
|
22
|
+
* them.
|
|
23
|
+
*/
|
|
24
|
+
exports.webhookEndpoints = (0, pg_core_1.pgTable)('webhook_endpoints', {
|
|
25
|
+
id: (0, pg_core_1.uuid)('id').primaryKey().defaultRandom(),
|
|
26
|
+
/** Human label shown in the list, e.g. "Rebuild the storefront". */
|
|
27
|
+
name: (0, pg_core_1.text)('name').notNull(),
|
|
28
|
+
/** Where deliveries are POSTed. Re-checked against the URL policy on
|
|
29
|
+
* every send, not only when it was saved. */
|
|
30
|
+
url: (0, pg_core_1.text)('url').notNull(),
|
|
31
|
+
/** The HMAC signing key. See the note above on why it is not hashed. */
|
|
32
|
+
secret: (0, pg_core_1.text)('secret').notNull(),
|
|
33
|
+
/** The last few characters, so the UI can tell two secrets apart after
|
|
34
|
+
* a rotation without ever showing one again. */
|
|
35
|
+
secretHint: (0, pg_core_1.text)('secret_hint').notNull(),
|
|
36
|
+
/** The manual switch. Auto-disabling clears this too. */
|
|
37
|
+
enabled: (0, pg_core_1.boolean)('enabled').notNull().default(true),
|
|
38
|
+
/**
|
|
39
|
+
* Subscribed event kinds. **Empty means every kind**, including ones
|
|
40
|
+
* added to the catalogue later — an endpoint that asked for everything
|
|
41
|
+
* should not silently stop covering a new event.
|
|
42
|
+
*/
|
|
43
|
+
eventKinds: (0, pg_core_1.jsonb)('event_kinds').notNull().default([]),
|
|
44
|
+
/** Subscribed content types. **Empty means every type.** */
|
|
45
|
+
contentTypes: (0, pg_core_1.jsonb)('content_types').notNull().default([]),
|
|
46
|
+
/**
|
|
47
|
+
* Whether the endpoint takes every workspace.
|
|
48
|
+
*
|
|
49
|
+
* Stored explicitly rather than inferred from an empty
|
|
50
|
+
* {@link webhookEndpointWorkspaces} set: fan-out runs on every event
|
|
51
|
+
* and must never have to disambiguate "no rows" between "all" and
|
|
52
|
+
* "none".
|
|
53
|
+
*/
|
|
54
|
+
allWorkspaces: (0, pg_core_1.boolean)('all_workspaces').notNull().default(false),
|
|
55
|
+
/** Extra static headers, filtered through the reserved-name allow-list. */
|
|
56
|
+
headers: (0, pg_core_1.jsonb)('headers').notNull().default({}),
|
|
57
|
+
/** Why the endpoint was switched off automatically; null otherwise. */
|
|
58
|
+
disabledReason: (0, pg_core_1.text)('disabled_reason'),
|
|
59
|
+
/** Consecutive dead deliveries; any success resets it to zero. */
|
|
60
|
+
consecutiveFailures: (0, pg_core_1.integer)('consecutive_failures')
|
|
61
|
+
.notNull()
|
|
62
|
+
.default(0),
|
|
63
|
+
/** Who created it. No FK — the endpoint outlives its author. */
|
|
64
|
+
createdBy: (0, pg_core_1.uuid)('created_by'),
|
|
65
|
+
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true })
|
|
66
|
+
.notNull()
|
|
67
|
+
.defaultNow(),
|
|
68
|
+
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true })
|
|
69
|
+
.notNull()
|
|
70
|
+
.defaultNow()
|
|
71
|
+
}, (table) => [
|
|
72
|
+
// Fan-out's only query is "every enabled endpoint", run once per
|
|
73
|
+
// content write. The table is small enough that this index mostly
|
|
74
|
+
// documents the access path — it earns its keep on an install with
|
|
75
|
+
// many endpoints, most of them switched off.
|
|
76
|
+
(0, pg_core_1.index)('webhook_endpoints_enabled_idx').on(table.enabled)
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* The workspaces an endpoint subscribes to, when it does not take them all.
|
|
80
|
+
*
|
|
81
|
+
* No FK on `workspaceId`: the `workspaces` table belongs to another plugin, and
|
|
82
|
+
* cross-plugin foreign keys are not how this codebase scopes rows — the same
|
|
83
|
+
* convention as `api_token_workspaces` and the generated content tables.
|
|
84
|
+
*/
|
|
85
|
+
exports.webhookEndpointWorkspaces = (0, pg_core_1.pgTable)('webhook_endpoint_workspaces', {
|
|
86
|
+
endpointId: (0, pg_core_1.uuid)('endpoint_id')
|
|
87
|
+
.notNull()
|
|
88
|
+
.references(() => exports.webhookEndpoints.id, { onDelete: 'cascade' }),
|
|
89
|
+
workspaceId: (0, pg_core_1.uuid)('workspace_id').notNull()
|
|
90
|
+
}, (table) => [
|
|
91
|
+
(0, pg_core_1.primaryKey)({ columns: [table.endpointId, table.workspaceId] }),
|
|
92
|
+
// "Which endpoints take this workspace" — the fan-out lookup.
|
|
93
|
+
(0, pg_core_1.index)('webhook_endpoint_workspaces_workspace_idx').on(table.workspaceId)
|
|
94
|
+
]);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type Database } from '@orthacms/database';
|
|
2
|
+
import type { DeliveryStatus } from '@orthacms/webhooks-domain';
|
|
3
|
+
import type { WebhookDeliveryDetailView, WebhookDeliveryPage } from '../domain/webhook-views';
|
|
4
|
+
/** A row to queue. */
|
|
5
|
+
export interface DeliveryToQueue {
|
|
6
|
+
/**
|
|
7
|
+
* Generated by the caller, not by the database.
|
|
8
|
+
*
|
|
9
|
+
* The envelope frozen in `payload` carries this id and so does the
|
|
10
|
+
* `X-Ortha-Delivery` header, so it has to exist before the row is written
|
|
11
|
+
* or the body and the header would name different deliveries.
|
|
12
|
+
*/
|
|
13
|
+
id: string;
|
|
14
|
+
endpointId: string;
|
|
15
|
+
eventId: string;
|
|
16
|
+
eventKind: string;
|
|
17
|
+
workspaceId: string | null;
|
|
18
|
+
contentType: string | null;
|
|
19
|
+
payload: unknown;
|
|
20
|
+
/** The delivery this one repeats, for a redelivery; null otherwise. */
|
|
21
|
+
redeliveryOf?: string | null;
|
|
22
|
+
}
|
|
23
|
+
/** A claimed row, as the worker needs it. */
|
|
24
|
+
export interface ClaimedDelivery {
|
|
25
|
+
id: string;
|
|
26
|
+
endpointId: string;
|
|
27
|
+
eventId: string;
|
|
28
|
+
eventKind: string;
|
|
29
|
+
workspaceId: string | null;
|
|
30
|
+
payload: unknown;
|
|
31
|
+
attempts: number;
|
|
32
|
+
}
|
|
33
|
+
/** What one attempt produced. */
|
|
34
|
+
export interface AttemptOutcome {
|
|
35
|
+
statusCode: number | null;
|
|
36
|
+
error: string | null;
|
|
37
|
+
responseSnippet: string | null;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
}
|
|
40
|
+
/** Filters the delivery log accepts. */
|
|
41
|
+
export interface DeliveryListQuery {
|
|
42
|
+
status?: DeliveryStatus;
|
|
43
|
+
eventKind?: string;
|
|
44
|
+
page: number;
|
|
45
|
+
pageSize: number;
|
|
46
|
+
}
|
|
47
|
+
/** The queue and the log, over one table. */
|
|
48
|
+
export declare class WebhookDeliveryRepository {
|
|
49
|
+
private readonly db;
|
|
50
|
+
constructor(db: Database);
|
|
51
|
+
/**
|
|
52
|
+
* Queues one delivery per endpoint, ignoring rows that already exist.
|
|
53
|
+
*
|
|
54
|
+
* `ON CONFLICT DO NOTHING` against `unique(endpoint_id, event_id)` is the
|
|
55
|
+
* whole idempotency story for fan-out: outbox delivery is at-least-once, so
|
|
56
|
+
* this subscriber may be handed the same event twice, and the second pass
|
|
57
|
+
* must be a no-op rather than a duplicate POST.
|
|
58
|
+
*/
|
|
59
|
+
enqueue(deliveries: DeliveryToQueue[]): Promise<number>;
|
|
60
|
+
/** Queues a single delivery and returns its id (used by redelivery). */
|
|
61
|
+
enqueueOne(delivery: DeliveryToQueue): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Claims up to `limit` deliveries and marks them `delivering`.
|
|
64
|
+
*
|
|
65
|
+
* The transaction covers **only the claim**. That is the point of the whole
|
|
66
|
+
* design: the HTTP request happens after this returns, with nothing open,
|
|
67
|
+
* so a slow receiver cannot hold a database transaction or a pool client.
|
|
68
|
+
*
|
|
69
|
+
* `FOR UPDATE SKIP LOCKED` lets several API processes claim disjoint rows
|
|
70
|
+
* without blocking each other, exactly as the outbox drain does.
|
|
71
|
+
*
|
|
72
|
+
* Rows stuck in `delivering` past `claimTimeoutMs` are reclaimed here
|
|
73
|
+
* rather than by a separate reaper: a worker that died mid-request left the
|
|
74
|
+
* row with no error, no retry and no way out, and folding the recovery into
|
|
75
|
+
* the claim means there is one query to get right instead of two.
|
|
76
|
+
*/
|
|
77
|
+
claim(limit: number, claimTimeoutMs: number): Promise<ClaimedDelivery[]>;
|
|
78
|
+
/** Records a successful attempt. */
|
|
79
|
+
markSucceeded(id: string, outcome: AttemptOutcome): Promise<void>;
|
|
80
|
+
/** Records a failed attempt that will be tried again at `nextAttemptAt`. */
|
|
81
|
+
markRetrying(id: string, outcome: AttemptOutcome, nextAttemptAt: Date): Promise<void>;
|
|
82
|
+
/** Records a final failure — no further attempt will be made. */
|
|
83
|
+
markDead(id: string, outcome: AttemptOutcome): Promise<void>;
|
|
84
|
+
/** One page of an endpoint's delivery log, newest first. */
|
|
85
|
+
list(endpointId: string, query: DeliveryListQuery): Promise<WebhookDeliveryPage>;
|
|
86
|
+
/**
|
|
87
|
+
* One delivery with its bodies — scoped to its endpoint so an id from
|
|
88
|
+
* another endpoint reads as "not found" rather than leaking across.
|
|
89
|
+
*/
|
|
90
|
+
findDetail(endpointId: string, deliveryId: string): Promise<WebhookDeliveryDetailView | null>;
|
|
91
|
+
/**
|
|
92
|
+
* Deletes completed rows older than `before`.
|
|
93
|
+
*
|
|
94
|
+
* Only `succeeded` and `dead` are eligible: a `failed` row is still
|
|
95
|
+
* scheduled, and a `delivering` row may be in flight.
|
|
96
|
+
*/
|
|
97
|
+
pruneCompletedBefore(before: Date): Promise<number>;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=webhook-delivery.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-delivery.repository.d.ts","sourceRoot":"","sources":["../../../src/lib/infrastructure/webhook-delivery.repository.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAahE,OAAO,KAAK,EACR,yBAAyB,EACzB,mBAAmB,EAEtB,MAAM,yBAAyB,CAAC;AAGjC,sBAAsB;AACtB,MAAM,WAAW,eAAe;IAC5B;;;;;;OAMG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,iCAAiC;AACjC,MAAM,WAAW,cAAc;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,wCAAwC;AACxC,MAAM,WAAW,iBAAiB;IAC9B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,6CAA6C;AAC7C,qBACa,yBAAyB;IACJ,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ;IAE3D;;;;;;;OAOG;IACG,OAAO,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAgC7D,wEAAwE;IAClE,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAkB5D;;;;;;;;;;;;;;OAcG;IACG,KAAK,CACP,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC;IAsD7B,oCAAoC;IAC9B,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBvE,4EAA4E;IACtE,YAAY,CACd,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,IAAI,GACpB,OAAO,CAAC,IAAI,CAAC;IAgBhB,iEAAiE;IAC3D,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBlE,4DAA4D;IACtD,IAAI,CACN,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,iBAAiB,GACzB,OAAO,CAAC,mBAAmB,CAAC;IAsC/B;;;OAGG;IACG,UAAU,CACZ,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC;IAoB5C;;;;;OAKG;IACG,oBAAoB,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;CAa5D"}
|