@open-mercato/core 0.6.6-develop.6398.1.013efe913e → 0.6.6-develop.6401.1.8c07df34a7
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/auth/lib/backendChrome.js +3 -2
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/currencies/backend/config/currency-fetching/page.meta.js +4 -13
- package/dist/modules/currencies/backend/config/currency-fetching/page.meta.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.meta.js +1 -1
- package/dist/modules/currencies/backend/currencies/page.meta.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.meta.js +1 -0
- package/dist/modules/currencies/backend/exchange-rates/page.meta.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +29 -2
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +9 -2
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.manage.js +14 -3
- package/dist/modules/entities/api/definitions.manage.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +43 -17
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/lib/definitions-version.js +55 -0
- package/dist/modules/entities/lib/definitions-version.js.map +7 -0
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +4 -6
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/lib/backendChrome.tsx +3 -2
- package/src/modules/currencies/backend/config/currency-fetching/page.meta.ts +4 -15
- package/src/modules/currencies/backend/currencies/page.meta.ts +1 -1
- package/src/modules/currencies/backend/exchange-rates/page.meta.ts +1 -0
- package/src/modules/entities/api/definitions.batch.ts +38 -1
- package/src/modules/entities/api/definitions.manage.ts +14 -1
- package/src/modules/entities/api/definitions.ts +10 -1
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +53 -17
- package/src/modules/entities/lib/definitions-version.ts +102 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +4 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/api/definitions.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 type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomEntity, CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { upsertCustomFieldDefSchema, fieldsetCodeRegex } from '@open-mercato/core/modules/entities/data/validators'\nimport {\n createDefinitionsCacheKey,\n createDefinitionsCacheTags,\n invalidateDefinitionsCache,\n ENTITY_DEFINITIONS_CACHE_TTL_MS,\n} from './definitions.cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { filterSelectableSystemEntityIds, isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { loadEntityFieldsetConfigs, CustomFieldsetDefinition } from '../lib/fieldsets'\nimport { installCustomEntitiesFromModules } from '../lib/install-from-ce'\nimport { normalizeCustomFieldOptions } from '@open-mercato/shared/modules/entities/options'\nimport { CURRENCY_OPTIONS_URL } from '@open-mercato/shared/modules/entities/kinds'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n beginEntitiesMutationGuard,\n FIELD_DEFINITION_RESOURCE_KIND,\n} from './definitions.mutation-guard'\nimport {\n createExactDefinitionWhere,\n createScopedDefinitionTombstone,\n createVisibleDefinitionWhere,\n markDefinitionTombstoned,\n resolveDefinitionScopeFromOrganizationScope,\n resolveDefinitionMutationScope,\n selectVisibleDefinitionWinner,\n} from '../lib/definition-scope'\n\n/**\n * Validate defaultValue against the field kind. Returns an error message string\n * if invalid, or null if valid. For dictionary and currency kinds, queries the\n * database to verify the token exists.\n */\nasync function validateDefaultValueByKind(\n value: unknown,\n kind: string,\n cfg: Record<string, unknown>,\n em: EntityManager,\n tenantContext: { tenantId: string | null; organizationId: string | null },\n): Promise<string | null> {\n switch (kind) {\n case 'boolean':\n if (typeof value !== 'boolean') return 'defaultValue for boolean fields must be true or false'\n return null\n case 'integer':\n if (typeof value !== 'number' || !Number.isInteger(value))\n return 'defaultValue for integer fields must be a whole number'\n return null\n case 'float':\n if (typeof value !== 'number' || !isFinite(value))\n return 'defaultValue for float fields must be a finite number'\n return null\n case 'text':\n case 'multiline':\n case 'date':\n case 'datetime':\n if (typeof value !== 'string')\n return `defaultValue for ${kind} fields must be a string`\n return null\n case 'dictionary': {\n if (typeof value !== 'string')\n return 'defaultValue for dictionary fields must be a string'\n const dictionaryId = typeof cfg.dictionaryId === 'string' ? cfg.dictionaryId.trim() : ''\n if (dictionaryId) {\n try {\n const { DictionaryEntry } = await import('@open-mercato/core/modules/dictionaries/data/entities')\n const entry = await em.findOne(DictionaryEntry, {\n dictionary: dictionaryId,\n value: value,\n ...(tenantContext.organizationId ? { organizationId: tenantContext.organizationId } : {}),\n ...(tenantContext.tenantId ? { tenantId: tenantContext.tenantId } : {}),\n })\n if (!entry) return `defaultValue \"${value}\" does not match any entry in the configured dictionary`\n } catch (err) {\n // If the dictionaries module is not available, skip entry validation\n console.debug('[entities.definitions] dictionary validation skipped \u2014 module not available', err)\n }\n }\n return null\n }\n case 'currency': {\n if (typeof value !== 'string')\n return 'defaultValue for currency fields must be a string'\n try {\n const { Currency } = await import('@open-mercato/core/modules/currencies/data/entities')\n const where: Record<string, string> = { code: value as string }\n if (tenantContext.tenantId) where.tenantId = tenantContext.tenantId\n if (tenantContext.organizationId) where.organizationId = tenantContext.organizationId\n const currency = await em.findOne(Currency, where)\n if (!currency) return `defaultValue \"${value}\" does not match any available currency`\n } catch (err) {\n // If the currencies module is not available, skip currency validation\n console.debug('[entities.definitions] currency validation skipped \u2014 module not available', err)\n }\n return null\n }\n case 'select': {\n if (typeof value !== 'string' && typeof value !== 'number')\n return 'defaultValue for select fields must be a string or number'\n const options = normalizeCustomFieldOptions(\n Array.isArray(cfg.options) ? cfg.options : [],\n )\n if (options.length > 0) {\n const match = options.some(\n (o) => o.value === value || String(o.value) === String(value),\n )\n if (!match) return 'defaultValue does not match any of the configured options'\n }\n return null\n }\n case 'relation':\n case 'attachment':\n return `defaultValue is not supported for ${kind} fields`\n default:\n return null\n }\n}\n\n/**\n * Normalize defaultValue to the canonical storage shape for the given kind.\n */\nfunction normalizeDefaultValueByKind(value: unknown, kind: string): unknown {\n switch (kind) {\n case 'boolean':\n return value === true\n case 'integer':\n return Math.round(Number(value))\n case 'float':\n return Number(value)\n default:\n return value\n }\n}\n\nexport const metadata = {\n // Reading definitions is needed by record forms; keep it auth-protected but accessible to all authenticated users\n GET: { requireAuth: true },\n // Mutations remain admin-only\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nfunction parseEntityIds(url: URL): string[] {\n const direct = url.searchParams.getAll('entityId').filter((id) => typeof id === 'string' && id.trim().length > 0)\n const combined = url.searchParams.get('entityIds')\n if (combined) {\n combined\n .split(',')\n .map((id) => id.trim())\n .filter((id) => id.length > 0)\n .forEach((id) => direct.push(id))\n }\n const unique: string[] = []\n const seen = new Set<string>()\n for (const id of direct) {\n if (seen.has(id)) continue\n seen.add(id)\n unique.push(id)\n }\n return unique\n}\n\nasync function resolveEntityDefaultEditor(em: any, entityId: string, tenantId: string | null | undefined): Promise<string | undefined> {\n try {\n const ent = await em.findOne(CustomEntity, {\n entityId,\n $and: [\n { $or: [ { tenantId: tenantId ?? undefined as any }, { tenantId: null } ] },\n ],\n } as any)\n if (ent && typeof (ent as any).defaultEditor === 'string') return (ent as any).defaultEditor\n } catch {}\n return undefined\n}\n\nfunction normalizeFieldGroup(raw: unknown): { code: string; title?: string; hint?: string } | undefined {\n if (!raw) return undefined\n if (typeof raw === 'string') {\n const code = raw.trim()\n return code ? { code } : undefined\n }\n if (typeof raw !== 'object') return undefined\n const entry = raw as Record<string, unknown>\n const code = typeof entry.code === 'string' ? entry.code.trim() : ''\n if (!code) return undefined\n const group: { code: string; title?: string; hint?: string } = { code }\n if (typeof entry.title === 'string' && entry.title.trim()) group.title = entry.title.trim()\n if (typeof entry.hint === 'string' && entry.hint.trim()) group.hint = entry.hint.trim()\n return group\n}\n\nfunction definitionMatchesReadScope(\n definition: { tenantId?: string | null; organizationId?: string | null },\n scope: { tenantId: string | null; organizationId: string | null },\n) {\n const definitionTenantId = definition.tenantId ?? null\n const definitionOrganizationId = definition.organizationId ?? null\n const tenantMatches = definitionTenantId === null || definitionTenantId === scope.tenantId\n const organizationMatches =\n definitionOrganizationId === null ||\n (scope.organizationId !== null && definitionOrganizationId === scope.organizationId)\n return tenantMatches && organizationMatches\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const requestedEntityIds = parseEntityIds(url)\n if (!requestedEntityIds.length) {\n return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n }\n const requestedFieldset = url.searchParams.get('fieldset')\n const fieldsetFilter = requestedFieldset && requestedFieldset.trim().length ? requestedFieldset.trim() : null\n\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const entityIds = filterSelectableSystemEntityIds(requestedEntityIds)\n if (!entityIds.length) {\n return NextResponse.json({ items: [] })\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const definitionScope = resolveDefinitionScopeFromOrganizationScope(auth, scope)\n const tenantId = definitionScope.tenantId\n if (!tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = definitionScope.organizationId\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 let canManageDefinitions = false\n if (typeof auth.sub === 'string' && auth.sub.length > 0) {\n try {\n const rbac = resolve('rbacService') as RbacService\n canManageDefinitions = await rbac.userHasAllFeatures(auth.sub, ['entities.definitions.manage'], {\n tenantId,\n organizationId,\n })\n } catch {\n // RBAC service unavailable \u2014 fall back to read-only (no definition sync)\n }\n }\n\n if (canManageDefinitions) {\n try {\n await installCustomEntitiesFromModules(em, cache, {\n tenantIds: [tenantId],\n entityIds,\n includeGlobal: true,\n createOnly: true,\n })\n } catch (err) {\n console.warn('[entities.definitions] Failed to synchronize module-backed definitions', {\n tenantId,\n entityIds,\n err,\n })\n }\n }\n\n let cacheKey: string | null = null\n if (cache && !fieldsetFilter) {\n cacheKey = createDefinitionsCacheKey({\n tenantId,\n organizationId,\n entityIds,\n })\n try {\n const cached = await cache.get(cacheKey)\n if (cached) {\n return NextResponse.json(cached)\n }\n } catch (err) {\n console.warn('[entities.definitions.cache] Failed to read cache', err)\n }\n }\n\n const fieldsetConfigs = await loadEntityFieldsetConfigs(em, {\n entityIds,\n tenantId,\n organizationId,\n mode: 'public',\n })\n\n const tenantCandidates = [{ tenantId }, { tenantId: null as string | null }]\n const organizationCandidates = [{ organizationId: null as string | null }]\n if (organizationId) organizationCandidates.unshift({ organizationId })\n const readScope = { tenantId, organizationId }\n\n const whereActive = {\n entityId: { $in: entityIds as any },\n deletedAt: null,\n $and: [\n { $or: tenantCandidates },\n { $or: organizationCandidates },\n ],\n } as any\n const defs = (await em.find(CustomFieldDef, whereActive as any))\n .filter((definition: any) => definitionMatchesReadScope(definition, readScope))\n const tombstones = (await em.find(CustomFieldDef, {\n entityId: { $in: entityIds as any },\n deletedAt: { $ne: null } as any,\n $and: [\n { $or: tenantCandidates },\n { $or: organizationCandidates },\n ],\n } as any)).filter((definition: any) => definitionMatchesReadScope(definition, readScope))\n\n const tombstonedByEntity = new Map<string, Set<string>>()\n for (const entry of tombstones as any[]) {\n const eid = String(entry.entityId)\n if (!tombstonedByEntity.has(eid)) tombstonedByEntity.set(eid, new Set())\n tombstonedByEntity.get(eid)!.add(entry.key)\n }\n\n const scopeScore = (x: any) => (x.tenantId ? 2 : 0) + (x.organizationId ? 1 : 0)\n\n const definitionsByEntity = new Map<string, any[]>()\n for (const d of defs) {\n const eid = String(d.entityId)\n if (!definitionsByEntity.has(eid)) definitionsByEntity.set(eid, [])\n definitionsByEntity.get(eid)!.push(d)\n }\n\n const entityDefaultEditors = new Map<string, string | undefined>()\n for (const entityId of entityIds) {\n const editor = await resolveEntityDefaultEditor(em, entityId, tenantId ?? null)\n entityDefaultEditors.set(entityId, editor)\n }\n\n const items: any[] = []\n\n const entityOrder = new Map<string, number>()\n entityIds.forEach((id, idx) => entityOrder.set(id, idx))\n\n for (const entityId of entityIds) {\n const defsForEntity = definitionsByEntity.get(entityId) ?? []\n if (!defsForEntity.length) continue\n const tombstonedKeys = tombstonedByEntity.get(entityId) ?? new Set<string>()\n const byKey = new Map<string, any>()\n for (const d of defsForEntity) {\n const existing = byKey.get(d.key)\n if (!existing) { byKey.set(d.key, d); continue }\n const sNew = scopeScore(d)\n const sOld = scopeScore(existing)\n if (sNew > sOld) { byKey.set(d.key, d); continue }\n if (sNew < sOld) continue\n const tNew = (d.updatedAt instanceof Date) ? d.updatedAt.getTime() : new Date(d.updatedAt).getTime()\n const tOld = (existing.updatedAt instanceof Date) ? existing.updatedAt.getTime() : new Date(existing.updatedAt).getTime()\n if (tNew >= tOld) byKey.set(d.key, d)\n }\n\n const defaultEditor = entityDefaultEditors.get(entityId)\n const winning = Array.from(byKey.values()).filter((d: any) => (d.isActive !== false) && !tombstonedKeys.has(d.key))\n winning.sort((a: any, b: any) => ((a.configJson?.priority ?? 0) - (b.configJson?.priority ?? 0)))\n\n for (const d of winning) {\n const rawFieldset = typeof d.configJson?.fieldset === 'string' ? d.configJson.fieldset.trim() : ''\n const normalizedFieldset = rawFieldset.length > 0 ? rawFieldset : undefined\n const normalizedFieldsets = Array.isArray(d.configJson?.fieldsets)\n ? d.configJson.fieldsets\n .filter((entry: unknown): entry is string => typeof entry === 'string')\n .map((entry: string) => entry.trim())\n .filter((entry: string) => entry.length > 0)\n : []\n const effectiveFieldsets = normalizedFieldsets.length > 0\n ? normalizedFieldsets\n : (normalizedFieldset ? [normalizedFieldset] : [])\n if (fieldsetFilter && !effectiveFieldsets.includes(fieldsetFilter)) continue\n const groupInfo = normalizeFieldGroup(d.configJson?.group)\n const keyLower = String(d.key).toLowerCase()\n const candidateBase = {\n key: d.key,\n kind: d.kind,\n label: d.configJson?.label || d.key,\n description: d.configJson?.description || undefined,\n multi: Boolean(d.configJson?.multi),\n options: (() => {\n if (d.kind === 'currency') return undefined\n const normalizedOptions = normalizeCustomFieldOptions(d.configJson?.options)\n return normalizedOptions.length ? normalizedOptions : undefined\n })(),\n optionsUrl: (() => {\n if (d.kind === 'currency') return CURRENCY_OPTIONS_URL\n const dictionaryId = typeof d.configJson?.dictionaryId === 'string' ? d.configJson.dictionaryId : undefined\n if (dictionaryId) return `/api/dictionaries/${dictionaryId}/entries`\n return typeof d.configJson?.optionsUrl === 'string' ? d.configJson.optionsUrl : undefined\n })(),\n filterable: Boolean(d.configJson?.filterable),\n formEditable: d.configJson?.formEditable !== undefined ? Boolean(d.configJson.formEditable) : true,\n listVisible: d.configJson?.listVisible !== undefined ? Boolean(d.configJson.listVisible) : true,\n editor: typeof d.configJson?.editor === 'string'\n ? d.configJson.editor\n : (d.kind === 'multiline' ? defaultEditor : undefined),\n input: typeof d.configJson?.input === 'string' ? d.configJson.input : undefined,\n dictionaryId: typeof d.configJson?.dictionaryId === 'string' ? d.configJson.dictionaryId : undefined,\n dictionaryInlineCreate: d.configJson?.dictionaryInlineCreate !== undefined\n ? Boolean(d.configJson.dictionaryInlineCreate)\n : undefined,\n priority: typeof d.configJson?.priority === 'number' ? d.configJson.priority : 0,\n validation: Array.isArray(d.configJson?.validation) ? d.configJson.validation : undefined,\n defaultValue: d.configJson?.defaultValue !== undefined ? d.configJson.defaultValue : undefined,\n // attachments config passthrough\n maxAttachmentSizeMb: typeof d.configJson?.maxAttachmentSizeMb === 'number' ? d.configJson.maxAttachmentSizeMb : undefined,\n acceptExtensions: Array.isArray(d.configJson?.acceptExtensions) ? d.configJson.acceptExtensions : undefined,\n entityId,\n fieldset: normalizedFieldset ?? effectiveFieldsets[0],\n fieldsets: effectiveFieldsets.length > 0 ? effectiveFieldsets : undefined,\n group: groupInfo,\n } as any\n const metrics = computeDefinitionScore(d, candidateBase, entityOrder.get(entityId) ?? Number.MAX_SAFE_INTEGER)\n const candidate = { ...candidateBase, __score: metrics }\n const existing = (items as any[]).find((entry) => entry.key.toLowerCase() === keyLower)\n if (!existing) {\n items.push(candidate)\n continue\n }\n const existingScore = existing.__score as { base: number; penalty: number; entityIndex: number }\n const candidateScoreInfo = candidate.__score as { base: number; penalty: number; entityIndex: number }\n const better =\n candidateScoreInfo.base > existingScore.base ||\n (candidateScoreInfo.base === existingScore.base && candidateScoreInfo.penalty < existingScore.penalty) ||\n (candidateScoreInfo.base === existingScore.base && candidateScoreInfo.penalty === existingScore.penalty && candidateScoreInfo.entityIndex < existingScore.entityIndex)\n if (better) {\n const index = items.findIndex((entry) => entry.key.toLowerCase() === keyLower)\n if (index >= 0) items[index] = candidate\n }\n }\n }\n\n const sanitized = items.map((item: any) => {\n const { __score, ...rest } = item\n return rest\n })\n sanitized.sort((a: any, b: any) => ((a.priority ?? 0) - (b.priority ?? 0)))\n\n const fieldsetsByEntity: Record<string, CustomFieldsetDefinition[]> = {}\n const entitySettings: Record<string, { singleFieldsetPerRecord: boolean }> = {}\n for (const entityId of entityIds) {\n const cfg = fieldsetConfigs.get(entityId) ?? { fieldsets: [], singleFieldsetPerRecord: true }\n fieldsetsByEntity[entityId] = cfg.fieldsets\n entitySettings[entityId] = { singleFieldsetPerRecord: cfg.singleFieldsetPerRecord }\n }\n\n const responseBody = { items: sanitized, fieldsetsByEntity, entitySettings }\n\n if (cache && cacheKey && !fieldsetFilter) {\n const tags = createDefinitionsCacheTags({\n tenantId,\n organizationId,\n entityIds,\n })\n try {\n await cache.set(cacheKey, responseBody, {\n ttl: ENTITY_DEFINITIONS_CACHE_TTL_MS,\n tags,\n })\n } catch (err) {\n console.warn('[entities.definitions.cache] Failed to store cache entry', err)\n }\n }\n\n return NextResponse.json(responseBody)\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 parsed = upsertCustomFieldDefSchema.safeParse(body)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const input = parsed.data\n\n if (!isSystemEntitySelectable(input.entityId)) {\n return NextResponse.json({ error: 'Custom fields are not supported for this entity' }, { status: 400 })\n }\n\n if (input.kind === 'dictionary') {\n const dictionaryId = input.configJson?.dictionaryId\n if (typeof dictionaryId !== 'string' || dictionaryId.trim().length === 0) {\n return NextResponse.json({ error: 'dictionaryId is required for dictionary custom fields' }, { status: 400 })\n }\n }\n\n const container = await createRequestContainer()\n const scope = await resolveDefinitionMutationScope({ auth, container, request: req })\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 = createExactDefinitionWhere(input.entityId, input.key, scope)\n let def = await em.findOne(CustomFieldDef, where)\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: FIELD_DEFINITION_RESOURCE_KIND,\n resourceId: def ? def.id : `${input.entityId}:${input.key}`,\n operation: def ? 'update' : 'create',\n mutationPayload: input as unknown as Record<string, unknown>,\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n if (!def) def = em.create(CustomFieldDef, { ...where, createdAt: new Date() })\n def.kind = input.kind\n const inCfg = (input as any).configJson ?? {}\n const cfg: Record<string, any> = { ...inCfg }\n if (cfg.label == null || String(cfg.label).trim() === '') cfg.label = input.key\n if (cfg.formEditable === undefined) cfg.formEditable = true\n if (cfg.listVisible === undefined) cfg.listVisible = true\n if (input.kind === 'dictionary') {\n const dictionaryId = typeof cfg.dictionaryId === 'string' ? cfg.dictionaryId.trim() : ''\n cfg.dictionaryId = dictionaryId\n cfg.dictionaryInlineCreate = cfg.dictionaryInlineCreate !== false\n }\n if (input.kind === 'currency') {\n cfg.optionsUrl = CURRENCY_OPTIONS_URL\n if (Array.isArray(cfg.options)) delete cfg.options\n }\n if (input.kind === 'multiline' && (cfg.editor == null || String(cfg.editor).trim() === '')) {\n cfg.editor = 'markdown'\n }\n // Validate and normalize defaultValue by kind before persisting\n if (cfg.defaultValue !== undefined && cfg.defaultValue !== null) {\n const validationError = await validateDefaultValueByKind(\n cfg.defaultValue, input.kind, cfg, em,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n if (validationError) {\n return NextResponse.json({ error: validationError }, { status: 400 })\n }\n cfg.defaultValue = normalizeDefaultValueByKind(cfg.defaultValue, input.kind)\n }\n def.configJson = cfg\n def.isActive = input.isActive ?? true\n def.deletedAt = def.isActive === false ? (def.deletedAt ?? new Date()) : null\n def.updatedAt = new Date()\n em.persist(def)\n await em.flush()\n await guard.runAfterSuccess()\n await invalidateDefinitionsCache(cache, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n entityIds: [input.entityId],\n })\n // Changing field definitions may impact forms but not sidebar items; no nav cache touch\n return NextResponse.json({ ok: true, item: { id: def.id, key: def.key, kind: def.kind, configJson: def.configJson, isActive: def.isActive } })\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, 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 scope = await resolveDefinitionMutationScope({ auth, container, request: req })\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 const where: any = createExactDefinitionWhere(entityId, key, scope)\n let def = await em.findOne(CustomFieldDef, where)\n let inherited: any | null = null\n if (!def) {\n inherited = selectVisibleDefinitionWinner(await em.find(CustomFieldDef, createVisibleDefinitionWhere(\n entityId,\n key,\n scope,\n { deletedAt: null, isActive: true },\n )))\n if (!inherited) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: FIELD_DEFINITION_RESOURCE_KIND,\n resourceId: def?.id ?? inherited?.id ?? `${entityId}:${key}`,\n operation: 'delete',\n mutationPayload: { entityId, key },\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n if (!def) {\n def = createScopedDefinitionTombstone(em, inherited, scope)\n } else {\n markDefinitionTombstoned(def)\n }\n em.persist(def)\n await em.flush()\n await guard.runAfterSuccess()\n await invalidateDefinitionsCache(cache, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n entityIds: [entityId],\n })\n // Changing field definitions may impact forms but not sidebar items; no nav cache touch\n return NextResponse.json({ ok: true })\n}\n\nconst definitionsQuerySchema = z\n .object({\n entityId: z.union([z.string(), z.array(z.string())]).optional(),\n entityIds: z.string().optional(),\n fieldset: z.string().regex(fieldsetCodeRegex).optional(),\n })\n .refine(\n (value) => {\n if (value.entityId && typeof value.entityId === 'string' && value.entityId.trim().length > 0) return true\n if (Array.isArray(value.entityId) && value.entityId.length > 0) return true\n return typeof value.entityIds === 'string' && value.entityIds.trim().length > 0\n },\n { message: 'Provide at least one entityId or an entityIds list.' }\n )\n\nconst customFieldOptionValueSchema = z.union([z.string(), z.number()])\n\nconst customFieldDefinitionSchema = z.object({\n key: z.string(),\n kind: z.string(),\n label: z.string(),\n description: z.string().optional(),\n multi: z.boolean().optional(),\n options: z.array(customFieldOptionValueSchema).optional(),\n optionsUrl: z.string().optional(),\n filterable: z.boolean().optional(),\n formEditable: z.boolean().optional(),\n listVisible: z.boolean().optional(),\n editor: z.string().optional(),\n input: z.string().optional(),\n dictionaryId: z.string().optional(),\n dictionaryInlineCreate: z.boolean().optional(),\n priority: z.number().optional(),\n validation: z.array(z.any()).optional(),\n defaultValue: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),\n maxAttachmentSizeMb: z.number().optional(),\n acceptExtensions: z.array(z.any()).optional(),\n entityId: z.string(),\n fieldset: z.string().optional(),\n group: z\n .object({\n code: z.string(),\n title: z.string().optional(),\n hint: z.string().optional(),\n })\n .optional(),\n})\n\nconst customFieldsetGroupResponseSchema = z.object({\n code: z.string(),\n title: z.string().optional(),\n hint: z.string().optional(),\n})\n\nconst entityFieldsetResponseSchema = z.object({\n code: z.string(),\n label: z.string(),\n icon: z.string().optional(),\n description: z.string().optional(),\n groups: z.array(customFieldsetGroupResponseSchema).optional(),\n})\n\nconst definitionsResponseSchema = z.object({\n items: z.array(customFieldDefinitionSchema),\n fieldsetsByEntity: z.record(z.string(), z.array(entityFieldsetResponseSchema)).optional(),\n entitySettings: z\n .record(\n z.string(),\n z.object({\n singleFieldsetPerRecord: z.boolean().optional(),\n })\n )\n .optional(),\n})\n\nconst upsertDefinitionResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string().uuid(),\n key: z.string(),\n kind: z.string(),\n configJson: z.record(z.string(), z.any()),\n isActive: z.boolean().optional(),\n }),\n})\n\nconst deleteDefinitionRequestSchema = z.object({\n entityId: z.string(),\n key: z.string(),\n})\n\nconst deleteDefinitionResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Manage custom field definitions',\n methods: {\n GET: {\n summary: 'List active custom field definitions',\n description: 'Returns active custom field definitions for the supplied entity ids, respecting tenant scope and tombstones.',\n query: definitionsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Definition list',\n schema: definitionsResponseSchema,\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 },\n POST: {\n summary: 'Upsert custom field definition',\n description: 'Creates or updates a custom field definition for the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: upsertCustomFieldDefSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Definition saved',\n schema: upsertDefinitionResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failed',\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 field definition',\n description: 'Marks the specified definition inactive and tombstones it for the current scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteDefinitionRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Definition deleted',\n schema: deleteDefinitionResponseSchema,\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}\nconst computeDefinitionScore = (def: any, cfg: Record<string, any>, entityIndex: number) => {\n const listVisibleScore = cfg.listVisible === false ? 0 : 1\n const formEditableScore = cfg.formEditable === false ? 0 : 1\n const filterableScore = cfg.filterable ? 1 : 0\n const kindScore = (() => {\n switch (def.kind) {\n case 'dictionary':\n return 8\n case 'relation':\n return 6\n case 'select':\n case 'currency':\n return 4\n case 'multiline':\n return 3\n case 'boolean':\n case 'integer':\n case 'float':\n return 2\n default:\n return 1\n }\n })()\n const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0\n const dictionaryBonus = typeof cfg.dictionaryId === 'string' && cfg.dictionaryId.trim().length ? 5 : 0\n const base = (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus\n const penalty = typeof cfg.priority === 'number' ? cfg.priority : 0\n return { base, penalty, entityIndex }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
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 type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomEntity, CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport { upsertCustomFieldDefSchema, fieldsetCodeRegex } from '@open-mercato/core/modules/entities/data/validators'\nimport {\n createDefinitionsCacheKey,\n createDefinitionsCacheTags,\n invalidateDefinitionsCache,\n ENTITY_DEFINITIONS_CACHE_TTL_MS,\n} from './definitions.cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { filterSelectableSystemEntityIds, isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { loadEntityFieldsetConfigs, CustomFieldsetDefinition } from '../lib/fieldsets'\nimport { installCustomEntitiesFromModules } from '../lib/install-from-ce'\nimport { normalizeCustomFieldOptions } from '@open-mercato/shared/modules/entities/options'\nimport { CURRENCY_OPTIONS_URL } from '@open-mercato/shared/modules/entities/kinds'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n beginEntitiesMutationGuard,\n FIELD_DEFINITION_RESOURCE_KIND,\n} from './definitions.mutation-guard'\nimport {\n createExactDefinitionWhere,\n createScopedDefinitionTombstone,\n createVisibleDefinitionWhere,\n markDefinitionTombstoned,\n resolveDefinitionScopeFromOrganizationScope,\n resolveDefinitionMutationScope,\n selectVisibleDefinitionWinner,\n} from '../lib/definition-scope'\nimport { resolveEntityDefinitionsVersion } from '../lib/definitions-version'\n\n/**\n * Validate defaultValue against the field kind. Returns an error message string\n * if invalid, or null if valid. For dictionary and currency kinds, queries the\n * database to verify the token exists.\n */\nasync function validateDefaultValueByKind(\n value: unknown,\n kind: string,\n cfg: Record<string, unknown>,\n em: EntityManager,\n tenantContext: { tenantId: string | null; organizationId: string | null },\n): Promise<string | null> {\n switch (kind) {\n case 'boolean':\n if (typeof value !== 'boolean') return 'defaultValue for boolean fields must be true or false'\n return null\n case 'integer':\n if (typeof value !== 'number' || !Number.isInteger(value))\n return 'defaultValue for integer fields must be a whole number'\n return null\n case 'float':\n if (typeof value !== 'number' || !isFinite(value))\n return 'defaultValue for float fields must be a finite number'\n return null\n case 'text':\n case 'multiline':\n case 'date':\n case 'datetime':\n if (typeof value !== 'string')\n return `defaultValue for ${kind} fields must be a string`\n return null\n case 'dictionary': {\n if (typeof value !== 'string')\n return 'defaultValue for dictionary fields must be a string'\n const dictionaryId = typeof cfg.dictionaryId === 'string' ? cfg.dictionaryId.trim() : ''\n if (dictionaryId) {\n try {\n const { DictionaryEntry } = await import('@open-mercato/core/modules/dictionaries/data/entities')\n const entry = await em.findOne(DictionaryEntry, {\n dictionary: dictionaryId,\n value: value,\n ...(tenantContext.organizationId ? { organizationId: tenantContext.organizationId } : {}),\n ...(tenantContext.tenantId ? { tenantId: tenantContext.tenantId } : {}),\n })\n if (!entry) return `defaultValue \"${value}\" does not match any entry in the configured dictionary`\n } catch (err) {\n // If the dictionaries module is not available, skip entry validation\n console.debug('[entities.definitions] dictionary validation skipped \u2014 module not available', err)\n }\n }\n return null\n }\n case 'currency': {\n if (typeof value !== 'string')\n return 'defaultValue for currency fields must be a string'\n try {\n const { Currency } = await import('@open-mercato/core/modules/currencies/data/entities')\n const where: Record<string, string> = { code: value as string }\n if (tenantContext.tenantId) where.tenantId = tenantContext.tenantId\n if (tenantContext.organizationId) where.organizationId = tenantContext.organizationId\n const currency = await em.findOne(Currency, where)\n if (!currency) return `defaultValue \"${value}\" does not match any available currency`\n } catch (err) {\n // If the currencies module is not available, skip currency validation\n console.debug('[entities.definitions] currency validation skipped \u2014 module not available', err)\n }\n return null\n }\n case 'select': {\n if (typeof value !== 'string' && typeof value !== 'number')\n return 'defaultValue for select fields must be a string or number'\n const options = normalizeCustomFieldOptions(\n Array.isArray(cfg.options) ? cfg.options : [],\n )\n if (options.length > 0) {\n const match = options.some(\n (o) => o.value === value || String(o.value) === String(value),\n )\n if (!match) return 'defaultValue does not match any of the configured options'\n }\n return null\n }\n case 'relation':\n case 'attachment':\n return `defaultValue is not supported for ${kind} fields`\n default:\n return null\n }\n}\n\n/**\n * Normalize defaultValue to the canonical storage shape for the given kind.\n */\nfunction normalizeDefaultValueByKind(value: unknown, kind: string): unknown {\n switch (kind) {\n case 'boolean':\n return value === true\n case 'integer':\n return Math.round(Number(value))\n case 'float':\n return Number(value)\n default:\n return value\n }\n}\n\nexport const metadata = {\n // Reading definitions is needed by record forms; keep it auth-protected but accessible to all authenticated users\n GET: { requireAuth: true },\n // Mutations remain admin-only\n POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nfunction parseEntityIds(url: URL): string[] {\n const direct = url.searchParams.getAll('entityId').filter((id) => typeof id === 'string' && id.trim().length > 0)\n const combined = url.searchParams.get('entityIds')\n if (combined) {\n combined\n .split(',')\n .map((id) => id.trim())\n .filter((id) => id.length > 0)\n .forEach((id) => direct.push(id))\n }\n const unique: string[] = []\n const seen = new Set<string>()\n for (const id of direct) {\n if (seen.has(id)) continue\n seen.add(id)\n unique.push(id)\n }\n return unique\n}\n\nasync function resolveEntityDefaultEditor(em: any, entityId: string, tenantId: string | null | undefined): Promise<string | undefined> {\n try {\n const ent = await em.findOne(CustomEntity, {\n entityId,\n $and: [\n { $or: [ { tenantId: tenantId ?? undefined as any }, { tenantId: null } ] },\n ],\n } as any)\n if (ent && typeof (ent as any).defaultEditor === 'string') return (ent as any).defaultEditor\n } catch {}\n return undefined\n}\n\nfunction normalizeFieldGroup(raw: unknown): { code: string; title?: string; hint?: string } | undefined {\n if (!raw) return undefined\n if (typeof raw === 'string') {\n const code = raw.trim()\n return code ? { code } : undefined\n }\n if (typeof raw !== 'object') return undefined\n const entry = raw as Record<string, unknown>\n const code = typeof entry.code === 'string' ? entry.code.trim() : ''\n if (!code) return undefined\n const group: { code: string; title?: string; hint?: string } = { code }\n if (typeof entry.title === 'string' && entry.title.trim()) group.title = entry.title.trim()\n if (typeof entry.hint === 'string' && entry.hint.trim()) group.hint = entry.hint.trim()\n return group\n}\n\nfunction definitionMatchesReadScope(\n definition: { tenantId?: string | null; organizationId?: string | null },\n scope: { tenantId: string | null; organizationId: string | null },\n) {\n const definitionTenantId = definition.tenantId ?? null\n const definitionOrganizationId = definition.organizationId ?? null\n const tenantMatches = definitionTenantId === null || definitionTenantId === scope.tenantId\n const organizationMatches =\n definitionOrganizationId === null ||\n (scope.organizationId !== null && definitionOrganizationId === scope.organizationId)\n return tenantMatches && organizationMatches\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const requestedEntityIds = parseEntityIds(url)\n if (!requestedEntityIds.length) {\n return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n }\n const requestedFieldset = url.searchParams.get('fieldset')\n const fieldsetFilter = requestedFieldset && requestedFieldset.trim().length ? requestedFieldset.trim() : null\n\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const entityIds = filterSelectableSystemEntityIds(requestedEntityIds)\n if (!entityIds.length) {\n return NextResponse.json({ items: [] })\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const definitionScope = resolveDefinitionScopeFromOrganizationScope(auth, scope)\n const tenantId = definitionScope.tenantId\n if (!tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = definitionScope.organizationId\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 let canManageDefinitions = false\n if (typeof auth.sub === 'string' && auth.sub.length > 0) {\n try {\n const rbac = resolve('rbacService') as RbacService\n canManageDefinitions = await rbac.userHasAllFeatures(auth.sub, ['entities.definitions.manage'], {\n tenantId,\n organizationId,\n })\n } catch {\n // RBAC service unavailable \u2014 fall back to read-only (no definition sync)\n }\n }\n\n if (canManageDefinitions) {\n try {\n await installCustomEntitiesFromModules(em, cache, {\n tenantIds: [tenantId],\n entityIds,\n includeGlobal: true,\n createOnly: true,\n })\n } catch (err) {\n console.warn('[entities.definitions] Failed to synchronize module-backed definitions', {\n tenantId,\n entityIds,\n err,\n })\n }\n }\n\n let cacheKey: string | null = null\n if (cache && !fieldsetFilter) {\n cacheKey = createDefinitionsCacheKey({\n tenantId,\n organizationId,\n entityIds,\n })\n try {\n const cached = await cache.get(cacheKey)\n if (cached) {\n return NextResponse.json(cached)\n }\n } catch (err) {\n console.warn('[entities.definitions.cache] Failed to read cache', err)\n }\n }\n\n const fieldsetConfigs = await loadEntityFieldsetConfigs(em, {\n entityIds,\n tenantId,\n organizationId,\n mode: 'public',\n })\n\n const tenantCandidates = [{ tenantId }, { tenantId: null as string | null }]\n const organizationCandidates = [{ organizationId: null as string | null }]\n if (organizationId) organizationCandidates.unshift({ organizationId })\n const readScope = { tenantId, organizationId }\n\n const whereActive = {\n entityId: { $in: entityIds as any },\n deletedAt: null,\n $and: [\n { $or: tenantCandidates },\n { $or: organizationCandidates },\n ],\n } as any\n const defs = (await em.find(CustomFieldDef, whereActive as any))\n .filter((definition: any) => definitionMatchesReadScope(definition, readScope))\n const tombstones = (await em.find(CustomFieldDef, {\n entityId: { $in: entityIds as any },\n deletedAt: { $ne: null } as any,\n $and: [\n { $or: tenantCandidates },\n { $or: organizationCandidates },\n ],\n } as any)).filter((definition: any) => definitionMatchesReadScope(definition, readScope))\n\n const tombstonedByEntity = new Map<string, Set<string>>()\n for (const entry of tombstones as any[]) {\n const eid = String(entry.entityId)\n if (!tombstonedByEntity.has(eid)) tombstonedByEntity.set(eid, new Set())\n tombstonedByEntity.get(eid)!.add(entry.key)\n }\n\n const scopeScore = (x: any) => (x.tenantId ? 2 : 0) + (x.organizationId ? 1 : 0)\n\n const definitionsByEntity = new Map<string, any[]>()\n for (const d of defs) {\n const eid = String(d.entityId)\n if (!definitionsByEntity.has(eid)) definitionsByEntity.set(eid, [])\n definitionsByEntity.get(eid)!.push(d)\n }\n\n const entityDefaultEditors = new Map<string, string | undefined>()\n for (const entityId of entityIds) {\n const editor = await resolveEntityDefaultEditor(em, entityId, tenantId ?? null)\n entityDefaultEditors.set(entityId, editor)\n }\n\n const items: any[] = []\n\n const entityOrder = new Map<string, number>()\n entityIds.forEach((id, idx) => entityOrder.set(id, idx))\n\n for (const entityId of entityIds) {\n const defsForEntity = definitionsByEntity.get(entityId) ?? []\n if (!defsForEntity.length) continue\n const tombstonedKeys = tombstonedByEntity.get(entityId) ?? new Set<string>()\n const byKey = new Map<string, any>()\n for (const d of defsForEntity) {\n const existing = byKey.get(d.key)\n if (!existing) { byKey.set(d.key, d); continue }\n const sNew = scopeScore(d)\n const sOld = scopeScore(existing)\n if (sNew > sOld) { byKey.set(d.key, d); continue }\n if (sNew < sOld) continue\n const tNew = (d.updatedAt instanceof Date) ? d.updatedAt.getTime() : new Date(d.updatedAt).getTime()\n const tOld = (existing.updatedAt instanceof Date) ? existing.updatedAt.getTime() : new Date(existing.updatedAt).getTime()\n if (tNew >= tOld) byKey.set(d.key, d)\n }\n\n const defaultEditor = entityDefaultEditors.get(entityId)\n const winning = Array.from(byKey.values()).filter((d: any) => (d.isActive !== false) && !tombstonedKeys.has(d.key))\n winning.sort((a: any, b: any) => ((a.configJson?.priority ?? 0) - (b.configJson?.priority ?? 0)))\n\n for (const d of winning) {\n const rawFieldset = typeof d.configJson?.fieldset === 'string' ? d.configJson.fieldset.trim() : ''\n const normalizedFieldset = rawFieldset.length > 0 ? rawFieldset : undefined\n const normalizedFieldsets = Array.isArray(d.configJson?.fieldsets)\n ? d.configJson.fieldsets\n .filter((entry: unknown): entry is string => typeof entry === 'string')\n .map((entry: string) => entry.trim())\n .filter((entry: string) => entry.length > 0)\n : []\n const effectiveFieldsets = normalizedFieldsets.length > 0\n ? normalizedFieldsets\n : (normalizedFieldset ? [normalizedFieldset] : [])\n if (fieldsetFilter && !effectiveFieldsets.includes(fieldsetFilter)) continue\n const groupInfo = normalizeFieldGroup(d.configJson?.group)\n const keyLower = String(d.key).toLowerCase()\n const candidateBase = {\n key: d.key,\n kind: d.kind,\n label: d.configJson?.label || d.key,\n description: d.configJson?.description || undefined,\n multi: Boolean(d.configJson?.multi),\n options: (() => {\n if (d.kind === 'currency') return undefined\n const normalizedOptions = normalizeCustomFieldOptions(d.configJson?.options)\n return normalizedOptions.length ? normalizedOptions : undefined\n })(),\n optionsUrl: (() => {\n if (d.kind === 'currency') return CURRENCY_OPTIONS_URL\n const dictionaryId = typeof d.configJson?.dictionaryId === 'string' ? d.configJson.dictionaryId : undefined\n if (dictionaryId) return `/api/dictionaries/${dictionaryId}/entries`\n return typeof d.configJson?.optionsUrl === 'string' ? d.configJson.optionsUrl : undefined\n })(),\n filterable: Boolean(d.configJson?.filterable),\n formEditable: d.configJson?.formEditable !== undefined ? Boolean(d.configJson.formEditable) : true,\n listVisible: d.configJson?.listVisible !== undefined ? Boolean(d.configJson.listVisible) : true,\n editor: typeof d.configJson?.editor === 'string'\n ? d.configJson.editor\n : (d.kind === 'multiline' ? defaultEditor : undefined),\n input: typeof d.configJson?.input === 'string' ? d.configJson.input : undefined,\n dictionaryId: typeof d.configJson?.dictionaryId === 'string' ? d.configJson.dictionaryId : undefined,\n dictionaryInlineCreate: d.configJson?.dictionaryInlineCreate !== undefined\n ? Boolean(d.configJson.dictionaryInlineCreate)\n : undefined,\n priority: typeof d.configJson?.priority === 'number' ? d.configJson.priority : 0,\n validation: Array.isArray(d.configJson?.validation) ? d.configJson.validation : undefined,\n defaultValue: d.configJson?.defaultValue !== undefined ? d.configJson.defaultValue : undefined,\n // attachments config passthrough\n maxAttachmentSizeMb: typeof d.configJson?.maxAttachmentSizeMb === 'number' ? d.configJson.maxAttachmentSizeMb : undefined,\n acceptExtensions: Array.isArray(d.configJson?.acceptExtensions) ? d.configJson.acceptExtensions : undefined,\n entityId,\n fieldset: normalizedFieldset ?? effectiveFieldsets[0],\n fieldsets: effectiveFieldsets.length > 0 ? effectiveFieldsets : undefined,\n group: groupInfo,\n } as any\n const metrics = computeDefinitionScore(d, candidateBase, entityOrder.get(entityId) ?? Number.MAX_SAFE_INTEGER)\n const candidate = { ...candidateBase, __score: metrics }\n const existing = (items as any[]).find((entry) => entry.key.toLowerCase() === keyLower)\n if (!existing) {\n items.push(candidate)\n continue\n }\n const existingScore = existing.__score as { base: number; penalty: number; entityIndex: number }\n const candidateScoreInfo = candidate.__score as { base: number; penalty: number; entityIndex: number }\n const better =\n candidateScoreInfo.base > existingScore.base ||\n (candidateScoreInfo.base === existingScore.base && candidateScoreInfo.penalty < existingScore.penalty) ||\n (candidateScoreInfo.base === existingScore.base && candidateScoreInfo.penalty === existingScore.penalty && candidateScoreInfo.entityIndex < existingScore.entityIndex)\n if (better) {\n const index = items.findIndex((entry) => entry.key.toLowerCase() === keyLower)\n if (index >= 0) items[index] = candidate\n }\n }\n }\n\n const sanitized = items.map((item: any) => {\n const { __score, ...rest } = item\n return rest\n })\n sanitized.sort((a: any, b: any) => ((a.priority ?? 0) - (b.priority ?? 0)))\n\n const fieldsetsByEntity: Record<string, CustomFieldsetDefinition[]> = {}\n const entitySettings: Record<string, { singleFieldsetPerRecord: boolean }> = {}\n for (const entityId of entityIds) {\n const cfg = fieldsetConfigs.get(entityId) ?? { fieldsets: [], singleFieldsetPerRecord: true }\n fieldsetsByEntity[entityId] = cfg.fieldsets\n entitySettings[entityId] = { singleFieldsetPerRecord: cfg.singleFieldsetPerRecord }\n }\n\n const responseBody = { items: sanitized, fieldsetsByEntity, entitySettings }\n\n if (cache && cacheKey && !fieldsetFilter) {\n const tags = createDefinitionsCacheTags({\n tenantId,\n organizationId,\n entityIds,\n })\n try {\n await cache.set(cacheKey, responseBody, {\n ttl: ENTITY_DEFINITIONS_CACHE_TTL_MS,\n tags,\n })\n } catch (err) {\n console.warn('[entities.definitions.cache] Failed to store cache entry', err)\n }\n }\n\n return NextResponse.json(responseBody)\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 parsed = upsertCustomFieldDefSchema.safeParse(body)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const input = parsed.data\n\n if (!isSystemEntitySelectable(input.entityId)) {\n return NextResponse.json({ error: 'Custom fields are not supported for this entity' }, { status: 400 })\n }\n\n if (input.kind === 'dictionary') {\n const dictionaryId = input.configJson?.dictionaryId\n if (typeof dictionaryId !== 'string' || dictionaryId.trim().length === 0) {\n return NextResponse.json({ error: 'dictionaryId is required for dictionary custom fields' }, { status: 400 })\n }\n }\n\n const container = await createRequestContainer()\n const scope = await resolveDefinitionMutationScope({ auth, container, request: req })\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 = createExactDefinitionWhere(input.entityId, input.key, scope)\n let def = await em.findOne(CustomFieldDef, where)\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: FIELD_DEFINITION_RESOURCE_KIND,\n resourceId: def ? def.id : `${input.entityId}:${input.key}`,\n operation: def ? 'update' : 'create',\n mutationPayload: input as unknown as Record<string, unknown>,\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n if (!def) def = em.create(CustomFieldDef, { ...where, createdAt: new Date() })\n def.kind = input.kind\n const inCfg = (input as any).configJson ?? {}\n const cfg: Record<string, any> = { ...inCfg }\n if (cfg.label == null || String(cfg.label).trim() === '') cfg.label = input.key\n if (cfg.formEditable === undefined) cfg.formEditable = true\n if (cfg.listVisible === undefined) cfg.listVisible = true\n if (input.kind === 'dictionary') {\n const dictionaryId = typeof cfg.dictionaryId === 'string' ? cfg.dictionaryId.trim() : ''\n cfg.dictionaryId = dictionaryId\n cfg.dictionaryInlineCreate = cfg.dictionaryInlineCreate !== false\n }\n if (input.kind === 'currency') {\n cfg.optionsUrl = CURRENCY_OPTIONS_URL\n if (Array.isArray(cfg.options)) delete cfg.options\n }\n if (input.kind === 'multiline' && (cfg.editor == null || String(cfg.editor).trim() === '')) {\n cfg.editor = 'markdown'\n }\n // Validate and normalize defaultValue by kind before persisting\n if (cfg.defaultValue !== undefined && cfg.defaultValue !== null) {\n const validationError = await validateDefaultValueByKind(\n cfg.defaultValue, input.kind, cfg, em,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n if (validationError) {\n return NextResponse.json({ error: validationError }, { status: 400 })\n }\n cfg.defaultValue = normalizeDefaultValueByKind(cfg.defaultValue, input.kind)\n }\n def.configJson = cfg\n def.isActive = input.isActive ?? true\n def.deletedAt = def.isActive === false ? (def.deletedAt ?? new Date()) : null\n def.updatedAt = new Date()\n em.persist(def)\n await em.flush()\n await guard.runAfterSuccess()\n await invalidateDefinitionsCache(cache, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n entityIds: [input.entityId],\n })\n // Changing field definitions may impact forms but not sidebar items; no nav cache touch\n return NextResponse.json({ ok: true, item: { id: def.id, key: def.key, kind: def.kind, configJson: def.configJson, isActive: def.isActive } })\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, 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 scope = await resolveDefinitionMutationScope({ auth, container, request: req })\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 const where: any = createExactDefinitionWhere(entityId, key, scope)\n let def = await em.findOne(CustomFieldDef, where)\n let inherited: any | null = null\n if (!def) {\n inherited = selectVisibleDefinitionWinner(await em.find(CustomFieldDef, createVisibleDefinitionWhere(\n entityId,\n key,\n scope,\n { deletedAt: null, isActive: true },\n )))\n if (!inherited) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: FIELD_DEFINITION_RESOURCE_KIND,\n resourceId: def?.id ?? inherited?.id ?? `${entityId}:${key}`,\n operation: 'delete',\n mutationPayload: { entityId, key },\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n if (!def) {\n def = createScopedDefinitionTombstone(em, inherited, scope)\n } else {\n markDefinitionTombstoned(def)\n }\n em.persist(def)\n await em.flush()\n await guard.runAfterSuccess()\n await invalidateDefinitionsCache(cache, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n entityIds: [entityId],\n })\n // Changing field definitions may impact forms but not sidebar items; no nav cache touch\n // Return the post-delete aggregate version so the edit form keeps its optimistic-lock\n // token in sync after removing a field out-of-band (issue #3152).\n const version = await resolveEntityDefinitionsVersion(em, {\n entityId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return NextResponse.json({ ok: true, version })\n}\n\nconst definitionsQuerySchema = z\n .object({\n entityId: z.union([z.string(), z.array(z.string())]).optional(),\n entityIds: z.string().optional(),\n fieldset: z.string().regex(fieldsetCodeRegex).optional(),\n })\n .refine(\n (value) => {\n if (value.entityId && typeof value.entityId === 'string' && value.entityId.trim().length > 0) return true\n if (Array.isArray(value.entityId) && value.entityId.length > 0) return true\n return typeof value.entityIds === 'string' && value.entityIds.trim().length > 0\n },\n { message: 'Provide at least one entityId or an entityIds list.' }\n )\n\nconst customFieldOptionValueSchema = z.union([z.string(), z.number()])\n\nconst customFieldDefinitionSchema = z.object({\n key: z.string(),\n kind: z.string(),\n label: z.string(),\n description: z.string().optional(),\n multi: z.boolean().optional(),\n options: z.array(customFieldOptionValueSchema).optional(),\n optionsUrl: z.string().optional(),\n filterable: z.boolean().optional(),\n formEditable: z.boolean().optional(),\n listVisible: z.boolean().optional(),\n editor: z.string().optional(),\n input: z.string().optional(),\n dictionaryId: z.string().optional(),\n dictionaryInlineCreate: z.boolean().optional(),\n priority: z.number().optional(),\n validation: z.array(z.any()).optional(),\n defaultValue: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),\n maxAttachmentSizeMb: z.number().optional(),\n acceptExtensions: z.array(z.any()).optional(),\n entityId: z.string(),\n fieldset: z.string().optional(),\n group: z\n .object({\n code: z.string(),\n title: z.string().optional(),\n hint: z.string().optional(),\n })\n .optional(),\n})\n\nconst customFieldsetGroupResponseSchema = z.object({\n code: z.string(),\n title: z.string().optional(),\n hint: z.string().optional(),\n})\n\nconst entityFieldsetResponseSchema = z.object({\n code: z.string(),\n label: z.string(),\n icon: z.string().optional(),\n description: z.string().optional(),\n groups: z.array(customFieldsetGroupResponseSchema).optional(),\n})\n\nconst definitionsResponseSchema = z.object({\n items: z.array(customFieldDefinitionSchema),\n fieldsetsByEntity: z.record(z.string(), z.array(entityFieldsetResponseSchema)).optional(),\n entitySettings: z\n .record(\n z.string(),\n z.object({\n singleFieldsetPerRecord: z.boolean().optional(),\n })\n )\n .optional(),\n})\n\nconst upsertDefinitionResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string().uuid(),\n key: z.string(),\n kind: z.string(),\n configJson: z.record(z.string(), z.any()),\n isActive: z.boolean().optional(),\n }),\n})\n\nconst deleteDefinitionRequestSchema = z.object({\n entityId: z.string(),\n key: z.string(),\n})\n\nconst deleteDefinitionResponseSchema = z.object({\n ok: z.literal(true),\n version: z.string().nullable().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Manage custom field definitions',\n methods: {\n GET: {\n summary: 'List active custom field definitions',\n description: 'Returns active custom field definitions for the supplied entity ids, respecting tenant scope and tombstones.',\n query: definitionsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Definition list',\n schema: definitionsResponseSchema,\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 },\n POST: {\n summary: 'Upsert custom field definition',\n description: 'Creates or updates a custom field definition for the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: upsertCustomFieldDefSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Definition saved',\n schema: upsertDefinitionResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failed',\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 field definition',\n description: 'Marks the specified definition inactive and tombstones it for the current scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteDefinitionRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Definition deleted',\n schema: deleteDefinitionResponseSchema,\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}\nconst computeDefinitionScore = (def: any, cfg: Record<string, any>, entityIndex: number) => {\n const listVisibleScore = cfg.listVisible === false ? 0 : 1\n const formEditableScore = cfg.formEditable === false ? 0 : 1\n const filterableScore = cfg.filterable ? 1 : 0\n const kindScore = (() => {\n switch (def.kind) {\n case 'dictionary':\n return 8\n case 'relation':\n return 6\n case 'select':\n case 'currency':\n return 4\n case 'multiline':\n return 3\n case 'boolean':\n case 'integer':\n case 'float':\n return 2\n default:\n return 1\n }\n })()\n const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0\n const dictionaryBonus = typeof cfg.dictionaryId === 'string' && cfg.dictionaryId.trim().length ? 5 : 0\n const base = (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus\n const penalty = typeof cfg.priority === 'number' ? cfg.priority : 0\n return { base, penalty, entityIndex }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["code"]
|
|
7
7
|
}
|
|
@@ -4,6 +4,8 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { CustomFieldDef } from "@open-mercato/core/modules/entities/data/entities";
|
|
6
6
|
import { loadEntityFieldsetConfigs } from "../lib/fieldsets.js";
|
|
7
|
+
import { resolveDefinitionMutationScope } from "../lib/definition-scope.js";
|
|
8
|
+
import { resolveEntityDefinitionsVersion } from "../lib/definitions-version.js";
|
|
7
9
|
const metadata = {
|
|
8
10
|
GET: { requireAuth: true, requireFeatures: ["entities.definitions.manage"] }
|
|
9
11
|
};
|
|
@@ -13,8 +15,10 @@ async function GET(req) {
|
|
|
13
15
|
if (!entityId) return NextResponse.json({ error: "entityId is required" }, { status: 400 });
|
|
14
16
|
const auth = await getAuthFromRequest(req);
|
|
15
17
|
if (!auth || !auth.tenantId || !auth.orgId && !auth.isSuperAdmin) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
16
|
-
const
|
|
18
|
+
const container = await createRequestContainer();
|
|
19
|
+
const { resolve } = container;
|
|
17
20
|
const em = resolve("em");
|
|
21
|
+
const versionScope = await resolveDefinitionMutationScope({ auth, container, request: req });
|
|
18
22
|
const [defs, tombstones, configMap] = await Promise.all([
|
|
19
23
|
em.find(CustomFieldDef, {
|
|
20
24
|
entityId,
|
|
@@ -78,11 +82,17 @@ async function GET(req) {
|
|
|
78
82
|
}));
|
|
79
83
|
const deletedKeys = Array.from(tombstonedKeys);
|
|
80
84
|
const cfg = configMap.get(entityId) ?? { fieldsets: [], singleFieldsetPerRecord: true };
|
|
85
|
+
const version = await resolveEntityDefinitionsVersion(em, {
|
|
86
|
+
entityId,
|
|
87
|
+
tenantId: versionScope.tenantId,
|
|
88
|
+
organizationId: versionScope.organizationId
|
|
89
|
+
});
|
|
81
90
|
return NextResponse.json({
|
|
82
91
|
items,
|
|
83
92
|
deletedKeys,
|
|
84
93
|
fieldsets: cfg.fieldsets,
|
|
85
|
-
settings: { singleFieldsetPerRecord: cfg.singleFieldsetPerRecord }
|
|
94
|
+
settings: { singleFieldsetPerRecord: cfg.singleFieldsetPerRecord },
|
|
95
|
+
version
|
|
86
96
|
});
|
|
87
97
|
}
|
|
88
98
|
const definitionsManageQuerySchema = z.object({
|
|
@@ -113,7 +123,8 @@ const definitionsManageResponseSchema = z.object({
|
|
|
113
123
|
items: z.array(managedDefinitionSchema),
|
|
114
124
|
deletedKeys: z.array(z.string()),
|
|
115
125
|
fieldsets: z.array(manageFieldsetSchema).optional(),
|
|
116
|
-
settings: z.object({ singleFieldsetPerRecord: z.boolean().optional() }).optional()
|
|
126
|
+
settings: z.object({ singleFieldsetPerRecord: z.boolean().optional() }).optional(),
|
|
127
|
+
version: z.string().nullable().optional()
|
|
117
128
|
});
|
|
118
129
|
const openApi = {
|
|
119
130
|
tag: "Entities",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/api/definitions.manage.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 { CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { loadEntityFieldsetConfigs } from '../lib/fieldsets'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\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 const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAE/B,SAAS,iCAAiC;
|
|
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 { CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { loadEntityFieldsetConfigs } from '../lib/fieldsets'\nimport { resolveDefinitionMutationScope } from '../lib/definition-scope'\nimport { resolveEntityDefinitionsVersion } from '../lib/definitions-version'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\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 const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n // Resolve the same definition scope the mutating endpoints use so the version\n // token round-trips consistently (issue #3152).\n const versionScope = await resolveDefinitionMutationScope({ auth, container, request: req })\n // Load all scoped records (active/inactive/deleted) so that per-scope tombstones\n // can shadow global definitions. We'll filter out deleted winners later.\n const [defs, tombstones, configMap] = await Promise.all([\n em.find(CustomFieldDef, {\n entityId,\n deletedAt: null,\n isActive: true,\n $and: [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ],\n }, { orderBy: { key: 'asc' } as any }),\n em.find(CustomFieldDef, {\n entityId,\n deletedAt: { $ne: null } as any,\n $and: [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ],\n }),\n loadEntityFieldsetConfigs(em, {\n entityIds: [entityId],\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n mode: 'manage',\n }),\n ])\n const tombstonedKeys = new Set<string>((tombstones as any[]).map((d: any) => d.key))\n\n // Deduplicate by key, with clear precedence that allows higher-scope tombstones\n // to shadow lower-scope active definitions:\n // 1) Scope specificity first: tenant > org > global\n // 2) Within the same scope, latest updatedAt wins\n const byKey = new Map<string, any>()\n const ts = (x: any) => {\n const u = (x?.updatedAt instanceof Date) ? x.updatedAt.getTime() : (x?.updatedAt ? new Date(x.updatedAt).getTime() : 0)\n if (u) return u\n const c = (x?.createdAt instanceof Date) ? x.createdAt.getTime() : (x?.createdAt ? new Date(x.createdAt).getTime() : 0)\n return c\n }\n const scopeScore = (x: any) => (x?.tenantId ? 2 : 0) + (x?.organizationId ? 1 : 0)\n for (const d of defs) {\n const existing = byKey.get(d.key)\n if (!existing) { byKey.set(d.key, d); continue }\n const sNew = scopeScore(d)\n const sOld = scopeScore(existing)\n if (sNew > sOld) { byKey.set(d.key, d); continue }\n if (sNew < sOld) continue\n const tNew = ts(d)\n const tOld = ts(existing)\n if (tNew >= tOld) byKey.set(d.key, d)\n }\n // Exclude winners that have a tombstone in scope\n const winners = Array.from(byKey.values()).filter((d: any) => !tombstonedKeys.has(d.key))\n const items = winners.map((d: any) => ({\n id: d.id,\n key: d.key,\n kind: d.kind,\n configJson: d.configJson,\n isActive: d.isActive,\n organizationId: d.organizationId ?? null,\n tenantId: d.tenantId ?? null,\n }))\n const deletedKeys = Array.from(tombstonedKeys)\n const cfg = configMap.get(entityId) ?? { fieldsets: [], singleFieldsetPerRecord: true }\n const version = await resolveEntityDefinitionsVersion(em, {\n entityId,\n tenantId: versionScope.tenantId,\n organizationId: versionScope.organizationId,\n })\n return NextResponse.json({\n items,\n deletedKeys,\n fieldsets: cfg.fieldsets,\n settings: { singleFieldsetPerRecord: cfg.singleFieldsetPerRecord },\n version,\n })\n}\n\nconst definitionsManageQuerySchema = z.object({\n entityId: z.string(),\n})\n\nconst managedDefinitionSchema = z.object({\n id: z.string().uuid(),\n key: z.string(),\n kind: z.string(),\n configJson: z.record(z.string(), z.any()).nullable().optional(),\n isActive: z.boolean().optional(),\n organizationId: z.string().uuid().nullable(),\n tenantId: z.string().uuid().nullable(),\n})\n\nconst manageFieldsetGroupSchema = z.object({\n code: z.string(),\n title: z.string().optional(),\n hint: z.string().optional(),\n})\n\nconst manageFieldsetSchema = z.object({\n code: z.string(),\n label: z.string(),\n icon: z.string().optional(),\n description: z.string().optional(),\n groups: z.array(manageFieldsetGroupSchema).optional(),\n})\n\nconst definitionsManageResponseSchema = z.object({\n items: z.array(managedDefinitionSchema),\n deletedKeys: z.array(z.string()),\n fieldsets: z.array(manageFieldsetSchema).optional(),\n settings: z.object({ singleFieldsetPerRecord: z.boolean().optional() }).optional(),\n version: z.string().nullable().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Inspect scoped custom field definitions',\n methods: {\n GET: {\n summary: 'Get management snapshot',\n description: 'Returns scoped custom field definitions (including inactive tombstones) for administration interfaces.',\n query: definitionsManageQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Scoped definitions and deleted keys',\n schema: definitionsManageResponseSchema,\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 or feature',\n schema: z.object({ error: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,7 +5,9 @@ import { useRouter, useSearchParams } from "next/navigation";
|
|
|
5
5
|
import { useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
7
7
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
8
|
-
import { apiCall, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
8
|
+
import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
9
|
+
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
10
|
+
import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
|
|
9
11
|
import { invalidateCustomFieldDefs } from "@open-mercato/ui/backend/utils/customFieldDefs";
|
|
10
12
|
import { upsertCustomEntitySchema, upsertCustomFieldDefSchema } from "@open-mercato/core/modules/entities/data/validators";
|
|
11
13
|
import { z } from "zod";
|
|
@@ -25,6 +27,9 @@ import {
|
|
|
25
27
|
} from "@open-mercato/ui/primitives/dialog";
|
|
26
28
|
import { normalizeCustomFieldOptions } from "@open-mercato/shared/modules/entities/options";
|
|
27
29
|
import { TranslationManager } from "@open-mercato/core/modules/translations/components/TranslationManager";
|
|
30
|
+
function readVersionToken(value) {
|
|
31
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
32
|
+
}
|
|
28
33
|
function EditDefinitionsPage({ params }) {
|
|
29
34
|
React.useEffect(() => {
|
|
30
35
|
loadGeneratedFieldRegistrations().catch(() => {
|
|
@@ -40,6 +45,7 @@ function EditDefinitionsPage({ params }) {
|
|
|
40
45
|
const [entityFormLoading, setEntityFormLoading] = useState(true);
|
|
41
46
|
const [entityInitial, setEntityInitial] = useState({});
|
|
42
47
|
const [defs, setDefs] = useState([]);
|
|
48
|
+
const [defsVersion, setDefsVersion] = useState(null);
|
|
43
49
|
const [orderDirty, setOrderDirty] = useState(false);
|
|
44
50
|
const [orderSaving, setOrderSaving] = useState(false);
|
|
45
51
|
const listRef = React.useRef(null);
|
|
@@ -177,6 +183,7 @@ function EditDefinitionsPage({ params }) {
|
|
|
177
183
|
(a, b) => Number(a.configJson?.priority ?? 0) - Number(b.configJson?.priority ?? 0)
|
|
178
184
|
);
|
|
179
185
|
setDefs(loaded);
|
|
186
|
+
setDefsVersion(readVersionToken(json.version));
|
|
180
187
|
setDefErrors({});
|
|
181
188
|
setDeletedKeys(Array.isArray(json.deletedKeys) ? json.deletedKeys : []);
|
|
182
189
|
const loadedFieldsets = Array.isArray(json.fieldsets) ? json.fieldsets : [];
|
|
@@ -232,6 +239,7 @@ function EditDefinitionsPage({ params }) {
|
|
|
232
239
|
(a, b) => Number(a.configJson?.priority ?? 0) - Number(b.configJson?.priority ?? 0)
|
|
233
240
|
);
|
|
234
241
|
setDefs(loaded);
|
|
242
|
+
setDefsVersion(readVersionToken(j2.version));
|
|
235
243
|
setDeletedKeys(Array.isArray(j2.deletedKeys) ? j2.deletedKeys : []);
|
|
236
244
|
flash(`Restored ${key}`, "success");
|
|
237
245
|
await invalidateCustomFieldDefs(queryClient, entityId);
|
|
@@ -256,14 +264,19 @@ function EditDefinitionsPage({ params }) {
|
|
|
256
264
|
isActive: d.isActive !== false
|
|
257
265
|
}))
|
|
258
266
|
};
|
|
259
|
-
const call = await
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
const call = await withScopedApiRequestHeaders(
|
|
268
|
+
buildOptimisticLockHeader(defsVersion),
|
|
269
|
+
() => apiCall("/api/entities/definitions.batch", {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "content-type": "application/json" },
|
|
272
|
+
body: JSON.stringify(payload)
|
|
273
|
+
})
|
|
274
|
+
);
|
|
264
275
|
if (!call.ok) {
|
|
276
|
+
if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return;
|
|
265
277
|
await raiseCrudError(call.response, "Failed to save definitions");
|
|
266
278
|
}
|
|
279
|
+
setDefsVersion(readVersionToken(call.result?.version));
|
|
267
280
|
await invalidateCustomFieldDefs(queryClient, entityId);
|
|
268
281
|
router.push(`/backend/entities/user?flash=Definitions%20saved&type=success`);
|
|
269
282
|
} catch (e) {
|
|
@@ -285,6 +298,7 @@ function EditDefinitionsPage({ params }) {
|
|
|
285
298
|
if (!call.ok) {
|
|
286
299
|
await raiseCrudError(call.response, "Failed to delete field");
|
|
287
300
|
}
|
|
301
|
+
setDefsVersion(readVersionToken(call.result?.version));
|
|
288
302
|
} catch (error2) {
|
|
289
303
|
const message = error2 instanceof Error ? error2.message : "Failed to delete field";
|
|
290
304
|
flash(message, "error");
|
|
@@ -337,14 +351,22 @@ function EditDefinitionsPage({ params }) {
|
|
|
337
351
|
isActive: d.isActive !== false
|
|
338
352
|
}))
|
|
339
353
|
};
|
|
340
|
-
const call = await
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
354
|
+
const call = await withScopedApiRequestHeaders(
|
|
355
|
+
buildOptimisticLockHeader(defsVersion),
|
|
356
|
+
() => apiCall("/api/entities/definitions.batch", {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: { "content-type": "application/json" },
|
|
359
|
+
body: JSON.stringify(payload)
|
|
360
|
+
})
|
|
361
|
+
);
|
|
345
362
|
if (!call.ok) {
|
|
363
|
+
if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) {
|
|
364
|
+
setOrderDirty(false);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
346
367
|
await raiseCrudError(call.response, "Failed to save order");
|
|
347
368
|
}
|
|
369
|
+
setDefsVersion(readVersionToken(call.result?.version));
|
|
348
370
|
setOrderDirty(false);
|
|
349
371
|
flash("Order saved", "success");
|
|
350
372
|
await invalidateCustomFieldDefs(queryClient, entityId);
|
|
@@ -473,21 +495,25 @@ function EditDefinitionsPage({ params }) {
|
|
|
473
495
|
fieldsets: buildFieldsetPayload(),
|
|
474
496
|
singleFieldsetPerRecord
|
|
475
497
|
});
|
|
476
|
-
const callDefs = await
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
498
|
+
const callDefs = await withScopedApiRequestHeaders(
|
|
499
|
+
buildOptimisticLockHeader(defsVersion),
|
|
500
|
+
() => apiCall("/api/entities/definitions.batch", {
|
|
501
|
+
method: "POST",
|
|
502
|
+
headers: { "content-type": "application/json" },
|
|
503
|
+
body: JSON.stringify(defsPayload)
|
|
504
|
+
})
|
|
505
|
+
);
|
|
481
506
|
if (!callDefs.ok) {
|
|
482
507
|
await raiseCrudError(callDefs.response, "Failed to save definitions");
|
|
483
508
|
}
|
|
509
|
+
setDefsVersion(readVersionToken(callDefs.result?.version));
|
|
484
510
|
try {
|
|
485
511
|
window.dispatchEvent(new Event("om:refresh-sidebar"));
|
|
486
512
|
} catch {
|
|
487
513
|
}
|
|
488
514
|
await invalidateCustomFieldDefs(queryClient, entityId);
|
|
489
515
|
flash("Definitions saved", "success");
|
|
490
|
-
}, [buildFieldsetPayload, defs, entityId, entitySource, queryClient, singleFieldsetPerRecord, validateAll]);
|
|
516
|
+
}, [buildFieldsetPayload, defs, defsVersion, entityId, entitySource, queryClient, singleFieldsetPerRecord, validateAll]);
|
|
491
517
|
if (!entityId) {
|
|
492
518
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
|
|
493
519
|
/* @__PURE__ */ jsx(AlertTitle, { children: "Invalid entity" }),
|