@shipfox/api-integration-linear 7.1.0 → 9.0.1

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.
Files changed (58) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +38 -0
  3. package/dist/core/agent-tools-provider.d.ts +2 -2
  4. package/dist/core/agent-tools-provider.d.ts.map +1 -1
  5. package/dist/core/agent-tools-provider.js.map +1 -1
  6. package/dist/core/agent-tools.d.ts +1 -1
  7. package/dist/core/agent-tools.d.ts.map +1 -1
  8. package/dist/core/agent-tools.js.map +1 -1
  9. package/dist/core/errors.d.ts +1 -1
  10. package/dist/core/errors.d.ts.map +1 -1
  11. package/dist/core/errors.js +1 -1
  12. package/dist/core/errors.js.map +1 -1
  13. package/dist/core/install.d.ts +1 -1
  14. package/dist/core/install.d.ts.map +1 -1
  15. package/dist/core/install.js.map +1 -1
  16. package/dist/core/webhook-processor.d.ts +1 -1
  17. package/dist/core/webhook-processor.d.ts.map +1 -1
  18. package/dist/core/webhook-processor.js +1 -1
  19. package/dist/core/webhook-processor.js.map +1 -1
  20. package/dist/core/webhook.d.ts +1 -1
  21. package/dist/core/webhook.d.ts.map +1 -1
  22. package/dist/core/webhook.js.map +1 -1
  23. package/dist/presentation/dto/integrations.d.ts +4 -5
  24. package/dist/presentation/dto/integrations.d.ts.map +1 -1
  25. package/dist/presentation/dto/integrations.js +15 -2
  26. package/dist/presentation/dto/integrations.js.map +1 -1
  27. package/dist/presentation/e2eRoutes/create-connection.d.ts +1 -1
  28. package/dist/presentation/e2eRoutes/create-connection.d.ts.map +1 -1
  29. package/dist/presentation/e2eRoutes/create-connection.js.map +1 -1
  30. package/dist/presentation/routes/errors.js +1 -1
  31. package/dist/presentation/routes/errors.js.map +1 -1
  32. package/dist/presentation/routes/install.d.ts +1 -1
  33. package/dist/presentation/routes/install.d.ts.map +1 -1
  34. package/dist/presentation/routes/install.js.map +1 -1
  35. package/dist/presentation/routes/webhooks.d.ts +1 -1
  36. package/dist/presentation/routes/webhooks.d.ts.map +1 -1
  37. package/dist/presentation/routes/webhooks.js +1 -1
  38. package/dist/presentation/routes/webhooks.js.map +1 -1
  39. package/dist/tsconfig.test.tsbuildinfo +1 -1
  40. package/package.json +12 -14
  41. package/src/core/agent-tools-provider.test.ts +1 -1
  42. package/src/core/agent-tools-provider.ts +1 -1
  43. package/src/core/agent-tools.ts +1 -1
  44. package/src/core/errors.ts +1 -1
  45. package/src/core/install.ts +1 -1
  46. package/src/core/webhook-processor.test.ts +2 -2
  47. package/src/core/webhook-processor.ts +5 -5
  48. package/src/core/webhook.test.ts +2 -2
  49. package/src/core/webhook.ts +6 -6
  50. package/src/presentation/dto/integrations.ts +20 -3
  51. package/src/presentation/e2eRoutes/create-connection.ts +1 -1
  52. package/src/presentation/e2eRoutes/index.test.ts +1 -1
  53. package/src/presentation/routes/errors.ts +1 -1
  54. package/src/presentation/routes/install.test.ts +1 -1
  55. package/src/presentation/routes/install.ts +1 -1
  56. package/src/presentation/routes/webhooks.test.ts +2 -2
  57. package/src/presentation/routes/webhooks.ts +2 -5
  58. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import type {\n GetIntegrationConnectionByIdFn,\n IntegrationTx,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {\n LINEAR_PROVIDER,\n type LinearWebhookBaseEnvelopeDto,\n type LinearWebhookEnvelopeDto,\n type LinearWebhookEventName,\n linearWebhookEnvelopeSchema,\n} from '@shipfox/api-integration-linear-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {getLinearInstallationByOrganizationId} from '#db/installations.js';\n\nexport interface HandleLinearWebhookParams {\n tx: IntegrationTx;\n deliveryId: string;\n payload: LinearWebhookBaseEnvelopeDto;\n rawPayload: unknown;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport type HandleLinearWebhookOutcome =\n | 'published'\n | 'duplicate'\n | 'unknown-organization'\n | 'revoked-installation'\n | 'missing-connection'\n | 'inactive-connection'\n | 'unsupported-event';\n\nexport async function handleLinearWebhook(\n params: HandleLinearWebhookParams,\n): Promise<{outcome: HandleLinearWebhookOutcome}> {\n const installation = await getLinearInstallationByOrganizationId(params.payload.organizationId, {\n tx: params.tx,\n });\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, organizationId: params.payload.organizationId},\n 'linear webhook: unknown organization, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'unknown-organization'};\n }\n\n if (installation.status !== 'installed') {\n logger().info(\n {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n connectionId: installation.connectionId,\n status: installation.status,\n },\n 'linear webhook: installation is not installed, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'revoked-installation'};\n }\n\n const connection = await params.getIntegrationConnectionById(installation.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n connectionId: installation.connectionId,\n },\n 'linear webhook: installation has no connection, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'missing-connection'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n const logContext = {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n connectionId: connection.id,\n workspaceId: connection.workspaceId,\n lifecycleStatus: connection.lifecycleStatus,\n };\n if (connection.lifecycleStatus === 'error') {\n logger().warn(logContext, 'linear webhook: connection in error state, dropping');\n } else {\n logger().info(logContext, 'linear webhook: connection disabled, dropping');\n }\n await recordDeliveryOnly(params);\n return {outcome: 'inactive-connection'};\n }\n\n const supported = linearWebhookEnvelopeSchema.safeParse(params.payload);\n if (!supported.success) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n type: params.payload.type,\n action: params.payload.action,\n },\n 'linear webhook: unsupported event, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'unsupported-event'};\n }\n\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: LINEAR_PROVIDER,\n source: connection.slug,\n event: linearWebhookEventName(supported.data),\n workspaceId: connection.workspaceId,\n connectionId: connection.id,\n connectionName: connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: params.rawPayload,\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nfunction linearWebhookEventName(payload: LinearWebhookEnvelopeDto): LinearWebhookEventName {\n if (payload.type === 'AgentSessionEvent') return `agentSession.${payload.action}`;\n return `${payload.type}.${payload.action}`;\n}\n\nasync function recordDeliveryOnly(params: {\n tx: IntegrationTx;\n deliveryId: string;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n}): Promise<void> {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: LINEAR_PROVIDER,\n deliveryId: params.deliveryId,\n });\n}\n"],"names":["LINEAR_PROVIDER","linearWebhookEnvelopeSchema","logger","getLinearInstallationByOrganizationId","handleLinearWebhook","params","installation","payload","organizationId","tx","warn","deliveryId","recordDeliveryOnly","outcome","status","info","connectionId","connection","getIntegrationConnectionById","lifecycleStatus","logContext","id","workspaceId","supported","safeParse","success","type","action","result","publishIntegrationEventReceived","event","provider","source","slug","linearWebhookEventName","data","connectionName","displayName","receivedAt","Date","toISOString","rawPayload","published"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n LINEAR_PROVIDER,\n type LinearWebhookBaseEnvelopeDto,\n type LinearWebhookEnvelopeDto,\n type LinearWebhookEventName,\n linearWebhookEnvelopeSchema,\n} from '@shipfox/api-integration-linear-dto';\nimport type {\n GetIntegrationConnectionByIdFn,\n IntegrationTx,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {getLinearInstallationByOrganizationId} from '#db/installations.js';\n\nexport interface HandleLinearWebhookParams {\n tx: IntegrationTx;\n deliveryId: string;\n payload: LinearWebhookBaseEnvelopeDto;\n rawPayload: unknown;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport type HandleLinearWebhookOutcome =\n | 'published'\n | 'duplicate'\n | 'unknown-organization'\n | 'revoked-installation'\n | 'missing-connection'\n | 'inactive-connection'\n | 'unsupported-event';\n\nexport async function handleLinearWebhook(\n params: HandleLinearWebhookParams,\n): Promise<{outcome: HandleLinearWebhookOutcome}> {\n const installation = await getLinearInstallationByOrganizationId(params.payload.organizationId, {\n tx: params.tx,\n });\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, organizationId: params.payload.organizationId},\n 'linear webhook: unknown organization, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'unknown-organization'};\n }\n\n if (installation.status !== 'installed') {\n logger().info(\n {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n connectionId: installation.connectionId,\n status: installation.status,\n },\n 'linear webhook: installation is not installed, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'revoked-installation'};\n }\n\n const connection = await params.getIntegrationConnectionById(installation.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n connectionId: installation.connectionId,\n },\n 'linear webhook: installation has no connection, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'missing-connection'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n const logContext = {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n connectionId: connection.id,\n workspaceId: connection.workspaceId,\n lifecycleStatus: connection.lifecycleStatus,\n };\n if (connection.lifecycleStatus === 'error') {\n logger().warn(logContext, 'linear webhook: connection in error state, dropping');\n } else {\n logger().info(logContext, 'linear webhook: connection disabled, dropping');\n }\n await recordDeliveryOnly(params);\n return {outcome: 'inactive-connection'};\n }\n\n const supported = linearWebhookEnvelopeSchema.safeParse(params.payload);\n if (!supported.success) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n organizationId: params.payload.organizationId,\n type: params.payload.type,\n action: params.payload.action,\n },\n 'linear webhook: unsupported event, dropping',\n );\n await recordDeliveryOnly(params);\n return {outcome: 'unsupported-event'};\n }\n\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: LINEAR_PROVIDER,\n source: connection.slug,\n event: linearWebhookEventName(supported.data),\n workspaceId: connection.workspaceId,\n connectionId: connection.id,\n connectionName: connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: params.rawPayload,\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nfunction linearWebhookEventName(payload: LinearWebhookEnvelopeDto): LinearWebhookEventName {\n if (payload.type === 'AgentSessionEvent') return `agentSession.${payload.action}`;\n return `${payload.type}.${payload.action}`;\n}\n\nasync function recordDeliveryOnly(params: {\n tx: IntegrationTx;\n deliveryId: string;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n}): Promise<void> {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: LINEAR_PROVIDER,\n deliveryId: params.deliveryId,\n });\n}\n"],"names":["LINEAR_PROVIDER","linearWebhookEnvelopeSchema","logger","getLinearInstallationByOrganizationId","handleLinearWebhook","params","installation","payload","organizationId","tx","warn","deliveryId","recordDeliveryOnly","outcome","status","info","connectionId","connection","getIntegrationConnectionById","lifecycleStatus","logContext","id","workspaceId","supported","safeParse","success","type","action","result","publishIntegrationEventReceived","event","provider","source","slug","linearWebhookEventName","data","connectionName","displayName","receivedAt","Date","toISOString","rawPayload","published"],"mappings":"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"}
@@ -1,17 +1,16 @@
1
- import type { IntegrationCapability, IntegrationConnection } from '@shipfox/api-integration-core-dto';
1
+ import type { IntegrationCapability, IntegrationConnection } from '@shipfox/api-integration-spi';
2
2
  export declare function toIntegrationConnectionDto(connection: IntegrationConnection, params: {
3
3
  capabilities: IntegrationCapability[];
4
4
  }): {
5
- created_at: string;
6
- updated_at: string;
7
- external_url?: string;
8
5
  id: string;
9
6
  workspace_id: string;
10
7
  provider: string;
11
8
  external_account_id: string;
12
9
  slug: string;
13
10
  display_name: string;
14
- lifecycle_status: import("@shipfox/api-integration-core-dto").IntegrationConnectionLifecycleStatus;
11
+ lifecycle_status: import("@shipfox/api-integration-spi").IntegrationConnectionLifecycleStatus;
15
12
  capabilities: IntegrationCapability[];
13
+ created_at: string;
14
+ updated_at: string;
16
15
  };
17
16
  //# sourceMappingURL=integrations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAGpG,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE;IAAC,YAAY,EAAE,qBAAqB,EAAE,CAAA;CAAC;;;;;;;;;;;;EAGhD"}
1
+ {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,8BAA8B,CAAC;AAE/F,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE;IAAC,YAAY,EAAE,qBAAqB,EAAE,CAAA;CAAC;;;;;;;;;;;EAGhD"}
@@ -1,6 +1,19 @@
1
- import { toIntegrationConnectionDto as toCoreIntegrationConnectionDto } from '@shipfox/api-integration-core-dto';
2
1
  export function toIntegrationConnectionDto(connection, params) {
3
- return toCoreIntegrationConnectionDto(connection, params);
2
+ return mapIntegrationConnection(connection, params.capabilities);
3
+ }
4
+ function mapIntegrationConnection(connection, capabilities) {
5
+ return {
6
+ id: connection.id,
7
+ workspace_id: connection.workspaceId,
8
+ provider: connection.provider,
9
+ external_account_id: connection.externalAccountId,
10
+ slug: connection.slug,
11
+ display_name: connection.displayName,
12
+ lifecycle_status: connection.lifecycleStatus,
13
+ capabilities,
14
+ created_at: connection.createdAt.toISOString(),
15
+ updated_at: connection.updatedAt.toISOString()
16
+ };
4
17
  }
5
18
 
6
19
  //# sourceMappingURL=integrations.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/dto/integrations.ts"],"sourcesContent":["import type {IntegrationCapability, IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport {toIntegrationConnectionDto as toCoreIntegrationConnectionDto} from '@shipfox/api-integration-core-dto';\n\nexport function toIntegrationConnectionDto(\n connection: IntegrationConnection,\n params: {capabilities: IntegrationCapability[]},\n) {\n return toCoreIntegrationConnectionDto(connection, params);\n}\n"],"names":["toIntegrationConnectionDto","toCoreIntegrationConnectionDto","connection","params"],"mappings":"AACA,SAAQA,8BAA8BC,8BAA8B,QAAO,oCAAoC;AAE/G,OAAO,SAASD,2BACdE,UAAiC,EACjCC,MAA+C;IAE/C,OAAOF,+BAA+BC,YAAYC;AACpD"}
1
+ {"version":3,"sources":["../../../src/presentation/dto/integrations.ts"],"sourcesContent":["import type {IntegrationCapability, IntegrationConnection} from '@shipfox/api-integration-spi';\n\nexport function toIntegrationConnectionDto(\n connection: IntegrationConnection,\n params: {capabilities: IntegrationCapability[]},\n) {\n return mapIntegrationConnection(connection, params.capabilities);\n}\n\nfunction mapIntegrationConnection(\n connection: IntegrationConnection,\n capabilities: IntegrationCapability[],\n) {\n return {\n id: connection.id,\n workspace_id: connection.workspaceId,\n provider: connection.provider,\n external_account_id: connection.externalAccountId,\n slug: connection.slug,\n display_name: connection.displayName,\n lifecycle_status: connection.lifecycleStatus,\n capabilities,\n created_at: connection.createdAt.toISOString(),\n updated_at: connection.updatedAt.toISOString(),\n };\n}\n"],"names":["toIntegrationConnectionDto","connection","params","mapIntegrationConnection","capabilities","id","workspace_id","workspaceId","provider","external_account_id","externalAccountId","slug","display_name","displayName","lifecycle_status","lifecycleStatus","created_at","createdAt","toISOString","updated_at","updatedAt"],"mappings":"AAEA,OAAO,SAASA,2BACdC,UAAiC,EACjCC,MAA+C;IAE/C,OAAOC,yBAAyBF,YAAYC,OAAOE,YAAY;AACjE;AAEA,SAASD,yBACPF,UAAiC,EACjCG,YAAqC;IAErC,OAAO;QACLC,IAAIJ,WAAWI,EAAE;QACjBC,cAAcL,WAAWM,WAAW;QACpCC,UAAUP,WAAWO,QAAQ;QAC7BC,qBAAqBR,WAAWS,iBAAiB;QACjDC,MAAMV,WAAWU,IAAI;QACrBC,cAAcX,WAAWY,WAAW;QACpCC,kBAAkBb,WAAWc,eAAe;QAC5CX;QACAY,YAAYf,WAAWgB,SAAS,CAACC,WAAW;QAC5CC,YAAYlB,WAAWmB,SAAS,CAACF,WAAW;IAC9C;AACF"}
@@ -1,4 +1,4 @@
1
- import type { IntegrationCapability, IntegrationConnection } from '@shipfox/api-integration-core-dto';
1
+ import type { IntegrationCapability, IntegrationConnection } from '@shipfox/api-integration-spi';
2
2
  import type { ConnectLinearInstallationInput } from '#core/install.js';
3
3
  import type { LinearTokenStore } from '#core/tokens.js';
4
4
  export interface CreateE2eLinearConnectionRouteOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"create-connection.d.ts","sourceRoot":"","sources":["../../../src/presentation/e2eRoutes/create-connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAOpG,OAAO,KAAK,EAAC,8BAA8B,EAAC,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAGtD,MAAM,WAAW,qCAAqC;IACpD,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IAClD,2BAA2B,EAAE,CAAC,KAAK,EAAE;QACnC,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC;IAC3D,yBAAyB,EAAE,CACzB,KAAK,EAAE,8BAA8B,KAClC,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,4BAA4B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;CACjD;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,qCAAqC,mDA+C5F"}
1
+ {"version":3,"file":"create-connection.d.ts","sourceRoot":"","sources":["../../../src/presentation/e2eRoutes/create-connection.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,8BAA8B,CAAC;AAG/F,OAAO,KAAK,EAAC,8BAA8B,EAAC,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAGtD,MAAM,WAAW,qCAAqC;IACpD,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IAClD,2BAA2B,EAAE,CAAC,KAAK,EAAE;QACnC,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC;IAC3D,yBAAyB,EAAE,CACzB,KAAK,EAAE,8BAA8B,KAClC,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,4BAA4B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;CACjD;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,qCAAqC,mDA+C5F"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/e2eRoutes/create-connection.ts"],"sourcesContent":["import type {IntegrationCapability, IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport {\n createE2eLinearConnectionBodySchema,\n createE2eLinearConnectionResponseSchema,\n} from '@shipfox/api-integration-linear-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {ConnectLinearInstallationInput} from '#core/install.js';\nimport type {LinearTokenStore} from '#core/tokens.js';\nimport {toIntegrationConnectionDto} from '#presentation/dto/integrations.js';\n\nexport interface CreateE2eLinearConnectionRouteOptions {\n tokenStore: Pick<LinearTokenStore, 'storeTokens'>;\n getExistingLinearConnection: (input: {\n organizationId: string;\n }) => Promise<IntegrationConnection<'linear'> | undefined>;\n connectLinearInstallation: (\n input: ConnectLinearInstallationInput,\n ) => Promise<IntegrationConnection<'linear'>>;\n disconnectLinearInstallation: (input: {connectionId: string}) => Promise<void>;\n connectionCapabilities: IntegrationCapability[];\n}\n\nexport function createE2eLinearConnectionRoute(options: CreateE2eLinearConnectionRouteOptions) {\n return defineRoute({\n method: 'POST',\n path: '/linear-connections',\n description: 'Create a synthetic Linear connection for E2E tests.',\n schema: {\n body: createE2eLinearConnectionBodySchema,\n response: {201: createE2eLinearConnectionResponseSchema},\n },\n handler: async (request, reply) => {\n const body = request.body;\n const existing = await options.getExistingLinearConnection({\n organizationId: body.organization_id,\n });\n if (existing && existing.workspaceId !== body.workspace_id) {\n throw new ClientError(\n 'Linear organization is already connected to another workspace',\n 'linear-connection-workspace-mismatch',\n {status: 409},\n );\n }\n const connection =\n existing ??\n (await options.connectLinearInstallation({\n workspaceId: body.workspace_id,\n organizationId: body.organization_id,\n organizationUrlKey: body.organization_url_key,\n appUserId: body.app_user_id,\n scopes: body.scopes,\n tokenExpiresAt: null,\n displayName: body.display_name,\n }));\n\n try {\n await options.tokenStore.storeTokens({\n connectionId: connection.id,\n accessToken: body.access_token,\n });\n } catch (error) {\n if (!existing) await bestEffortDisconnectLinearInstallation(options, connection.id);\n throw error;\n }\n\n reply.code(201);\n return toIntegrationConnectionDto(connection, {capabilities: options.connectionCapabilities});\n },\n });\n}\n\nasync function bestEffortDisconnectLinearInstallation(\n options: CreateE2eLinearConnectionRouteOptions,\n connectionId: string,\n): Promise<void> {\n try {\n await options.disconnectLinearInstallation({connectionId});\n } catch (error) {\n logger().warn(\n {err: error, connectionId},\n 'Linear E2E connection compensation failed after token storage rejection',\n );\n }\n}\n"],"names":["createE2eLinearConnectionBodySchema","createE2eLinearConnectionResponseSchema","ClientError","defineRoute","logger","toIntegrationConnectionDto","createE2eLinearConnectionRoute","options","method","path","description","schema","body","response","handler","request","reply","existing","getExistingLinearConnection","organizationId","organization_id","workspaceId","workspace_id","status","connection","connectLinearInstallation","organizationUrlKey","organization_url_key","appUserId","app_user_id","scopes","tokenExpiresAt","displayName","display_name","tokenStore","storeTokens","connectionId","id","accessToken","access_token","error","bestEffortDisconnectLinearInstallation","code","capabilities","connectionCapabilities","disconnectLinearInstallation","warn","err"],"mappings":"AACA,SACEA,mCAAmC,EACnCC,uCAAuC,QAClC,sCAAsC;AAC7C,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,MAAM,QAAO,8BAA8B;AAGnD,SAAQC,0BAA0B,QAAO,oCAAoC;AAc7E,OAAO,SAASC,+BAA+BC,OAA8C;IAC3F,OAAOJ,YAAY;QACjBK,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,MAAMZ;YACNa,UAAU;gBAAC,KAAKZ;YAAuC;QACzD;QACAa,SAAS,OAAOC,SAASC;YACvB,MAAMJ,OAAOG,QAAQH,IAAI;YACzB,MAAMK,WAAW,MAAMV,QAAQW,2BAA2B,CAAC;gBACzDC,gBAAgBP,KAAKQ,eAAe;YACtC;YACA,IAAIH,YAAYA,SAASI,WAAW,KAAKT,KAAKU,YAAY,EAAE;gBAC1D,MAAM,IAAIpB,YACR,iEACA,wCACA;oBAACqB,QAAQ;gBAAG;YAEhB;YACA,MAAMC,aACJP,YACC,MAAMV,QAAQkB,yBAAyB,CAAC;gBACvCJ,aAAaT,KAAKU,YAAY;gBAC9BH,gBAAgBP,KAAKQ,eAAe;gBACpCM,oBAAoBd,KAAKe,oBAAoB;gBAC7CC,WAAWhB,KAAKiB,WAAW;gBAC3BC,QAAQlB,KAAKkB,MAAM;gBACnBC,gBAAgB;gBAChBC,aAAapB,KAAKqB,YAAY;YAChC;YAEF,IAAI;gBACF,MAAM1B,QAAQ2B,UAAU,CAACC,WAAW,CAAC;oBACnCC,cAAcZ,WAAWa,EAAE;oBAC3BC,aAAa1B,KAAK2B,YAAY;gBAChC;YACF,EAAE,OAAOC,OAAO;gBACd,IAAI,CAACvB,UAAU,MAAMwB,uCAAuClC,SAASiB,WAAWa,EAAE;gBAClF,MAAMG;YACR;YAEAxB,MAAM0B,IAAI,CAAC;YACX,OAAOrC,2BAA2BmB,YAAY;gBAACmB,cAAcpC,QAAQqC,sBAAsB;YAAA;QAC7F;IACF;AACF;AAEA,eAAeH,uCACblC,OAA8C,EAC9C6B,YAAoB;IAEpB,IAAI;QACF,MAAM7B,QAAQsC,4BAA4B,CAAC;YAACT;QAAY;IAC1D,EAAE,OAAOI,OAAO;QACdpC,SAAS0C,IAAI,CACX;YAACC,KAAKP;YAAOJ;QAAY,GACzB;IAEJ;AACF"}
1
+ {"version":3,"sources":["../../../src/presentation/e2eRoutes/create-connection.ts"],"sourcesContent":["import {\n createE2eLinearConnectionBodySchema,\n createE2eLinearConnectionResponseSchema,\n} from '@shipfox/api-integration-linear-dto';\nimport type {IntegrationCapability, IntegrationConnection} from '@shipfox/api-integration-spi';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {ConnectLinearInstallationInput} from '#core/install.js';\nimport type {LinearTokenStore} from '#core/tokens.js';\nimport {toIntegrationConnectionDto} from '#presentation/dto/integrations.js';\n\nexport interface CreateE2eLinearConnectionRouteOptions {\n tokenStore: Pick<LinearTokenStore, 'storeTokens'>;\n getExistingLinearConnection: (input: {\n organizationId: string;\n }) => Promise<IntegrationConnection<'linear'> | undefined>;\n connectLinearInstallation: (\n input: ConnectLinearInstallationInput,\n ) => Promise<IntegrationConnection<'linear'>>;\n disconnectLinearInstallation: (input: {connectionId: string}) => Promise<void>;\n connectionCapabilities: IntegrationCapability[];\n}\n\nexport function createE2eLinearConnectionRoute(options: CreateE2eLinearConnectionRouteOptions) {\n return defineRoute({\n method: 'POST',\n path: '/linear-connections',\n description: 'Create a synthetic Linear connection for E2E tests.',\n schema: {\n body: createE2eLinearConnectionBodySchema,\n response: {201: createE2eLinearConnectionResponseSchema},\n },\n handler: async (request, reply) => {\n const body = request.body;\n const existing = await options.getExistingLinearConnection({\n organizationId: body.organization_id,\n });\n if (existing && existing.workspaceId !== body.workspace_id) {\n throw new ClientError(\n 'Linear organization is already connected to another workspace',\n 'linear-connection-workspace-mismatch',\n {status: 409},\n );\n }\n const connection =\n existing ??\n (await options.connectLinearInstallation({\n workspaceId: body.workspace_id,\n organizationId: body.organization_id,\n organizationUrlKey: body.organization_url_key,\n appUserId: body.app_user_id,\n scopes: body.scopes,\n tokenExpiresAt: null,\n displayName: body.display_name,\n }));\n\n try {\n await options.tokenStore.storeTokens({\n connectionId: connection.id,\n accessToken: body.access_token,\n });\n } catch (error) {\n if (!existing) await bestEffortDisconnectLinearInstallation(options, connection.id);\n throw error;\n }\n\n reply.code(201);\n return toIntegrationConnectionDto(connection, {capabilities: options.connectionCapabilities});\n },\n });\n}\n\nasync function bestEffortDisconnectLinearInstallation(\n options: CreateE2eLinearConnectionRouteOptions,\n connectionId: string,\n): Promise<void> {\n try {\n await options.disconnectLinearInstallation({connectionId});\n } catch (error) {\n logger().warn(\n {err: error, connectionId},\n 'Linear E2E connection compensation failed after token storage rejection',\n );\n }\n}\n"],"names":["createE2eLinearConnectionBodySchema","createE2eLinearConnectionResponseSchema","ClientError","defineRoute","logger","toIntegrationConnectionDto","createE2eLinearConnectionRoute","options","method","path","description","schema","body","response","handler","request","reply","existing","getExistingLinearConnection","organizationId","organization_id","workspaceId","workspace_id","status","connection","connectLinearInstallation","organizationUrlKey","organization_url_key","appUserId","app_user_id","scopes","tokenExpiresAt","displayName","display_name","tokenStore","storeTokens","connectionId","id","accessToken","access_token","error","bestEffortDisconnectLinearInstallation","code","capabilities","connectionCapabilities","disconnectLinearInstallation","warn","err"],"mappings":"AAAA,SACEA,mCAAmC,EACnCC,uCAAuC,QAClC,sCAAsC;AAE7C,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,MAAM,QAAO,8BAA8B;AAGnD,SAAQC,0BAA0B,QAAO,oCAAoC;AAc7E,OAAO,SAASC,+BAA+BC,OAA8C;IAC3F,OAAOJ,YAAY;QACjBK,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,MAAMZ;YACNa,UAAU;gBAAC,KAAKZ;YAAuC;QACzD;QACAa,SAAS,OAAOC,SAASC;YACvB,MAAMJ,OAAOG,QAAQH,IAAI;YACzB,MAAMK,WAAW,MAAMV,QAAQW,2BAA2B,CAAC;gBACzDC,gBAAgBP,KAAKQ,eAAe;YACtC;YACA,IAAIH,YAAYA,SAASI,WAAW,KAAKT,KAAKU,YAAY,EAAE;gBAC1D,MAAM,IAAIpB,YACR,iEACA,wCACA;oBAACqB,QAAQ;gBAAG;YAEhB;YACA,MAAMC,aACJP,YACC,MAAMV,QAAQkB,yBAAyB,CAAC;gBACvCJ,aAAaT,KAAKU,YAAY;gBAC9BH,gBAAgBP,KAAKQ,eAAe;gBACpCM,oBAAoBd,KAAKe,oBAAoB;gBAC7CC,WAAWhB,KAAKiB,WAAW;gBAC3BC,QAAQlB,KAAKkB,MAAM;gBACnBC,gBAAgB;gBAChBC,aAAapB,KAAKqB,YAAY;YAChC;YAEF,IAAI;gBACF,MAAM1B,QAAQ2B,UAAU,CAACC,WAAW,CAAC;oBACnCC,cAAcZ,WAAWa,EAAE;oBAC3BC,aAAa1B,KAAK2B,YAAY;gBAChC;YACF,EAAE,OAAOC,OAAO;gBACd,IAAI,CAACvB,UAAU,MAAMwB,uCAAuClC,SAASiB,WAAWa,EAAE;gBAClF,MAAMG;YACR;YAEAxB,MAAM0B,IAAI,CAAC;YACX,OAAOrC,2BAA2BmB,YAAY;gBAACmB,cAAcpC,QAAQqC,sBAAsB;YAAA;QAC7F;IACF;AACF;AAEA,eAAeH,uCACblC,OAA8C,EAC9C6B,YAAoB;IAEpB,IAAI;QACF,MAAM7B,QAAQsC,4BAA4B,CAAC;YAACT;QAAY;IAC1D,EAAE,OAAOI,OAAO;QACdpC,SAAS0C,IAAI,CACX;YAACC,KAAKP;YAAOJ;QAAY,GACzB;IAEJ;AACF"}
@@ -1,4 +1,4 @@
1
- import { ConnectionSlugConflictError } from '@shipfox/api-integration-core-dto';
1
+ import { ConnectionSlugConflictError } from '@shipfox/api-integration-spi';
2
2
  import { ClientError } from '@shipfox/node-fastify';
3
3
  import { LinearAuthorizationScopeMismatchError, LinearConnectionAlreadyLinkedError, LinearInstallationAlreadyLinkedError, LinearInstallStateActorMismatchError, LinearInstallStateError, LinearIntegrationProviderError, LinearOAuthCallbackError } from '#core/errors.js';
4
4
  function providerStatus(reason) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/errors.ts"],"sourcesContent":["import {\n ConnectionSlugConflictError,\n type IntegrationProviderErrorReason,\n} from '@shipfox/api-integration-core-dto';\nimport {ClientError} from '@shipfox/node-fastify';\nimport {\n LinearAuthorizationScopeMismatchError,\n LinearConnectionAlreadyLinkedError,\n LinearInstallationAlreadyLinkedError,\n LinearInstallStateActorMismatchError,\n LinearInstallStateError,\n LinearIntegrationProviderError,\n LinearOAuthCallbackError,\n} from '#core/errors.js';\n\nfunction providerStatus(reason: IntegrationProviderErrorReason): number {\n if (reason === 'rate-limited') return 429;\n if (reason === 'timeout' || reason === 'provider-unavailable') return 503;\n return 422;\n}\n\nexport function linearRouteErrorHandler(error: unknown): never {\n if (error instanceof LinearInstallStateError) {\n throw new ClientError(error.message, 'invalid-linear-install-state', {status: 400});\n }\n if (error instanceof LinearInstallStateActorMismatchError) {\n throw new ClientError(error.message, 'linear-install-state-actor-mismatch', {status: 403});\n }\n if (error instanceof LinearInstallationAlreadyLinkedError) {\n throw new ClientError(error.message, 'linear-installation-already-linked', {status: 409});\n }\n if (error instanceof LinearConnectionAlreadyLinkedError) {\n throw new ClientError(error.message, 'linear-connection-already-linked', {status: 409});\n }\n if (error instanceof LinearAuthorizationScopeMismatchError) {\n throw new ClientError(error.message, 'linear-authorization-scope-mismatch', {\n status: 422,\n details: {missing_scopes: error.missingScopes},\n });\n }\n if (error instanceof LinearOAuthCallbackError) {\n throw new ClientError(error.message, 'linear-oauth-callback-error', {\n status: 422,\n details: {\n error: error.providerError,\n ...(error.providerDescription ? {error_description: error.providerDescription} : {}),\n },\n });\n }\n if (error instanceof ConnectionSlugConflictError) {\n throw new ClientError(error.message, 'slug-conflict', {status: 409});\n }\n if (error instanceof LinearIntegrationProviderError) {\n throw new ClientError(error.message, error.reason, {\n details: {retry_after_seconds: error.retryAfterSeconds},\n status: providerStatus(error.reason),\n });\n }\n throw error;\n}\n"],"names":["ConnectionSlugConflictError","ClientError","LinearAuthorizationScopeMismatchError","LinearConnectionAlreadyLinkedError","LinearInstallationAlreadyLinkedError","LinearInstallStateActorMismatchError","LinearInstallStateError","LinearIntegrationProviderError","LinearOAuthCallbackError","providerStatus","reason","linearRouteErrorHandler","error","message","status","details","missing_scopes","missingScopes","providerError","providerDescription","error_description","retry_after_seconds","retryAfterSeconds"],"mappings":"AAAA,SACEA,2BAA2B,QAEtB,oCAAoC;AAC3C,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SACEC,qCAAqC,EACrCC,kCAAkC,EAClCC,oCAAoC,EACpCC,oCAAoC,EACpCC,uBAAuB,EACvBC,8BAA8B,EAC9BC,wBAAwB,QACnB,kBAAkB;AAEzB,SAASC,eAAeC,MAAsC;IAC5D,IAAIA,WAAW,gBAAgB,OAAO;IACtC,IAAIA,WAAW,aAAaA,WAAW,wBAAwB,OAAO;IACtE,OAAO;AACT;AAEA,OAAO,SAASC,wBAAwBC,KAAc;IACpD,IAAIA,iBAAiBN,yBAAyB;QAC5C,MAAM,IAAIL,YAAYW,MAAMC,OAAO,EAAE,gCAAgC;YAACC,QAAQ;QAAG;IACnF;IACA,IAAIF,iBAAiBP,sCAAsC;QACzD,MAAM,IAAIJ,YAAYW,MAAMC,OAAO,EAAE,uCAAuC;YAACC,QAAQ;QAAG;IAC1F;IACA,IAAIF,iBAAiBR,sCAAsC;QACzD,MAAM,IAAIH,YAAYW,MAAMC,OAAO,EAAE,sCAAsC;YAACC,QAAQ;QAAG;IACzF;IACA,IAAIF,iBAAiBT,oCAAoC;QACvD,MAAM,IAAIF,YAAYW,MAAMC,OAAO,EAAE,oCAAoC;YAACC,QAAQ;QAAG;IACvF;IACA,IAAIF,iBAAiBV,uCAAuC;QAC1D,MAAM,IAAID,YAAYW,MAAMC,OAAO,EAAE,uCAAuC;YAC1EC,QAAQ;YACRC,SAAS;gBAACC,gBAAgBJ,MAAMK,aAAa;YAAA;QAC/C;IACF;IACA,IAAIL,iBAAiBJ,0BAA0B;QAC7C,MAAM,IAAIP,YAAYW,MAAMC,OAAO,EAAE,+BAA+B;YAClEC,QAAQ;YACRC,SAAS;gBACPH,OAAOA,MAAMM,aAAa;gBAC1B,GAAIN,MAAMO,mBAAmB,GAAG;oBAACC,mBAAmBR,MAAMO,mBAAmB;gBAAA,IAAI,CAAC,CAAC;YACrF;QACF;IACF;IACA,IAAIP,iBAAiBZ,6BAA6B;QAChD,MAAM,IAAIC,YAAYW,MAAMC,OAAO,EAAE,iBAAiB;YAACC,QAAQ;QAAG;IACpE;IACA,IAAIF,iBAAiBL,gCAAgC;QACnD,MAAM,IAAIN,YAAYW,MAAMC,OAAO,EAAED,MAAMF,MAAM,EAAE;YACjDK,SAAS;gBAACM,qBAAqBT,MAAMU,iBAAiB;YAAA;YACtDR,QAAQL,eAAeG,MAAMF,MAAM;QACrC;IACF;IACA,MAAME;AACR"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/errors.ts"],"sourcesContent":["import {\n ConnectionSlugConflictError,\n type IntegrationProviderErrorReason,\n} from '@shipfox/api-integration-spi';\nimport {ClientError} from '@shipfox/node-fastify';\nimport {\n LinearAuthorizationScopeMismatchError,\n LinearConnectionAlreadyLinkedError,\n LinearInstallationAlreadyLinkedError,\n LinearInstallStateActorMismatchError,\n LinearInstallStateError,\n LinearIntegrationProviderError,\n LinearOAuthCallbackError,\n} from '#core/errors.js';\n\nfunction providerStatus(reason: IntegrationProviderErrorReason): number {\n if (reason === 'rate-limited') return 429;\n if (reason === 'timeout' || reason === 'provider-unavailable') return 503;\n return 422;\n}\n\nexport function linearRouteErrorHandler(error: unknown): never {\n if (error instanceof LinearInstallStateError) {\n throw new ClientError(error.message, 'invalid-linear-install-state', {status: 400});\n }\n if (error instanceof LinearInstallStateActorMismatchError) {\n throw new ClientError(error.message, 'linear-install-state-actor-mismatch', {status: 403});\n }\n if (error instanceof LinearInstallationAlreadyLinkedError) {\n throw new ClientError(error.message, 'linear-installation-already-linked', {status: 409});\n }\n if (error instanceof LinearConnectionAlreadyLinkedError) {\n throw new ClientError(error.message, 'linear-connection-already-linked', {status: 409});\n }\n if (error instanceof LinearAuthorizationScopeMismatchError) {\n throw new ClientError(error.message, 'linear-authorization-scope-mismatch', {\n status: 422,\n details: {missing_scopes: error.missingScopes},\n });\n }\n if (error instanceof LinearOAuthCallbackError) {\n throw new ClientError(error.message, 'linear-oauth-callback-error', {\n status: 422,\n details: {\n error: error.providerError,\n ...(error.providerDescription ? {error_description: error.providerDescription} : {}),\n },\n });\n }\n if (error instanceof ConnectionSlugConflictError) {\n throw new ClientError(error.message, 'slug-conflict', {status: 409});\n }\n if (error instanceof LinearIntegrationProviderError) {\n throw new ClientError(error.message, error.reason, {\n details: {retry_after_seconds: error.retryAfterSeconds},\n status: providerStatus(error.reason),\n });\n }\n throw error;\n}\n"],"names":["ConnectionSlugConflictError","ClientError","LinearAuthorizationScopeMismatchError","LinearConnectionAlreadyLinkedError","LinearInstallationAlreadyLinkedError","LinearInstallStateActorMismatchError","LinearInstallStateError","LinearIntegrationProviderError","LinearOAuthCallbackError","providerStatus","reason","linearRouteErrorHandler","error","message","status","details","missing_scopes","missingScopes","providerError","providerDescription","error_description","retry_after_seconds","retryAfterSeconds"],"mappings":"AAAA,SACEA,2BAA2B,QAEtB,+BAA+B;AACtC,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SACEC,qCAAqC,EACrCC,kCAAkC,EAClCC,oCAAoC,EACpCC,oCAAoC,EACpCC,uBAAuB,EACvBC,8BAA8B,EAC9BC,wBAAwB,QACnB,kBAAkB;AAEzB,SAASC,eAAeC,MAAsC;IAC5D,IAAIA,WAAW,gBAAgB,OAAO;IACtC,IAAIA,WAAW,aAAaA,WAAW,wBAAwB,OAAO;IACtE,OAAO;AACT;AAEA,OAAO,SAASC,wBAAwBC,KAAc;IACpD,IAAIA,iBAAiBN,yBAAyB;QAC5C,MAAM,IAAIL,YAAYW,MAAMC,OAAO,EAAE,gCAAgC;YAACC,QAAQ;QAAG;IACnF;IACA,IAAIF,iBAAiBP,sCAAsC;QACzD,MAAM,IAAIJ,YAAYW,MAAMC,OAAO,EAAE,uCAAuC;YAACC,QAAQ;QAAG;IAC1F;IACA,IAAIF,iBAAiBR,sCAAsC;QACzD,MAAM,IAAIH,YAAYW,MAAMC,OAAO,EAAE,sCAAsC;YAACC,QAAQ;QAAG;IACzF;IACA,IAAIF,iBAAiBT,oCAAoC;QACvD,MAAM,IAAIF,YAAYW,MAAMC,OAAO,EAAE,oCAAoC;YAACC,QAAQ;QAAG;IACvF;IACA,IAAIF,iBAAiBV,uCAAuC;QAC1D,MAAM,IAAID,YAAYW,MAAMC,OAAO,EAAE,uCAAuC;YAC1EC,QAAQ;YACRC,SAAS;gBAACC,gBAAgBJ,MAAMK,aAAa;YAAA;QAC/C;IACF;IACA,IAAIL,iBAAiBJ,0BAA0B;QAC7C,MAAM,IAAIP,YAAYW,MAAMC,OAAO,EAAE,+BAA+B;YAClEC,QAAQ;YACRC,SAAS;gBACPH,OAAOA,MAAMM,aAAa;gBAC1B,GAAIN,MAAMO,mBAAmB,GAAG;oBAACC,mBAAmBR,MAAMO,mBAAmB;gBAAA,IAAI,CAAC,CAAC;YACrF;QACF;IACF;IACA,IAAIP,iBAAiBZ,6BAA6B;QAChD,MAAM,IAAIC,YAAYW,MAAMC,OAAO,EAAE,iBAAiB;YAACC,QAAQ;QAAG;IACpE;IACA,IAAIF,iBAAiBL,gCAAgC;QACnD,MAAM,IAAIN,YAAYW,MAAMC,OAAO,EAAED,MAAMF,MAAM,EAAE;YACjDK,SAAS;gBAACM,qBAAqBT,MAAMU,iBAAiB;YAAA;YACtDR,QAAQL,eAAeG,MAAMF,MAAM;QACrC;IACF;IACA,MAAME;AACR"}
@@ -1,4 +1,4 @@
1
- import type { IntegrationCapability, IntegrationConnection } from '@shipfox/api-integration-core-dto';
1
+ import type { IntegrationCapability, IntegrationConnection } from '@shipfox/api-integration-spi';
2
2
  import { type RouteGroup } from '@shipfox/node-fastify';
3
3
  import type { LinearApiClient } from '#api/client.js';
4
4
  import { type ConnectLinearInstallationInput } from '#core/install.js';
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/install.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAQpG,OAAO,EAAc,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EACL,KAAK,8BAA8B,EAGpC,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAItD,MAAM,WAAW,oCAAoC;IACnD,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IAClD,2BAA2B,EAAE,CAAC,KAAK,EAAE;QACnC,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC;IAC3D,yBAAyB,EAAE,CACzB,KAAK,EAAE,8BAA8B,KAClC,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,4BAA4B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;IAChD,gCAAgC,CAAC,EAAE,CAAC,KAAK,EAAE;QACzC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,OAAO,2BAA2B,EAAE,qBAAqB,CAAC,CAAC;KACvF,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,MAAM,EACN,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,4BAA4B,EAC5B,sBAAsB,EACtB,gCAAgC,GACjC,EAAE,oCAAoC,GAAG,UAAU,CA+EnD"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/install.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAc,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EACL,KAAK,8BAA8B,EAGpC,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAItD,MAAM,WAAW,oCAAoC;IACnD,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IAClD,2BAA2B,EAAE,CAAC,KAAK,EAAE;QACnC,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC;IAC3D,yBAAyB,EAAE,CACzB,KAAK,EAAE,8BAA8B,KAClC,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,4BAA4B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;IAChD,gCAAgC,CAAC,EAAE,CAAC,KAAK,EAAE;QACzC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,OAAO,2BAA2B,EAAE,qBAAqB,CAAC,CAAC;KACvF,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,MAAM,EACN,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,4BAA4B,EAC5B,sBAAsB,EACtB,gCAAgC,GACjC,EAAE,oCAAoC,GAAG,UAAU,CA+EnD"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/install.ts"],"sourcesContent":["import {AUTH_USER, requireUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport type {IntegrationCapability, IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport {\n createLinearInstallBodySchema,\n createLinearInstallResponseSchema,\n type LinearCallbackQueryDto,\n linearCallbackQuerySchema,\n linearCallbackResponseSchema,\n} from '@shipfox/api-integration-linear-dto';\nimport {defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport type {LinearApiClient} from '#api/client.js';\nimport {config} from '#config.js';\nimport {\n type ConnectLinearInstallationInput,\n handleLinearCallback,\n handleLinearOAuthCallbackError,\n} from '#core/install.js';\nimport {formatLinearOAuthScopes} from '#core/scopes.js';\nimport {signLinearInstallState} from '#core/state.js';\nimport type {LinearTokenStore} from '#core/tokens.js';\nimport {toIntegrationConnectionDto} from '#presentation/dto/integrations.js';\nimport {linearRouteErrorHandler} from './errors.js';\n\nexport interface CreateLinearIntegrationRoutesOptions {\n linear: LinearApiClient;\n tokenStore: Pick<LinearTokenStore, 'storeTokens'>;\n getExistingLinearConnection: (input: {\n organizationId: string;\n }) => Promise<IntegrationConnection<'linear'> | undefined>;\n connectLinearInstallation: (\n input: ConnectLinearInstallationInput,\n ) => Promise<IntegrationConnection<'linear'>>;\n disconnectLinearInstallation: (input: {connectionId: string}) => Promise<void>;\n connectionCapabilities: IntegrationCapability[];\n requireActiveWorkspaceMembership?: (input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<import('@shipfox/api-auth-context').UserContextMembership>;\n }) => Promise<unknown>;\n}\n\nexport function createLinearIntegrationRoutes({\n linear,\n tokenStore,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n connectionCapabilities,\n requireActiveWorkspaceMembership,\n}: CreateLinearIntegrationRoutesOptions): RouteGroup {\n const createInstallRoute = defineRoute({\n method: 'POST',\n path: '/install',\n auth: AUTH_USER,\n description: 'Create a Linear OAuth authorization URL for a workspace.',\n schema: {\n body: createLinearInstallBodySchema,\n response: {\n 200: createLinearInstallResponseSchema,\n },\n },\n handler: (request) => {\n const {workspace_id: workspaceId} = request.body;\n const actor = requireUserContext(request);\n\n requireWorkspaceAccess({request, workspaceId});\n\n const state = signLinearInstallState({workspaceId, userId: actor.userId});\n const installUrl = new URL('https://linear.app/oauth/authorize');\n installUrl.searchParams.set('client_id', config.LINEAR_OAUTH_CLIENT_ID);\n installUrl.searchParams.set('redirect_uri', config.LINEAR_OAUTH_REDIRECT_URL);\n installUrl.searchParams.set('response_type', 'code');\n installUrl.searchParams.set('state', state);\n installUrl.searchParams.set('actor', 'app');\n installUrl.searchParams.set('scope', formatLinearOAuthScopes());\n\n return {install_url: installUrl.toString()};\n },\n });\n\n const callbackApiRoute = defineRoute({\n method: 'GET',\n path: '/callback/api',\n auth: AUTH_USER,\n description: 'Handle the Linear OAuth callback.',\n schema: {\n querystring: linearCallbackQuerySchema,\n response: {\n 200: linearCallbackResponseSchema,\n },\n },\n errorHandler: linearRouteErrorHandler,\n handler: async (request) => {\n const actor = requireUserContext(request);\n const query = request.query;\n if (isLinearOAuthErrorCallback(query)) {\n return await handleLinearOAuthCallbackError({\n state: query.state,\n error: query.error,\n errorDescription: query.error_description,\n sessionUserId: actor.userId,\n sessionMemberships: actor.memberships,\n requireWorkspaceMembership:\n requireActiveWorkspaceMembership ?? unavailableWorkspaceMembershipCheck,\n });\n }\n const connection = await handleLinearCallback({\n linear,\n tokenStore,\n code: query.code,\n state: query.state,\n sessionUserId: actor.userId,\n sessionMemberships: actor.memberships,\n requireWorkspaceMembership:\n requireActiveWorkspaceMembership ?? unavailableWorkspaceMembershipCheck,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n });\n\n return toIntegrationConnectionDto(connection, {capabilities: connectionCapabilities});\n },\n });\n\n return {\n prefix: '/integrations/linear',\n routes: [createInstallRoute, callbackApiRoute],\n };\n}\n\nfunction unavailableWorkspaceMembershipCheck(_input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<import('@shipfox/api-auth-context').UserContextMembership>;\n}): Promise<never> {\n return Promise.reject(new Error('Workspaces inter-module client is not configured'));\n}\n\nfunction isLinearOAuthErrorCallback(\n query: LinearCallbackQueryDto,\n): query is LinearCallbackQueryDto & {\n error: string;\n error_description?: string | undefined;\n state: string;\n} {\n return 'error' in query && typeof query.error === 'string';\n}\n"],"names":["AUTH_USER","requireUserContext","requireWorkspaceAccess","createLinearInstallBodySchema","createLinearInstallResponseSchema","linearCallbackQuerySchema","linearCallbackResponseSchema","defineRoute","config","handleLinearCallback","handleLinearOAuthCallbackError","formatLinearOAuthScopes","signLinearInstallState","toIntegrationConnectionDto","linearRouteErrorHandler","createLinearIntegrationRoutes","linear","tokenStore","getExistingLinearConnection","connectLinearInstallation","disconnectLinearInstallation","connectionCapabilities","requireActiveWorkspaceMembership","createInstallRoute","method","path","auth","description","schema","body","response","handler","request","workspace_id","workspaceId","actor","state","userId","installUrl","URL","searchParams","set","LINEAR_OAUTH_CLIENT_ID","LINEAR_OAUTH_REDIRECT_URL","install_url","toString","callbackApiRoute","querystring","errorHandler","query","isLinearOAuthErrorCallback","error","errorDescription","error_description","sessionUserId","sessionMemberships","memberships","requireWorkspaceMembership","unavailableWorkspaceMembershipCheck","connection","code","capabilities","prefix","routes","_input","Promise","reject","Error"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/install.ts"],"sourcesContent":["import {AUTH_USER, requireUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {\n createLinearInstallBodySchema,\n createLinearInstallResponseSchema,\n type LinearCallbackQueryDto,\n linearCallbackQuerySchema,\n linearCallbackResponseSchema,\n} from '@shipfox/api-integration-linear-dto';\nimport type {IntegrationCapability, IntegrationConnection} from '@shipfox/api-integration-spi';\nimport {defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport type {LinearApiClient} from '#api/client.js';\nimport {config} from '#config.js';\nimport {\n type ConnectLinearInstallationInput,\n handleLinearCallback,\n handleLinearOAuthCallbackError,\n} from '#core/install.js';\nimport {formatLinearOAuthScopes} from '#core/scopes.js';\nimport {signLinearInstallState} from '#core/state.js';\nimport type {LinearTokenStore} from '#core/tokens.js';\nimport {toIntegrationConnectionDto} from '#presentation/dto/integrations.js';\nimport {linearRouteErrorHandler} from './errors.js';\n\nexport interface CreateLinearIntegrationRoutesOptions {\n linear: LinearApiClient;\n tokenStore: Pick<LinearTokenStore, 'storeTokens'>;\n getExistingLinearConnection: (input: {\n organizationId: string;\n }) => Promise<IntegrationConnection<'linear'> | undefined>;\n connectLinearInstallation: (\n input: ConnectLinearInstallationInput,\n ) => Promise<IntegrationConnection<'linear'>>;\n disconnectLinearInstallation: (input: {connectionId: string}) => Promise<void>;\n connectionCapabilities: IntegrationCapability[];\n requireActiveWorkspaceMembership?: (input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<import('@shipfox/api-auth-context').UserContextMembership>;\n }) => Promise<unknown>;\n}\n\nexport function createLinearIntegrationRoutes({\n linear,\n tokenStore,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n connectionCapabilities,\n requireActiveWorkspaceMembership,\n}: CreateLinearIntegrationRoutesOptions): RouteGroup {\n const createInstallRoute = defineRoute({\n method: 'POST',\n path: '/install',\n auth: AUTH_USER,\n description: 'Create a Linear OAuth authorization URL for a workspace.',\n schema: {\n body: createLinearInstallBodySchema,\n response: {\n 200: createLinearInstallResponseSchema,\n },\n },\n handler: (request) => {\n const {workspace_id: workspaceId} = request.body;\n const actor = requireUserContext(request);\n\n requireWorkspaceAccess({request, workspaceId});\n\n const state = signLinearInstallState({workspaceId, userId: actor.userId});\n const installUrl = new URL('https://linear.app/oauth/authorize');\n installUrl.searchParams.set('client_id', config.LINEAR_OAUTH_CLIENT_ID);\n installUrl.searchParams.set('redirect_uri', config.LINEAR_OAUTH_REDIRECT_URL);\n installUrl.searchParams.set('response_type', 'code');\n installUrl.searchParams.set('state', state);\n installUrl.searchParams.set('actor', 'app');\n installUrl.searchParams.set('scope', formatLinearOAuthScopes());\n\n return {install_url: installUrl.toString()};\n },\n });\n\n const callbackApiRoute = defineRoute({\n method: 'GET',\n path: '/callback/api',\n auth: AUTH_USER,\n description: 'Handle the Linear OAuth callback.',\n schema: {\n querystring: linearCallbackQuerySchema,\n response: {\n 200: linearCallbackResponseSchema,\n },\n },\n errorHandler: linearRouteErrorHandler,\n handler: async (request) => {\n const actor = requireUserContext(request);\n const query = request.query;\n if (isLinearOAuthErrorCallback(query)) {\n return await handleLinearOAuthCallbackError({\n state: query.state,\n error: query.error,\n errorDescription: query.error_description,\n sessionUserId: actor.userId,\n sessionMemberships: actor.memberships,\n requireWorkspaceMembership:\n requireActiveWorkspaceMembership ?? unavailableWorkspaceMembershipCheck,\n });\n }\n const connection = await handleLinearCallback({\n linear,\n tokenStore,\n code: query.code,\n state: query.state,\n sessionUserId: actor.userId,\n sessionMemberships: actor.memberships,\n requireWorkspaceMembership:\n requireActiveWorkspaceMembership ?? unavailableWorkspaceMembershipCheck,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n });\n\n return toIntegrationConnectionDto(connection, {capabilities: connectionCapabilities});\n },\n });\n\n return {\n prefix: '/integrations/linear',\n routes: [createInstallRoute, callbackApiRoute],\n };\n}\n\nfunction unavailableWorkspaceMembershipCheck(_input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<import('@shipfox/api-auth-context').UserContextMembership>;\n}): Promise<never> {\n return Promise.reject(new Error('Workspaces inter-module client is not configured'));\n}\n\nfunction isLinearOAuthErrorCallback(\n query: LinearCallbackQueryDto,\n): query is LinearCallbackQueryDto & {\n error: string;\n error_description?: string | undefined;\n state: string;\n} {\n return 'error' in query && typeof query.error === 'string';\n}\n"],"names":["AUTH_USER","requireUserContext","requireWorkspaceAccess","createLinearInstallBodySchema","createLinearInstallResponseSchema","linearCallbackQuerySchema","linearCallbackResponseSchema","defineRoute","config","handleLinearCallback","handleLinearOAuthCallbackError","formatLinearOAuthScopes","signLinearInstallState","toIntegrationConnectionDto","linearRouteErrorHandler","createLinearIntegrationRoutes","linear","tokenStore","getExistingLinearConnection","connectLinearInstallation","disconnectLinearInstallation","connectionCapabilities","requireActiveWorkspaceMembership","createInstallRoute","method","path","auth","description","schema","body","response","handler","request","workspace_id","workspaceId","actor","state","userId","installUrl","URL","searchParams","set","LINEAR_OAUTH_CLIENT_ID","LINEAR_OAUTH_REDIRECT_URL","install_url","toString","callbackApiRoute","querystring","errorHandler","query","isLinearOAuthErrorCallback","error","errorDescription","error_description","sessionUserId","sessionMemberships","memberships","requireWorkspaceMembership","unavailableWorkspaceMembershipCheck","connection","code","capabilities","prefix","routes","_input","Promise","reject","Error"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { GetIntegrationConnectionByIdFn, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
1
+ import type { GetIntegrationConnectionByIdFn, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-spi';
2
2
  import { type RouteGroup } from '@shipfox/node-fastify';
3
3
  import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
4
4
  import { type LinearWebhookProcessor } from '#core/webhook-processor.js';
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,8BAA8B,EAC9B,iCAAiC,EACjC,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,EAEL,KAAK,sBAAsB,EAC5B,MAAM,4BAA4B,CAAC;AAMpC,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;IAC7D,SAAS,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAChD;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,UAAU,CAqD/F"}
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,8BAA8B,EAC9B,iCAAiC,EACjC,oBAAoB,EAGrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,4BAA4B,CAAC;AAMpC,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;IAC7D,SAAS,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAChD;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,UAAU,CAqD/F"}
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { createStoredWebhookRequest, WEBHOOK_MAX_RAW_BODY_BYTES } from '@shipfox/api-integration-core-dto';
2
+ import { createStoredWebhookRequest, WEBHOOK_MAX_RAW_BODY_BYTES } from '@shipfox/api-integration-spi';
3
3
  import { ClientError, defineRoute, rawBodyPlugin, WEBHOOK_BODY_LIMIT } from '@shipfox/node-fastify';
4
4
  import { createLinearWebhookProcessor } from '#core/webhook-processor.js';
5
5
  const DELIVERY_HEADER = 'linear-delivery';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/webhooks.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport type {\n GetIntegrationConnectionByIdFn,\n PublishIntegrationEventReceivedFn,\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 {\n createLinearWebhookProcessor,\n type LinearWebhookProcessor,\n} from '#core/webhook-processor.js';\n\nconst DELIVERY_HEADER = 'linear-delivery';\nconst EVENT_HEADER = 'linear-event';\nconst SIGNATURE_HEADER = 'linear-signature';\n\nexport interface CreateLinearWebhookRoutesOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n processor?: LinearWebhookProcessor | undefined;\n}\n\nexport function createLinearWebhookRoutes(options: CreateLinearWebhookRoutesOptions): RouteGroup {\n const processor = options.processor ?? createLinearWebhookProcessor(options);\n const webhookRoute = defineRoute({\n method: 'POST',\n path: '/',\n auth: [],\n description: 'Linear webhook receiver.',\n options: {bodyLimit: WEBHOOK_BODY_LIMIT},\n handler: async (request, reply) => {\n const receivedAt = new Date().toISOString();\n const deliveryHeader = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n\n if (typeof deliveryHeader !== 'string' || !deliveryHeader) {\n reply.code(400);\n return {error: 'missing Linear-Delivery header'};\n }\n if (typeof event !== 'string' || !event) {\n reply.code(400);\n return {error: 'missing Linear-Event header'};\n }\n if (typeof signature !== 'string' || !signature) {\n reply.code(401);\n return {error: 'missing Linear-Signature header'};\n }\n\n const body = request.body;\n if (!(body instanceof Uint8Array)) {\n reply.code(400);\n return {error: 'expected raw JSON body'};\n }\n const result = await processor.process(\n createLinearStoredWebhookRequest(\n {\n body,\n headers: request.headers,\n rawQueryString: request.raw.url?.split('?')[1] ?? '',\n },\n receivedAt,\n ),\n );\n\n return sendLinearWebhookResponse(reply, result);\n },\n });\n\n return {\n prefix: '/webhooks/integrations/linear',\n auth: [],\n plugins: [rawBodyPlugin],\n routes: [webhookRoute],\n };\n}\n\nfunction createLinearStoredWebhookRequest(\n input: {\n body: Uint8Array;\n headers: Record<string, string | string[] | undefined>;\n rawQueryString: string;\n },\n receivedAt: 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\n try {\n return createStoredWebhookRequest({\n requestId: randomUUID(),\n routeId: 'linear',\n receivedAt,\n rawQueryString: input.rawQueryString,\n headers: linearWebhookHeaders(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 linearWebhookHeaders(headers: Record<string, string | string[] | undefined>) {\n return Object.fromEntries(\n ['content-type', DELIVERY_HEADER, EVENT_HEADER, SIGNATURE_HEADER, 'linear-timestamp'].flatMap(\n (name) => {\n const value = headers[name];\n return typeof value === 'string' ? [[name, value]] : [];\n },\n ),\n );\n}\n\nfunction sendLinearWebhookResponse(\n reply: {code(statusCode: number): void},\n result: WebhookProcessingResult,\n) {\n if (result.outcome !== 'discarded') {\n reply.code(200);\n return null;\n }\n\n if (result.reason === 'invalid_signature') {\n reply.code(401);\n return {error: 'invalid signature'};\n }\n if (result.reason === 'stale_at_receipt') {\n reply.code(401);\n return {error: 'stale webhook timestamp'};\n }\n if (result.reason === 'malformed_payload') {\n reply.code(400);\n return {error: 'malformed JSON'};\n }\n\n reply.code(200);\n return null;\n}\n"],"names":["randomUUID","createStoredWebhookRequest","WEBHOOK_MAX_RAW_BODY_BYTES","ClientError","defineRoute","rawBodyPlugin","WEBHOOK_BODY_LIMIT","createLinearWebhookProcessor","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createLinearWebhookRoutes","options","processor","webhookRoute","method","path","auth","description","bodyLimit","handler","request","reply","receivedAt","Date","toISOString","deliveryHeader","headers","event","signature","code","error","body","Uint8Array","result","process","createLinearStoredWebhookRequest","rawQueryString","raw","url","split","sendLinearWebhookResponse","prefix","plugins","routes","input","byteLength","status","requestId","routeId","linearWebhookHeaders","cause","Object","fromEntries","flatMap","name","value","outcome","reason"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/webhooks.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport type {\n GetIntegrationConnectionByIdFn,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n StoredWebhookRequest,\n WebhookProcessingResult,\n} from '@shipfox/api-integration-spi';\nimport {createStoredWebhookRequest, WEBHOOK_MAX_RAW_BODY_BYTES} from '@shipfox/api-integration-spi';\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 {\n createLinearWebhookProcessor,\n type LinearWebhookProcessor,\n} from '#core/webhook-processor.js';\n\nconst DELIVERY_HEADER = 'linear-delivery';\nconst EVENT_HEADER = 'linear-event';\nconst SIGNATURE_HEADER = 'linear-signature';\n\nexport interface CreateLinearWebhookRoutesOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n processor?: LinearWebhookProcessor | undefined;\n}\n\nexport function createLinearWebhookRoutes(options: CreateLinearWebhookRoutesOptions): RouteGroup {\n const processor = options.processor ?? createLinearWebhookProcessor(options);\n const webhookRoute = defineRoute({\n method: 'POST',\n path: '/',\n auth: [],\n description: 'Linear webhook receiver.',\n options: {bodyLimit: WEBHOOK_BODY_LIMIT},\n handler: async (request, reply) => {\n const receivedAt = new Date().toISOString();\n const deliveryHeader = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n\n if (typeof deliveryHeader !== 'string' || !deliveryHeader) {\n reply.code(400);\n return {error: 'missing Linear-Delivery header'};\n }\n if (typeof event !== 'string' || !event) {\n reply.code(400);\n return {error: 'missing Linear-Event header'};\n }\n if (typeof signature !== 'string' || !signature) {\n reply.code(401);\n return {error: 'missing Linear-Signature header'};\n }\n\n const body = request.body;\n if (!(body instanceof Uint8Array)) {\n reply.code(400);\n return {error: 'expected raw JSON body'};\n }\n const result = await processor.process(\n createLinearStoredWebhookRequest(\n {\n body,\n headers: request.headers,\n rawQueryString: request.raw.url?.split('?')[1] ?? '',\n },\n receivedAt,\n ),\n );\n\n return sendLinearWebhookResponse(reply, result);\n },\n });\n\n return {\n prefix: '/webhooks/integrations/linear',\n auth: [],\n plugins: [rawBodyPlugin],\n routes: [webhookRoute],\n };\n}\n\nfunction createLinearStoredWebhookRequest(\n input: {\n body: Uint8Array;\n headers: Record<string, string | string[] | undefined>;\n rawQueryString: string;\n },\n receivedAt: 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\n try {\n return createStoredWebhookRequest({\n requestId: randomUUID(),\n routeId: 'linear',\n receivedAt,\n rawQueryString: input.rawQueryString,\n headers: linearWebhookHeaders(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 linearWebhookHeaders(headers: Record<string, string | string[] | undefined>) {\n return Object.fromEntries(\n ['content-type', DELIVERY_HEADER, EVENT_HEADER, SIGNATURE_HEADER, 'linear-timestamp'].flatMap(\n (name) => {\n const value = headers[name];\n return typeof value === 'string' ? [[name, value]] : [];\n },\n ),\n );\n}\n\nfunction sendLinearWebhookResponse(\n reply: {code(statusCode: number): void},\n result: WebhookProcessingResult,\n) {\n if (result.outcome !== 'discarded') {\n reply.code(200);\n return null;\n }\n\n if (result.reason === 'invalid_signature') {\n reply.code(401);\n return {error: 'invalid signature'};\n }\n if (result.reason === 'stale_at_receipt') {\n reply.code(401);\n return {error: 'stale webhook timestamp'};\n }\n if (result.reason === 'malformed_payload') {\n reply.code(400);\n return {error: 'malformed JSON'};\n }\n\n reply.code(200);\n return null;\n}\n"],"names":["randomUUID","createStoredWebhookRequest","WEBHOOK_MAX_RAW_BODY_BYTES","ClientError","defineRoute","rawBodyPlugin","WEBHOOK_BODY_LIMIT","createLinearWebhookProcessor","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createLinearWebhookRoutes","options","processor","webhookRoute","method","path","auth","description","bodyLimit","handler","request","reply","receivedAt","Date","toISOString","deliveryHeader","headers","event","signature","code","error","body","Uint8Array","result","process","createLinearStoredWebhookRequest","rawQueryString","raw","url","split","sendLinearWebhookResponse","prefix","plugins","routes","input","byteLength","status","requestId","routeId","linearWebhookHeaders","cause","Object","fromEntries","flatMap","name","value","outcome","reason"],"mappings":"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"}