@open-mercato/core 0.6.6-develop.5503.1.6cdc4dda5f → 0.6.6-develop.5509.1.006f4d4f24

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.
Files changed (43) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/organization/index.js +2 -0
  3. package/dist/generated/entities/organization/index.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +1 -0
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/auth/api/admin/nav.js +9 -0
  7. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  8. package/dist/modules/auth/lib/backendChrome.js +35 -2
  9. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  10. package/dist/modules/directory/api/organization-branding/route.js +214 -0
  11. package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
  12. package/dist/modules/directory/api/organizations/route.js +7 -0
  13. package/dist/modules/directory/api/organizations/route.js.map +3 -3
  14. package/dist/modules/directory/backend/directory/branding/page.js +214 -0
  15. package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
  16. package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
  17. package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
  18. package/dist/modules/directory/commands/organizations.js +8 -1
  19. package/dist/modules/directory/commands/organizations.js.map +2 -2
  20. package/dist/modules/directory/data/entities.js +3 -0
  21. package/dist/modules/directory/data/entities.js.map +2 -2
  22. package/dist/modules/directory/data/validators.js +9 -0
  23. package/dist/modules/directory/data/validators.js.map +2 -2
  24. package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
  25. package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
  26. package/generated/entities/organization/index.ts +1 -0
  27. package/generated/entity-fields-registry.ts +1 -0
  28. package/package.json +7 -7
  29. package/src/modules/auth/api/admin/nav.ts +9 -0
  30. package/src/modules/auth/lib/backendChrome.tsx +37 -1
  31. package/src/modules/directory/api/organization-branding/route.ts +238 -0
  32. package/src/modules/directory/api/organizations/route.ts +7 -0
  33. package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
  34. package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
  35. package/src/modules/directory/commands/organizations.ts +9 -1
  36. package/src/modules/directory/data/entities.ts +3 -0
  37. package/src/modules/directory/data/validators.ts +12 -0
  38. package/src/modules/directory/i18n/de.json +21 -0
  39. package/src/modules/directory/i18n/en.json +21 -0
  40. package/src/modules/directory/i18n/es.json +21 -0
  41. package/src/modules/directory/i18n/pl.json +21 -0
  42. package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
  43. package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
@@ -43,6 +43,13 @@ const sectionGroupSchema = z.object({
43
43
  items: z.array(sectionItemSchema)
44
44
  });
