@open-mercato/core 0.6.6-develop.6204.1.30b1f58642 → 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/dictionaries/components/DictionariesManager.js +7 -4
- package/dist/modules/dictionaries/components/DictionariesManager.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/dictionaries/components/DictionariesManager.tsx +7 -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/entities/api/definitions.restore.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { invalidateDefinitionsCache } from './definitions.cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const { entityId, key } = body || {}\n if (!entityId || !key) return NextResponse.json({ error: 'entityId and key are required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n let cache: CacheStrategy | undefined\n try {\n cache = resolve('cache') as CacheStrategy\n } catch {}\n\n const where: any = { entityId, key, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n const def = await em.findOne(CustomFieldDef, where)\n if (!def) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n ;(def as any).deletedAt = null\n ;(def as any).isActive = true\n ;(def as any).updatedAt = new Date()\n em.persist(def)\n await em.flush()\n await invalidateDefinitionsCache(cache, {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n entityIds: [entityId],\n })\n return NextResponse.json({ ok: true })\n}\n\nconst restoreDefinitionRequestSchema = z.object({\n entityId: z.string(),\n key: z.string(),\n})\n\nconst restoreDefinitionResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Restore soft-deleted custom field definitions',\n methods: {\n POST: {\n summary: 'Restore definition',\n description: 'Reactivates a previously soft-deleted definition within the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: restoreDefinitionRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Definition restored',\n schema: restoreDefinitionResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id or key',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 404,\n description: 'Definition not found',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAC/B,SAAS,kCAAkC;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { invalidateDefinitionsCache } from './definitions.cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n beginEntitiesMutationGuard,\n FIELD_DEFINITION_RESOURCE_KIND,\n} from './definitions.mutation-guard'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const { entityId, key } = body || {}\n if (!entityId || !key) return NextResponse.json({ error: 'entityId and key are required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n let cache: CacheStrategy | undefined\n try {\n cache = resolve('cache') as CacheStrategy\n } catch {}\n\n const where: any = { entityId, key, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n const def = await em.findOne(CustomFieldDef, where)\n if (!def) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: FIELD_DEFINITION_RESOURCE_KIND,\n resourceId: def.id,\n operation: 'custom',\n mutationPayload: { entityId, key },\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n ;(def as any).deletedAt = null\n ;(def as any).isActive = true\n ;(def as any).updatedAt = new Date()\n em.persist(def)\n await em.flush()\n await guard.runAfterSuccess()\n await invalidateDefinitionsCache(cache, {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n entityIds: [entityId],\n })\n return NextResponse.json({ ok: true })\n}\n\nconst restoreDefinitionRequestSchema = z.object({\n entityId: z.string(),\n key: z.string(),\n})\n\nconst restoreDefinitionResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Restore soft-deleted custom field definitions',\n methods: {\n POST: {\n summary: 'Restore definition',\n description: 'Reactivates a previously soft-deleted definition within the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: restoreDefinitionRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Definition restored',\n schema: restoreDefinitionResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id or key',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 404,\n description: 'Definition not found',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,7 +9,11 @@ import { isSystemEntitySelectable } from "@open-mercato/shared/lib/entities/syst
|
|
|
9
9
|
import { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from "@open-mercato/shared/lib/data/engine";
|
|
10
10
|
import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
11
11
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
12
|
-
|
|
12
|
+
import {
|
|
13
|
+
beginEntitiesMutationGuard,
|
|
14
|
+
ENTITY_DEFINITION_RESOURCE_KIND
|
|
15
|
+
} from "./definitions.mutation-guard.js";
|
|
16
|
+
const CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND = ENTITY_DEFINITION_RESOURCE_KIND;
|
|
13
17
|
const metadata = {
|
|
14
18
|
GET: { requireAuth: true },
|
|
15
19
|
POST: { requireAuth: true, requireFeatures: ["entities.definitions.manage"] },
|
|
@@ -93,7 +97,8 @@ async function POST(req) {
|
|
|
93
97
|
return NextResponse.json({ error: "Validation failed", details: parsed.error.flatten() }, { status: 400 });
|
|
94
98
|
}
|
|
95
99
|
const input = parsed.data;
|
|
96
|
-
const
|
|
100
|
+
const container = await createRequestContainer();
|
|
101
|
+
const { resolve } = container;
|
|
97
102
|
const em = resolve("em");
|
|
98
103
|
if (isOrmBackedSystemEntityId(em, input.entityId)) {
|
|
99
104
|
return NextResponse.json(
|
|
@@ -118,6 +123,16 @@ async function POST(req) {
|
|
|
118
123
|
throw lockError;
|
|
119
124
|
}
|
|
120
125
|
}
|
|
126
|
+
const guard = await beginEntitiesMutationGuard({
|
|
127
|
+
container,
|
|
128
|
+
auth,
|
|
129
|
+
req,
|
|
130
|
+
resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,
|
|
131
|
+
resourceId: ent ? ent.id : input.entityId,
|
|
132
|
+
operation: ent ? "update" : "create",
|
|
133
|
+
mutationPayload: input
|
|
134
|
+
});
|
|
135
|
+
if (guard.blockedResponse) return guard.blockedResponse;
|
|
121
136
|
if (!ent) ent = em.create(CustomEntity, { ...where, createdAt: /* @__PURE__ */ new Date() });
|
|
122
137
|
ent.label = input.label;
|
|
123
138
|
ent.description = input.description ?? null;
|
|
@@ -128,6 +143,7 @@ async function POST(req) {
|
|
|
128
143
|
ent.updatedAt = /* @__PURE__ */ new Date();
|
|
129
144
|
em.persist(ent);
|
|
130
145
|
await em.flush();
|
|
146
|
+
await guard.runAfterSuccess();
|
|
131
147
|
try {
|
|
132
148
|
const cache = (await createRequestContainer()).resolve("cache");
|
|
133
149
|
if (cache) {
|
|
@@ -148,16 +164,28 @@ async function DELETE(req) {
|
|
|
148
164
|
}
|
|
149
165
|
const entityId = body?.entityId;
|
|
150
166
|
if (!entityId) return NextResponse.json({ error: "entityId is required" }, { status: 400 });
|
|
151
|
-
const
|
|
167
|
+
const container = await createRequestContainer();
|
|
168
|
+
const { resolve } = container;
|
|
152
169
|
const em = resolve("em");
|
|
153
170
|
const where = { entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null };
|
|
154
171
|
const ent = await em.findOne(CustomEntity, where);
|
|
155
172
|
if (!ent) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
173
|
+
const guard = await beginEntitiesMutationGuard({
|
|
174
|
+
container,
|
|
175
|
+
auth,
|
|
176
|
+
req,
|
|
177
|
+
resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,
|
|
178
|
+
resourceId: ent.id,
|
|
179
|
+
operation: "delete",
|
|
180
|
+
mutationPayload: { entityId }
|
|
181
|
+
});
|
|
182
|
+
if (guard.blockedResponse) return guard.blockedResponse;
|
|
156
183
|
ent.isActive = false;
|
|
157
184
|
ent.updatedAt = /* @__PURE__ */ new Date();
|
|
158
185
|
ent.deletedAt = ent.deletedAt ?? /* @__PURE__ */ new Date();
|
|
159
186
|
em.persist(ent);
|
|
160
187
|
await em.flush();
|
|
188
|
+
await guard.runAfterSuccess();
|
|
161
189
|
try {
|
|
162
190
|
const cache = (await createRequestContainer()).resolve("cache");
|
|
163
191
|
if (cache) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/api/entities.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CustomEntity, CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/data/validators'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\nconst CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND = 'entities.entity'\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n // Generated entities from code\n const AllEntities = getEntityIds()\n const generated: { entityId: string; source: 'code'; label: string }[] = []\n for (const modId of Object.keys(AllEntities)) {\n const entities = (AllEntities as any)[modId] as Record<string, string>\n for (const k of Object.keys(entities)) {\n const id = entities[k]\n if (!isSystemEntitySelectable(id)) continue\n generated.push({ entityId: id, source: 'code', label: id })\n }\n }\n\n // Custom user-defined entities (global/org/tenant scoped)\n const where: any = { isActive: true }\n where.$and = [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ]\n const customs = await em.find(CustomEntity as any, where as any, { orderBy: { entityId: 'asc' } as any })\n // Resolve overlay precedence: prefer organization/tenant-specific over global\n const customByEntityId = new Map<string, any>()\n for (const c of customs as any[]) {\n const specificity = (c.organizationId ? 2 : 0) + (c.tenantId ? 1 : 0)\n const prev = customByEntityId.get(c.entityId)\n const prevSpec = prev ? ((prev.organizationId ? 2 : 0) + (prev.tenantId ? 1 : 0)) : -1\n if (!prev || specificity > prevSpec) customByEntityId.set(c.entityId, c)\n }\n\n const custom = Array.from(customByEntityId.values())\n .filter((c) => isSystemEntitySelectable(c.entityId))\n .map((c) => ({\n entityId: c.entityId,\n source: 'custom' as const,\n label: c.label,\n description: c.description ?? undefined,\n labelField: (c as any).labelField ?? undefined,\n defaultEditor: (c as any).defaultEditor ?? undefined,\n showInSidebar: (c as any).showInSidebar ?? false,\n updatedAt: c.updatedAt instanceof Date ? c.updatedAt.toISOString() : (c.updatedAt ?? undefined),\n }))\n\n const byId = new Map<string, any>()\n for (const g of generated) byId.set(g.entityId, g)\n for (const cu of custom) {\n const existing = byId.get(cu.entityId)\n byId.set(cu.entityId, { ...existing, ...cu, source: existing?.source ?? cu.source })\n }\n\n // Count field definitions scoped to current tenant/org (same scoping as custom entities)\n const defsWhere: any = { isActive: true }\n defsWhere.$and = [\n //{ $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] }, // the entities and custom fields are defined per tenant\n { tenantId: auth.tenantId ?? undefined as any },\n ]\n const defs = await em.find(CustomFieldDef as any, defsWhere as any)\n // Count distinct field names (keys) per entityId\n const keySets = new Map<string, Set<string>>()\n for (const d of defs as any[]) {\n const eid = String(d.entityId)\n const k = String(d.key)\n if (!isSystemEntitySelectable(eid)) continue\n const set = keySets.get(eid) || new Set<string>()\n set.add(k)\n keySets.set(eid, set)\n }\n const counts: Record<string, number> = {}\n for (const [eid, set] of keySets.entries()) counts[eid] = set.size\n\n const items = Array.from(byId.values()).map((it: any) => ({ ...it, count: counts[it.entityId] || 0 }))\n return NextResponse.json({ items })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = upsertCustomEntitySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n }\n const input = parsed.data\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n // A registration for a module-declared, table-backed system entity would flip\n // query-engine classification to doc storage for the whole entity type (#2939's\n // failure mode via another door) \u2014 refuse to create one.\n if (isOrmBackedSystemEntityId(em, input.entityId)) {\n return NextResponse.json(\n { error: 'System entities cannot be registered as custom entities', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId: input.entityId },\n { status: 400 },\n )\n }\n\n const where: any = { entityId: input.entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n let ent = await em.findOne(CustomEntity, where)\n if (ent) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent.id,\n current: ent.updatedAt ?? null,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n }\n if (!ent) ent = em.create(CustomEntity, { ...where, createdAt: new Date() })\n ent.label = input.label\n ent.description = input.description ?? null\n ent.isActive = input.isActive ?? true\n ent.labelField = input.labelField ?? ent.labelField ?? null\n ent.defaultEditor = input.defaultEditor ?? ent.defaultEditor ?? null\n ent.showInSidebar = input.showInSidebar ?? ent.showInSidebar ?? false\n ent.updatedAt = new Date()\n em.persist(ent)\n await em.flush()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true, item: { id: ent.id, entityId: ent.entityId, label: ent.label, description: ent.description ?? undefined } })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const entityId = body?.entityId\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n const where: any = { entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n const ent = await em.findOne(CustomEntity, where)\n if (!ent) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n ent.isActive = false\n ent.updatedAt = new Date()\n ent.deletedAt = ent.deletedAt ?? new Date()\n em.persist(ent)\n await em.flush()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true })\n}\n\nconst entitySummarySchema = z.object({\n entityId: z.string(),\n source: z.enum(['code', 'custom']),\n label: z.string(),\n description: z.string().optional(),\n labelField: z.string().optional(),\n defaultEditor: z.string().optional(),\n showInSidebar: z.boolean().optional(),\n updatedAt: z.string().optional(),\n count: z.number(),\n})\n\nconst entityListResponseSchema = z.object({\n items: z.array(entitySummarySchema),\n})\n\nconst deleteEntityRequestSchema = z.object({\n entityId: z.string(),\n})\n\nconst upsertCustomEntityResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string().uuid(),\n entityId: z.string(),\n label: z.string(),\n description: z.string().optional(),\n }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Manage custom entities',\n methods: {\n GET: {\n summary: 'List available entities',\n description: 'Returns generated and custom entities scoped to the caller with field counts per entity.',\n responses: [\n {\n status: 200,\n description: 'List of entities',\n schema: entityListResponseSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n POST: {\n summary: 'Upsert custom entity',\n description: 'Creates or updates a tenant/org scoped custom entity definition.',\n requestBody: {\n contentType: 'application/json',\n schema: upsertCustomEntitySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity saved',\n schema: upsertCustomEntityResponseSchema,\n },\n {\n status: 400,\n description: 'Validation error',\n schema: z.object({\n error: z.string(),\n details: z.any().optional(),\n }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n DELETE: {\n summary: 'Soft delete custom entity',\n description: 'Marks the specified custom entity inactive within the current scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteEntityRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity deleted',\n schema: z.object({ ok: z.boolean() }),\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 404,\n description: 'Entity not found in scope',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,cAAc,sBAAsB;AAC7C,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AAEzC,SAAS,gCAAgC;AACzC,SAAS,oCAAoC,iCAAiC;AAC9E,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CustomEntity, CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/data/validators'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n beginEntitiesMutationGuard,\n ENTITY_DEFINITION_RESOURCE_KIND,\n} from './definitions.mutation-guard'\n\nconst CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND = ENTITY_DEFINITION_RESOURCE_KIND\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n // Generated entities from code\n const AllEntities = getEntityIds()\n const generated: { entityId: string; source: 'code'; label: string }[] = []\n for (const modId of Object.keys(AllEntities)) {\n const entities = (AllEntities as any)[modId] as Record<string, string>\n for (const k of Object.keys(entities)) {\n const id = entities[k]\n if (!isSystemEntitySelectable(id)) continue\n generated.push({ entityId: id, source: 'code', label: id })\n }\n }\n\n // Custom user-defined entities (global/org/tenant scoped)\n const where: any = { isActive: true }\n where.$and = [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ]\n const customs = await em.find(CustomEntity as any, where as any, { orderBy: { entityId: 'asc' } as any })\n // Resolve overlay precedence: prefer organization/tenant-specific over global\n const customByEntityId = new Map<string, any>()\n for (const c of customs as any[]) {\n const specificity = (c.organizationId ? 2 : 0) + (c.tenantId ? 1 : 0)\n const prev = customByEntityId.get(c.entityId)\n const prevSpec = prev ? ((prev.organizationId ? 2 : 0) + (prev.tenantId ? 1 : 0)) : -1\n if (!prev || specificity > prevSpec) customByEntityId.set(c.entityId, c)\n }\n\n const custom = Array.from(customByEntityId.values())\n .filter((c) => isSystemEntitySelectable(c.entityId))\n .map((c) => ({\n entityId: c.entityId,\n source: 'custom' as const,\n label: c.label,\n description: c.description ?? undefined,\n labelField: (c as any).labelField ?? undefined,\n defaultEditor: (c as any).defaultEditor ?? undefined,\n showInSidebar: (c as any).showInSidebar ?? false,\n updatedAt: c.updatedAt instanceof Date ? c.updatedAt.toISOString() : (c.updatedAt ?? undefined),\n }))\n\n const byId = new Map<string, any>()\n for (const g of generated) byId.set(g.entityId, g)\n for (const cu of custom) {\n const existing = byId.get(cu.entityId)\n byId.set(cu.entityId, { ...existing, ...cu, source: existing?.source ?? cu.source })\n }\n\n // Count field definitions scoped to current tenant/org (same scoping as custom entities)\n const defsWhere: any = { isActive: true }\n defsWhere.$and = [\n //{ $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] }, // the entities and custom fields are defined per tenant\n { tenantId: auth.tenantId ?? undefined as any },\n ]\n const defs = await em.find(CustomFieldDef as any, defsWhere as any)\n // Count distinct field names (keys) per entityId\n const keySets = new Map<string, Set<string>>()\n for (const d of defs as any[]) {\n const eid = String(d.entityId)\n const k = String(d.key)\n if (!isSystemEntitySelectable(eid)) continue\n const set = keySets.get(eid) || new Set<string>()\n set.add(k)\n keySets.set(eid, set)\n }\n const counts: Record<string, number> = {}\n for (const [eid, set] of keySets.entries()) counts[eid] = set.size\n\n const items = Array.from(byId.values()).map((it: any) => ({ ...it, count: counts[it.entityId] || 0 }))\n return NextResponse.json({ items })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = upsertCustomEntitySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n }\n const input = parsed.data\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n\n // A registration for a module-declared, table-backed system entity would flip\n // query-engine classification to doc storage for the whole entity type (#2939's\n // failure mode via another door) \u2014 refuse to create one.\n if (isOrmBackedSystemEntityId(em, input.entityId)) {\n return NextResponse.json(\n { error: 'System entities cannot be registered as custom entities', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId: input.entityId },\n { status: 400 },\n )\n }\n\n const where: any = { entityId: input.entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n let ent = await em.findOne(CustomEntity, where)\n if (ent) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent.id,\n current: ent.updatedAt ?? null,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n }\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent ? ent.id : input.entityId,\n operation: ent ? 'update' : 'create',\n mutationPayload: input as unknown as Record<string, unknown>,\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n if (!ent) ent = em.create(CustomEntity, { ...where, createdAt: new Date() })\n ent.label = input.label\n ent.description = input.description ?? null\n ent.isActive = input.isActive ?? true\n ent.labelField = input.labelField ?? ent.labelField ?? null\n ent.defaultEditor = input.defaultEditor ?? ent.defaultEditor ?? null\n ent.showInSidebar = input.showInSidebar ?? ent.showInSidebar ?? false\n ent.updatedAt = new Date()\n em.persist(ent)\n await em.flush()\n await guard.runAfterSuccess()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true, item: { id: ent.id, entityId: ent.entityId, label: ent.label, description: ent.description ?? undefined } })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n let body: any\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const entityId = body?.entityId\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n\n const where: any = { entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n const ent = await em.findOne(CustomEntity, where)\n if (!ent) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: ENTITY_DEFINITION_RESOURCE_KIND,\n resourceId: ent.id,\n operation: 'delete',\n mutationPayload: { entityId },\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n ent.isActive = false\n ent.updatedAt = new Date()\n ent.deletedAt = ent.deletedAt ?? new Date()\n em.persist(ent)\n await em.flush()\n await guard.runAfterSuccess()\n // Invalidate sidebar/nav cache for tenant scope (also when tenantId is null)\n try {\n const cache = (await createRequestContainer()).resolve('cache') as any\n if (cache) {\n await cache.deleteByTags([`nav:entities:${auth.tenantId || 'null'}`])\n }\n } catch {}\n return NextResponse.json({ ok: true })\n}\n\nconst entitySummarySchema = z.object({\n entityId: z.string(),\n source: z.enum(['code', 'custom']),\n label: z.string(),\n description: z.string().optional(),\n labelField: z.string().optional(),\n defaultEditor: z.string().optional(),\n showInSidebar: z.boolean().optional(),\n updatedAt: z.string().optional(),\n count: z.number(),\n})\n\nconst entityListResponseSchema = z.object({\n items: z.array(entitySummarySchema),\n})\n\nconst deleteEntityRequestSchema = z.object({\n entityId: z.string(),\n})\n\nconst upsertCustomEntityResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string().uuid(),\n entityId: z.string(),\n label: z.string(),\n description: z.string().optional(),\n }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Manage custom entities',\n methods: {\n GET: {\n summary: 'List available entities',\n description: 'Returns generated and custom entities scoped to the caller with field counts per entity.',\n responses: [\n {\n status: 200,\n description: 'List of entities',\n schema: entityListResponseSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n POST: {\n summary: 'Upsert custom entity',\n description: 'Creates or updates a tenant/org scoped custom entity definition.',\n requestBody: {\n contentType: 'application/json',\n schema: upsertCustomEntitySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity saved',\n schema: upsertCustomEntityResponseSchema,\n },\n {\n status: 400,\n description: 'Validation error',\n schema: z.object({\n error: z.string(),\n details: z.any().optional(),\n }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n DELETE: {\n summary: 'Soft delete custom entity',\n description: 'Marks the specified custom entity inactive within the current scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteEntityRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Entity deleted',\n schema: z.object({ ok: z.boolean() }),\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n {\n status: 404,\n description: 'Entity not found in scope',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,cAAc,sBAAsB;AAC7C,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AAEzC,SAAS,gCAAgC;AACzC,SAAS,oCAAoC,iCAAiC;AAC9E,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,yCAAyC;AAExC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAAA,EACzB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAAA,EAC5E,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAChF;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,YAAa,CAAC,KAAK,SAAS,CAAC,KAAK,aAAe,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEvI,QAAM,EAAE,QAAQ,IAAI,MAAM,uBAAuB;AACjD,QAAM,KAAK,QAAQ,IAAI;AAGvB,QAAM,cAAc,aAAa;AACjC,QAAM,YAAmE,CAAC;AAC1E,aAAW,SAAS,OAAO,KAAK,WAAW,GAAG;AAC5C,UAAM,WAAY,YAAoB,KAAK;AAC3C,eAAW,KAAK,OAAO,KAAK,QAAQ,GAAG;AACrC,YAAM,KAAK,SAAS,CAAC;AACrB,UAAI,CAAC,yBAAyB,EAAE,EAAG;AACnC,gBAAU,KAAK,EAAE,UAAU,IAAI,QAAQ,QAAQ,OAAO,GAAG,CAAC;AAAA,IAC5D;AAAA,EACF;AAGA,QAAM,QAAa,EAAE,UAAU,KAAK;AACpC,QAAM,OAAO;AAAA,IACX,EAAE,KAAK,CAAE,EAAE,gBAAgB,KAAK,SAAS,OAAiB,GAAG,EAAE,gBAAgB,KAAK,CAAE,EAAE;AAAA,IACxF,EAAE,KAAK,CAAE,EAAE,UAAU,KAAK,YAAY,OAAiB,GAAG,EAAE,UAAU,KAAK,CAAE,EAAE;AAAA,EACjF;AACA,QAAM,UAAU,MAAM,GAAG,KAAK,cAAqB,OAAc,EAAE,SAAS,EAAE,UAAU,MAAM,EAAS,CAAC;AAExG,QAAM,mBAAmB,oBAAI,IAAiB;AAC9C,aAAW,KAAK,SAAkB;AAChC,UAAM,eAAe,EAAE,iBAAiB,IAAI,MAAM,EAAE,WAAW,IAAI;AACnE,UAAM,OAAO,iBAAiB,IAAI,EAAE,QAAQ;AAC5C,UAAM,WAAW,QAAS,KAAK,iBAAiB,IAAI,MAAM,KAAK,WAAW,IAAI,KAAM;AACpF,QAAI,CAAC,QAAQ,cAAc,SAAU,kBAAiB,IAAI,EAAE,UAAU,CAAC;AAAA,EACzE;AAEA,QAAM,SAAS,MAAM,KAAK,iBAAiB,OAAO,CAAC,EAChD,OAAO,CAAC,MAAM,yBAAyB,EAAE,QAAQ,CAAC,EAClD,IAAI,CAAC,OAAO;AAAA,IACX,UAAU,EAAE;AAAA,IACZ,QAAQ;AAAA,IACR,OAAO,EAAE;AAAA,IACT,aAAa,EAAE,eAAe;AAAA,IAC9B,YAAa,EAAU,cAAc;AAAA,IACrC,eAAgB,EAAU,iBAAiB;AAAA,IAC3C,eAAgB,EAAU,iBAAiB;AAAA,IAC3C,WAAW,EAAE,qBAAqB,OAAO,EAAE,UAAU,YAAY,IAAK,EAAE,aAAa;AAAA,EACvF,EAAE;AAEJ,QAAM,OAAO,oBAAI,IAAiB;AAClC,aAAW,KAAK,UAAW,MAAK,IAAI,EAAE,UAAU,CAAC;AACjD,aAAW,MAAM,QAAQ;AACvB,UAAM,WAAW,KAAK,IAAI,GAAG,QAAQ;AACrC,SAAK,IAAI,GAAG,UAAU,EAAE,GAAG,UAAU,GAAG,IAAI,QAAQ,UAAU,UAAU,GAAG,OAAO,CAAC;AAAA,EACrF;AAGA,QAAM,YAAiB,EAAE,UAAU,KAAK;AACxC,YAAU,OAAO;AAAA;AAAA,IAEf,EAAE,UAAU,KAAK,YAAY,OAAiB;AAAA,EAChD;AACA,QAAM,OAAO,MAAM,GAAG,KAAK,gBAAuB,SAAgB;AAElE,QAAM,UAAU,oBAAI,IAAyB;AAC7C,aAAW,KAAK,MAAe;AAC7B,UAAM,MAAM,OAAO,EAAE,QAAQ;AAC7B,UAAM,IAAI,OAAO,EAAE,GAAG;AACtB,QAAI,CAAC,yBAAyB,GAAG,EAAG;AACpC,UAAM,MAAM,QAAQ,IAAI,GAAG,KAAK,oBAAI,IAAY;AAChD,QAAI,IAAI,CAAC;AACT,YAAQ,IAAI,KAAK,GAAG;AAAA,EACtB;AACA,QAAM,SAAiC,CAAC;AACxC,aAAW,CAAC,KAAK,GAAG,KAAK,QAAQ,QAAQ,EAAG,QAAO,GAAG,IAAI,IAAI;AAE9D,QAAM,QAAQ,MAAM,KAAK,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC,QAAa,EAAE,GAAG,IAAI,OAAO,OAAO,GAAG,QAAQ,KAAK,EAAE,EAAE;AACrG,SAAO,aAAa,KAAK,EAAE,MAAM,CAAC;AACpC;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,MAAO,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE7F,MAAI;AACJ,MAAI;AAAE,WAAO,MAAM,IAAI,KAAK;AAAA,EAAE,QAAQ;AAAE,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAAE;AAC7G,QAAM,SAAS,yBAAyB,UAAU,IAAI;AACtD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,qBAAqB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3G;AACA,QAAM,QAAQ,OAAO;AAErB,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,KAAK,QAAQ,IAAI;AAKvB,MAAI,0BAA0B,IAAI,MAAM,QAAQ,GAAG;AACjD,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,2DAA2D,MAAM,oCAAoC,UAAU,MAAM,SAAS;AAAA,MACvI,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,QAAa,EAAE,UAAU,MAAM,UAAU,gBAAgB,KAAK,SAAS,MAAM,UAAU,KAAK,YAAY,KAAK;AACnH,MAAI,MAAM,MAAM,GAAG,QAAQ,cAAc,KAAK;AAC9C,MAAI,KAAK;AACP,QAAI;AACF,mCAA6B;AAAA,QAC3B,cAAc;AAAA,QACd,YAAY,IAAI;AAAA,QAChB,SAAS,IAAI,aAAa;AAAA,QAC1B,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,WAAW;AAClB,UAAI,gBAAgB,SAAS,GAAG;AAC9B,eAAO,aAAa,KAAK,UAAU,MAAM,EAAE,QAAQ,UAAU,OAAO,CAAC;AAAA,MACvE;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,2BAA2B;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,YAAY,MAAM,IAAI,KAAK,MAAM;AAAA,IACjC,WAAW,MAAM,WAAW;AAAA,IAC5B,iBAAiB;AAAA,EACnB,CAAC;AACD,MAAI,MAAM,gBAAiB,QAAO,MAAM;AAExC,MAAI,CAAC,IAAK,OAAM,GAAG,OAAO,cAAc,EAAE,GAAG,OAAO,WAAW,oBAAI,KAAK,EAAE,CAAC;AAC3E,MAAI,QAAQ,MAAM;AAClB,MAAI,cAAc,MAAM,eAAe;AACvC,MAAI,WAAW,MAAM,YAAY;AACjC,MAAI,aAAa,MAAM,cAAc,IAAI,cAAc;AACvD,MAAI,gBAAgB,MAAM,iBAAiB,IAAI,iBAAiB;AAChE,MAAI,gBAAgB,MAAM,iBAAiB,IAAI,iBAAiB;AAChE,MAAI,YAAY,oBAAI,KAAK;AACzB,KAAG,QAAQ,GAAG;AACd,QAAM,GAAG,MAAM;AACf,QAAM,MAAM,gBAAgB;AAE5B,MAAI;AACF,UAAM,SAAS,MAAM,uBAAuB,GAAG,QAAQ,OAAO;AAC9D,QAAI,OAAO;AACT,YAAM,MAAM,aAAa,CAAC,gBAAgB,KAAK,YAAY,MAAM,EAAE,CAAC;AAAA,IACtE;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,MAAM,EAAE,IAAI,IAAI,IAAI,UAAU,IAAI,UAAU,OAAO,IAAI,OAAO,aAAa,IAAI,eAAe,OAAU,EAAE,CAAC;AAClJ;AAEA,eAAsB,OAAO,KAAc;AACzC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,MAAO,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC7F,MAAI;AACJ,MAAI;AAAE,WAAO,MAAM,IAAI,KAAK;AAAA,EAAE,QAAQ;AAAE,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAAE;AAC7G,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,SAAU,QAAO,aAAa,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE1F,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,KAAK,QAAQ,IAAI;AAEvB,QAAM,QAAa,EAAE,UAAU,gBAAgB,KAAK,SAAS,MAAM,UAAU,KAAK,YAAY,KAAK;AACnG,QAAM,MAAM,MAAM,GAAG,QAAQ,cAAc,KAAK;AAChD,MAAI,CAAC,IAAK,QAAO,aAAa,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE1E,QAAM,QAAQ,MAAM,2BAA2B;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,YAAY,IAAI;AAAA,IAChB,WAAW;AAAA,IACX,iBAAiB,EAAE,SAAS;AAAA,EAC9B,CAAC;AACD,MAAI,MAAM,gBAAiB,QAAO,MAAM;AAExC,MAAI,WAAW;AACf,MAAI,YAAY,oBAAI,KAAK;AACzB,MAAI,YAAY,IAAI,aAAa,oBAAI,KAAK;AAC1C,KAAG,QAAQ,GAAG;AACd,QAAM,GAAG,MAAM;AACf,QAAM,MAAM,gBAAgB;AAE5B,MAAI;AACF,UAAM,SAAS,MAAM,uBAAuB,GAAG,QAAQ,OAAO;AAC9D,QAAI,OAAO;AACT,YAAM,MAAM,aAAa,CAAC,gBAAgB,KAAK,YAAY,MAAM,EAAE,CAAC;AAAA,IACtE;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,UAAU,EAAE,OAAO;AAAA,EACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ,QAAQ,CAAC;AAAA,EACjC,OAAO,EAAE,OAAO;AAAA,EAChB,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,eAAe,EAAE,QAAQ,EAAE,SAAS;AAAA,EACpC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,OAAO,EAAE,MAAM,mBAAmB;AACpC,CAAC;AAED,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,UAAU,EAAE,OAAO;AACrB,CAAC;AAED,MAAM,mCAAmC,EAAE,OAAO;AAAA,EAChD,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,MAAM,EAAE,OAAO;AAAA,IACb,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,UAAU,EAAE,OAAO;AAAA,IACnB,OAAO,EAAE,OAAO;AAAA,IAChB,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,CAAC;AACH,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO;AAAA,YACf,OAAO,EAAE,OAAO;AAAA,YAChB,SAAS,EAAE,IAAI,EAAE,SAAS;AAAA,UAC5B,CAAC;AAAA,QACH;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAAA,QACtC;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,QACxC;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,QACxC;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -468,6 +468,24 @@ async function DELETE(req) {
|
|
|
468
468
|
if (entityKind === "system") return systemEntityRecordsRejection(entityId);
|
|
469
469
|
const isCustomEntity = entityKind === "custom";
|
|
470
470
|
await assertEntityAclForRequest({ auth, entityId, action: "manage", isCustomEntity, rbac });
|
|
471
|
+
try {
|
|
472
|
+
const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {
|
|
473
|
+
entityType: entityId,
|
|
474
|
+
entityId: recordId,
|
|
475
|
+
organizationId: targetOrgId
|
|
476
|
+
});
|
|
477
|
+
enforceCommandOptimisticLock({
|
|
478
|
+
resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,
|
|
479
|
+
resourceId: recordId,
|
|
480
|
+
current: currentUpdatedAt,
|
|
481
|
+
request: req
|
|
482
|
+
});
|
|
483
|
+
} catch (lockError) {
|
|
484
|
+
if (isCrudHttpError(lockError)) {
|
|
485
|
+
return NextResponse.json(lockError.body, { status: lockError.status });
|
|
486
|
+
}
|
|
487
|
+
throw lockError;
|
|
488
|
+
}
|
|
471
489
|
await de.deleteCustomEntityRecord({ entityId, recordId, organizationId: targetOrgId, tenantId: auth.tenantId, soft: true });
|
|
472
490
|
return NextResponse.json({ ok: true });
|
|
473
491
|
} catch (e) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/api/records.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport type { QueryEngine, QueryOptions, Where, Sort } from '@open-mercato/shared/lib/query/types'\nimport { normalizeExportFormat, serializeExport, defaultExportFilename, ensureColumns } from '@open-mercato/shared/lib/crud/exporters'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { resolveOrganizationScope, getSelectedOrganizationFromRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { setRecordCustomFields } from '../lib/helpers'\nimport { CustomFieldValue } from '../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\nimport { assertEntityAclForRequest } from '../lib/entityAcl'\n\nlet declaredCustomEntityIds: Set<string> | null = null\nfunction isDeclaredCustomEntity(entityId: string): boolean {\n if (declaredCustomEntityIds === null) {\n try {\n const mods = getModules() as Array<{ customEntities?: Array<{ id?: string }> }>\n if (Array.isArray(mods) && mods.length) {\n const ids = new Set<string>()\n for (const mod of mods) {\n for (const spec of mod?.customEntities ?? []) {\n if (spec?.id) ids.add(spec.id)\n }\n }\n declaredCustomEntityIds = ids\n }\n } catch {}\n }\n return declaredCustomEntityIds?.has(entityId) ?? false\n}\n\nconst CUSTOM_ENTITY_RECORD_RESOURCE_KIND = 'entities.record'\n\ntype RecordsEntityKind = 'system' | 'custom' | 'unknown'\n\n// This surface manages doc-storage records, which exist for CUSTOM entities only.\n// Module-declared ids backed by a registered ORM table are system entities \u2014 their\n// records live in their own module tables/APIs, and stray doc rows for them poisoned\n// read-path classification platform-wide (#2939) \u2014 so they are rejected outright. The\n// previous fallback that classified an entity by the mere presence of\n// `custom_entities_storage` rows is gone: within the allowed set, declaration (ce.ts)\n// or an active `custom_entities` registration is authoritative.\nasync function classifyRecordsEntity(em: any, entityId: string): Promise<RecordsEntityKind> {\n if (isOrmBackedSystemEntityId(em, entityId)) return 'system'\n if (isDeclaredCustomEntity(entityId)) return 'custom'\n try {\n const { CustomEntity } = await import('../data/entities')\n // Any registration row \u2014 active or soft-deleted \u2014 proves the id is a custom\n // entity. Records persist beyond the definition's soft delete (TC-ENTITIES-006)\n // and must stay readable/deletable, e.g. for the restore flow and cleanup.\n const found = await em.findOne(CustomEntity as any, { entityId })\n if (found) return 'custom'\n } catch {}\n return 'unknown'\n}\n\nfunction systemEntityRecordsRejection(entityId: string) {\n return NextResponse.json(\n { error: 'Records are available for custom entities only', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId },\n { status: 400 },\n )\n}\n\nasync function readCustomEntityRecordUpdatedAt(\n em: any,\n input: { entityType: string; entityId: string; organizationId: string | null },\n): Promise<string | null> {\n try {\n const db = em.getKysely()\n let query = db\n .selectFrom('custom_entities_storage' as any)\n .select(['updated_at' as any])\n .where('entity_type' as any, '=', input.entityType)\n .where('entity_id' as any, '=', input.entityId)\n query = input.organizationId === null\n ? query.where('organization_id' as any, 'is', null as any)\n : query.where('organization_id' as any, '=', input.organizationId)\n const row = await query.executeTakeFirst()\n const value = (row as any)?.updated_at\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string' && value.length > 0) return value\n return null\n } catch {\n return null\n }\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.records.view'] },\n POST: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n}\n\nconst DEFAULT_EXPORT_PAGE_SIZE = 1000\n\nconst listRecordsQuerySchema = z\n .object({\n entityId: z.string().min(1),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n withDeleted: z.coerce.boolean().optional(),\n format: z.enum(['csv', 'json', 'xml', 'markdown']).optional(),\n exportScope: z.enum(['full']).optional(),\n export_scope: z.enum(['full']).optional(),\n all: z.coerce.boolean().optional(),\n full: z.coerce.boolean().optional(),\n })\n .passthrough()\n\nconst recordItemSchema = z.record(z.string(), z.any())\n\nconst listRecordsResponseSchema = z.object({\n items: z.array(recordItemSchema),\n total: z.number(),\n page: z.number(),\n pageSize: z.number(),\n totalPages: z.number(),\n})\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const entityId = url.searchParams.get('entityId') || ''\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const requestedExport = normalizeExportFormat(url.searchParams.get('format'))\n const exportScopeRaw = (url.searchParams.get('exportScope') || url.searchParams.get('export_scope') || '').toLowerCase()\n const exportFullRequested = requestedExport != null && (exportScopeRaw === 'full' || parseBooleanWithDefault(url.searchParams.get('full'), false))\n const exportAll = parseBooleanWithDefault(url.searchParams.get('all'), false)\n const noPagination = exportAll || requestedExport != null\n const page = noPagination ? 1 : Math.max(parseInt(url.searchParams.get('page') || '1', 10) || 1, 1)\n const basePageSize = Math.min(Math.max(parseInt(url.searchParams.get('pageSize') || '50', 10) || 50, 1), 100)\n const pageSize = noPagination ? Math.max(basePageSize, DEFAULT_EXPORT_PAGE_SIZE) : basePageSize\n const sortField = url.searchParams.get('sortField') || 'id'\n const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'\n const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)\n\n const qpEntries: Array<[string, string]> = []\n for (const [key, val] of url.searchParams.entries()) {\n if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full'].includes(key)) continue\n qpEntries.push([key, val])\n }\n\n try {\n const { resolve } = await createRequestContainer()\n const qe = resolve('queryEngine') as QueryEngine\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n let organizationIds: string[] | null = scope.filterIds\n // Module-declared custom entities (ce.ts) carry frozen system-style ids and are never\n // registered in `custom_entities`, so classification checks the declared registry plus\n // active registrations. System (table-backed) ids are rejected above; for the allowed\n // set `isCustomEntity` drives mapRow's cf_ stripping so the edit form reads back values.\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'view', isCustomEntity, rbac })\n if (organizationIds && organizationIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 0 })\n }\n const normalizeCustomEntityValue = (value: unknown) => {\n if (Array.isArray(value)) {\n return value.map((entry) => {\n if (typeof entry !== 'string') return entry\n const parsed = parseBooleanToken(entry)\n return parsed === null ? entry : parsed\n })\n }\n if (typeof value !== 'string') return value\n const parsed = parseBooleanToken(value)\n return parsed === null ? value : parsed\n }\n const mapRow = (row: any) => {\n if (!isCustomEntity || !row || typeof row !== 'object') return row\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(row)) {\n if (k.startsWith('cf_')) out[k.replace(/^cf_/, '')] = normalizeCustomEntityValue(v)\n else out[k] = v\n }\n return out\n }\n const mapFullRow = (row: any) => {\n if (!row || typeof row !== 'object') return row\n return { ...(row as Record<string, unknown>) }\n }\n // Build filters with awareness of custom-entity mode\n const filtersObj: Where<any> = {}\n const buildFilter = (key: string, val: string, allowAnyKey: boolean) => {\n if (key.startsWith('cf_')) {\n if (key.endsWith('In')) {\n const base = key.slice(0, -2)\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[base] = { $in: values }\n } else {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n }\n } else if (allowAnyKey) {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n } else {\n if (['id', 'created_at', 'updated_at', 'deleted_at', 'name', 'title', 'email'].includes(key)) {\n ;(filtersObj as any)[key] = val\n }\n }\n }\n\n if (organizationIds && organizationIds.length) {\n (filtersObj as any).organization_id = { $in: organizationIds }\n }\n const qopts: QueryOptions = {\n tenantId: auth.tenantId!,\n includeCustomFields: true,\n page: { page, pageSize },\n sort: [{ field: sortField as any, dir: sortDir as any }] as Sort[],\n filters: filtersObj as any,\n withDeleted,\n }\n if (organizationIds && organizationIds.length) {\n qopts.organizationIds = organizationIds\n }\n // Allowed entities are doc-storage-backed by definition (system ids were rejected\n // above) \u2014 direct the engine to doc storage explicitly so reads stay deterministic\n // even before the first record exists.\n if (isCustomEntity) qopts.forceCustomEntityStorage = true\n for (const [k, v] of qpEntries) buildFilter(k, v, isCustomEntity)\n const res = await qe.query(entityId as any, qopts)\n const rawItems = res.items || []\n const viewPageItems = rawItems.map(mapRow)\n const fullPageItems = rawItems.map(mapFullRow)\n\n // Expose `updated_at` on custom-entity records. The query engine returns only\n // the `doc` fields + `id`, dropping the base `updated_at` column \u2014 which made\n // optimistic locking impossible end-to-end (no version for the edit page to\n // round-trip as the lock header). Batch-read it from storage and merge it in.\n if (isCustomEntity && viewPageItems.length) {\n try {\n const recordIds = viewPageItems\n .map((it: any) => it?.id)\n .filter((v: any): v is string => typeof v === 'string' && v.length > 0)\n if (recordIds.length) {\n const db = em.getKysely()\n const rows = await db\n .selectFrom('custom_entities_storage' as any)\n .select(['entity_id' as any, 'updated_at' as any])\n .where('entity_type' as any, '=', entityId)\n .where('entity_id' as any, 'in', recordIds as any)\n .execute()\n const updatedById = new Map<string, string>()\n for (const row of rows as any[]) {\n const value = row?.updated_at\n const iso = value instanceof Date ? value.toISOString() : (typeof value === 'string' && value.length > 0 ? value : null)\n if (iso && row?.entity_id) updatedById.set(String(row.entity_id), iso)\n }\n for (const item of viewPageItems as any[]) {\n const iso = updatedById.get(String(item?.id))\n if (iso) {\n item.updated_at = iso\n item.updatedAt = iso\n }\n }\n }\n } catch { /* best-effort: locking simply will not engage if storage is unavailable */ }\n }\n\n const total = typeof res.total === 'number' ? res.total : rawItems.length\n const effectivePageSize = res.pageSize || pageSize\n const payload = {\n items: viewPageItems,\n total,\n page: res.page || page,\n pageSize: effectivePageSize,\n totalPages: Math.ceil(total / (effectivePageSize || 1)),\n }\n\n if (requestedExport) {\n let exportItems: any[] = exportFullRequested ? [...fullPageItems] : [...viewPageItems]\n if (total > exportItems.length) {\n let nextPage = 2\n while (exportItems.length < total) {\n const nextRes = await qe.query(entityId as any, {\n ...qopts,\n page: { page: nextPage, pageSize },\n })\n const nextRawItems = nextRes.items || []\n if (!nextRawItems.length) break\n const nextViewItems = nextRawItems.map(mapRow)\n const nextFullItems = nextRawItems.map(mapFullRow)\n const nextBatch = exportFullRequested ? nextFullItems : nextViewItems\n exportItems.push(...nextBatch)\n if (nextBatch.length < pageSize) break\n nextPage += 1\n }\n }\n const prepared = {\n columns: ensureColumns(exportItems),\n rows: exportItems,\n }\n const filenameBase = exportFullRequested ? `${entityId || 'records'}_full` : entityId || 'records'\n const serialized = serializeExport(prepared, requestedExport)\n const filename = defaultExportFilename(filenameBase, requestedExport)\n return new Response(serialized.body, {\n headers: {\n 'content-type': serialized.contentType,\n 'content-disposition': `attachment; filename=\"${filename}\"`,\n },\n })\n }\n\n return NextResponse.json(payload)\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.GET] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst postBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1).optional(),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst putBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst mutationResponseSchema = z.object({\n ok: z.literal(true),\n item: z\n .object({\n entityId: z.string(),\n recordId: z.string(),\n })\n .optional(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: unknown\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = postBodySchema.safeParse(json)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId } = parsed.data\n let { recordId, values } = parsed.data as { recordId?: string; values: Record<string, any> }\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n const normalizedRecordId = (() => {\n const raw = String(recordId || '').trim()\n if (!raw) return undefined\n const low = raw.toLowerCase()\n if (low === 'create' || low === 'new' || low === 'null' || low === 'undefined') return undefined\n // Enforce UUID only; any non-uuid is ignored so we generate one in the DE\n const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n return uuid.test(raw) ? raw : undefined\n })()\n const { id } = await de.createCustomEntityRecord({\n entityId,\n recordId: normalizedRecordId,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n\n return NextResponse.json({ ok: true, item: { entityId, recordId: id } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.POST] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\n// Avoid zod here to prevent runtime import issues in some environments\nfunction parsePutBody(json: any): { ok: true; data: { entityId: string; recordId: string; values: Record<string, any> } } | { ok: false; error: string } {\n if (!json || typeof json !== 'object') return { ok: false, error: 'Invalid JSON' }\n const entityId = typeof json.entityId === 'string' && json.entityId.length ? json.entityId : ''\n const recordId = typeof json.recordId === 'string' && json.recordId.length ? json.recordId : ''\n const values = (json.values && typeof json.values === 'object') ? json.values as Record<string, any> : {}\n if (!entityId || !recordId) return { ok: false, error: 'entityId and recordId are required' }\n return { ok: true, data: { entityId, recordId, values } }\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: any\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = parsePutBody(json)\n if (!parsed.ok) return NextResponse.json({ error: parsed.error }, { status: 400 })\n const { entityId, recordId, values } = parsed.data\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n // Normalize recordId: if blank/sentinel/non-uuid => create instead of update\n const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n const rid = String(recordId || '').trim()\n const low = rid.toLowerCase()\n const isSentinel = !rid || low === 'create' || low === 'new' || low === 'null' || low === 'undefined'\n const isUuid = uuidRe.test(rid)\n if (isSentinel || !isUuid) {\n const created = await de.createCustomEntityRecord({\n entityId,\n recordId: undefined,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: created.id } })\n }\n\n try {\n const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {\n entityType: entityId,\n entityId: rid,\n organizationId: targetOrgId,\n })\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,\n resourceId: rid,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n await de.updateCustomEntityRecord({\n entityId,\n recordId: rid,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: rid } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst deleteBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n})\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const qpEntityId = url.searchParams.get('entityId')\n const qpRecordId = url.searchParams.get('recordId')\n let payload: any = qpEntityId && qpRecordId ? { entityId: qpEntityId, recordId: qpRecordId } : null\n if (!payload) {\n try { payload = await req.json() } catch { payload = null }\n }\n const parsed = deleteBodySchema.safeParse(payload)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId, recordId } = parsed.data\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n await de.deleteCustomEntityRecord({ entityId, recordId, organizationId: targetOrgId, tenantId: auth.tenantId!, soft: true })\n return NextResponse.json({ ok: true })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nfunction normalizeValues(input: Record<string, any>): Record<string, any> {\n const out: Record<string, any> = {}\n for (const [k, v] of Object.entries(input || {})) {\n const key = k.startsWith('cf_') ? k.replace(/^cf_/, '') : k\n out[key] = v\n }\n return out\n}\n\nconst deleteResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n}).passthrough()\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'CRUD operations on entity records',\n methods: {\n GET: {\n summary: 'List records',\n description:\n 'Returns paginated records for the supplied entity. Supports custom field filters, exports, and soft-delete toggles.',\n query: listRecordsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Paginated records',\n schema: listRecordsResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n POST: {\n summary: 'Create record',\n description:\n 'Creates a record for the given entity. When `recordId` is omitted or not a UUID the data engine will generate one automatically.',\n requestBody: {\n contentType: 'application/json',\n schema: postBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record created',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n PUT: {\n summary: 'Update record',\n description:\n 'Updates an existing record. If the provided recordId is not a UUID the record will be created instead to support optimistic flows.',\n requestBody: {\n contentType: 'application/json',\n schema: putBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record updated',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n DELETE: {\n summary: 'Delete record',\n description: 'Soft deletes the specified record within the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record deleted',\n schema: deleteResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id or record id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 404,\n description: 'Record not found',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport type { QueryEngine, QueryOptions, Where, Sort } from '@open-mercato/shared/lib/query/types'\nimport { normalizeExportFormat, serializeExport, defaultExportFilename, ensureColumns } from '@open-mercato/shared/lib/crud/exporters'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { resolveOrganizationScope, getSelectedOrganizationFromRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { setRecordCustomFields } from '../lib/helpers'\nimport { CustomFieldValue } from '../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\nimport { assertEntityAclForRequest } from '../lib/entityAcl'\n\nlet declaredCustomEntityIds: Set<string> | null = null\nfunction isDeclaredCustomEntity(entityId: string): boolean {\n if (declaredCustomEntityIds === null) {\n try {\n const mods = getModules() as Array<{ customEntities?: Array<{ id?: string }> }>\n if (Array.isArray(mods) && mods.length) {\n const ids = new Set<string>()\n for (const mod of mods) {\n for (const spec of mod?.customEntities ?? []) {\n if (spec?.id) ids.add(spec.id)\n }\n }\n declaredCustomEntityIds = ids\n }\n } catch {}\n }\n return declaredCustomEntityIds?.has(entityId) ?? false\n}\n\nconst CUSTOM_ENTITY_RECORD_RESOURCE_KIND = 'entities.record'\n\ntype RecordsEntityKind = 'system' | 'custom' | 'unknown'\n\n// This surface manages doc-storage records, which exist for CUSTOM entities only.\n// Module-declared ids backed by a registered ORM table are system entities \u2014 their\n// records live in their own module tables/APIs, and stray doc rows for them poisoned\n// read-path classification platform-wide (#2939) \u2014 so they are rejected outright. The\n// previous fallback that classified an entity by the mere presence of\n// `custom_entities_storage` rows is gone: within the allowed set, declaration (ce.ts)\n// or an active `custom_entities` registration is authoritative.\nasync function classifyRecordsEntity(em: any, entityId: string): Promise<RecordsEntityKind> {\n if (isOrmBackedSystemEntityId(em, entityId)) return 'system'\n if (isDeclaredCustomEntity(entityId)) return 'custom'\n try {\n const { CustomEntity } = await import('../data/entities')\n // Any registration row \u2014 active or soft-deleted \u2014 proves the id is a custom\n // entity. Records persist beyond the definition's soft delete (TC-ENTITIES-006)\n // and must stay readable/deletable, e.g. for the restore flow and cleanup.\n const found = await em.findOne(CustomEntity as any, { entityId })\n if (found) return 'custom'\n } catch {}\n return 'unknown'\n}\n\nfunction systemEntityRecordsRejection(entityId: string) {\n return NextResponse.json(\n { error: 'Records are available for custom entities only', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId },\n { status: 400 },\n )\n}\n\nasync function readCustomEntityRecordUpdatedAt(\n em: any,\n input: { entityType: string; entityId: string; organizationId: string | null },\n): Promise<string | null> {\n try {\n const db = em.getKysely()\n let query = db\n .selectFrom('custom_entities_storage' as any)\n .select(['updated_at' as any])\n .where('entity_type' as any, '=', input.entityType)\n .where('entity_id' as any, '=', input.entityId)\n query = input.organizationId === null\n ? query.where('organization_id' as any, 'is', null as any)\n : query.where('organization_id' as any, '=', input.organizationId)\n const row = await query.executeTakeFirst()\n const value = (row as any)?.updated_at\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string' && value.length > 0) return value\n return null\n } catch {\n return null\n }\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.records.view'] },\n POST: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n}\n\nconst DEFAULT_EXPORT_PAGE_SIZE = 1000\n\nconst listRecordsQuerySchema = z\n .object({\n entityId: z.string().min(1),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n withDeleted: z.coerce.boolean().optional(),\n format: z.enum(['csv', 'json', 'xml', 'markdown']).optional(),\n exportScope: z.enum(['full']).optional(),\n export_scope: z.enum(['full']).optional(),\n all: z.coerce.boolean().optional(),\n full: z.coerce.boolean().optional(),\n })\n .passthrough()\n\nconst recordItemSchema = z.record(z.string(), z.any())\n\nconst listRecordsResponseSchema = z.object({\n items: z.array(recordItemSchema),\n total: z.number(),\n page: z.number(),\n pageSize: z.number(),\n totalPages: z.number(),\n})\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const entityId = url.searchParams.get('entityId') || ''\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const requestedExport = normalizeExportFormat(url.searchParams.get('format'))\n const exportScopeRaw = (url.searchParams.get('exportScope') || url.searchParams.get('export_scope') || '').toLowerCase()\n const exportFullRequested = requestedExport != null && (exportScopeRaw === 'full' || parseBooleanWithDefault(url.searchParams.get('full'), false))\n const exportAll = parseBooleanWithDefault(url.searchParams.get('all'), false)\n const noPagination = exportAll || requestedExport != null\n const page = noPagination ? 1 : Math.max(parseInt(url.searchParams.get('page') || '1', 10) || 1, 1)\n const basePageSize = Math.min(Math.max(parseInt(url.searchParams.get('pageSize') || '50', 10) || 50, 1), 100)\n const pageSize = noPagination ? Math.max(basePageSize, DEFAULT_EXPORT_PAGE_SIZE) : basePageSize\n const sortField = url.searchParams.get('sortField') || 'id'\n const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'\n const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)\n\n const qpEntries: Array<[string, string]> = []\n for (const [key, val] of url.searchParams.entries()) {\n if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full'].includes(key)) continue\n qpEntries.push([key, val])\n }\n\n try {\n const { resolve } = await createRequestContainer()\n const qe = resolve('queryEngine') as QueryEngine\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n let organizationIds: string[] | null = scope.filterIds\n // Module-declared custom entities (ce.ts) carry frozen system-style ids and are never\n // registered in `custom_entities`, so classification checks the declared registry plus\n // active registrations. System (table-backed) ids are rejected above; for the allowed\n // set `isCustomEntity` drives mapRow's cf_ stripping so the edit form reads back values.\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'view', isCustomEntity, rbac })\n if (organizationIds && organizationIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 0 })\n }\n const normalizeCustomEntityValue = (value: unknown) => {\n if (Array.isArray(value)) {\n return value.map((entry) => {\n if (typeof entry !== 'string') return entry\n const parsed = parseBooleanToken(entry)\n return parsed === null ? entry : parsed\n })\n }\n if (typeof value !== 'string') return value\n const parsed = parseBooleanToken(value)\n return parsed === null ? value : parsed\n }\n const mapRow = (row: any) => {\n if (!isCustomEntity || !row || typeof row !== 'object') return row\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(row)) {\n if (k.startsWith('cf_')) out[k.replace(/^cf_/, '')] = normalizeCustomEntityValue(v)\n else out[k] = v\n }\n return out\n }\n const mapFullRow = (row: any) => {\n if (!row || typeof row !== 'object') return row\n return { ...(row as Record<string, unknown>) }\n }\n // Build filters with awareness of custom-entity mode\n const filtersObj: Where<any> = {}\n const buildFilter = (key: string, val: string, allowAnyKey: boolean) => {\n if (key.startsWith('cf_')) {\n if (key.endsWith('In')) {\n const base = key.slice(0, -2)\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[base] = { $in: values }\n } else {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n }\n } else if (allowAnyKey) {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n } else {\n if (['id', 'created_at', 'updated_at', 'deleted_at', 'name', 'title', 'email'].includes(key)) {\n ;(filtersObj as any)[key] = val\n }\n }\n }\n\n if (organizationIds && organizationIds.length) {\n (filtersObj as any).organization_id = { $in: organizationIds }\n }\n const qopts: QueryOptions = {\n tenantId: auth.tenantId!,\n includeCustomFields: true,\n page: { page, pageSize },\n sort: [{ field: sortField as any, dir: sortDir as any }] as Sort[],\n filters: filtersObj as any,\n withDeleted,\n }\n if (organizationIds && organizationIds.length) {\n qopts.organizationIds = organizationIds\n }\n // Allowed entities are doc-storage-backed by definition (system ids were rejected\n // above) \u2014 direct the engine to doc storage explicitly so reads stay deterministic\n // even before the first record exists.\n if (isCustomEntity) qopts.forceCustomEntityStorage = true\n for (const [k, v] of qpEntries) buildFilter(k, v, isCustomEntity)\n const res = await qe.query(entityId as any, qopts)\n const rawItems = res.items || []\n const viewPageItems = rawItems.map(mapRow)\n const fullPageItems = rawItems.map(mapFullRow)\n\n // Expose `updated_at` on custom-entity records. The query engine returns only\n // the `doc` fields + `id`, dropping the base `updated_at` column \u2014 which made\n // optimistic locking impossible end-to-end (no version for the edit page to\n // round-trip as the lock header). Batch-read it from storage and merge it in.\n if (isCustomEntity && viewPageItems.length) {\n try {\n const recordIds = viewPageItems\n .map((it: any) => it?.id)\n .filter((v: any): v is string => typeof v === 'string' && v.length > 0)\n if (recordIds.length) {\n const db = em.getKysely()\n const rows = await db\n .selectFrom('custom_entities_storage' as any)\n .select(['entity_id' as any, 'updated_at' as any])\n .where('entity_type' as any, '=', entityId)\n .where('entity_id' as any, 'in', recordIds as any)\n .execute()\n const updatedById = new Map<string, string>()\n for (const row of rows as any[]) {\n const value = row?.updated_at\n const iso = value instanceof Date ? value.toISOString() : (typeof value === 'string' && value.length > 0 ? value : null)\n if (iso && row?.entity_id) updatedById.set(String(row.entity_id), iso)\n }\n for (const item of viewPageItems as any[]) {\n const iso = updatedById.get(String(item?.id))\n if (iso) {\n item.updated_at = iso\n item.updatedAt = iso\n }\n }\n }\n } catch { /* best-effort: locking simply will not engage if storage is unavailable */ }\n }\n\n const total = typeof res.total === 'number' ? res.total : rawItems.length\n const effectivePageSize = res.pageSize || pageSize\n const payload = {\n items: viewPageItems,\n total,\n page: res.page || page,\n pageSize: effectivePageSize,\n totalPages: Math.ceil(total / (effectivePageSize || 1)),\n }\n\n if (requestedExport) {\n let exportItems: any[] = exportFullRequested ? [...fullPageItems] : [...viewPageItems]\n if (total > exportItems.length) {\n let nextPage = 2\n while (exportItems.length < total) {\n const nextRes = await qe.query(entityId as any, {\n ...qopts,\n page: { page: nextPage, pageSize },\n })\n const nextRawItems = nextRes.items || []\n if (!nextRawItems.length) break\n const nextViewItems = nextRawItems.map(mapRow)\n const nextFullItems = nextRawItems.map(mapFullRow)\n const nextBatch = exportFullRequested ? nextFullItems : nextViewItems\n exportItems.push(...nextBatch)\n if (nextBatch.length < pageSize) break\n nextPage += 1\n }\n }\n const prepared = {\n columns: ensureColumns(exportItems),\n rows: exportItems,\n }\n const filenameBase = exportFullRequested ? `${entityId || 'records'}_full` : entityId || 'records'\n const serialized = serializeExport(prepared, requestedExport)\n const filename = defaultExportFilename(filenameBase, requestedExport)\n return new Response(serialized.body, {\n headers: {\n 'content-type': serialized.contentType,\n 'content-disposition': `attachment; filename=\"${filename}\"`,\n },\n })\n }\n\n return NextResponse.json(payload)\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.GET] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst postBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1).optional(),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst putBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst mutationResponseSchema = z.object({\n ok: z.literal(true),\n item: z\n .object({\n entityId: z.string(),\n recordId: z.string(),\n })\n .optional(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: unknown\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = postBodySchema.safeParse(json)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId } = parsed.data\n let { recordId, values } = parsed.data as { recordId?: string; values: Record<string, any> }\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n const normalizedRecordId = (() => {\n const raw = String(recordId || '').trim()\n if (!raw) return undefined\n const low = raw.toLowerCase()\n if (low === 'create' || low === 'new' || low === 'null' || low === 'undefined') return undefined\n // Enforce UUID only; any non-uuid is ignored so we generate one in the DE\n const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n return uuid.test(raw) ? raw : undefined\n })()\n const { id } = await de.createCustomEntityRecord({\n entityId,\n recordId: normalizedRecordId,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n\n return NextResponse.json({ ok: true, item: { entityId, recordId: id } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.POST] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\n// Avoid zod here to prevent runtime import issues in some environments\nfunction parsePutBody(json: any): { ok: true; data: { entityId: string; recordId: string; values: Record<string, any> } } | { ok: false; error: string } {\n if (!json || typeof json !== 'object') return { ok: false, error: 'Invalid JSON' }\n const entityId = typeof json.entityId === 'string' && json.entityId.length ? json.entityId : ''\n const recordId = typeof json.recordId === 'string' && json.recordId.length ? json.recordId : ''\n const values = (json.values && typeof json.values === 'object') ? json.values as Record<string, any> : {}\n if (!entityId || !recordId) return { ok: false, error: 'entityId and recordId are required' }\n return { ok: true, data: { entityId, recordId, values } }\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: any\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = parsePutBody(json)\n if (!parsed.ok) return NextResponse.json({ error: parsed.error }, { status: 400 })\n const { entityId, recordId, values } = parsed.data\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n // Normalize recordId: if blank/sentinel/non-uuid => create instead of update\n const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n const rid = String(recordId || '').trim()\n const low = rid.toLowerCase()\n const isSentinel = !rid || low === 'create' || low === 'new' || low === 'null' || low === 'undefined'\n const isUuid = uuidRe.test(rid)\n if (isSentinel || !isUuid) {\n const created = await de.createCustomEntityRecord({\n entityId,\n recordId: undefined,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: created.id } })\n }\n\n try {\n const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {\n entityType: entityId,\n entityId: rid,\n organizationId: targetOrgId,\n })\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,\n resourceId: rid,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n await de.updateCustomEntityRecord({\n entityId,\n recordId: rid,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: rid } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst deleteBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n})\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const qpEntityId = url.searchParams.get('entityId')\n const qpRecordId = url.searchParams.get('recordId')\n let payload: any = qpEntityId && qpRecordId ? { entityId: qpEntityId, recordId: qpRecordId } : null\n if (!payload) {\n try { payload = await req.json() } catch { payload = null }\n }\n const parsed = deleteBodySchema.safeParse(payload)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId, recordId } = parsed.data\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n\n try {\n const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {\n entityType: entityId,\n entityId: recordId,\n organizationId: targetOrgId,\n })\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,\n resourceId: recordId,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n await de.deleteCustomEntityRecord({ entityId, recordId, organizationId: targetOrgId, tenantId: auth.tenantId!, soft: true })\n return NextResponse.json({ ok: true })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nfunction normalizeValues(input: Record<string, any>): Record<string, any> {\n const out: Record<string, any> = {}\n for (const [k, v] of Object.entries(input || {})) {\n const key = k.startsWith('cf_') ? k.replace(/^cf_/, '') : k\n out[key] = v\n }\n return out\n}\n\nconst deleteResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n}).passthrough()\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'CRUD operations on entity records',\n methods: {\n GET: {\n summary: 'List records',\n description:\n 'Returns paginated records for the supplied entity. Supports custom field filters, exports, and soft-delete toggles.',\n query: listRecordsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Paginated records',\n schema: listRecordsResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n POST: {\n summary: 'Create record',\n description:\n 'Creates a record for the given entity. When `recordId` is omitted or not a UUID the data engine will generate one automatically.',\n requestBody: {\n contentType: 'application/json',\n schema: postBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record created',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n PUT: {\n summary: 'Update record',\n description:\n 'Updates an existing record. If the provided recordId is not a UUID the record will be created instead to support optimistic flows.',\n requestBody: {\n contentType: 'application/json',\n schema: putBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record updated',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n DELETE: {\n summary: 'Delete record',\n description: 'Soft deletes the specified record within the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record deleted',\n schema: deleteResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id or record id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 404,\n description: 'Record not found',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AAEnC,SAAS,uBAAuB,iBAAiB,uBAAuB,qBAAqB;AAE7F,SAAS,0BAA0B,0CAA0C;AAC7E,SAAS,oCAAoC,iCAAiC;AAC9E,SAAS,mBAAmB,+BAA+B;AAI3D,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAC3B,SAAS,iCAAiC;AAE1C,IAAI,0BAA8C;AAClD,SAAS,uBAAuB,UAA2B;AACzD,MAAI,4BAA4B,MAAM;AACpC,QAAI;AACF,YAAM,OAAO,WAAW;AACxB,UAAI,MAAM,QAAQ,IAAI,KAAK,KAAK,QAAQ;AACtC,cAAM,MAAM,oBAAI,IAAY;AAC5B,mBAAW,OAAO,MAAM;AACtB,qBAAW,QAAQ,KAAK,kBAAkB,CAAC,GAAG;AAC5C,gBAAI,MAAM,GAAI,KAAI,IAAI,KAAK,EAAE;AAAA,UAC/B;AAAA,QACF;AACA,kCAA0B;AAAA,MAC5B;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AACA,SAAO,yBAAyB,IAAI,QAAQ,KAAK;AACnD;AAEA,MAAM,qCAAqC;AAW3C,eAAe,sBAAsB,IAAS,UAA8C;AAC1F,MAAI,0BAA0B,IAAI,QAAQ,EAAG,QAAO;AACpD,MAAI,uBAAuB,QAAQ,EAAG,QAAO;AAC7C,MAAI;AACF,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,kBAAkB;AAIxD,UAAM,QAAQ,MAAM,GAAG,QAAQ,cAAqB,EAAE,SAAS,CAAC;AAChE,QAAI,MAAO,QAAO;AAAA,EACpB,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAEA,SAAS,6BAA6B,UAAkB;AACtD,SAAO,aAAa;AAAA,IAClB,EAAE,OAAO,kDAAkD,MAAM,oCAAoC,SAAS;AAAA,IAC9G,EAAE,QAAQ,IAAI;AAAA,EAChB;AACF;AAEA,eAAe,gCACb,IACA,OACwB;AACxB,MAAI;AACF,UAAM,KAAK,GAAG,UAAU;AACxB,QAAI,QAAQ,GACT,WAAW,yBAAgC,EAC3C,OAAO,CAAC,YAAmB,CAAC,EAC5B,MAAM,eAAsB,KAAK,MAAM,UAAU,EACjD,MAAM,aAAoB,KAAK,MAAM,QAAQ;AAChD,YAAQ,MAAM,mBAAmB,OAC7B,MAAM,MAAM,mBAA0B,MAAM,IAAW,IACvD,MAAM,MAAM,mBAA0B,KAAK,MAAM,cAAc;AACnE,UAAM,MAAM,MAAM,MAAM,iBAAiB;AACzC,UAAM,QAAS,KAAa;AAC5B,QAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,QAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAC1D,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,uBAAuB,EAAE;AAAA,EACrE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAAA,EACxE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAAA,EACvE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAC5E;AAEA,MAAM,2BAA2B;AAEjC,MAAM,yBAAyB,EAC5B,OAAO;AAAA,EACN,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC3D,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAAA,EAC1C,aAAa,EAAE,OAAO,QAAQ,EAAE,SAAS;AAAA,EACzC,QAAQ,EAAE,KAAK,CAAC,OAAO,QAAQ,OAAO,UAAU,CAAC,EAAE,SAAS;AAAA,EAC5D,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,SAAS;AAAA,EACvC,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,SAAS;AAAA,EACxC,KAAK,EAAE,OAAO,QAAQ,EAAE,SAAS;AAAA,EACjC,MAAM,EAAE,OAAO,QAAQ,EAAE,SAAS;AACpC,CAAC,EACA,YAAY;AAEf,MAAM,mBAAmB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,IAAI,CAAC;AAErD,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,OAAO,EAAE,MAAM,gBAAgB;AAAA,EAC/B,OAAO,EAAE,OAAO;AAAA,EAChB,MAAM,EAAE,OAAO;AAAA,EACf,UAAU,EAAE,OAAO;AAAA,EACnB,YAAY,EAAE,OAAO;AACvB,CAAC;AAED,eAAsB,IAAI,KAAc;AACtC,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,WAAW,IAAI,aAAa,IAAI,UAAU,KAAK;AACrD,MAAI,CAAC,SAAU,QAAO,aAAa,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE1F,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAU,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEhG,QAAM,kBAAkB,sBAAsB,IAAI,aAAa,IAAI,QAAQ,CAAC;AAC5E,QAAM,kBAAkB,IAAI,aAAa,IAAI,aAAa,KAAK,IAAI,aAAa,IAAI,cAAc,KAAK,IAAI,YAAY;AACvH,QAAM,sBAAsB,mBAAmB,SAAS,mBAAmB,UAAU,wBAAwB,IAAI,aAAa,IAAI,MAAM,GAAG,KAAK;AAChJ,QAAM,YAAY,wBAAwB,IAAI,aAAa,IAAI,KAAK,GAAG,KAAK;AAC5E,QAAM,eAAe,aAAa,mBAAmB;AACrD,QAAM,OAAO,eAAe,IAAI,KAAK,IAAI,SAAS,IAAI,aAAa,IAAI,MAAM,KAAK,KAAK,EAAE,KAAK,GAAG,CAAC;AAClG,QAAM,eAAe,KAAK,IAAI,KAAK,IAAI,SAAS,IAAI,aAAa,IAAI,UAAU,KAAK,MAAM,EAAE,KAAK,IAAI,CAAC,GAAG,GAAG;AAC5G,QAAM,WAAW,eAAe,KAAK,IAAI,cAAc,wBAAwB,IAAI;AACnF,QAAM,YAAY,IAAI,aAAa,IAAI,WAAW,KAAK;AACvD,QAAM,WAAW,IAAI,aAAa,IAAI,SAAS,KAAK,OAAO,YAAY,MAAM,SAAS,SAAS;AAC/F,QAAM,cAAc,wBAAwB,IAAI,aAAa,IAAI,aAAa,GAAG,KAAK;AAEtF,QAAM,YAAqC,CAAC;AAC5C,aAAW,CAAC,KAAK,GAAG,KAAK,IAAI,aAAa,QAAQ,GAAG;AACnD,QAAI,CAAC,YAAW,QAAO,YAAW,aAAY,WAAU,eAAc,UAAS,eAAc,gBAAe,OAAM,MAAM,EAAE,SAAS,GAAG,EAAG;AACzI,cAAU,KAAK,CAAC,KAAK,GAAG,CAAC;AAAA,EAC3B;AAEA,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,uBAAuB;AACjD,UAAM,KAAK,QAAQ,aAAa;AAChC,UAAM,KAAK,QAAQ,IAAI;AACvB,UAAM,OAAO,QAAQ,aAAa;AAClC,UAAM,QAAQ,MAAM,yBAAyB,EAAE,IAAI,MAAM,MAAM,YAAY,mCAAmC,GAAG,EAAE,CAAC;AACpH,QAAI,kBAAmC,MAAM;AAK7C,UAAM,aAAa,MAAM,sBAAsB,IAAI,QAAQ;AAC3D,QAAI,eAAe,SAAU,QAAO,6BAA6B,QAAQ;AACzE,UAAM,iBAAiB,eAAe;AACtC,UAAM,0BAA0B,EAAE,MAAM,UAAU,QAAQ,QAAQ,gBAAgB,KAAK,CAAC;AACxF,QAAI,mBAAmB,gBAAgB,WAAW,GAAG;AACnD,aAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,UAAU,YAAY,EAAE,CAAC;AAAA,IACjF;AACA,UAAM,6BAA6B,CAAC,UAAmB;AACrD,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAO,MAAM,IAAI,CAAC,UAAU;AAC1B,cAAI,OAAO,UAAU,SAAU,QAAO;AACtC,gBAAMA,UAAS,kBAAkB,KAAK;AACtC,iBAAOA,YAAW,OAAO,QAAQA;AAAA,QACnC,CAAC;AAAA,MACH;AACA,UAAI,OAAO,UAAU,SAAU,QAAO;AACtC,YAAM,SAAS,kBAAkB,KAAK;AACtC,aAAO,WAAW,OAAO,QAAQ;AAAA,IACnC;AACA,UAAM,SAAS,CAAC,QAAa;AAC3B,UAAI,CAAC,kBAAkB,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC/D,YAAM,MAA+B,CAAC;AACtC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,YAAI,EAAE,WAAW,KAAK,EAAG,KAAI,EAAE,QAAQ,QAAQ,EAAE,CAAC,IAAI,2BAA2B,CAAC;AAAA,YAC7E,KAAI,CAAC,IAAI;AAAA,MAChB;AACA,aAAO;AAAA,IACT;AACA,UAAM,aAAa,CAAC,QAAa;AAC/B,UAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,aAAO,EAAE,GAAI,IAAgC;AAAA,IAC/C;AAEA,UAAM,aAAyB,CAAC;AAChC,UAAM,cAAc,CAAC,KAAa,KAAa,gBAAyB;AACtE,UAAI,IAAI,WAAW,KAAK,GAAG;AACzB,YAAI,IAAI,SAAS,IAAI,GAAG;AACtB,gBAAM,OAAO,IAAI,MAAM,GAAG,EAAE;AAC5B,gBAAM,SAAS,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAChE,UAAC,WAAmB,IAAI,IAAI,EAAE,KAAK,OAAO;AAAA,QAC7C,OAAO;AACL,cAAI,IAAI,SAAS,GAAG,GAAG;AACrB,kBAAM,SAAS,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAChE,YAAC,WAAmB,GAAG,IAAI,EAAE,KAAK,OAAO;AAAA,UAC5C,OAAO;AACL,kBAAM,SAAS,kBAAkB,GAAG;AACnC,YAAC,WAAmB,GAAG,IAAI,WAAW,OAAO,MAAM;AAAA,UACtD;AAAA,QACF;AAAA,MACF,WAAW,aAAa;AACtB,YAAI,IAAI,SAAS,GAAG,GAAG;AACrB,gBAAM,SAAS,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAChE,UAAC,WAAmB,GAAG,IAAI,EAAE,KAAK,OAAO;AAAA,QAC5C,OAAO;AACL,gBAAM,SAAS,kBAAkB,GAAG;AACnC,UAAC,WAAmB,GAAG,IAAI,WAAW,OAAO,MAAM;AAAA,QACtD;AAAA,MACF,OAAO;AACL,YAAI,CAAC,MAAM,cAAc,cAAc,cAAc,QAAQ,SAAS,OAAO,EAAE,SAAS,GAAG,GAAG;AAC5F;AAAC,UAAC,WAAmB,GAAG,IAAI;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,mBAAmB,gBAAgB,QAAQ;AAC7C,MAAC,WAAmB,kBAAkB,EAAE,KAAK,gBAAgB;AAAA,IAC/D;AACA,UAAM,QAAsB;AAAA,MAC1B,UAAU,KAAK;AAAA,MACf,qBAAqB;AAAA,MACrB,MAAM,EAAE,MAAM,SAAS;AAAA,MACvB,MAAM,CAAC,EAAE,OAAO,WAAkB,KAAK,QAAe,CAAC;AAAA,MACvD,SAAS;AAAA,MACT;AAAA,IACF;AACA,QAAI,mBAAmB,gBAAgB,QAAQ;AAC7C,YAAM,kBAAkB;AAAA,IAC1B;AAIA,QAAI,eAAgB,OAAM,2BAA2B;AACrD,eAAW,CAAC,GAAG,CAAC,KAAK,UAAW,aAAY,GAAG,GAAG,cAAc;AAChE,UAAM,MAAM,MAAM,GAAG,MAAM,UAAiB,KAAK;AACjD,UAAM,WAAW,IAAI,SAAS,CAAC;AAC/B,UAAM,gBAAgB,SAAS,IAAI,MAAM;AACzC,UAAM,gBAAgB,SAAS,IAAI,UAAU;AAM7C,QAAI,kBAAkB,cAAc,QAAQ;AAC1C,UAAI;AACF,cAAM,YAAY,cACf,IAAI,CAAC,OAAY,IAAI,EAAE,EACvB,OAAO,CAAC,MAAwB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC;AACxE,YAAI,UAAU,QAAQ;AACpB,gBAAM,KAAK,GAAG,UAAU;AACxB,gBAAM,OAAO,MAAM,GAChB,WAAW,yBAAgC,EAC3C,OAAO,CAAC,aAAoB,YAAmB,CAAC,EAChD,MAAM,eAAsB,KAAK,QAAQ,EACzC,MAAM,aAAoB,MAAM,SAAgB,EAChD,QAAQ;AACX,gBAAM,cAAc,oBAAI,IAAoB;AAC5C,qBAAW,OAAO,MAAe;AAC/B,kBAAM,QAAQ,KAAK;AACnB,kBAAM,MAAM,iBAAiB,OAAO,MAAM,YAAY,IAAK,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACnH,gBAAI,OAAO,KAAK,UAAW,aAAY,IAAI,OAAO,IAAI,SAAS,GAAG,GAAG;AAAA,UACvE;AACA,qBAAW,QAAQ,eAAwB;AACzC,kBAAM,MAAM,YAAY,IAAI,OAAO,MAAM,EAAE,CAAC;AAC5C,gBAAI,KAAK;AACP,mBAAK,aAAa;AAClB,mBAAK,YAAY;AAAA,YACnB;AAAA,UACF;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAA8E;AAAA,IACxF;AAEA,UAAM,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,SAAS;AACnE,UAAM,oBAAoB,IAAI,YAAY;AAC1C,UAAM,UAAU;AAAA,MACd,OAAO;AAAA,MACP;AAAA,MACA,MAAM,IAAI,QAAQ;AAAA,MAClB,UAAU;AAAA,MACV,YAAY,KAAK,KAAK,SAAS,qBAAqB,EAAE;AAAA,IACxD;AAEA,QAAI,iBAAiB;AACnB,UAAI,cAAqB,sBAAsB,CAAC,GAAG,aAAa,IAAI,CAAC,GAAG,aAAa;AACrF,UAAI,QAAQ,YAAY,QAAQ;AAC9B,YAAI,WAAW;AACf,eAAO,YAAY,SAAS,OAAO;AACjC,gBAAM,UAAU,MAAM,GAAG,MAAM,UAAiB;AAAA,YAC9C,GAAG;AAAA,YACH,MAAM,EAAE,MAAM,UAAU,SAAS;AAAA,UACnC,CAAC;AACD,gBAAM,eAAe,QAAQ,SAAS,CAAC;AACvC,cAAI,CAAC,aAAa,OAAQ;AAC1B,gBAAM,gBAAgB,aAAa,IAAI,MAAM;AAC7C,gBAAM,gBAAgB,aAAa,IAAI,UAAU;AACjD,gBAAM,YAAY,sBAAsB,gBAAgB;AACxD,sBAAY,KAAK,GAAG,SAAS;AAC7B,cAAI,UAAU,SAAS,SAAU;AACjC,sBAAY;AAAA,QACd;AAAA,MACF;AACA,YAAM,WAAW;AAAA,QACf,SAAS,cAAc,WAAW;AAAA,QAClC,MAAM;AAAA,MACR;AACA,YAAM,eAAe,sBAAsB,GAAG,YAAY,SAAS,UAAU,YAAY;AACzF,YAAM,aAAa,gBAAgB,UAAU,eAAe;AAC5D,YAAM,WAAW,sBAAsB,cAAc,eAAe;AACpE,aAAO,IAAI,SAAS,WAAW,MAAM;AAAA,QACnC,SAAS;AAAA,UACP,gBAAgB,WAAW;AAAA,UAC3B,uBAAuB,yBAAyB,QAAQ;AAAA,QAC1D;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,aAAa,KAAK,OAAO;AAAA,EAClC,SAAS,GAAG;AACV,QAAI,gBAAgB,CAAC,EAAG,QAAO,aAAa,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;AAC7E,QAAI;AAAE,cAAQ,MAAM,gCAAgC,CAAC;AAAA,IAAE,QAAQ;AAAA,IAAC;AAChE,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAEA,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACrC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,MAAM,EACH,OAAO;AAAA,IACN,UAAU,EAAE,OAAO;AAAA,IACnB,UAAU,EAAE,OAAO;AAAA,EACrB,CAAC,EACA,SAAS;AACd,CAAC;AAED,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAU,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEhG,MAAI;AACJ,MAAI;AAAE,WAAO,MAAM,IAAI,KAAK;AAAA,EAAE,QAAQ;AAAE,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAAE;AAC7G,QAAM,SAAS,eAAe,UAAU,IAAI;AAC5C,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,KAAK,EAAE,OAAO,qBAAqB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9H,QAAM,EAAE,SAAS,IAAI,OAAO;AAC5B,MAAI,EAAE,UAAU,OAAO,IAAI,OAAO;AAElC,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,uBAAuB;AACjD,UAAM,KAAK,QAAQ,YAAY;AAC/B,UAAM,KAAK,QAAQ,IAAI;AACvB,UAAM,OAAO,QAAQ,aAAa;AAClC,UAAM,QAAQ,MAAM,yBAAyB,EAAE,IAAI,MAAM,MAAM,YAAY,mCAAmC,GAAG,EAAE,CAAC;AACpH,UAAM,cAAc,MAAM,cAAc,KAAK;AAC7C,QAAI,CAAC,YAAa,QAAO,aAAa,KAAK,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AACzG,UAAM,aAAa,MAAM,sBAAsB,IAAI,QAAQ;AAC3D,QAAI,eAAe,SAAU,QAAO,6BAA6B,QAAQ;AACzE,UAAM,iBAAiB,eAAe;AACtC,UAAM,0BAA0B,EAAE,MAAM,UAAU,QAAQ,UAAU,gBAAgB,KAAK,CAAC;AAC1F,UAAM,OAAO,gBAAgB,MAAM;AAGnC,QAAI;AACF,YAAM,EAAE,gCAAgC,IAAI,MAAM,OAAO,mBAAmB;AAC5E,YAAM,QAAQ,MAAM,gCAAgC,IAAI,EAAE,UAAU,gBAAgB,aAAa,UAAU,KAAK,UAAW,QAAQ,MAAM,sBAAsB,KAAK,CAAC;AACrK,UAAI,CAAC,MAAM,GAAI,QAAO,aAAa,KAAK,EAAE,OAAO,qBAAqB,QAAQ,MAAM,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpH,QAAQ;AAAA,IAAiC;AAEzC,UAAM,sBAAsB,MAAM;AAChC,YAAM,MAAM,OAAO,YAAY,EAAE,EAAE,KAAK;AACxC,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,MAAM,IAAI,YAAY;AAC5B,UAAI,QAAQ,YAAY,QAAQ,SAAS,QAAQ,UAAU,QAAQ,YAAa,QAAO;AAEvF,YAAM,OAAO;AACb,aAAO,KAAK,KAAK,GAAG,IAAI,MAAM;AAAA,IAChC,GAAG;AACH,UAAM,EAAE,GAAG,IAAI,MAAM,GAAG,yBAAyB;AAAA,MAC/C;AAAA,MACA,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,UAAU,KAAK;AAAA,MACf,QAAQ;AAAA,IACV,CAAC;AAED,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,MAAM,EAAE,UAAU,UAAU,GAAG,EAAE,CAAC;AAAA,EACzE,SAAS,GAAG;AACV,QAAI,gBAAgB,CAAC,EAAG,QAAO,aAAa,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;AAC7E,QAAI;AAAE,cAAQ,MAAM,iCAAiC,CAAC;AAAA,IAAE,QAAQ;AAAA,IAAC;AACjE,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAGA,SAAS,aAAa,MAAmI;AACvJ,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,EAAE,IAAI,OAAO,OAAO,eAAe;AACjF,QAAM,WAAW,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,SAAS,KAAK,WAAW;AAC7F,QAAM,WAAW,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,SAAS,KAAK,WAAW;AAC7F,QAAM,SAAU,KAAK,UAAU,OAAO,KAAK,WAAW,WAAY,KAAK,SAAgC,CAAC;AACxG,MAAI,CAAC,YAAY,CAAC,SAAU,QAAO,EAAE,IAAI,OAAO,OAAO,qCAAqC;AAC5F,SAAO,EAAE,IAAI,MAAM,MAAM,EAAE,UAAU,UAAU,OAAO,EAAE;AAC1D;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAU,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEhG,MAAI;AACJ,MAAI;AAAE,WAAO,MAAM,IAAI,KAAK;AAAA,EAAE,QAAQ;AAAE,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAAE;AAC7G,QAAM,SAAS,aAAa,IAAI;AAChC,MAAI,CAAC,OAAO,GAAI,QAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,IAAI,CAAC;AACjF,QAAM,EAAE,UAAU,UAAU,OAAO,IAAI,OAAO;AAE9C,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,uBAAuB;AACjD,UAAM,KAAK,QAAQ,YAAY;AAC/B,UAAM,KAAK,QAAQ,IAAI;AACvB,UAAM,OAAO,QAAQ,aAAa;AAClC,UAAM,QAAQ,MAAM,yBAAyB,EAAE,IAAI,MAAM,MAAM,YAAY,mCAAmC,GAAG,EAAE,CAAC;AACpH,UAAM,cAAc,MAAM,cAAc,KAAK;AAC7C,QAAI,CAAC,YAAa,QAAO,aAAa,KAAK,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AACzG,UAAM,aAAa,MAAM,sBAAsB,IAAI,QAAQ;AAC3D,QAAI,eAAe,SAAU,QAAO,6BAA6B,QAAQ;AACzE,UAAM,iBAAiB,eAAe;AACtC,UAAM,0BAA0B,EAAE,MAAM,UAAU,QAAQ,UAAU,gBAAgB,KAAK,CAAC;AAC1F,UAAM,OAAO,gBAAgB,MAAM;AAGnC,QAAI;AACF,YAAM,EAAE,gCAAgC,IAAI,MAAM,OAAO,mBAAmB;AAC5E,YAAM,QAAQ,MAAM,gCAAgC,IAAI,EAAE,UAAU,gBAAgB,aAAa,UAAU,KAAK,UAAW,QAAQ,MAAM,sBAAsB,KAAK,CAAC;AACrK,UAAI,CAAC,MAAM,GAAI,QAAO,aAAa,KAAK,EAAE,OAAO,qBAAqB,QAAQ,MAAM,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpH,QAAQ;AAAA,IAAiC;AAGzC,UAAM,SAAS;AACf,UAAM,MAAM,OAAO,YAAY,EAAE,EAAE,KAAK;AACxC,UAAM,MAAM,IAAI,YAAY;AAC5B,UAAM,aAAa,CAAC,OAAO,QAAQ,YAAY,QAAQ,SAAS,QAAQ,UAAU,QAAQ;AAC1F,UAAM,SAAS,OAAO,KAAK,GAAG;AAC9B,QAAI,cAAc,CAAC,QAAQ;AACzB,YAAM,UAAU,MAAM,GAAG,yBAAyB;AAAA,QAChD;AAAA,QACA,UAAU;AAAA,QACV,gBAAgB;AAAA,QAChB,UAAU,KAAK;AAAA,QACf,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,aAAa,KAAK,EAAE,IAAI,MAAM,MAAM,EAAE,UAAU,UAAU,QAAQ,GAAG,EAAE,CAAC;AAAA,IACjF;AAEA,QAAI;AACF,YAAM,mBAAmB,MAAM,gCAAgC,IAAI;AAAA,QACjE,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,gBAAgB;AAAA,MAClB,CAAC;AACD,mCAA6B;AAAA,QAC3B,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,WAAW;AAClB,UAAI,gBAAgB,SAAS,GAAG;AAC9B,eAAO,aAAa,KAAK,UAAU,MAAM,EAAE,QAAQ,UAAU,OAAO,CAAC;AAAA,MACvE;AACA,YAAM;AAAA,IACR;AAEA,UAAM,GAAG,yBAAyB;AAAA,MAChC;AAAA,MACA,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,UAAU,KAAK;AAAA,MACf,QAAQ;AAAA,IACV,CAAC;AACD,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,MAAM,EAAE,UAAU,UAAU,IAAI,EAAE,CAAC;AAAA,EAC1E,SAAS,GAAG;AACV,QAAI,gBAAgB,CAAC,EAAG,QAAO,aAAa,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;AAC7E,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAEA,MAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAC5B,CAAC;AAED,eAAsB,OAAO,KAAc;AACzC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAU,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEhG,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,aAAa,IAAI,aAAa,IAAI,UAAU;AAClD,QAAM,aAAa,IAAI,aAAa,IAAI,UAAU;AAClD,MAAI,UAAe,cAAc,aAAa,EAAE,UAAU,YAAY,UAAU,WAAW,IAAI;AAC/F,MAAI,CAAC,SAAS;AACZ,QAAI;AAAE,gBAAU,MAAM,IAAI,KAAK;AAAA,IAAE,QAAQ;AAAE,gBAAU;AAAA,IAAK;AAAA,EAC5D;AACA,QAAM,SAAS,iBAAiB,UAAU,OAAO;AACjD,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,KAAK,EAAE,OAAO,qBAAqB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9H,QAAM,EAAE,UAAU,SAAS,IAAI,OAAO;AAEtC,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,uBAAuB;AACjD,UAAM,KAAK,QAAQ,YAAY;AAC/B,UAAM,KAAK,QAAQ,IAAI;AACvB,UAAM,OAAO,QAAQ,aAAa;AAClC,UAAM,QAAQ,MAAM,yBAAyB,EAAE,IAAI,MAAM,MAAM,YAAY,mCAAmC,GAAG,EAAE,CAAC;AACpH,UAAM,cAAc,MAAM,cAAc,KAAK;AAC7C,QAAI,CAAC,YAAa,QAAO,aAAa,KAAK,EAAE,OAAO,mCAAmC,GAAG,EAAE,QAAQ,IAAI,CAAC;AACzG,UAAM,aAAa,MAAM,sBAAsB,IAAI,QAAQ;AAC3D,QAAI,eAAe,SAAU,QAAO,6BAA6B,QAAQ;AACzE,UAAM,iBAAiB,eAAe;AACtC,UAAM,0BAA0B,EAAE,MAAM,UAAU,QAAQ,UAAU,gBAAgB,KAAK,CAAC;AAE1F,QAAI;AACF,YAAM,mBAAmB,MAAM,gCAAgC,IAAI;AAAA,QACjE,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,gBAAgB;AAAA,MAClB,CAAC;AACD,mCAA6B;AAAA,QAC3B,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,WAAW;AAClB,UAAI,gBAAgB,SAAS,GAAG;AAC9B,eAAO,aAAa,KAAK,UAAU,MAAM,EAAE,QAAQ,UAAU,OAAO,CAAC;AAAA,MACvE;AACA,YAAM;AAAA,IACR;AAEA,UAAM,GAAG,yBAAyB,EAAE,UAAU,UAAU,gBAAgB,aAAa,UAAU,KAAK,UAAW,MAAM,KAAK,CAAC;AAC3H,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC,SAAS,GAAG;AACV,QAAI,gBAAgB,CAAC,EAAG,QAAO,aAAa,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;AAC7E,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAEA,SAAS,gBAAgB,OAAiD;AACxE,QAAM,MAA2B,CAAC;AAClC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,CAAC,CAAC,GAAG;AAChD,UAAM,MAAM,EAAE,WAAW,KAAK,IAAI,EAAE,QAAQ,QAAQ,EAAE,IAAI;AAC1D,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;AAEA,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,IAAI,EAAE,QAAQ,IAAI;AACpB,CAAC;AAED,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,OAAO,EAAE,OAAO;AAClB,CAAC,EAAE,YAAY;AAER,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,OAAO;AAAA,MACP,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": ["parsed"]
|
|
7
7
|
}
|
|
@@ -14,6 +14,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
|
14
14
|
import { raiseCrudError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
15
15
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
16
16
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
17
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
17
18
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
18
19
|
import { ErrorMessage, LoadingMessage } from "@open-mercato/ui/backend/detail";
|
|
19
20
|
import { useRecordsEntityGuard } from "@open-mercato/core/modules/entities/components/useRecordsEntityGuard";
|
|
@@ -41,6 +42,7 @@ function RecordsPage({ params }) {
|
|
|
41
42
|
return /* @__PURE__ */ jsx(RecordsPageInner, { params });
|
|
42
43
|
}
|
|
43
44
|
function RecordsPageInner({ params }) {
|
|
45
|
+
const t = useT();
|
|
44
46
|
const entityId = decodeURIComponent(params?.entityId || "");
|
|
45
47
|
const [sorting, setSorting] = React.useState([{ id: "id", desc: false }]);
|
|
46
48
|
const [page, setPage] = React.useState(1);
|
|
@@ -54,6 +56,11 @@ function RecordsPageInner({ params }) {
|
|
|
54
56
|
const [loading, setLoading] = React.useState(false);
|
|
55
57
|
const scopeVersion = useOrganizationScopeVersion();
|
|
56
58
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
59
|
+
const deleteMutationContextId = `entities.user.records.${entityId}:single-delete`;
|
|
60
|
+
const { runMutation: runDeleteMutation, retryLastMutation: retryDeleteMutation } = useGuardedMutation({
|
|
61
|
+
contextId: deleteMutationContextId,
|
|
62
|
+
blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
|
|
63
|
+
});
|
|
57
64
|
const { data: cfDefs = [] } = useCustomFieldDefs(entityId, {
|
|
58
65
|
enabled: Boolean(entityId),
|
|
59
66
|
keyExtras: [scopeVersion]
|
|
@@ -300,22 +307,33 @@ export RECORD_ID="<record uuid>"` }) }),
|
|
|
300
307
|
items: [
|
|
301
308
|
{ id: "edit", label: "Edit", href: `/backend/entities/user/${encodeURIComponent(entityId)}/records/${encodeURIComponent(String(row.id))}` },
|
|
302
309
|
{ id: "delete", label: "Delete", destructive: true, onSelect: async () => {
|
|
310
|
+
const recordId = String(row.id);
|
|
303
311
|
try {
|
|
304
312
|
const confirmed = await confirm({
|
|
305
313
|
title: "Delete this record?",
|
|
306
314
|
variant: "destructive"
|
|
307
315
|
});
|
|
308
316
|
if (!confirmed) return;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
await runDeleteMutation({
|
|
318
|
+
operation: async () => {
|
|
319
|
+
const deleteCall = await withScopedApiRequestHeaders(
|
|
320
|
+
buildOptimisticLockHeader(row.updatedAt),
|
|
321
|
+
() => apiCall(
|
|
322
|
+
`/api/entities/records?entityId=${encodeURIComponent(entityId)}&recordId=${encodeURIComponent(recordId)}`,
|
|
323
|
+
{ method: "DELETE" }
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
if (!deleteCall.ok) {
|
|
327
|
+
await raiseCrudError(deleteCall.response, "Failed to delete record");
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
context: {
|
|
331
|
+
formId: deleteMutationContextId,
|
|
332
|
+
resourceKind: "entities.record",
|
|
333
|
+
resourceId: recordId,
|
|
334
|
+
retryLastMutation: retryDeleteMutation
|
|
335
|
+
}
|
|
336
|
+
});
|
|
319
337
|
const j = await readApiResultOrThrow(
|
|
320
338
|
`/api/entities/records?entityId=${encodeURIComponent(entityId)}&page=${page}&pageSize=${pageSize}`,
|
|
321
339
|
void 0,
|