@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,144 @@
|
|
|
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 type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
|
|
6
|
+
import { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { SyncMapping } from '../../data/entities'
|
|
8
|
+
|
|
9
|
+
const listMappingsQuerySchema = z.object({
|
|
10
|
+
integrationId: z.string().min(1).optional(),
|
|
11
|
+
entityType: z.string().min(1).optional(),
|
|
12
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
13
|
+
pageSize: z.coerce.number().int().min(1).max(100).default(20),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const createMappingSchema = z.object({
|
|
17
|
+
integrationId: z.string().min(1),
|
|
18
|
+
entityType: z.string().min(1),
|
|
19
|
+
mapping: z.record(z.string(), z.unknown()),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const metadata = {
|
|
23
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.view'] },
|
|
24
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const openApi = {
|
|
28
|
+
tags: ['DataSync'],
|
|
29
|
+
summary: 'List or create field mappings',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function GET(req: Request) {
|
|
33
|
+
const auth = await getAuthFromRequest(req)
|
|
34
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
35
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const url = new URL(req.url)
|
|
39
|
+
const parsed = listMappingsQuerySchema.safeParse({
|
|
40
|
+
integrationId: url.searchParams.get('integrationId') ?? undefined,
|
|
41
|
+
entityType: url.searchParams.get('entityType') ?? undefined,
|
|
42
|
+
page: url.searchParams.get('page') ?? undefined,
|
|
43
|
+
pageSize: url.searchParams.get('pageSize') ?? undefined,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const container = await createRequestContainer()
|
|
51
|
+
const em = container.resolve('em') as EntityManager
|
|
52
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
53
|
+
|
|
54
|
+
const where: FilterQuery<SyncMapping> = {
|
|
55
|
+
organizationId: scope.organizationId,
|
|
56
|
+
tenantId: scope.tenantId,
|
|
57
|
+
}
|
|
58
|
+
if (parsed.data.integrationId) where.integrationId = parsed.data.integrationId
|
|
59
|
+
if (parsed.data.entityType) where.entityType = parsed.data.entityType
|
|
60
|
+
|
|
61
|
+
const [items, total] = await findAndCountWithDecryption(
|
|
62
|
+
em,
|
|
63
|
+
SyncMapping,
|
|
64
|
+
where,
|
|
65
|
+
{
|
|
66
|
+
orderBy: { createdAt: 'DESC' },
|
|
67
|
+
limit: parsed.data.pageSize,
|
|
68
|
+
offset: (parsed.data.page - 1) * parsed.data.pageSize,
|
|
69
|
+
},
|
|
70
|
+
scope,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return NextResponse.json({
|
|
74
|
+
items: items.map((item) => ({
|
|
75
|
+
id: item.id,
|
|
76
|
+
integrationId: item.integrationId,
|
|
77
|
+
entityType: item.entityType,
|
|
78
|
+
mapping: item.mapping,
|
|
79
|
+
createdAt: item.createdAt.toISOString(),
|
|
80
|
+
updatedAt: item.updatedAt.toISOString(),
|
|
81
|
+
})),
|
|
82
|
+
total,
|
|
83
|
+
page: parsed.data.page,
|
|
84
|
+
pageSize: parsed.data.pageSize,
|
|
85
|
+
totalPages: Math.max(1, Math.ceil(total / parsed.data.pageSize)),
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function POST(req: Request) {
|
|
90
|
+
const auth = await getAuthFromRequest(req)
|
|
91
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
92
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const payload = await req.json().catch(() => null)
|
|
96
|
+
const parsed = createMappingSchema.safeParse(payload)
|
|
97
|
+
if (!parsed.success) {
|
|
98
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const container = await createRequestContainer()
|
|
102
|
+
const em = container.resolve('em') as EntityManager
|
|
103
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
104
|
+
|
|
105
|
+
const existing = await findOneWithDecryption(
|
|
106
|
+
em,
|
|
107
|
+
SyncMapping,
|
|
108
|
+
{
|
|
109
|
+
integrationId: parsed.data.integrationId,
|
|
110
|
+
entityType: parsed.data.entityType,
|
|
111
|
+
organizationId: scope.organizationId,
|
|
112
|
+
tenantId: scope.tenantId,
|
|
113
|
+
},
|
|
114
|
+
undefined,
|
|
115
|
+
scope,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if (existing) {
|
|
119
|
+
existing.mapping = parsed.data.mapping
|
|
120
|
+
await em.flush()
|
|
121
|
+
return NextResponse.json({
|
|
122
|
+
id: existing.id,
|
|
123
|
+
integrationId: existing.integrationId,
|
|
124
|
+
entityType: existing.entityType,
|
|
125
|
+
mapping: existing.mapping,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const created = em.create(SyncMapping, {
|
|
130
|
+
integrationId: parsed.data.integrationId,
|
|
131
|
+
entityType: parsed.data.entityType,
|
|
132
|
+
mapping: parsed.data.mapping,
|
|
133
|
+
organizationId: scope.organizationId,
|
|
134
|
+
tenantId: scope.tenantId,
|
|
135
|
+
})
|
|
136
|
+
await em.persistAndFlush(created)
|
|
137
|
+
|
|
138
|
+
return NextResponse.json({
|
|
139
|
+
id: created.id,
|
|
140
|
+
integrationId: created.integrationId,
|
|
141
|
+
entityType: created.entityType,
|
|
142
|
+
mapping: created.mapping,
|
|
143
|
+
}, { status: 201 })
|
|
144
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import type { ProgressService } from '../../progress/lib/progressService'
|
|
5
|
+
import type { SyncRunService } from '../lib/sync-run-service'
|
|
6
|
+
import { runSyncSchema } from '../data/validators'
|
|
7
|
+
import { getSyncQueue } from '../lib/queue'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const openApi = {
|
|
14
|
+
tags: ['DataSync'],
|
|
15
|
+
summary: 'Start a data sync run',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function POST(req: Request) {
|
|
19
|
+
const auth = await getAuthFromRequest(req)
|
|
20
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
21
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const payload = await req.json().catch(() => null)
|
|
25
|
+
const parsed = runSyncSchema.safeParse(payload)
|
|
26
|
+
if (!parsed.success) {
|
|
27
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const container = await createRequestContainer()
|
|
31
|
+
const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
|
|
32
|
+
const progressService = container.resolve('progressService') as ProgressService
|
|
33
|
+
|
|
34
|
+
const scope = {
|
|
35
|
+
organizationId: auth.orgId as string,
|
|
36
|
+
tenantId: auth.tenantId,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const overlap = await syncRunService.findRunningOverlap(
|
|
40
|
+
parsed.data.integrationId,
|
|
41
|
+
parsed.data.entityType,
|
|
42
|
+
parsed.data.direction,
|
|
43
|
+
scope,
|
|
44
|
+
)
|
|
45
|
+
if (overlap) {
|
|
46
|
+
return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const cursor = parsed.data.fullSync
|
|
50
|
+
? null
|
|
51
|
+
: await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope)
|
|
52
|
+
|
|
53
|
+
const progressJob = await progressService.createJob(
|
|
54
|
+
{
|
|
55
|
+
jobType: `data_sync:${parsed.data.direction}`,
|
|
56
|
+
name: `Data sync ${parsed.data.integrationId}`,
|
|
57
|
+
description: `${parsed.data.entityType} ${parsed.data.direction}`,
|
|
58
|
+
cancellable: true,
|
|
59
|
+
meta: {
|
|
60
|
+
integrationId: parsed.data.integrationId,
|
|
61
|
+
entityType: parsed.data.entityType,
|
|
62
|
+
direction: parsed.data.direction,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
tenantId: auth.tenantId,
|
|
67
|
+
organizationId: auth.orgId,
|
|
68
|
+
userId: auth.sub,
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const run = await syncRunService.createRun(
|
|
73
|
+
{
|
|
74
|
+
integrationId: parsed.data.integrationId,
|
|
75
|
+
entityType: parsed.data.entityType,
|
|
76
|
+
direction: parsed.data.direction,
|
|
77
|
+
cursor,
|
|
78
|
+
triggeredBy: parsed.data.triggeredBy ?? auth.sub,
|
|
79
|
+
progressJobId: progressJob.id,
|
|
80
|
+
},
|
|
81
|
+
scope,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const queueName = parsed.data.direction === 'import' ? 'data-sync-import' : 'data-sync-export'
|
|
85
|
+
const queue = getSyncQueue(queueName)
|
|
86
|
+
await queue.enqueue({
|
|
87
|
+
runId: run.id,
|
|
88
|
+
batchSize: parsed.data.batchSize,
|
|
89
|
+
scope: {
|
|
90
|
+
organizationId: scope.organizationId,
|
|
91
|
+
tenantId: scope.tenantId,
|
|
92
|
+
userId: auth.sub,
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return NextResponse.json({ id: run.id, progressJobId: progressJob.id }, { status: 201 })
|
|
97
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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 type { ProgressService } from '../../../../progress/lib/progressService'
|
|
6
|
+
import type { SyncRunService } from '../../../lib/sync-run-service'
|
|
7
|
+
|
|
8
|
+
const paramsSchema = z.object({ id: z.string().uuid() })
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const openApi = {
|
|
15
|
+
tags: ['DataSync'],
|
|
16
|
+
summary: 'Cancel a running sync',
|
|
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 parsed = paramsSchema.safeParse(rawParams)
|
|
30
|
+
if (!parsed.success) {
|
|
31
|
+
return NextResponse.json({ error: 'Invalid run id' }, { status: 400 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const container = await createRequestContainer()
|
|
35
|
+
const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
|
|
36
|
+
const progressService = container.resolve('progressService') as ProgressService
|
|
37
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
38
|
+
|
|
39
|
+
const run = await syncRunService.getRun(parsed.data.id, scope)
|
|
40
|
+
if (!run) {
|
|
41
|
+
return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
42
|
+
}
|
|
43
|
+
if (run.status !== 'running' && run.status !== 'pending') {
|
|
44
|
+
return NextResponse.json({ error: 'Only pending or running runs can be cancelled' }, { status: 409 })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (run.progressJobId) {
|
|
48
|
+
await progressService.cancelJob(run.progressJobId, {
|
|
49
|
+
tenantId: auth.tenantId,
|
|
50
|
+
organizationId: auth.orgId,
|
|
51
|
+
userId: auth.sub,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await syncRunService.markStatus(run.id, 'cancelled', scope)
|
|
56
|
+
return NextResponse.json({ ok: true })
|
|
57
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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 type { ProgressService } from '../../../../progress/lib/progressService'
|
|
6
|
+
import type { SyncRunService } from '../../../lib/sync-run-service'
|
|
7
|
+
import { retrySyncSchema } from '../../../data/validators'
|
|
8
|
+
import { getSyncQueue } from '../../../lib/queue'
|
|
9
|
+
|
|
10
|
+
const paramsSchema = z.object({ id: z.string().uuid() })
|
|
11
|
+
|
|
12
|
+
export const metadata = {
|
|
13
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const openApi = {
|
|
17
|
+
tags: ['DataSync'],
|
|
18
|
+
summary: 'Retry a failed sync run',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
22
|
+
const auth = await getAuthFromRequest(req)
|
|
23
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
24
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
|
|
28
|
+
? await (ctx.params as Promise<{ id?: string }>)
|
|
29
|
+
: (ctx.params as { id?: string } | undefined)
|
|
30
|
+
|
|
31
|
+
const parsedParams = paramsSchema.safeParse(rawParams)
|
|
32
|
+
if (!parsedParams.success) {
|
|
33
|
+
return NextResponse.json({ error: 'Invalid run id' }, { status: 400 })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const payload = await req.json().catch(() => null)
|
|
37
|
+
const parsedBody = retrySyncSchema.safeParse(payload ?? {})
|
|
38
|
+
if (!parsedBody.success) {
|
|
39
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsedBody.error.flatten() }, { status: 422 })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const container = await createRequestContainer()
|
|
43
|
+
const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
|
|
44
|
+
const progressService = container.resolve('progressService') as ProgressService
|
|
45
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
46
|
+
|
|
47
|
+
const previous = await syncRunService.getRun(parsedParams.data.id, scope)
|
|
48
|
+
if (!previous) {
|
|
49
|
+
return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
50
|
+
}
|
|
51
|
+
if (previous.status !== 'failed' && previous.status !== 'cancelled') {
|
|
52
|
+
return NextResponse.json({ error: 'Only failed or cancelled runs can be retried' }, { status: 409 })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const overlap = await syncRunService.findRunningOverlap(
|
|
56
|
+
previous.integrationId,
|
|
57
|
+
previous.entityType,
|
|
58
|
+
previous.direction,
|
|
59
|
+
scope,
|
|
60
|
+
)
|
|
61
|
+
if (overlap) {
|
|
62
|
+
return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const cursor = parsedBody.data.fromBeginning
|
|
66
|
+
? null
|
|
67
|
+
: previous.cursor ?? await syncRunService.resolveCursor(previous.integrationId, previous.entityType, previous.direction, scope)
|
|
68
|
+
|
|
69
|
+
const progressJob = await progressService.createJob(
|
|
70
|
+
{
|
|
71
|
+
jobType: `data_sync:${previous.direction}`,
|
|
72
|
+
name: `Retry data sync ${previous.integrationId}`,
|
|
73
|
+
description: `${previous.entityType} ${previous.direction}`,
|
|
74
|
+
cancellable: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
tenantId: auth.tenantId,
|
|
78
|
+
organizationId: auth.orgId,
|
|
79
|
+
userId: auth.sub,
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const run = await syncRunService.createRun(
|
|
84
|
+
{
|
|
85
|
+
integrationId: previous.integrationId,
|
|
86
|
+
entityType: previous.entityType,
|
|
87
|
+
direction: previous.direction,
|
|
88
|
+
cursor,
|
|
89
|
+
triggeredBy: auth.sub,
|
|
90
|
+
progressJobId: progressJob.id,
|
|
91
|
+
},
|
|
92
|
+
scope,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const queueName = run.direction === 'import' ? 'data-sync-import' : 'data-sync-export'
|
|
96
|
+
const queue = getSyncQueue(queueName)
|
|
97
|
+
await queue.enqueue({
|
|
98
|
+
runId: run.id,
|
|
99
|
+
batchSize: 100,
|
|
100
|
+
scope: {
|
|
101
|
+
organizationId: scope.organizationId,
|
|
102
|
+
tenantId: scope.tenantId,
|
|
103
|
+
userId: auth.sub,
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return NextResponse.json({ id: run.id, progressJobId: progressJob.id }, { status: 201 })
|
|
108
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 type { ProgressService } from '../../../../progress/lib/progressService'
|
|
6
|
+
import type { SyncRunService } from '../../../lib/sync-run-service'
|
|
7
|
+
|
|
8
|
+
const paramsSchema = z.object({ id: z.string().uuid() })
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.view'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const openApi = {
|
|
15
|
+
tags: ['DataSync'],
|
|
16
|
+
summary: 'Get sync run detail',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function GET(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 parsed = paramsSchema.safeParse(rawParams)
|
|
30
|
+
if (!parsed.success) {
|
|
31
|
+
return NextResponse.json({ error: 'Invalid run id' }, { status: 400 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const container = await createRequestContainer()
|
|
35
|
+
const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
|
|
36
|
+
const progressService = container.resolve('progressService') as ProgressService
|
|
37
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
38
|
+
|
|
39
|
+
const run = await syncRunService.getRun(parsed.data.id, scope)
|
|
40
|
+
if (!run) {
|
|
41
|
+
return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const progressJob = run.progressJobId
|
|
45
|
+
? await progressService.getJob(run.progressJobId, {
|
|
46
|
+
tenantId: auth.tenantId,
|
|
47
|
+
organizationId: auth.orgId,
|
|
48
|
+
userId: auth.sub,
|
|
49
|
+
})
|
|
50
|
+
: null
|
|
51
|
+
|
|
52
|
+
return NextResponse.json({
|
|
53
|
+
id: run.id,
|
|
54
|
+
integrationId: run.integrationId,
|
|
55
|
+
entityType: run.entityType,
|
|
56
|
+
direction: run.direction,
|
|
57
|
+
status: run.status,
|
|
58
|
+
cursor: run.cursor ?? null,
|
|
59
|
+
initialCursor: run.initialCursor ?? null,
|
|
60
|
+
createdCount: run.createdCount,
|
|
61
|
+
updatedCount: run.updatedCount,
|
|
62
|
+
skippedCount: run.skippedCount,
|
|
63
|
+
failedCount: run.failedCount,
|
|
64
|
+
batchesCompleted: run.batchesCompleted,
|
|
65
|
+
lastError: run.lastError ?? null,
|
|
66
|
+
progressJobId: run.progressJobId ?? null,
|
|
67
|
+
progressJob: progressJob
|
|
68
|
+
? {
|
|
69
|
+
id: progressJob.id,
|
|
70
|
+
status: progressJob.status,
|
|
71
|
+
progressPercent: progressJob.progressPercent,
|
|
72
|
+
processedCount: progressJob.processedCount,
|
|
73
|
+
totalCount: progressJob.totalCount ?? null,
|
|
74
|
+
etaSeconds: progressJob.etaSeconds ?? null,
|
|
75
|
+
}
|
|
76
|
+
: null,
|
|
77
|
+
triggeredBy: run.triggeredBy ?? null,
|
|
78
|
+
createdAt: run.createdAt.toISOString(),
|
|
79
|
+
updatedAt: run.updatedAt.toISOString(),
|
|
80
|
+
})
|
|
81
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { listSyncRunsQuerySchema } from '../data/validators'
|
|
5
|
+
import type { SyncRunService } from '../lib/sync-run-service'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.view'] },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const openApi = {
|
|
12
|
+
tags: ['DataSync'],
|
|
13
|
+
summary: 'List sync runs',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function GET(req: Request) {
|
|
17
|
+
const auth = await getAuthFromRequest(req)
|
|
18
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
19
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const url = new URL(req.url)
|
|
23
|
+
const parsed = listSyncRunsQuerySchema.safeParse({
|
|
24
|
+
integrationId: url.searchParams.get('integrationId') ?? undefined,
|
|
25
|
+
entityType: url.searchParams.get('entityType') ?? undefined,
|
|
26
|
+
direction: url.searchParams.get('direction') ?? undefined,
|
|
27
|
+
status: url.searchParams.get('status') ?? undefined,
|
|
28
|
+
page: url.searchParams.get('page') ?? undefined,
|
|
29
|
+
pageSize: url.searchParams.get('pageSize') ?? undefined,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (!parsed.success) {
|
|
33
|
+
return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const container = await createRequestContainer()
|
|
37
|
+
const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
|
|
38
|
+
|
|
39
|
+
const { items, total } = await syncRunService.listRuns(parsed.data, {
|
|
40
|
+
organizationId: auth.orgId as string,
|
|
41
|
+
tenantId: auth.tenantId,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return NextResponse.json({
|
|
45
|
+
items: items.map((item) => ({
|
|
46
|
+
id: item.id,
|
|
47
|
+
integrationId: item.integrationId,
|
|
48
|
+
entityType: item.entityType,
|
|
49
|
+
direction: item.direction,
|
|
50
|
+
status: item.status,
|
|
51
|
+
cursor: item.cursor ?? null,
|
|
52
|
+
initialCursor: item.initialCursor ?? null,
|
|
53
|
+
createdCount: item.createdCount,
|
|
54
|
+
updatedCount: item.updatedCount,
|
|
55
|
+
skippedCount: item.skippedCount,
|
|
56
|
+
failedCount: item.failedCount,
|
|
57
|
+
batchesCompleted: item.batchesCompleted,
|
|
58
|
+
lastError: item.lastError ?? null,
|
|
59
|
+
progressJobId: item.progressJobId ?? null,
|
|
60
|
+
triggeredBy: item.triggeredBy ?? null,
|
|
61
|
+
createdAt: item.createdAt.toISOString(),
|
|
62
|
+
updatedAt: item.updatedAt.toISOString(),
|
|
63
|
+
})),
|
|
64
|
+
total,
|
|
65
|
+
page: parsed.data.page,
|
|
66
|
+
pageSize: parsed.data.pageSize,
|
|
67
|
+
totalPages: Math.max(1, Math.ceil(total / parsed.data.pageSize)),
|
|
68
|
+
})
|
|
69
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { getIntegration } from '@open-mercato/shared/modules/integrations/types'
|
|
5
|
+
import type { CredentialsService } from '../../integrations/lib/credentials-service'
|
|
6
|
+
import { validateConnectionSchema } from '../data/validators'
|
|
7
|
+
import { getDataSyncAdapter } from '../lib/adapter-registry'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const openApi = {
|
|
14
|
+
tags: ['DataSync'],
|
|
15
|
+
summary: 'Validate sync connection',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function POST(req: Request) {
|
|
19
|
+
const auth = await getAuthFromRequest(req)
|
|
20
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
21
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parsed = validateConnectionSchema.safeParse(await req.json())
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const integration = getIntegration(parsed.data.integrationId)
|
|
30
|
+
if (!integration?.providerKey) {
|
|
31
|
+
return NextResponse.json({ ok: false, message: 'Integration or providerKey not found' }, { status: 404 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const adapter = getDataSyncAdapter(integration.providerKey)
|
|
35
|
+
if (!adapter) {
|
|
36
|
+
return NextResponse.json({ ok: false, message: 'No registered sync adapter for provider' }, { status: 404 })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const container = await createRequestContainer()
|
|
40
|
+
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
41
|
+
const credentials = await credentialsService.resolve(integration.id, {
|
|
42
|
+
organizationId: auth.orgId as string,
|
|
43
|
+
tenantId: auth.tenantId,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (!credentials) {
|
|
47
|
+
return NextResponse.json({ ok: false, message: 'Missing credentials' }, { status: 422 })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const mapping = await adapter.getMapping({
|
|
51
|
+
entityType: parsed.data.entityType,
|
|
52
|
+
scope: {
|
|
53
|
+
organizationId: auth.orgId as string,
|
|
54
|
+
tenantId: auth.tenantId,
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (!adapter.validateConnection) {
|
|
59
|
+
return NextResponse.json({ ok: true, message: 'Adapter does not implement active connection validation' })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = await adapter.validateConnection({
|
|
63
|
+
entityType: parsed.data.entityType,
|
|
64
|
+
credentials,
|
|
65
|
+
mapping,
|
|
66
|
+
scope: {
|
|
67
|
+
organizationId: auth.orgId as string,
|
|
68
|
+
tenantId: auth.tenantId,
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return NextResponse.json(result, { status: result.ok ? 200 : 422 })
|
|
73
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const syncIcon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },
|
|
4
|
+
React.createElement('path', { d: 'M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }),
|
|
5
|
+
React.createElement('path', { d: 'M3 3v5h5' }),
|
|
6
|
+
React.createElement('path', { d: 'M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16' }),
|
|
7
|
+
React.createElement('path', { d: 'M16 16h5v5' }),
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
requireAuth: true,
|
|
12
|
+
requireFeatures: ['data_sync.view'],
|
|
13
|
+
pageTitle: 'Data Sync',
|
|
14
|
+
pageTitleKey: 'data_sync.nav.title',
|
|
15
|
+
pageGroup: 'Settings',
|
|
16
|
+
pageGroupKey: 'settings.sections.general',
|
|
17
|
+
pageOrder: 51,
|
|
18
|
+
icon: syncIcon,
|
|
19
|
+
pageContext: 'settings' as const,
|
|
20
|
+
breadcrumb: [{ label: 'Data Sync', labelKey: 'data_sync.nav.title' }],
|
|
21
|
+
}
|