@shipfox/api-integration-gitea 5.0.0 → 7.1.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.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 17 files with swc (328.43ms)
2
+ Successfully compiled: 18 files with swc (422.94ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @shipfox/api-integration-gitea
2
2
 
3
+ ## 7.1.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [ac42c96]
8
+ - Updated dependencies [6ce08c0]
9
+ - @shipfox/node-fastify@0.3.0
10
+ - @shipfox/node-opentelemetry@0.6.0
11
+ - @shipfox/api-auth-context@7.1.0
12
+
13
+ ## 6.0.0
14
+
15
+ ### Minor Changes
16
+
17
+ - f262539: Adds a composed webhook processor and optional provider-neutral delivery source for hosted API runtimes.
18
+ - a869cfd: Adds shared stored-request processors for GitHub and Gitea webhook reception.
19
+
20
+ ### Patch Changes
21
+
22
+ - f73da5d: Enforces bounded API context imports and routes inter-module consumers through producer contracts.
23
+ - Updated dependencies [0bb82a4]
24
+ - Updated dependencies [7ac43a4]
25
+ - Updated dependencies [f262539]
26
+ - Updated dependencies [3bb4e26]
27
+ - Updated dependencies [8bdc149]
28
+ - Updated dependencies [b00ed29]
29
+ - Updated dependencies [8aa7cd3]
30
+ - Updated dependencies [4604a06]
31
+ - @shipfox/api-integration-core-dto@6.0.0
32
+ - @shipfox/node-drizzle@0.3.2
33
+ - @shipfox/api-auth-context@6.0.0
34
+ - @shipfox/node-fastify@0.2.4
35
+ - @shipfox/api-integration-gitea-dto@6.0.0
36
+
3
37
  ## 5.0.0
4
38
 
5
39
  ### Patch Changes
@@ -0,0 +1,13 @@
1
+ import { type GetIntegrationConnectionByIdFn, type PublishSourcePushFn, type RecordDeliveryOnlyFn, type StoredWebhookRequest, type WebhookProcessingResult } from '@shipfox/api-integration-core-dto';
2
+ import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
3
+ export interface CreateGiteaWebhookProcessorOptions {
4
+ coreDb: () => NodePgDatabase<Record<string, unknown>>;
5
+ publishSourcePush: PublishSourcePushFn;
6
+ recordDeliveryOnly: RecordDeliveryOnlyFn;
7
+ getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
8
+ }
9
+ export interface GiteaWebhookProcessor {
10
+ process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;
11
+ }
12
+ export declare function createGiteaWebhookProcessor(options: CreateGiteaWebhookProcessorOptions): GiteaWebhookProcessor;
13
+ //# sourceMappingURL=webhook-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC7B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAY9D,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,qBAAqB,CAEvB"}
@@ -0,0 +1,84 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { decodeWebhookBody } from '@shipfox/api-integration-core-dto';
3
+ import { verifyHexHmacSignature } from '@shipfox/node-fastify';
4
+ import { logger } from '@shipfox/node-opentelemetry';
5
+ import { config } from '#config.js';
6
+ import { GiteaWebhookMalformedJsonError, GiteaWebhookMalformedPushPayloadError, handleGiteaWebhook } from '#core/webhook.js';
7
+ const DELIVERY_HEADER = 'x-gitea-delivery';
8
+ const EVENT_HEADER = 'x-gitea-event';
9
+ const SIGNATURE_HEADER = 'x-gitea-signature';
10
+ export function createGiteaWebhookProcessor(options) {
11
+ return {
12
+ process: (request)=>processGiteaWebhookRequest(options, request)
13
+ };
14
+ }
15
+ async function processGiteaWebhookRequest(options, request) {
16
+ if (request.route_id !== 'gitea') {
17
+ throw new Error(`Gitea processor cannot process ${request.route_id} requests`);
18
+ }
19
+ const deliveryId = request.headers[DELIVERY_HEADER];
20
+ const event = request.headers[EVENT_HEADER];
21
+ const signature = request.headers[SIGNATURE_HEADER];
22
+ if (!deliveryId || !event || !signature) {
23
+ return {
24
+ outcome: 'discarded',
25
+ reason: 'missing_required_input'
26
+ };
27
+ }
28
+ const rawBody = Buffer.from(decodeWebhookBody(request.body));
29
+ if (!verifyHexHmacSignature({
30
+ rawBody,
31
+ signature,
32
+ secret: config.GITEA_WEBHOOK_SECRET
33
+ })) {
34
+ return {
35
+ outcome: 'discarded',
36
+ reason: 'invalid_signature',
37
+ deliveryId
38
+ };
39
+ }
40
+ try {
41
+ const result = await options.coreDb().transaction(async (tx)=>handleGiteaWebhook({
42
+ tx,
43
+ deliveryId,
44
+ event,
45
+ rawBody: rawBody.toString('utf8'),
46
+ publishSourcePush: options.publishSourcePush,
47
+ recordDeliveryOnly: options.recordDeliveryOnly,
48
+ getIntegrationConnectionById: options.getIntegrationConnectionById
49
+ }));
50
+ return result.outcome === 'duplicate' ? {
51
+ outcome: 'duplicate',
52
+ deliveryId
53
+ } : {
54
+ outcome: 'processed',
55
+ deliveryId
56
+ };
57
+ } catch (error) {
58
+ if (error instanceof GiteaWebhookMalformedJsonError) {
59
+ logger().warn({
60
+ deliveryId,
61
+ err: error
62
+ }, 'gitea webhook payload JSON parse failed');
63
+ return {
64
+ outcome: 'discarded',
65
+ reason: 'malformed_payload',
66
+ deliveryId
67
+ };
68
+ }
69
+ if (error instanceof GiteaWebhookMalformedPushPayloadError) {
70
+ logger().warn({
71
+ deliveryId,
72
+ issues: error.issues
73
+ }, 'gitea webhook push payload failed schema validation');
74
+ return {
75
+ outcome: 'discarded',
76
+ reason: 'unsupported_event',
77
+ deliveryId
78
+ };
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ //# sourceMappingURL=webhook-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {\n decodeWebhookBody,\n type GetIntegrationConnectionByIdFn,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type StoredWebhookRequest,\n type WebhookProcessingResult,\n} from '@shipfox/api-integration-core-dto';\nimport {verifyHexHmacSignature} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {\n GiteaWebhookMalformedJsonError,\n GiteaWebhookMalformedPushPayloadError,\n handleGiteaWebhook,\n} from '#core/webhook.js';\n\nconst DELIVERY_HEADER = 'x-gitea-delivery';\nconst EVENT_HEADER = 'x-gitea-event';\nconst SIGNATURE_HEADER = 'x-gitea-signature';\n\nexport interface CreateGiteaWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface GiteaWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createGiteaWebhookProcessor(\n options: CreateGiteaWebhookProcessorOptions,\n): GiteaWebhookProcessor {\n return {process: (request) => processGiteaWebhookRequest(options, request)};\n}\n\nasync function processGiteaWebhookRequest(\n options: CreateGiteaWebhookProcessorOptions,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'gitea') {\n throw new Error(`Gitea processor cannot process ${request.route_id} requests`);\n }\n\n const deliveryId = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n if (!deliveryId || !event || !signature) {\n return {outcome: 'discarded', reason: 'missing_required_input'};\n }\n\n const rawBody = Buffer.from(decodeWebhookBody(request.body));\n if (!verifyHexHmacSignature({rawBody, signature, secret: config.GITEA_WEBHOOK_SECRET})) {\n return {outcome: 'discarded', reason: 'invalid_signature', deliveryId};\n }\n\n try {\n const result = await options.coreDb().transaction(async (tx) =>\n handleGiteaWebhook({\n tx,\n deliveryId,\n event,\n rawBody: rawBody.toString('utf8'),\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return result.outcome === 'duplicate'\n ? {outcome: 'duplicate', deliveryId}\n : {outcome: 'processed', deliveryId};\n } catch (error) {\n if (error instanceof GiteaWebhookMalformedJsonError) {\n logger().warn({deliveryId, err: error}, 'gitea webhook payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload', deliveryId};\n }\n if (error instanceof GiteaWebhookMalformedPushPayloadError) {\n logger().warn(\n {deliveryId, issues: error.issues},\n 'gitea webhook push payload failed schema validation',\n );\n return {outcome: 'discarded', reason: 'unsupported_event', deliveryId};\n }\n throw error;\n }\n}\n"],"names":["Buffer","decodeWebhookBody","verifyHexHmacSignature","logger","config","GiteaWebhookMalformedJsonError","GiteaWebhookMalformedPushPayloadError","handleGiteaWebhook","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createGiteaWebhookProcessor","options","process","request","processGiteaWebhookRequest","route_id","Error","deliveryId","headers","event","signature","outcome","reason","rawBody","from","body","secret","GITEA_WEBHOOK_SECRET","result","coreDb","transaction","tx","toString","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","error","warn","err","issues"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AACnC,SACEC,iBAAiB,QAMZ,oCAAoC;AAC3C,SAAQC,sBAAsB,QAAO,wBAAwB;AAC7D,SAAQC,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SACEC,8BAA8B,EAC9BC,qCAAqC,EACrCC,kBAAkB,QACb,mBAAmB;AAE1B,MAAMC,kBAAkB;AACxB,MAAMC,eAAe;AACrB,MAAMC,mBAAmB;AAazB,OAAO,SAASC,4BACdC,OAA2C;IAE3C,OAAO;QAACC,SAAS,CAACC,UAAYC,2BAA2BH,SAASE;IAAQ;AAC5E;AAEA,eAAeC,2BACbH,OAA2C,EAC3CE,OAA6B;IAE7B,IAAIA,QAAQE,QAAQ,KAAK,SAAS;QAChC,MAAM,IAAIC,MAAM,CAAC,+BAA+B,EAAEH,QAAQE,QAAQ,CAAC,SAAS,CAAC;IAC/E;IAEA,MAAME,aAAaJ,QAAQK,OAAO,CAACX,gBAAgB;IACnD,MAAMY,QAAQN,QAAQK,OAAO,CAACV,aAAa;IAC3C,MAAMY,YAAYP,QAAQK,OAAO,CAACT,iBAAiB;IACnD,IAAI,CAACQ,cAAc,CAACE,SAAS,CAACC,WAAW;QACvC,OAAO;YAACC,SAAS;YAAaC,QAAQ;QAAwB;IAChE;IAEA,MAAMC,UAAUxB,OAAOyB,IAAI,CAACxB,kBAAkBa,QAAQY,IAAI;IAC1D,IAAI,CAACxB,uBAAuB;QAACsB;QAASH;QAAWM,QAAQvB,OAAOwB,oBAAoB;IAAA,IAAI;QACtF,OAAO;YAACN,SAAS;YAAaC,QAAQ;YAAqBL;QAAU;IACvE;IAEA,IAAI;QACF,MAAMW,SAAS,MAAMjB,QAAQkB,MAAM,GAAGC,WAAW,CAAC,OAAOC,KACvDzB,mBAAmB;gBACjByB;gBACAd;gBACAE;gBACAI,SAASA,QAAQS,QAAQ,CAAC;gBAC1BC,mBAAmBtB,QAAQsB,iBAAiB;gBAC5CC,oBAAoBvB,QAAQuB,kBAAkB;gBAC9CC,8BAA8BxB,QAAQwB,4BAA4B;YACpE;QAEF,OAAOP,OAAOP,OAAO,KAAK,cACtB;YAACA,SAAS;YAAaJ;QAAU,IACjC;YAACI,SAAS;YAAaJ;QAAU;IACvC,EAAE,OAAOmB,OAAO;QACd,IAAIA,iBAAiBhC,gCAAgC;YACnDF,SAASmC,IAAI,CAAC;gBAACpB;gBAAYqB,KAAKF;YAAK,GAAG;YACxC,OAAO;gBAACf,SAAS;gBAAaC,QAAQ;gBAAqBL;YAAU;QACvE;QACA,IAAImB,iBAAiB/B,uCAAuC;YAC1DH,SAASmC,IAAI,CACX;gBAACpB;gBAAYsB,QAAQH,MAAMG,MAAM;YAAA,GACjC;YAEF,OAAO;gBAAClB,SAAS;gBAAaC,QAAQ;gBAAqBL;YAAU;QACvE;QACA,MAAMmB;IACR;AACF"}
package/dist/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export type { ConnectGiteaConnectionInput } from '#core/connect.js';
11
11
  export { handleGiteaConnect } from '#core/connect.js';
12
12
  export { GiteaIntegrationProviderError, GiteaOrgAlreadyLinkedError, GiteaOrganizationNotFoundError, } from '#core/errors.js';
13
13
  export { GiteaSourceControlProvider } from '#core/source-control.js';
14
+ export type { CreateGiteaWebhookProcessorOptions, GiteaWebhookProcessor, } from '#core/webhook-processor.js';
15
+ export { createGiteaWebhookProcessor } from '#core/webhook-processor.js';
14
16
  export type { GiteaConnection, UpsertGiteaConnectionParams } from '#db/connections.js';
15
17
  export { getGiteaConnectionByConnectionId, getGiteaConnectionByOrg, upsertGiteaConnection, } from '#db/connections.js';
16
18
  export { closeDb, db, migrationsPath };
@@ -35,5 +37,9 @@ export declare function createGiteaIntegrationProvider(options: CreateGiteaInteg
35
37
  externalAccountId: string;
36
38
  }): Promise<string | undefined>;
37
39
  routes: import("@shipfox/node-fastify").RouteGroup[];
40
+ webhookProcessors: {
41
+ routeIds: readonly ["gitea"];
42
+ processor: import("#core/webhook-processor.js").GiteaWebhookProcessor;
43
+ }[];
38
44
  };
39
45
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAuB,KAAK,cAAc,EAAC,MAAM,gBAAgB,CAAC;AACzE,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAC,OAAO,EAAE,EAAE,EAAC,MAAM,WAAW,CAAC;AACtC,OAAO,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAIjD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,oBAAoB,EAAC,MAAM,gBAAgB,CAAC;AACpD,YAAY,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAC,eAAe,EAAE,2BAA2B,EAAC,MAAM,oBAAoB,CAAC;AACrF,OAAO,EACL,gCAAgC,EAChC,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAC,CAAC;AAErC,MAAM,WAAW,qCAAqC;IACpD,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CACtB,KAAK,EAAE,2BAA2B,KAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,qCAAqC;;;;;;sCASvD;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;EAiB9F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAuB,KAAK,cAAc,EAAC,MAAM,gBAAgB,CAAC;AACzE,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAC,OAAO,EAAE,EAAE,EAAC,MAAM,WAAW,CAAC;AACtC,OAAO,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAIjD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,oBAAoB,EAAC,MAAM,gBAAgB,CAAC;AACpD,YAAY,EAAC,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AACnE,YAAY,EACV,kCAAkC,EAClC,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,2BAA2B,EAAC,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAC,eAAe,EAAE,2BAA2B,EAAC,MAAM,oBAAoB,CAAC;AACrF,OAAO,EACL,gCAAgC,EAChC,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAC,CAAC;AAErC,MAAM,WAAW,qCAAqC;IACpD,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CACtB,KAAK,EAAE,2BAA2B,KAC/B,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,qCAAqC;;;;;;sCAUvD;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;EAmB9F"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { giteaProviderKind } from '@shipfox/api-integration-gitea-dto';
2
2
  import { createGiteaApiClient } from '#api/client.js';
3
3
  import { giteaConnectionExternalUrl } from '#core/connection-url.js';
4
4
  import { GiteaSourceControlProvider } from '#core/source-control.js';
5
+ import { createGiteaWebhookProcessor } from '#core/webhook-processor.js';
5
6
  import { closeDb, db } from '#db/db.js';
6
7
  import { migrationsPath } from '#db/migrations.js';
7
8
  import { createGiteaConnectionRoutes } from '#presentation/routes/connections.js';
@@ -10,10 +11,12 @@ export { createGiteaApiClient } from '#api/client.js';
10
11
  export { handleGiteaConnect } from '#core/connect.js';
11
12
  export { GiteaIntegrationProviderError, GiteaOrgAlreadyLinkedError, GiteaOrganizationNotFoundError } from '#core/errors.js';
12
13
  export { GiteaSourceControlProvider } from '#core/source-control.js';
14
+ export { createGiteaWebhookProcessor } from '#core/webhook-processor.js';
13
15
  export { getGiteaConnectionByConnectionId, getGiteaConnectionByOrg, upsertGiteaConnection } from '#db/connections.js';
14
16
  export { closeDb, db, migrationsPath };
15
17
  export function createGiteaIntegrationProvider(options) {
16
18
  const gitea = options.gitea ?? createGiteaApiClient();
19
+ const webhookProcessor = createGiteaWebhookProcessor(options);
17
20
  return {
18
21
  provider: giteaProviderKind,
19
22
  displayName: 'Gitea',
@@ -33,8 +36,17 @@ export function createGiteaIntegrationProvider(options) {
33
36
  coreDb: options.coreDb,
34
37
  publishSourcePush: options.publishSourcePush,
35
38
  recordDeliveryOnly: options.recordDeliveryOnly,
36
- getIntegrationConnectionById: options.getIntegrationConnectionById
39
+ getIntegrationConnectionById: options.getIntegrationConnectionById,
40
+ processor: webhookProcessor
37
41
  })
42
+ ],
43
+ webhookProcessors: [
44
+ {
45
+ routeIds: [
46
+ 'gitea'
47
+ ],
48
+ processor: webhookProcessor
49
+ }
38
50
  ]
39
51
  };
40
52
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n GetIntegrationConnectionByIdFn,\n IntegrationConnection,\n PublishSourcePushFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {createGiteaApiClient, type GiteaApiClient} from '#api/client.js';\nimport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nimport {giteaConnectionExternalUrl} from '#core/connection-url.js';\nimport {GiteaSourceControlProvider} from '#core/source-control.js';\nimport {closeDb, db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {createGiteaConnectionRoutes} from '#presentation/routes/connections.js';\nimport {createGiteaWebhookRoutes} from '#presentation/routes/webhooks.js';\n\nexport type {\n GiteaApiClient,\n GiteaFileContent,\n GiteaRepository,\n GiteaRepositoryPage,\n GiteaTree,\n GiteaTreeBlob,\n} from '#api/client.js';\nexport {createGiteaApiClient} from '#api/client.js';\nexport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nexport {handleGiteaConnect} from '#core/connect.js';\nexport {\n GiteaIntegrationProviderError,\n GiteaOrgAlreadyLinkedError,\n GiteaOrganizationNotFoundError,\n} from '#core/errors.js';\nexport {GiteaSourceControlProvider} from '#core/source-control.js';\nexport type {GiteaConnection, UpsertGiteaConnectionParams} from '#db/connections.js';\nexport {\n getGiteaConnectionByConnectionId,\n getGiteaConnectionByOrg,\n upsertGiteaConnection,\n} from '#db/connections.js';\nexport {closeDb, db, migrationsPath};\n\nexport interface CreateGiteaIntegrationProviderOptions {\n gitea?: GiteaApiClient | undefined;\n getExistingGiteaConnection: (input: {\n org: string;\n }) => Promise<IntegrationConnection<'gitea'> | undefined>;\n connectGiteaConnection: (\n input: ConnectGiteaConnectionInput,\n ) => Promise<IntegrationConnection<'gitea'>>;\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport function createGiteaIntegrationProvider(options: CreateGiteaIntegrationProviderOptions) {\n const gitea = options.gitea ?? createGiteaApiClient();\n\n return {\n provider: giteaProviderKind,\n displayName: 'Gitea',\n adapters: {\n source_control: new GiteaSourceControlProvider(gitea),\n },\n connectionExternalUrl(connection: {externalAccountId: string}): Promise<string | undefined> {\n return Promise.resolve(giteaConnectionExternalUrl(connection.externalAccountId));\n },\n routes: [\n createGiteaConnectionRoutes({\n gitea,\n getExistingGiteaConnection: options.getExistingGiteaConnection,\n connectGiteaConnection: options.connectGiteaConnection,\n }),\n createGiteaWebhookRoutes({\n coreDb: options.coreDb,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n ],\n };\n}\n"],"names":["giteaProviderKind","createGiteaApiClient","giteaConnectionExternalUrl","GiteaSourceControlProvider","closeDb","db","migrationsPath","createGiteaConnectionRoutes","createGiteaWebhookRoutes","handleGiteaConnect","GiteaIntegrationProviderError","GiteaOrgAlreadyLinkedError","GiteaOrganizationNotFoundError","getGiteaConnectionByConnectionId","getGiteaConnectionByOrg","upsertGiteaConnection","createGiteaIntegrationProvider","options","gitea","provider","displayName","adapters","source_control","connectionExternalUrl","connection","Promise","resolve","externalAccountId","routes","getExistingGiteaConnection","connectGiteaConnection","coreDb","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById"],"mappings":"AAMA,SAAQA,iBAAiB,QAAO,qCAAqC;AAErE,SAAQC,oBAAoB,QAA4B,iBAAiB;AAEzE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,OAAO,EAAEC,EAAE,QAAO,YAAY;AACtC,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,2BAA2B,QAAO,sCAAsC;AAChF,SAAQC,wBAAwB,QAAO,mCAAmC;AAU1E,SAAQP,oBAAoB,QAAO,iBAAiB;AAEpD,SAAQQ,kBAAkB,QAAO,mBAAmB;AACpD,SACEC,6BAA6B,EAC7BC,0BAA0B,EAC1BC,8BAA8B,QACzB,kBAAkB;AACzB,SAAQT,0BAA0B,QAAO,0BAA0B;AAEnE,SACEU,gCAAgC,EAChCC,uBAAuB,EACvBC,qBAAqB,QAChB,qBAAqB;AAC5B,SAAQX,OAAO,EAAEC,EAAE,EAAEC,cAAc,GAAE;AAgBrC,OAAO,SAASU,+BAA+BC,OAA8C;IAC3F,MAAMC,QAAQD,QAAQC,KAAK,IAAIjB;IAE/B,OAAO;QACLkB,UAAUnB;QACVoB,aAAa;QACbC,UAAU;YACRC,gBAAgB,IAAInB,2BAA2Be;QACjD;QACAK,uBAAsBC,UAAuC;YAC3D,OAAOC,QAAQC,OAAO,CAACxB,2BAA2BsB,WAAWG,iBAAiB;QAChF;QACAC,QAAQ;YACNrB,4BAA4B;gBAC1BW;gBACAW,4BAA4BZ,QAAQY,0BAA0B;gBAC9DC,wBAAwBb,QAAQa,sBAAsB;YACxD;YACAtB,yBAAyB;gBACvBuB,QAAQd,QAAQc,MAAM;gBACtBC,mBAAmBf,QAAQe,iBAAiB;gBAC5CC,oBAAoBhB,QAAQgB,kBAAkB;gBAC9CC,8BAA8BjB,QAAQiB,4BAA4B;YACpE;SACD;IACH;AACF"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n GetIntegrationConnectionByIdFn,\n IntegrationConnection,\n PublishSourcePushFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {createGiteaApiClient, type GiteaApiClient} from '#api/client.js';\nimport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nimport {giteaConnectionExternalUrl} from '#core/connection-url.js';\nimport {GiteaSourceControlProvider} from '#core/source-control.js';\nimport {createGiteaWebhookProcessor} from '#core/webhook-processor.js';\nimport {closeDb, db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {createGiteaConnectionRoutes} from '#presentation/routes/connections.js';\nimport {createGiteaWebhookRoutes} from '#presentation/routes/webhooks.js';\n\nexport type {\n GiteaApiClient,\n GiteaFileContent,\n GiteaRepository,\n GiteaRepositoryPage,\n GiteaTree,\n GiteaTreeBlob,\n} from '#api/client.js';\nexport {createGiteaApiClient} from '#api/client.js';\nexport type {ConnectGiteaConnectionInput} from '#core/connect.js';\nexport {handleGiteaConnect} from '#core/connect.js';\nexport {\n GiteaIntegrationProviderError,\n GiteaOrgAlreadyLinkedError,\n GiteaOrganizationNotFoundError,\n} from '#core/errors.js';\nexport {GiteaSourceControlProvider} from '#core/source-control.js';\nexport type {\n CreateGiteaWebhookProcessorOptions,\n GiteaWebhookProcessor,\n} from '#core/webhook-processor.js';\nexport {createGiteaWebhookProcessor} from '#core/webhook-processor.js';\nexport type {GiteaConnection, UpsertGiteaConnectionParams} from '#db/connections.js';\nexport {\n getGiteaConnectionByConnectionId,\n getGiteaConnectionByOrg,\n upsertGiteaConnection,\n} from '#db/connections.js';\nexport {closeDb, db, migrationsPath};\n\nexport interface CreateGiteaIntegrationProviderOptions {\n gitea?: GiteaApiClient | undefined;\n getExistingGiteaConnection: (input: {\n org: string;\n }) => Promise<IntegrationConnection<'gitea'> | undefined>;\n connectGiteaConnection: (\n input: ConnectGiteaConnectionInput,\n ) => Promise<IntegrationConnection<'gitea'>>;\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport function createGiteaIntegrationProvider(options: CreateGiteaIntegrationProviderOptions) {\n const gitea = options.gitea ?? createGiteaApiClient();\n const webhookProcessor = createGiteaWebhookProcessor(options);\n\n return {\n provider: giteaProviderKind,\n displayName: 'Gitea',\n adapters: {\n source_control: new GiteaSourceControlProvider(gitea),\n },\n connectionExternalUrl(connection: {externalAccountId: string}): Promise<string | undefined> {\n return Promise.resolve(giteaConnectionExternalUrl(connection.externalAccountId));\n },\n routes: [\n createGiteaConnectionRoutes({\n gitea,\n getExistingGiteaConnection: options.getExistingGiteaConnection,\n connectGiteaConnection: options.connectGiteaConnection,\n }),\n createGiteaWebhookRoutes({\n coreDb: options.coreDb,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n processor: webhookProcessor,\n }),\n ],\n webhookProcessors: [{routeIds: ['gitea'] as const, processor: webhookProcessor}],\n };\n}\n"],"names":["giteaProviderKind","createGiteaApiClient","giteaConnectionExternalUrl","GiteaSourceControlProvider","createGiteaWebhookProcessor","closeDb","db","migrationsPath","createGiteaConnectionRoutes","createGiteaWebhookRoutes","handleGiteaConnect","GiteaIntegrationProviderError","GiteaOrgAlreadyLinkedError","GiteaOrganizationNotFoundError","getGiteaConnectionByConnectionId","getGiteaConnectionByOrg","upsertGiteaConnection","createGiteaIntegrationProvider","options","gitea","webhookProcessor","provider","displayName","adapters","source_control","connectionExternalUrl","connection","Promise","resolve","externalAccountId","routes","getExistingGiteaConnection","connectGiteaConnection","coreDb","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","processor","webhookProcessors","routeIds"],"mappings":"AAMA,SAAQA,iBAAiB,QAAO,qCAAqC;AAErE,SAAQC,oBAAoB,QAA4B,iBAAiB;AAEzE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,0BAA0B,QAAO,0BAA0B;AACnE,SAAQC,2BAA2B,QAAO,6BAA6B;AACvE,SAAQC,OAAO,EAAEC,EAAE,QAAO,YAAY;AACtC,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,2BAA2B,QAAO,sCAAsC;AAChF,SAAQC,wBAAwB,QAAO,mCAAmC;AAU1E,SAAQR,oBAAoB,QAAO,iBAAiB;AAEpD,SAAQS,kBAAkB,QAAO,mBAAmB;AACpD,SACEC,6BAA6B,EAC7BC,0BAA0B,EAC1BC,8BAA8B,QACzB,kBAAkB;AACzB,SAAQV,0BAA0B,QAAO,0BAA0B;AAKnE,SAAQC,2BAA2B,QAAO,6BAA6B;AAEvE,SACEU,gCAAgC,EAChCC,uBAAuB,EACvBC,qBAAqB,QAChB,qBAAqB;AAC5B,SAAQX,OAAO,EAAEC,EAAE,EAAEC,cAAc,GAAE;AAgBrC,OAAO,SAASU,+BAA+BC,OAA8C;IAC3F,MAAMC,QAAQD,QAAQC,KAAK,IAAIlB;IAC/B,MAAMmB,mBAAmBhB,4BAA4Bc;IAErD,OAAO;QACLG,UAAUrB;QACVsB,aAAa;QACbC,UAAU;YACRC,gBAAgB,IAAIrB,2BAA2BgB;QACjD;QACAM,uBAAsBC,UAAuC;YAC3D,OAAOC,QAAQC,OAAO,CAAC1B,2BAA2BwB,WAAWG,iBAAiB;QAChF;QACAC,QAAQ;YACNtB,4BAA4B;gBAC1BW;gBACAY,4BAA4Bb,QAAQa,0BAA0B;gBAC9DC,wBAAwBd,QAAQc,sBAAsB;YACxD;YACAvB,yBAAyB;gBACvBwB,QAAQf,QAAQe,MAAM;gBACtBC,mBAAmBhB,QAAQgB,iBAAiB;gBAC5CC,oBAAoBjB,QAAQiB,kBAAkB;gBAC9CC,8BAA8BlB,QAAQkB,4BAA4B;gBAClEC,WAAWjB;YACb;SACD;QACDkB,mBAAmB;YAAC;gBAACC,UAAU;oBAAC;iBAAQ;gBAAWF,WAAWjB;YAAgB;SAAE;IAClF;AACF"}
@@ -1,11 +1,13 @@
1
1
  import type { GetIntegrationConnectionByIdFn, PublishSourcePushFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
2
2
  import { type RouteGroup } from '@shipfox/node-fastify';
3
3
  import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
4
+ import { type GiteaWebhookProcessor } from '#core/webhook-processor.js';
4
5
  export interface CreateGiteaWebhookRoutesOptions {
5
6
  coreDb: () => NodePgDatabase<Record<string, unknown>>;
6
7
  publishSourcePush: PublishSourcePushFn;
7
8
  recordDeliveryOnly: RecordDeliveryOnlyFn;
8
9
  getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
10
+ processor?: GiteaWebhookProcessor | undefined;
9
11
  }
10
12
  export declare function createGiteaWebhookRoutes(options: CreateGiteaWebhookRoutesOptions): RouteGroup;
11
13
  //# sourceMappingURL=webhooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAEL,KAAK,UAAU,EAIhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAY9D,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,UAAU,CA6E7F"}
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EAGrB,MAAM,mCAAmC,CAAC;AAK3C,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAA8B,KAAK,qBAAqB,EAAC,MAAM,4BAA4B,CAAC;AAMnG,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;IAC7D,SAAS,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CAC/C;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,UAAU,CA+C7F"}
@@ -1,12 +1,12 @@
1
- import { Buffer } from 'node:buffer';
2
- import { defineRoute, rawBodyPlugin, verifyHexHmacSignature, WEBHOOK_BODY_LIMIT } from '@shipfox/node-fastify';
3
- import { logger } from '@shipfox/node-opentelemetry';
4
- import { config } from '#config.js';
5
- import { GiteaWebhookMalformedJsonError, GiteaWebhookMalformedPushPayloadError, handleGiteaWebhook } from '#core/webhook.js';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createStoredWebhookRequest, WEBHOOK_MAX_RAW_BODY_BYTES } from '@shipfox/api-integration-core-dto';
3
+ import { ClientError, defineRoute, rawBodyPlugin, WEBHOOK_BODY_LIMIT } from '@shipfox/node-fastify';
4
+ import { createGiteaWebhookProcessor } from '#core/webhook-processor.js';
6
5
  const SIGNATURE_HEADER = 'x-gitea-signature';
7
6
  const EVENT_HEADER = 'x-gitea-event';
8
7
  const DELIVERY_HEADER = 'x-gitea-delivery';
9
8
  export function createGiteaWebhookRoutes(options) {
9
+ const processor = options.processor ?? createGiteaWebhookProcessor(options);
10
10
  const pushRoute = defineRoute({
11
11
  method: 'POST',
12
12
  path: '/',
@@ -37,61 +37,18 @@ export function createGiteaWebhookRoutes(options) {
37
37
  error: 'missing X-Gitea-Event header'
38
38
  };
39
39
  }
40
- const body = request.body;
41
- if (!Buffer.isBuffer(body)) {
40
+ if (!(request.body instanceof Uint8Array)) {
42
41
  reply.code(400);
43
42
  return {
44
43
  error: 'expected raw JSON body'
45
44
  };
46
45
  }
47
- const rawBody = body.toString('utf8');
48
- if (!verifyHexHmacSignature({
49
- rawBody,
50
- signature,
51
- secret: config.GITEA_WEBHOOK_SECRET
52
- })) {
53
- reply.code(401);
54
- return {
55
- error: 'invalid signature'
56
- };
57
- }
58
- try {
59
- await options.coreDb().transaction(async (tx)=>{
60
- await handleGiteaWebhook({
61
- tx,
62
- deliveryId,
63
- event,
64
- rawBody,
65
- publishSourcePush: options.publishSourcePush,
66
- recordDeliveryOnly: options.recordDeliveryOnly,
67
- getIntegrationConnectionById: options.getIntegrationConnectionById
68
- });
69
- });
70
- } catch (error) {
71
- if (error instanceof GiteaWebhookMalformedJsonError) {
72
- logger().warn({
73
- deliveryId,
74
- err: error
75
- }, 'gitea webhook payload JSON parse failed');
76
- reply.code(400);
77
- return {
78
- error: 'malformed JSON'
79
- };
80
- }
81
- if (error instanceof GiteaWebhookMalformedPushPayloadError) {
82
- logger().warn({
83
- deliveryId,
84
- issues: error.issues
85
- }, 'gitea webhook push payload failed schema validation');
86
- reply.code(400);
87
- return {
88
- error: 'malformed push payload'
89
- };
90
- }
91
- throw error;
92
- }
93
- reply.code(204);
94
- return null;
46
+ const result = await processor.process(createGiteaStoredWebhookRequest({
47
+ body: request.body,
48
+ headers: request.headers,
49
+ rawQueryString: request.raw.url?.split('?')[1] ?? ''
50
+ }));
51
+ return sendGiteaWebhookResponse(reply, result);
95
52
  }
96
53
  });
97
54
  return {
@@ -105,5 +62,64 @@ export function createGiteaWebhookRoutes(options) {
105
62
  ]
106
63
  };
107
64
  }
65
+ function createGiteaStoredWebhookRequest(input) {
66
+ if (input.body.byteLength > WEBHOOK_MAX_RAW_BODY_BYTES) {
67
+ throw new ClientError('Webhook request body is too large', 'body-too-large', {
68
+ status: 413
69
+ });
70
+ }
71
+ try {
72
+ return createStoredWebhookRequest({
73
+ requestId: randomUUID(),
74
+ routeId: 'gitea',
75
+ receivedAt: new Date().toISOString(),
76
+ rawQueryString: input.rawQueryString,
77
+ headers: giteaWebhookHeaders(input.headers),
78
+ body: input.body
79
+ });
80
+ } catch (error) {
81
+ throw new ClientError('Webhook request metadata is invalid', 'invalid-webhook-request', {
82
+ cause: error
83
+ });
84
+ }
85
+ }
86
+ function giteaWebhookHeaders(headers) {
87
+ return Object.fromEntries([
88
+ 'content-type',
89
+ DELIVERY_HEADER,
90
+ EVENT_HEADER,
91
+ SIGNATURE_HEADER
92
+ ].flatMap((name)=>{
93
+ const value = headers[name];
94
+ return typeof value === 'string' ? [
95
+ [
96
+ name,
97
+ value
98
+ ]
99
+ ] : [];
100
+ }));
101
+ }
102
+ function sendGiteaWebhookResponse(reply, result) {
103
+ if (result.outcome === 'discarded' && result.reason === 'invalid_signature') {
104
+ reply.code(401);
105
+ return {
106
+ error: 'invalid signature'
107
+ };
108
+ }
109
+ if (result.outcome === 'discarded' && result.reason === 'malformed_payload') {
110
+ reply.code(400);
111
+ return {
112
+ error: 'malformed JSON'
113
+ };
114
+ }
115
+ if (result.outcome === 'discarded' && result.reason === 'unsupported_event') {
116
+ reply.code(400);
117
+ return {
118
+ error: 'malformed push payload'
119
+ };
120
+ }
121
+ reply.code(204);
122
+ return null;
123
+ }
108
124
 
109
125
  //# sourceMappingURL=webhooks.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/webhooks.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport type {\n GetIntegrationConnectionByIdFn,\n PublishSourcePushFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {\n defineRoute,\n type RouteGroup,\n rawBodyPlugin,\n verifyHexHmacSignature,\n WEBHOOK_BODY_LIMIT,\n} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {\n GiteaWebhookMalformedJsonError,\n GiteaWebhookMalformedPushPayloadError,\n handleGiteaWebhook,\n} from '#core/webhook.js';\n\nconst SIGNATURE_HEADER = 'x-gitea-signature';\nconst EVENT_HEADER = 'x-gitea-event';\nconst DELIVERY_HEADER = 'x-gitea-delivery';\n\nexport interface CreateGiteaWebhookRoutesOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport function createGiteaWebhookRoutes(options: CreateGiteaWebhookRoutesOptions): RouteGroup {\n const pushRoute = defineRoute({\n method: 'POST',\n path: '/',\n auth: [],\n description: 'Gitea org webhook receiver.',\n options: {bodyLimit: WEBHOOK_BODY_LIMIT},\n handler: async (request, reply) => {\n const deliveryId = request.headers[DELIVERY_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n const event = request.headers[EVENT_HEADER];\n\n if (typeof deliveryId !== 'string' || !deliveryId) {\n reply.code(400);\n return {error: 'missing X-Gitea-Delivery header'};\n }\n if (typeof signature !== 'string' || !signature) {\n reply.code(401);\n return {error: 'missing X-Gitea-Signature header'};\n }\n if (typeof event !== 'string' || !event) {\n reply.code(400);\n return {error: 'missing X-Gitea-Event header'};\n }\n\n const body = request.body;\n if (!Buffer.isBuffer(body)) {\n reply.code(400);\n return {error: 'expected raw JSON body'};\n }\n const rawBody = body.toString('utf8');\n\n if (!verifyHexHmacSignature({rawBody, signature, secret: config.GITEA_WEBHOOK_SECRET})) {\n reply.code(401);\n return {error: 'invalid signature'};\n }\n\n try {\n await options.coreDb().transaction(async (tx) => {\n await handleGiteaWebhook({\n tx,\n deliveryId,\n event,\n rawBody,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n });\n });\n } catch (error) {\n if (error instanceof GiteaWebhookMalformedJsonError) {\n logger().warn({deliveryId, err: error}, 'gitea webhook payload JSON parse failed');\n reply.code(400);\n return {error: 'malformed JSON'};\n }\n if (error instanceof GiteaWebhookMalformedPushPayloadError) {\n logger().warn(\n {deliveryId, issues: error.issues},\n 'gitea webhook push payload failed schema validation',\n );\n reply.code(400);\n return {error: 'malformed push payload'};\n }\n throw error;\n }\n\n reply.code(204);\n return null;\n },\n });\n\n return {\n prefix: '/webhooks/integrations/gitea',\n auth: [],\n plugins: [rawBodyPlugin],\n routes: [pushRoute],\n };\n}\n"],"names":["Buffer","defineRoute","rawBodyPlugin","verifyHexHmacSignature","WEBHOOK_BODY_LIMIT","logger","config","GiteaWebhookMalformedJsonError","GiteaWebhookMalformedPushPayloadError","handleGiteaWebhook","SIGNATURE_HEADER","EVENT_HEADER","DELIVERY_HEADER","createGiteaWebhookRoutes","options","pushRoute","method","path","auth","description","bodyLimit","handler","request","reply","deliveryId","headers","signature","event","code","error","body","isBuffer","rawBody","toString","secret","GITEA_WEBHOOK_SECRET","coreDb","transaction","tx","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","warn","err","issues","prefix","plugins","routes"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AAMnC,SACEC,WAAW,EAEXC,aAAa,EACbC,sBAAsB,EACtBC,kBAAkB,QACb,wBAAwB;AAC/B,SAAQC,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SACEC,8BAA8B,EAC9BC,qCAAqC,EACrCC,kBAAkB,QACb,mBAAmB;AAE1B,MAAMC,mBAAmB;AACzB,MAAMC,eAAe;AACrB,MAAMC,kBAAkB;AASxB,OAAO,SAASC,yBAAyBC,OAAwC;IAC/E,MAAMC,YAAYd,YAAY;QAC5Be,QAAQ;QACRC,MAAM;QACNC,MAAM,EAAE;QACRC,aAAa;QACbL,SAAS;YAACM,WAAWhB;QAAkB;QACvCiB,SAAS,OAAOC,SAASC;YACvB,MAAMC,aAAaF,QAAQG,OAAO,CAACb,gBAAgB;YACnD,MAAMc,YAAYJ,QAAQG,OAAO,CAACf,iBAAiB;YACnD,MAAMiB,QAAQL,QAAQG,OAAO,CAACd,aAAa;YAE3C,IAAI,OAAOa,eAAe,YAAY,CAACA,YAAY;gBACjDD,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAiC;YAClD;YACA,IAAI,OAAOH,cAAc,YAAY,CAACA,WAAW;gBAC/CH,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAkC;YACnD;YACA,IAAI,OAAOF,UAAU,YAAY,CAACA,OAAO;gBACvCJ,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAA8B;YAC/C;YAEA,MAAMC,OAAOR,QAAQQ,IAAI;YACzB,IAAI,CAAC9B,OAAO+B,QAAQ,CAACD,OAAO;gBAC1BP,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAwB;YACzC;YACA,MAAMG,UAAUF,KAAKG,QAAQ,CAAC;YAE9B,IAAI,CAAC9B,uBAAuB;gBAAC6B;gBAASN;gBAAWQ,QAAQ5B,OAAO6B,oBAAoB;YAAA,IAAI;gBACtFZ,MAAMK,IAAI,CAAC;gBACX,OAAO;oBAACC,OAAO;gBAAmB;YACpC;YAEA,IAAI;gBACF,MAAMf,QAAQsB,MAAM,GAAGC,WAAW,CAAC,OAAOC;oBACxC,MAAM7B,mBAAmB;wBACvB6B;wBACAd;wBACAG;wBACAK;wBACAO,mBAAmBzB,QAAQyB,iBAAiB;wBAC5CC,oBAAoB1B,QAAQ0B,kBAAkB;wBAC9CC,8BAA8B3B,QAAQ2B,4BAA4B;oBACpE;gBACF;YACF,EAAE,OAAOZ,OAAO;gBACd,IAAIA,iBAAiBtB,gCAAgC;oBACnDF,SAASqC,IAAI,CAAC;wBAAClB;wBAAYmB,KAAKd;oBAAK,GAAG;oBACxCN,MAAMK,IAAI,CAAC;oBACX,OAAO;wBAACC,OAAO;oBAAgB;gBACjC;gBACA,IAAIA,iBAAiBrB,uCAAuC;oBAC1DH,SAASqC,IAAI,CACX;wBAAClB;wBAAYoB,QAAQf,MAAMe,MAAM;oBAAA,GACjC;oBAEFrB,MAAMK,IAAI,CAAC;oBACX,OAAO;wBAACC,OAAO;oBAAwB;gBACzC;gBACA,MAAMA;YACR;YAEAN,MAAMK,IAAI,CAAC;YACX,OAAO;QACT;IACF;IAEA,OAAO;QACLiB,QAAQ;QACR3B,MAAM,EAAE;QACR4B,SAAS;YAAC5C;SAAc;QACxB6C,QAAQ;YAAChC;SAAU;IACrB;AACF"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/webhooks.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport type {\n GetIntegrationConnectionByIdFn,\n PublishSourcePushFn,\n RecordDeliveryOnlyFn,\n StoredWebhookRequest,\n WebhookProcessingResult,\n} from '@shipfox/api-integration-core-dto';\nimport {\n createStoredWebhookRequest,\n WEBHOOK_MAX_RAW_BODY_BYTES,\n} from '@shipfox/api-integration-core-dto';\nimport {\n ClientError,\n defineRoute,\n type RouteGroup,\n rawBodyPlugin,\n WEBHOOK_BODY_LIMIT,\n} from '@shipfox/node-fastify';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {createGiteaWebhookProcessor, type GiteaWebhookProcessor} from '#core/webhook-processor.js';\n\nconst SIGNATURE_HEADER = 'x-gitea-signature';\nconst EVENT_HEADER = 'x-gitea-event';\nconst DELIVERY_HEADER = 'x-gitea-delivery';\n\nexport interface CreateGiteaWebhookRoutesOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n processor?: GiteaWebhookProcessor | undefined;\n}\n\nexport function createGiteaWebhookRoutes(options: CreateGiteaWebhookRoutesOptions): RouteGroup {\n const processor = options.processor ?? createGiteaWebhookProcessor(options);\n const pushRoute = defineRoute({\n method: 'POST',\n path: '/',\n auth: [],\n description: 'Gitea org webhook receiver.',\n options: {bodyLimit: WEBHOOK_BODY_LIMIT},\n handler: async (request, reply) => {\n const deliveryId = request.headers[DELIVERY_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n const event = request.headers[EVENT_HEADER];\n\n if (typeof deliveryId !== 'string' || !deliveryId) {\n reply.code(400);\n return {error: 'missing X-Gitea-Delivery header'};\n }\n if (typeof signature !== 'string' || !signature) {\n reply.code(401);\n return {error: 'missing X-Gitea-Signature header'};\n }\n if (typeof event !== 'string' || !event) {\n reply.code(400);\n return {error: 'missing X-Gitea-Event header'};\n }\n\n if (!(request.body instanceof Uint8Array)) {\n reply.code(400);\n return {error: 'expected raw JSON body'};\n }\n const result = await processor.process(\n createGiteaStoredWebhookRequest({\n body: request.body,\n headers: request.headers,\n rawQueryString: request.raw.url?.split('?')[1] ?? '',\n }),\n );\n return sendGiteaWebhookResponse(reply, result);\n },\n });\n\n return {\n prefix: '/webhooks/integrations/gitea',\n auth: [],\n plugins: [rawBodyPlugin],\n routes: [pushRoute],\n };\n}\n\nfunction createGiteaStoredWebhookRequest(input: {\n body: Uint8Array;\n headers: Record<string, string | string[] | undefined>;\n rawQueryString: string;\n}): StoredWebhookRequest {\n if (input.body.byteLength > WEBHOOK_MAX_RAW_BODY_BYTES) {\n throw new ClientError('Webhook request body is too large', 'body-too-large', {status: 413});\n }\n try {\n return createStoredWebhookRequest({\n requestId: randomUUID(),\n routeId: 'gitea',\n receivedAt: new Date().toISOString(),\n rawQueryString: input.rawQueryString,\n headers: giteaWebhookHeaders(input.headers),\n body: input.body,\n });\n } catch (error) {\n throw new ClientError('Webhook request metadata is invalid', 'invalid-webhook-request', {\n cause: error,\n });\n }\n}\n\nfunction giteaWebhookHeaders(headers: Record<string, string | string[] | undefined>) {\n return Object.fromEntries(\n ['content-type', DELIVERY_HEADER, EVENT_HEADER, SIGNATURE_HEADER].flatMap((name) => {\n const value = headers[name];\n return typeof value === 'string' ? [[name, value]] : [];\n }),\n );\n}\n\nfunction sendGiteaWebhookResponse(\n reply: {code(statusCode: number): void},\n result: WebhookProcessingResult,\n) {\n if (result.outcome === 'discarded' && result.reason === 'invalid_signature') {\n reply.code(401);\n return {error: 'invalid signature'};\n }\n if (result.outcome === 'discarded' && result.reason === 'malformed_payload') {\n reply.code(400);\n return {error: 'malformed JSON'};\n }\n if (result.outcome === 'discarded' && result.reason === 'unsupported_event') {\n reply.code(400);\n return {error: 'malformed push payload'};\n }\n reply.code(204);\n return null;\n}\n"],"names":["randomUUID","createStoredWebhookRequest","WEBHOOK_MAX_RAW_BODY_BYTES","ClientError","defineRoute","rawBodyPlugin","WEBHOOK_BODY_LIMIT","createGiteaWebhookProcessor","SIGNATURE_HEADER","EVENT_HEADER","DELIVERY_HEADER","createGiteaWebhookRoutes","options","processor","pushRoute","method","path","auth","description","bodyLimit","handler","request","reply","deliveryId","headers","signature","event","code","error","body","Uint8Array","result","process","createGiteaStoredWebhookRequest","rawQueryString","raw","url","split","sendGiteaWebhookResponse","prefix","plugins","routes","input","byteLength","status","requestId","routeId","receivedAt","Date","toISOString","giteaWebhookHeaders","cause","Object","fromEntries","flatMap","name","value","outcome","reason"],"mappings":"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"}