@open-mercato/core 0.6.6-develop.6345.1.b236ea3d4b → 0.6.6-develop.6351.1.c140d703c9
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/entities/api/definitions.batch.js +68 -21
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +57 -20
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.restore.js +5 -3
- package/dist/modules/entities/api/definitions.restore.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +21 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/lib/definition-scope.js +102 -0
- package/dist/modules/entities/lib/definition-scope.js.map +7 -0
- package/package.json +7 -7
- package/src/modules/entities/api/definitions.batch.ts +69 -20
- package/src/modules/entities/api/definitions.restore.ts +5 -3
- package/src/modules/entities/api/definitions.ts +65 -21
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +30 -13
- package/src/modules/entities/lib/definition-scope.ts +172 -0
|
@@ -24,6 +24,15 @@ import {
|
|
|
24
24
|
beginEntitiesMutationGuard,
|
|
25
25
|
FIELD_DEFINITION_RESOURCE_KIND,
|
|
26
26
|
} from './definitions.mutation-guard'
|
|
27
|
+
import {
|
|
28
|
+
createExactDefinitionWhere,
|
|
29
|
+
createScopedDefinitionTombstone,
|
|
30
|
+
createVisibleDefinitionWhere,
|
|
31
|
+
markDefinitionTombstoned,
|
|
32
|
+
resolveDefinitionScopeFromOrganizationScope,
|
|
33
|
+
resolveDefinitionMutationScope,
|
|
34
|
+
selectVisibleDefinitionWinner,
|
|
35
|
+
} from '../lib/definition-scope'
|
|
27
36
|
|
|
28
37
|
/**
|
|
29
38
|
* Validate defaultValue against the field kind. Returns an error message string
|
|
@@ -188,6 +197,19 @@ function normalizeFieldGroup(raw: unknown): { code: string; title?: string; hint
|
|
|
188
197
|
return group
|
|
189
198
|
}
|
|
190
199
|
|
|
200
|
+
function definitionMatchesReadScope(
|
|
201
|
+
definition: { tenantId?: string | null; organizationId?: string | null },
|
|
202
|
+
scope: { tenantId: string | null; organizationId: string | null },
|
|
203
|
+
) {
|
|
204
|
+
const definitionTenantId = definition.tenantId ?? null
|
|
205
|
+
const definitionOrganizationId = definition.organizationId ?? null
|
|
206
|
+
const tenantMatches = definitionTenantId === null || definitionTenantId === scope.tenantId
|
|
207
|
+
const organizationMatches =
|
|
208
|
+
definitionOrganizationId === null ||
|
|
209
|
+
(scope.organizationId !== null && definitionOrganizationId === scope.organizationId)
|
|
210
|
+
return tenantMatches && organizationMatches
|
|
211
|
+
}
|
|
212
|
+
|
|
191
213
|
export async function GET(req: Request) {
|
|
192
214
|
const url = new URL(req.url)
|
|
193
215
|
const requestedEntityIds = parseEntityIds(url)
|
|
@@ -207,11 +229,12 @@ export async function GET(req: Request) {
|
|
|
207
229
|
|
|
208
230
|
const container = await createRequestContainer()
|
|
209
231
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
210
|
-
const
|
|
232
|
+
const definitionScope = resolveDefinitionScopeFromOrganizationScope(auth, scope)
|
|
233
|
+
const tenantId = definitionScope.tenantId
|
|
211
234
|
if (!tenantId) {
|
|
212
235
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
213
236
|
}
|
|
214
|
-
const organizationId =
|
|
237
|
+
const organizationId = definitionScope.organizationId
|
|
215
238
|
const { resolve } = container
|
|
216
239
|
const em = resolve('em') as any
|
|
217
240
|
let cache: CacheStrategy | undefined
|
|
@@ -273,22 +296,29 @@ export async function GET(req: Request) {
|
|
|
273
296
|
mode: 'public',
|
|
274
297
|
})
|
|
275
298
|
|
|
276
|
-
|
|
299
|
+
const tenantCandidates = [{ tenantId }, { tenantId: null as string | null }]
|
|
300
|
+
const organizationCandidates = [{ organizationId: null as string | null }]
|
|
301
|
+
if (organizationId) organizationCandidates.unshift({ organizationId })
|
|
302
|
+
const readScope = { tenantId, organizationId }
|
|
303
|
+
|
|
277
304
|
const whereActive = {
|
|
278
305
|
entityId: { $in: entityIds as any },
|
|
279
306
|
deletedAt: null,
|
|
280
307
|
$and: [
|
|
281
|
-
{ $or:
|
|
308
|
+
{ $or: tenantCandidates },
|
|
309
|
+
{ $or: organizationCandidates },
|
|
282
310
|
],
|
|
283
311
|
} as any
|
|
284
|
-
const defs = await em.find(CustomFieldDef, whereActive as any)
|
|
285
|
-
|
|
312
|
+
const defs = (await em.find(CustomFieldDef, whereActive as any))
|
|
313
|
+
.filter((definition: any) => definitionMatchesReadScope(definition, readScope))
|
|
314
|
+
const tombstones = (await em.find(CustomFieldDef, {
|
|
286
315
|
entityId: { $in: entityIds as any },
|
|
287
316
|
deletedAt: { $ne: null } as any,
|
|
288
317
|
$and: [
|
|
289
|
-
{ $or:
|
|
318
|
+
{ $or: tenantCandidates },
|
|
319
|
+
{ $or: organizationCandidates },
|
|
290
320
|
],
|
|
291
|
-
} as any)
|
|
321
|
+
} as any)).filter((definition: any) => definitionMatchesReadScope(definition, readScope))
|
|
292
322
|
|
|
293
323
|
const tombstonedByEntity = new Map<string, Set<string>>()
|
|
294
324
|
for (const entry of tombstones as any[]) {
|
|
@@ -468,6 +498,7 @@ export async function POST(req: Request) {
|
|
|
468
498
|
}
|
|
469
499
|
|
|
470
500
|
const container = await createRequestContainer()
|
|
501
|
+
const scope = await resolveDefinitionMutationScope({ auth, container, request: req })
|
|
471
502
|
const { resolve } = container
|
|
472
503
|
const em = resolve('em') as any
|
|
473
504
|
let cache: CacheStrategy | undefined
|
|
@@ -475,7 +506,7 @@ export async function POST(req: Request) {
|
|
|
475
506
|
cache = resolve('cache') as CacheStrategy
|
|
476
507
|
} catch {}
|
|
477
508
|
|
|
478
|
-
const where: any =
|
|
509
|
+
const where: any = createExactDefinitionWhere(input.entityId, input.key, scope)
|
|
479
510
|
let def = await em.findOne(CustomFieldDef, where)
|
|
480
511
|
|
|
481
512
|
const guard = await beginEntitiesMutationGuard({
|
|
@@ -512,7 +543,7 @@ export async function POST(req: Request) {
|
|
|
512
543
|
if (cfg.defaultValue !== undefined && cfg.defaultValue !== null) {
|
|
513
544
|
const validationError = await validateDefaultValueByKind(
|
|
514
545
|
cfg.defaultValue, input.kind, cfg, em,
|
|
515
|
-
{ tenantId:
|
|
546
|
+
{ tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
516
547
|
)
|
|
517
548
|
if (validationError) {
|
|
518
549
|
return NextResponse.json({ error: validationError }, { status: 400 })
|
|
@@ -521,13 +552,14 @@ export async function POST(req: Request) {
|
|
|
521
552
|
}
|
|
522
553
|
def.configJson = cfg
|
|
523
554
|
def.isActive = input.isActive ?? true
|
|
555
|
+
def.deletedAt = def.isActive === false ? (def.deletedAt ?? new Date()) : null
|
|
524
556
|
def.updatedAt = new Date()
|
|
525
557
|
em.persist(def)
|
|
526
558
|
await em.flush()
|
|
527
559
|
await guard.runAfterSuccess()
|
|
528
560
|
await invalidateDefinitionsCache(cache, {
|
|
529
|
-
tenantId:
|
|
530
|
-
organizationId:
|
|
561
|
+
tenantId: scope.tenantId,
|
|
562
|
+
organizationId: scope.organizationId,
|
|
531
563
|
entityIds: [input.entityId],
|
|
532
564
|
})
|
|
533
565
|
// Changing field definitions may impact forms but not sidebar items; no nav cache touch
|
|
@@ -543,36 +575,48 @@ export async function DELETE(req: Request) {
|
|
|
543
575
|
if (!entityId || !key) return NextResponse.json({ error: 'entityId and key are required' }, { status: 400 })
|
|
544
576
|
|
|
545
577
|
const container = await createRequestContainer()
|
|
578
|
+
const scope = await resolveDefinitionMutationScope({ auth, container, request: req })
|
|
546
579
|
const { resolve } = container
|
|
547
580
|
const em = resolve('em') as any
|
|
548
581
|
let cache: CacheStrategy | undefined
|
|
549
582
|
try {
|
|
550
583
|
cache = resolve('cache') as CacheStrategy
|
|
551
584
|
} catch {}
|
|
552
|
-
const where: any =
|
|
553
|
-
|
|
554
|
-
|
|
585
|
+
const where: any = createExactDefinitionWhere(entityId, key, scope)
|
|
586
|
+
let def = await em.findOne(CustomFieldDef, where)
|
|
587
|
+
let inherited: any | null = null
|
|
588
|
+
if (!def) {
|
|
589
|
+
inherited = selectVisibleDefinitionWinner(await em.find(CustomFieldDef, createVisibleDefinitionWhere(
|
|
590
|
+
entityId,
|
|
591
|
+
key,
|
|
592
|
+
scope,
|
|
593
|
+
{ deletedAt: null, isActive: true },
|
|
594
|
+
)))
|
|
595
|
+
if (!inherited) return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
|
596
|
+
}
|
|
555
597
|
|
|
556
598
|
const guard = await beginEntitiesMutationGuard({
|
|
557
599
|
container,
|
|
558
600
|
auth,
|
|
559
601
|
req,
|
|
560
602
|
resourceKind: FIELD_DEFINITION_RESOURCE_KIND,
|
|
561
|
-
resourceId: def
|
|
603
|
+
resourceId: def?.id ?? inherited?.id ?? `${entityId}:${key}`,
|
|
562
604
|
operation: 'delete',
|
|
563
605
|
mutationPayload: { entityId, key },
|
|
564
606
|
})
|
|
565
607
|
if (guard.blockedResponse) return guard.blockedResponse
|
|
566
608
|
|
|
567
|
-
def
|
|
568
|
-
|
|
569
|
-
|
|
609
|
+
if (!def) {
|
|
610
|
+
def = createScopedDefinitionTombstone(em, inherited, scope)
|
|
611
|
+
} else {
|
|
612
|
+
markDefinitionTombstoned(def)
|
|
613
|
+
}
|
|
570
614
|
em.persist(def)
|
|
571
615
|
await em.flush()
|
|
572
616
|
await guard.runAfterSuccess()
|
|
573
617
|
await invalidateDefinitionsCache(cache, {
|
|
574
|
-
tenantId:
|
|
575
|
-
organizationId:
|
|
618
|
+
tenantId: scope.tenantId,
|
|
619
|
+
organizationId: scope.organizationId,
|
|
576
620
|
entityIds: [entityId],
|
|
577
621
|
})
|
|
578
622
|
// Changing field definitions may impact forms but not sidebar items; no nav cache touch
|
|
@@ -27,6 +27,7 @@ import { normalizeCustomFieldOptions } from '@open-mercato/shared/modules/entiti
|
|
|
27
27
|
import { TranslationManager } from '@open-mercato/core/modules/translations/components/TranslationManager'
|
|
28
28
|
|
|
29
29
|
type Def = FieldDefinition
|
|
30
|
+
export type EntitySource = 'code' | 'custom'
|
|
30
31
|
type EntitiesListResponse = { items?: Array<Record<string, unknown>> }
|
|
31
32
|
type FieldsetGroup = { code: string; title?: string; hint?: string }
|
|
32
33
|
type FieldsetDefinition = { code: string; label: string; icon?: string; description?: string; groups?: FieldsetGroup[] }
|
|
@@ -43,7 +44,7 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
43
44
|
const queryClient = useQueryClient()
|
|
44
45
|
const entityId = useMemo(() => decodeURIComponent((params?.entityId as any) || ''), [params])
|
|
45
46
|
const [label, setLabel] = useState('')
|
|
46
|
-
const [entitySource, setEntitySource] = useState<
|
|
47
|
+
const [entitySource, setEntitySource] = useState<EntitySource>('custom')
|
|
47
48
|
const [entityFormLoading, setEntityFormLoading] = useState(true)
|
|
48
49
|
const [entityInitial, setEntityInitial] = useState<{ label?: string; description?: string; labelField?: string; defaultEditor?: string; showInSidebar?: boolean; updatedAt?: string }>({})
|
|
49
50
|
const [defs, setDefs] = useState<Def[]>([])
|
|
@@ -391,13 +392,15 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
391
392
|
showInSidebar: z.boolean().optional(),
|
|
392
393
|
}) as z.ZodType<Record<string, unknown>>
|
|
393
394
|
|
|
395
|
+
const metadataFieldsReadOnly = entitySource === 'code'
|
|
394
396
|
const fields: CrudField[] = [
|
|
395
|
-
{ id: 'label', label: 'Label', type: 'text', required: true },
|
|
396
|
-
{ id: 'description', label: 'Description', type: 'textarea' },
|
|
397
|
+
{ id: 'label', label: 'Label', type: 'text', required: true, readOnly: metadataFieldsReadOnly },
|
|
398
|
+
{ id: 'description', label: 'Description', type: 'textarea', readOnly: metadataFieldsReadOnly },
|
|
397
399
|
{
|
|
398
400
|
id: 'defaultEditor',
|
|
399
401
|
label: 'Default Editor (multiline)',
|
|
400
402
|
type: 'select',
|
|
403
|
+
readOnly: metadataFieldsReadOnly,
|
|
401
404
|
options: [
|
|
402
405
|
{ value: '', label: 'Default (Markdown)' },
|
|
403
406
|
{ value: 'markdown', label: 'Markdown (UIW)' },
|
|
@@ -493,17 +496,12 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
493
496
|
}
|
|
494
497
|
try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}
|
|
495
498
|
}
|
|
496
|
-
const defsPayload = {
|
|
499
|
+
const defsPayload = buildDefinitionsBatchPayload({
|
|
497
500
|
entityId,
|
|
498
|
-
|
|
499
|
-
key: d.key,
|
|
500
|
-
kind: d.kind,
|
|
501
|
-
configJson: d.configJson,
|
|
502
|
-
isActive: d.isActive !== false,
|
|
503
|
-
})),
|
|
501
|
+
defs,
|
|
504
502
|
fieldsets: buildFieldsetPayload(),
|
|
505
503
|
singleFieldsetPerRecord,
|
|
506
|
-
}
|
|
504
|
+
})
|
|
507
505
|
const callDefs = await apiCall('/api/entities/definitions.batch', {
|
|
508
506
|
method: 'POST',
|
|
509
507
|
headers: { 'content-type': 'application/json' },
|
|
@@ -607,12 +605,31 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
607
605
|
)
|
|
608
606
|
}
|
|
609
607
|
|
|
610
|
-
export function shouldRegisterEntityMetadata(entitySource:
|
|
608
|
+
export function shouldRegisterEntityMetadata(entitySource: EntitySource): boolean {
|
|
611
609
|
return entitySource === 'custom'
|
|
612
610
|
}
|
|
613
611
|
|
|
612
|
+
export function buildDefinitionsBatchPayload(options: {
|
|
613
|
+
entityId: string
|
|
614
|
+
defs: Array<Pick<Def, 'key' | 'kind' | 'configJson' | 'isActive'>>
|
|
615
|
+
fieldsets: FieldsetDefinition[]
|
|
616
|
+
singleFieldsetPerRecord: boolean
|
|
617
|
+
}) {
|
|
618
|
+
return {
|
|
619
|
+
entityId: options.entityId,
|
|
620
|
+
definitions: options.defs.filter((d) => !!d.key).map((d) => ({
|
|
621
|
+
key: d.key,
|
|
622
|
+
kind: d.kind,
|
|
623
|
+
configJson: d.configJson,
|
|
624
|
+
isActive: d.isActive !== false,
|
|
625
|
+
})),
|
|
626
|
+
fieldsets: options.fieldsets,
|
|
627
|
+
singleFieldsetPerRecord: options.singleFieldsetPerRecord,
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
614
631
|
export function buildEntityMetadataPayload(
|
|
615
|
-
entitySource:
|
|
632
|
+
entitySource: EntitySource,
|
|
616
633
|
vals: Record<string, unknown>,
|
|
617
634
|
): Record<string, unknown> | null {
|
|
618
635
|
const partial = entitySource === 'custom'
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
3
|
+
import { CustomFieldDef } from '@open-mercato/core/modules/entities/data/entities'
|
|
4
|
+
|
|
5
|
+
type AuthScope = {
|
|
6
|
+
tenantId?: string | null
|
|
7
|
+
orgId?: string | null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type DefinitionMutationScope = {
|
|
11
|
+
tenantId: string | null
|
|
12
|
+
organizationId: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type OrganizationScopeLike = {
|
|
16
|
+
tenantId?: string | null
|
|
17
|
+
selectedId?: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type DefinitionKeySelector = string | { $in: string[] }
|
|
21
|
+
|
|
22
|
+
type DefinitionVisibilityOptions = {
|
|
23
|
+
deletedAt?: null | { $ne: null }
|
|
24
|
+
isActive?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type DefinitionScopeRecord = {
|
|
28
|
+
tenantId?: string | null
|
|
29
|
+
organizationId?: string | null
|
|
30
|
+
updatedAt?: Date | string | number | null
|
|
31
|
+
createdAt?: Date | string | number | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type MutableDefinitionScopeRecord = DefinitionScopeRecord & {
|
|
35
|
+
isActive?: boolean
|
|
36
|
+
deletedAt?: Date | string | number | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveDefinitionScopeFromOrganizationScope(
|
|
40
|
+
auth: AuthScope,
|
|
41
|
+
scope: OrganizationScopeLike,
|
|
42
|
+
): DefinitionMutationScope {
|
|
43
|
+
const authTenantId = auth.tenantId ?? null
|
|
44
|
+
const scopeTenantId = scope.tenantId ?? null
|
|
45
|
+
const tenantMismatch = Boolean(authTenantId && scopeTenantId && authTenantId !== scopeTenantId)
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
tenantId: tenantMismatch ? authTenantId : (scopeTenantId ?? authTenantId),
|
|
49
|
+
organizationId: tenantMismatch
|
|
50
|
+
? (auth.orgId ?? null)
|
|
51
|
+
: (scope.selectedId ?? auth.orgId ?? null),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function resolveDefinitionMutationScope({
|
|
56
|
+
auth,
|
|
57
|
+
container,
|
|
58
|
+
request,
|
|
59
|
+
}: {
|
|
60
|
+
auth: AuthScope
|
|
61
|
+
container: unknown
|
|
62
|
+
request: Request
|
|
63
|
+
}): Promise<DefinitionMutationScope> {
|
|
64
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request } as any)
|
|
65
|
+
return resolveDefinitionScopeFromOrganizationScope(auth, scope)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createExactDefinitionWhere(
|
|
69
|
+
entityId: string,
|
|
70
|
+
key: DefinitionKeySelector,
|
|
71
|
+
scope: DefinitionMutationScope,
|
|
72
|
+
) {
|
|
73
|
+
return {
|
|
74
|
+
entityId,
|
|
75
|
+
key,
|
|
76
|
+
organizationId: scope.organizationId,
|
|
77
|
+
tenantId: scope.tenantId,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createVisibleDefinitionWhere(
|
|
82
|
+
entityId: string,
|
|
83
|
+
key: DefinitionKeySelector,
|
|
84
|
+
scope: DefinitionMutationScope,
|
|
85
|
+
options: DefinitionVisibilityOptions = {},
|
|
86
|
+
) {
|
|
87
|
+
const organizationCandidates = [{ organizationId: null as string | null }]
|
|
88
|
+
if (scope.organizationId) organizationCandidates.unshift({ organizationId: scope.organizationId })
|
|
89
|
+
|
|
90
|
+
const tenantCandidates = [{ tenantId: null as string | null }]
|
|
91
|
+
if (scope.tenantId) tenantCandidates.unshift({ tenantId: scope.tenantId })
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
entityId,
|
|
95
|
+
key,
|
|
96
|
+
...options,
|
|
97
|
+
$and: [
|
|
98
|
+
{ $or: organizationCandidates },
|
|
99
|
+
{ $or: tenantCandidates },
|
|
100
|
+
],
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function definitionTimestamp(def: DefinitionScopeRecord) {
|
|
105
|
+
const updatedAt = def?.updatedAt instanceof Date
|
|
106
|
+
? def.updatedAt.getTime()
|
|
107
|
+
: (def?.updatedAt ? new Date(def.updatedAt).getTime() : 0)
|
|
108
|
+
if (updatedAt) return updatedAt
|
|
109
|
+
return def?.createdAt instanceof Date
|
|
110
|
+
? def.createdAt.getTime()
|
|
111
|
+
: (def?.createdAt ? new Date(def.createdAt).getTime() : 0)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function definitionScopeScore(def: DefinitionScopeRecord) {
|
|
115
|
+
return (def?.tenantId ? 2 : 0) + (def?.organizationId ? 1 : 0)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function selectVisibleDefinitionWinner<T extends DefinitionScopeRecord>(definitions: T[]) {
|
|
119
|
+
let winner: T | null = null
|
|
120
|
+
for (const definition of definitions) {
|
|
121
|
+
if (!winner) {
|
|
122
|
+
winner = definition
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
const nextScore = definitionScopeScore(definition)
|
|
126
|
+
const winnerScore = definitionScopeScore(winner)
|
|
127
|
+
if (nextScore > winnerScore) {
|
|
128
|
+
winner = definition
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if (nextScore < winnerScore) continue
|
|
132
|
+
if (definitionTimestamp(definition) >= definitionTimestamp(winner)) winner = definition
|
|
133
|
+
}
|
|
134
|
+
return winner
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function markDefinitionTombstoned<T extends MutableDefinitionScopeRecord>(definition: T, now = new Date()) {
|
|
138
|
+
definition.isActive = false
|
|
139
|
+
definition.deletedAt = definition.deletedAt ?? now
|
|
140
|
+
definition.updatedAt = now
|
|
141
|
+
return definition
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function cloneConfigJson(configJson: unknown) {
|
|
145
|
+
if (configJson == null) return configJson
|
|
146
|
+
return JSON.parse(JSON.stringify(configJson))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createScopedDefinitionTombstone(
|
|
150
|
+
em: Pick<EntityManager, 'create'>,
|
|
151
|
+
source: {
|
|
152
|
+
entityId: string
|
|
153
|
+
key: string
|
|
154
|
+
kind: string
|
|
155
|
+
configJson?: unknown
|
|
156
|
+
},
|
|
157
|
+
scope: DefinitionMutationScope,
|
|
158
|
+
now = new Date(),
|
|
159
|
+
) {
|
|
160
|
+
return em.create(CustomFieldDef, {
|
|
161
|
+
entityId: source.entityId,
|
|
162
|
+
key: source.key,
|
|
163
|
+
kind: source.kind,
|
|
164
|
+
configJson: cloneConfigJson(source.configJson) ?? {},
|
|
165
|
+
organizationId: scope.organizationId,
|
|
166
|
+
tenantId: scope.tenantId,
|
|
167
|
+
isActive: false,
|
|
168
|
+
createdAt: now,
|
|
169
|
+
updatedAt: now,
|
|
170
|
+
deletedAt: now,
|
|
171
|
+
})
|
|
172
|
+
}
|