@open-mercato/core 0.6.6-develop.6205.1.109e4b6a84 → 0.6.6-develop.6227.1.2695efff30
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/currencies/backend/currencies/[id]/page.js +33 -8
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +63 -27
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +57 -31
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +156 -76
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/lib/calendar/labels.js +33 -0
- package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/route.js +44 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +31 -0
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +33 -4
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +54 -2
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/branding/page.js +15 -9
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +15 -0
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +26 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
- package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
- package/dist/modules/entities/api/definitions.restore.js +15 -0
- package/dist/modules/entities/api/definitions.restore.js.map +2 -2
- package/dist/modules/entities/api/entities.js +31 -3
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +37 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/guards.js +31 -0
- package/dist/modules/payment_gateways/api/guards.js.map +7 -0
- package/dist/modules/payment_gateways/api/refund/route.js +37 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +11 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +16 -0
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/purge.js +35 -3
- package/dist/modules/query_index/api/purge.js.map +2 -2
- package/dist/modules/query_index/api/reindex.js +41 -3
- package/dist/modules/query_index/api/reindex.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +6 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
- package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +49 -20
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
- package/src/modules/currencies/backend/currencies/page.tsx +68 -29
- package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
- package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
- package/src/modules/customers/components/DictionarySettings.tsx +63 -29
- package/src/modules/customers/components/PipelineSettings.tsx +165 -80
- package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
- package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
- package/src/modules/customers/i18n/de.json +11 -7
- package/src/modules/customers/i18n/en.json +6 -2
- package/src/modules/customers/i18n/es.json +11 -7
- package/src/modules/customers/i18n/pl.json +11 -7
- package/src/modules/customers/lib/calendar/labels.ts +42 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
- package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
- package/src/modules/data_sync/api/mappings/route.ts +48 -0
- package/src/modules/data_sync/api/run.ts +33 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
- package/src/modules/data_sync/api/schedules/route.ts +36 -4
- package/src/modules/directory/api/organization-branding/route.ts +61 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
- package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
- package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
- package/src/modules/entities/api/definitions.batch.ts +17 -0
- package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
- package/src/modules/entities/api/definitions.restore.ts +17 -0
- package/src/modules/entities/api/definitions.ts +30 -0
- package/src/modules/entities/api/entities.ts +35 -3
- package/src/modules/entities/api/records.ts +20 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
- package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
- package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
- package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
- package/src/modules/payment_gateways/api/capture/route.ts +39 -0
- package/src/modules/payment_gateways/api/guards.ts +59 -0
- package/src/modules/payment_gateways/api/refund/route.ts +39 -0
- package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
- package/src/modules/planner/api/availability-date-specific.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
- package/src/modules/planner/commands/availability-date-specific.ts +24 -0
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
- package/src/modules/query_index/api/purge.ts +37 -3
- package/src/modules/query_index/api/reindex.ts +43 -3
- package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
- package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
- package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
- package/src/modules/shipping_carriers/data/validators.ts +5 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
- package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
- package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
- package/src/modules/translations/components/TranslationManager.tsx +65 -21
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/data_sync/api/runs/%5Bid%5D/cancel.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { IntegrationLogService } from '../../../../integrations/lib/log-service'\nimport type { IntegrationStateService } from '../../../../integrations/lib/state-service'\nimport type { ProgressService } from '../../../../progress/lib/progressService'\nimport type { SyncRunService } from '../../../lib/sync-run-service'\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Cancel a running sync',\n}\n\nexport async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsed = paramsSchema.safeParse(rawParams)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid run id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const integrationLogService = container.resolve('integrationLogService') as IntegrationLogService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const run = await syncRunService.getRun(parsed.data.id, scope)\n if (!run) {\n return NextResponse.json({ error: 'Run not found' }, { status: 404 })\n }\n if (run.status !== 'running' && run.status !== 'pending') {\n return NextResponse.json({ error: 'Only pending or running runs can be cancelled' }, { status: 409 })\n }\n\n const progressCtx = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n }\n\n if (run.progressJobId) {\n try {\n await progressService.markCancelled(run.progressJobId, progressCtx)\n } catch (error) {\n const job = await progressService.getJob(run.progressJobId, progressCtx)\n const cancelRequested = job && (job.status === 'running' || job.status === 'cancelled')\n ? await progressService.isCancellationRequested(run.progressJobId, progressCtx.tenantId)\n : false\n\n if (job?.status !== 'cancelled' && !cancelRequested) {\n throw error\n }\n }\n }\n\n await syncRunService.markStatus(run.id, 'cancelled', scope)\n await integrationStateService.upsert(run.integrationId, {\n lastHealthStatus: 'degraded',\n lastHealthCheckedAt: new Date(),\n }, scope)\n await integrationLogService.write({\n integrationId: run.integrationId,\n runId: run.id,\n level: 'warn',\n message: 'Sync run cancelled',\n payload: {\n operationalStatus: 'cancelled',\n summary: 'The sync run was cancelled before completion.',\n },\n }, scope)\n return NextResponse.json({ ok: true })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { IntegrationLogService } from '../../../../integrations/lib/log-service'\nimport type { IntegrationStateService } from '../../../../integrations/lib/state-service'\nimport type { ProgressService } from '../../../../progress/lib/progressService'\nimport type { SyncRunService } from '../../../lib/sync-run-service'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Cancel a running sync',\n}\n\nexport async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsed = paramsSchema.safeParse(rawParams)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid run id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const integrationLogService = container.resolve('integrationLogService') as IntegrationLogService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const run = await syncRunService.getRun(parsed.data.id, scope)\n if (!run) {\n return NextResponse.json({ error: 'Run not found' }, { status: 404 })\n }\n if (run.status !== 'running' && run.status !== 'pending') {\n return NextResponse.json({ error: 'Only pending or running runs can be cancelled' }, { status: 409 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.run',\n resourceId: run.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { action: 'cancel' },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const progressCtx = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n }\n\n if (run.progressJobId) {\n try {\n await progressService.markCancelled(run.progressJobId, progressCtx)\n } catch (error) {\n const job = await progressService.getJob(run.progressJobId, progressCtx)\n const cancelRequested = job && (job.status === 'running' || job.status === 'cancelled')\n ? await progressService.isCancellationRequested(run.progressJobId, progressCtx.tenantId)\n : false\n\n if (job?.status !== 'cancelled' && !cancelRequested) {\n throw error\n }\n }\n }\n\n await syncRunService.markStatus(run.id, 'cancelled', scope)\n await integrationStateService.upsert(run.integrationId, {\n lastHealthStatus: 'degraded',\n lastHealthCheckedAt: new Date(),\n }, scope)\n await integrationLogService.write({\n integrationId: run.integrationId,\n runId: run.id,\n level: 'warn',\n message: 'Sync run cancelled',\n payload: {\n operationalStatus: 'cancelled',\n summary: 'The sync run was cancelled before completion.',\n },\n }, scope)\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.run',\n resourceId: run.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,10 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
5
5
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
6
6
|
import { retrySyncSchema } from "../../../data/validators.js";
|
|
7
7
|
import { startDataSyncRun } from "../../../lib/start-run.js";
|
|
8
|
+
import {
|
|
9
|
+
runCrudMutationGuardAfterSuccess,
|
|
10
|
+
validateCrudMutationGuard
|
|
11
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
8
12
|
const paramsSchema = z.object({ id: z.string().uuid() });
|
|
9
13
|
const metadata = {
|
|
10
14
|
POST: { requireAuth: true, requireFeatures: ["data_sync.run"] }
|
|
@@ -48,6 +52,20 @@ async function POST(req, ctx) {
|
|
|
48
52
|
if (overlap) {
|
|
49
53
|
return NextResponse.json({ error: "A sync run is already in progress for this integration and entity direction" }, { status: 409 });
|
|
50
54
|
}
|
|
55
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
56
|
+
tenantId: auth.tenantId,
|
|
57
|
+
organizationId: scope.organizationId,
|
|
58
|
+
userId: auth.sub,
|
|
59
|
+
resourceKind: "data_sync.run",
|
|
60
|
+
resourceId: previous.id,
|
|
61
|
+
operation: "custom",
|
|
62
|
+
requestMethod: req.method,
|
|
63
|
+
requestHeaders: req.headers,
|
|
64
|
+
mutationPayload: { action: "retry", ...parsedBody.data }
|
|
65
|
+
});
|
|
66
|
+
if (guardResult && !guardResult.ok) {
|
|
67
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
68
|
+
}
|
|
51
69
|
const cursor = parsedBody.data.fromBeginning ? null : previous.cursor ?? await syncRunService.resolveCursor(previous.integrationId, previous.entityType, previous.direction, scope);
|
|
52
70
|
const { run, progressJob } = await startDataSyncRun({
|
|
53
71
|
syncRunService,
|
|
@@ -68,6 +86,19 @@ async function POST(req, ctx) {
|
|
|
68
86
|
}
|
|
69
87
|
}
|
|
70
88
|
});
|
|
89
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
90
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
91
|
+
tenantId: auth.tenantId,
|
|
92
|
+
organizationId: scope.organizationId,
|
|
93
|
+
userId: auth.sub,
|
|
94
|
+
resourceKind: "data_sync.run",
|
|
95
|
+
resourceId: run.id,
|
|
96
|
+
operation: "custom",
|
|
97
|
+
requestMethod: req.method,
|
|
98
|
+
requestHeaders: req.headers,
|
|
99
|
+
metadata: guardResult.metadata ?? null
|
|
100
|
+
});
|
|
101
|
+
}
|
|
71
102
|
return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 });
|
|
72
103
|
}
|
|
73
104
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/data_sync/api/runs/%5Bid%5D/retry.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { ProgressService } from '../../../../progress/lib/progressService'\nimport type { SyncRunService } from '../../../lib/sync-run-service'\nimport { retrySyncSchema } from '../../../data/validators'\nimport { startDataSyncRun } from '../../../lib/start-run'\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Retry a failed sync run',\n}\n\nexport async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid run id' }, { status: 400 })\n }\n\n const payload = await readJsonSafe(req)\n const parsedBody = retrySyncSchema.safeParse(payload ?? {})\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const previous = await syncRunService.getRun(parsedParams.data.id, scope)\n if (!previous) {\n return NextResponse.json({ error: 'Run not found' }, { status: 404 })\n }\n if (previous.status !== 'failed' && previous.status !== 'cancelled') {\n return NextResponse.json({ error: 'Only failed or cancelled runs can be retried' }, { status: 409 })\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n previous.integrationId,\n previous.entityType,\n previous.direction,\n scope,\n )\n if (overlap) {\n return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })\n }\n\n const cursor = parsedBody.data.fromBeginning\n ? null\n : previous.cursor ?? await syncRunService.resolveCursor(previous.integrationId, previous.entityType, previous.direction, scope)\n\n const { run, progressJob } = await startDataSyncRun({\n syncRunService,\n progressService,\n scope: {\n ...scope,\n userId: auth.sub,\n },\n input: {\n integrationId: previous.integrationId,\n entityType: previous.entityType,\n direction: previous.direction,\n cursor,\n triggeredBy: auth.sub,\n batchSize: 100,\n progressJob: {\n name: `Retry data sync ${previous.integrationId} \u2014 ${previous.entityType}`,\n },\n },\n })\n\n return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAG7B,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { ProgressService } from '../../../../progress/lib/progressService'\nimport type { SyncRunService } from '../../../lib/sync-run-service'\nimport { retrySyncSchema } from '../../../data/validators'\nimport { startDataSyncRun } from '../../../lib/start-run'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Retry a failed sync run',\n}\n\nexport async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid run id' }, { status: 400 })\n }\n\n const payload = await readJsonSafe(req)\n const parsedBody = retrySyncSchema.safeParse(payload ?? {})\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const previous = await syncRunService.getRun(parsedParams.data.id, scope)\n if (!previous) {\n return NextResponse.json({ error: 'Run not found' }, { status: 404 })\n }\n if (previous.status !== 'failed' && previous.status !== 'cancelled') {\n return NextResponse.json({ error: 'Only failed or cancelled runs can be retried' }, { status: 409 })\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n previous.integrationId,\n previous.entityType,\n previous.direction,\n scope,\n )\n if (overlap) {\n return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.run',\n resourceId: previous.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { action: 'retry', ...parsedBody.data },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const cursor = parsedBody.data.fromBeginning\n ? null\n : previous.cursor ?? await syncRunService.resolveCursor(previous.integrationId, previous.entityType, previous.direction, scope)\n\n const { run, progressJob } = await startDataSyncRun({\n syncRunService,\n progressService,\n scope: {\n ...scope,\n userId: auth.sub,\n },\n input: {\n integrationId: previous.integrationId,\n entityType: previous.entityType,\n direction: previous.direction,\n cursor,\n triggeredBy: auth.sub,\n batchSize: 100,\n progressJob: {\n name: `Retry data sync ${previous.integrationId} \u2014 ${previous.entityType}`,\n },\n },\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.run',\n resourceId: run.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,10 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
5
5
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
6
6
|
import { updateSyncScheduleSchema } from "../../../data/validators.js";
|
|
7
7
|
import { serializeSchedule } from "../serialize.js";
|
|
8
|
+
import {
|
|
9
|
+
runCrudMutationGuardAfterSuccess,
|
|
10
|
+
validateCrudMutationGuard
|
|
11
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
8
12
|
const paramsSchema = z.object({
|
|
9
13
|
id: z.string().uuid()
|
|
10
14
|
});
|
|
@@ -60,6 +64,20 @@ async function PUT(req, ctx) {
|
|
|
60
64
|
if (!current) {
|
|
61
65
|
return NextResponse.json({ error: "Schedule not found" }, { status: 404 });
|
|
62
66
|
}
|
|
67
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
68
|
+
tenantId: auth.tenantId,
|
|
69
|
+
organizationId: scope.organizationId,
|
|
70
|
+
userId: auth.sub,
|
|
71
|
+
resourceKind: "data_sync.schedule",
|
|
72
|
+
resourceId: current.id,
|
|
73
|
+
operation: "update",
|
|
74
|
+
requestMethod: req.method,
|
|
75
|
+
requestHeaders: req.headers,
|
|
76
|
+
mutationPayload: parsed.data
|
|
77
|
+
});
|
|
78
|
+
if (guardResult && !guardResult.ok) {
|
|
79
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
80
|
+
}
|
|
63
81
|
try {
|
|
64
82
|
const schedule = await scheduleService.saveSchedule({
|
|
65
83
|
id: current.id,
|
|
@@ -72,6 +90,19 @@ async function PUT(req, ctx) {
|
|
|
72
90
|
fullSync: parsed.data.fullSync ?? current.fullSync,
|
|
73
91
|
isEnabled: parsed.data.isEnabled ?? current.isEnabled
|
|
74
92
|
}, scope);
|
|
93
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
94
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
95
|
+
tenantId: auth.tenantId,
|
|
96
|
+
organizationId: scope.organizationId,
|
|
97
|
+
userId: auth.sub,
|
|
98
|
+
resourceKind: "data_sync.schedule",
|
|
99
|
+
resourceId: schedule.id,
|
|
100
|
+
operation: "update",
|
|
101
|
+
requestMethod: req.method,
|
|
102
|
+
requestHeaders: req.headers,
|
|
103
|
+
metadata: guardResult.metadata ?? null
|
|
104
|
+
});
|
|
105
|
+
}
|
|
75
106
|
return NextResponse.json(serializeSchedule(schedule));
|
|
76
107
|
} catch (error) {
|
|
77
108
|
const message = error instanceof Error ? error.message : "Failed to update sync schedule";
|
|
@@ -90,13 +121,38 @@ async function DELETE(req, ctx) {
|
|
|
90
121
|
}
|
|
91
122
|
const container = await createRequestContainer();
|
|
92
123
|
const scheduleService = container.resolve("dataSyncScheduleService");
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
tenantId: auth.tenantId
|
|
124
|
+
const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
|
|
125
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
126
|
+
tenantId: auth.tenantId,
|
|
127
|
+
organizationId: scope.organizationId,
|
|
128
|
+
userId: auth.sub,
|
|
129
|
+
resourceKind: "data_sync.schedule",
|
|
130
|
+
resourceId: parsedParams.data.id,
|
|
131
|
+
operation: "delete",
|
|
132
|
+
requestMethod: req.method,
|
|
133
|
+
requestHeaders: req.headers,
|
|
134
|
+
mutationPayload: null
|
|
96
135
|
});
|
|
136
|
+
if (guardResult && !guardResult.ok) {
|
|
137
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
138
|
+
}
|
|
139
|
+
const deleted = await scheduleService.deleteSchedule(parsedParams.data.id, scope);
|
|
97
140
|
if (!deleted) {
|
|
98
141
|
return NextResponse.json({ error: "Schedule not found" }, { status: 404 });
|
|
99
142
|
}
|
|
143
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
144
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
145
|
+
tenantId: auth.tenantId,
|
|
146
|
+
organizationId: scope.organizationId,
|
|
147
|
+
userId: auth.sub,
|
|
148
|
+
resourceKind: "data_sync.schedule",
|
|
149
|
+
resourceId: parsedParams.data.id,
|
|
150
|
+
operation: "delete",
|
|
151
|
+
requestMethod: req.method,
|
|
152
|
+
requestHeaders: req.headers,
|
|
153
|
+
metadata: guardResult.metadata ?? null
|
|
154
|
+
});
|
|
155
|
+
}
|
|
100
156
|
return NextResponse.json({ deleted: true });
|
|
101
157
|
}
|
|
102
158
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/data_sync/api/schedules/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { updateSyncScheduleSchema } from '../../../data/validators'\nimport type { SyncScheduleService } from '../../../lib/sync-schedule-service'\nimport { serializeSchedule } from '../serialize'\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n PUT: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n DELETE: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Manage a sync schedule',\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid schedule id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const schedule = await scheduleService.getById(parsedParams.data.id, {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n if (!schedule) {\n return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })\n }\n\n return NextResponse.json(serializeSchedule(schedule))\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid schedule id' }, { status: 400 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = updateSyncScheduleSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n const current = await scheduleService.getById(parsedParams.data.id, scope)\n\n if (!current) {\n return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })\n }\n\n try {\n const schedule = await scheduleService.saveSchedule({\n id: current.id,\n integrationId: parsed.data.integrationId ?? current.integrationId,\n entityType: parsed.data.entityType ?? current.entityType,\n direction: parsed.data.direction ?? current.direction,\n scheduleType: parsed.data.scheduleType ?? current.scheduleType,\n scheduleValue: parsed.data.scheduleValue ?? current.scheduleValue,\n timezone: parsed.data.timezone ?? current.timezone,\n fullSync: parsed.data.fullSync ?? current.fullSync,\n isEnabled: parsed.data.isEnabled ?? current.isEnabled,\n }, scope)\n return NextResponse.json(serializeSchedule(schedule))\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to update sync schedule'\n return NextResponse.json({ error: message }, { status: 422 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid schedule id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AAEzC,SAAS,yBAAyB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { updateSyncScheduleSchema } from '../../../data/validators'\nimport type { SyncScheduleService } from '../../../lib/sync-schedule-service'\nimport { serializeSchedule } from '../serialize'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n PUT: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n DELETE: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Manage a sync schedule',\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid schedule id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const schedule = await scheduleService.getById(parsedParams.data.id, {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n if (!schedule) {\n return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })\n }\n\n return NextResponse.json(serializeSchedule(schedule))\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid schedule id' }, { status: 400 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = updateSyncScheduleSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n const current = await scheduleService.getById(parsedParams.data.id, scope)\n\n if (!current) {\n return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.schedule',\n resourceId: current.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n try {\n const schedule = await scheduleService.saveSchedule({\n id: current.id,\n integrationId: parsed.data.integrationId ?? current.integrationId,\n entityType: parsed.data.entityType ?? current.entityType,\n direction: parsed.data.direction ?? current.direction,\n scheduleType: parsed.data.scheduleType ?? current.scheduleType,\n scheduleValue: parsed.data.scheduleValue ?? current.scheduleValue,\n timezone: parsed.data.timezone ?? current.timezone,\n fullSync: parsed.data.fullSync ?? current.fullSync,\n isEnabled: parsed.data.isEnabled ?? current.isEnabled,\n }, scope)\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.schedule',\n resourceId: schedule.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json(serializeSchedule(schedule))\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to update sync schedule'\n return NextResponse.json({ error: message }, { status: 422 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = paramsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid schedule id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.schedule',\n resourceId: parsedParams.data.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const deleted = await scheduleService.deleteSchedule(parsedParams.data.id, scope)\n\n if (!deleted) {\n return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.schedule',\n resourceId: parsedParams.data.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ deleted: true })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,6 +6,10 @@ import { createSyncScheduleSchema, listSyncSchedulesQuerySchema } from "../../da
|
|
|
6
6
|
import { serializeSchedule } from "./serialize.js";
|
|
7
7
|
import { readOptimisticLockExpected } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
8
8
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
9
|
+
import {
|
|
10
|
+
runCrudMutationGuardAfterSuccess,
|
|
11
|
+
validateCrudMutationGuard
|
|
12
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
9
13
|
const metadata = {
|
|
10
14
|
GET: { requireAuth: true, requireFeatures: ["data_sync.configure"] },
|
|
11
15
|
POST: { requireAuth: true, requireFeatures: ["data_sync.configure"] }
|
|
@@ -54,14 +58,39 @@ async function POST(req) {
|
|
|
54
58
|
}
|
|
55
59
|
const container = await createRequestContainer();
|
|
56
60
|
const scheduleService = container.resolve("dataSyncScheduleService");
|
|
61
|
+
const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
|
|
62
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
63
|
+
tenantId: auth.tenantId,
|
|
64
|
+
organizationId: scope.organizationId,
|
|
65
|
+
userId: auth.sub,
|
|
66
|
+
resourceKind: "data_sync.schedule",
|
|
67
|
+
resourceId: scope.organizationId,
|
|
68
|
+
operation: "create",
|
|
69
|
+
requestMethod: req.method,
|
|
70
|
+
requestHeaders: req.headers,
|
|
71
|
+
mutationPayload: parsed.data
|
|
72
|
+
});
|
|
73
|
+
if (guardResult && !guardResult.ok) {
|
|
74
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
75
|
+
}
|
|
57
76
|
try {
|
|
58
77
|
const schedule = await scheduleService.saveSchedule({
|
|
59
78
|
...parsed.data,
|
|
60
79
|
expectedUpdatedAt: readOptimisticLockExpected(req)
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
}, scope);
|
|
81
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
82
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
83
|
+
tenantId: auth.tenantId,
|
|
84
|
+
organizationId: scope.organizationId,
|
|
85
|
+
userId: auth.sub,
|
|
86
|
+
resourceKind: "data_sync.schedule",
|
|
87
|
+
resourceId: schedule.id,
|
|
88
|
+
operation: "create",
|
|
89
|
+
requestMethod: req.method,
|
|
90
|
+
requestHeaders: req.headers,
|
|
91
|
+
metadata: guardResult.metadata ?? null
|
|
92
|
+
});
|
|
93
|
+
}
|
|
65
94
|
return NextResponse.json(serializeSchedule(schedule), { status: 201 });
|
|
66
95
|
} catch (error) {
|
|
67
96
|
if (isCrudHttpError(error)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/data_sync/api/schedules/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { createSyncScheduleSchema, listSyncSchedulesQuerySchema } from '../../data/validators'\nimport type { SyncScheduleService } from '../../lib/sync-schedule-service'\nimport { serializeSchedule } from './serialize'\nimport { readOptimisticLockExpected } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'List or create sync schedules',\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listSyncSchedulesQuerySchema.safeParse({\n integrationId: url.searchParams.get('integrationId') ?? undefined,\n entityType: url.searchParams.get('entityType') ?? undefined,\n direction: url.searchParams.get('direction') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n const { items, total } = await scheduleService.listSchedules(parsed.data, scope)\n\n return NextResponse.json({\n items: items.map(serializeSchedule),\n total,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n totalPages: Math.max(1, Math.ceil(total / parsed.data.pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = createSyncScheduleSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n\n try {\n const schedule = await scheduleService.saveSchedule({\n ...parsed.data,\n expectedUpdatedAt: readOptimisticLockExpected(req),\n }, {\n organizationId: auth.
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B,oCAAoC;AAEvE,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAC3C,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { createSyncScheduleSchema, listSyncSchedulesQuerySchema } from '../../data/validators'\nimport type { SyncScheduleService } from '../../lib/sync-schedule-service'\nimport { serializeSchedule } from './serialize'\nimport { readOptimisticLockExpected } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'List or create sync schedules',\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listSyncSchedulesQuerySchema.safeParse({\n integrationId: url.searchParams.get('integrationId') ?? undefined,\n entityType: url.searchParams.get('entityType') ?? undefined,\n direction: url.searchParams.get('direction') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n const { items, total } = await scheduleService.listSchedules(parsed.data, scope)\n\n return NextResponse.json({\n items: items.map(serializeSchedule),\n total,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n totalPages: Math.max(1, Math.ceil(total / parsed.data.pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = createSyncScheduleSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.schedule',\n resourceId: scope.organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n try {\n const schedule = await scheduleService.saveSchedule({\n ...parsed.data,\n expectedUpdatedAt: readOptimisticLockExpected(req),\n }, scope)\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.schedule',\n resourceId: schedule.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json(serializeSchedule(schedule), { status: 201 })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n const message = error instanceof Error ? error.message : 'Failed to save sync schedule'\n return NextResponse.json({ error: message }, { status: 422 })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,11 @@ import { runWithCacheTenant } from "@open-mercato/cache";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
6
6
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import {
|
|
8
|
+
runCrudMutationGuardAfterSuccess,
|
|
9
|
+
validateCrudMutationGuard
|
|
10
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
11
|
+
import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
7
12
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
8
13
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
9
14
|
import { Organization } from "@open-mercato/core/modules/directory/data/entities";
|
|
@@ -18,7 +23,8 @@ const brandingResponseSchema = z.object({
|
|
|
18
23
|
organizationId: z.string().uuid(),
|
|
19
24
|
organizationName: z.string(),
|
|
20
25
|
tenantId: z.string().uuid(),
|
|
21
|
-
logoUrl: z.string().nullable()
|
|
26
|
+
logoUrl: z.string().nullable(),
|
|
27
|
+
updatedAt: z.string().nullable()
|
|
22
28
|
});
|
|
23
29
|
const brandingUpdateSchema = z.object({
|
|
24
30
|
logoUrl: organizationUpdateSchema.shape.logoUrl
|
|
@@ -84,12 +90,24 @@ async function resolveCurrentOrganization(req) {
|
|
|
84
90
|
}
|
|
85
91
|
return { auth, container, organization, organizationId, tenantId, translate };
|
|
86
92
|
}
|
|
93
|
+
function toIsoOrNull(value) {
|
|
94
|
+
if (value == null) return null;
|
|
95
|
+
if (value instanceof Date) {
|
|
96
|
+
const ms = value.getTime();
|
|
97
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
|
|
98
|
+
}
|
|
99
|
+
const trimmed = String(value).trim();
|
|
100
|
+
if (!trimmed) return null;
|
|
101
|
+
const parsed = new Date(trimmed);
|
|
102
|
+
return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : null;
|
|
103
|
+
}
|
|
87
104
|
function toResponsePayload(organization, tenantId) {
|
|
88
105
|
return {
|
|
89
106
|
organizationId: String(organization.id),
|
|
90
107
|
organizationName: organization.name,
|
|
91
108
|
tenantId,
|
|
92
|
-
logoUrl: organization.logoUrl ?? null
|
|
109
|
+
logoUrl: organization.logoUrl ?? null,
|
|
110
|
+
updatedAt: toIsoOrNull(organization.updatedAt)
|
|
93
111
|
};
|
|
94
112
|
}
|
|
95
113
|
async function invalidateSidebarBrandingCache(container, organizationId, tenantId) {
|
|
@@ -139,6 +157,26 @@ async function PUT(req) {
|
|
|
139
157
|
);
|
|
140
158
|
}
|
|
141
159
|
try {
|
|
160
|
+
enforceCommandOptimisticLock({
|
|
161
|
+
resourceKind: "directory.organization",
|
|
162
|
+
resourceId: resolved.organizationId,
|
|
163
|
+
current: resolved.organization.updatedAt ?? null,
|
|
164
|
+
request: req
|
|
165
|
+
});
|
|
166
|
+
const guardResult = await validateCrudMutationGuard(resolved.container, {
|
|
167
|
+
tenantId: resolved.tenantId,
|
|
168
|
+
organizationId: resolved.organizationId,
|
|
169
|
+
userId: resolved.auth.sub,
|
|
170
|
+
resourceKind: "directory.organization",
|
|
171
|
+
resourceId: resolved.organizationId,
|
|
172
|
+
operation: "update",
|
|
173
|
+
requestMethod: req.method,
|
|
174
|
+
requestHeaders: req.headers,
|
|
175
|
+
mutationPayload: { logoUrl: parsed.data.logoUrl ?? null }
|
|
176
|
+
});
|
|
177
|
+
if (guardResult && !guardResult.ok) {
|
|
178
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
179
|
+
}
|
|
142
180
|
const commandBus = resolved.container.resolve("commandBus");
|
|
143
181
|
const ctx = buildCommandContext(
|
|
144
182
|
resolved.container,
|
|
@@ -158,6 +196,19 @@ async function PUT(req) {
|
|
|
158
196
|
ctx
|
|
159
197
|
}
|
|
160
198
|
);
|
|
199
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
200
|
+
await runCrudMutationGuardAfterSuccess(resolved.container, {
|
|
201
|
+
tenantId: resolved.tenantId,
|
|
202
|
+
organizationId: resolved.organizationId,
|
|
203
|
+
userId: resolved.auth.sub,
|
|
204
|
+
resourceKind: "directory.organization",
|
|
205
|
+
resourceId: resolved.organizationId,
|
|
206
|
+
operation: "update",
|
|
207
|
+
requestMethod: req.method,
|
|
208
|
+
requestHeaders: req.headers,
|
|
209
|
+
metadata: guardResult.metadata ?? null
|
|
210
|
+
});
|
|
211
|
+
}
|
|
161
212
|
await invalidateSidebarBrandingCache(resolved.container, resolved.organizationId, resolved.tenantId);
|
|
162
213
|
return NextResponse.json(toResponsePayload(result, resolved.tenantId));
|
|
163
214
|
} catch (err) {
|
|
@@ -200,6 +251,7 @@ const openApi = {
|
|
|
200
251
|
errors: [
|
|
201
252
|
{ status: 400, description: "Save failed", schema: errorSchema },
|
|
202
253
|
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
254
|
+
{ status: 409, description: "Organization branding changed since it was loaded", schema: errorSchema },
|
|
203
255
|
{ status: 422, description: "Invalid logo URL", schema: errorSchema }
|
|
204
256
|
]
|
|
205
257
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/directory/api/organization-branding/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport '@open-mercato/core/modules/directory/commands/organizations'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['directory.organizations.view'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n}\n\nconst brandingResponseSchema = z.object({\n organizationId: z.string().uuid(),\n organizationName: z.string(),\n tenantId: z.string().uuid(),\n logoUrl: z.string().nullable(),\n})\n\nconst brandingUpdateSchema = z.object({\n logoUrl: organizationUpdateSchema.shape.logoUrl,\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\ntype RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>\n\nfunction buildCommandContext(\n container: RequestContainer,\n auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>,\n req: Request,\n organizationId: string,\n tenantId: string,\n): CommandRuntimeContext {\n return {\n container,\n auth,\n organizationScope: {\n selectedId: organizationId,\n filterIds: [organizationId],\n allowedIds: null,\n tenantId,\n },\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n request: req,\n }\n}\n\nasync function resolveCurrentOrganization(req: Request) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return {\n response: NextResponse.json({ error: translate('api.errors.unauthorized', 'Unauthorized') }, { status: 401 }),\n }\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope.selectedId ?? auth.orgId ?? null\n const tenantId = scope.tenantId ?? auth.tenantId ?? null\n if (!organizationId || !tenantId) {\n return {\n response: NextResponse.json(\n {\n error: translate(\n 'directory.branding.errors.organizationRequired',\n 'Select a single organization before changing sidebar branding.',\n ),\n },\n { status: 400 },\n ),\n }\n }\n\n const em = container.resolve('em') as EntityManager\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId, tenant: tenantId, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId, organizationId },\n )\n if (!organization) {\n return {\n response: NextResponse.json(\n { error: translate('directory.branding.errors.notFound', 'Organization not found') },\n { status: 404 },\n ),\n }\n }\n\n return { auth, container, organization, organizationId, tenantId, translate }\n}\n\nfunction toResponsePayload(organization: Organization, tenantId: string) {\n return {\n organizationId: String(organization.id),\n organizationName: organization.name,\n tenantId,\n logoUrl: organization.logoUrl ?? null,\n }\n}\n\nasync function invalidateSidebarBrandingCache(container: RequestContainer, organizationId: string, tenantId: string) {\n try {\n const cache = container.resolve('cache') as {\n deleteByTags?: (tags: string[]) => Promise<unknown>\n } | null\n await runWithCacheTenant(tenantId, () =>\n cache?.deleteByTags?.([\n `nav:sidebar:organization:${organizationId}`,\n `nav:sidebar:tenant:${tenantId}`,\n ]),\n )\n } catch {\n // Cache invalidation is best-effort; the persisted branding is the source of truth.\n }\n}\n\nexport async function GET(req: Request) {\n const resolved = await resolveCurrentOrganization(req)\n if ('response' in resolved) return resolved.response\n\n return NextResponse.json(toResponsePayload(resolved.organization, resolved.tenantId))\n}\n\nexport async function PUT(req: Request) {\n const resolved = await resolveCurrentOrganization(req)\n if ('response' in resolved) return resolved.response\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.') },\n { status: 422 },\n )\n }\n if (!body || typeof body !== 'object' || !Object.prototype.hasOwnProperty.call(body, 'logoUrl')) {\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.') },\n { status: 422 },\n )\n }\n\n const parsed = brandingUpdateSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n {\n error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.'),\n issues: parsed.error.issues,\n },\n { status: 422 },\n )\n }\n\n try {\n const commandBus = resolved.container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(\n resolved.container,\n resolved.auth,\n req,\n resolved.organizationId,\n resolved.tenantId,\n )\n const { result } = await commandBus.execute<Record<string, unknown>, Organization>(\n 'directory.organizations.update',\n {\n input: {\n id: resolved.organizationId,\n tenantId: resolved.tenantId,\n logoUrl: parsed.data.logoUrl ?? null,\n },\n ctx,\n },\n )\n await invalidateSidebarBrandingCache(resolved.container, resolved.organizationId, resolved.tenantId)\n return NextResponse.json(toResponsePayload(result, resolved.tenantId))\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('directory.organization-branding.update failed', err)\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.save', 'Failed to update organization branding.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Directory',\n summary: 'Current organization branding',\n methods: {\n GET: {\n summary: 'Read sidebar branding for the selected organization',\n description: 'Returns the logo URL used by the backend sidebar for the currently selected organization.',\n responses: [\n { status: 200, description: 'Organization branding', schema: brandingResponseSchema },\n ],\n errors: [\n { status: 400, description: 'A concrete organization scope is required', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Organization not found', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update sidebar branding for the selected organization',\n description: 'Stores an external image URL or an internal attachment image URL as the selected organization logo.',\n requestBody: {\n contentType: 'application/json',\n schema: brandingUpdateSchema,\n },\n responses: [\n { status: 200, description: 'Updated organization branding', schema: brandingResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Save failed', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 422, description: 'Invalid logo URL', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AAGnC,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AACzC,SAAS,0CAA0C;AACnD,OAAO;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,8BAA8B,EAAE;AAAA,EAC5E,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,gCAAgC,EAAE;AAChF;AAEA,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,EAChC,kBAAkB,EAAE,OAAO;AAAA,EAC3B,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,SAAS,EAAE,OAAO,EAAE,SAAS;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport '@open-mercato/core/modules/directory/commands/organizations'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['directory.organizations.view'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n}\n\nconst brandingResponseSchema = z.object({\n organizationId: z.string().uuid(),\n organizationName: z.string(),\n tenantId: z.string().uuid(),\n logoUrl: z.string().nullable(),\n updatedAt: z.string().nullable(),\n})\n\nconst brandingUpdateSchema = z.object({\n logoUrl: organizationUpdateSchema.shape.logoUrl,\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\ntype RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>\n\nfunction buildCommandContext(\n container: RequestContainer,\n auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>,\n req: Request,\n organizationId: string,\n tenantId: string,\n): CommandRuntimeContext {\n return {\n container,\n auth,\n organizationScope: {\n selectedId: organizationId,\n filterIds: [organizationId],\n allowedIds: null,\n tenantId,\n },\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n request: req,\n }\n}\n\nasync function resolveCurrentOrganization(req: Request) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return {\n response: NextResponse.json({ error: translate('api.errors.unauthorized', 'Unauthorized') }, { status: 401 }),\n }\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope.selectedId ?? auth.orgId ?? null\n const tenantId = scope.tenantId ?? auth.tenantId ?? null\n if (!organizationId || !tenantId) {\n return {\n response: NextResponse.json(\n {\n error: translate(\n 'directory.branding.errors.organizationRequired',\n 'Select a single organization before changing sidebar branding.',\n ),\n },\n { status: 400 },\n ),\n }\n }\n\n const em = container.resolve('em') as EntityManager\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId, tenant: tenantId, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId, organizationId },\n )\n if (!organization) {\n return {\n response: NextResponse.json(\n { error: translate('directory.branding.errors.notFound', 'Organization not found') },\n { status: 404 },\n ),\n }\n }\n\n return { auth, container, organization, organizationId, tenantId, translate }\n}\n\nfunction toIsoOrNull(value: Date | string | null | undefined): string | null {\n if (value == null) return null\n if (value instanceof Date) {\n const ms = value.getTime()\n return Number.isFinite(ms) ? new Date(ms).toISOString() : null\n }\n const trimmed = String(value).trim()\n if (!trimmed) return null\n const parsed = new Date(trimmed)\n return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : null\n}\n\nfunction toResponsePayload(organization: Organization, tenantId: string) {\n return {\n organizationId: String(organization.id),\n organizationName: organization.name,\n tenantId,\n logoUrl: organization.logoUrl ?? null,\n updatedAt: toIsoOrNull(organization.updatedAt),\n }\n}\n\nasync function invalidateSidebarBrandingCache(container: RequestContainer, organizationId: string, tenantId: string) {\n try {\n const cache = container.resolve('cache') as {\n deleteByTags?: (tags: string[]) => Promise<unknown>\n } | null\n await runWithCacheTenant(tenantId, () =>\n cache?.deleteByTags?.([\n `nav:sidebar:organization:${organizationId}`,\n `nav:sidebar:tenant:${tenantId}`,\n ]),\n )\n } catch {\n // Cache invalidation is best-effort; the persisted branding is the source of truth.\n }\n}\n\nexport async function GET(req: Request) {\n const resolved = await resolveCurrentOrganization(req)\n if ('response' in resolved) return resolved.response\n\n return NextResponse.json(toResponsePayload(resolved.organization, resolved.tenantId))\n}\n\nexport async function PUT(req: Request) {\n const resolved = await resolveCurrentOrganization(req)\n if ('response' in resolved) return resolved.response\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.') },\n { status: 422 },\n )\n }\n if (!body || typeof body !== 'object' || !Object.prototype.hasOwnProperty.call(body, 'logoUrl')) {\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.') },\n { status: 422 },\n )\n }\n\n const parsed = brandingUpdateSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n {\n error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.'),\n issues: parsed.error.issues,\n },\n { status: 422 },\n )\n }\n\n try {\n // Refuse a stale overwrite when two tabs edit the same organization branding.\n // Strictly additive \u2014 a no-op when the client omits the expected-version header.\n enforceCommandOptimisticLock({\n resourceKind: 'directory.organization',\n resourceId: resolved.organizationId,\n current: resolved.organization.updatedAt ?? null,\n request: req,\n })\n\n // Mutation-guard contract for custom write routes: validate before the write,\n // then run the after-success hook once the command persists.\n const guardResult = await validateCrudMutationGuard(resolved.container, {\n tenantId: resolved.tenantId,\n organizationId: resolved.organizationId,\n userId: resolved.auth.sub,\n resourceKind: 'directory.organization',\n resourceId: resolved.organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { logoUrl: parsed.data.logoUrl ?? null },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = resolved.container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(\n resolved.container,\n resolved.auth,\n req,\n resolved.organizationId,\n resolved.tenantId,\n )\n const { result } = await commandBus.execute<Record<string, unknown>, Organization>(\n 'directory.organizations.update',\n {\n input: {\n id: resolved.organizationId,\n tenantId: resolved.tenantId,\n logoUrl: parsed.data.logoUrl ?? null,\n },\n ctx,\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(resolved.container, {\n tenantId: resolved.tenantId,\n organizationId: resolved.organizationId,\n userId: resolved.auth.sub,\n resourceKind: 'directory.organization',\n resourceId: resolved.organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n await invalidateSidebarBrandingCache(resolved.container, resolved.organizationId, resolved.tenantId)\n return NextResponse.json(toResponsePayload(result, resolved.tenantId))\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('directory.organization-branding.update failed', err)\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.save', 'Failed to update organization branding.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Directory',\n summary: 'Current organization branding',\n methods: {\n GET: {\n summary: 'Read sidebar branding for the selected organization',\n description: 'Returns the logo URL used by the backend sidebar for the currently selected organization.',\n responses: [\n { status: 200, description: 'Organization branding', schema: brandingResponseSchema },\n ],\n errors: [\n { status: 400, description: 'A concrete organization scope is required', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Organization not found', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update sidebar branding for the selected organization',\n description: 'Stores an external image URL or an internal attachment image URL as the selected organization logo.',\n requestBody: {\n contentType: 'application/json',\n schema: brandingUpdateSchema,\n },\n responses: [\n { status: 200, description: 'Updated organization branding', schema: brandingResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Save failed', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 409, description: 'Organization branding changed since it was loaded', schema: errorSchema },\n { status: 422, description: 'Invalid logo URL', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,7 +7,9 @@ import { Page, PageBody, PageHeader } from "@open-mercato/ui/backend/Page";
|
|
|
7
7
|
import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
8
8
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
9
9
|
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
10
|
-
import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
10
|
+
import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
|
+
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
12
|
+
import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
|
|
11
13
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
12
14
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
13
15
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -73,14 +75,17 @@ function OrganizationBrandingPage() {
|
|
|
73
75
|
operation: async () => {
|
|
74
76
|
const uploadedLogoUrl = shouldUpload ? await uploadLogo(data.organizationId) : null;
|
|
75
77
|
const resolvedLogoUrl = uploadedLogoUrl ?? nextLogoUrl ?? logoUrl.trim();
|
|
76
|
-
const response = await
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const response = await withScopedApiRequestHeaders(
|
|
79
|
+
buildOptimisticLockHeader(data.updatedAt),
|
|
80
|
+
() => apiCallOrThrow(
|
|
81
|
+
BRANDING_API,
|
|
82
|
+
{
|
|
83
|
+
method: "PUT",
|
|
84
|
+
headers: { "content-type": "application/json" },
|
|
85
|
+
body: JSON.stringify({ logoUrl: resolvedLogoUrl || null })
|
|
86
|
+
},
|
|
87
|
+
{ errorMessage: t("directory.branding.errors.save", "Failed to update organization branding") }
|
|
88
|
+
)
|
|
84
89
|
);
|
|
85
90
|
return response.result;
|
|
86
91
|
},
|
|
@@ -101,6 +106,7 @@ function OrganizationBrandingPage() {
|
|
|
101
106
|
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
102
107
|
flash(t("directory.branding.flash.saved", "Organization branding updated"), "success");
|
|
103
108
|
} catch (err) {
|
|
109
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
104
110
|
const fallback = t("directory.branding.errors.save", "Failed to update organization branding");
|
|
105
111
|
const message = err instanceof Error ? err.message : fallback;
|
|
106
112
|
flash(message, "error");
|