45
45
  const adminNavResponseSchema = z.object({
46
+ brand: z.object({
47
+ name: z.string().optional(),
48
+ logo: z.object({
49
+ src: z.string(),
50
+ alt: z.string().optional()
51
+ }).nullable().optional()
52
+ }).nullable().optional(),
46
53
  groups: z.array(
47
54
  z.object({
48
55
  id: z.string().optional(),
@@ -123,6 +130,8 @@ async function GET(req) {
123
130
  `nav:entities:${cacheScopeTenantId || "null"}`,
124
131
  `nav:locale:${locale}`,
125
132
  `nav:sidebar:user:${auth.sub}`,
133
+ cacheScopeTenantId ? `nav:sidebar:tenant:${cacheScopeTenantId}` : void 0,
134
+ cacheScopeOrganizationId ? `nav:sidebar:organization:${cacheScopeOrganizationId}` : void 0,
126
135
  `nav:sidebar:scope:${auth.sub}:${cacheScopeTenantId || "null"}:${cacheScopeOrganizationId || "null"}:${locale}`,
127
136
  ...(Array.isArray(auth.roles) ? auth.roles : []).map((role) => `nav:sidebar:role:${role}`)
128
137
  ].filter(Boolean);
@@ -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 { resolveTranslations } 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 { getBackendRouteManifests } from '@open-mercato/shared/modules/registry'\nimport { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { groupBackendRoutesByModule, resolveBackendChromePayload } from '../../lib/backendChrome'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst sidebarNavItemSchema: z.ZodType<{\n id?: string\n href: string\n title: string\n defaultTitle?: string\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string().optional(),\n href: z.string(),\n title: z.string(),\n defaultTitle: z.string().optional(),\n enabled: z.boolean().optional(),\n hidden: z.boolean().optional(),\n pageContext: z.enum(['main', 'admin', 'settings', 'profile']).optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sidebarNavItemSchema).optional(),\n }),\n)\n\nconst sectionItemSchema: z.ZodType<{\n id: string\n label: string\n labelKey?: string\n href: string\n order?: number\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n href: z.string(),\n order: z.number().optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sectionItemSchema).optional(),\n }),\n)\n\nconst sectionGroupSchema = z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n order: z.number().optional(),\n items: z.array(sectionItemSchema),\n})\n\nconst adminNavResponseSchema = z.object({\n groups: z.array(\n z.object({\n id: z.string().optional(),\n name: z.string(),\n defaultName: z.string().optional(),\n items: z.array(sidebarNavItemSchema),\n }),\n ),\n settingsSections: z.array(sectionGroupSchema),\n settingsPathPrefixes: z.array(z.string()),\n profileSections: z.array(sectionGroupSchema),\n profilePathPrefixes: z.array(z.string()),\n grantedFeatures: z.array(z.string()),\n roles: z.array(z.string()),\n})\n\nconst adminNavErrorSchema = z.object({\n error: z.string(),\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 const container = await createRequestContainer()\n const cache = container.resolve('cache') as {\n get?: (key: string) => Promise<unknown>\n set?: (key: string, value: unknown, options?: { tags?: string[] }) => Promise<void>\n } | null\n let selectedOrganizationId: string | null | undefined\n let selectedTenantId: string | null | undefined\n try {\n const url = new URL(req.url)\n const orgParam = url.searchParams.get('orgId')\n const tenantParam = url.searchParams.get('tenantId')\n selectedOrganizationId = orgParam === null ? undefined : orgParam || null\n selectedTenantId = tenantParam === null ? undefined : tenantParam || null\n } catch {\n selectedOrganizationId = undefined\n selectedTenantId = undefined\n }\n\n let cacheScopeTenantId = auth.tenantId ?? null\n let cacheScopeOrganizationId = auth.orgId ?? null\n try {\n const { organizationId, scope } = await resolveFeatureCheckContext({\n container,\n auth,\n selectedId: selectedOrganizationId,\n tenantId: selectedTenantId,\n request: req,\n })\n cacheScopeOrganizationId = organizationId\n cacheScopeTenantId = scope.tenantId ?? auth.tenantId ?? null\n } catch {\n cacheScopeOrganizationId = auth.orgId ?? null\n cacheScopeTenantId = auth.tenantId ?? null\n selectedOrganizationId = auth.orgId ?? null\n selectedTenantId = auth.tenantId ?? null\n }\n\n const cacheVersion = 'v2'\n const cacheKey = `nav:sidebar:${cacheVersion}:${locale}:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}`\n try {\n if (cache?.get) {\n const cached = await cache.get(cacheKey)\n if (cached) return NextResponse.json(cached)\n }\n } catch {\n // ignore cache read failures\n }\n\n const payload = await resolveBackendChromePayload({\n auth,\n locale,\n modules: groupBackendRoutesByModule(getBackendRouteManifests()),\n translate: (key, fallback) => (key ? translate(key, fallback) : fallback),\n request: req,\n selectedOrganizationId,\n selectedTenantId,\n })\n\n try {\n if (cache?.set) {\n const tags = [\n `rbac:user:${auth.sub}`,\n cacheScopeTenantId ? `rbac:tenant:${cacheScopeTenantId}` : undefined,\n `nav:entities:${cacheScopeTenantId || 'null'}`,\n `nav:locale:${locale}`,\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}:${locale}`,\n ...((Array.isArray(auth.roles) ? auth.roles : []).map((role) => `nav:sidebar:role:${role}`)),\n ].filter(Boolean) as string[]\n await cache.set(cacheKey, payload, { tags })\n }\n } catch {\n // ignore cache write failures\n }\n\n return NextResponse.json(payload)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Admin sidebar navigation',\n methods: {\n GET: {\n summary: 'Resolve backend chrome bootstrap payload',\n description:\n 'Returns the backend chrome payload available to the authenticated administrator after applying scope, RBAC, role defaults, and personal sidebar preferences.',\n responses: [\n { status: 200, description: 'Backend chrome payload', 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 { resolveTranslations } 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 { getBackendRouteManifests } from '@open-mercato/shared/modules/registry'\nimport { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { groupBackendRoutesByModule, resolveBackendChromePayload } from '../../lib/backendChrome'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst sidebarNavItemSchema: z.ZodType<{\n id?: string\n href: string\n title: string\n defaultTitle?: string\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string().optional(),\n href: z.string(),\n title: z.string(),\n defaultTitle: z.string().optional(),\n enabled: z.boolean().optional(),\n hidden: z.boolean().optional(),\n pageContext: z.enum(['main', 'admin', 'settings', 'profile']).optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sidebarNavItemSchema).optional(),\n }),\n)\n\nconst sectionItemSchema: z.ZodType<{\n id: string\n label: string\n labelKey?: string\n href: string\n order?: number\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n href: z.string(),\n order: z.number().optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sectionItemSchema).optional(),\n }),\n)\n\nconst sectionGroupSchema = z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n order: z.number().optional(),\n items: z.array(sectionItemSchema),\n})\n\nconst adminNavResponseSchema = z.object({\n brand: z.object({\n name: z.string().optional(),\n logo: z.object({\n src: z.string(),\n alt: z.string().optional(),\n }).nullable().optional(),\n }).nullable().optional(),\n groups: z.array(\n z.object({\n id: z.string().optional(),\n name: z.string(),\n defaultName: z.string().optional(),\n items: z.array(sidebarNavItemSchema),\n }),\n ),\n settingsSections: z.array(sectionGroupSchema),\n settingsPathPrefixes: z.array(z.string()),\n profileSections: z.array(sectionGroupSchema),\n profilePathPrefixes: z.array(z.string()),\n grantedFeatures: z.array(z.string()),\n roles: z.array(z.string()),\n})\n\nconst adminNavErrorSchema = z.object({\n error: z.string(),\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 const container = await createRequestContainer()\n const cache = container.resolve('cache') as {\n get?: (key: string) => Promise<unknown>\n set?: (key: string, value: unknown, options?: { tags?: string[] }) => Promise<void>\n } | null\n let selectedOrganizationId: string | null | undefined\n let selectedTenantId: string | null | undefined\n try {\n const url = new URL(req.url)\n const orgParam = url.searchParams.get('orgId')\n const tenantParam = url.searchParams.get('tenantId')\n selectedOrganizationId = orgParam === null ? undefined : orgParam || null\n selectedTenantId = tenantParam === null ? undefined : tenantParam || null\n } catch {\n selectedOrganizationId = undefined\n selectedTenantId = undefined\n }\n\n let cacheScopeTenantId = auth.tenantId ?? null\n let cacheScopeOrganizationId = auth.orgId ?? null\n try {\n const { organizationId, scope } = await resolveFeatureCheckContext({\n container,\n auth,\n selectedId: selectedOrganizationId,\n tenantId: selectedTenantId,\n request: req,\n })\n cacheScopeOrganizationId = organizationId\n cacheScopeTenantId = scope.tenantId ?? auth.tenantId ?? null\n } catch {\n cacheScopeOrganizationId = auth.orgId ?? null\n cacheScopeTenantId = auth.tenantId ?? null\n selectedOrganizationId = auth.orgId ?? null\n selectedTenantId = auth.tenantId ?? null\n }\n\n const cacheVersion = 'v2'\n const cacheKey = `nav:sidebar:${cacheVersion}:${locale}:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}`\n try {\n if (cache?.get) {\n const cached = await cache.get(cacheKey)\n if (cached) return NextResponse.json(cached)\n }\n } catch {\n // ignore cache read failures\n }\n\n const payload = await resolveBackendChromePayload({\n auth,\n locale,\n modules: groupBackendRoutesByModule(getBackendRouteManifests()),\n translate: (key, fallback) => (key ? translate(key, fallback) : fallback),\n request: req,\n selectedOrganizationId,\n selectedTenantId,\n })\n\n try {\n if (cache?.set) {\n const tags = [\n `rbac:user:${auth.sub}`,\n cacheScopeTenantId ? `rbac:tenant:${cacheScopeTenantId}` : undefined,\n `nav:entities:${cacheScopeTenantId || 'null'}`,\n `nav:locale:${locale}`,\n `nav:sidebar:user:${auth.sub}`,\n cacheScopeTenantId ? `nav:sidebar:tenant:${cacheScopeTenantId}` : undefined,\n cacheScopeOrganizationId ? `nav:sidebar:organization:${cacheScopeOrganizationId}` : undefined,\n `nav:sidebar:scope:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}:${locale}`,\n ...((Array.isArray(auth.roles) ? auth.roles : []).map((role) => `nav:sidebar:role:${role}`)),\n ].filter(Boolean) as string[]\n await cache.set(cacheKey, payload, { tags })\n }\n } catch {\n // ignore cache write failures\n }\n\n return NextResponse.json(payload)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Admin sidebar navigation',\n methods: {\n GET: {\n summary: 'Resolve backend chrome bootstrap payload',\n description:\n 'Returns the backend chrome payload available to the authenticated administrator after applying scope, RBAC, role defaults, and personal sidebar preferences.',\n responses: [\n { status: 200, description: 'Backend chrome payload', schema: adminNavResponseSchema },\n { status: 401, description: 'Unauthorized', schema: adminNavErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -9,9 +9,15 @@ import { resolveRegisteredLucideIconNode } from "@open-mercato/ui/backend/icons/
9
9
  import { profilePathPrefixes, profileSections } from "./profile-sections.js";
10
10
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
11
11
  import { filterGrantsByEnabledModules } from "@open-mercato/shared/security/enabledModulesRegistry";
12
- import { resolveFeatureCheckContext } from "@open-mercato/core/modules/directory/utils/organizationScope";
12
+ import {
13
+ getSelectedOrganizationFromRequest,
14
+ resolveFeatureCheckContext
15
+ } from "@open-mercato/core/modules/directory/utils/organizationScope";
16
+ import { isAllOrganizationsSelection } from "@open-mercato/core/modules/directory/constants";
17
+ import { Organization } from "@open-mercato/core/modules/directory/data/entities";
13
18
  import { CustomEntity } from "@open-mercato/core/modules/entities/data/entities";
14
19
  import { Role } from "@open-mercato/core/modules/auth/data/entities";
20
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
15
21
  import {
16
22
  applySidebarPreference,
17
23
  loadFirstRoleSidebarPreference,
@@ -276,6 +282,32 @@ async function resolveBackendChromePayload({
276
282
  translate
277
283
  )
278
284
  );
285
+ const requestOrganizationId = request ? getSelectedOrganizationFromRequest(request) : null;
286
+ const fallbackOrganizationId = selectedOrganizationId ?? requestOrganizationId ?? auth.orgId ?? null;
287
+ const brandOrganizationId = scopedOrganizationId ?? (fallbackOrganizationId && !isAllOrganizationsSelection(fallbackOrganizationId) ? fallbackOrganizationId : null);
288
+ let brand = null;
289
+ if (brandOrganizationId && scopedTenantId) {
290
+ try {
291
+ const organization = await findOneWithDecryption(
292
+ em,
293
+ Organization,
294
+ { id: brandOrganizationId, tenant: scopedTenantId, deletedAt: null },
295
+ void 0,
296
+ { tenantId: scopedTenantId, organizationId: brandOrganizationId }
297
+ );
298
+ if (organization?.logoUrl) {
299
+ brand = {
300
+ name: organization.name,
301
+ logo: {
302
+ src: organization.logoUrl,
303
+ alt: `${organization.name} logo`
304
+ }
305
+ };
306
+ }
307
+ } catch {
308
+ brand = null;
309
+ }
310
+ }
279
311
  return {
280
312
  groups: appliedGroups.map(({ weight: _weight, ...group }) => group),
281
313
  settingsSections,
@@ -283,7 +315,8 @@ async function resolveBackendChromePayload({
283
315
  profileSections: await serializeSectionGroups(profileSections),
284
316
  profilePathPrefixes,
285
317
  grantedFeatures,
286
- roles: Array.isArray(auth.roles) ? auth.roles : []
318
+ roles: Array.isArray(auth.roles) ? auth.roles : [],
319
+ brand
287
320
  };
288
321
  }
289
322
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/lib/backendChrome.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport type { BackendRouteManifestEntry } from '@open-mercato/shared/modules/registry'\nimport type {\n BackendChromePayload,\n BackendChromeNavGroup,\n BackendChromeNavItem,\n BackendChromeSectionGroup,\n BackendChromeSectionItem,\n} from '@open-mercato/shared/modules/navigation/backendChrome'\nimport {\n buildAdminNav,\n buildSettingsSections,\n computeSettingsPathPrefixes,\n convertToSectionNavGroups,\n type AdminNavItem,\n} from '@open-mercato/ui/backend/utils/nav'\nimport { resolveRegisteredLucideIconNode } from '@open-mercato/ui/backend/icons/lucideRegistry'\nimport { profilePathPrefixes, profileSections } from './profile-sections'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { filterGrantsByEnabledModules } from '@open-mercato/shared/security/enabledModulesRegistry'\nimport { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CustomEntity } from '@open-mercato/core/modules/entities/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport {\n applySidebarPreference,\n loadFirstRoleSidebarPreference,\n loadSidebarPreference,\n} from '@open-mercato/core/modules/auth/services/sidebarPreferencesService'\nimport type { SidebarPreferencesSettings } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\n\ntype TranslationFn = (key: string | undefined, fallback: string) => string\n\ntype RouteModule = {\n id: string\n backendRoutes?: BackendRouteManifestEntry[]\n}\n\nexport function groupBackendRoutesByModule(routes: BackendRouteManifestEntry[]): RouteModule[] {\n return Array.from(\n routes.reduce((grouped, route) => {\n const list = grouped.get(route.moduleId) ?? []\n list.push(route)\n grouped.set(route.moduleId, list)\n return grouped\n }, new Map<string, BackendRouteManifestEntry[]>()),\n ).map(([id, backendRoutes]) => ({ id, backendRoutes }))\n}\n\ntype SerializableSectionItem = {\n id: string\n label: string\n labelKey?: string\n href: string\n icon?: React.ReactNode\n order?: number\n children?: SerializableSectionItem[]\n}\n\ntype SerializableSectionGroup = {\n id: string\n label: string\n labelKey?: string\n order?: number\n items: SerializableSectionItem[]\n}\n\ntype ResolvedNavItem = Omit<BackendChromeNavItem, 'defaultTitle' | 'children'> & {\n defaultTitle: string\n children?: ResolvedNavItem[]\n}\n\ntype ResolveBackendChromePayloadArgs = {\n auth: Exclude<AuthContext, null>\n locale: string\n modules: RouteModule[]\n translate: TranslationFn\n request?: Request\n selectedOrganizationId?: string | null\n selectedTenantId?: string | null\n}\n\nconst settingsSectionOrder: Record<string, number> = {\n system: 1,\n auth: 2,\n 'customer-portal': 3,\n 'data-designer': 4,\n 'module-configs': 5,\n directory: 6,\n 'feature-toggles': 7,\n}\n\ntype NavGroupWithWeight = Omit<BackendChromeNavGroup, 'id' | 'defaultName' | 'items'> & {\n id: string\n defaultName: string\n items: ResolvedNavItem[]\n weight: number\n}\n\nlet renderToStaticMarkupPromise: Promise<typeof import('react-dom/server')> | null = null\n\nasync function serializeIconMarkup(icon: React.ReactNode | undefined): Promise<string | undefined> {\n if (!icon) return undefined\n if (!renderToStaticMarkupPromise) {\n renderToStaticMarkupPromise = import('react-dom/server')\n }\n const { renderToStaticMarkup } = await renderToStaticMarkupPromise\n\n const normalizedIcon = typeof icon === 'string'\n ? resolveRegisteredLucideIconNode(icon, 'size-4')\n : icon\n\n if (!normalizedIcon) return undefined\n\n try {\n const markup = renderToStaticMarkup(<>{normalizedIcon}</>)\n return markup.trim().length > 0 ? markup : undefined\n } catch {\n // Some icon values may be client-only component references after dependency upgrades.\n // Avoid taking down the entire nav payload because one icon cannot be rendered server-side.\n return undefined\n }\n}\n\nasync function serializeNavItem(item: AdminNavItem): Promise<ResolvedNavItem> {\n return {\n id: item.href,\n href: item.href,\n title: item.title,\n defaultTitle: item.defaultTitle,\n enabled: item.enabled,\n hidden: item.hidden,\n pageContext: item.pageContext,\n iconName: typeof item.icon === 'string' ? item.icon : undefined,\n iconMarkup: await serializeIconMarkup(item.icon),\n children: item.children ? await Promise.all(item.children.map((child) => serializeNavItem(child))) : undefined,\n }\n}\n\nfunction normalizeGroupWeights(groups: NavGroupWithWeight[]): NavGroupWithWeight[] {\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 group.weight =\n (rank !== undefined ? rank : defaultGroupCount + index) * 1_000_000 +\n Math.min(Math.max(fallbackWeight, 0), 999_999)\n })\n return groups\n}\n\nasync function groupEntries(entries: AdminNavItem[]): Promise<NavGroupWithWeight[]> {\n const groupMap = new Map<string, NavGroupWithWeight>()\n for (const entry of entries) {\n const weight = entry.priority ?? entry.order ?? 10_000\n const serializedItem = await serializeNavItem(entry)\n const existing = groupMap.get(entry.groupId)\n if (existing) {\n existing.items.push(serializedItem)\n if (weight < existing.weight) existing.weight = weight\n continue\n }\n groupMap.set(entry.groupId, {\n id: entry.groupId,\n name: entry.group,\n defaultName: entry.groupDefaultName,\n items: [serializedItem],\n weight,\n })\n }\n return normalizeGroupWeights(Array.from(groupMap.values()))\n}\n\nfunction adoptSidebarDefaults(groups: NavGroupWithWeight[]): NavGroupWithWeight[] {\n const adoptItems = (items: ResolvedNavItem[]): ResolvedNavItem[] =>\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\nasync function serializeSectionItem(item: {\n id: string\n label: string\n labelKey?: string\n href: string\n icon?: React.ReactNode\n order?: number\n children?: SerializableSectionItem[]\n}): Promise<BackendChromeSectionItem> {\n return {\n id: item.id,\n label: item.label,\n labelKey: item.labelKey,\n href: item.href,\n order: item.order,\n iconName: typeof item.icon === 'string' ? item.icon : undefined,\n iconMarkup: await serializeIconMarkup(item.icon),\n children: item.children ? await Promise.all(item.children.map((child) => serializeSectionItem(child))) : undefined,\n }\n}\n\nasync function serializeSectionGroups(groups: SerializableSectionGroup[]): Promise<BackendChromeSectionGroup[]> {\n return Promise.all(groups.map(async (group) => ({\n id: group.id,\n label: group.label,\n labelKey: group.labelKey,\n order: group.order,\n items: await Promise.all(group.items.map((item) => serializeSectionItem(item))),\n })))\n}\n\nasync function loadScopedContainer(): Promise<AwilixContainer> {\n return createRequestContainer()\n}\n\nexport async function resolveBackendChromePayload({\n auth,\n locale,\n modules,\n translate,\n request,\n selectedOrganizationId,\n selectedTenantId,\n}: ResolveBackendChromePayloadArgs): Promise<BackendChromePayload> {\n const container = await loadScopedContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as {\n loadAcl: (userId: string, scope: { tenantId: string | null; organizationId: string | null }) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n }>\n userHasAllFeatures: (userId: string, required: string[], scope: { tenantId: string | null; organizationId: string | null }) => Promise<boolean>\n }\n\n let scopedOrganizationId: string | null = auth.orgId ?? null\n let scopedTenantId: string | null = auth.tenantId ?? null\n let allowNavigation = true\n\n try {\n const { organizationId, scope, allowedOrganizationIds } = await resolveFeatureCheckContext({\n container,\n auth,\n request,\n selectedId: selectedOrganizationId,\n tenantId: selectedTenantId,\n })\n scopedOrganizationId = organizationId\n scopedTenantId = scope.tenantId ?? auth.tenantId ?? null\n if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {\n allowNavigation = false\n }\n } catch {\n scopedOrganizationId = auth.orgId ?? null\n scopedTenantId = auth.tenantId ?? null\n }\n\n const acl = allowNavigation\n ? await rbac.loadAcl(auth.sub, {\n tenantId: scopedTenantId,\n organizationId: scopedOrganizationId,\n })\n : { isSuperAdmin: false, features: [] }\n\n const rawGrantedFeatures = acl.isSuperAdmin ? ['*'] : acl.features\n const grantedFeatures = filterGrantsByEnabledModules(rawGrantedFeatures)\n const featureChecker = async (features: string[]): Promise<string[]> => {\n if (!allowNavigation || !features.length) return []\n const context = {\n tenantId: scopedTenantId ?? auth.tenantId ?? null,\n organizationId: scopedOrganizationId ?? null,\n }\n const hasAll = await rbac.userHasAllFeatures(auth.sub, features, context)\n if (hasAll) return features\n\n const granted: string[] = []\n for (const feature of features) {\n const hasFeature = await rbac.userHasAllFeatures(auth.sub, [feature], context)\n if (hasFeature) granted.push(feature)\n }\n return granted\n }\n\n let userEntities: Array<{ entityId: string; label: string; href: string }> = []\n if (allowNavigation) {\n try {\n const where: FilterQuery<CustomEntity> = {\n isActive: true,\n showInSidebar: true,\n }\n where.$and = [\n { $or: [{ organizationId: scopedOrganizationId ?? undefined }, { organizationId: null }] },\n { $or: [{ tenantId: scopedTenantId ?? undefined }, { tenantId: null }] },\n ]\n const entities = await em.find(CustomEntity, where, { orderBy: { label: 'asc' } })\n userEntities = entities.map((entity) => ({\n entityId: entity.entityId,\n label: entity.label,\n href: `/backend/entities/user/${encodeURIComponent(entity.entityId)}/records`,\n }))\n } catch {\n userEntities = []\n }\n }\n\n const ctxAuth = {\n roles: auth.roles || [],\n sub: auth.sub,\n tenantId: scopedTenantId,\n orgId: scopedOrganizationId,\n }\n const entries = allowNavigation\n ? await buildAdminNav(\n modules,\n { auth: ctxAuth },\n userEntities,\n translate,\n { checkFeatures: featureChecker },\n )\n : []\n\n let rolePreference: SidebarPreferencesSettings | null = null\n let userPreference: SidebarPreferencesSettings | null = null\n\n if (Array.isArray(auth.roles) && auth.roles.length > 0) {\n const roleRecords = scopedTenantId\n ? await em.find(Role, {\n name: { $in: auth.roles },\n tenantId: scopedTenantId,\n })\n : []\n const roleIds = Array.isArray(roleRecords) ? roleRecords.map((role) => role.id) : []\n if (roleIds.length > 0) {\n rolePreference = await loadFirstRoleSidebarPreference(em, {\n roleIds,\n tenantId: scopedTenantId,\n locale,\n })\n }\n }\n\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n if (effectiveUserId) {\n userPreference = await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: scopedTenantId,\n organizationId: scopedOrganizationId,\n locale,\n })\n }\n\n const baseGroups = await groupEntries(entries)\n const groupsWithRole = rolePreference\n ? applySidebarPreference<NavGroupWithWeight>(baseGroups, rolePreference)\n : baseGroups\n const baseForUser = adoptSidebarDefaults(groupsWithRole)\n const appliedGroups = userPreference\n ? applySidebarPreference<NavGroupWithWeight>(baseForUser, userPreference)\n : baseForUser\n\n const settingsSections = await serializeSectionGroups(\n convertToSectionNavGroups(\n buildSettingsSections(entries, settingsSectionOrder),\n translate,\n ),\n )\n\n return {\n groups: appliedGroups.map(({ weight: _weight, ...group }) => group),\n settingsSections,\n settingsPathPrefixes: computeSettingsPathPrefixes(buildSettingsSections(entries, settingsSectionOrder)),\n profileSections: await serializeSectionGroups(profileSections),\n profilePathPrefixes,\n grantedFeatures,\n roles: Array.isArray(auth.roles) ? auth.roles : [],\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport type { BackendRouteManifestEntry } from '@open-mercato/shared/modules/registry'\nimport type {\n BackendChromePayload,\n BackendChromeNavGroup,\n BackendChromeNavItem,\n BackendChromeSectionGroup,\n BackendChromeSectionItem,\n} from '@open-mercato/shared/modules/navigation/backendChrome'\nimport {\n buildAdminNav,\n buildSettingsSections,\n computeSettingsPathPrefixes,\n convertToSectionNavGroups,\n type AdminNavItem,\n} from '@open-mercato/ui/backend/utils/nav'\nimport { resolveRegisteredLucideIconNode } from '@open-mercato/ui/backend/icons/lucideRegistry'\nimport { profilePathPrefixes, profileSections } from './profile-sections'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { filterGrantsByEnabledModules } from '@open-mercato/shared/security/enabledModulesRegistry'\nimport {\n getSelectedOrganizationFromRequest,\n resolveFeatureCheckContext,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { CustomEntity } from '@open-mercato/core/modules/entities/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n applySidebarPreference,\n loadFirstRoleSidebarPreference,\n loadSidebarPreference,\n} from '@open-mercato/core/modules/auth/services/sidebarPreferencesService'\nimport type { SidebarPreferencesSettings } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\n\ntype TranslationFn = (key: string | undefined, fallback: string) => string\n\ntype RouteModule = {\n id: string\n backendRoutes?: BackendRouteManifestEntry[]\n}\n\nexport function groupBackendRoutesByModule(routes: BackendRouteManifestEntry[]): RouteModule[] {\n return Array.from(\n routes.reduce((grouped, route) => {\n const list = grouped.get(route.moduleId) ?? []\n list.push(route)\n grouped.set(route.moduleId, list)\n return grouped\n }, new Map<string, BackendRouteManifestEntry[]>()),\n ).map(([id, backendRoutes]) => ({ id, backendRoutes }))\n}\n\ntype SerializableSectionItem = {\n id: string\n label: string\n labelKey?: string\n href: string\n icon?: React.ReactNode\n order?: number\n children?: SerializableSectionItem[]\n}\n\ntype SerializableSectionGroup = {\n id: string\n label: string\n labelKey?: string\n order?: number\n items: SerializableSectionItem[]\n}\n\ntype ResolvedNavItem = Omit<BackendChromeNavItem, 'defaultTitle' | 'children'> & {\n defaultTitle: string\n children?: ResolvedNavItem[]\n}\n\ntype ResolveBackendChromePayloadArgs = {\n auth: Exclude<AuthContext, null>\n locale: string\n modules: RouteModule[]\n translate: TranslationFn\n request?: Request\n selectedOrganizationId?: string | null\n selectedTenantId?: string | null\n}\n\nconst settingsSectionOrder: Record<string, number> = {\n system: 1,\n auth: 2,\n 'customer-portal': 3,\n 'data-designer': 4,\n 'module-configs': 5,\n directory: 6,\n 'feature-toggles': 7,\n}\n\ntype NavGroupWithWeight = Omit<BackendChromeNavGroup, 'id' | 'defaultName' | 'items'> & {\n id: string\n defaultName: string\n items: ResolvedNavItem[]\n weight: number\n}\n\nlet renderToStaticMarkupPromise: Promise<typeof import('react-dom/server')> | null = null\n\nasync function serializeIconMarkup(icon: React.ReactNode | undefined): Promise<string | undefined> {\n if (!icon) return undefined\n if (!renderToStaticMarkupPromise) {\n renderToStaticMarkupPromise = import('react-dom/server')\n }\n const { renderToStaticMarkup } = await renderToStaticMarkupPromise\n\n const normalizedIcon = typeof icon === 'string'\n ? resolveRegisteredLucideIconNode(icon, 'size-4')\n : icon\n\n if (!normalizedIcon) return undefined\n\n try {\n const markup = renderToStaticMarkup(<>{normalizedIcon}</>)\n return markup.trim().length > 0 ? markup : undefined\n } catch {\n // Some icon values may be client-only component references after dependency upgrades.\n // Avoid taking down the entire nav payload because one icon cannot be rendered server-side.\n return undefined\n }\n}\n\nasync function serializeNavItem(item: AdminNavItem): Promise<ResolvedNavItem> {\n return {\n id: item.href,\n href: item.href,\n title: item.title,\n defaultTitle: item.defaultTitle,\n enabled: item.enabled,\n hidden: item.hidden,\n pageContext: item.pageContext,\n iconName: typeof item.icon === 'string' ? item.icon : undefined,\n iconMarkup: await serializeIconMarkup(item.icon),\n children: item.children ? await Promise.all(item.children.map((child) => serializeNavItem(child))) : undefined,\n }\n}\n\nfunction normalizeGroupWeights(groups: NavGroupWithWeight[]): NavGroupWithWeight[] {\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 group.weight =\n (rank !== undefined ? rank : defaultGroupCount + index) * 1_000_000 +\n Math.min(Math.max(fallbackWeight, 0), 999_999)\n })\n return groups\n}\n\nasync function groupEntries(entries: AdminNavItem[]): Promise<NavGroupWithWeight[]> {\n const groupMap = new Map<string, NavGroupWithWeight>()\n for (const entry of entries) {\n const weight = entry.priority ?? entry.order ?? 10_000\n const serializedItem = await serializeNavItem(entry)\n const existing = groupMap.get(entry.groupId)\n if (existing) {\n existing.items.push(serializedItem)\n if (weight < existing.weight) existing.weight = weight\n continue\n }\n groupMap.set(entry.groupId, {\n id: entry.groupId,\n name: entry.group,\n defaultName: entry.groupDefaultName,\n items: [serializedItem],\n weight,\n })\n }\n return normalizeGroupWeights(Array.from(groupMap.values()))\n}\n\nfunction adoptSidebarDefaults(groups: NavGroupWithWeight[]): NavGroupWithWeight[] {\n const adoptItems = (items: ResolvedNavItem[]): ResolvedNavItem[] =>\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\nasync function serializeSectionItem(item: {\n id: string\n label: string\n labelKey?: string\n href: string\n icon?: React.ReactNode\n order?: number\n children?: SerializableSectionItem[]\n}): Promise<BackendChromeSectionItem> {\n return {\n id: item.id,\n label: item.label,\n labelKey: item.labelKey,\n href: item.href,\n order: item.order,\n iconName: typeof item.icon === 'string' ? item.icon : undefined,\n iconMarkup: await serializeIconMarkup(item.icon),\n children: item.children ? await Promise.all(item.children.map((child) => serializeSectionItem(child))) : undefined,\n }\n}\n\nasync function serializeSectionGroups(groups: SerializableSectionGroup[]): Promise<BackendChromeSectionGroup[]> {\n return Promise.all(groups.map(async (group) => ({\n id: group.id,\n label: group.label,\n labelKey: group.labelKey,\n order: group.order,\n items: await Promise.all(group.items.map((item) => serializeSectionItem(item))),\n })))\n}\n\nasync function loadScopedContainer(): Promise<AwilixContainer> {\n return createRequestContainer()\n}\n\nexport async function resolveBackendChromePayload({\n auth,\n locale,\n modules,\n translate,\n request,\n selectedOrganizationId,\n selectedTenantId,\n}: ResolveBackendChromePayloadArgs): Promise<BackendChromePayload> {\n const container = await loadScopedContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as {\n loadAcl: (userId: string, scope: { tenantId: string | null; organizationId: string | null }) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n }>\n userHasAllFeatures: (userId: string, required: string[], scope: { tenantId: string | null; organizationId: string | null }) => Promise<boolean>\n }\n\n let scopedOrganizationId: string | null = auth.orgId ?? null\n let scopedTenantId: string | null = auth.tenantId ?? null\n let allowNavigation = true\n\n try {\n const { organizationId, scope, allowedOrganizationIds } = await resolveFeatureCheckContext({\n container,\n auth,\n request,\n selectedId: selectedOrganizationId,\n tenantId: selectedTenantId,\n })\n scopedOrganizationId = organizationId\n scopedTenantId = scope.tenantId ?? auth.tenantId ?? null\n if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {\n allowNavigation = false\n }\n } catch {\n scopedOrganizationId = auth.orgId ?? null\n scopedTenantId = auth.tenantId ?? null\n }\n\n const acl = allowNavigation\n ? await rbac.loadAcl(auth.sub, {\n tenantId: scopedTenantId,\n organizationId: scopedOrganizationId,\n })\n : { isSuperAdmin: false, features: [] }\n\n const rawGrantedFeatures = acl.isSuperAdmin ? ['*'] : acl.features\n const grantedFeatures = filterGrantsByEnabledModules(rawGrantedFeatures)\n const featureChecker = async (features: string[]): Promise<string[]> => {\n if (!allowNavigation || !features.length) return []\n const context = {\n tenantId: scopedTenantId ?? auth.tenantId ?? null,\n organizationId: scopedOrganizationId ?? null,\n }\n const hasAll = await rbac.userHasAllFeatures(auth.sub, features, context)\n if (hasAll) return features\n\n const granted: string[] = []\n for (const feature of features) {\n const hasFeature = await rbac.userHasAllFeatures(auth.sub, [feature], context)\n if (hasFeature) granted.push(feature)\n }\n return granted\n }\n\n let userEntities: Array<{ entityId: string; label: string; href: string }> = []\n if (allowNavigation) {\n try {\n const where: FilterQuery<CustomEntity> = {\n isActive: true,\n showInSidebar: true,\n }\n where.$and = [\n { $or: [{ organizationId: scopedOrganizationId ?? undefined }, { organizationId: null }] },\n { $or: [{ tenantId: scopedTenantId ?? undefined }, { tenantId: null }] },\n ]\n const entities = await em.find(CustomEntity, where, { orderBy: { label: 'asc' } })\n userEntities = entities.map((entity) => ({\n entityId: entity.entityId,\n label: entity.label,\n href: `/backend/entities/user/${encodeURIComponent(entity.entityId)}/records`,\n }))\n } catch {\n userEntities = []\n }\n }\n\n const ctxAuth = {\n roles: auth.roles || [],\n sub: auth.sub,\n tenantId: scopedTenantId,\n orgId: scopedOrganizationId,\n }\n const entries = allowNavigation\n ? await buildAdminNav(\n modules,\n { auth: ctxAuth },\n userEntities,\n translate,\n { checkFeatures: featureChecker },\n )\n : []\n\n let rolePreference: SidebarPreferencesSettings | null = null\n let userPreference: SidebarPreferencesSettings | null = null\n\n if (Array.isArray(auth.roles) && auth.roles.length > 0) {\n const roleRecords = scopedTenantId\n ? await em.find(Role, {\n name: { $in: auth.roles },\n tenantId: scopedTenantId,\n })\n : []\n const roleIds = Array.isArray(roleRecords) ? roleRecords.map((role) => role.id) : []\n if (roleIds.length > 0) {\n rolePreference = await loadFirstRoleSidebarPreference(em, {\n roleIds,\n tenantId: scopedTenantId,\n locale,\n })\n }\n }\n\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n if (effectiveUserId) {\n userPreference = await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: scopedTenantId,\n organizationId: scopedOrganizationId,\n locale,\n })\n }\n\n const baseGroups = await groupEntries(entries)\n const groupsWithRole = rolePreference\n ? applySidebarPreference<NavGroupWithWeight>(baseGroups, rolePreference)\n : baseGroups\n const baseForUser = adoptSidebarDefaults(groupsWithRole)\n const appliedGroups = userPreference\n ? applySidebarPreference<NavGroupWithWeight>(baseForUser, userPreference)\n : baseForUser\n\n const settingsSections = await serializeSectionGroups(\n convertToSectionNavGroups(\n buildSettingsSections(entries, settingsSectionOrder),\n translate,\n ),\n )\n\n const requestOrganizationId = request ? getSelectedOrganizationFromRequest(request) : null\n const fallbackOrganizationId = selectedOrganizationId ?? requestOrganizationId ?? auth.orgId ?? null\n const brandOrganizationId = scopedOrganizationId\n ?? (fallbackOrganizationId && !isAllOrganizationsSelection(fallbackOrganizationId) ? fallbackOrganizationId : null)\n\n let brand: BackendChromePayload['brand'] = null\n if (brandOrganizationId && scopedTenantId) {\n try {\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: brandOrganizationId, tenant: scopedTenantId, deletedAt: null },\n undefined,\n { tenantId: scopedTenantId, organizationId: brandOrganizationId },\n )\n if (organization?.logoUrl) {\n brand = {\n name: organization.name,\n logo: {\n src: organization.logoUrl,\n alt: `${organization.name} logo`,\n },\n }\n }\n } catch {\n brand = null\n }\n }\n\n return {\n groups: appliedGroups.map(({ weight: _weight, ...group }) => group),\n settingsSections,\n settingsPathPrefixes: computeSettingsPathPrefixes(buildSettingsSections(entries, settingsSectionOrder)),\n profileSections: await serializeSectionGroups(profileSections),\n profilePathPrefixes,\n grantedFeatures,\n roles: Array.isArray(auth.roles) ? auth.roles : [],\n brand,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,214 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { runWithCacheTenant } from "@open-mercato/cache";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
6
+ import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
9
+ import { Organization } from "@open-mercato/core/modules/directory/data/entities";
10
+ import { organizationUpdateSchema } from "@open-mercato/core/modules/directory/data/validators";
11
+ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
12
+ import "@open-mercato/core/modules/directory/commands/organizations";
13
+ const metadata = {
14
+ GET: { requireAuth: true, requireFeatures: ["directory.organizations.view"] },
15
+ PUT: { requireAuth: true, requireFeatures: ["directory.organizations.manage"] }
16
+ };
17
+ const brandingResponseSchema = z.object({
18
+ organizationId: z.string().uuid(),
19
+ organizationName: z.string(),
20
+ tenantId: z.string().uuid(),
21
+ logoUrl: z.string().nullable()
22
+ });
23
+ const brandingUpdateSchema = z.object({
24
+ logoUrl: organizationUpdateSchema.shape.logoUrl
25
+ });
26
+ const errorSchema = z.object({
27
+ error: z.string()
28
+ });
29
+ function buildCommandContext(container, auth, req, organizationId, tenantId) {
30
+ return {
31
+ container,
32
+ auth,
33
+ organizationScope: {
34
+ selectedId: organizationId,
35
+ filterIds: [organizationId],
36
+ allowedIds: null,
37
+ tenantId
38
+ },
39
+ selectedOrganizationId: organizationId,
40
+ organizationIds: [organizationId],
41
+ request: req
42
+ };
43
+ }
44
+ async function resolveCurrentOrganization(req) {
45
+ const { translate } = await resolveTranslations();
46
+ const auth = await getAuthFromRequest(req);
47
+ if (!auth?.sub) {
48
+ return {
49
+ response: NextResponse.json({ error: translate("api.errors.unauthorized", "Unauthorized") }, { status: 401 })
50
+ };
51
+ }
52
+ const container = await createRequestContainer();
53
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
54
+ const organizationId = scope.selectedId ?? auth.orgId ?? null;
55
+ const tenantId = scope.tenantId ?? auth.tenantId ?? null;
56
+ if (!organizationId || !tenantId) {
57
+ return {
58
+ response: NextResponse.json(
59
+ {
60
+ error: translate(
61
+ "directory.branding.errors.organizationRequired",
62
+ "Select a single organization before changing sidebar branding."
63
+ )
64
+ },
65
+ { status: 400 }
66
+ )
67
+ };
68
+ }
69
+ const em = container.resolve("em");
70
+ const organization = await findOneWithDecryption(
71
+ em,
72
+ Organization,
73
+ { id: organizationId, tenant: tenantId, deletedAt: null },
74
+ { populate: ["tenant"] },
75
+ { tenantId, organizationId }
76
+ );
77
+ if (!organization) {
78
+ return {
79
+ response: NextResponse.json(
80
+ { error: translate("directory.branding.errors.notFound", "Organization not found") },
81
+ { status: 404 }
82
+ )
83
+ };
84
+ }
85
+ return { auth, container, organization, organizationId, tenantId, translate };
86
+ }
87
+ function toResponsePayload(organization, tenantId) {
88
+ return {
89
+ organizationId: String(organization.id),
90
+ organizationName: organization.name,
91
+ tenantId,
92
+ logoUrl: organization.logoUrl ?? null
93
+ };
94
+ }
95
+ async function invalidateSidebarBrandingCache(container, organizationId, tenantId) {
96
+ try {
97
+ const cache = container.resolve("cache");
98
+ await runWithCacheTenant(
99
+ tenantId,
100
+ () => cache?.deleteByTags?.([
101
+ `nav:sidebar:organization:${organizationId}`,
102
+ `nav:sidebar:tenant:${tenantId}`
103
+ ])
104
+ );
105
+ } catch {
106
+ }
107
+ }
108
+ async function GET(req) {
109
+ const resolved = await resolveCurrentOrganization(req);
110
+ if ("response" in resolved) return resolved.response;
111
+ return NextResponse.json(toResponsePayload(resolved.organization, resolved.tenantId));
112
+ }
113
+ async function PUT(req) {
114
+ const resolved = await resolveCurrentOrganization(req);
115
+ if ("response" in resolved) return resolved.response;
116
+ let body;
117
+ try {
118
+ body = await req.json();
119
+ } catch {
120
+ return NextResponse.json(
121
+ { error: resolved.translate("directory.branding.errors.invalidLogoUrl", "Enter a valid image URL.") },
122
+ { status: 422 }
123
+ );
124
+ }
125
+ if (!body || typeof body !== "object" || !Object.prototype.hasOwnProperty.call(body, "logoUrl")) {
126
+ return NextResponse.json(
127
+ { error: resolved.translate("directory.branding.errors.invalidLogoUrl", "Enter a valid image URL.") },
128
+ { status: 422 }
129
+ );
130
+ }
131
+ const parsed = brandingUpdateSchema.safeParse(body);
132
+ if (!parsed.success) {
133
+ return NextResponse.json(
134
+ {
135
+ error: resolved.translate("directory.branding.errors.invalidLogoUrl", "Enter a valid image URL."),
136
+ issues: parsed.error.issues
137
+ },
138
+ { status: 422 }
139
+ );
140
+ }
141
+ try {
142
+ const commandBus = resolved.container.resolve("commandBus");
143
+ const ctx = buildCommandContext(
144
+ resolved.container,
145
+ resolved.auth,
146
+ req,
147
+ resolved.organizationId,
148
+ resolved.tenantId
149
+ );
150
+ const { result } = await commandBus.execute(
151
+ "directory.organizations.update",
152
+ {
153
+ input: {
154
+ id: resolved.organizationId,
155
+ tenantId: resolved.tenantId,
156
+ logoUrl: parsed.data.logoUrl ?? null
157
+ },
158
+ ctx
159
+ }
160
+ );
161
+ await invalidateSidebarBrandingCache(resolved.container, resolved.organizationId, resolved.tenantId);
162
+ return NextResponse.json(toResponsePayload(result, resolved.tenantId));
163
+ } catch (err) {
164
+ if (isCrudHttpError(err)) {
165
+ return NextResponse.json(err.body, { status: err.status });
166
+ }
167
+ console.error("directory.organization-branding.update failed", err);
168
+ return NextResponse.json(
169
+ { error: resolved.translate("directory.branding.errors.save", "Failed to update organization branding.") },
170
+ { status: 400 }
171
+ );
172
+ }
173
+ }
174
+ const openApi = {
175
+ tag: "Directory",
176
+ summary: "Current organization branding",
177
+ methods: {
178
+ GET: {
179
+ summary: "Read sidebar branding for the selected organization",
180
+ description: "Returns the logo URL used by the backend sidebar for the currently selected organization.",
181
+ responses: [
182
+ { status: 200, description: "Organization branding", schema: brandingResponseSchema }
183
+ ],
184
+ errors: [
185
+ { status: 400, description: "A concrete organization scope is required", schema: errorSchema },
186
+ { status: 401, description: "Unauthorized", schema: errorSchema },
187
+ { status: 404, description: "Organization not found", schema: errorSchema }
188
+ ]
189
+ },
190
+ PUT: {
191
+ summary: "Update sidebar branding for the selected organization",
192
+ description: "Stores an external image URL or an internal attachment image URL as the selected organization logo.",
193
+ requestBody: {
194
+ contentType: "application/json",
195
+ schema: brandingUpdateSchema
196
+ },
197
+ responses: [
198
+ { status: 200, description: "Updated organization branding", schema: brandingResponseSchema }
199
+ ],
200
+ errors: [
201
+ { status: 400, description: "Save failed", schema: errorSchema },
202
+ { status: 401, description: "Unauthorized", schema: errorSchema },
203
+ { status: 422, description: "Invalid logo URL", schema: errorSchema }
204
+ ]
205
+ }
206
+ }
207
+ };
208
+ export {
209
+ GET,
210
+ PUT,
211
+ metadata,
212
+ openApi
213
+ };
214
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/directory/api/organization-branding/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport '@open-mercato/core/modules/directory/commands/organizations'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['directory.organizations.view'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n}\n\nconst brandingResponseSchema = z.object({\n organizationId: z.string().uuid(),\n organizationName: z.string(),\n tenantId: z.string().uuid(),\n logoUrl: z.string().nullable(),\n})\n\nconst brandingUpdateSchema = z.object({\n logoUrl: organizationUpdateSchema.shape.logoUrl,\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\ntype RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>\n\nfunction buildCommandContext(\n container: RequestContainer,\n auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>,\n req: Request,\n organizationId: string,\n tenantId: string,\n): CommandRuntimeContext {\n return {\n container,\n auth,\n organizationScope: {\n selectedId: organizationId,\n filterIds: [organizationId],\n allowedIds: null,\n tenantId,\n },\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n request: req,\n }\n}\n\nasync function resolveCurrentOrganization(req: Request) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return {\n response: NextResponse.json({ error: translate('api.errors.unauthorized', 'Unauthorized') }, { status: 401 }),\n }\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope.selectedId ?? auth.orgId ?? null\n const tenantId = scope.tenantId ?? auth.tenantId ?? null\n if (!organizationId || !tenantId) {\n return {\n response: NextResponse.json(\n {\n error: translate(\n 'directory.branding.errors.organizationRequired',\n 'Select a single organization before changing sidebar branding.',\n ),\n },\n { status: 400 },\n ),\n }\n }\n\n const em = container.resolve('em') as EntityManager\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId, tenant: tenantId, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId, organizationId },\n )\n if (!organization) {\n return {\n response: NextResponse.json(\n { error: translate('directory.branding.errors.notFound', 'Organization not found') },\n { status: 404 },\n ),\n }\n }\n\n return { auth, container, organization, organizationId, tenantId, translate }\n}\n\nfunction toResponsePayload(organization: Organization, tenantId: string) {\n return {\n organizationId: String(organization.id),\n organizationName: organization.name,\n tenantId,\n logoUrl: organization.logoUrl ?? null,\n }\n}\n\nasync function invalidateSidebarBrandingCache(container: RequestContainer, organizationId: string, tenantId: string) {\n try {\n const cache = container.resolve('cache') as {\n deleteByTags?: (tags: string[]) => Promise<unknown>\n } | null\n await runWithCacheTenant(tenantId, () =>\n cache?.deleteByTags?.([\n `nav:sidebar:organization:${organizationId}`,\n `nav:sidebar:tenant:${tenantId}`,\n ]),\n )\n } catch {\n // Cache invalidation is best-effort; the persisted branding is the source of truth.\n }\n}\n\nexport async function GET(req: Request) {\n const resolved = await resolveCurrentOrganization(req)\n if ('response' in resolved) return resolved.response\n\n return NextResponse.json(toResponsePayload(resolved.organization, resolved.tenantId))\n}\n\nexport async function PUT(req: Request) {\n const resolved = await resolveCurrentOrganization(req)\n if ('response' in resolved) return resolved.response\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.') },\n { status: 422 },\n )\n }\n if (!body || typeof body !== 'object' || !Object.prototype.hasOwnProperty.call(body, 'logoUrl')) {\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.') },\n { status: 422 },\n )\n }\n\n const parsed = brandingUpdateSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n {\n error: resolved.translate('directory.branding.errors.invalidLogoUrl', 'Enter a valid image URL.'),\n issues: parsed.error.issues,\n },\n { status: 422 },\n )\n }\n\n try {\n const commandBus = resolved.container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(\n resolved.container,\n resolved.auth,\n req,\n resolved.organizationId,\n resolved.tenantId,\n )\n const { result } = await commandBus.execute<Record<string, unknown>, Organization>(\n 'directory.organizations.update',\n {\n input: {\n id: resolved.organizationId,\n tenantId: resolved.tenantId,\n logoUrl: parsed.data.logoUrl ?? null,\n },\n ctx,\n },\n )\n await invalidateSidebarBrandingCache(resolved.container, resolved.organizationId, resolved.tenantId)\n return NextResponse.json(toResponsePayload(result, resolved.tenantId))\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('directory.organization-branding.update failed', err)\n return NextResponse.json(\n { error: resolved.translate('directory.branding.errors.save', 'Failed to update organization branding.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Directory',\n summary: 'Current organization branding',\n methods: {\n GET: {\n summary: 'Read sidebar branding for the selected organization',\n description: 'Returns the logo URL used by the backend sidebar for the currently selected organization.',\n responses: [\n { status: 200, description: 'Organization branding', schema: brandingResponseSchema },\n ],\n errors: [\n { status: 400, description: 'A concrete organization scope is required', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Organization not found', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update sidebar branding for the selected organization',\n description: 'Stores an external image URL or an internal attachment image URL as the selected organization logo.',\n requestBody: {\n contentType: 'application/json',\n schema: brandingUpdateSchema,\n },\n responses: [\n { status: 200, description: 'Updated organization branding', schema: brandingResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Save failed', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 422, description: 'Invalid logo URL', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AAGnC,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AACzC,SAAS,0CAA0C;AACnD,OAAO;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,8BAA8B,EAAE;AAAA,EAC5E,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,gCAAgC,EAAE;AAChF;AAEA,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,EAChC,kBAAkB,EAAE,OAAO;AAAA,EAC3B,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,SAAS,EAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAED,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,SAAS,yBAAyB,MAAM;AAC1C,CAAC;AAED,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,OAAO,EAAE,OAAO;AAClB,CAAC;AAID,SAAS,oBACP,WACA,MACA,KACA,gBACA,UACuB;AACvB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,MACjB,YAAY;AAAA,MACZ,WAAW,CAAC,cAAc;AAAA,MAC1B,YAAY;AAAA,MACZ;AAAA,IACF;AAAA,IACA,wBAAwB;AAAA,IACxB,iBAAiB,CAAC,cAAc;AAAA,IAChC,SAAS;AAAA,EACX;AACF;AAEA,eAAe,2BAA2B,KAAc;AACtD,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,KAAK;AACd,WAAO;AAAA,MACL,UAAU,aAAa,KAAK,EAAE,OAAO,UAAU,2BAA2B,cAAc,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9G;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,iBAAiB,MAAM,cAAc,KAAK,SAAS;AACzD,QAAM,WAAW,MAAM,YAAY,KAAK,YAAY;AACpD,MAAI,CAAC,kBAAkB,CAAC,UAAU;AAChC,WAAO;AAAA,MACL,UAAU,aAAa;AAAA,QACrB;AAAA,UACE,OAAO;AAAA,YACL;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,QACA,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,eAAe,MAAM;AAAA,IACzB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,gBAAgB,QAAQ,UAAU,WAAW,KAAK;AAAA,IACxD,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,IACvB,EAAE,UAAU,eAAe;AAAA,EAC7B;AACA,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,MACL,UAAU,aAAa;AAAA,QACrB,EAAE,OAAO,UAAU,sCAAsC,wBAAwB,EAAE;AAAA,QACnF,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,WAAW,cAAc,gBAAgB,UAAU,UAAU;AAC9E;AAEA,SAAS,kBAAkB,cAA4B,UAAkB;AACvE,SAAO;AAAA,IACL,gBAAgB,OAAO,aAAa,EAAE;AAAA,IACtC,kBAAkB,aAAa;AAAA,IAC/B;AAAA,IACA,SAAS,aAAa,WAAW;AAAA,EACnC;AACF;AAEA,eAAe,+BAA+B,WAA6B,gBAAwB,UAAkB;AACnH,MAAI;AACF,UAAM,QAAQ,UAAU,QAAQ,OAAO;AAGvC,UAAM;AAAA,MAAmB;AAAA,MAAU,MACjC,OAAO,eAAe;AAAA,QACpB,4BAA4B,cAAc;AAAA,QAC1C,sBAAsB,QAAQ;AAAA,MAChC,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,WAAW,MAAM,2BAA2B,GAAG;AACrD,MAAI,cAAc,SAAU,QAAO,SAAS;AAE5C,SAAO,aAAa,KAAK,kBAAkB,SAAS,cAAc,SAAS,QAAQ,CAAC;AACtF;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,WAAW,MAAM,2BAA2B,GAAG;AACrD,MAAI,cAAc,SAAU,QAAO,SAAS;AAE5C,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,SAAS,UAAU,4CAA4C,0BAA0B,EAAE;AAAA,MACpG,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACA,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,OAAO,UAAU,eAAe,KAAK,MAAM,SAAS,GAAG;AAC/F,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,SAAS,UAAU,4CAA4C,0BAA0B,EAAE;AAAA,MACpG,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,qBAAqB,UAAU,IAAI;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa;AAAA,MAClB;AAAA,QACE,OAAO,SAAS,UAAU,4CAA4C,0BAA0B;AAAA,QAChG,QAAQ,OAAO,MAAM;AAAA,MACvB;AAAA,MACA,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,aAAa,SAAS,UAAU,QAAQ,YAAY;AAC1D,UAAM,MAAM;AAAA,MACV,SAAS;AAAA,MACT,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AACA,UAAM,EAAE,OAAO,IAAI,MAAM,WAAW;AAAA,MAClC;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,IAAI,SAAS;AAAA,UACb,UAAU,SAAS;AAAA,UACnB,SAAS,OAAO,KAAK,WAAW;AAAA,QAClC;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,+BAA+B,SAAS,WAAW,SAAS,gBAAgB,SAAS,QAAQ;AACnG,WAAO,aAAa,KAAK,kBAAkB,QAAQ,SAAS,QAAQ,CAAC;AAAA,EACvE,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,YAAQ,MAAM,iDAAiD,GAAG;AAClE,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,SAAS,UAAU,kCAAkC,yCAAyC,EAAE;AAAA,MACzG,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,yBAAyB,QAAQ,uBAAuB;AAAA,MACtF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,6CAA6C,QAAQ,YAAY;AAAA,QAC7F,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,YAAY;AAAA,MAC5E;AAAA,IACF;AAAA,IACA,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,iCAAiC,QAAQ,uBAAuB;AAAA,MAC9F;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe,QAAQ,YAAY;AAAA,QAC/D,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,YAAY;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -211,6 +211,7 @@ async function GET(req) {
211
211
  const items2 = orgs2.map((org) => ({
212
212
  id: stringId(org.id),
213
213
  name: org.name,
214
+ logoUrl: org.logoUrl ?? null,
214
215
  parentId: org.parentId ?? null,
215
216
  tenantId,
216
217
  isActive: !!org.isActive,
@@ -297,8 +298,10 @@ async function GET(req) {
297
298
  byTenant.get(tid).push(org);
298
299
  }
299
300
  const slugByOrgId2 = /* @__PURE__ */ new Map();
301
+ const logoUrlByOrgId2 = /* @__PURE__ */ new Map();
300
302
  for (const org of allOrgs) {
301
303
  slugByOrgId2.set(String(org.id), org.slug ?? null);
304
+ logoUrlByOrgId2.set(String(org.id), org.logoUrl ?? null);
302
305
  }
303
306
  const tenantIds = Array.from(byTenant.keys());
304
307
  const tenants = tenantIds.length ? await em.find(Tenant, { id: { $in: tenantIds } }) : [];
@@ -372,6 +375,7 @@ async function GET(req) {
372
375
  id: node.id,
373
376
  name: node.name,
374
377
  slug: slugByOrgId2.get(recordId) ?? null,
378
+ logoUrl: logoUrlByOrgId2.get(recordId) ?? null,
375
379
  tenantId: tid,
376
380
  tenantName: tenantNameMap[tid] ?? tid,
377
381
  parentId: node.parentId,
@@ -411,9 +415,11 @@ async function GET(req) {
411
415
  const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: "ASC" } });
412
416
  const hierarchy = computeHierarchyForOrganizations(orgs, tenantId);
413
417
  const slugByOrgId = /* @__PURE__ */ new Map();
418
+ const logoUrlByOrgId = /* @__PURE__ */ new Map();
414
419
  const updatedAtByOrgId = /* @__PURE__ */ new Map();
415
420
  for (const org of orgs) {
416
421
  slugByOrgId.set(String(org.id), org.slug ?? null);
422
+ logoUrlByOrgId.set(String(org.id), org.logoUrl ?? null);
417
423
  updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null);
418
424
  }
419
425
  const search = (query.search || "").trim().toLowerCase();
@@ -471,6 +477,7 @@ async function GET(req) {
471
477
  id: node.id,
472
478
  name: node.name,
473
479
  slug: slugByOrgId.get(recordId) ?? null,
480
+ logoUrl: logoUrlByOrgId.get(recordId) ?? null,
474
481
  updatedAt: updatedAtByOrgId.get(recordId) ?? null,
475
482
  tenantId: node.tenantId,
476
483
  tenantName,