@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,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SECRET_PREFIX = void 0;
|
|
4
|
+
exports.generateSecret = generateSecret;
|
|
5
|
+
exports.secretHint = secretHint;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
/**
|
|
8
|
+
* The prefix every signing secret carries, so one found in a log or a config
|
|
9
|
+
* file is recognisable for what it is — and so a scanner can be taught to spot
|
|
10
|
+
* a leaked one.
|
|
11
|
+
*/
|
|
12
|
+
exports.SECRET_PREFIX = 'whsec_';
|
|
13
|
+
/** How many characters of the secret the UI may show after mint. */
|
|
14
|
+
const HINT_LENGTH = 4;
|
|
15
|
+
/**
|
|
16
|
+
* Mints a signing secret.
|
|
17
|
+
*
|
|
18
|
+
* 32 bytes of `randomBytes` — the same order of entropy as the API tokens, and
|
|
19
|
+
* far more than an HMAC key needs. Base64url so it survives a shell, a YAML
|
|
20
|
+
* file and an environment variable without quoting.
|
|
21
|
+
*/
|
|
22
|
+
function generateSecret() {
|
|
23
|
+
const secret = `${exports.SECRET_PREFIX}${(0, node_crypto_1.randomBytes)(32).toString('base64url')}`;
|
|
24
|
+
return { secret, hint: secretHint(secret) };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The displayable tail of a secret.
|
|
28
|
+
*
|
|
29
|
+
* Four characters is enough to tell "the one I rotated to" from "the one I
|
|
30
|
+
* replaced" and far too little to shorten a brute force against a 256-bit key.
|
|
31
|
+
*/
|
|
32
|
+
function secretHint(secret) {
|
|
33
|
+
return secret.slice(-HINT_LENGTH);
|
|
34
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { DeliveryStatus } from '@orthacms/webhooks-domain';
|
|
2
|
+
/**
|
|
3
|
+
* The read shapes the application layer hands to the HTTP layer.
|
|
4
|
+
*
|
|
5
|
+
* They are what the API returns, so the one rule that matters here is what is
|
|
6
|
+
* **missing**: no `secret`. It is written on mint and on rotation, returned in
|
|
7
|
+
* that one response, and never present in a shape that a `GET` can reach.
|
|
8
|
+
*/
|
|
9
|
+
/** An endpoint as the list and detail routes return it. */
|
|
10
|
+
export interface WebhookEndpointView {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
url: string;
|
|
14
|
+
/** The trailing characters of the secret. Never the secret itself. */
|
|
15
|
+
secretHint: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
/** Empty means every kind. */
|
|
18
|
+
eventKinds: string[];
|
|
19
|
+
/** Empty means every type. */
|
|
20
|
+
contentTypes: string[];
|
|
21
|
+
allWorkspaces: boolean;
|
|
22
|
+
/** Empty when {@link allWorkspaces} is true. */
|
|
23
|
+
workspaceIds: string[];
|
|
24
|
+
headers: Record<string, string>;
|
|
25
|
+
/** Set when the endpoint switched itself off; null otherwise. */
|
|
26
|
+
disabledReason: string | null;
|
|
27
|
+
consecutiveFailures: number;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
/** The most recent delivery's outcome, for the list's status column. */
|
|
31
|
+
lastDelivery: WebhookLastDeliveryView | null;
|
|
32
|
+
}
|
|
33
|
+
/** The one-line delivery summary shown against an endpoint in the list. */
|
|
34
|
+
export interface WebhookLastDeliveryView {
|
|
35
|
+
id: string;
|
|
36
|
+
status: DeliveryStatus;
|
|
37
|
+
eventKind: string;
|
|
38
|
+
statusCode: number | null;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
}
|
|
41
|
+
/** A delivery as the log's list route returns it — no bodies. */
|
|
42
|
+
export interface WebhookDeliveryView {
|
|
43
|
+
id: string;
|
|
44
|
+
endpointId: string;
|
|
45
|
+
eventId: string;
|
|
46
|
+
eventKind: string;
|
|
47
|
+
workspaceId: string | null;
|
|
48
|
+
contentType: string | null;
|
|
49
|
+
status: DeliveryStatus;
|
|
50
|
+
attempts: number;
|
|
51
|
+
nextAttemptAt: string | null;
|
|
52
|
+
statusCode: number | null;
|
|
53
|
+
error: string | null;
|
|
54
|
+
durationMs: number | null;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
completedAt: string | null;
|
|
57
|
+
}
|
|
58
|
+
/** A delivery with its request and response bodies, for the detail panel. */
|
|
59
|
+
export interface WebhookDeliveryDetailView extends WebhookDeliveryView {
|
|
60
|
+
/** The frozen envelope — exactly what was, or will be, posted. */
|
|
61
|
+
payload: unknown;
|
|
62
|
+
/** The first couple of kilobytes of the receiver's response. */
|
|
63
|
+
responseSnippet: string | null;
|
|
64
|
+
}
|
|
65
|
+
/** A page of deliveries. */
|
|
66
|
+
export interface WebhookDeliveryPage {
|
|
67
|
+
items: WebhookDeliveryView[];
|
|
68
|
+
total: number;
|
|
69
|
+
page: number;
|
|
70
|
+
pageSize: number;
|
|
71
|
+
pageCount: number;
|
|
72
|
+
}
|
|
73
|
+
/** The result of "Send test" — reported synchronously, never queued. */
|
|
74
|
+
export interface WebhookTestResult {
|
|
75
|
+
/** Whether the receiver answered 2xx. */
|
|
76
|
+
ok: boolean;
|
|
77
|
+
/** The HTTP status, or null when nothing answered. */
|
|
78
|
+
statusCode: number | null;
|
|
79
|
+
/** Why it failed, when it did. */
|
|
80
|
+
error: string | null;
|
|
81
|
+
durationMs: number;
|
|
82
|
+
/** The first couple of kilobytes of the response. */
|
|
83
|
+
responseSnippet: string | null;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=webhook-views.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-views.d.ts","sourceRoot":"","sources":["../../../src/lib/domain/webhook-views.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE;;;;;;GAMG;AAEH,2DAA2D;AAC3D,MAAM,WAAW,mBAAmB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,8BAA8B;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,iEAAiE;IACjE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,YAAY,EAAE,uBAAuB,GAAG,IAAI,CAAC;CAChD;AAED,2EAA2E;AAC3E,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAChC,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,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,6EAA6E;AAC7E,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IAClE,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,4BAA4B;AAC5B,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAC9B,yCAAyC;IACzC,EAAE,EAAE,OAAO,CAAC;IACZ,sDAAsD;IACtD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kCAAkC;IAClC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { WebhookEndpointsService } from '../../application/webhook-endpoints.service';
|
|
2
|
+
import { ListDeliveriesQueryDto } from '../../application/dto/list-deliveries-query.dto';
|
|
3
|
+
import type { WebhookDeliveryDetailView, WebhookDeliveryPage, WebhookDeliveryView } from '../../domain/webhook-views';
|
|
4
|
+
/**
|
|
5
|
+
* The delivery log: `/api/webhooks/:id/deliveries`.
|
|
6
|
+
*
|
|
7
|
+
* Every route is scoped to the endpoint in the path, so a delivery id belonging
|
|
8
|
+
* to another endpoint reads as "not found" rather than resolving — the log is
|
|
9
|
+
* not a way to enumerate ids across endpoints.
|
|
10
|
+
*/
|
|
11
|
+
export declare class WebhookDeliveriesController {
|
|
12
|
+
private readonly endpoints;
|
|
13
|
+
constructor(endpoints: WebhookEndpointsService);
|
|
14
|
+
/** One page of the endpoint's log, newest first. */
|
|
15
|
+
list(endpointId: string, query: ListDeliveriesQueryDto): Promise<WebhookDeliveryPage>;
|
|
16
|
+
/** One delivery, with the body that was sent and the response. */
|
|
17
|
+
get(endpointId: string, deliveryId: string): Promise<WebhookDeliveryDetailView>;
|
|
18
|
+
/**
|
|
19
|
+
* Queues the same event again.
|
|
20
|
+
*
|
|
21
|
+
* Needs `webhooks:manage`, not `webhooks:read`: it causes an outgoing
|
|
22
|
+
* request to someone else's system, which is a write however it is spelled.
|
|
23
|
+
*/
|
|
24
|
+
redeliver(endpointId: string, deliveryId: string): Promise<WebhookDeliveryView>;
|
|
25
|
+
/** Maps this plugin's not-found errors onto 404. */
|
|
26
|
+
private run;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=webhook-deliveries.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-deliveries.controller.d.ts","sourceRoot":"","sources":["../../../../src/lib/http/controllers/webhook-deliveries.controller.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AAKzF,OAAO,KAAK,EACR,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACtB,MAAM,4BAA4B,CAAC;AAKpC;;;;;;GAMG;AACH,qBAGa,2BAA2B;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,uBAAuB;IAE/D,oDAAoD;IAS9C,IAAI,CACsB,UAAU,EAAE,MAAM,EACrC,KAAK,EAAE,sBAAsB,GACvC,OAAO,CAAC,mBAAmB,CAAC;IAW/B,kEAAkE;IAS5D,GAAG,CACuB,UAAU,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,GACvD,OAAO,CAAC,yBAAyB,CAAC;IAMrC;;;;;OAKG;IAUG,SAAS,CACiB,UAAU,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,GACvD,OAAO,CAAC,mBAAmB,CAAC;IAI/B,oDAAoD;YACtC,GAAG;CAapB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookDeliveriesController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
8
|
+
const webhook_endpoints_service_1 = require("../../application/webhook-endpoints.service");
|
|
9
|
+
const list_deliveries_query_dto_1 = require("../../application/dto/list-deliveries-query.dto");
|
|
10
|
+
const errors_1 = require("../../domain/errors");
|
|
11
|
+
/** Default rows per page when the caller does not say. */
|
|
12
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
13
|
+
/**
|
|
14
|
+
* The delivery log: `/api/webhooks/:id/deliveries`.
|
|
15
|
+
*
|
|
16
|
+
* Every route is scoped to the endpoint in the path, so a delivery id belonging
|
|
17
|
+
* to another endpoint reads as "not found" rather than resolving — the log is
|
|
18
|
+
* not a way to enumerate ids across endpoints.
|
|
19
|
+
*/
|
|
20
|
+
let WebhookDeliveriesController = class WebhookDeliveriesController {
|
|
21
|
+
endpoints;
|
|
22
|
+
constructor(endpoints) {
|
|
23
|
+
this.endpoints = endpoints;
|
|
24
|
+
}
|
|
25
|
+
/** One page of the endpoint's log, newest first. */
|
|
26
|
+
async list(endpointId, query) {
|
|
27
|
+
return this.run(() => this.endpoints.listDeliveries(endpointId, {
|
|
28
|
+
status: query.status,
|
|
29
|
+
eventKind: query.eventKind,
|
|
30
|
+
page: query.page ?? 1,
|
|
31
|
+
pageSize: query.pageSize ?? DEFAULT_PAGE_SIZE
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
/** One delivery, with the body that was sent and the response. */
|
|
35
|
+
async get(endpointId, deliveryId) {
|
|
36
|
+
return this.run(() => this.endpoints.getDelivery(endpointId, deliveryId));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Queues the same event again.
|
|
40
|
+
*
|
|
41
|
+
* Needs `webhooks:manage`, not `webhooks:read`: it causes an outgoing
|
|
42
|
+
* request to someone else's system, which is a write however it is spelled.
|
|
43
|
+
*/
|
|
44
|
+
async redeliver(endpointId, deliveryId) {
|
|
45
|
+
return this.run(() => this.endpoints.redeliver(endpointId, deliveryId));
|
|
46
|
+
}
|
|
47
|
+
/** Maps this plugin's not-found errors onto 404. */
|
|
48
|
+
async run(operation) {
|
|
49
|
+
try {
|
|
50
|
+
return await operation();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof errors_1.WebhookEndpointNotFoundError ||
|
|
54
|
+
error instanceof errors_1.WebhookDeliveryNotFoundError) {
|
|
55
|
+
throw new common_1.NotFoundException(error.message);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.WebhookDeliveriesController = WebhookDeliveriesController;
|
|
62
|
+
tslib_1.__decorate([
|
|
63
|
+
(0, swagger_1.ApiOperation)({
|
|
64
|
+
summary: 'List webhook deliveries',
|
|
65
|
+
description: 'One page of the endpoint’s delivery log, newest first, ' +
|
|
66
|
+
'optionally filtered by state or event kind.'
|
|
67
|
+
}),
|
|
68
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_READ),
|
|
69
|
+
(0, common_1.Get)(),
|
|
70
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
71
|
+
tslib_1.__param(1, (0, common_1.Query)()),
|
|
72
|
+
tslib_1.__metadata("design:type", Function),
|
|
73
|
+
tslib_1.__metadata("design:paramtypes", [String, list_deliveries_query_dto_1.ListDeliveriesQueryDto]),
|
|
74
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
75
|
+
], WebhookDeliveriesController.prototype, "list", null);
|
|
76
|
+
tslib_1.__decorate([
|
|
77
|
+
(0, swagger_1.ApiOperation)({
|
|
78
|
+
summary: 'Get a webhook delivery',
|
|
79
|
+
description: 'The delivery with its frozen request body and the first couple ' +
|
|
80
|
+
'of kilobytes of the response.'
|
|
81
|
+
}),
|
|
82
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_READ),
|
|
83
|
+
(0, common_1.Get)(':deliveryId'),
|
|
84
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
85
|
+
tslib_1.__param(1, (0, common_1.Param)('deliveryId', common_1.ParseUUIDPipe)),
|
|
86
|
+
tslib_1.__metadata("design:type", Function),
|
|
87
|
+
tslib_1.__metadata("design:paramtypes", [String, String]),
|
|
88
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
89
|
+
], WebhookDeliveriesController.prototype, "get", null);
|
|
90
|
+
tslib_1.__decorate([
|
|
91
|
+
(0, swagger_1.ApiOperation)({
|
|
92
|
+
summary: 'Send a delivery again',
|
|
93
|
+
description: 'Queues a fresh delivery with the same body and the same event ' +
|
|
94
|
+
'id, so a receiver that deduplicates still recognises the repeat.'
|
|
95
|
+
}),
|
|
96
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_MANAGE),
|
|
97
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
98
|
+
(0, common_1.Post)(':deliveryId/redeliver'),
|
|
99
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
100
|
+
tslib_1.__param(1, (0, common_1.Param)('deliveryId', common_1.ParseUUIDPipe)),
|
|
101
|
+
tslib_1.__metadata("design:type", Function),
|
|
102
|
+
tslib_1.__metadata("design:paramtypes", [String, String]),
|
|
103
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
104
|
+
], WebhookDeliveriesController.prototype, "redeliver", null);
|
|
105
|
+
exports.WebhookDeliveriesController = WebhookDeliveriesController = tslib_1.__decorate([
|
|
106
|
+
(0, swagger_1.ApiTags)('webhooks'),
|
|
107
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard),
|
|
108
|
+
(0, common_1.Controller)('webhooks/:id/deliveries'),
|
|
109
|
+
tslib_1.__metadata("design:paramtypes", [webhook_endpoints_service_1.WebhookEndpointsService])
|
|
110
|
+
], WebhookDeliveriesController);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { WebhookEndpointsService } from '../../application/webhook-endpoints.service';
|
|
3
|
+
import { SaveWebhookEndpointDto } from '../../application/dto/save-webhook-endpoint.dto';
|
|
4
|
+
import type { WebhookEndpointView, WebhookTestResult } from '../../domain/webhook-views';
|
|
5
|
+
/** An endpoint, plus the secret — the create and rotate responses only. */
|
|
6
|
+
interface EndpointWithSecretResponse {
|
|
7
|
+
endpoint: WebhookEndpointView;
|
|
8
|
+
secret: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The endpoint surface: `/api/webhooks`.
|
|
12
|
+
*
|
|
13
|
+
* Global rather than workspace-scoped, so there is no `WorkspaceGuard` here:
|
|
14
|
+
* an endpoint spans whichever workspaces it names, and which systems this
|
|
15
|
+
* installation notifies is a deployment decision. Both permissions are
|
|
16
|
+
* admin-only for the same reason — the rows hold a signing secret and reach
|
|
17
|
+
* across every workspace.
|
|
18
|
+
*
|
|
19
|
+
* `OriginGuard` is applied per write route, matching content's and alarms'
|
|
20
|
+
* controllers: these are cookie-authenticated and therefore CSRF-able, while
|
|
21
|
+
* the reads change nothing.
|
|
22
|
+
*/
|
|
23
|
+
export declare class WebhookEndpointsController {
|
|
24
|
+
private readonly endpoints;
|
|
25
|
+
constructor(endpoints: WebhookEndpointsService);
|
|
26
|
+
/** Every endpoint, newest first, each with its last delivery. */
|
|
27
|
+
list(): Promise<WebhookEndpointView[]>;
|
|
28
|
+
/** One endpoint. */
|
|
29
|
+
get(id: string): Promise<WebhookEndpointView>;
|
|
30
|
+
/**
|
|
31
|
+
* Creates an endpoint and mints its signing secret.
|
|
32
|
+
*
|
|
33
|
+
* **This is the only response that ever contains the secret.** Every other
|
|
34
|
+
* route returns `secretHint` instead, so a secret cannot be recovered by
|
|
35
|
+
* anyone who missed it — including whoever created it.
|
|
36
|
+
*/
|
|
37
|
+
create(body: SaveWebhookEndpointDto, user: PublicUser): Promise<EndpointWithSecretResponse>;
|
|
38
|
+
/** Replaces an endpoint's configuration. */
|
|
39
|
+
update(id: string, body: SaveWebhookEndpointDto): Promise<WebhookEndpointView>;
|
|
40
|
+
/** Deletes the endpoint and its delivery log. */
|
|
41
|
+
delete(id: string): Promise<void>;
|
|
42
|
+
/** Rotates the signing secret and returns the new one, once. */
|
|
43
|
+
rotateSecret(id: string): Promise<EndpointWithSecretResponse>;
|
|
44
|
+
/**
|
|
45
|
+
* Sends a synthetic `ping` and reports the result.
|
|
46
|
+
*
|
|
47
|
+
* Synchronous and unqueued: the person who pressed the button is waiting,
|
|
48
|
+
* and it does not count against the endpoint's failure budget.
|
|
49
|
+
*/
|
|
50
|
+
test(id: string): Promise<WebhookTestResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Maps this plugin's domain errors onto HTTP.
|
|
53
|
+
*
|
|
54
|
+
* A rejected URL is a 422 rather than a 400 because the request was
|
|
55
|
+
* well-formed and the value was refused by policy — and the message is
|
|
56
|
+
* written to be shown in the form that produced it.
|
|
57
|
+
*/
|
|
58
|
+
private run;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=webhook-endpoints.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-endpoints.controller.d.ts","sourceRoot":"","sources":["../../../../src/lib/http/controllers/webhook-endpoints.controller.ts"],"names":[],"mappings":"AAeA,OAAO,EAMH,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AAEzF,OAAO,KAAK,EACR,mBAAmB,EACnB,iBAAiB,EACpB,MAAM,4BAA4B,CAAC;AAGpC,2EAA2E;AAC3E,UAAU,0BAA0B;IAChC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,qBAGa,0BAA0B;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,uBAAuB;IAE/D,iEAAiE;IASjE,IAAI,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAItC,oBAAoB;IAId,GAAG,CACuB,EAAE,EAAE,MAAM,GACvC,OAAO,CAAC,mBAAmB,CAAC;IAI/B;;;;;;OAMG;IAUG,MAAM,CACA,IAAI,EAAE,sBAAsB,EACrB,IAAI,EAAE,UAAU,GAChC,OAAO,CAAC,0BAA0B,CAAC;IAMtC,4CAA4C;IAUtC,MAAM,CACoB,EAAE,EAAE,MAAM,EAC9B,IAAI,EAAE,sBAAsB,GACrC,OAAO,CAAC,mBAAmB,CAAC;IAI/B,iDAAiD;IAS3C,MAAM,CAA6B,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInE,gEAAgE;IAW1D,YAAY,CACc,EAAE,EAAE,MAAM,GACvC,OAAO,CAAC,0BAA0B,CAAC;IAItC;;;;;OAKG;IAWG,IAAI,CACsB,EAAE,EAAE,MAAM,GACvC,OAAO,CAAC,iBAAiB,CAAC;IAI7B;;;;;;OAMG;YACW,GAAG;CAapB"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookEndpointsController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
8
|
+
const webhooks_domain_1 = require("@orthacms/webhooks-domain");
|
|
9
|
+
const webhook_endpoints_service_1 = require("../../application/webhook-endpoints.service");
|
|
10
|
+
const save_webhook_endpoint_dto_1 = require("../../application/dto/save-webhook-endpoint.dto");
|
|
11
|
+
const errors_1 = require("../../domain/errors");
|
|
12
|
+
/**
|
|
13
|
+
* The endpoint surface: `/api/webhooks`.
|
|
14
|
+
*
|
|
15
|
+
* Global rather than workspace-scoped, so there is no `WorkspaceGuard` here:
|
|
16
|
+
* an endpoint spans whichever workspaces it names, and which systems this
|
|
17
|
+
* installation notifies is a deployment decision. Both permissions are
|
|
18
|
+
* admin-only for the same reason — the rows hold a signing secret and reach
|
|
19
|
+
* across every workspace.
|
|
20
|
+
*
|
|
21
|
+
* `OriginGuard` is applied per write route, matching content's and alarms'
|
|
22
|
+
* controllers: these are cookie-authenticated and therefore CSRF-able, while
|
|
23
|
+
* the reads change nothing.
|
|
24
|
+
*/
|
|
25
|
+
let WebhookEndpointsController = class WebhookEndpointsController {
|
|
26
|
+
endpoints;
|
|
27
|
+
constructor(endpoints) {
|
|
28
|
+
this.endpoints = endpoints;
|
|
29
|
+
}
|
|
30
|
+
/** Every endpoint, newest first, each with its last delivery. */
|
|
31
|
+
list() {
|
|
32
|
+
return this.endpoints.list();
|
|
33
|
+
}
|
|
34
|
+
/** One endpoint. */
|
|
35
|
+
async get(id) {
|
|
36
|
+
return this.run(() => this.endpoints.get(id));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates an endpoint and mints its signing secret.
|
|
40
|
+
*
|
|
41
|
+
* **This is the only response that ever contains the secret.** Every other
|
|
42
|
+
* route returns `secretHint` instead, so a secret cannot be recovered by
|
|
43
|
+
* anyone who missed it — including whoever created it.
|
|
44
|
+
*/
|
|
45
|
+
async create(body, user) {
|
|
46
|
+
return this.run(() => this.endpoints.create(toWriteModel(body), user.id));
|
|
47
|
+
}
|
|
48
|
+
/** Replaces an endpoint's configuration. */
|
|
49
|
+
async update(id, body) {
|
|
50
|
+
return this.run(() => this.endpoints.update(id, toWriteModel(body)));
|
|
51
|
+
}
|
|
52
|
+
/** Deletes the endpoint and its delivery log. */
|
|
53
|
+
async delete(id) {
|
|
54
|
+
await this.run(() => this.endpoints.delete(id));
|
|
55
|
+
}
|
|
56
|
+
/** Rotates the signing secret and returns the new one, once. */
|
|
57
|
+
async rotateSecret(id) {
|
|
58
|
+
return this.run(() => this.endpoints.rotateSecret(id));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Sends a synthetic `ping` and reports the result.
|
|
62
|
+
*
|
|
63
|
+
* Synchronous and unqueued: the person who pressed the button is waiting,
|
|
64
|
+
* and it does not count against the endpoint's failure budget.
|
|
65
|
+
*/
|
|
66
|
+
async test(id) {
|
|
67
|
+
return this.run(() => this.endpoints.sendTest(id));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Maps this plugin's domain errors onto HTTP.
|
|
71
|
+
*
|
|
72
|
+
* A rejected URL is a 422 rather than a 400 because the request was
|
|
73
|
+
* well-formed and the value was refused by policy — and the message is
|
|
74
|
+
* written to be shown in the form that produced it.
|
|
75
|
+
*/
|
|
76
|
+
async run(operation) {
|
|
77
|
+
try {
|
|
78
|
+
return await operation();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof errors_1.WebhookEndpointNotFoundError) {
|
|
82
|
+
throw new common_1.NotFoundException(error.message);
|
|
83
|
+
}
|
|
84
|
+
if (error instanceof webhooks_domain_1.WebhookUrlRejectedError) {
|
|
85
|
+
throw new common_1.UnprocessableEntityException(error.message);
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
exports.WebhookEndpointsController = WebhookEndpointsController;
|
|
92
|
+
tslib_1.__decorate([
|
|
93
|
+
(0, swagger_1.ApiOperation)({
|
|
94
|
+
summary: 'List webhook endpoints',
|
|
95
|
+
description: 'Every configured endpoint with its filters and the outcome of ' +
|
|
96
|
+
'its most recent delivery. Never includes the signing secret.'
|
|
97
|
+
}),
|
|
98
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_READ),
|
|
99
|
+
(0, common_1.Get)(),
|
|
100
|
+
tslib_1.__metadata("design:type", Function),
|
|
101
|
+
tslib_1.__metadata("design:paramtypes", []),
|
|
102
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
103
|
+
], WebhookEndpointsController.prototype, "list", null);
|
|
104
|
+
tslib_1.__decorate([
|
|
105
|
+
(0, swagger_1.ApiOperation)({ summary: 'Get a webhook endpoint' }),
|
|
106
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_READ),
|
|
107
|
+
(0, common_1.Get)(':id'),
|
|
108
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
109
|
+
tslib_1.__metadata("design:type", Function),
|
|
110
|
+
tslib_1.__metadata("design:paramtypes", [String]),
|
|
111
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
112
|
+
], WebhookEndpointsController.prototype, "get", null);
|
|
113
|
+
tslib_1.__decorate([
|
|
114
|
+
(0, swagger_1.ApiOperation)({
|
|
115
|
+
summary: 'Create a webhook endpoint',
|
|
116
|
+
description: 'Creates the endpoint and returns its signing secret. The secret ' +
|
|
117
|
+
'is shown once and is never returned again.'
|
|
118
|
+
}),
|
|
119
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_MANAGE),
|
|
120
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
121
|
+
(0, common_1.Post)(),
|
|
122
|
+
tslib_1.__param(0, (0, common_1.Body)()),
|
|
123
|
+
tslib_1.__param(1, (0, identity_server_1.CurrentUser)()),
|
|
124
|
+
tslib_1.__metadata("design:type", Function),
|
|
125
|
+
tslib_1.__metadata("design:paramtypes", [save_webhook_endpoint_dto_1.SaveWebhookEndpointDto, Object]),
|
|
126
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
127
|
+
], WebhookEndpointsController.prototype, "create", null);
|
|
128
|
+
tslib_1.__decorate([
|
|
129
|
+
(0, swagger_1.ApiOperation)({
|
|
130
|
+
summary: 'Update a webhook endpoint',
|
|
131
|
+
description: 'Replaces the endpoint’s configuration. Re-enabling it by hand ' +
|
|
132
|
+
'also clears an automatic disable and its failure count.'
|
|
133
|
+
}),
|
|
134
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_MANAGE),
|
|
135
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
136
|
+
(0, common_1.Patch)(':id'),
|
|
137
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
138
|
+
tslib_1.__param(1, (0, common_1.Body)()),
|
|
139
|
+
tslib_1.__metadata("design:type", Function),
|
|
140
|
+
tslib_1.__metadata("design:paramtypes", [String, save_webhook_endpoint_dto_1.SaveWebhookEndpointDto]),
|
|
141
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
142
|
+
], WebhookEndpointsController.prototype, "update", null);
|
|
143
|
+
tslib_1.__decorate([
|
|
144
|
+
(0, swagger_1.ApiOperation)({
|
|
145
|
+
summary: 'Delete a webhook endpoint',
|
|
146
|
+
description: 'Deletes the endpoint and, with it, its delivery log.'
|
|
147
|
+
}),
|
|
148
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_MANAGE),
|
|
149
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
150
|
+
(0, common_1.Delete)(':id'),
|
|
151
|
+
(0, common_1.HttpCode)(204),
|
|
152
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
153
|
+
tslib_1.__metadata("design:type", Function),
|
|
154
|
+
tslib_1.__metadata("design:paramtypes", [String]),
|
|
155
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
156
|
+
], WebhookEndpointsController.prototype, "delete", null);
|
|
157
|
+
tslib_1.__decorate([
|
|
158
|
+
(0, swagger_1.ApiOperation)({
|
|
159
|
+
summary: 'Rotate a webhook signing secret',
|
|
160
|
+
description: 'Mints a new signing secret and returns it once. It takes effect ' +
|
|
161
|
+
'immediately, including for deliveries already queued, so the ' +
|
|
162
|
+
'receiver needs it in place before rotating.'
|
|
163
|
+
}),
|
|
164
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_MANAGE),
|
|
165
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
166
|
+
(0, common_1.Post)(':id/secret'),
|
|
167
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
168
|
+
tslib_1.__metadata("design:type", Function),
|
|
169
|
+
tslib_1.__metadata("design:paramtypes", [String]),
|
|
170
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
171
|
+
], WebhookEndpointsController.prototype, "rotateSecret", null);
|
|
172
|
+
tslib_1.__decorate([
|
|
173
|
+
(0, swagger_1.ApiOperation)({
|
|
174
|
+
summary: 'Send a test delivery',
|
|
175
|
+
description: 'Posts a synthetic `ping` event and reports the response. It is ' +
|
|
176
|
+
'not queued and does not count towards automatic disabling.'
|
|
177
|
+
}),
|
|
178
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_MANAGE),
|
|
179
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
180
|
+
(0, common_1.Post)(':id/test'),
|
|
181
|
+
(0, common_1.HttpCode)(200),
|
|
182
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
183
|
+
tslib_1.__metadata("design:type", Function),
|
|
184
|
+
tslib_1.__metadata("design:paramtypes", [String]),
|
|
185
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
186
|
+
], WebhookEndpointsController.prototype, "test", null);
|
|
187
|
+
exports.WebhookEndpointsController = WebhookEndpointsController = tslib_1.__decorate([
|
|
188
|
+
(0, swagger_1.ApiTags)('webhooks'),
|
|
189
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard),
|
|
190
|
+
(0, common_1.Controller)('webhooks'),
|
|
191
|
+
tslib_1.__metadata("design:paramtypes", [webhook_endpoints_service_1.WebhookEndpointsService])
|
|
192
|
+
], WebhookEndpointsController);
|
|
193
|
+
/**
|
|
194
|
+
* DTO → the repository's write model, resolving what an omitted field means.
|
|
195
|
+
*
|
|
196
|
+
* Every default here says "everything": an omitted filter is not an empty
|
|
197
|
+
* subscription, it is an unfiltered one. The one exception is `allWorkspaces`,
|
|
198
|
+
* which defaults to false — "all workspaces" reaches across tenants and should
|
|
199
|
+
* be something someone chose, not something they forgot to say.
|
|
200
|
+
*/
|
|
201
|
+
function toWriteModel(dto) {
|
|
202
|
+
const allWorkspaces = dto.allWorkspaces ?? false;
|
|
203
|
+
return {
|
|
204
|
+
name: dto.name.trim(),
|
|
205
|
+
url: dto.url.trim(),
|
|
206
|
+
enabled: dto.enabled ?? true,
|
|
207
|
+
eventKinds: dto.eventKinds ?? [],
|
|
208
|
+
contentTypes: dto.contentTypes ?? [],
|
|
209
|
+
allWorkspaces,
|
|
210
|
+
workspaceIds: allWorkspaces ? [] : (dto.workspaceIds ?? []),
|
|
211
|
+
headers: dto.headers ?? {}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type WebhookEventDescriptor } from '@orthacms/webhooks-domain';
|
|
2
|
+
/**
|
|
3
|
+
* `GET /api/webhook-events` — the catalogue of subscribable event kinds.
|
|
4
|
+
*
|
|
5
|
+
* The admin's event picker is built from this rather than from a list compiled
|
|
6
|
+
* into the SPA, so a deployment running a newer server offers the kinds that
|
|
7
|
+
* server actually knows about. Same idea as `GET /api/content-schema`.
|
|
8
|
+
*/
|
|
9
|
+
export declare class WebhookEventsController {
|
|
10
|
+
/** Every kind an endpoint may subscribe to. */
|
|
11
|
+
list(): readonly WebhookEventDescriptor[];
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=webhook-events.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-events.controller.d.ts","sourceRoot":"","sources":["../../../../src/lib/http/controllers/webhook-events.controller.ts"],"names":[],"mappings":"AAOA,OAAO,EAEH,KAAK,sBAAsB,EAC9B,MAAM,2BAA2B,CAAC;AAEnC;;;;;;GAMG;AACH,qBAGa,uBAAuB;IAChC,+CAA+C;IAQ/C,IAAI,IAAI,SAAS,sBAAsB,EAAE;CAG5C"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookEventsController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
8
|
+
const webhooks_domain_1 = require("@orthacms/webhooks-domain");
|
|
9
|
+
/**
|
|
10
|
+
* `GET /api/webhook-events` — the catalogue of subscribable event kinds.
|
|
11
|
+
*
|
|
12
|
+
* The admin's event picker is built from this rather than from a list compiled
|
|
13
|
+
* into the SPA, so a deployment running a newer server offers the kinds that
|
|
14
|
+
* server actually knows about. Same idea as `GET /api/content-schema`.
|
|
15
|
+
*/
|
|
16
|
+
let WebhookEventsController = class WebhookEventsController {
|
|
17
|
+
/** Every kind an endpoint may subscribe to. */
|
|
18
|
+
list() {
|
|
19
|
+
return webhooks_domain_1.WEBHOOK_EVENTS;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
exports.WebhookEventsController = WebhookEventsController;
|
|
23
|
+
tslib_1.__decorate([
|
|
24
|
+
(0, swagger_1.ApiOperation)({
|
|
25
|
+
summary: 'List subscribable webhook events',
|
|
26
|
+
description: 'The catalogue the endpoint editor’s event picker is built from.'
|
|
27
|
+
}),
|
|
28
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.WEBHOOKS_READ),
|
|
29
|
+
(0, common_1.Get)(),
|
|
30
|
+
tslib_1.__metadata("design:type", Function),
|
|
31
|
+
tslib_1.__metadata("design:paramtypes", []),
|
|
32
|
+
tslib_1.__metadata("design:returntype", Array)
|
|
33
|
+
], WebhookEventsController.prototype, "list", null);
|
|
34
|
+
exports.WebhookEventsController = WebhookEventsController = tslib_1.__decorate([
|
|
35
|
+
(0, swagger_1.ApiTags)('webhooks'),
|
|
36
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard),
|
|
37
|
+
(0, common_1.Controller)('webhook-events')
|
|
38
|
+
], WebhookEventsController);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DomainEvent } from '@orthacms/database';
|
|
2
|
+
import type { WebhookRoutableEvent, WebhookSourceEvent } from '@orthacms/webhooks-domain';
|
|
3
|
+
/** The routing facts, for the subscription filters. */
|
|
4
|
+
export declare function toRoutableEvent(event: DomainEvent): WebhookRoutableEvent;
|
|
5
|
+
/**
|
|
6
|
+
* The full source event, for the envelope.
|
|
7
|
+
*
|
|
8
|
+
* `actor` and `workspaceId` are lifted out of the payload into the envelope's
|
|
9
|
+
* own fields, so `data` carries what the event kind is *about* and nothing that
|
|
10
|
+
* every event has.
|
|
11
|
+
*/
|
|
12
|
+
export declare function toSourceEvent(event: DomainEvent): WebhookSourceEvent;
|
|
13
|
+
//# sourceMappingURL=event-mapping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-mapping.d.ts","sourceRoot":"","sources":["../../../src/lib/infrastructure/event-mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAER,oBAAoB,EACpB,kBAAkB,EACrB,MAAM,2BAA2B,CAAC;AAcnC,uDAAuD;AACvD,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,oBAAoB,CAMxE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,kBAAkB,CAgBpE"}
|