@open-mercato/core 0.4.6-develop-6d72ec5960 → 0.4.6-develop-cd1e2a9a0e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -0
- package/dist/generated/entities/integration_credentials/index.js +19 -0
- package/dist/generated/entities/integration_credentials/index.js.map +7 -0
- package/dist/generated/entities/integration_log/index.js +27 -0
- package/dist/generated/entities/integration_log/index.js.map +7 -0
- package/dist/generated/entities/integration_state/index.js +27 -0
- package/dist/generated/entities/integration_state/index.js.map +7 -0
- package/dist/generated/entities/sync_cursor/index.js +19 -0
- package/dist/generated/entities/sync_cursor/index.js.map +7 -0
- package/dist/generated/entities/sync_external_id_mapping/index.js +27 -0
- package/dist/generated/entities/sync_external_id_mapping/index.js.map +7 -0
- package/dist/generated/entities/sync_mapping/index.js +19 -0
- package/dist/generated/entities/sync_mapping/index.js.map +7 -0
- package/dist/generated/entities/sync_run/index.js +45 -0
- package/dist/generated/entities/sync_run/index.js.map +7 -0
- package/dist/generated/entities/sync_schedule/index.js +35 -0
- package/dist/generated/entities/sync_schedule/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +14 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +16 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/data_sync/acl.js +11 -0
- package/dist/modules/data_sync/acl.js.map +7 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js +137 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +7 -0
- package/dist/modules/data_sync/api/mappings/route.js +132 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +7 -0
- package/dist/modules/data_sync/api/run.js +87 -0
- package/dist/modules/data_sync/api/run.js.map +7 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +49 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +7 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js +93 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +7 -0
- package/dist/modules/data_sync/api/runs/[id]/route.js +69 -0
- package/dist/modules/data_sync/api/runs/[id]/route.js.map +7 -0
- package/dist/modules/data_sync/api/runs.js +66 -0
- package/dist/modules/data_sync/api/runs.js.map +7 -0
- package/dist/modules/data_sync/api/validate.js +66 -0
- package/dist/modules/data_sync/api/validate.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/page.js +216 -0
- package/dist/modules/data_sync/backend/data-sync/page.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/page.meta.js +25 -0
- package/dist/modules/data_sync/backend/data-sync/page.meta.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +178 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js +14 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js.map +7 -0
- package/dist/modules/data_sync/data/entities.js +228 -0
- package/dist/modules/data_sync/data/entities.js.map +7 -0
- package/dist/modules/data_sync/data/validators.js +32 -0
- package/dist/modules/data_sync/data/validators.js.map +7 -0
- package/dist/modules/data_sync/di.js +26 -0
- package/dist/modules/data_sync/di.js.map +7 -0
- package/dist/modules/data_sync/events.js +16 -0
- package/dist/modules/data_sync/events.js.map +7 -0
- package/dist/modules/data_sync/index.js +9 -0
- package/dist/modules/data_sync/index.js.map +7 -0
- package/dist/modules/data_sync/lib/adapter-registry.js +16 -0
- package/dist/modules/data_sync/lib/adapter-registry.js.map +7 -0
- package/dist/modules/data_sync/lib/adapter.js +1 -0
- package/dist/modules/data_sync/lib/adapter.js.map +7 -0
- package/dist/modules/data_sync/lib/id-mapping.js +79 -0
- package/dist/modules/data_sync/lib/id-mapping.js.map +7 -0
- package/dist/modules/data_sync/lib/queue.js +17 -0
- package/dist/modules/data_sync/lib/queue.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-engine.js +309 -0
- package/dist/modules/data_sync/lib/sync-engine.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-run-service.js +148 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +7 -0
- package/dist/modules/data_sync/migrations/Migration20260304113737.js +17 -0
- package/dist/modules/data_sync/migrations/Migration20260304113737.js.map +7 -0
- package/dist/modules/data_sync/setup.js +13 -0
- package/dist/modules/data_sync/setup.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-export.js +14 -0
- package/dist/modules/data_sync/workers/sync-export.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-import.js +14 -0
- package/dist/modules/data_sync/workers/sync-import.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js +63 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js.map +7 -0
- package/dist/modules/entities/lib/encryptionDefaults.js +4 -0
- package/dist/modules/entities/lib/encryptionDefaults.js.map +2 -2
- package/dist/modules/integrations/acl.js +4 -1
- package/dist/modules/integrations/acl.js.map +2 -2
- package/dist/modules/integrations/api/[id]/credentials/route.js +127 -0
- package/dist/modules/integrations/api/[id]/credentials/route.js.map +7 -0
- package/dist/modules/integrations/api/[id]/health/route.js +46 -0
- package/dist/modules/integrations/api/[id]/health/route.js.map +7 -0
- package/dist/modules/integrations/api/[id]/route.js +65 -0
- package/dist/modules/integrations/api/[id]/route.js.map +7 -0
- package/dist/modules/integrations/api/[id]/state/route.js +109 -0
- package/dist/modules/integrations/api/[id]/state/route.js.map +7 -0
- package/dist/modules/integrations/api/[id]/version/route.js +117 -0
- package/dist/modules/integrations/api/[id]/version/route.js.map +7 -0
- package/dist/modules/integrations/api/guards.js +31 -0
- package/dist/modules/integrations/api/guards.js.map +7 -0
- package/dist/modules/integrations/api/logs/route.js +60 -0
- package/dist/modules/integrations/api/logs/route.js.map +7 -0
- package/dist/modules/integrations/api/openapi.js +25 -0
- package/dist/modules/integrations/api/openapi.js.map +7 -0
- package/dist/modules/integrations/api/route.js +68 -0
- package/dist/modules/integrations/api/route.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js +313 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.meta.js +15 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.meta.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +189 -0
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.meta.js +15 -0
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.meta.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/page.js +212 -0
- package/dist/modules/integrations/backend/integrations/page.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/page.meta.js +22 -0
- package/dist/modules/integrations/backend/integrations/page.meta.js.map +7 -0
- package/dist/modules/integrations/data/enrichers.js +27 -12
- package/dist/modules/integrations/data/enrichers.js.map +2 -2
- package/dist/modules/integrations/data/entities.js +136 -1
- package/dist/modules/integrations/data/entities.js.map +2 -2
- package/dist/modules/integrations/data/validators.js +36 -0
- package/dist/modules/integrations/data/validators.js.map +7 -0
- package/dist/modules/integrations/di.js +24 -0
- package/dist/modules/integrations/di.js.map +7 -0
- package/dist/modules/integrations/events.js +19 -0
- package/dist/modules/integrations/events.js.map +7 -0
- package/dist/modules/integrations/lib/credentials-service.js +159 -0
- package/dist/modules/integrations/lib/credentials-service.js.map +7 -0
- package/dist/modules/integrations/lib/health-service.js +37 -0
- package/dist/modules/integrations/lib/health-service.js.map +7 -0
- package/dist/modules/integrations/lib/log-service.js +66 -0
- package/dist/modules/integrations/lib/log-service.js.map +7 -0
- package/dist/modules/integrations/lib/registry-service.js +33 -0
- package/dist/modules/integrations/lib/registry-service.js.map +7 -0
- package/dist/modules/integrations/lib/state-service.js +55 -0
- package/dist/modules/integrations/lib/state-service.js.map +7 -0
- package/dist/modules/integrations/lib/types.js +1 -0
- package/dist/modules/integrations/lib/types.js.map +7 -0
- package/dist/modules/integrations/migrations/Migration20260304113737.js +19 -0
- package/dist/modules/integrations/migrations/Migration20260304113737.js.map +7 -0
- package/dist/modules/integrations/setup.js +2 -2
- package/dist/modules/integrations/setup.js.map +2 -2
- package/dist/modules/integrations/widgets/injection-table.js.map +1 -1
- package/dist/modules/integrations/workers/log-pruner.js +18 -0
- package/dist/modules/integrations/workers/log-pruner.js.map +7 -0
- package/generated/entities/integration_credentials/index.ts +8 -0
- package/generated/entities/integration_log/index.ts +12 -0
- package/generated/entities/integration_state/index.ts +12 -0
- package/generated/entities/sync_cursor/index.ts +8 -0
- package/generated/entities/sync_external_id_mapping/index.ts +12 -0
- package/generated/entities/sync_mapping/index.ts +8 -0
- package/generated/entities/sync_run/index.ts +21 -0
- package/generated/entities/sync_schedule/index.ts +16 -0
- package/generated/entities.ids.generated.ts +14 -0
- package/generated/entity-fields-registry.ts +16 -0
- package/package.json +2 -2
- package/src/modules/data_sync/AGENTS.md +157 -0
- package/src/modules/data_sync/acl.ts +7 -0
- package/src/modules/data_sync/api/mappings/[id]/route.ts +158 -0
- package/src/modules/data_sync/api/mappings/route.ts +144 -0
- package/src/modules/data_sync/api/run.ts +97 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +57 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +108 -0
- package/src/modules/data_sync/api/runs/[id]/route.ts +81 -0
- package/src/modules/data_sync/api/runs.ts +69 -0
- package/src/modules/data_sync/api/validate.ts +73 -0
- package/src/modules/data_sync/backend/data-sync/page.meta.ts +21 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +244 -0
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.meta.ts +10 -0
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +278 -0
- package/src/modules/data_sync/data/entities.ts +180 -0
- package/src/modules/data_sync/data/validators.ts +35 -0
- package/src/modules/data_sync/di.ts +38 -0
- package/src/modules/data_sync/events.ts +12 -0
- package/src/modules/data_sync/i18n/de.json +48 -0
- package/src/modules/data_sync/i18n/en.json +48 -0
- package/src/modules/data_sync/i18n/es.json +48 -0
- package/src/modules/data_sync/i18n/pl.json +48 -0
- package/src/modules/data_sync/index.ts +5 -0
- package/src/modules/data_sync/lib/adapter-registry.ts +15 -0
- package/src/modules/data_sync/lib/adapter.ts +90 -0
- package/src/modules/data_sync/lib/id-mapping.ts +95 -0
- package/src/modules/data_sync/lib/queue.ts +19 -0
- package/src/modules/data_sync/lib/sync-engine.ts +375 -0
- package/src/modules/data_sync/lib/sync-run-service.ts +187 -0
- package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +653 -0
- package/src/modules/data_sync/migrations/Migration20260304113737.ts +19 -0
- package/src/modules/data_sync/setup.ts +11 -0
- package/src/modules/data_sync/workers/sync-export.ts +27 -0
- package/src/modules/data_sync/workers/sync-import.ts +27 -0
- package/src/modules/data_sync/workers/sync-scheduled.ts +84 -0
- package/src/modules/entities/lib/encryptionDefaults.ts +4 -0
- package/src/modules/integrations/AGENTS.md +160 -0
- package/src/modules/integrations/acl.ts +3 -0
- package/src/modules/integrations/api/[id]/credentials/route.ts +142 -0
- package/src/modules/integrations/api/[id]/health/route.ts +53 -0
- package/src/modules/integrations/api/[id]/route.ts +76 -0
- package/src/modules/integrations/api/[id]/state/route.ts +121 -0
- package/src/modules/integrations/api/[id]/version/route.ts +132 -0
- package/src/modules/integrations/api/guards.ts +59 -0
- package/src/modules/integrations/api/logs/route.ts +63 -0
- package/src/modules/integrations/api/openapi.ts +22 -0
- package/src/modules/integrations/api/route.ts +73 -0
- package/src/modules/integrations/backend/integrations/[id]/page.meta.ts +11 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +424 -0
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.meta.ts +11 -0
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +249 -0
- package/src/modules/integrations/backend/integrations/page.meta.ts +18 -0
- package/src/modules/integrations/backend/integrations/page.tsx +296 -0
- package/src/modules/integrations/data/enrichers.ts +35 -18
- package/src/modules/integrations/data/entities.ts +114 -5
- package/src/modules/integrations/data/validators.ts +41 -0
- package/src/modules/integrations/di.ts +31 -0
- package/src/modules/integrations/events.ts +17 -0
- package/src/modules/integrations/i18n/de.json +70 -0
- package/src/modules/integrations/i18n/en.json +70 -0
- package/src/modules/integrations/i18n/es.json +70 -0
- package/src/modules/integrations/i18n/pl.json +70 -0
- package/src/modules/integrations/lib/credentials-service.ts +204 -0
- package/src/modules/integrations/lib/health-service.ts +59 -0
- package/src/modules/integrations/lib/log-service.ts +84 -0
- package/src/modules/integrations/lib/registry-service.ts +42 -0
- package/src/modules/integrations/lib/state-service.ts +64 -0
- package/src/modules/integrations/lib/types.ts +4 -0
- package/src/modules/integrations/migrations/.snapshot-open-mercato.json +582 -0
- package/src/modules/integrations/migrations/Migration20260304113737.ts +21 -0
- package/src/modules/integrations/setup.ts +2 -2
- package/src/modules/integrations/widgets/injection-table.ts +1 -1
- package/src/modules/integrations/workers/log-pruner.ts +30 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
2
|
+
import type { SyncEngine } from '../lib/sync-engine'
|
|
3
|
+
|
|
4
|
+
type SyncJobPayload = {
|
|
5
|
+
runId: string
|
|
6
|
+
batchSize: number
|
|
7
|
+
scope: {
|
|
8
|
+
organizationId: string
|
|
9
|
+
tenantId: string
|
|
10
|
+
userId?: string | null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const metadata: WorkerMeta = {
|
|
15
|
+
queue: 'data-sync-export',
|
|
16
|
+
id: 'data-sync:export',
|
|
17
|
+
concurrency: 5,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type HandlerContext = JobContext & {
|
|
21
|
+
resolve: <T = unknown>(name: string) => T
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function handle(job: QueuedJob<SyncJobPayload>, ctx: HandlerContext): Promise<void> {
|
|
25
|
+
const engine = ctx.resolve<SyncEngine>('dataSyncEngine')
|
|
26
|
+
await engine.runExport(job.payload.runId, job.payload.batchSize, job.payload.scope)
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
2
|
+
import type { SyncEngine } from '../lib/sync-engine'
|
|
3
|
+
|
|
4
|
+
type SyncJobPayload = {
|
|
5
|
+
runId: string
|
|
6
|
+
batchSize: number
|
|
7
|
+
scope: {
|
|
8
|
+
organizationId: string
|
|
9
|
+
tenantId: string
|
|
10
|
+
userId?: string | null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const metadata: WorkerMeta = {
|
|
15
|
+
queue: 'data-sync-import',
|
|
16
|
+
id: 'data-sync:import',
|
|
17
|
+
concurrency: 5,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type HandlerContext = JobContext & {
|
|
21
|
+
resolve: <T = unknown>(name: string) => T
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function handle(job: QueuedJob<SyncJobPayload>, ctx: HandlerContext): Promise<void> {
|
|
25
|
+
const engine = ctx.resolve<SyncEngine>('dataSyncEngine')
|
|
26
|
+
await engine.runImport(job.payload.runId, job.payload.batchSize, job.payload.scope)
|
|
27
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import type { SyncRunService } from '../lib/sync-run-service'
|
|
5
|
+
import { SyncSchedule } from '../data/entities'
|
|
6
|
+
import { getSyncQueue } from '../lib/queue'
|
|
7
|
+
|
|
8
|
+
type ScheduledSyncPayload = {
|
|
9
|
+
scheduleId: string
|
|
10
|
+
scope: {
|
|
11
|
+
organizationId: string
|
|
12
|
+
tenantId: string
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const metadata: WorkerMeta = {
|
|
17
|
+
queue: 'data-sync-scheduled',
|
|
18
|
+
id: 'data-sync:scheduled',
|
|
19
|
+
concurrency: 3,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HandlerContext = JobContext & {
|
|
23
|
+
resolve: <T = unknown>(name: string) => T
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default async function handle(job: QueuedJob<ScheduledSyncPayload>, ctx: HandlerContext): Promise<void> {
|
|
27
|
+
const em = ctx.resolve<EntityManager>('em')
|
|
28
|
+
const syncRunService = ctx.resolve<SyncRunService>('dataSyncRunService')
|
|
29
|
+
|
|
30
|
+
const schedule = await findOneWithDecryption(
|
|
31
|
+
em,
|
|
32
|
+
SyncSchedule,
|
|
33
|
+
{
|
|
34
|
+
id: job.payload.scheduleId,
|
|
35
|
+
organizationId: job.payload.scope.organizationId,
|
|
36
|
+
tenantId: job.payload.scope.tenantId,
|
|
37
|
+
deletedAt: null,
|
|
38
|
+
},
|
|
39
|
+
undefined,
|
|
40
|
+
job.payload.scope,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if (!schedule || !schedule.isEnabled) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const overlap = await syncRunService.findRunningOverlap(
|
|
48
|
+
schedule.integrationId,
|
|
49
|
+
schedule.entityType,
|
|
50
|
+
schedule.direction,
|
|
51
|
+
job.payload.scope,
|
|
52
|
+
)
|
|
53
|
+
if (overlap) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const cursor = schedule.fullSync
|
|
58
|
+
? null
|
|
59
|
+
: await syncRunService.resolveCursor(
|
|
60
|
+
schedule.integrationId,
|
|
61
|
+
schedule.entityType,
|
|
62
|
+
schedule.direction,
|
|
63
|
+
job.payload.scope,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const run = await syncRunService.createRun({
|
|
67
|
+
integrationId: schedule.integrationId,
|
|
68
|
+
entityType: schedule.entityType,
|
|
69
|
+
direction: schedule.direction,
|
|
70
|
+
cursor,
|
|
71
|
+
triggeredBy: 'scheduler',
|
|
72
|
+
}, job.payload.scope)
|
|
73
|
+
|
|
74
|
+
schedule.lastRunAt = new Date()
|
|
75
|
+
await em.flush()
|
|
76
|
+
|
|
77
|
+
const queueName = schedule.direction === 'import' ? 'data-sync-import' : 'data-sync-export'
|
|
78
|
+
const queue = getSyncQueue(queueName)
|
|
79
|
+
await queue.enqueue({
|
|
80
|
+
runId: run.id,
|
|
81
|
+
batchSize: 100,
|
|
82
|
+
scope: job.payload.scope,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
@@ -144,6 +144,10 @@ export const DEFAULT_ENCRYPTION_MAPS: Array<{ entityId: string; fields: Array<{
|
|
|
144
144
|
{ field: 'context_json' },
|
|
145
145
|
],
|
|
146
146
|
},
|
|
147
|
+
{
|
|
148
|
+
entityId: 'integrations:integration_credentials',
|
|
149
|
+
fields: [{ field: 'credentials' }],
|
|
150
|
+
},
|
|
147
151
|
{
|
|
148
152
|
entityId: 'staff:staff_leave_request',
|
|
149
153
|
fields: [
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Integrations Module — Agent Guide
|
|
2
|
+
|
|
3
|
+
The `integrations` module is the foundation layer for all external connectors (payment gateways, shipping carriers, communication channels, data sync providers, etc.). It provides three shared mechanisms: **Integration Registry**, **Credentials API**, and **Operation Logs**.
|
|
4
|
+
|
|
5
|
+
**Spec**: `.ai/specs/SPEC-045-2026-02-24-integration-marketplace.md` + `.ai/specs/SPEC-045a-foundation.md`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Module Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
packages/core/src/modules/integrations/
|
|
13
|
+
├── index.ts # Module metadata
|
|
14
|
+
├── di.ts # DI registrations (4 services)
|
|
15
|
+
├── acl.ts # Features: view, manage, credentials.manage
|
|
16
|
+
├── setup.ts # Default role features
|
|
17
|
+
├── events.ts # 4 typed events
|
|
18
|
+
├── data/
|
|
19
|
+
│ ├── entities.ts # IntegrationCredentials, IntegrationState, IntegrationLog, SyncExternalIdMapping
|
|
20
|
+
│ ├── validators.ts # Zod schemas for all API inputs
|
|
21
|
+
│ └── enrichers.ts # External ID response enricher
|
|
22
|
+
├── lib/
|
|
23
|
+
│ ├── registry-service.ts # Read-only access to in-memory integration registry
|
|
24
|
+
│ ├── credentials-service.ts # Encrypted CRUD + bundle credential fallthrough
|
|
25
|
+
│ ├── state-service.ts # Enable/disable + API version + reauth + health state
|
|
26
|
+
│ ├── log-service.ts # Structured logging with scoped loggers + pruning
|
|
27
|
+
│ └── health-service.ts # Resolves and runs provider health checks via DI
|
|
28
|
+
├── api/
|
|
29
|
+
│ ├── route.ts # GET /api/integrations — list all
|
|
30
|
+
│ ├── logs/route.ts # GET /api/integrations/logs — query logs
|
|
31
|
+
│ └── [id]/
|
|
32
|
+
│ ├── route.ts # GET /api/integrations/:id — detail
|
|
33
|
+
│ ├── state/route.ts # PUT — enable/disable
|
|
34
|
+
│ ├── credentials/route.ts # GET/PUT — read/save credentials
|
|
35
|
+
│ ├── version/route.ts # PUT — change API version
|
|
36
|
+
│ └── health/route.ts # POST — trigger health check
|
|
37
|
+
├── workers/
|
|
38
|
+
│ └── log-pruner.ts # Scheduled log retention cleanup
|
|
39
|
+
├── backend/
|
|
40
|
+
│ └── integrations/
|
|
41
|
+
│ ├── page.tsx # Marketplace listing page
|
|
42
|
+
│ ├── page.meta.ts
|
|
43
|
+
│ ├── [id]/
|
|
44
|
+
│ │ ├── page.tsx # Integration detail (tabs: credentials/version/health/logs)
|
|
45
|
+
│ │ └── page.meta.ts
|
|
46
|
+
│ └── bundle/[id]/
|
|
47
|
+
│ ├── page.tsx # Bundle config (shared credentials + per-integration toggles)
|
|
48
|
+
│ └── page.meta.ts
|
|
49
|
+
├── widgets/
|
|
50
|
+
│ ├── injection-table.ts
|
|
51
|
+
│ └── injection/external-ids/
|
|
52
|
+
│ └── widget.client.tsx # External ID display for any entity detail page
|
|
53
|
+
└── i18n/
|
|
54
|
+
├── en.json
|
|
55
|
+
└── pl.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Key Services (DI)
|
|
59
|
+
|
|
60
|
+
| Service Name | Factory | Purpose |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `integrationCredentialsService` | `createCredentialsService(em)` | Encrypted credential CRUD with bundle fallthrough |
|
|
63
|
+
| `integrationStateService` | `createIntegrationStateService(em)` | Upsert integration state (enabled, version, health, reauth) |
|
|
64
|
+
| `integrationLogService` | `createIntegrationLogService(em)` | Structured logging: write, query, prune, scoped logger |
|
|
65
|
+
| `integrationHealthService` | `createHealthService(container, stateService, logService)` | Resolves named health check service from DI, runs check, updates state |
|
|
66
|
+
|
|
67
|
+
## Adding a New Integration Provider
|
|
68
|
+
|
|
69
|
+
1. Create a new module (e.g., `packages/core/src/modules/gateway_stripe/`)
|
|
70
|
+
2. Add `integration.ts` at the module root exporting `IntegrationDefinition`
|
|
71
|
+
3. Declare `credentials.fields` for the admin UI to render a dynamic form
|
|
72
|
+
4. Optionally declare `healthCheck.service` (register the service in your `di.ts`)
|
|
73
|
+
5. Optionally declare `apiVersions` for versioned external APIs
|
|
74
|
+
6. Run `yarn generate` to auto-discover the integration
|
|
75
|
+
|
|
76
|
+
### Bundle Integrations
|
|
77
|
+
|
|
78
|
+
For platform connectors with multiple integrations (e.g., MedusaJS):
|
|
79
|
+
- Export `bundle: IntegrationBundle` and `integrations: IntegrationDefinition[]`
|
|
80
|
+
- Set `bundleId` on each child integration
|
|
81
|
+
- Bundle credentials are shared via fallthrough: child reads own credentials first, then bundle's
|
|
82
|
+
|
|
83
|
+
## Credential Resolution Order
|
|
84
|
+
|
|
85
|
+
1. Direct credentials for the integration ID
|
|
86
|
+
2. If `bundleId` is set, fallback to bundle's credentials
|
|
87
|
+
3. Return `null` if neither exists
|
|
88
|
+
|
|
89
|
+
## Events
|
|
90
|
+
|
|
91
|
+
| Event ID | Emitted When |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `integrations.credentials.updated` | Credentials saved |
|
|
94
|
+
| `integrations.state.updated` | Integration enabled/disabled or reauth flag changed |
|
|
95
|
+
| `integrations.version.changed` | API version changed |
|
|
96
|
+
| `integrations.log.created` | Log entry written (excluded from triggers) |
|
|
97
|
+
|
|
98
|
+
## ACL Features
|
|
99
|
+
|
|
100
|
+
- `integrations.view` — view marketplace, detail, logs
|
|
101
|
+
- `integrations.manage` — enable/disable, change version, run health checks
|
|
102
|
+
- `integrations.credentials.manage` — read/save credentials
|
|
103
|
+
|
|
104
|
+
## UMES Extensibility
|
|
105
|
+
|
|
106
|
+
Integration provider modules can leverage the full **Unified Module Extension System (UMES)** — see `.ai/specs/SPEC-041-2026-02-24-universal-module-extension-system.md` for details.
|
|
107
|
+
|
|
108
|
+
### Available Extension Points for Providers
|
|
109
|
+
|
|
110
|
+
| Extension Mechanism | Use Case | Files |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| **Widget Injection** | Inject UI tabs, cards, or status badges into other modules' pages | `widgets/injection/`, `widgets/injection-table.ts` |
|
|
113
|
+
| **Event Subscribers** | React to integration events (`integrations.state.updated`, etc.) for side-effects | `subscribers/*.ts` |
|
|
114
|
+
| **Entity Extensions** | Link provider data to core entities (e.g., external IDs on orders) | `data/extensions.ts` |
|
|
115
|
+
| **Response Enrichers** | Attach provider-specific data to other modules' API responses | `data/enrichers.ts` |
|
|
116
|
+
| **API Interceptors** | Intercept other modules' API routes (before/after hooks) | `api/interceptors.ts` |
|
|
117
|
+
| **Component Replacement** | Override or wrap UI components from other modules | `widgets/components.ts` |
|
|
118
|
+
| **Menu Injection** | Add sidebar/settings menu items | via `useInjectedMenuItems` |
|
|
119
|
+
| **Notifications** | Emit in-app notifications on integration events | `notifications.ts`, `subscribers/` |
|
|
120
|
+
| **DOM Event Bridge** | Push real-time events to browser (SSE) | Set `clientBroadcast: true` in event definitions |
|
|
121
|
+
|
|
122
|
+
### Key UMES Imports for Providers
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { InjectionPosition } from '@open-mercato/shared/modules/widgets/injection-position'
|
|
126
|
+
import { useInjectionDataWidgets } from '@open-mercato/ui/backend/injection/useInjectionDataWidgets'
|
|
127
|
+
import { useInjectedMenuItems } from '@open-mercato/ui/backend/injection/useInjectedMenuItems'
|
|
128
|
+
import { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'
|
|
129
|
+
import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
|
|
130
|
+
import type { ResponseEnricher } from '@open-mercato/shared/lib/crud/response-enricher'
|
|
131
|
+
import type { ApiInterceptor } from '@open-mercato/shared/lib/crud/api-interceptor'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Example: External ID Widget
|
|
135
|
+
|
|
136
|
+
The integrations module itself uses UMES to inject external ID displays on any entity detail page via `widgets/injection/external-ids/widget.client.tsx`, mapped through `widgets/injection-table.ts`. Follow this pattern for provider-specific widgets.
|
|
137
|
+
|
|
138
|
+
## Progress Delivery Contract
|
|
139
|
+
|
|
140
|
+
- `ProgressTopBar` polls `/api/progress/active` every 5s (`useProgressPoll`).
|
|
141
|
+
- SSE DOM bridge forwards only events with `clientBroadcast: true`.
|
|
142
|
+
- `progress.job.*` events are not yet marked `clientBroadcast: true` — polling is the active mechanism.
|
|
143
|
+
|
|
144
|
+
## Integration Test Expectations
|
|
145
|
+
|
|
146
|
+
- Module-local integration tests go under `__integration__/`
|
|
147
|
+
- Use helpers from `@open-mercato/core/modules/core/__integration__/helpers/*`
|
|
148
|
+
- Tests must create prerequisites via API and clean up in `finally`
|
|
149
|
+
|
|
150
|
+
## MUST Rules
|
|
151
|
+
|
|
152
|
+
- **Never import from provider modules** — integrations module is generic; providers import from integrations, not vice versa
|
|
153
|
+
- **Always scope by organizationId + tenantId** — every entity query and service call
|
|
154
|
+
- **Use `findWithDecryption`/`findOneWithDecryption`** for credential reads
|
|
155
|
+
- **Never log credential values** — log service strips secret fields from payload
|
|
156
|
+
- **Health check services** must be registered in DI by the provider module, not by integrations
|
|
157
|
+
- **API routes must export `openApi`** for documentation generation
|
|
158
|
+
- **All user-facing strings** via i18n keys in `i18n/en.json`
|
|
159
|
+
- **Keep ACL default export shape** consistent: `export const features = [...]; export default features`
|
|
160
|
+
- **Registry/type contracts** live in `@open-mercato/shared/modules/integrations/types`
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export const features = [
|
|
2
2
|
{ id: 'integrations.view', title: 'View integrations and external ID mappings', module: 'integrations' },
|
|
3
3
|
{ id: 'integrations.manage', title: 'Manage integration configurations', module: 'integrations' },
|
|
4
|
+
{ id: 'integrations.credentials.manage', title: 'Manage integration credentials', module: 'integrations' },
|
|
4
5
|
]
|
|
6
|
+
|
|
7
|
+
export default features
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { getIntegration } from '@open-mercato/shared/modules/integrations/types'
|
|
6
|
+
import { emitIntegrationsEvent } from '../../../events'
|
|
7
|
+
import { saveCredentialsSchema } from '../../../data/validators'
|
|
8
|
+
import type { CredentialsService } from '../../../lib/credentials-service'
|
|
9
|
+
import {
|
|
10
|
+
resolveUserFeatures,
|
|
11
|
+
runIntegrationMutationGuardAfterSuccess,
|
|
12
|
+
runIntegrationMutationGuards,
|
|
13
|
+
} from '../../guards'
|
|
14
|
+
|
|
15
|
+
const idParamsSchema = z.object({ id: z.string().min(1) })
|
|
16
|
+
|
|
17
|
+
export const metadata = {
|
|
18
|
+
GET: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },
|
|
19
|
+
PUT: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const openApi = {
|
|
23
|
+
tags: ['Integrations'],
|
|
24
|
+
summary: 'Get or save integration credentials',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string } }): Promise<{ id?: string } | undefined> | { id?: string } | undefined {
|
|
28
|
+
if (!ctx.params) return undefined
|
|
29
|
+
if (typeof (ctx.params as Promise<unknown>).then === 'function') {
|
|
30
|
+
return ctx.params as Promise<{ id?: string }>
|
|
31
|
+
}
|
|
32
|
+
return ctx.params as { id?: string }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
36
|
+
const auth = await getAuthFromRequest(req)
|
|
37
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
38
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rawParams = await resolveParams(ctx)
|
|
42
|
+
const parsedParams = idParamsSchema.safeParse(rawParams)
|
|
43
|
+
if (!parsedParams.success) {
|
|
44
|
+
return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const integration = getIntegration(parsedParams.data.id)
|
|
48
|
+
if (!integration) {
|
|
49
|
+
return NextResponse.json({ error: 'Integration not found' }, { status: 404 })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const container = await createRequestContainer()
|
|
53
|
+
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
54
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
55
|
+
|
|
56
|
+
const values = await credentialsService.resolve(integration.id, scope)
|
|
57
|
+
|
|
58
|
+
return NextResponse.json({
|
|
59
|
+
integrationId: integration.id,
|
|
60
|
+
schema: credentialsService.getSchema(integration.id),
|
|
61
|
+
credentials: values ?? {},
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
66
|
+
const auth = await getAuthFromRequest(req)
|
|
67
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
68
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rawParams = await resolveParams(ctx)
|
|
72
|
+
const parsedParams = idParamsSchema.safeParse(rawParams)
|
|
73
|
+
if (!parsedParams.success) {
|
|
74
|
+
return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const integration = getIntegration(parsedParams.data.id)
|
|
78
|
+
if (!integration) {
|
|
79
|
+
return NextResponse.json({ error: 'Integration not found' }, { status: 404 })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const payload = await req.json().catch(() => null)
|
|
83
|
+
const parsedBody = saveCredentialsSchema.safeParse(payload)
|
|
84
|
+
if (!parsedBody.success) {
|
|
85
|
+
return NextResponse.json({ error: 'Invalid credentials payload', details: parsedBody.error.flatten() }, { status: 422 })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const container = await createRequestContainer()
|
|
89
|
+
const guardResult = await runIntegrationMutationGuards(
|
|
90
|
+
container,
|
|
91
|
+
{
|
|
92
|
+
tenantId: auth.tenantId,
|
|
93
|
+
organizationId: auth.orgId,
|
|
94
|
+
userId: auth.sub ?? '',
|
|
95
|
+
resourceKind: 'integrations.integration',
|
|
96
|
+
resourceId: integration.id,
|
|
97
|
+
operation: 'update',
|
|
98
|
+
requestMethod: req.method,
|
|
99
|
+
requestHeaders: req.headers,
|
|
100
|
+
mutationPayload: parsedBody.data as Record<string, unknown>,
|
|
101
|
+
},
|
|
102
|
+
resolveUserFeatures(auth),
|
|
103
|
+
)
|
|
104
|
+
if (!guardResult.ok) {
|
|
105
|
+
return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let payloadData = parsedBody.data
|
|
109
|
+
if (guardResult.modifiedPayload) {
|
|
110
|
+
const mergedPayload = { ...parsedBody.data, ...guardResult.modifiedPayload }
|
|
111
|
+
const reparsed = saveCredentialsSchema.safeParse(mergedPayload)
|
|
112
|
+
if (!reparsed.success) {
|
|
113
|
+
return NextResponse.json({ error: 'Invalid credentials payload after guard transform', details: reparsed.error.flatten() }, { status: 422 })
|
|
114
|
+
}
|
|
115
|
+
payloadData = reparsed.data
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
119
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
120
|
+
|
|
121
|
+
await credentialsService.save(integration.id, payloadData.credentials, scope)
|
|
122
|
+
|
|
123
|
+
await emitIntegrationsEvent('integrations.credentials.updated', {
|
|
124
|
+
integrationId: integration.id,
|
|
125
|
+
tenantId: auth.tenantId,
|
|
126
|
+
organizationId: auth.orgId,
|
|
127
|
+
userId: auth.sub,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
131
|
+
tenantId: auth.tenantId,
|
|
132
|
+
organizationId: auth.orgId,
|
|
133
|
+
userId: auth.sub ?? '',
|
|
134
|
+
resourceKind: 'integrations.integration',
|
|
135
|
+
resourceId: integration.id,
|
|
136
|
+
operation: 'update',
|
|
137
|
+
requestMethod: req.method,
|
|
138
|
+
requestHeaders: req.headers,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return NextResponse.json({ ok: true })
|
|
142
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { getIntegration } from '@open-mercato/shared/modules/integrations/types'
|
|
6
|
+
import type { IntegrationHealthService } from '../../../lib/health-service'
|
|
7
|
+
|
|
8
|
+
const idParamsSchema = z.object({ id: z.string().min(1) })
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
POST: { requireAuth: true, requireFeatures: ['integrations.manage'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const openApi = {
|
|
15
|
+
tags: ['Integrations'],
|
|
16
|
+
summary: 'Run health check for an integration',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
20
|
+
const auth = await getAuthFromRequest(req)
|
|
21
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
22
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
|
|
26
|
+
? await (ctx.params as Promise<{ id?: string }>)
|
|
27
|
+
: (ctx.params as { id?: string } | undefined)
|
|
28
|
+
|
|
29
|
+
const parsedParams = idParamsSchema.safeParse(rawParams)
|
|
30
|
+
if (!parsedParams.success) {
|
|
31
|
+
return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const integration = getIntegration(parsedParams.data.id)
|
|
35
|
+
if (!integration) {
|
|
36
|
+
return NextResponse.json({ error: 'Integration not found' }, { status: 404 })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const container = await createRequestContainer()
|
|
40
|
+
const healthService = container.resolve('integrationHealthService') as IntegrationHealthService
|
|
41
|
+
|
|
42
|
+
const result = await healthService.runHealthCheck(
|
|
43
|
+
integration.id,
|
|
44
|
+
{ organizationId: auth.orgId as string, tenantId: auth.tenantId },
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return NextResponse.json({
|
|
48
|
+
status: result.status,
|
|
49
|
+
message: result.message ?? null,
|
|
50
|
+
details: result.details ?? null,
|
|
51
|
+
checkedAt: new Date().toISOString(),
|
|
52
|
+
})
|
|
53
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { getBundle, getBundleIntegrations, getIntegration } from '@open-mercato/shared/modules/integrations/types'
|
|
6
|
+
import type { CredentialsService } from '../../lib/credentials-service'
|
|
7
|
+
import type { IntegrationStateService } from '../../lib/state-service'
|
|
8
|
+
|
|
9
|
+
const idParamsSchema = z.object({ id: z.string().min(1) })
|
|
10
|
+
|
|
11
|
+
export const metadata = {
|
|
12
|
+
GET: { requireAuth: true, requireFeatures: ['integrations.view'] },
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const openApi = {
|
|
16
|
+
tags: ['Integrations'],
|
|
17
|
+
summary: 'Get integration detail',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
21
|
+
const auth = await getAuthFromRequest(req)
|
|
22
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
23
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
|
|
27
|
+
? await (ctx.params as Promise<{ id?: string }>)
|
|
28
|
+
: (ctx.params as { id?: string } | undefined)
|
|
29
|
+
|
|
30
|
+
const parsedParams = idParamsSchema.safeParse(rawParams)
|
|
31
|
+
if (!parsedParams.success) {
|
|
32
|
+
return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const integration = getIntegration(parsedParams.data.id)
|
|
36
|
+
if (!integration) {
|
|
37
|
+
return NextResponse.json({ error: 'Integration not found' }, { status: 404 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const container = await createRequestContainer()
|
|
41
|
+
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
42
|
+
const stateService = container.resolve('integrationStateService') as IntegrationStateService
|
|
43
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
44
|
+
|
|
45
|
+
const [credentials, state] = await Promise.all([
|
|
46
|
+
credentialsService.resolve(integration.id, scope),
|
|
47
|
+
stateService.get(integration.id, scope),
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
const bundle = integration.bundleId ? getBundle(integration.bundleId) : undefined
|
|
51
|
+
const bundleIntegrations = integration.bundleId
|
|
52
|
+
? await Promise.all(
|
|
53
|
+
getBundleIntegrations(integration.bundleId).map(async (item) => {
|
|
54
|
+
const itemState = await stateService.get(item.id, scope)
|
|
55
|
+
return {
|
|
56
|
+
...item,
|
|
57
|
+
isEnabled: itemState?.isEnabled ?? true,
|
|
58
|
+
}
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
: []
|
|
62
|
+
|
|
63
|
+
return NextResponse.json({
|
|
64
|
+
integration,
|
|
65
|
+
bundle,
|
|
66
|
+
bundleIntegrations,
|
|
67
|
+
state: {
|
|
68
|
+
isEnabled: state?.isEnabled ?? true,
|
|
69
|
+
apiVersion: state?.apiVersion ?? null,
|
|
70
|
+
reauthRequired: state?.reauthRequired ?? false,
|
|
71
|
+
lastHealthStatus: state?.lastHealthStatus ?? null,
|
|
72
|
+
lastHealthCheckedAt: state?.lastHealthCheckedAt?.toISOString() ?? null,
|
|
73
|
+
},
|
|
74
|
+
hasCredentials: Boolean(credentials),
|
|
75
|
+
})
|
|
76
|
+
}
|