@open-mercato/core 0.4.5-develop-539cff4960 → 0.4.5-develop-7f44fcf045

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.
@@ -112,13 +112,16 @@ async function GET(req) {
112
112
  href: `/backend/entities/user/${encodeURIComponent(e.entityId)}/records`
113
113
  }));
114
114
  if (items.length) {
115
- const dd = roots.find((it) => it.groupKey === "entities.nav.group" && it.titleKey === "entities.nav.userEntities");
116
- if (dd) {
117
- const existing = dd.children || [];
115
+ const userEntitiesLegacyGroupKeys = /* @__PURE__ */ new Set(["settings.sections.dataDesigner", "entities.nav.group"]);
116
+ const userEntitiesAnchor = entries.find((entry) => entry.href === "/backend/entities/user") ?? entries.find(
117
+ (entry) => entry.titleKey === "entities.nav.userEntities" && typeof entry.groupKey === "string" && userEntitiesLegacyGroupKeys.has(entry.groupKey)
118
+ );
119
+ if (userEntitiesAnchor) {
120
+ const existing = userEntitiesAnchor.children || [];
118
121
  const dynamic = items.map((it) => ({
119
- groupId: dd.groupId,
120
- groupName: dd.groupName,
121
- groupKey: dd.groupKey,
122
+ groupId: userEntitiesAnchor.groupId,
123
+ groupName: userEntitiesAnchor.groupName,
124
+ groupKey: userEntitiesAnchor.groupKey,
122
125
  title: it.label,
123
126
  href: it.href,
124
127
  enabled: true,
@@ -128,7 +131,7 @@ async function GET(req) {
128
131
  const byHref = /* @__PURE__ */ new Map();
129
132
  for (const c of existing) if (!byHref.has(c.href)) byHref.set(c.href, c);
130
133
  for (const c of dynamic) if (!byHref.has(c.href)) byHref.set(c.href, c);
131
- dd.children = Array.from(byHref.values());
134
+ userEntitiesAnchor.children = Array.from(byHref.values());
132
135
  }
133
136
  }
134
137
  } catch (e) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/auth/api/admin/nav.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { hasAllFeatures } from '@open-mercato/shared/security/features'\nimport { CustomEntity } from '@open-mercato/core/modules/entities/data/entities'\nimport { slugifySidebarId } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport { applySidebarPreference, loadFirstRoleSidebarPreference, loadSidebarPreference } from '../../services/sidebarPreferencesService'\nimport { Role } from '../../data/entities'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst sidebarNavItemSchema: z.ZodType<{ href: string; title: string; defaultTitle: string; enabled: boolean; hidden?: boolean; children?: any[] }> = z.lazy(() =>\n z.object({\n href: z.string(),\n title: z.string(),\n defaultTitle: z.string(),\n enabled: z.boolean(),\n hidden: z.boolean().optional(),\n children: z.array(sidebarNavItemSchema).optional(),\n })\n)\n\nconst adminNavResponseSchema = z.object({\n groups: z.array(\n z.object({\n id: z.string(),\n name: z.string(),\n defaultName: z.string(),\n items: z.array(sidebarNavItemSchema),\n })\n ),\n})\n\nconst adminNavErrorSchema = z.object({\n error: z.string(),\n})\n\ntype SidebarItemNode = {\n href: string\n title: string\n defaultTitle: string\n enabled: boolean\n hidden?: boolean\n children?: SidebarItemNode[]\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { translate, locale } = await resolveTranslations()\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const cache = resolve('cache') as any\n\n // Cache key is user + tenant + organization scoped\n const cacheKey = `nav:sidebar:${locale}:${auth.sub}:${auth.tenantId || 'null'}:${auth.orgId || 'null'}`\n // try {\n // if (cache) {\n // const cached = await cache.get(cacheKey)\n // if (cached) return NextResponse.json(cached)\n // }\n // } catch {}\n\n // Load ACL once; we'll evaluate features locally without multiple calls\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n\n // Build nav entries from discovered backend routes\n type Entry = {\n groupId: string\n groupName: string\n groupKey?: string\n title: string\n titleKey?: string\n href: string\n enabled: boolean\n order?: number\n priority?: number\n children?: Entry[]\n }\n const entries: Entry[] = []\n\n function capitalize(s: string) { return s.charAt(0).toUpperCase() + s.slice(1) }\n function deriveTitleFromPath(p: string) {\n const seg = p.split('/').filter(Boolean).pop() || ''\n return seg ? seg.split('-').map(capitalize).join(' ') : 'Home'\n }\n\n const ctx = { auth: { roles: auth.roles || [], sub: auth.sub, tenantId: auth.tenantId, orgId: auth.orgId } }\n const modules = getModules()\n for (const m of (modules as any[])) {\n const groupDefault = capitalize(m.id)\n for (const r of (m.backendRoutes || [])) {\n const href = (r.pattern ?? r.path ?? '') as string\n if (!href || href.includes('[')) continue\n if ((r as any).navHidden) continue\n const title = (r.title as string) || deriveTitleFromPath(href)\n const titleKey = (r as any).pageTitleKey ?? (r as any).titleKey\n const groupName = (r.group as string) || groupDefault\n const groupKey = (r as any).pageGroupKey ?? (r as any).groupKey\n const groupId = typeof groupKey === 'string' && groupKey ? groupKey : slugifySidebarId(groupName)\n const visible = r.visible ? await Promise.resolve(r.visible(ctx)) : true\n if (!visible) continue\n const enabled = r.enabled ? await Promise.resolve(r.enabled(ctx)) : true\n const requiredRoles = (r.requireRoles as string[]) || []\n if (requiredRoles.length) {\n const roles = auth.roles || []\n const ok = requiredRoles.some((role) => roles.includes(role))\n if (!ok) continue\n }\n const features = (r as any).requireFeatures as string[] | undefined\n if (!acl.isSuperAdmin && !hasAllFeatures(acl.features, features)) continue\n const order = (r as any).order as number | undefined\n const priority = ((r as any).priority as number | undefined) ?? order\n entries.push({ groupId, groupName, groupKey, title, titleKey, href, enabled, order, priority })\n }\n }\n\n // Parent-child relationships within the same group by href prefix\n const roots: any[] = []\n for (const e of entries) {\n let parent: any | undefined\n for (const p of entries) {\n if (p === e) continue\n if (p.groupId !== e.groupId) continue\n if (!e.href.startsWith(p.href + '/')) continue\n if (!parent || p.href.length > parent.href.length) parent = p\n }\n if (parent) {\n ;(parent as any).children = (parent as any).children || []\n ;(parent as any).children.push(e)\n } else {\n roots.push(e)\n }\n }\n\n // Add dynamic user entities into Data designer > User Entities\n const where: any = { isActive: true, showInSidebar: true }\n where.$and = [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ]\n try {\n const entities = await em.find(CustomEntity as any, where as any, { orderBy: { label: 'asc' } as any })\n const items = (entities as any[]).map((e) => ({\n entityId: e.entityId,\n label: e.label,\n href: `/backend/entities/user/${encodeURIComponent(e.entityId)}/records`\n }))\n if (items.length) {\n const dd = roots.find((it: Entry) => it.groupKey === 'entities.nav.group' && it.titleKey === 'entities.nav.userEntities')\n if (dd) {\n const existing = dd.children || []\n const dynamic = items.map((it) => ({\n groupId: dd.groupId,\n groupName: dd.groupName,\n groupKey: dd.groupKey,\n title: it.label,\n href: it.href,\n enabled: true,\n order: 1000,\n priority: 1000,\n }))\n const byHref = new Map<string, Entry>()\n for (const c of existing) if (!byHref.has(c.href)) byHref.set(c.href, c)\n for (const c of dynamic) if (!byHref.has(c.href)) byHref.set(c.href, c)\n dd.children = Array.from(byHref.values())\n }\n }\n } catch (e) {\n console.error('Error loading user entities', e)\n }\n\n // Sort roots and children\n const sortItems = (arr: any[]) => {\n arr.sort((a, b) => {\n if (a.group !== b.group) return a.group.localeCompare(b.group)\n const ap = a.priority ?? a.order ?? 10000\n const bp = b.priority ?? b.order ?? 10000\n if (ap !== bp) return ap - bp\n return String(a.title).localeCompare(String(b.title))\n })\n for (const it of arr) if (it.children?.length) sortItems(it.children)\n }\n sortItems(roots)\n\n // Group into sidebar groups\n type GroupBucket = {\n id: string\n rawName: string\n key?: string\n weight: number\n entries: Entry[]\n }\n\n const groupBuckets = new Map<string, GroupBucket>()\n for (const entry of roots) {\n const weight = entry.priority ?? entry.order ?? 10_000\n if (!groupBuckets.has(entry.groupId)) {\n groupBuckets.set(entry.groupId, {\n id: entry.groupId,\n rawName: entry.groupName,\n key: entry.groupKey as string | undefined,\n weight,\n entries: [entry],\n })\n } else {\n const bucket = groupBuckets.get(entry.groupId)!\n bucket.entries.push(entry)\n if (weight < bucket.weight) bucket.weight = weight\n if (!bucket.key && entry.groupKey) bucket.key = entry.groupKey as string\n if (!bucket.rawName && entry.groupName) bucket.rawName = entry.groupName\n }\n }\n\n const toItem = (entry: Entry): SidebarItemNode => {\n const defaultTitle = entry.titleKey ? translate(entry.titleKey, entry.title) : entry.title\n return {\n href: entry.href,\n title: defaultTitle,\n defaultTitle,\n enabled: entry.enabled,\n children: entry.children?.map((child) => toItem(child)),\n }\n }\n\n const groups = Array.from(groupBuckets.values()).map((bucket) => {\n const defaultName = bucket.key ? translate(bucket.key, bucket.rawName) : bucket.rawName\n return {\n id: bucket.id,\n key: bucket.key,\n name: defaultName,\n defaultName,\n weight: bucket.weight,\n items: bucket.entries.map((entry) => toItem(entry)),\n }\n })\n const defaultGroupOrder = [\n 'customers.nav.group',\n 'catalog.nav.group',\n 'customers~sales.nav.group',\n 'resources.nav.group',\n 'staff.nav.group',\n 'entities.nav.group',\n 'directory.nav.group',\n 'customers.storage.nav.group',\n ]\n const groupOrderIndex = new Map(defaultGroupOrder.map((id, index) => [id, index]))\n groups.sort((a, b) => {\n const aIndex = groupOrderIndex.get(a.id)\n const bIndex = groupOrderIndex.get(b.id)\n if (aIndex !== undefined || bIndex !== undefined) {\n if (aIndex === undefined) return 1\n if (bIndex === undefined) return -1\n if (aIndex !== bIndex) return aIndex - bIndex\n }\n if (a.weight !== b.weight) return a.weight - b.weight\n return a.name.localeCompare(b.name)\n })\n const defaultGroupCount = defaultGroupOrder.length\n groups.forEach((group, index) => {\n const rank = groupOrderIndex.get(group.id)\n const fallbackWeight = typeof group.weight === 'number' ? group.weight : 10_000\n const normalized =\n (rank !== undefined ? rank : defaultGroupCount + index) * 1_000_000 +\n Math.min(Math.max(fallbackWeight, 0), 999_999)\n group.weight = normalized\n })\n\n let rolePreference = null\n if (Array.isArray(auth.roles) && auth.roles.length) {\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const roleRecords = await em.find(Role, {\n name: { $in: auth.roles },\n ...roleScope,\n } as any)\n const roleIds = roleRecords.map((role: Role) => role.id)\n if (roleIds.length) {\n rolePreference = await loadFirstRoleSidebarPreference(em, {\n roleIds,\n tenantId: auth.tenantId ?? null,\n locale,\n })\n }\n }\n\n const groupsWithRole = rolePreference ? applySidebarPreference(groups, rolePreference) : groups\n const baseForUser = adoptSidebarDefaults(groupsWithRole)\n\n // For API key auth, use userId (the actual user) if available; otherwise skip user preferences\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n const preference = effectiveUserId\n ? await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n : null\n\n const withPreference = applySidebarPreference(baseForUser, preference)\n\n const payload = {\n groups: withPreference.map((group) => ({\n id: group.id,\n name: group.name,\n defaultName: group.defaultName,\n items: (group.items as SidebarItemNode[]).map((item) => ({\n href: item.href,\n title: item.title,\n defaultTitle: item.defaultTitle,\n enabled: item.enabled,\n hidden: item.hidden,\n children: item.children?.map((child) => ({\n href: child.href,\n title: child.title,\n defaultTitle: child.defaultTitle,\n enabled: child.enabled,\n hidden: child.hidden,\n })),\n })),\n })),\n }\n\n try {\n if (cache) {\n const tags = [\n `rbac:user:${auth.sub}`,\n auth.tenantId ? `rbac:tenant:${auth.tenantId}` : undefined,\n `nav:entities:${auth.tenantId || 'null'}`,\n `nav:locale:${locale}`,\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${auth.tenantId || 'null'}:${auth.orgId || 'null'}:${locale}`,\n ...(Array.isArray(auth.roles) ? auth.roles.map((role: string) => `nav:sidebar:role:${role}`) : []),\n ].filter(Boolean) as string[]\n await cache.set(cacheKey, payload, { tags })\n }\n } catch {}\n\n return NextResponse.json(payload)\n}\n\nfunction adoptSidebarDefaults(groups: ReturnType<typeof applySidebarPreference>) {\n const adoptItems = <T extends { title: string; defaultTitle?: string; children?: T[] }>(items: T[]): T[] =>\n items.map((item) => ({\n ...item,\n defaultTitle: item.title,\n children: item.children ? adoptItems(item.children) : undefined,\n }))\n\n return groups.map((group) => ({\n ...group,\n defaultName: group.name,\n items: adoptItems(group.items),\n }))\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Admin sidebar navigation',\n methods: {\n GET: {\n summary: 'Resolve sidebar entries',\n description:\n 'Returns the backend navigation tree available to the authenticated administrator after applying role and personal sidebar preferences.',\n responses: [\n { status: 200, description: 'Sidebar navigation structure', schema: adminNavResponseSchema },\n { status: 401, description: 'Unauthorized', schema: adminNavErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { hasAllFeatures } from '@open-mercato/shared/security/features'\nimport { CustomEntity } from '@open-mercato/core/modules/entities/data/entities'\nimport { slugifySidebarId } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport { applySidebarPreference, loadFirstRoleSidebarPreference, loadSidebarPreference } from '../../services/sidebarPreferencesService'\nimport { Role } from '../../data/entities'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst sidebarNavItemSchema: z.ZodType<{ href: string; title: string; defaultTitle: string; enabled: boolean; hidden?: boolean; children?: any[] }> = z.lazy(() =>\n z.object({\n href: z.string(),\n title: z.string(),\n defaultTitle: z.string(),\n enabled: z.boolean(),\n hidden: z.boolean().optional(),\n children: z.array(sidebarNavItemSchema).optional(),\n })\n)\n\nconst adminNavResponseSchema = z.object({\n groups: z.array(\n z.object({\n id: z.string(),\n name: z.string(),\n defaultName: z.string(),\n items: z.array(sidebarNavItemSchema),\n })\n ),\n})\n\nconst adminNavErrorSchema = z.object({\n error: z.string(),\n})\n\ntype SidebarItemNode = {\n href: string\n title: string\n defaultTitle: string\n enabled: boolean\n hidden?: boolean\n children?: SidebarItemNode[]\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { translate, locale } = await resolveTranslations()\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const cache = resolve('cache') as any\n\n // Cache key is user + tenant + organization scoped\n const cacheKey = `nav:sidebar:${locale}:${auth.sub}:${auth.tenantId || 'null'}:${auth.orgId || 'null'}`\n // try {\n // if (cache) {\n // const cached = await cache.get(cacheKey)\n // if (cached) return NextResponse.json(cached)\n // }\n // } catch {}\n\n // Load ACL once; we'll evaluate features locally without multiple calls\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n\n // Build nav entries from discovered backend routes\n type Entry = {\n groupId: string\n groupName: string\n groupKey?: string\n title: string\n titleKey?: string\n href: string\n enabled: boolean\n order?: number\n priority?: number\n children?: Entry[]\n }\n const entries: Entry[] = []\n\n function capitalize(s: string) { return s.charAt(0).toUpperCase() + s.slice(1) }\n function deriveTitleFromPath(p: string) {\n const seg = p.split('/').filter(Boolean).pop() || ''\n return seg ? seg.split('-').map(capitalize).join(' ') : 'Home'\n }\n\n const ctx = { auth: { roles: auth.roles || [], sub: auth.sub, tenantId: auth.tenantId, orgId: auth.orgId } }\n const modules = getModules()\n for (const m of (modules as any[])) {\n const groupDefault = capitalize(m.id)\n for (const r of (m.backendRoutes || [])) {\n const href = (r.pattern ?? r.path ?? '') as string\n if (!href || href.includes('[')) continue\n if ((r as any).navHidden) continue\n const title = (r.title as string) || deriveTitleFromPath(href)\n const titleKey = (r as any).pageTitleKey ?? (r as any).titleKey\n const groupName = (r.group as string) || groupDefault\n const groupKey = (r as any).pageGroupKey ?? (r as any).groupKey\n const groupId = typeof groupKey === 'string' && groupKey ? groupKey : slugifySidebarId(groupName)\n const visible = r.visible ? await Promise.resolve(r.visible(ctx)) : true\n if (!visible) continue\n const enabled = r.enabled ? await Promise.resolve(r.enabled(ctx)) : true\n const requiredRoles = (r.requireRoles as string[]) || []\n if (requiredRoles.length) {\n const roles = auth.roles || []\n const ok = requiredRoles.some((role) => roles.includes(role))\n if (!ok) continue\n }\n const features = (r as any).requireFeatures as string[] | undefined\n if (!acl.isSuperAdmin && !hasAllFeatures(acl.features, features)) continue\n const order = (r as any).order as number | undefined\n const priority = ((r as any).priority as number | undefined) ?? order\n entries.push({ groupId, groupName, groupKey, title, titleKey, href, enabled, order, priority })\n }\n }\n\n // Parent-child relationships within the same group by href prefix\n const roots: any[] = []\n for (const e of entries) {\n let parent: any | undefined\n for (const p of entries) {\n if (p === e) continue\n if (p.groupId !== e.groupId) continue\n if (!e.href.startsWith(p.href + '/')) continue\n if (!parent || p.href.length > parent.href.length) parent = p\n }\n if (parent) {\n ;(parent as any).children = (parent as any).children || []\n ;(parent as any).children.push(e)\n } else {\n roots.push(e)\n }\n }\n\n // Add dynamic user entities into Data designer > User Entities\n const where: any = { isActive: true, showInSidebar: true }\n where.$and = [\n { $or: [ { organizationId: auth.orgId ?? undefined as any }, { organizationId: null } ] },\n { $or: [ { tenantId: auth.tenantId ?? undefined as any }, { tenantId: null } ] },\n ]\n try {\n const entities = await em.find(CustomEntity as any, where as any, { orderBy: { label: 'asc' } as any })\n const items = (entities as any[]).map((e) => ({\n entityId: e.entityId,\n label: e.label,\n href: `/backend/entities/user/${encodeURIComponent(e.entityId)}/records`\n }))\n if (items.length) {\n const userEntitiesLegacyGroupKeys = new Set(['settings.sections.dataDesigner', 'entities.nav.group'])\n const userEntitiesAnchor = entries.find((entry: Entry) => entry.href === '/backend/entities/user')\n ?? entries.find((entry: Entry) =>\n entry.titleKey === 'entities.nav.userEntities' &&\n typeof entry.groupKey === 'string' &&\n userEntitiesLegacyGroupKeys.has(entry.groupKey),\n )\n if (userEntitiesAnchor) {\n const existing = userEntitiesAnchor.children || []\n const dynamic = items.map((it) => ({\n groupId: userEntitiesAnchor.groupId,\n groupName: userEntitiesAnchor.groupName,\n groupKey: userEntitiesAnchor.groupKey,\n title: it.label,\n href: it.href,\n enabled: true,\n order: 1000,\n priority: 1000,\n }))\n const byHref = new Map<string, Entry>()\n for (const c of existing) if (!byHref.has(c.href)) byHref.set(c.href, c)\n for (const c of dynamic) if (!byHref.has(c.href)) byHref.set(c.href, c)\n userEntitiesAnchor.children = Array.from(byHref.values())\n }\n }\n } catch (e) {\n console.error('Error loading user entities', e)\n }\n\n // Sort roots and children\n const sortItems = (arr: any[]) => {\n arr.sort((a, b) => {\n if (a.group !== b.group) return a.group.localeCompare(b.group)\n const ap = a.priority ?? a.order ?? 10000\n const bp = b.priority ?? b.order ?? 10000\n if (ap !== bp) return ap - bp\n return String(a.title).localeCompare(String(b.title))\n })\n for (const it of arr) if (it.children?.length) sortItems(it.children)\n }\n sortItems(roots)\n\n // Group into sidebar groups\n type GroupBucket = {\n id: string\n rawName: string\n key?: string\n weight: number\n entries: Entry[]\n }\n\n const groupBuckets = new Map<string, GroupBucket>()\n for (const entry of roots) {\n const weight = entry.priority ?? entry.order ?? 10_000\n if (!groupBuckets.has(entry.groupId)) {\n groupBuckets.set(entry.groupId, {\n id: entry.groupId,\n rawName: entry.groupName,\n key: entry.groupKey as string | undefined,\n weight,\n entries: [entry],\n })\n } else {\n const bucket = groupBuckets.get(entry.groupId)!\n bucket.entries.push(entry)\n if (weight < bucket.weight) bucket.weight = weight\n if (!bucket.key && entry.groupKey) bucket.key = entry.groupKey as string\n if (!bucket.rawName && entry.groupName) bucket.rawName = entry.groupName\n }\n }\n\n const toItem = (entry: Entry): SidebarItemNode => {\n const defaultTitle = entry.titleKey ? translate(entry.titleKey, entry.title) : entry.title\n return {\n href: entry.href,\n title: defaultTitle,\n defaultTitle,\n enabled: entry.enabled,\n children: entry.children?.map((child) => toItem(child)),\n }\n }\n\n const groups = Array.from(groupBuckets.values()).map((bucket) => {\n const defaultName = bucket.key ? translate(bucket.key, bucket.rawName) : bucket.rawName\n return {\n id: bucket.id,\n key: bucket.key,\n name: defaultName,\n defaultName,\n weight: bucket.weight,\n items: bucket.entries.map((entry) => toItem(entry)),\n }\n })\n const defaultGroupOrder = [\n 'customers.nav.group',\n 'catalog.nav.group',\n 'customers~sales.nav.group',\n 'resources.nav.group',\n 'staff.nav.group',\n 'entities.nav.group',\n 'directory.nav.group',\n 'customers.storage.nav.group',\n ]\n const groupOrderIndex = new Map(defaultGroupOrder.map((id, index) => [id, index]))\n groups.sort((a, b) => {\n const aIndex = groupOrderIndex.get(a.id)\n const bIndex = groupOrderIndex.get(b.id)\n if (aIndex !== undefined || bIndex !== undefined) {\n if (aIndex === undefined) return 1\n if (bIndex === undefined) return -1\n if (aIndex !== bIndex) return aIndex - bIndex\n }\n if (a.weight !== b.weight) return a.weight - b.weight\n return a.name.localeCompare(b.name)\n })\n const defaultGroupCount = defaultGroupOrder.length\n groups.forEach((group, index) => {\n const rank = groupOrderIndex.get(group.id)\n const fallbackWeight = typeof group.weight === 'number' ? group.weight : 10_000\n const normalized =\n (rank !== undefined ? rank : defaultGroupCount + index) * 1_000_000 +\n Math.min(Math.max(fallbackWeight, 0), 999_999)\n group.weight = normalized\n })\n\n let rolePreference = null\n if (Array.isArray(auth.roles) && auth.roles.length) {\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const roleRecords = await em.find(Role, {\n name: { $in: auth.roles },\n ...roleScope,\n } as any)\n const roleIds = roleRecords.map((role: Role) => role.id)\n if (roleIds.length) {\n rolePreference = await loadFirstRoleSidebarPreference(em, {\n roleIds,\n tenantId: auth.tenantId ?? null,\n locale,\n })\n }\n }\n\n const groupsWithRole = rolePreference ? applySidebarPreference(groups, rolePreference) : groups\n const baseForUser = adoptSidebarDefaults(groupsWithRole)\n\n // For API key auth, use userId (the actual user) if available; otherwise skip user preferences\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n const preference = effectiveUserId\n ? await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n : null\n\n const withPreference = applySidebarPreference(baseForUser, preference)\n\n const payload = {\n groups: withPreference.map((group) => ({\n id: group.id,\n name: group.name,\n defaultName: group.defaultName,\n items: (group.items as SidebarItemNode[]).map((item) => ({\n href: item.href,\n title: item.title,\n defaultTitle: item.defaultTitle,\n enabled: item.enabled,\n hidden: item.hidden,\n children: item.children?.map((child) => ({\n href: child.href,\n title: child.title,\n defaultTitle: child.defaultTitle,\n enabled: child.enabled,\n hidden: child.hidden,\n })),\n })),\n })),\n }\n\n try {\n if (cache) {\n const tags = [\n `rbac:user:${auth.sub}`,\n auth.tenantId ? `rbac:tenant:${auth.tenantId}` : undefined,\n `nav:entities:${auth.tenantId || 'null'}`,\n `nav:locale:${locale}`,\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${auth.tenantId || 'null'}:${auth.orgId || 'null'}:${locale}`,\n ...(Array.isArray(auth.roles) ? auth.roles.map((role: string) => `nav:sidebar:role:${role}`) : []),\n ].filter(Boolean) as string[]\n await cache.set(cacheKey, payload, { tags })\n }\n } catch {}\n\n return NextResponse.json(payload)\n}\n\nfunction adoptSidebarDefaults(groups: ReturnType<typeof applySidebarPreference>) {\n const adoptItems = <T extends { title: string; defaultTitle?: string; children?: T[] }>(items: T[]): T[] =>\n items.map((item) => ({\n ...item,\n defaultTitle: item.title,\n children: item.children ? adoptItems(item.children) : undefined,\n }))\n\n return groups.map((group) => ({\n ...group,\n defaultName: group.name,\n items: adoptItems(group.items),\n }))\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Admin sidebar navigation',\n methods: {\n GET: {\n summary: 'Resolve sidebar entries',\n description:\n 'Returns the backend navigation tree available to the authenticated administrator after applying role and personal sidebar preferences.',\n responses: [\n { status: 200, description: 'Sidebar navigation structure', schema: adminNavResponseSchema },\n { status: 401, description: 'Unauthorized', schema: adminNavErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,SAAS;AAClB,SAAS,kBAAkB;AAC3B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,wBAAwB,gCAAgC,6BAA6B;AAC9F,SAAS,YAAY;AAEd,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,MAAM,uBAA+I,EAAE;AAAA,EAAK,MAC1J,EAAE,OAAO;AAAA,IACP,MAAM,EAAE,OAAO;AAAA,IACf,OAAO,EAAE,OAAO;AAAA,IAChB,cAAc,EAAE,OAAO;AAAA,IACvB,SAAS,EAAE,QAAQ;AAAA,IACnB,QAAQ,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC7B,UAAU,EAAE,MAAM,oBAAoB,EAAE,SAAS;AAAA,EACnD,CAAC;AACH;AAEA,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,QAAQ,EAAE;AAAA,IACR,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO;AAAA,MACb,MAAM,EAAE,OAAO;AAAA,MACf,aAAa,EAAE,OAAO;AAAA,MACtB,OAAO,EAAE,MAAM,oBAAoB;AAAA,IACrC,CAAC;AAAA,EACH;AACF,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,OAAO,EAAE,OAAO;AAClB,CAAC;AAWD,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE9E,QAAM,EAAE,WAAW,OAAO,IAAI,MAAM,oBAAoB;AAExD,QAAM,EAAE,QAAQ,IAAI,MAAM,uBAAuB;AACjD,QAAM,KAAK,QAAQ,IAAI;AACvB,QAAM,OAAO,QAAQ,aAAa;AAClC,QAAM,QAAQ,QAAQ,OAAO;AAG7B,QAAM,WAAW,eAAe,MAAM,IAAI,KAAK,GAAG,IAAI,KAAK,YAAY,MAAM,IAAI,KAAK,SAAS,MAAM;AASrG,QAAM,MAAM,MAAM,KAAK,QAAQ,KAAK,KAAK,EAAE,UAAU,KAAK,YAAY,MAAM,gBAAgB,KAAK,SAAS,KAAK,CAAC;AAehH,QAAM,UAAmB,CAAC;AAE1B,WAAS,WAAW,GAAW;AAAE,WAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC;AAAA,EAAE;AAC/E,WAAS,oBAAoB,GAAW;AACtC,UAAM,MAAM,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI,KAAK;AAClD,WAAO,MAAM,IAAI,MAAM,GAAG,EAAE,IAAI,UAAU,EAAE,KAAK,GAAG,IAAI;AAAA,EAC1D;AAEA,QAAM,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC,GAAG,KAAK,KAAK,KAAK,UAAU,KAAK,UAAU,OAAO,KAAK,MAAM,EAAE;AAC3G,QAAM,UAAU,WAAW;AAC3B,aAAW,KAAM,SAAmB;AAClC,UAAM,eAAe,WAAW,EAAE,EAAE;AACpC,eAAW,KAAM,EAAE,iBAAiB,CAAC,GAAI;AACvC,YAAM,OAAQ,EAAE,WAAW,EAAE,QAAQ;AACrC,UAAI,CAAC,QAAQ,KAAK,SAAS,GAAG,EAAG;AACjC,UAAK,EAAU,UAAW;AAC1B,YAAM,QAAS,EAAE,SAAoB,oBAAoB,IAAI;AAC7D,YAAM,WAAY,EAAU,gBAAiB,EAAU;AACvD,YAAM,YAAa,EAAE,SAAoB;AACzC,YAAM,WAAY,EAAU,gBAAiB,EAAU;AACvD,YAAM,UAAU,OAAO,aAAa,YAAY,WAAW,WAAW,iBAAiB,SAAS;AAChG,YAAM,UAAU,EAAE,UAAU,MAAM,QAAQ,QAAQ,EAAE,QAAQ,GAAG,CAAC,IAAI;AACpE,UAAI,CAAC,QAAS;AACd,YAAM,UAAU,EAAE,UAAU,MAAM,QAAQ,QAAQ,EAAE,QAAQ,GAAG,CAAC,IAAI;AACpE,YAAM,gBAAiB,EAAE,gBAA6B,CAAC;AACvD,UAAI,cAAc,QAAQ;AACxB,cAAM,QAAQ,KAAK,SAAS,CAAC;AAC7B,cAAM,KAAK,cAAc,KAAK,CAAC,SAAS,MAAM,SAAS,IAAI,CAAC;AAC5D,YAAI,CAAC,GAAI;AAAA,MACX;AACA,YAAM,WAAY,EAAU;AAC5B,UAAI,CAAC,IAAI,gBAAgB,CAAC,eAAe,IAAI,UAAU,QAAQ,EAAG;AAClE,YAAM,QAAS,EAAU;AACzB,YAAM,WAAa,EAAU,YAAmC;AAChE,cAAQ,KAAK,EAAE,SAAS,WAAW,UAAU,OAAO,UAAU,MAAM,SAAS,OAAO,SAAS,CAAC;AAAA,IAChG;AAAA,EACF;AAGA,QAAM,QAAe,CAAC;AACtB,aAAW,KAAK,SAAS;AACvB,QAAI;AACJ,eAAW,KAAK,SAAS;AACvB,UAAI,MAAM,EAAG;AACb,UAAI,EAAE,YAAY,EAAE,QAAS;AAC7B,UAAI,CAAC,EAAE,KAAK,WAAW,EAAE,OAAO,GAAG,EAAG;AACtC,UAAI,CAAC,UAAU,EAAE,KAAK,SAAS,OAAO,KAAK,OAAQ,UAAS;AAAA,IAC9D;AACA,QAAI,QAAQ;AACV;AAAC,MAAC,OAAe,WAAY,OAAe,YAAY,CAAC;AACxD,MAAC,OAAe,SAAS,KAAK,CAAC;AAAA,IAClC,OAAO;AACL,YAAM,KAAK,CAAC;AAAA,IACd;AAAA,EACF;AAGA,QAAM,QAAa,EAAE,UAAU,MAAM,eAAe,KAAK;AACzD,QAAM,OAAO;AAAA,IACX,EAAE,KAAK,CAAE,EAAE,gBAAgB,KAAK,SAAS,OAAiB,GAAG,EAAE,gBAAgB,KAAK,CAAE,EAAE;AAAA,IACxF,EAAE,KAAK,CAAE,EAAE,UAAU,KAAK,YAAY,OAAiB,GAAG,EAAE,UAAU,KAAK,CAAE,EAAE;AAAA,EACjF;AACA,MAAI;AACF,UAAM,WAAW,MAAM,GAAG,KAAK,cAAqB,OAAc,EAAE,SAAS,EAAE,OAAO,MAAM,EAAS,CAAC;AACtG,UAAM,QAAS,SAAmB,IAAI,CAAC,OAAO;AAAA,MAC5C,UAAU,EAAE;AAAA,MACZ,OAAO,EAAE;AAAA,MACT,MAAM,0BAA0B,mBAAmB,EAAE,QAAQ,CAAC;AAAA,IAChE,EAAE;AACF,QAAI,MAAM,QAAQ;AAChB,YAAM,8BAA8B,oBAAI,IAAI,CAAC,kCAAkC,oBAAoB,CAAC;AACpG,YAAM,qBAAqB,QAAQ,KAAK,CAAC,UAAiB,MAAM,SAAS,wBAAwB,KAC5F,QAAQ;AAAA,QAAK,CAAC,UACf,MAAM,aAAa,+BACnB,OAAO,MAAM,aAAa,YAC1B,4BAA4B,IAAI,MAAM,QAAQ;AAAA,MAChD;AACF,UAAI,oBAAoB;AACtB,cAAM,WAAW,mBAAmB,YAAY,CAAC;AACjD,cAAM,UAAU,MAAM,IAAI,CAAC,QAAQ;AAAA,UACjC,SAAS,mBAAmB;AAAA,UAC5B,WAAW,mBAAmB;AAAA,UAC9B,UAAU,mBAAmB;AAAA,UAC7B,OAAO,GAAG;AAAA,UACV,MAAM,GAAG;AAAA,UACT,SAAS;AAAA,UACT,OAAO;AAAA,UACP,UAAU;AAAA,QACZ,EAAE;AACF,cAAM,SAAS,oBAAI,IAAmB;AACtC,mBAAW,KAAK,SAAU,KAAI,CAAC,OAAO,IAAI,EAAE,IAAI,EAAG,QAAO,IAAI,EAAE,MAAM,CAAC;AACvE,mBAAW,KAAK,QAAS,KAAI,CAAC,OAAO,IAAI,EAAE,IAAI,EAAG,QAAO,IAAI,EAAE,MAAM,CAAC;AACtE,2BAAmB,WAAW,MAAM,KAAK,OAAO,OAAO,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,EACF,SAAS,GAAG;AACV,YAAQ,MAAM,+BAA+B,CAAC;AAAA,EAChD;AAGA,QAAM,YAAY,CAAC,QAAe;AAChC,QAAI,KAAK,CAAC,GAAG,MAAM;AACjB,UAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,MAAM,cAAc,EAAE,KAAK;AAC7D,YAAM,KAAK,EAAE,YAAY,EAAE,SAAS;AACpC,YAAM,KAAK,EAAE,YAAY,EAAE,SAAS;AACpC,UAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,aAAO,OAAO,EAAE,KAAK,EAAE,cAAc,OAAO,EAAE,KAAK,CAAC;AAAA,IACtD,CAAC;AACD,eAAW,MAAM,IAAK,KAAI,GAAG,UAAU,OAAQ,WAAU,GAAG,QAAQ;AAAA,EACtE;AACA,YAAU,KAAK;AAWf,QAAM,eAAe,oBAAI,IAAyB;AAClD,aAAW,SAAS,OAAO;AACzB,UAAM,SAAS,MAAM,YAAY,MAAM,SAAS;AAChD,QAAI,CAAC,aAAa,IAAI,MAAM,OAAO,GAAG;AACpC,mBAAa,IAAI,MAAM,SAAS;AAAA,QAC9B,IAAI,MAAM;AAAA,QACV,SAAS,MAAM;AAAA,QACf,KAAK,MAAM;AAAA,QACX;AAAA,QACA,SAAS,CAAC,KAAK;AAAA,MACjB,CAAC;AAAA,IACH,OAAO;AACL,YAAM,SAAS,aAAa,IAAI,MAAM,OAAO;AAC7C,aAAO,QAAQ,KAAK,KAAK;AACzB,UAAI,SAAS,OAAO,OAAQ,QAAO,SAAS;AAC5C,UAAI,CAAC,OAAO,OAAO,MAAM,SAAU,QAAO,MAAM,MAAM;AACtD,UAAI,CAAC,OAAO,WAAW,MAAM,UAAW,QAAO,UAAU,MAAM;AAAA,IACjE;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,UAAkC;AAChD,UAAM,eAAe,MAAM,WAAW,UAAU,MAAM,UAAU,MAAM,KAAK,IAAI,MAAM;AACrF,WAAO;AAAA,MACL,MAAM,MAAM;AAAA,MACZ,OAAO;AAAA,MACP;AAAA,MACA,SAAS,MAAM;AAAA,MACf,UAAU,MAAM,UAAU,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,KAAK,aAAa,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW;AAC/D,UAAM,cAAc,OAAO,MAAM,UAAU,OAAO,KAAK,OAAO,OAAO,IAAI,OAAO;AAChF,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,MACX,KAAK,OAAO;AAAA,MACZ,MAAM;AAAA,MACN;AAAA,MACA,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO,QAAQ,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC;AAAA,IACpD;AAAA,EACF,CAAC;AACD,QAAM,oBAAoB;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,kBAAkB,IAAI,IAAI,kBAAkB,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,CAAC;AACjF,SAAO,KAAK,CAAC,GAAG,MAAM;AACpB,UAAM,SAAS,gBAAgB,IAAI,EAAE,EAAE;AACvC,UAAM,SAAS,gBAAgB,IAAI,EAAE,EAAE;AACvC,QAAI,WAAW,UAAa,WAAW,QAAW;AAChD,UAAI,WAAW,OAAW,QAAO;AACjC,UAAI,WAAW,OAAW,QAAO;AACjC,UAAI,WAAW,OAAQ,QAAO,SAAS;AAAA,IACzC;AACA,QAAI,EAAE,WAAW,EAAE,OAAQ,QAAO,EAAE,SAAS,EAAE;AAC/C,WAAO,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EACpC,CAAC;AACD,QAAM,oBAAoB,kBAAkB;AAC5C,SAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,UAAM,OAAO,gBAAgB,IAAI,MAAM,EAAE;AACzC,UAAM,iBAAiB,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS;AACzE,UAAM,cACH,SAAS,SAAY,OAAO,oBAAoB,SAAS,MAC1D,KAAK,IAAI,KAAK,IAAI,gBAAgB,CAAC,GAAG,MAAO;AAC/C,UAAM,SAAS;AAAA,EACjB,CAAC;AAED,MAAI,iBAAiB;AACrB,MAAI,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,QAAQ;AAClD,UAAM,YAAY,KAAK,WACnB,EAAE,KAAK,CAAC,EAAE,UAAU,KAAK,SAAS,GAAG,EAAE,UAAU,KAAK,CAAC,EAAE,IACzD,EAAE,UAAU,KAAK;AACrB,UAAM,cAAc,MAAM,GAAG,KAAK,MAAM;AAAA,MACtC,MAAM,EAAE,KAAK,KAAK,MAAM;AAAA,MACxB,GAAG;AAAA,IACL,CAAQ;AACR,UAAM,UAAU,YAAY,IAAI,CAAC,SAAe,KAAK,EAAE;AACvD,QAAI,QAAQ,QAAQ;AAClB,uBAAiB,MAAM,+BAA+B,IAAI;AAAA,QACxD;AAAA,QACA,UAAU,KAAK,YAAY;AAAA,QAC3B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,iBAAiB,iBAAiB,uBAAuB,QAAQ,cAAc,IAAI;AACzF,QAAM,cAAc,qBAAqB,cAAc;AAGvD,QAAM,kBAAkB,KAAK,WAAW,KAAK,SAAS,KAAK;AAC3D,QAAM,aAAa,kBACf,MAAM,sBAAsB,IAAI;AAAA,IAC9B,QAAQ;AAAA,IACR,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,IAC9B;AAAA,EACF,CAAC,IACD;AAEJ,QAAM,iBAAiB,uBAAuB,aAAa,UAAU;AAErE,QAAM,UAAU;AAAA,IACd,QAAQ,eAAe,IAAI,CAAC,WAAW;AAAA,MACrC,IAAI,MAAM;AAAA,MACV,MAAM,MAAM;AAAA,MACZ,aAAa,MAAM;AAAA,MACnB,OAAQ,MAAM,MAA4B,IAAI,CAAC,UAAU;AAAA,QACvD,MAAM,KAAK;AAAA,QACX,OAAO,KAAK;AAAA,QACZ,cAAc,KAAK;AAAA,QACnB,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK,UAAU,IAAI,CAAC,WAAW;AAAA,UACvC,MAAM,MAAM;AAAA,UACZ,OAAO,MAAM;AAAA,UACb,cAAc,MAAM;AAAA,UACpB,SAAS,MAAM;AAAA,UACf,QAAQ,MAAM;AAAA,QAChB,EAAE;AAAA,MACJ,EAAE;AAAA,IACJ,EAAE;AAAA,EACJ;AAEA,MAAI;AACF,QAAI,OAAO;AACT,YAAM,OAAO;AAAA,QACX,aAAa,KAAK,GAAG;AAAA,QACrB,KAAK,WAAW,eAAe,KAAK,QAAQ,KAAK;AAAA,QACjD,gBAAgB,KAAK,YAAY,MAAM;AAAA,QACvC,cAAc,MAAM;AAAA,QACpB,oBAAoB,KAAK,GAAG;AAAA,QAC5B,qBAAqB,KAAK,GAAG,IAAI,KAAK,YAAY,MAAM,IAAI,KAAK,SAAS,MAAM,IAAI,MAAM;AAAA,QAC1F,GAAI,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC,SAAiB,oBAAoB,IAAI,EAAE,IAAI,CAAC;AAAA,MAClG,EAAE,OAAO,OAAO;AAChB,YAAM,MAAM,IAAI,UAAU,SAAS,EAAE,KAAK,CAAC;AAAA,IAC7C;AAAA,EACF,QAAQ;AAAA,EAAC;AAET,SAAO,aAAa,KAAK,OAAO;AAClC;AAEA,SAAS,qBAAqB,QAAmD;AAC/E,QAAM,aAAa,CAAqE,UACtF,MAAM,IAAI,CAAC,UAAU;AAAA,IACnB,GAAG;AAAA,IACH,cAAc,KAAK;AAAA,IACnB,UAAU,KAAK,WAAW,WAAW,KAAK,QAAQ,IAAI;AAAA,EACxD,EAAE;AAEJ,SAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC5B,GAAG;AAAA,IACH,aAAa,MAAM;AAAA,IACnB,OAAO,WAAW,MAAM,KAAK;AAAA,EAC/B,EAAE;AACJ;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,gCAAgC,QAAQ,uBAAuB;AAAA,QAC3F,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.4.5-develop-539cff4960",
3
+ "version": "0.4.5-develop-7f44fcf045",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -207,7 +207,7 @@
207
207
  }
208
208
  },
209
209
  "dependencies": {
210
- "@open-mercato/shared": "0.4.5-develop-539cff4960",
210
+ "@open-mercato/shared": "0.4.5-develop-7f44fcf045",
211
211
  "@types/semver": "^7.5.8",
212
212
  "@xyflow/react": "^12.6.0",
213
213
  "ai": "^6.0.0",
@@ -156,13 +156,19 @@ export async function GET(req: Request) {
156
156
  href: `/backend/entities/user/${encodeURIComponent(e.entityId)}/records`
157
157
  }))
158
158
  if (items.length) {
159
- const dd = roots.find((it: Entry) => it.groupKey === 'entities.nav.group' && it.titleKey === 'entities.nav.userEntities')
160
- if (dd) {
161
- const existing = dd.children || []
159
+ const userEntitiesLegacyGroupKeys = new Set(['settings.sections.dataDesigner', 'entities.nav.group'])
160
+ const userEntitiesAnchor = entries.find((entry: Entry) => entry.href === '/backend/entities/user')
161
+ ?? entries.find((entry: Entry) =>
162
+ entry.titleKey === 'entities.nav.userEntities' &&
163
+ typeof entry.groupKey === 'string' &&
164
+ userEntitiesLegacyGroupKeys.has(entry.groupKey),
165
+ )
166
+ if (userEntitiesAnchor) {
167
+ const existing = userEntitiesAnchor.children || []
162
168
  const dynamic = items.map((it) => ({
163
- groupId: dd.groupId,
164
- groupName: dd.groupName,
165
- groupKey: dd.groupKey,
169
+ groupId: userEntitiesAnchor.groupId,
170
+ groupName: userEntitiesAnchor.groupName,
171
+ groupKey: userEntitiesAnchor.groupKey,
166
172
  title: it.label,
167
173
  href: it.href,
168
174
  enabled: true,
@@ -172,7 +178,7 @@ export async function GET(req: Request) {
172
178
  const byHref = new Map<string, Entry>()
173
179
  for (const c of existing) if (!byHref.has(c.href)) byHref.set(c.href, c)
174
180
  for (const c of dynamic) if (!byHref.has(c.href)) byHref.set(c.href, c)
175
- dd.children = Array.from(byHref.values())
181
+ userEntitiesAnchor.children = Array.from(byHref.values())
176
182
  }
177
183
  }
178
184
  } catch (e) {