@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,121 @@
|
|
|
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 { updateStateSchema } from '../../../data/validators'
|
|
8
|
+
import type { IntegrationStateService } from '../../../lib/state-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
|
+
PUT: { requireAuth: true, requireFeatures: ['integrations.manage'] },
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const openApi = {
|
|
22
|
+
tags: ['Integrations'],
|
|
23
|
+
summary: 'Update integration state',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
27
|
+
const auth = await getAuthFromRequest(req)
|
|
28
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
29
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
|
|
33
|
+
? await (ctx.params as Promise<{ id?: string }>)
|
|
34
|
+
: (ctx.params as { id?: string } | undefined)
|
|
35
|
+
|
|
36
|
+
const parsedParams = idParamsSchema.safeParse(rawParams)
|
|
37
|
+
if (!parsedParams.success) {
|
|
38
|
+
return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const integration = getIntegration(parsedParams.data.id)
|
|
42
|
+
if (!integration) {
|
|
43
|
+
return NextResponse.json({ error: 'Integration not found' }, { status: 404 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const payload = await req.json().catch(() => null)
|
|
47
|
+
const parsedBody = updateStateSchema.safeParse(payload)
|
|
48
|
+
if (!parsedBody.success) {
|
|
49
|
+
return NextResponse.json({ error: 'Invalid state payload', details: parsedBody.error.flatten() }, { status: 422 })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const container = await createRequestContainer()
|
|
53
|
+
const guardResult = await runIntegrationMutationGuards(
|
|
54
|
+
container,
|
|
55
|
+
{
|
|
56
|
+
tenantId: auth.tenantId,
|
|
57
|
+
organizationId: auth.orgId,
|
|
58
|
+
userId: auth.sub ?? '',
|
|
59
|
+
resourceKind: 'integrations.integration',
|
|
60
|
+
resourceId: integration.id,
|
|
61
|
+
operation: 'update',
|
|
62
|
+
requestMethod: req.method,
|
|
63
|
+
requestHeaders: req.headers,
|
|
64
|
+
mutationPayload: parsedBody.data as Record<string, unknown>,
|
|
65
|
+
},
|
|
66
|
+
resolveUserFeatures(auth),
|
|
67
|
+
)
|
|
68
|
+
if (!guardResult.ok) {
|
|
69
|
+
return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let payloadData = parsedBody.data
|
|
73
|
+
if (guardResult.modifiedPayload) {
|
|
74
|
+
const mergedPayload = { ...parsedBody.data, ...guardResult.modifiedPayload }
|
|
75
|
+
const reparsed = updateStateSchema.safeParse(mergedPayload)
|
|
76
|
+
if (!reparsed.success) {
|
|
77
|
+
return NextResponse.json({ error: 'Invalid state payload after guard transform', details: reparsed.error.flatten() }, { status: 422 })
|
|
78
|
+
}
|
|
79
|
+
payloadData = reparsed.data
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const stateService = container.resolve('integrationStateService') as IntegrationStateService
|
|
83
|
+
|
|
84
|
+
const state = await stateService.upsert(
|
|
85
|
+
integration.id,
|
|
86
|
+
{
|
|
87
|
+
isEnabled: payloadData.isEnabled,
|
|
88
|
+
reauthRequired: payloadData.reauthRequired,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
organizationId: auth.orgId as string,
|
|
92
|
+
tenantId: auth.tenantId,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
await emitIntegrationsEvent('integrations.state.updated', {
|
|
97
|
+
integrationId: integration.id,
|
|
98
|
+
isEnabled: state.isEnabled,
|
|
99
|
+
reauthRequired: state.reauthRequired,
|
|
100
|
+
tenantId: auth.tenantId,
|
|
101
|
+
organizationId: auth.orgId,
|
|
102
|
+
userId: auth.sub,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
106
|
+
tenantId: auth.tenantId,
|
|
107
|
+
organizationId: auth.orgId,
|
|
108
|
+
userId: auth.sub ?? '',
|
|
109
|
+
resourceKind: 'integrations.integration',
|
|
110
|
+
resourceId: integration.id,
|
|
111
|
+
operation: 'update',
|
|
112
|
+
requestMethod: req.method,
|
|
113
|
+
requestHeaders: req.headers,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
return NextResponse.json({
|
|
117
|
+
isEnabled: state.isEnabled,
|
|
118
|
+
reauthRequired: state.reauthRequired,
|
|
119
|
+
apiVersion: state.apiVersion ?? null,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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 { updateVersionSchema } from '../../../data/validators'
|
|
8
|
+
import type { IntegrationStateService } from '../../../lib/state-service'
|
|
9
|
+
import { resolveDefaultApiVersion } from '../../../lib/registry-service'
|
|
10
|
+
import {
|
|
11
|
+
resolveUserFeatures,
|
|
12
|
+
runIntegrationMutationGuardAfterSuccess,
|
|
13
|
+
runIntegrationMutationGuards,
|
|
14
|
+
} from '../../guards'
|
|
15
|
+
|
|
16
|
+
const idParamsSchema = z.object({ id: z.string().min(1) })
|
|
17
|
+
|
|
18
|
+
export const metadata = {
|
|
19
|
+
PUT: { requireAuth: true, requireFeatures: ['integrations.manage'] },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const openApi = {
|
|
23
|
+
tags: ['Integrations'],
|
|
24
|
+
summary: 'Change integration API version',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
28
|
+
const auth = await getAuthFromRequest(req)
|
|
29
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
30
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
|
|
34
|
+
? await (ctx.params as Promise<{ id?: string }>)
|
|
35
|
+
: (ctx.params as { id?: string } | undefined)
|
|
36
|
+
|
|
37
|
+
const parsedParams = idParamsSchema.safeParse(rawParams)
|
|
38
|
+
if (!parsedParams.success) {
|
|
39
|
+
return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const integration = getIntegration(parsedParams.data.id)
|
|
43
|
+
if (!integration) {
|
|
44
|
+
return NextResponse.json({ error: 'Integration not found' }, { status: 404 })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const payload = await req.json().catch(() => null)
|
|
48
|
+
const parsedBody = updateVersionSchema.safeParse(payload)
|
|
49
|
+
if (!parsedBody.success) {
|
|
50
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsedBody.error.flatten() }, { status: 422 })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const requestedVersion = parsedBody.data.apiVersion
|
|
54
|
+
const availableVersions = integration.apiVersions ?? []
|
|
55
|
+
if (availableVersions.length === 0) {
|
|
56
|
+
return NextResponse.json({ error: 'This integration is not versioned' }, { status: 422 })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const exists = availableVersions.some((version) => version.id === requestedVersion)
|
|
60
|
+
if (!exists) {
|
|
61
|
+
return NextResponse.json({ error: 'Unknown integration version' }, { status: 422 })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const defaultVersion = resolveDefaultApiVersion(availableVersions)
|
|
65
|
+
if (!defaultVersion) {
|
|
66
|
+
return NextResponse.json({ error: 'Integration version configuration is invalid' }, { status: 422 })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const container = await createRequestContainer()
|
|
70
|
+
const guardResult = await runIntegrationMutationGuards(
|
|
71
|
+
container,
|
|
72
|
+
{
|
|
73
|
+
tenantId: auth.tenantId,
|
|
74
|
+
organizationId: auth.orgId,
|
|
75
|
+
userId: auth.sub ?? '',
|
|
76
|
+
resourceKind: 'integrations.integration',
|
|
77
|
+
resourceId: integration.id,
|
|
78
|
+
operation: 'update',
|
|
79
|
+
requestMethod: req.method,
|
|
80
|
+
requestHeaders: req.headers,
|
|
81
|
+
mutationPayload: parsedBody.data as Record<string, unknown>,
|
|
82
|
+
},
|
|
83
|
+
resolveUserFeatures(auth),
|
|
84
|
+
)
|
|
85
|
+
if (!guardResult.ok) {
|
|
86
|
+
return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let payloadData = parsedBody.data
|
|
90
|
+
if (guardResult.modifiedPayload) {
|
|
91
|
+
const mergedPayload = { ...parsedBody.data, ...guardResult.modifiedPayload }
|
|
92
|
+
const reparsed = updateVersionSchema.safeParse(mergedPayload)
|
|
93
|
+
if (!reparsed.success) {
|
|
94
|
+
return NextResponse.json({ error: 'Invalid payload after guard transform', details: reparsed.error.flatten() }, { status: 422 })
|
|
95
|
+
}
|
|
96
|
+
payloadData = reparsed.data
|
|
97
|
+
}
|
|
98
|
+
if (!availableVersions.some((version) => version.id === payloadData.apiVersion)) {
|
|
99
|
+
return NextResponse.json({ error: 'Unknown integration version' }, { status: 422 })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const stateService = container.resolve('integrationStateService') as IntegrationStateService
|
|
103
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
104
|
+
|
|
105
|
+
const before = await stateService.resolveApiVersion(integration.id, scope)
|
|
106
|
+
await stateService.upsert(integration.id, { apiVersion: payloadData.apiVersion }, scope)
|
|
107
|
+
|
|
108
|
+
await emitIntegrationsEvent('integrations.version.changed', {
|
|
109
|
+
integrationId: integration.id,
|
|
110
|
+
previousVersion: before ?? defaultVersion,
|
|
111
|
+
apiVersion: payloadData.apiVersion,
|
|
112
|
+
tenantId: auth.tenantId,
|
|
113
|
+
organizationId: auth.orgId,
|
|
114
|
+
userId: auth.sub,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
118
|
+
tenantId: auth.tenantId,
|
|
119
|
+
organizationId: auth.orgId,
|
|
120
|
+
userId: auth.sub ?? '',
|
|
121
|
+
resourceKind: 'integrations.integration',
|
|
122
|
+
resourceId: integration.id,
|
|
123
|
+
operation: 'update',
|
|
124
|
+
requestMethod: req.method,
|
|
125
|
+
requestHeaders: req.headers,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return NextResponse.json({
|
|
129
|
+
apiVersion: payloadData.apiVersion,
|
|
130
|
+
previousVersion: before ?? defaultVersion,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AwilixContainer } from 'awilix'
|
|
2
|
+
import {
|
|
3
|
+
bridgeLegacyGuard,
|
|
4
|
+
runMutationGuards,
|
|
5
|
+
type MutationGuard,
|
|
6
|
+
type MutationGuardInput,
|
|
7
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
8
|
+
|
|
9
|
+
type GuardAfterCallback = {
|
|
10
|
+
guard: MutationGuard
|
|
11
|
+
metadata: Record<string, unknown> | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveUserFeatures(auth: unknown): string[] {
|
|
15
|
+
const features = (auth as { features?: unknown })?.features
|
|
16
|
+
if (!Array.isArray(features)) return []
|
|
17
|
+
return features.filter((value): value is string => typeof value === 'string')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runIntegrationMutationGuards(
|
|
21
|
+
container: AwilixContainer,
|
|
22
|
+
input: MutationGuardInput,
|
|
23
|
+
userFeatures: string[],
|
|
24
|
+
): Promise<{
|
|
25
|
+
ok: boolean
|
|
26
|
+
errorBody?: Record<string, unknown>
|
|
27
|
+
errorStatus?: number
|
|
28
|
+
modifiedPayload?: Record<string, unknown>
|
|
29
|
+
afterSuccessCallbacks: GuardAfterCallback[]
|
|
30
|
+
}> {
|
|
31
|
+
const legacyGuard = bridgeLegacyGuard(container)
|
|
32
|
+
if (!legacyGuard) {
|
|
33
|
+
return { ok: true, afterSuccessCallbacks: [] }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return runMutationGuards([legacyGuard], input, { userFeatures })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runIntegrationMutationGuardAfterSuccess(
|
|
40
|
+
callbacks: GuardAfterCallback[],
|
|
41
|
+
input: {
|
|
42
|
+
tenantId: string
|
|
43
|
+
organizationId: string | null
|
|
44
|
+
userId: string
|
|
45
|
+
resourceKind: string
|
|
46
|
+
resourceId: string
|
|
47
|
+
operation: 'create' | 'update' | 'delete'
|
|
48
|
+
requestMethod: string
|
|
49
|
+
requestHeaders: Headers
|
|
50
|
+
},
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
for (const callback of callbacks) {
|
|
53
|
+
if (!callback.guard.afterSuccess) continue
|
|
54
|
+
await callback.guard.afterSuccess({
|
|
55
|
+
...input,
|
|
56
|
+
metadata: callback.metadata ?? null,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
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 { listIntegrationLogsQuerySchema } from '../../data/validators'
|
|
5
|
+
import type { IntegrationLogService } from '../../lib/log-service'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
GET: { requireAuth: true, requireFeatures: ['integrations.view'] },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const openApi = {
|
|
12
|
+
tags: ['Integrations'],
|
|
13
|
+
summary: 'List integration logs',
|
|
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 = listIntegrationLogsQuerySchema.safeParse({
|
|
24
|
+
integrationId: url.searchParams.get('integrationId') ?? undefined,
|
|
25
|
+
level: url.searchParams.get('level') ?? undefined,
|
|
26
|
+
runId: url.searchParams.get('runId') ?? undefined,
|
|
27
|
+
entityType: url.searchParams.get('entityType') ?? undefined,
|
|
28
|
+
entityId: url.searchParams.get('entityId') ?? undefined,
|
|
29
|
+
page: url.searchParams.get('page') ?? undefined,
|
|
30
|
+
pageSize: url.searchParams.get('pageSize') ?? undefined,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const container = await createRequestContainer()
|
|
38
|
+
const logService = container.resolve('integrationLogService') as IntegrationLogService
|
|
39
|
+
|
|
40
|
+
const { items, total } = await logService.query(parsed.data, {
|
|
41
|
+
organizationId: auth.orgId as string,
|
|
42
|
+
tenantId: auth.tenantId,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return NextResponse.json({
|
|
46
|
+
items: items.map((item) => ({
|
|
47
|
+
id: item.id,
|
|
48
|
+
integrationId: item.integrationId,
|
|
49
|
+
runId: item.runId ?? null,
|
|
50
|
+
scopeEntityType: item.scopeEntityType ?? null,
|
|
51
|
+
scopeEntityId: item.scopeEntityId ?? null,
|
|
52
|
+
level: item.level,
|
|
53
|
+
message: item.message,
|
|
54
|
+
code: item.code ?? null,
|
|
55
|
+
payload: item.payload ?? null,
|
|
56
|
+
createdAt: item.createdAt.toISOString(),
|
|
57
|
+
})),
|
|
58
|
+
total,
|
|
59
|
+
page: parsed.data.page,
|
|
60
|
+
pageSize: parsed.data.pageSize,
|
|
61
|
+
totalPages: Math.max(1, Math.ceil(total / parsed.data.pageSize)),
|
|
62
|
+
})
|
|
63
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z, type ZodTypeAny } from 'zod'
|
|
2
|
+
import { createCrudOpenApiFactory, createPagedListResponseSchema as createSharedPagedListResponseSchema } from '@open-mercato/shared/lib/openapi/crud'
|
|
3
|
+
|
|
4
|
+
export function createPagedListResponseSchema(itemSchema: ZodTypeAny) {
|
|
5
|
+
return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true })
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const integrationInfoSchema = z.object({
|
|
9
|
+
id: z.string(),
|
|
10
|
+
title: z.string(),
|
|
11
|
+
category: z.string().nullable(),
|
|
12
|
+
hub: z.string().nullable(),
|
|
13
|
+
providerKey: z.string().nullable(),
|
|
14
|
+
bundleId: z.string().nullable(),
|
|
15
|
+
hasCredentials: z.boolean(),
|
|
16
|
+
isEnabled: z.boolean(),
|
|
17
|
+
apiVersion: z.string().nullable(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export const buildIntegrationsCrudOpenApi = createCrudOpenApiFactory({
|
|
21
|
+
defaultTag: 'Integrations',
|
|
22
|
+
})
|
|
@@ -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 type { CredentialsService } from '../lib/credentials-service'
|
|
5
|
+
import type { IntegrationStateService } from '../lib/state-service'
|
|
6
|
+
import { getAllBundles, getAllIntegrations } from '@open-mercato/shared/modules/integrations/types'
|
|
7
|
+
import { buildIntegrationsCrudOpenApi, createPagedListResponseSchema, integrationInfoSchema } from './openapi'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
GET: { requireAuth: true, requireFeatures: ['integrations.view'] },
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const openApi = buildIntegrationsCrudOpenApi({
|
|
14
|
+
resourceName: 'Integration',
|
|
15
|
+
pluralName: 'Integrations',
|
|
16
|
+
listResponseSchema: createPagedListResponseSchema(integrationInfoSchema),
|
|
17
|
+
querySchema: undefined,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export async function GET(req: Request) {
|
|
21
|
+
const auth = await getAuthFromRequest(req)
|
|
22
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
23
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const container = await createRequestContainer()
|
|
27
|
+
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
28
|
+
const stateService = container.resolve('integrationStateService') as IntegrationStateService
|
|
29
|
+
|
|
30
|
+
const rows = await Promise.all(
|
|
31
|
+
getAllIntegrations().map(async (integration) => {
|
|
32
|
+
const [resolvedCredentials, state] = await Promise.all([
|
|
33
|
+
credentialsService.resolve(integration.id, { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }),
|
|
34
|
+
stateService.get(integration.id, { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }),
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
id: integration.id,
|
|
39
|
+
title: integration.title,
|
|
40
|
+
category: integration.category ?? null,
|
|
41
|
+
hub: integration.hub ?? null,
|
|
42
|
+
providerKey: integration.providerKey ?? null,
|
|
43
|
+
bundleId: integration.bundleId ?? null,
|
|
44
|
+
hasCredentials: Boolean(resolvedCredentials),
|
|
45
|
+
isEnabled: state?.isEnabled ?? true,
|
|
46
|
+
apiVersion: state?.apiVersion ?? null,
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const bundles = getAllBundles().map((bundle) => {
|
|
52
|
+
const bundleIntegrations = rows.filter((row) => row.bundleId === bundle.id)
|
|
53
|
+
const enabledCount = bundleIntegrations.reduce((count, integration) => count + (integration.isEnabled ? 1 : 0), 0)
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
id: bundle.id,
|
|
57
|
+
title: bundle.title,
|
|
58
|
+
description: bundle.description,
|
|
59
|
+
icon: bundle.icon ?? null,
|
|
60
|
+
integrationCount: bundleIntegrations.length,
|
|
61
|
+
enabledCount,
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return NextResponse.json({
|
|
66
|
+
items: rows,
|
|
67
|
+
bundles,
|
|
68
|
+
total: rows.length,
|
|
69
|
+
page: 1,
|
|
70
|
+
pageSize: 100,
|
|
71
|
+
totalPages: 1,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const metadata = {
|
|
2
|
+
requireAuth: true,
|
|
3
|
+
requireFeatures: ['integrations.view'],
|
|
4
|
+
pageContext: 'settings' as const,
|
|
5
|
+
pageTitle: 'Integration Detail',
|
|
6
|
+
pageTitleKey: 'integrations.detail.title',
|
|
7
|
+
navHidden: true,
|
|
8
|
+
breadcrumb: [
|
|
9
|
+
{ label: 'Integrations', labelKey: 'integrations.nav.title', href: '/backend/integrations' },
|
|
10
|
+
],
|
|
11
|
+
}
|