@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ortha CMS contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@orthacms/webhooks-server` — outgoing webhooks on content changes.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to the transactional outbox, queues one delivery row per
|
|
5
|
+
* subscribed endpoint, and sends them from a worker that holds no transaction
|
|
6
|
+
* while it waits on someone else's server. Owns three tables and ships their
|
|
7
|
+
* migrations; the rules about what a delivery *means* live in
|
|
8
|
+
* `@orthacms/webhooks-domain`.
|
|
9
|
+
*/
|
|
10
|
+
export { WebhooksPlugin, type WebhooksServerPluginType } from './lib/utils/webhooks-plugin';
|
|
11
|
+
export { WebhooksModule } from './lib/webhooks.module';
|
|
12
|
+
export { WEBHOOKS_DEFAULTS, resolveWebhooksConfig, type ResolvedWebhooksConfig, type WebhooksPluginConfig } from './lib/types/webhooks-config';
|
|
13
|
+
export { InjectWebhooksConfig, WEBHOOKS_CONFIG } from './lib/webhooks.tokens';
|
|
14
|
+
export { WebhookEndpointsService, type WebhookEndpointWithSecret } from './lib/application/webhook-endpoints.service';
|
|
15
|
+
export { WebhookDeliveryWorker } from './lib/infrastructure/webhook-delivery.worker';
|
|
16
|
+
export { WebhookDeliveryNotFoundError, WebhookEndpointNotFoundError } from './lib/domain/errors';
|
|
17
|
+
export type { WebhookDeliveryDetailView, WebhookDeliveryPage, WebhookDeliveryView, WebhookEndpointView, WebhookLastDeliveryView, WebhookTestResult } from './lib/domain/webhook-views';
|
|
18
|
+
export { webhookDeliveries, webhookEndpointWorkspaces, webhookEndpoints } from './lib/infrastructure/schema';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACH,cAAc,EACd,KAAK,wBAAwB,EAChC,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EACH,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAC5B,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EACH,uBAAuB,EACvB,KAAK,yBAAyB,EACjC,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AAErF,OAAO,EACH,4BAA4B,EAC5B,4BAA4B,EAC/B,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACR,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACH,iBAAiB,EACjB,yBAAyB,EACzB,gBAAgB,EACnB,MAAM,6BAA6B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@orthacms/webhooks-server` — outgoing webhooks on content changes.
|
|
4
|
+
*
|
|
5
|
+
* Subscribes to the transactional outbox, queues one delivery row per
|
|
6
|
+
* subscribed endpoint, and sends them from a worker that holds no transaction
|
|
7
|
+
* while it waits on someone else's server. Owns three tables and ships their
|
|
8
|
+
* migrations; the rules about what a delivery *means* live in
|
|
9
|
+
* `@orthacms/webhooks-domain`.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.webhookEndpoints = exports.webhookEndpointWorkspaces = exports.webhookDeliveries = exports.WebhookEndpointNotFoundError = exports.WebhookDeliveryNotFoundError = exports.WebhookDeliveryWorker = exports.WebhookEndpointsService = exports.WEBHOOKS_CONFIG = exports.InjectWebhooksConfig = exports.resolveWebhooksConfig = exports.WEBHOOKS_DEFAULTS = exports.WebhooksModule = exports.WebhooksPlugin = void 0;
|
|
13
|
+
var webhooks_plugin_1 = require("./lib/utils/webhooks-plugin");
|
|
14
|
+
Object.defineProperty(exports, "WebhooksPlugin", { enumerable: true, get: function () { return webhooks_plugin_1.WebhooksPlugin; } });
|
|
15
|
+
var webhooks_module_1 = require("./lib/webhooks.module");
|
|
16
|
+
Object.defineProperty(exports, "WebhooksModule", { enumerable: true, get: function () { return webhooks_module_1.WebhooksModule; } });
|
|
17
|
+
var webhooks_config_1 = require("./lib/types/webhooks-config");
|
|
18
|
+
Object.defineProperty(exports, "WEBHOOKS_DEFAULTS", { enumerable: true, get: function () { return webhooks_config_1.WEBHOOKS_DEFAULTS; } });
|
|
19
|
+
Object.defineProperty(exports, "resolveWebhooksConfig", { enumerable: true, get: function () { return webhooks_config_1.resolveWebhooksConfig; } });
|
|
20
|
+
var webhooks_tokens_1 = require("./lib/webhooks.tokens");
|
|
21
|
+
Object.defineProperty(exports, "InjectWebhooksConfig", { enumerable: true, get: function () { return webhooks_tokens_1.InjectWebhooksConfig; } });
|
|
22
|
+
Object.defineProperty(exports, "WEBHOOKS_CONFIG", { enumerable: true, get: function () { return webhooks_tokens_1.WEBHOOKS_CONFIG; } });
|
|
23
|
+
var webhook_endpoints_service_1 = require("./lib/application/webhook-endpoints.service");
|
|
24
|
+
Object.defineProperty(exports, "WebhookEndpointsService", { enumerable: true, get: function () { return webhook_endpoints_service_1.WebhookEndpointsService; } });
|
|
25
|
+
var webhook_delivery_worker_1 = require("./lib/infrastructure/webhook-delivery.worker");
|
|
26
|
+
Object.defineProperty(exports, "WebhookDeliveryWorker", { enumerable: true, get: function () { return webhook_delivery_worker_1.WebhookDeliveryWorker; } });
|
|
27
|
+
var errors_1 = require("./lib/domain/errors");
|
|
28
|
+
Object.defineProperty(exports, "WebhookDeliveryNotFoundError", { enumerable: true, get: function () { return errors_1.WebhookDeliveryNotFoundError; } });
|
|
29
|
+
Object.defineProperty(exports, "WebhookEndpointNotFoundError", { enumerable: true, get: function () { return errors_1.WebhookEndpointNotFoundError; } });
|
|
30
|
+
var schema_1 = require("./lib/infrastructure/schema");
|
|
31
|
+
Object.defineProperty(exports, "webhookDeliveries", { enumerable: true, get: function () { return schema_1.webhookDeliveries; } });
|
|
32
|
+
Object.defineProperty(exports, "webhookEndpointWorkspaces", { enumerable: true, get: function () { return schema_1.webhookEndpointWorkspaces; } });
|
|
33
|
+
Object.defineProperty(exports, "webhookEndpoints", { enumerable: true, get: function () { return schema_1.webhookEndpoints; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-deliveries-query.dto.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/dto/list-deliveries-query.dto.ts"],"names":[],"mappings":"AAWA,iEAAiE;AACjE,qBAAa,sBAAsB;IAO/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAQhB,SAAS,CAAC,EAAE,MAAM,CAAC;IAYnB,IAAI,CAAC,EAAE,MAAM,CAAC;IAcd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListDeliveriesQueryDto = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
6
|
+
const webhooks_domain_1 = require("@orthacms/webhooks-domain");
|
|
7
|
+
const class_transformer_1 = require("class-transformer");
|
|
8
|
+
const class_validator_1 = require("class-validator");
|
|
9
|
+
/** The largest page the delivery log will return. */
|
|
10
|
+
const MAX_PAGE_SIZE = 100;
|
|
11
|
+
/** Filters and paging for `GET /api/webhooks/:id/deliveries`. */
|
|
12
|
+
class ListDeliveriesQueryDto {
|
|
13
|
+
status;
|
|
14
|
+
eventKind;
|
|
15
|
+
page;
|
|
16
|
+
pageSize;
|
|
17
|
+
}
|
|
18
|
+
exports.ListDeliveriesQueryDto = ListDeliveriesQueryDto;
|
|
19
|
+
tslib_1.__decorate([
|
|
20
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
21
|
+
description: 'Only deliveries in this state.',
|
|
22
|
+
enum: webhooks_domain_1.DELIVERY_STATUSES
|
|
23
|
+
}),
|
|
24
|
+
(0, class_validator_1.IsOptional)(),
|
|
25
|
+
(0, class_validator_1.IsIn)(webhooks_domain_1.DELIVERY_STATUSES),
|
|
26
|
+
tslib_1.__metadata("design:type", String)
|
|
27
|
+
], ListDeliveriesQueryDto.prototype, "status", void 0);
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
30
|
+
description: 'Only deliveries of this event kind.',
|
|
31
|
+
enum: webhooks_domain_1.WEBHOOK_EVENT_KINDS
|
|
32
|
+
}),
|
|
33
|
+
(0, class_validator_1.IsOptional)(),
|
|
34
|
+
(0, class_validator_1.IsIn)(webhooks_domain_1.WEBHOOK_EVENT_KINDS),
|
|
35
|
+
tslib_1.__metadata("design:type", String)
|
|
36
|
+
], ListDeliveriesQueryDto.prototype, "eventKind", void 0);
|
|
37
|
+
tslib_1.__decorate([
|
|
38
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
39
|
+
description: '1-based page number.',
|
|
40
|
+
type: 'integer',
|
|
41
|
+
minimum: 1,
|
|
42
|
+
default: 1
|
|
43
|
+
}),
|
|
44
|
+
(0, class_validator_1.IsOptional)(),
|
|
45
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
46
|
+
(0, class_validator_1.IsInt)(),
|
|
47
|
+
(0, class_validator_1.Min)(1),
|
|
48
|
+
tslib_1.__metadata("design:type", Number)
|
|
49
|
+
], ListDeliveriesQueryDto.prototype, "page", void 0);
|
|
50
|
+
tslib_1.__decorate([
|
|
51
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
52
|
+
description: 'Rows per page.',
|
|
53
|
+
type: 'integer',
|
|
54
|
+
minimum: 1,
|
|
55
|
+
maximum: MAX_PAGE_SIZE,
|
|
56
|
+
default: 25
|
|
57
|
+
}),
|
|
58
|
+
(0, class_validator_1.IsOptional)(),
|
|
59
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
60
|
+
(0, class_validator_1.IsInt)(),
|
|
61
|
+
(0, class_validator_1.Min)(1),
|
|
62
|
+
(0, class_validator_1.Max)(MAX_PAGE_SIZE),
|
|
63
|
+
tslib_1.__metadata("design:type", Number)
|
|
64
|
+
], ListDeliveriesQueryDto.prototype, "pageSize", void 0);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The body of a create or update.
|
|
3
|
+
*
|
|
4
|
+
* The three filter arrays are **optional, and an omitted or empty one means
|
|
5
|
+
* "everything"** — including things that do not exist yet. That is the whole
|
|
6
|
+
* subscription model, and it is why the shape is three plain lists rather than
|
|
7
|
+
* an expression.
|
|
8
|
+
*
|
|
9
|
+
* The URL is only shape-checked here (`@IsString`); whether it is *reachable*
|
|
10
|
+
* under this deployment's policy is decided in the service, which owns the
|
|
11
|
+
* policy and can say why in a message worth showing.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SaveWebhookEndpointDto {
|
|
14
|
+
name: string;
|
|
15
|
+
url: string;
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
eventKinds?: string[];
|
|
18
|
+
contentTypes?: string[];
|
|
19
|
+
allWorkspaces?: boolean;
|
|
20
|
+
workspaceIds?: string[];
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=save-webhook-endpoint.dto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"save-webhook-endpoint.dto.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/dto/save-webhook-endpoint.dto.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;;;GAWG;AACH,qBAAa,sBAAsB;IAS/B,IAAI,EAAG,MAAM,CAAC;IAWd,GAAG,EAAG,MAAM,CAAC;IAQb,OAAO,CAAC,EAAE,OAAO,CAAC;IAelB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAatB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IASxB,aAAa,CAAC,EAAE,OAAO,CAAC;IAaxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAWxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SaveWebhookEndpointDto = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
6
|
+
const webhooks_domain_1 = require("@orthacms/webhooks-domain");
|
|
7
|
+
const class_validator_1 = require("class-validator");
|
|
8
|
+
/** Bounds on the free-text fields, restated in the OpenAPI schema below. */
|
|
9
|
+
const NAME_MAX = 120;
|
|
10
|
+
const URL_MAX = 2_048;
|
|
11
|
+
const LIST_MAX = 200;
|
|
12
|
+
/**
|
|
13
|
+
* The body of a create or update.
|
|
14
|
+
*
|
|
15
|
+
* The three filter arrays are **optional, and an omitted or empty one means
|
|
16
|
+
* "everything"** — including things that do not exist yet. That is the whole
|
|
17
|
+
* subscription model, and it is why the shape is three plain lists rather than
|
|
18
|
+
* an expression.
|
|
19
|
+
*
|
|
20
|
+
* The URL is only shape-checked here (`@IsString`); whether it is *reachable*
|
|
21
|
+
* under this deployment's policy is decided in the service, which owns the
|
|
22
|
+
* policy and can say why in a message worth showing.
|
|
23
|
+
*/
|
|
24
|
+
class SaveWebhookEndpointDto {
|
|
25
|
+
name;
|
|
26
|
+
url;
|
|
27
|
+
enabled;
|
|
28
|
+
eventKinds;
|
|
29
|
+
contentTypes;
|
|
30
|
+
allWorkspaces;
|
|
31
|
+
workspaceIds;
|
|
32
|
+
headers;
|
|
33
|
+
}
|
|
34
|
+
exports.SaveWebhookEndpointDto = SaveWebhookEndpointDto;
|
|
35
|
+
tslib_1.__decorate([
|
|
36
|
+
(0, swagger_1.ApiProperty)({
|
|
37
|
+
description: 'Human-readable name shown in the webhooks list.',
|
|
38
|
+
maxLength: NAME_MAX,
|
|
39
|
+
example: 'Rebuild the storefront'
|
|
40
|
+
}),
|
|
41
|
+
(0, class_validator_1.IsString)(),
|
|
42
|
+
(0, class_validator_1.MinLength)(1),
|
|
43
|
+
(0, class_validator_1.MaxLength)(NAME_MAX),
|
|
44
|
+
tslib_1.__metadata("design:type", String)
|
|
45
|
+
], SaveWebhookEndpointDto.prototype, "name", void 0);
|
|
46
|
+
tslib_1.__decorate([
|
|
47
|
+
(0, swagger_1.ApiProperty)({
|
|
48
|
+
description: 'Where deliveries are POSTed. Must be https:// and resolve to a public address unless the deployment allows otherwise.',
|
|
49
|
+
maxLength: URL_MAX,
|
|
50
|
+
example: 'https://example.com/hooks/ortha'
|
|
51
|
+
}),
|
|
52
|
+
(0, class_validator_1.IsString)(),
|
|
53
|
+
(0, class_validator_1.MinLength)(1),
|
|
54
|
+
(0, class_validator_1.MaxLength)(URL_MAX),
|
|
55
|
+
tslib_1.__metadata("design:type", String)
|
|
56
|
+
], SaveWebhookEndpointDto.prototype, "url", void 0);
|
|
57
|
+
tslib_1.__decorate([
|
|
58
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
59
|
+
description: 'Whether the endpoint receives deliveries.',
|
|
60
|
+
default: true
|
|
61
|
+
}),
|
|
62
|
+
(0, class_validator_1.IsOptional)(),
|
|
63
|
+
(0, class_validator_1.IsBoolean)(),
|
|
64
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
65
|
+
], SaveWebhookEndpointDto.prototype, "enabled", void 0);
|
|
66
|
+
tslib_1.__decorate([
|
|
67
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
68
|
+
description: 'Event kinds to subscribe to. Omit or leave empty to receive every kind, including ones added later.',
|
|
69
|
+
type: [String],
|
|
70
|
+
enum: webhooks_domain_1.WEBHOOK_EVENT_KINDS,
|
|
71
|
+
maxItems: LIST_MAX
|
|
72
|
+
}),
|
|
73
|
+
(0, class_validator_1.IsOptional)(),
|
|
74
|
+
(0, class_validator_1.IsArray)(),
|
|
75
|
+
(0, class_validator_1.ArrayMaxSize)(LIST_MAX)
|
|
76
|
+
// Derived from the catalogue, so a new kind is subscribable the moment it
|
|
77
|
+
// is added there and a removed one stops validating.
|
|
78
|
+
,
|
|
79
|
+
(0, class_validator_1.IsIn)(webhooks_domain_1.WEBHOOK_EVENT_KINDS, { each: true }),
|
|
80
|
+
tslib_1.__metadata("design:type", Array)
|
|
81
|
+
], SaveWebhookEndpointDto.prototype, "eventKinds", void 0);
|
|
82
|
+
tslib_1.__decorate([
|
|
83
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
84
|
+
description: 'Content types to subscribe to. Omit or leave empty to receive every type.',
|
|
85
|
+
type: [String],
|
|
86
|
+
maxItems: LIST_MAX
|
|
87
|
+
}),
|
|
88
|
+
(0, class_validator_1.IsOptional)(),
|
|
89
|
+
(0, class_validator_1.IsArray)(),
|
|
90
|
+
(0, class_validator_1.ArrayMaxSize)(LIST_MAX),
|
|
91
|
+
(0, class_validator_1.IsString)({ each: true }),
|
|
92
|
+
(0, class_validator_1.MaxLength)(200, { each: true }),
|
|
93
|
+
tslib_1.__metadata("design:type", Array)
|
|
94
|
+
], SaveWebhookEndpointDto.prototype, "contentTypes", void 0);
|
|
95
|
+
tslib_1.__decorate([
|
|
96
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
97
|
+
description: 'Receive events from every workspace, including workspaces created later. When true, workspaceIds is ignored.',
|
|
98
|
+
default: false
|
|
99
|
+
}),
|
|
100
|
+
(0, class_validator_1.IsOptional)(),
|
|
101
|
+
(0, class_validator_1.IsBoolean)(),
|
|
102
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
103
|
+
], SaveWebhookEndpointDto.prototype, "allWorkspaces", void 0);
|
|
104
|
+
tslib_1.__decorate([
|
|
105
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
106
|
+
description: 'Workspaces to receive events from. Ignored when allWorkspaces is true; an empty list with allWorkspaces false receives nothing.',
|
|
107
|
+
type: [String],
|
|
108
|
+
format: 'uuid',
|
|
109
|
+
maxItems: LIST_MAX
|
|
110
|
+
}),
|
|
111
|
+
(0, class_validator_1.IsOptional)(),
|
|
112
|
+
(0, class_validator_1.IsArray)(),
|
|
113
|
+
(0, class_validator_1.ArrayMaxSize)(LIST_MAX),
|
|
114
|
+
(0, class_validator_1.IsUUID)('4', { each: true }),
|
|
115
|
+
tslib_1.__metadata("design:type", Array)
|
|
116
|
+
], SaveWebhookEndpointDto.prototype, "workspaceIds", void 0);
|
|
117
|
+
tslib_1.__decorate([
|
|
118
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
119
|
+
description: 'Extra static headers sent with every delivery. Delivery metadata headers (X-Ortha-*) and transport headers cannot be set.',
|
|
120
|
+
type: 'object',
|
|
121
|
+
additionalProperties: { type: 'string' },
|
|
122
|
+
example: { Authorization: 'Bearer …' }
|
|
123
|
+
}),
|
|
124
|
+
(0, class_validator_1.IsOptional)(),
|
|
125
|
+
(0, class_validator_1.IsObject)(),
|
|
126
|
+
tslib_1.__metadata("design:type", Object)
|
|
127
|
+
], SaveWebhookEndpointDto.prototype, "headers", void 0);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { WebhookDeliveryDetailView, WebhookDeliveryPage, WebhookDeliveryView, WebhookEndpointView, WebhookTestResult } from '../domain/webhook-views';
|
|
2
|
+
import { WebhookDeliveryRepository, type DeliveryListQuery } from '../infrastructure/webhook-delivery.repository';
|
|
3
|
+
import { WebhookEndpointRepository, type EndpointWriteModel } from '../infrastructure/webhook-endpoint.repository';
|
|
4
|
+
import { WebhookHttpClient } from '../infrastructure/webhook-http.client';
|
|
5
|
+
import type { ResolvedWebhooksConfig } from '../types/webhooks-config';
|
|
6
|
+
/** An endpoint plus the secret, returned only from create and rotate. */
|
|
7
|
+
export interface WebhookEndpointWithSecret {
|
|
8
|
+
endpoint: WebhookEndpointView;
|
|
9
|
+
/** The plaintext signing secret. This is the only time it is ever returned. */
|
|
10
|
+
secret: string;
|
|
11
|
+
}
|
|
12
|
+
/** Orchestration over the two repositories. */
|
|
13
|
+
export declare class WebhookEndpointsService {
|
|
14
|
+
private readonly endpoints;
|
|
15
|
+
private readonly deliveries;
|
|
16
|
+
private readonly http;
|
|
17
|
+
private readonly config;
|
|
18
|
+
constructor(endpoints: WebhookEndpointRepository, deliveries: WebhookDeliveryRepository, http: WebhookHttpClient, config: ResolvedWebhooksConfig);
|
|
19
|
+
/** Every endpoint, newest first. */
|
|
20
|
+
list(): Promise<WebhookEndpointView[]>;
|
|
21
|
+
/** One endpoint. Throws {@link WebhookEndpointNotFoundError} when absent. */
|
|
22
|
+
get(id: string): Promise<WebhookEndpointView>;
|
|
23
|
+
/**
|
|
24
|
+
* Creates an endpoint and mints its secret.
|
|
25
|
+
*
|
|
26
|
+
* The URL is checked here as well as in the worker so a typo is refused in
|
|
27
|
+
* the dialog rather than discovered in the delivery log an hour later.
|
|
28
|
+
*/
|
|
29
|
+
create(model: EndpointWriteModel, createdBy: string | null): Promise<WebhookEndpointWithSecret>;
|
|
30
|
+
/** Applies a full write model to an existing endpoint. */
|
|
31
|
+
update(id: string, model: EndpointWriteModel): Promise<WebhookEndpointView>;
|
|
32
|
+
/** Deletes the endpoint and, by cascade, its delivery log. */
|
|
33
|
+
delete(id: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Rotates the signing secret.
|
|
36
|
+
*
|
|
37
|
+
* The new secret takes effect immediately, including for deliveries already
|
|
38
|
+
* queued — they are signed when they are sent, not when they were queued.
|
|
39
|
+
* A receiver therefore needs the new secret in place before rotating, which
|
|
40
|
+
* is what the dialog says.
|
|
41
|
+
*/
|
|
42
|
+
rotateSecret(id: string): Promise<WebhookEndpointWithSecret>;
|
|
43
|
+
/**
|
|
44
|
+
* Sends a synthetic `ping` and reports the result **synchronously**.
|
|
45
|
+
*
|
|
46
|
+
* Deliberately not queued: the person pressing "Send test" is waiting for
|
|
47
|
+
* an answer, and a row in the log they would have to go and find is not
|
|
48
|
+
* one. It is also why a test never counts against the endpoint's
|
|
49
|
+
* consecutive-failure budget — it is a probe, not traffic.
|
|
50
|
+
*/
|
|
51
|
+
sendTest(id: string): Promise<WebhookTestResult>;
|
|
52
|
+
/** One page of an endpoint's delivery log. */
|
|
53
|
+
listDeliveries(endpointId: string, query: DeliveryListQuery): Promise<WebhookDeliveryPage>;
|
|
54
|
+
/** One delivery with its bodies. */
|
|
55
|
+
getDelivery(endpointId: string, deliveryId: string): Promise<WebhookDeliveryDetailView>;
|
|
56
|
+
/**
|
|
57
|
+
* Queues the same event again as a fresh delivery.
|
|
58
|
+
*
|
|
59
|
+
* The body is copied rather than rebuilt, so the receiver gets exactly what
|
|
60
|
+
* it was sent before — with one substitution: `id` becomes the new delivery
|
|
61
|
+
* id, because that is also what the `X-Ortha-Delivery` header will say, and
|
|
62
|
+
* a body naming a different delivery than its own header is a bug waiting
|
|
63
|
+
* to be found by whoever writes the receiver.
|
|
64
|
+
*
|
|
65
|
+
* `eventId` is kept, so a receiver that deduplicates on it correctly
|
|
66
|
+
* ignores the repeat — which is exactly what a redelivery is *for* when the
|
|
67
|
+
* receiver, not the event, was the thing that was broken. Keeping it is
|
|
68
|
+
* only possible because `redeliveryOf` exempts the row from the partial
|
|
69
|
+
* unique index that makes fan-out idempotent.
|
|
70
|
+
*/
|
|
71
|
+
redeliver(endpointId: string, deliveryId: string): Promise<WebhookDeliveryView>;
|
|
72
|
+
/**
|
|
73
|
+
* Refuses a write that the delivery path would only reject later.
|
|
74
|
+
*
|
|
75
|
+
* Both checks are also enforced downstream — the URL on every send, the
|
|
76
|
+
* headers when the request is built — because a row can predate a policy
|
|
77
|
+
* change. Doing them here is what turns a silent failure in the log into a
|
|
78
|
+
* message in the form.
|
|
79
|
+
*/
|
|
80
|
+
private assertWritable;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=webhook-endpoints.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-endpoints.service.d.ts","sourceRoot":"","sources":["../../../src/lib/application/webhook-endpoints.service.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACR,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACH,yBAAyB,EACzB,KAAK,iBAAiB,EACzB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EACH,yBAAyB,EACzB,KAAK,kBAAkB,EAC1B,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAE1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,yEAAyE;AACzE,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,+CAA+C;AAC/C,qBACa,uBAAuB;IAE5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAJN,SAAS,EAAE,yBAAyB,EACpC,UAAU,EAAE,yBAAyB,EACrC,IAAI,EAAE,iBAAiB,EAEvB,MAAM,EAAE,sBAAsB;IAGnD,oCAAoC;IACpC,IAAI,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAItC,6EAA6E;IACvE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAMnD;;;;;OAKG;IACG,MAAM,CACR,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,yBAAyB,CAAC;IAQrC,0DAA0D;IACpD,MAAM,CACR,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,kBAAkB,GAC1B,OAAO,CAAC,mBAAmB,CAAC;IAU/B,8DAA8D;IACxD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvC;;;;;;;OAOG;IACG,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAUlE;;;;;;;OAOG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyCtD,8CAA8C;IACxC,cAAc,CAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,iBAAiB,GACzB,OAAO,CAAC,mBAAmB,CAAC;IAO/B,oCAAoC;IAC9B,WAAW,CACb,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,yBAAyB,CAAC;IASrC;;;;;;;;;;;;;;OAcG;IACG,SAAS,CACX,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,mBAAmB,CAAC;IAwB/B;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;CAczB"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookEndpointsService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const webhooks_domain_1 = require("@orthacms/webhooks-domain");
|
|
8
|
+
const errors_1 = require("../domain/errors");
|
|
9
|
+
const webhook_secret_1 = require("../domain/webhook-secret");
|
|
10
|
+
const webhook_delivery_repository_1 = require("../infrastructure/webhook-delivery.repository");
|
|
11
|
+
const webhook_endpoint_repository_1 = require("../infrastructure/webhook-endpoint.repository");
|
|
12
|
+
const webhook_http_client_1 = require("../infrastructure/webhook-http.client");
|
|
13
|
+
const webhooks_tokens_1 = require("../webhooks.tokens");
|
|
14
|
+
/** Orchestration over the two repositories. */
|
|
15
|
+
let WebhookEndpointsService = class WebhookEndpointsService {
|
|
16
|
+
endpoints;
|
|
17
|
+
deliveries;
|
|
18
|
+
http;
|
|
19
|
+
config;
|
|
20
|
+
constructor(endpoints, deliveries, http, config) {
|
|
21
|
+
this.endpoints = endpoints;
|
|
22
|
+
this.deliveries = deliveries;
|
|
23
|
+
this.http = http;
|
|
24
|
+
this.config = config;
|
|
25
|
+
}
|
|
26
|
+
/** Every endpoint, newest first. */
|
|
27
|
+
list() {
|
|
28
|
+
return this.endpoints.list();
|
|
29
|
+
}
|
|
30
|
+
/** One endpoint. Throws {@link WebhookEndpointNotFoundError} when absent. */
|
|
31
|
+
async get(id) {
|
|
32
|
+
const endpoint = await this.endpoints.findById(id);
|
|
33
|
+
if (!endpoint)
|
|
34
|
+
throw new errors_1.WebhookEndpointNotFoundError(id);
|
|
35
|
+
return endpoint;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates an endpoint and mints its secret.
|
|
39
|
+
*
|
|
40
|
+
* The URL is checked here as well as in the worker so a typo is refused in
|
|
41
|
+
* the dialog rather than discovered in the delivery log an hour later.
|
|
42
|
+
*/
|
|
43
|
+
async create(model, createdBy) {
|
|
44
|
+
this.assertWritable(model);
|
|
45
|
+
const { secret } = (0, webhook_secret_1.generateSecret)();
|
|
46
|
+
const id = await this.endpoints.create(model, secret, createdBy);
|
|
47
|
+
return { endpoint: await this.get(id), secret };
|
|
48
|
+
}
|
|
49
|
+
/** Applies a full write model to an existing endpoint. */
|
|
50
|
+
async update(id, model) {
|
|
51
|
+
if (!(await this.endpoints.exists(id))) {
|
|
52
|
+
throw new errors_1.WebhookEndpointNotFoundError(id);
|
|
53
|
+
}
|
|
54
|
+
this.assertWritable(model);
|
|
55
|
+
await this.endpoints.update(id, model);
|
|
56
|
+
return this.get(id);
|
|
57
|
+
}
|
|
58
|
+
/** Deletes the endpoint and, by cascade, its delivery log. */
|
|
59
|
+
async delete(id) {
|
|
60
|
+
if (!(await this.endpoints.exists(id))) {
|
|
61
|
+
throw new errors_1.WebhookEndpointNotFoundError(id);
|
|
62
|
+
}
|
|
63
|
+
await this.endpoints.delete(id);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Rotates the signing secret.
|
|
67
|
+
*
|
|
68
|
+
* The new secret takes effect immediately, including for deliveries already
|
|
69
|
+
* queued — they are signed when they are sent, not when they were queued.
|
|
70
|
+
* A receiver therefore needs the new secret in place before rotating, which
|
|
71
|
+
* is what the dialog says.
|
|
72
|
+
*/
|
|
73
|
+
async rotateSecret(id) {
|
|
74
|
+
if (!(await this.endpoints.exists(id))) {
|
|
75
|
+
throw new errors_1.WebhookEndpointNotFoundError(id);
|
|
76
|
+
}
|
|
77
|
+
const { secret } = (0, webhook_secret_1.generateSecret)();
|
|
78
|
+
await this.endpoints.replaceSecret(id, secret);
|
|
79
|
+
return { endpoint: await this.get(id), secret };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Sends a synthetic `ping` and reports the result **synchronously**.
|
|
83
|
+
*
|
|
84
|
+
* Deliberately not queued: the person pressing "Send test" is waiting for
|
|
85
|
+
* an answer, and a row in the log they would have to go and find is not
|
|
86
|
+
* one. It is also why a test never counts against the endpoint's
|
|
87
|
+
* consecutive-failure budget — it is a probe, not traffic.
|
|
88
|
+
*/
|
|
89
|
+
async sendTest(id) {
|
|
90
|
+
const endpoint = await this.endpoints.findForDelivery(id);
|
|
91
|
+
if (!endpoint)
|
|
92
|
+
throw new errors_1.WebhookEndpointNotFoundError(id);
|
|
93
|
+
const deliveryId = (0, node_crypto_1.randomUUID)();
|
|
94
|
+
const eventId = (0, node_crypto_1.randomUUID)();
|
|
95
|
+
const envelope = (0, webhooks_domain_1.buildEnvelope)(deliveryId, {
|
|
96
|
+
eventId,
|
|
97
|
+
kind: 'ping',
|
|
98
|
+
occurredAt: new Date(),
|
|
99
|
+
workspaceId: null,
|
|
100
|
+
aggregateType: 'webhook_endpoint',
|
|
101
|
+
aggregateId: id,
|
|
102
|
+
data: { endpointName: endpoint.name },
|
|
103
|
+
actor: null
|
|
104
|
+
});
|
|
105
|
+
const response = await this.http.send({
|
|
106
|
+
url: endpoint.url,
|
|
107
|
+
secret: endpoint.secret,
|
|
108
|
+
headers: endpoint.headers,
|
|
109
|
+
body: envelope,
|
|
110
|
+
eventKind: 'ping',
|
|
111
|
+
deliveryId,
|
|
112
|
+
eventId,
|
|
113
|
+
workspaceId: null,
|
|
114
|
+
attempt: 1
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
ok: response.statusCode !== null &&
|
|
118
|
+
response.statusCode >= 200 &&
|
|
119
|
+
response.statusCode < 300,
|
|
120
|
+
statusCode: response.statusCode,
|
|
121
|
+
error: response.error,
|
|
122
|
+
durationMs: response.durationMs,
|
|
123
|
+
responseSnippet: response.responseSnippet
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** One page of an endpoint's delivery log. */
|
|
127
|
+
async listDeliveries(endpointId, query) {
|
|
128
|
+
if (!(await this.endpoints.exists(endpointId))) {
|
|
129
|
+
throw new errors_1.WebhookEndpointNotFoundError(endpointId);
|
|
130
|
+
}
|
|
131
|
+
return this.deliveries.list(endpointId, query);
|
|
132
|
+
}
|
|
133
|
+
/** One delivery with its bodies. */
|
|
134
|
+
async getDelivery(endpointId, deliveryId) {
|
|
135
|
+
const delivery = await this.deliveries.findDetail(endpointId, deliveryId);
|
|
136
|
+
if (!delivery)
|
|
137
|
+
throw new errors_1.WebhookDeliveryNotFoundError(deliveryId);
|
|
138
|
+
return delivery;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Queues the same event again as a fresh delivery.
|
|
142
|
+
*
|
|
143
|
+
* The body is copied rather than rebuilt, so the receiver gets exactly what
|
|
144
|
+
* it was sent before — with one substitution: `id` becomes the new delivery
|
|
145
|
+
* id, because that is also what the `X-Ortha-Delivery` header will say, and
|
|
146
|
+
* a body naming a different delivery than its own header is a bug waiting
|
|
147
|
+
* to be found by whoever writes the receiver.
|
|
148
|
+
*
|
|
149
|
+
* `eventId` is kept, so a receiver that deduplicates on it correctly
|
|
150
|
+
* ignores the repeat — which is exactly what a redelivery is *for* when the
|
|
151
|
+
* receiver, not the event, was the thing that was broken. Keeping it is
|
|
152
|
+
* only possible because `redeliveryOf` exempts the row from the partial
|
|
153
|
+
* unique index that makes fan-out idempotent.
|
|
154
|
+
*/
|
|
155
|
+
async redeliver(endpointId, deliveryId) {
|
|
156
|
+
const original = await this.getDelivery(endpointId, deliveryId);
|
|
157
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
158
|
+
await this.deliveries.enqueueOne({
|
|
159
|
+
id,
|
|
160
|
+
endpointId,
|
|
161
|
+
eventId: original.eventId,
|
|
162
|
+
eventKind: original.eventKind,
|
|
163
|
+
workspaceId: original.workspaceId,
|
|
164
|
+
contentType: original.contentType,
|
|
165
|
+
payload: withDeliveryId(original.payload, id),
|
|
166
|
+
// Points at the delivery this repeats — both as provenance in the
|
|
167
|
+
// log and as the exemption from the fan-out unique index.
|
|
168
|
+
redeliveryOf: original.id
|
|
169
|
+
});
|
|
170
|
+
const queued = await this.deliveries.findDetail(endpointId, id);
|
|
171
|
+
// The row was just written in the same request; its absence would mean
|
|
172
|
+
// the endpoint was deleted underneath us.
|
|
173
|
+
if (!queued)
|
|
174
|
+
throw new errors_1.WebhookEndpointNotFoundError(endpointId);
|
|
175
|
+
return queued;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Refuses a write that the delivery path would only reject later.
|
|
179
|
+
*
|
|
180
|
+
* Both checks are also enforced downstream — the URL on every send, the
|
|
181
|
+
* headers when the request is built — because a row can predate a policy
|
|
182
|
+
* change. Doing them here is what turns a silent failure in the log into a
|
|
183
|
+
* message in the form.
|
|
184
|
+
*/
|
|
185
|
+
assertWritable(model) {
|
|
186
|
+
(0, webhooks_domain_1.assertUrlShape)(model.url, {
|
|
187
|
+
allowInsecureUrls: this.config.allowInsecureUrls,
|
|
188
|
+
allowPrivateNetworks: this.config.allowPrivateNetworks
|
|
189
|
+
});
|
|
190
|
+
for (const name of Object.keys(model.headers)) {
|
|
191
|
+
if (!(0, webhooks_domain_1.isAllowedCustomHeader)(name)) {
|
|
192
|
+
throw new webhooks_domain_1.WebhookUrlRejectedError(`The header "${name}" cannot be set on a webhook — it is reserved for the delivery's own metadata.`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
exports.WebhookEndpointsService = WebhookEndpointsService;
|
|
198
|
+
exports.WebhookEndpointsService = WebhookEndpointsService = tslib_1.__decorate([
|
|
199
|
+
(0, common_1.Injectable)(),
|
|
200
|
+
tslib_1.__param(3, (0, webhooks_tokens_1.InjectWebhooksConfig)()),
|
|
201
|
+
tslib_1.__metadata("design:paramtypes", [webhook_endpoint_repository_1.WebhookEndpointRepository,
|
|
202
|
+
webhook_delivery_repository_1.WebhookDeliveryRepository,
|
|
203
|
+
webhook_http_client_1.WebhookHttpClient, Object])
|
|
204
|
+
], WebhookEndpointsService);
|
|
205
|
+
/**
|
|
206
|
+
* The stored envelope with its `id` swapped for this delivery's, leaving
|
|
207
|
+
* everything else — `eventId` above all — exactly as it was sent.
|
|
208
|
+
*/
|
|
209
|
+
function withDeliveryId(payload, deliveryId) {
|
|
210
|
+
if (typeof payload !== 'object' || payload === null)
|
|
211
|
+
return payload;
|
|
212
|
+
return { ...payload, id: deliveryId };
|
|
213
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The webhooks plugin's pass over the host's OpenAPI document.
|
|
3
|
+
*
|
|
4
|
+
* The routes here are ordinary decorated controllers, so `@nestjs/swagger`
|
|
5
|
+
* already knows their paths, parameters and request bodies. What it cannot
|
|
6
|
+
* know is what comes **back**: every response view is a TypeScript
|
|
7
|
+
* `interface`, erased before the scanner runs, which is why each operation
|
|
8
|
+
* arrives carrying a bare `200`/`201` with no content. This pass writes the
|
|
9
|
+
* response schemas onto the operations the plugin owns and nothing else.
|
|
10
|
+
*
|
|
11
|
+
* Pure: it takes the document and mutates only its own paths.
|
|
12
|
+
*/
|
|
13
|
+
import type { OpenApiDocument } from '@orthacms/bootstrap-server';
|
|
14
|
+
/** Adds this plugin's schemas and describes its own operations' responses. */
|
|
15
|
+
export declare function describeWebhooksApi(document: OpenApiDocument): void;
|
|
16
|
+
//# sourceMappingURL=describe-webhooks-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describe-webhooks-api.d.ts","sourceRoot":"","sources":["../../../src/lib/docs/describe-webhooks-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AA4JlE,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CA4DnE"}
|