@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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/directory/api/organizations/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { organizationCreateSchema, organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport {\n computeHierarchyForOrganizations,\n type ComputedHierarchy,\n type ComputedOrganizationNode,\n} from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport {\n getSelectedOrganizationFromRequest,\n resolveOrganizationScopeForRequest,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { organizationCrudEvents, organizationCrudIndexer } from '@open-mercato/core/modules/directory/commands/organizations'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport {\n directoryTag,\n directoryErrorSchema,\n directoryOkSchema,\n organizationListResponseSchema,\n} from '../openapi'\nimport { resolveIsSuperAdmin, enforceTenantSelection } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\ntype CrudInput = Record<string, unknown>\nconst rawBodySchema = z.object({}).passthrough()\n\ntype TreeNode = {\n id: string\n name: string\n parentId: string | null\n tenantId: string | null\n depth: number\n ancestorIds: string[]\n childIds: string[]\n descendantIds: string[]\n isActive: boolean\n treePath: string | null\n pathLabel: string\n children: TreeNode[]\n}\n\nconst viewSchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n search: z.string().optional(),\n view: z.enum(['options', 'manage', 'tree']).default('options'),\n ids: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n includeInactive: z.enum(['true', 'false']).optional(),\n status: z.enum(['all', 'active', 'inactive']).optional(),\n})\n\nfunction parseIds(raw: string | null): string[] | null {\n if (!raw) return null\n const ids = raw.split(',').map((s) => s.trim()).filter(Boolean)\n return ids.length ? Array.from(new Set(ids)) : null\n}\n\nfunction stringId(value: unknown): string {\n return String(value)\n}\n\nfunction enforceTenantScope(\n authTenantId: string | null,\n requestedTenantId: string | null,\n isSuperAdmin: boolean,\n): string | null {\n if (isSuperAdmin) {\n return requestedTenantId || authTenantId || null\n }\n if (authTenantId && requestedTenantId && requestedTenantId !== authTenantId) {\n return null\n }\n return requestedTenantId || authTenantId\n}\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['directory.organizations.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n },\n orm: {\n entity: Organization,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: organizationCrudEvents,\n indexer: organizationCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.organizations.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.organizations.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.organizations.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const metadata = crud.metadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [] }, { status: 401 })\n\n const url = new URL(req.url)\n const parsed = viewSchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n view: url.searchParams.get('view') ?? undefined,\n ids: url.searchParams.get('ids') ?? undefined,\n tenantId: url.searchParams.get('tenantId') ?? undefined,\n includeInactive: url.searchParams.get('includeInactive') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [] }, { status: 400 })\n\n const query = parsed.data\n const ids = parseIds(query.ids ?? null)\n const requestedTenantRaw = query.tenantId ?? null\n const normalizedRequestedTenantId = requestedTenantRaw && requestedTenantRaw.toLowerCase() === 'all' ? null : requestedTenantRaw\n const authTenantId = auth.tenantId ?? null\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = (container.resolve('em') as EntityManager)\n const allowAllTenants = isSuperAdmin && !normalizedRequestedTenantId && query.view === 'manage'\n let tenantId = allowAllTenants ? null : enforceTenantScope(authTenantId, normalizedRequestedTenantId, isSuperAdmin)\n const status = query.status ?? 'all'\n const includeInactive = parseBooleanToken(query.includeInactive) === true || status !== 'active'\n\n if (!allowAllTenants && !tenantId && !authTenantId && ids?.length) {\n let scopedIds = ids\n if (!isSuperAdmin) {\n let allowedIds: string[] | null = null\n try {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n allowedIds = Array.isArray(scope.allowedIds) ? scope.allowedIds : null\n } catch {}\n const allowedSet = allowedIds ? new Set(allowedIds) : new Set<string>()\n scopedIds = ids.filter((id) => allowedSet.has(id))\n }\n if (scopedIds.length) {\n const scopedOrgs: Organization[] = await findWithDecryption(\n em,\n Organization,\n { id: { $in: scopedIds }, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: null },\n )\n const tenantCandidates = new Set<string>()\n for (const org of scopedOrgs) {\n const orgTenantId = org.tenant?.id ? stringId(org.tenant.id) : ''\n if (orgTenantId) tenantCandidates.add(orgTenantId)\n }\n if (tenantCandidates.size === 1) {\n const candidateTenantId = Array.from(tenantCandidates)[0] ?? null\n try {\n tenantId = await enforceTenantSelection({ auth, container }, candidateTenantId)\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n throw error\n }\n } else if (tenantCandidates.size > 1) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n const candidateOrgIds = new Set<string>()\n const cookieOrgId = getSelectedOrganizationFromRequest(req)\n const effectiveCookieOrgId = cookieOrgId && !isAllOrganizationsSelection(cookieOrgId) ? cookieOrgId : null\n if (effectiveCookieOrgId) candidateOrgIds.add(effectiveCookieOrgId)\n if (auth.orgId) candidateOrgIds.add(auth.orgId)\n\n try {\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: effectiveCookieOrgId ?? undefined,\n })\n if (scope.selectedId) candidateOrgIds.add(scope.selectedId)\n if (Array.isArray(scope.filterIds) && scope.filterIds.length) {\n candidateOrgIds.add(scope.filterIds[0]!)\n }\n if (Array.isArray(scope.allowedIds) && scope.allowedIds.length) {\n candidateOrgIds.add(scope.allowedIds[0]!)\n }\n } catch {}\n\n for (const orgId of candidateOrgIds) {\n if (!orgId) continue\n const org = await findOneWithDecryption(\n em,\n Organization,\n { id: orgId, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: orgId },\n )\n if (org?.tenant && org.tenant.id) {\n tenantId = stringId(org.tenant.id)\n break\n }\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n if (query.view === 'options') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const where: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n if (status === 'active') where.isActive = true\n if (status === 'inactive') where.isActive = false\n if (status === 'all' && !includeInactive) where.isActive = true\n if (ids) where.id = { $in: ids }\n const orgs = await em.find(Organization, where, { orderBy: { name: 'ASC' } })\n const items = orgs.map((org) => ({\n id: stringId(org.id),\n name: org.name,\n parentId: org.parentId ?? null,\n tenantId: tenantId,\n isActive: !!org.isActive,\n depth: org.depth ?? 0,\n treePath: org.treePath ?? stringId(org.id),\n }))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items })\n }\n\n if (query.view === 'tree') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n const nodeMap = new Map<string, { node: ComputedOrganizationNode; children: TreeNode[] }>()\n const roots: TreeNode[] = []\n for (const node of hierarchy.ordered) {\n const treeNode: TreeNode = {\n id: node.id,\n name: node.name,\n parentId: node.parentId,\n tenantId: node.tenantId,\n depth: node.depth,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n isActive: node.isActive,\n treePath: node.treePath,\n pathLabel: node.pathLabel,\n children: [],\n }\n nodeMap.set(node.id, { node, children: treeNode.children })\n if (node.parentId && nodeMap.has(node.parentId)) {\n const parentEntry = nodeMap.get(node.parentId)!\n parentEntry.children.push(treeNode)\n } else {\n roots.push(treeNode)\n }\n }\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: roots,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n })\n return NextResponse.json({ items: roots })\n }\n\n if (query.view !== 'manage') {\n return NextResponse.json({ items: [] }, { status: 400 })\n }\n\n if (allowAllTenants) {\n // Multi-tenant aggregate view for super administrators\n const search = (query.search || '').trim().toLowerCase()\n const allOrgs = await findWithDecryption(\n em,\n Organization,\n { deletedAt: null },\n { orderBy: { name: 'ASC' }, populate: ['tenant'] },\n { tenantId: null, organizationId: null },\n )\n const byTenant = new Map<string, Organization[]>()\n for (const org of allOrgs) {\n const tenantEntity = org.tenant\n const tid = tenantEntity ? stringId(tenantEntity.id) : null\n if (!tid) continue\n if (!byTenant.has(tid)) byTenant.set(tid, [])\n byTenant.get(tid)!.push(org)\n }\n\n const slugByOrgId = new Map<string, string | null>()\n for (const org of allOrgs) {\n slugByOrgId.set(String(org.id), org.slug ?? null)\n }\n\n const tenantIds = Array.from(byTenant.keys())\n const tenants = tenantIds.length\n ? await em.find(Tenant, { id: { $in: tenantIds as unknown as string[] } })\n : []\n const tenantNameMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tid = stringId(tenant.id)\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : tid\n acc[tid] = name\n return acc\n }, {})\n\n const hierarchies = new Map<string, ComputedHierarchy>()\n const orderedNodes: Array<{ tenantId: string; node: ComputedOrganizationNode }> = []\n for (const [tid, orgs] of byTenant.entries()) {\n const hierarchy = computeHierarchyForOrganizations(orgs, tid)\n hierarchies.set(tid, hierarchy)\n for (const node of hierarchy.ordered) {\n orderedNodes.push({ tenantId: tid, node })\n }\n }\n\n let rows = orderedNodes\n if (query.status === 'active') {\n rows = rows.filter((entry) => entry.node.isActive)\n } else if (query.status === 'inactive') {\n rows = rows.filter((entry) => !entry.node.isActive)\n }\n\n if (search) {\n rows = rows.filter(({ node }) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter(({ node }) => idSet.has(node.id))\n }\n\n rows.sort((a, b) => {\n const nameA = tenantNameMap[a.tenantId] ?? a.tenantId\n const nameB = tenantNameMap[b.tenantId] ?? b.tenantId\n const tenantCompare = nameA.localeCompare(nameB)\n if (tenantCompare !== 0) return tenantCompare\n return a.node.pathLabel.localeCompare(b.node.pathLabel)\n })\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const entry of paged) {\n const recordId = String(entry.node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = entry.tenantId\n organizationIdByRecord[recordId] = recordId\n }\n const tenantFallbacks = Array.from(new Set(recordIds.map((id) => tenantIdByRecord[id]).filter((value): value is string => typeof value === 'string' && value.length > 0)))\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks,\n })\n : {}\n const items = paged.map(({ tenantId: tid, node }) => {\n const hierarchy = hierarchies.get(tid)\n const parentName = node.parentId && hierarchy ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n slug: slugByOrgId.get(recordId) ?? null,\n tenantId: tid,\n tenantName: tenantNameMap[tid] ?? tid,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId: null,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n }\n\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n const slugByOrgId = new Map<string, string | null>()\n const updatedAtByOrgId = new Map<string, string | null>()\n for (const org of orgs) {\n slugByOrgId.set(String(org.id), org.slug ?? null)\n updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null)\n }\n\n // Manage view: paginated flat list for a single tenant\n const search = (query.search || '').trim().toLowerCase()\n let rows = hierarchy.ordered\n if (status === 'active') {\n rows = rows.filter((node) => node.isActive)\n } else if (status === 'inactive') {\n rows = rows.filter((node) => !node.isActive)\n }\n\n if (search) {\n rows = rows.filter((node) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter((node) => idSet.has(node.id))\n }\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const node of paged) {\n const recordId = String(node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = node.tenantId ? String(node.tenantId) : null\n organizationIdByRecord[recordId] = recordId\n }\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: tenantId ? [tenantId] : [],\n })\n : {}\n let tenantName: string | null = null\n if (isSuperAdmin && tenantId) {\n const tenant = await em.findOne(Tenant, { id: tenantId })\n if (tenant) {\n const display = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : stringId(tenant.id)\n tenantName = display\n }\n }\n const items = paged.map((node) => {\n const parentName = node.parentId ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n slug: slugByOrgId.get(recordId) ?? null,\n updatedAt: updatedAtByOrgId.get(recordId) ?? null,\n tenantId: node.tenantId,\n tenantName,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst organizationCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationsGetDoc: OpenApiMethodDoc = {\n summary: 'List organizations',\n description: 'Returns organizations using options, tree, or paginated manage view depending on the `view` parameter.',\n tags: [directoryTag],\n query: viewSchema,\n responses: [\n { status: 200, description: 'Organization data for the requested view.', schema: organizationListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query or tenant scope', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPostDoc: OpenApiMethodDoc = {\n summary: 'Create organization',\n description: 'Creates a new organization within a tenant and optionally assigns hierarchy relationships.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationCreateSchema,\n description: 'Organization attributes and optional hierarchy configuration.',\n },\n responses: [\n { status: 201, description: 'Organization created.', schema: organizationCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPutDoc: OpenApiMethodDoc = {\n summary: 'Update organization',\n description: 'Updates organization details and hierarchy assignments.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationUpdateSchema,\n description: 'Organization identifier followed by fields to update.',\n },\n responses: [\n { status: 200, description: 'Organization updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete organization',\n description: 'Soft deletes an organization identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationDeleteRequestSchema,\n description: 'Identifier of the organization to delete.',\n },\n responses: [\n { status: 200, description: 'Organization deleted.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage organizations',\n methods: {\n GET: organizationsGetDoc,\n POST: organizationsPostDoc,\n PUT: organizationsPutDoc,\n DELETE: organizationsDeleteDoc,\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,eAAe,qBAAqB;AAC7C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,cAAc,cAAc;AACrC,SAAS,0BAA0B,gCAAgC;AACnE;AAAA,EACE;AAAA,OAGK;AACP,SAAS,mCAAmC;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AACtC,SAAS,SAAS;AAGlB,SAAS,wBAAwB,+BAA+B;AAEhE,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB,8BAA8B;AAC5D,SAAS,uBAAuB;AAChC,SAAS,oBAAoB,6BAA6B;AAG1D,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAiB/C,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EACxC,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACtD,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,UAAU,MAAM,CAAC,EAAE,QAAQ,SAAS;AAAA,EAC7D,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EACzB,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACrC,iBAAiB,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC,EAAE,SAAS;AAAA,EACpD,QAAQ,EAAE,KAAK,CAAC,OAAO,UAAU,UAAU,CAAC,EAAE,SAAS;AACzD,CAAC;AAED,SAAS,SAAS,KAAqC;AACrD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,MAAM,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAC9D,SAAO,IAAI,SAAS,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI;AACjD;AAEA,SAAS,SAAS,OAAwB;AACxC,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,mBACP,cACA,mBACA,cACe;AACf,MAAI,cAAc;AAChB,WAAO,qBAAqB,gBAAgB;AAAA,EAC9C;AACA,MAAI,gBAAgB,qBAAqB,sBAAsB,cAAc;AAC3E,WAAO;AAAA,EACT;AACA,SAAO,qBAAqB;AAC9B;AAEA,MAAM,OAAO,cAA6D;AAAA,EACxE,UAAU;AAAA,IACR,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,8BAA8B,EAAE;AAAA,IAC5E,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,gCAAgC,EAAE;AAAA,IAC/E,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,gCAAgC,EAAE;AAAA,IAC9E,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,gCAAgC,EAAE;AAAA,EACnF;AAAA,EACA,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,CAAC,EAAE,OAAO,MAAM;AAAA,MAC1B,UAAU,CAAC,EAAE,OAAO,OAAO,EAAE,IAAI,OAAO,OAAO,EAAE,EAAE;AAAA,MACnD,QAAQ;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,CAAC,EAAE,OAAO,MAAM;AAAA,MAC1B,UAAU,OAAO,EAAE,IAAI,KAAK;AAAA,IAC9B;AAAA,IACA,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,UAAU,OAAO,EAAE,IAAI,KAAK;AAAA,IAC9B;AAAA,EACF;AACF,CAAC;AAEM,MAAM,WAAW,KAAK;AAE7B,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElE,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,SAAS,WAAW,UAAU;AAAA,IAClC,MAAM,IAAI,aAAa,IAAI,MAAM,KAAK;AAAA,IACtC,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,IAC9C,QAAQ,IAAI,aAAa,IAAI,QAAQ,KAAK;AAAA,IAC1C,MAAM,IAAI,aAAa,IAAI,MAAM,KAAK;AAAA,IACtC,KAAK,IAAI,aAAa,IAAI,KAAK,KAAK;AAAA,IACpC,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,IAC9C,iBAAiB,IAAI,aAAa,IAAI,iBAAiB,KAAK;AAAA,IAC5D,QAAQ,IAAI,aAAa,IAAI,QAAQ,KAAK;AAAA,EAC5C,CAAC;AACD,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,KAAK,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE5E,QAAM,QAAQ,OAAO;AACrB,QAAM,MAAM,SAAS,MAAM,OAAO,IAAI;AACtC,QAAM,qBAAqB,MAAM,YAAY;AAC7C,QAAM,8BAA8B,sBAAsB,mBAAmB,YAAY,MAAM,QAAQ,OAAO;AAC9G,QAAM,eAAe,KAAK,YAAY;AACtC,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,eAAe,MAAM,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAClE,QAAM,KAAM,UAAU,QAAQ,IAAI;AAClC,QAAM,kBAAkB,gBAAgB,CAAC,+BAA+B,MAAM,SAAS;AACvF,MAAI,WAAW,kBAAkB,OAAO,mBAAmB,cAAc,6BAA6B,YAAY;AAClH,QAAM,SAAS,MAAM,UAAU;AAC/B,QAAM,kBAAkB,kBAAkB,MAAM,eAAe,MAAM,QAAQ,WAAW;AAExF,MAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,gBAAgB,KAAK,QAAQ;AACjE,QAAI,YAAY;AAChB,QAAI,CAAC,cAAc;AACjB,UAAI,aAA8B;AAClC,UAAI;AACF,cAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,qBAAa,MAAM,QAAQ,MAAM,UAAU,IAAI,MAAM,aAAa;AAAA,MACpE,QAAQ;AAAA,MAAC;AACT,YAAM,aAAa,aAAa,IAAI,IAAI,UAAU,IAAI,oBAAI,IAAY;AACtE,kBAAY,IAAI,OAAO,CAAC,OAAO,WAAW,IAAI,EAAE,CAAC;AAAA,IACnD;AACA,QAAI,UAAU,QAAQ;AACpB,YAAM,aAA6B,MAAM;AAAA,QACvC;AAAA,QACA;AAAA,QACA,EAAE,IAAI,EAAE,KAAK,UAAU,GAAG,WAAW,KAAK;AAAA,QAC1C,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,QACvB,EAAE,UAAU,cAAc,gBAAgB,KAAK;AAAA,MACjD;AACA,YAAM,mBAAmB,oBAAI,IAAY;AACzC,iBAAW,OAAO,YAAY;AAC5B,cAAM,cAAc,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,EAAE,IAAI;AAC/D,YAAI,YAAa,kBAAiB,IAAI,WAAW;AAAA,MACnD;AACA,UAAI,iBAAiB,SAAS,GAAG;AAC/B,cAAM,oBAAoB,MAAM,KAAK,gBAAgB,EAAE,CAAC,KAAK;AAC7D,YAAI;AACF,qBAAW,MAAM,uBAAuB,EAAE,MAAM,UAAU,GAAG,iBAAiB;AAAA,QAChF,SAAS,OAAO;AACd,cAAI,gBAAgB,KAAK,GAAG;AAC1B,mBAAO,aAAa,KAAK,MAAM,MAAM,EAAE,QAAQ,MAAM,OAAO,CAAC;AAAA,UAC/D;AACA,gBAAM;AAAA,QACR;AAAA,MACF,WAAW,iBAAiB,OAAO,GAAG;AACpC,eAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,mBAAmB,CAAC,UAAU;AACjC,UAAM,kBAAkB,oBAAI,IAAY;AACxC,UAAM,cAAc,mCAAmC,GAAG;AAC1D,UAAM,uBAAuB,eAAe,CAAC,4BAA4B,WAAW,IAAI,cAAc;AACtG,QAAI,qBAAsB,iBAAgB,IAAI,oBAAoB;AAClE,QAAI,KAAK,MAAO,iBAAgB,IAAI,KAAK,KAAK;AAE9C,QAAI;AACF,YAAM,QAAQ,MAAM,mCAAmC;AAAA,QACrD;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,YAAY,wBAAwB;AAAA,MACtC,CAAC;AACD,UAAI,MAAM,WAAY,iBAAgB,IAAI,MAAM,UAAU;AAC1D,UAAI,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM,UAAU,QAAQ;AAC5D,wBAAgB,IAAI,MAAM,UAAU,CAAC,CAAE;AAAA,MACzC;AACA,UAAI,MAAM,QAAQ,MAAM,UAAU,KAAK,MAAM,WAAW,QAAQ;AAC9D,wBAAgB,IAAI,MAAM,WAAW,CAAC,CAAE;AAAA,MAC1C;AAAA,IACF,QAAQ;AAAA,IAAC;AAET,eAAW,SAAS,iBAAiB;AACnC,UAAI,CAAC,MAAO;AACZ,YAAM,MAAM,MAAM;AAAA,QAChB;AAAA,QACA;AAAA,QACA,EAAE,IAAI,OAAO,WAAW,KAAK;AAAA,QAC7B,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,QACvB,EAAE,UAAU,cAAc,gBAAgB,MAAM;AAAA,MAClD;AACA,UAAI,KAAK,UAAU,IAAI,OAAO,IAAI;AAChC,mBAAW,SAAS,IAAI,OAAO,EAAE;AACjC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,mBAAmB,CAAC,UAAU;AACjC,WAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzF;AAEA,MAAI,MAAM,SAAS,WAAW;AAC5B,QAAI,CAAC,UAAU;AACb,aAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzF;AACA,UAAM,QAAmC,EAAE,QAAQ,UAAU,WAAW,KAAK;AAC7E,QAAI,WAAW,SAAU,OAAM,WAAW;AAC1C,QAAI,WAAW,WAAY,OAAM,WAAW;AAC5C,QAAI,WAAW,SAAS,CAAC,gBAAiB,OAAM,WAAW;AAC3D,QAAI,IAAK,OAAM,KAAK,EAAE,KAAK,IAAI;AAC/B,UAAMA,QAAO,MAAM,GAAG,KAAK,cAAc,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,EAAE,CAAC;AAC5E,UAAMC,SAAQD,MAAK,IAAI,CAAC,SAAS;AAAA,MAC/B,IAAI,SAAS,IAAI,EAAE;AAAA,MACnB,MAAM,IAAI;AAAA,MACV,UAAU,IAAI,YAAY;AAAA,MAC1B;AAAA,MACA,UAAU,CAAC,CAAC,IAAI;AAAA,MAChB,OAAO,IAAI,SAAS;AAAA,MACpB,UAAU,IAAI,YAAY,SAAS,IAAI,EAAE;AAAA,IAC3C,EAAE;AACF,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,OAAAC;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,MACA,YAAY,OAAO,IAAI,WAAW,IAAI,cAAc;AAAA,IACtD,CAAC;AACD,WAAO,aAAa,KAAK,EAAE,OAAAA,OAAM,CAAC;AAAA,EACpC;AAEA,MAAI,MAAM,SAAS,QAAQ;AACzB,QAAI,CAAC,UAAU;AACb,aAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzF;AACA,UAAMC,iBAA2C,EAAE,QAAQ,UAAU,WAAW,KAAK;AACrF,UAAMF,QAAO,MAAM,GAAG,KAAK,cAAcE,gBAAe,EAAE,SAAS,EAAE,MAAM,MAAM,EAAE,CAAC;AACpF,UAAMC,aAAY,iCAAiCH,OAAM,QAAQ;AACjE,UAAM,UAAU,oBAAI,IAAsE;AAC1F,UAAM,QAAoB,CAAC;AAC3B,eAAW,QAAQG,WAAU,SAAS;AACpC,YAAM,WAAqB;AAAA,QACzB,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,QACX,UAAU,KAAK;AAAA,QACf,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK;AAAA,QACf,eAAe,KAAK;AAAA,QACpB,UAAU,KAAK;AAAA,QACf,UAAU,KAAK;AAAA,QACf,WAAW,KAAK;AAAA,QAChB,UAAU,CAAC;AAAA,MACb;AACA,cAAQ,IAAI,KAAK,IAAI,EAAE,MAAM,UAAU,SAAS,SAAS,CAAC;AAC1D,UAAI,KAAK,YAAY,QAAQ,IAAI,KAAK,QAAQ,GAAG;AAC/C,cAAM,cAAc,QAAQ,IAAI,KAAK,QAAQ;AAC7C,oBAAY,SAAS,KAAK,QAAQ;AAAA,MACpC,OAAO;AACL,cAAM,KAAK,QAAQ;AAAA,MACrB;AAAA,IACF;AACA,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS;AAAA,MACT,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,aAAa,KAAK,EAAE,OAAO,MAAM,CAAC;AAAA,EAC3C;AAEA,MAAI,MAAM,SAAS,UAAU;AAC3B,WAAO,aAAa,KAAK,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzD;AAEA,MAAI,iBAAiB;AAEnB,UAAMC,WAAU,MAAM,UAAU,IAAI,KAAK,EAAE,YAAY;AACvD,UAAM,UAAU,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,MACA,EAAE,WAAW,KAAK;AAAA,MAClB,EAAE,SAAS,EAAE,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE;AAAA,MACjD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,IACzC;AACA,UAAM,WAAW,oBAAI,IAA4B;AACjD,eAAW,OAAO,SAAS;AACzB,YAAM,eAAe,IAAI;AACzB,YAAM,MAAM,eAAe,SAAS,aAAa,EAAE,IAAI;AACvD,UAAI,CAAC,IAAK;AACV,UAAI,CAAC,SAAS,IAAI,GAAG,EAAG,UAAS,IAAI,KAAK,CAAC,CAAC;AAC5C,eAAS,IAAI,GAAG,EAAG,KAAK,GAAG;AAAA,IAC7B;AAEA,UAAMC,eAAc,oBAAI,IAA2B;AACnD,eAAW,OAAO,SAAS;AACzB,MAAAA,aAAY,IAAI,OAAO,IAAI,EAAE,GAAG,IAAI,QAAQ,IAAI;AAAA,IAClD;AAEA,UAAM,YAAY,MAAM,KAAK,SAAS,KAAK,CAAC;AAC5C,UAAM,UAAU,UAAU,SACtB,MAAM,GAAG,KAAK,QAAQ,EAAE,IAAI,EAAE,KAAK,UAAiC,EAAE,CAAC,IACvE,CAAC;AACL,UAAM,gBAAgB,QAAQ,OAA+B,CAAC,KAAK,WAAW;AAC5E,YAAM,MAAM,SAAS,OAAO,EAAE;AAC9B,YAAM,OAAO,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,IAAI,OAAO,OAAO;AACvF,UAAI,GAAG,IAAI;AACX,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAEL,UAAM,cAAc,oBAAI,IAA+B;AACvD,UAAM,eAA4E,CAAC;AACnF,eAAW,CAAC,KAAKL,KAAI,KAAK,SAAS,QAAQ,GAAG;AAC5C,YAAMG,aAAY,iCAAiCH,OAAM,GAAG;AAC5D,kBAAY,IAAI,KAAKG,UAAS;AAC9B,iBAAW,QAAQA,WAAU,SAAS;AACpC,qBAAa,KAAK,EAAE,UAAU,KAAK,KAAK,CAAC;AAAA,MAC3C;AAAA,IACF;AAEA,QAAIG,QAAO;AACX,QAAI,MAAM,WAAW,UAAU;AAC7B,MAAAA,QAAOA,MAAK,OAAO,CAAC,UAAU,MAAM,KAAK,QAAQ;AAAA,IACnD,WAAW,MAAM,WAAW,YAAY;AACtC,MAAAA,QAAOA,MAAK,OAAO,CAAC,UAAU,CAAC,MAAM,KAAK,QAAQ;AAAA,IACpD;AAEA,QAAIF,SAAQ;AACV,MAAAE,QAAOA,MAAK,OAAO,CAAC,EAAE,KAAK,MAAM;AAC/B,cAAM,aAAa,KAAK,aAAa,IAAI,YAAY;AACrD,eAAO,KAAK,KAAK,YAAY,EAAE,SAASF,OAAM,KAAK,UAAU,SAASA,OAAM;AAAA,MAC9E,CAAC;AAAA,IACH;AACA,QAAI,KAAK;AACP,YAAM,QAAQ,IAAI,IAAI,GAAG;AACzB,MAAAE,QAAOA,MAAK,OAAO,CAAC,EAAE,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;AAAA,IACrD;AAEA,IAAAA,MAAK,KAAK,CAAC,GAAG,MAAM;AAClB,YAAM,QAAQ,cAAc,EAAE,QAAQ,KAAK,EAAE;AAC7C,YAAM,QAAQ,cAAc,EAAE,QAAQ,KAAK,EAAE;AAC7C,YAAM,gBAAgB,MAAM,cAAc,KAAK;AAC/C,UAAI,kBAAkB,EAAG,QAAO;AAChC,aAAO,EAAE,KAAK,UAAU,cAAc,EAAE,KAAK,SAAS;AAAA,IACxD,CAAC;AAED,UAAMC,SAAQD,MAAK;AACnB,UAAME,YAAW,MAAM;AACvB,UAAMC,QAAO,MAAM;AACnB,UAAMC,UAASD,QAAO,KAAKD;AAC3B,UAAMG,SAAQL,MAAK,MAAMI,QAAOA,SAAQF,SAAQ;AAChD,UAAMI,aAAsB,CAAC;AAC7B,UAAMC,oBAAkD,CAAC;AACzD,UAAMC,0BAAwD,CAAC;AAC/D,eAAW,SAASH,QAAO;AACzB,YAAM,WAAW,OAAO,MAAM,KAAK,EAAE;AACrC,MAAAC,WAAU,KAAK,QAAQ;AACvB,MAAAC,kBAAiB,QAAQ,IAAI,MAAM;AACnC,MAAAC,wBAAuB,QAAQ,IAAI;AAAA,IACrC;AACA,UAAM,kBAAkB,MAAM,KAAK,IAAI,IAAIF,WAAU,IAAI,CAAC,OAAOC,kBAAiB,EAAE,CAAC,EAAE,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,CAAC,CAAC;AACzK,UAAME,WAAUH,WAAU,SACtB,MAAM,sBAAsB;AAAA,MAC1B;AAAA,MACA,UAAU,EAAE,UAAU;AAAA,MACtB,WAAAA;AAAA,MACA,kBAAAC;AAAA,MACA,wBAAAC;AAAA,MACA;AAAA,IACF,CAAC,IACD,CAAC;AACL,UAAMb,SAAQU,OAAM,IAAI,CAAC,EAAE,UAAU,KAAK,KAAK,MAAM;AACnD,YAAMR,aAAY,YAAY,IAAI,GAAG;AACrC,YAAM,aAAa,KAAK,YAAYA,aAAYA,WAAU,IAAI,IAAI,KAAK,QAAQ,GAAG,QAAQ,OAAO;AACjG,YAAM,YAAY,KAAK,aAAa,KAAK;AACzC,YAAM,WAAW,OAAO,KAAK,EAAE;AAC/B,aAAO;AAAA,QACL,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,QACX,MAAME,aAAY,IAAI,QAAQ,KAAK;AAAA,QACnC,UAAU;AAAA,QACV,YAAY,cAAc,GAAG,KAAK;AAAA,QAClC,UAAU,KAAK;AAAA,QACf;AAAA,QACA,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK;AAAA,QACf,eAAe,KAAK;AAAA,QACpB,eAAe,KAAK,SAAS;AAAA,QAC7B,kBAAkB,KAAK,cAAc;AAAA,QACrC,UAAU,KAAK;AAAA,QACf,GAAIU,SAAQ,QAAQ,KAAK,CAAC;AAAA,MAC5B;AAAA,IACF,CAAC;AACD,UAAMC,cAAa,KAAK,IAAI,GAAG,KAAK,KAAKT,SAAQC,SAAQ,CAAC;AAC1D,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,OAAAP;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,UAAU;AAAA,MACV;AAAA,MACA,YAAY,OAAO,IAAI,WAAW,IAAI,cAAc;AAAA,IACtD,CAAC;AACD,WAAO,aAAa,KAAK,EAAE,OAAAA,QAAO,OAAAM,QAAO,MAAAE,OAAM,UAAAD,WAAU,YAAAQ,aAAY,aAAa,CAAC;AAAA,EACrF;AAEA,MAAI,CAAC,UAAU;AACb,WAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzF;AAEA,QAAM,gBAA2C,EAAE,QAAQ,UAAU,WAAW,KAAK;AACrF,QAAM,OAAO,MAAM,GAAG,KAAK,cAAc,eAAe,EAAE,SAAS,EAAE,MAAM,MAAM,EAAE,CAAC;AACpF,QAAM,YAAY,iCAAiC,MAAM,QAAQ;AACjE,QAAM,cAAc,oBAAI,IAA2B;AACnD,QAAM,mBAAmB,oBAAI,IAA2B;AACxD,aAAW,OAAO,MAAM;AACtB,gBAAY,IAAI,OAAO,IAAI,EAAE,GAAG,IAAI,QAAQ,IAAI;AAChD,qBAAiB,IAAI,OAAO,IAAI,EAAE,GAAG,IAAI,qBAAqB,OAAO,IAAI,UAAU,YAAY,IAAI,IAAI;AAAA,EACzG;AAGA,QAAM,UAAU,MAAM,UAAU,IAAI,KAAK,EAAE,YAAY;AACvD,MAAI,OAAO,UAAU;AACrB,MAAI,WAAW,UAAU;AACvB,WAAO,KAAK,OAAO,CAAC,SAAS,KAAK,QAAQ;AAAA,EAC5C,WAAW,WAAW,YAAY;AAChC,WAAO,KAAK,OAAO,CAAC,SAAS,CAAC,KAAK,QAAQ;AAAA,EAC7C;AAEA,MAAI,QAAQ;AACV,WAAO,KAAK,OAAO,CAAC,SAAS;AAC3B,YAAM,aAAa,KAAK,aAAa,IAAI,YAAY;AACrD,aAAO,KAAK,KAAK,YAAY,EAAE,SAAS,MAAM,KAAK,UAAU,SAAS,MAAM;AAAA,IAC9E,CAAC;AAAA,EACH;AACA,MAAI,KAAK;AACP,UAAM,QAAQ,IAAI,IAAI,GAAG;AACzB,WAAO,KAAK,OAAO,CAAC,SAAS,MAAM,IAAI,KAAK,EAAE,CAAC;AAAA,EACjD;AAEA,QAAM,QAAQ,KAAK;AACnB,QAAM,WAAW,MAAM;AACvB,QAAM,OAAO,MAAM;AACnB,QAAM,SAAS,OAAO,KAAK;AAC3B,QAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,QAAQ;AAChD,QAAM,YAAsB,CAAC;AAC7B,QAAM,mBAAkD,CAAC;AACzD,QAAM,yBAAwD,CAAC;AAC/D,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,OAAO,KAAK,EAAE;AAC/B,cAAU,KAAK,QAAQ;AACvB,qBAAiB,QAAQ,IAAI,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AACrE,2BAAuB,QAAQ,IAAI;AAAA,EACrC;AACA,QAAM,UAAU,UAAU,SACtB,MAAM,sBAAsB;AAAA,IAC1B;AAAA,IACA,UAAU,EAAE,UAAU;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB,WAAW,CAAC,QAAQ,IAAI,CAAC;AAAA,EAC5C,CAAC,IACD,CAAC;AACL,MAAI,aAA4B;AAChC,MAAI,gBAAgB,UAAU;AAC5B,UAAM,SAAS,MAAM,GAAG,QAAQ,QAAQ,EAAE,IAAI,SAAS,CAAC;AACxD,QAAI,QAAQ;AACV,YAAM,UAAU,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,IAAI,OAAO,OAAO,SAAS,OAAO,EAAE;AAC5G,mBAAa;AAAA,IACf;AAAA,EACF;AACA,QAAM,QAAQ,MAAM,IAAI,CAAC,SAAS;AAChC,UAAM,aAAa,KAAK,WAAW,UAAU,IAAI,IAAI,KAAK,QAAQ,GAAG,QAAQ,OAAO;AACpF,UAAM,YAAY,KAAK,aAAa,KAAK;AACzC,UAAM,WAAW,OAAO,KAAK,EAAE;AAC/B,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,IAAI,QAAQ,KAAK;AAAA,MACnC,WAAW,iBAAiB,IAAI,QAAQ,KAAK;AAAA,MAC7C,UAAU,KAAK;AAAA,MACf;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,UAAU,KAAK;AAAA,MACf,eAAe,KAAK;AAAA,MACpB,eAAe,KAAK,SAAS;AAAA,MAC7B,kBAAkB,KAAK,cAAc;AAAA,MACrC,UAAU,KAAK;AAAA,MACf,GAAI,QAAQ,QAAQ,KAAK,CAAC;AAAA,IAC5B;AAAA,EACF,CAAC;AACD,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,QAAQ,CAAC;AAC1D,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA,IACT,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,YAAY,OAAO,IAAI,WAAW,IAAI,cAAc;AAAA,EACtD,CAAC;AACD,SAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,UAAU,YAAY,aAAa,CAAC;AACrF;AAEO,MAAM,OAAO,KAAK;AAClB,MAAM,MAAM,KAAK;AACjB,MAAM,SAAS,KAAK;AAE3B,MAAM,mCAAmC,EAAE,OAAO;AAAA,EAChD,IAAI,EAAE,OAAO,EAAE,KAAK;AACtB,CAAC;AAED,MAAM,kCAAkC,EAAE,OAAO;AAAA,EAC/C,IAAI,EAAE,OAAO,EAAE,KAAK;AACtB,CAAC;AAED,MAAM,sBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,YAAY;AAAA,EACnB,OAAO;AAAA,EACP,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,6CAA6C,QAAQ,+BAA+B;AAAA,EAClH;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,iCAAiC,QAAQ,qBAAqB;AAAA,IAC1F,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,qBAAqB;AAAA,EACtF;AACF;AAEA,MAAM,uBAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,YAAY;AAAA,EACnB,aAAa;AAAA,IACX,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,yBAAyB,QAAQ,iCAAiC;AAAA,EAChG;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,IAC9E,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,qBAAqB;AAAA,IACpF,EAAE,QAAQ,KAAK,aAAa,kDAAkD,QAAQ,qBAAqB;AAAA,EAC7G;AACF;AAEA,MAAM,sBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,YAAY;AAAA,EACnB,aAAa;AAAA,IACX,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,yBAAyB,QAAQ,kBAAkB;AAAA,EACjF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,IAC9E,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,qBAAqB;AAAA,IACpF,EAAE,QAAQ,KAAK,aAAa,kDAAkD,QAAQ,qBAAqB;AAAA,EAC7G;AACF;AAEA,MAAM,yBAA2C;AAAA,EAC/C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,YAAY;AAAA,EACnB,aAAa;AAAA,IACX,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,yBAAyB,QAAQ,kBAAkB;AAAA,EACjF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,IAC9E,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,qBAAqB;AAAA,IACpF,EAAE,QAAQ,KAAK,aAAa,kDAAkD,QAAQ,qBAAqB;AAAA,EAC7G;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AACF;",
6
- "names": ["orgs", "items", "orgListFilter", "hierarchy", "search", "slugByOrgId", "rows", "total", "pageSize", "page", "start", "paged", "recordIds", "tenantIdByRecord", "organizationIdByRecord", "cfByOrg", "totalPages"]
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { organizationCreateSchema, organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport {\n computeHierarchyForOrganizations,\n type ComputedHierarchy,\n type ComputedOrganizationNode,\n} from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport {\n getSelectedOrganizationFromRequest,\n resolveOrganizationScopeForRequest,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { organizationCrudEvents, organizationCrudIndexer } from '@open-mercato/core/modules/directory/commands/organizations'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport {\n directoryTag,\n directoryErrorSchema,\n directoryOkSchema,\n organizationListResponseSchema,\n} from '../openapi'\nimport { resolveIsSuperAdmin, enforceTenantSelection } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\ntype CrudInput = Record<string, unknown>\nconst rawBodySchema = z.object({}).passthrough()\n\ntype TreeNode = {\n id: string\n name: string\n parentId: string | null\n tenantId: string | null\n depth: number\n ancestorIds: string[]\n childIds: string[]\n descendantIds: string[]\n isActive: boolean\n treePath: string | null\n pathLabel: string\n children: TreeNode[]\n}\n\nconst viewSchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n search: z.string().optional(),\n view: z.enum(['options', 'manage', 'tree']).default('options'),\n ids: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n includeInactive: z.enum(['true', 'false']).optional(),\n status: z.enum(['all', 'active', 'inactive']).optional(),\n})\n\nfunction parseIds(raw: string | null): string[] | null {\n if (!raw) return null\n const ids = raw.split(',').map((s) => s.trim()).filter(Boolean)\n return ids.length ? Array.from(new Set(ids)) : null\n}\n\nfunction stringId(value: unknown): string {\n return String(value)\n}\n\nfunction enforceTenantScope(\n authTenantId: string | null,\n requestedTenantId: string | null,\n isSuperAdmin: boolean,\n): string | null {\n if (isSuperAdmin) {\n return requestedTenantId || authTenantId || null\n }\n if (authTenantId && requestedTenantId && requestedTenantId !== authTenantId) {\n return null\n }\n return requestedTenantId || authTenantId\n}\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['directory.organizations.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n },\n orm: {\n entity: Organization,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: organizationCrudEvents,\n indexer: organizationCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.organizations.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.organizations.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.organizations.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const metadata = crud.metadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [] }, { status: 401 })\n\n const url = new URL(req.url)\n const parsed = viewSchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n view: url.searchParams.get('view') ?? undefined,\n ids: url.searchParams.get('ids') ?? undefined,\n tenantId: url.searchParams.get('tenantId') ?? undefined,\n includeInactive: url.searchParams.get('includeInactive') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [] }, { status: 400 })\n\n const query = parsed.data\n const ids = parseIds(query.ids ?? null)\n const requestedTenantRaw = query.tenantId ?? null\n const normalizedRequestedTenantId = requestedTenantRaw && requestedTenantRaw.toLowerCase() === 'all' ? null : requestedTenantRaw\n const authTenantId = auth.tenantId ?? null\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = (container.resolve('em') as EntityManager)\n const allowAllTenants = isSuperAdmin && !normalizedRequestedTenantId && query.view === 'manage'\n let tenantId = allowAllTenants ? null : enforceTenantScope(authTenantId, normalizedRequestedTenantId, isSuperAdmin)\n const status = query.status ?? 'all'\n const includeInactive = parseBooleanToken(query.includeInactive) === true || status !== 'active'\n\n if (!allowAllTenants && !tenantId && !authTenantId && ids?.length) {\n let scopedIds = ids\n if (!isSuperAdmin) {\n let allowedIds: string[] | null = null\n try {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n allowedIds = Array.isArray(scope.allowedIds) ? scope.allowedIds : null\n } catch {}\n const allowedSet = allowedIds ? new Set(allowedIds) : new Set<string>()\n scopedIds = ids.filter((id) => allowedSet.has(id))\n }\n if (scopedIds.length) {\n const scopedOrgs: Organization[] = await findWithDecryption(\n em,\n Organization,\n { id: { $in: scopedIds }, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: null },\n )\n const tenantCandidates = new Set<string>()\n for (const org of scopedOrgs) {\n const orgTenantId = org.tenant?.id ? stringId(org.tenant.id) : ''\n if (orgTenantId) tenantCandidates.add(orgTenantId)\n }\n if (tenantCandidates.size === 1) {\n const candidateTenantId = Array.from(tenantCandidates)[0] ?? null\n try {\n tenantId = await enforceTenantSelection({ auth, container }, candidateTenantId)\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n throw error\n }\n } else if (tenantCandidates.size > 1) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n const candidateOrgIds = new Set<string>()\n const cookieOrgId = getSelectedOrganizationFromRequest(req)\n const effectiveCookieOrgId = cookieOrgId && !isAllOrganizationsSelection(cookieOrgId) ? cookieOrgId : null\n if (effectiveCookieOrgId) candidateOrgIds.add(effectiveCookieOrgId)\n if (auth.orgId) candidateOrgIds.add(auth.orgId)\n\n try {\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: effectiveCookieOrgId ?? undefined,\n })\n if (scope.selectedId) candidateOrgIds.add(scope.selectedId)\n if (Array.isArray(scope.filterIds) && scope.filterIds.length) {\n candidateOrgIds.add(scope.filterIds[0]!)\n }\n if (Array.isArray(scope.allowedIds) && scope.allowedIds.length) {\n candidateOrgIds.add(scope.allowedIds[0]!)\n }\n } catch {}\n\n for (const orgId of candidateOrgIds) {\n if (!orgId) continue\n const org = await findOneWithDecryption(\n em,\n Organization,\n { id: orgId, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: orgId },\n )\n if (org?.tenant && org.tenant.id) {\n tenantId = stringId(org.tenant.id)\n break\n }\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n if (query.view === 'options') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const where: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n if (status === 'active') where.isActive = true\n if (status === 'inactive') where.isActive = false\n if (status === 'all' && !includeInactive) where.isActive = true\n if (ids) where.id = { $in: ids }\n const orgs = await em.find(Organization, where, { orderBy: { name: 'ASC' } })\n const items = orgs.map((org) => ({\n id: stringId(org.id),\n name: org.name,\n logoUrl: org.logoUrl ?? null,\n parentId: org.parentId ?? null,\n tenantId: tenantId,\n isActive: !!org.isActive,\n depth: org.depth ?? 0,\n treePath: org.treePath ?? stringId(org.id),\n }))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items })\n }\n\n if (query.view === 'tree') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n const nodeMap = new Map<string, { node: ComputedOrganizationNode; children: TreeNode[] }>()\n const roots: TreeNode[] = []\n for (const node of hierarchy.ordered) {\n const treeNode: TreeNode = {\n id: node.id,\n name: node.name,\n parentId: node.parentId,\n tenantId: node.tenantId,\n depth: node.depth,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n isActive: node.isActive,\n treePath: node.treePath,\n pathLabel: node.pathLabel,\n children: [],\n }\n nodeMap.set(node.id, { node, children: treeNode.children })\n if (node.parentId && nodeMap.has(node.parentId)) {\n const parentEntry = nodeMap.get(node.parentId)!\n parentEntry.children.push(treeNode)\n } else {\n roots.push(treeNode)\n }\n }\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: roots,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n })\n return NextResponse.json({ items: roots })\n }\n\n if (query.view !== 'manage') {\n return NextResponse.json({ items: [] }, { status: 400 })\n }\n\n if (allowAllTenants) {\n // Multi-tenant aggregate view for super administrators\n const search = (query.search || '').trim().toLowerCase()\n const allOrgs = await findWithDecryption(\n em,\n Organization,\n { deletedAt: null },\n { orderBy: { name: 'ASC' }, populate: ['tenant'] },\n { tenantId: null, organizationId: null },\n )\n const byTenant = new Map<string, Organization[]>()\n for (const org of allOrgs) {\n const tenantEntity = org.tenant\n const tid = tenantEntity ? stringId(tenantEntity.id) : null\n if (!tid) continue\n if (!byTenant.has(tid)) byTenant.set(tid, [])\n byTenant.get(tid)!.push(org)\n }\n\n const slugByOrgId = new Map<string, string | null>()\n const logoUrlByOrgId = new Map<string, string | null>()\n for (const org of allOrgs) {\n slugByOrgId.set(String(org.id), org.slug ?? null)\n logoUrlByOrgId.set(String(org.id), org.logoUrl ?? null)\n }\n\n const tenantIds = Array.from(byTenant.keys())\n const tenants = tenantIds.length\n ? await em.find(Tenant, { id: { $in: tenantIds as unknown as string[] } })\n : []\n const tenantNameMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tid = stringId(tenant.id)\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : tid\n acc[tid] = name\n return acc\n }, {})\n\n const hierarchies = new Map<string, ComputedHierarchy>()\n const orderedNodes: Array<{ tenantId: string; node: ComputedOrganizationNode }> = []\n for (const [tid, orgs] of byTenant.entries()) {\n const hierarchy = computeHierarchyForOrganizations(orgs, tid)\n hierarchies.set(tid, hierarchy)\n for (const node of hierarchy.ordered) {\n orderedNodes.push({ tenantId: tid, node })\n }\n }\n\n let rows = orderedNodes\n if (query.status === 'active') {\n rows = rows.filter((entry) => entry.node.isActive)\n } else if (query.status === 'inactive') {\n rows = rows.filter((entry) => !entry.node.isActive)\n }\n\n if (search) {\n rows = rows.filter(({ node }) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter(({ node }) => idSet.has(node.id))\n }\n\n rows.sort((a, b) => {\n const nameA = tenantNameMap[a.tenantId] ?? a.tenantId\n const nameB = tenantNameMap[b.tenantId] ?? b.tenantId\n const tenantCompare = nameA.localeCompare(nameB)\n if (tenantCompare !== 0) return tenantCompare\n return a.node.pathLabel.localeCompare(b.node.pathLabel)\n })\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const entry of paged) {\n const recordId = String(entry.node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = entry.tenantId\n organizationIdByRecord[recordId] = recordId\n }\n const tenantFallbacks = Array.from(new Set(recordIds.map((id) => tenantIdByRecord[id]).filter((value): value is string => typeof value === 'string' && value.length > 0)))\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks,\n })\n : {}\n const items = paged.map(({ tenantId: tid, node }) => {\n const hierarchy = hierarchies.get(tid)\n const parentName = node.parentId && hierarchy ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n slug: slugByOrgId.get(recordId) ?? null,\n logoUrl: logoUrlByOrgId.get(recordId) ?? null,\n tenantId: tid,\n tenantName: tenantNameMap[tid] ?? tid,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId: null,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n }\n\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n const slugByOrgId = new Map<string, string | null>()\n const logoUrlByOrgId = new Map<string, string | null>()\n const updatedAtByOrgId = new Map<string, string | null>()\n for (const org of orgs) {\n slugByOrgId.set(String(org.id), org.slug ?? null)\n logoUrlByOrgId.set(String(org.id), org.logoUrl ?? null)\n updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null)\n }\n\n // Manage view: paginated flat list for a single tenant\n const search = (query.search || '').trim().toLowerCase()\n let rows = hierarchy.ordered\n if (status === 'active') {\n rows = rows.filter((node) => node.isActive)\n } else if (status === 'inactive') {\n rows = rows.filter((node) => !node.isActive)\n }\n\n if (search) {\n rows = rows.filter((node) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter((node) => idSet.has(node.id))\n }\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const node of paged) {\n const recordId = String(node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = node.tenantId ? String(node.tenantId) : null\n organizationIdByRecord[recordId] = recordId\n }\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: tenantId ? [tenantId] : [],\n })\n : {}\n let tenantName: string | null = null\n if (isSuperAdmin && tenantId) {\n const tenant = await em.findOne(Tenant, { id: tenantId })\n if (tenant) {\n const display = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : stringId(tenant.id)\n tenantName = display\n }\n }\n const items = paged.map((node) => {\n const parentName = node.parentId ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n slug: slugByOrgId.get(recordId) ?? null,\n logoUrl: logoUrlByOrgId.get(recordId) ?? null,\n updatedAt: updatedAtByOrgId.get(recordId) ?? null,\n tenantId: node.tenantId,\n tenantName,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst organizationCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationsGetDoc: OpenApiMethodDoc = {\n summary: 'List organizations',\n description: 'Returns organizations using options, tree, or paginated manage view depending on the `view` parameter.',\n tags: [directoryTag],\n query: viewSchema,\n responses: [\n { status: 200, description: 'Organization data for the requested view.', schema: organizationListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query or tenant scope', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPostDoc: OpenApiMethodDoc = {\n summary: 'Create organization',\n description: 'Creates a new organization within a tenant and optionally assigns hierarchy relationships.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationCreateSchema,\n description: 'Organization attributes and optional hierarchy configuration.',\n },\n responses: [\n { status: 201, description: 'Organization created.', schema: organizationCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPutDoc: OpenApiMethodDoc = {\n summary: 'Update organization',\n description: 'Updates organization details and hierarchy assignments.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationUpdateSchema,\n description: 'Organization identifier followed by fields to update.',\n },\n responses: [\n { status: 200, description: 'Organization updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete organization',\n description: 'Soft deletes an organization identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationDeleteRequestSchema,\n description: 'Identifier of the organization to delete.',\n },\n responses: [\n { status: 200, description: 'Organization deleted.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage organizations',\n methods: {\n GET: organizationsGetDoc,\n POST: organizationsPostDoc,\n PUT: organizationsPutDoc,\n DELETE: organizationsDeleteDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["orgs", "items", "orgListFilter", "hierarchy", "search", "slugByOrgId", "logoUrlByOrgId", "rows", "total", "pageSize", "page", "start", "paged", "recordIds", "tenantIdByRecord", "organizationIdByRecord", "cfByOrg", "totalPages"]
7
7
  }
@@ -0,0 +1,214 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import { ImagePlus, Loader2, RotateCcw, Save } from "lucide-react";
6
+ import { Page, PageBody, PageHeader } from "@open-mercato/ui/backend/Page";
7
+ import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
8
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
9
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
10
+ import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
11
+ import { Button } from "@open-mercato/ui/primitives/button";
12
+ import { Input } from "@open-mercato/ui/primitives/input";
13
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
14
+ const BRANDING_API = "/api/directory/organization-branding";
15
+ const BRANDING_ENTITY_ID = "directory.organization";
16
+ function OrganizationBrandingPage() {
17
+ const t = useT();
18
+ const queryClient = useQueryClient();
19
+ const [logoUrl, setLogoUrl] = React.useState("");
20
+ const [selectedFile, setSelectedFile] = React.useState(null);
21
+ const [filePreviewUrl, setFilePreviewUrl] = React.useState(null);
22
+ const [saving, setSaving] = React.useState(false);
23
+ const fileInputRef = React.useRef(null);
24
+ const { runMutation } = useGuardedMutation({
25
+ contextId: "directory.organization-branding",
26
+ blockedMessage: t("directory.branding.errors.blocked", "Branding save was blocked.")
27
+ });
28
+ const { data, isLoading, error } = useQuery({
29
+ queryKey: ["directory-organization-branding"],
30
+ queryFn: () => readApiResultOrThrow(
31
+ BRANDING_API,
32
+ void 0,
33
+ { errorMessage: t("directory.branding.errors.load", "Failed to load organization branding") }
34
+ )
35
+ });
36
+ React.useEffect(() => {
37
+ setLogoUrl(data?.logoUrl ?? "");
38
+ setSelectedFile(null);
39
+ }, [data?.logoUrl]);
40
+ React.useEffect(() => {
41
+ if (!selectedFile || typeof URL === "undefined") {
42
+ setFilePreviewUrl(null);
43
+ return;
44
+ }
45
+ const nextPreviewUrl = URL.createObjectURL(selectedFile);
46
+ setFilePreviewUrl(nextPreviewUrl);
47
+ return () => URL.revokeObjectURL(nextPreviewUrl);
48
+ }, [selectedFile]);
49
+ const currentPreviewUrl = filePreviewUrl ?? logoUrl;
50
+ const uploadLogo = React.useCallback(async (organizationId) => {
51
+ if (!selectedFile) return null;
52
+ const form = new FormData();
53
+ form.set("entityId", BRANDING_ENTITY_ID);
54
+ form.set("recordId", organizationId);
55
+ form.set("file", selectedFile);
56
+ form.set("tags", JSON.stringify(["organization-logo"]));
57
+ const upload = await readApiResultOrThrow(
58
+ "/api/attachments",
59
+ {
60
+ method: "POST",
61
+ body: form
62
+ },
63
+ { errorMessage: t("directory.branding.errors.upload", "Failed to upload logo") }
64
+ );
65
+ return upload?.item.thumbnailUrl ?? upload?.item.url ?? null;
66
+ }, [selectedFile, t]);
67
+ const saveBranding = React.useCallback(async (nextLogoUrl, options) => {
68
+ if (!data) return;
69
+ const shouldUpload = Boolean(selectedFile && !options?.skipUpload);
70
+ setSaving(true);
71
+ try {
72
+ await runMutation({
73
+ operation: async () => {
74
+ const uploadedLogoUrl = shouldUpload ? await uploadLogo(data.organizationId) : null;
75
+ const resolvedLogoUrl = uploadedLogoUrl ?? nextLogoUrl ?? logoUrl.trim();
76
+ const response = await apiCallOrThrow(
77
+ BRANDING_API,
78
+ {
79
+ method: "PUT",
80
+ headers: { "content-type": "application/json" },
81
+ body: JSON.stringify({ logoUrl: resolvedLogoUrl || null })
82
+ },
83
+ { errorMessage: t("directory.branding.errors.save", "Failed to update organization branding") }
84
+ );
85
+ return response.result;
86
+ },
87
+ context: {
88
+ entityId: BRANDING_ENTITY_ID,
89
+ recordId: data.organizationId,
90
+ operation: "update-branding"
91
+ },
92
+ mutationPayload: {
93
+ organizationId: data.organizationId,
94
+ logoUrl: (nextLogoUrl ?? logoUrl.trim()) || null,
95
+ hasUpload: shouldUpload
96
+ }
97
+ });
98
+ await queryClient.invalidateQueries({ queryKey: ["directory-organization-branding"] });
99
+ window.dispatchEvent(new Event("om:refresh-sidebar"));
100
+ setSelectedFile(null);
101
+ if (fileInputRef.current) fileInputRef.current.value = "";
102
+ flash(t("directory.branding.flash.saved", "Organization branding updated"), "success");
103
+ } catch (err) {
104
+ const fallback = t("directory.branding.errors.save", "Failed to update organization branding");
105
+ const message = err instanceof Error ? err.message : fallback;
106
+ flash(message, "error");
107
+ } finally {
108
+ setSaving(false);
109
+ }
110
+ }, [data, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo]);
111
+ const handleSubmit = React.useCallback((event) => {
112
+ event.preventDefault();
113
+ void saveBranding();
114
+ }, [saveBranding]);
115
+ if (isLoading) {
116
+ return /* @__PURE__ */ jsx(LoadingMessage, { label: t("directory.branding.loading", "Loading organization branding...") });
117
+ }
118
+ if (error || !data) {
119
+ return /* @__PURE__ */ jsx(
120
+ ErrorMessage,
121
+ {
122
+ label: t("directory.branding.errors.load", "Failed to load organization branding"),
123
+ description: error instanceof Error ? error.message : void 0
124
+ }
125
+ );
126
+ }
127
+ return /* @__PURE__ */ jsxs(Page, { children: [
128
+ /* @__PURE__ */ jsx(
129
+ PageHeader,
130
+ {
131
+ title: t("directory.branding.title", "Organization branding"),
132
+ description: t(
133
+ "directory.branding.description",
134
+ "Set the logo used in the backend sidebar for the currently selected organization."
135
+ )
136
+ }
137
+ ),
138
+ /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx("form", { className: "space-y-5", onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs("div", { className: "grid gap-5 lg:grid-cols-[260px_1fr]", children: [
139
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
140
+ /* @__PURE__ */ jsx("div", { className: "flex aspect-square w-full max-w-[220px] items-center justify-center overflow-hidden rounded-lg border bg-muted/30", children: currentPreviewUrl ? /* @__PURE__ */ jsx(
141
+ "img",
142
+ {
143
+ src: currentPreviewUrl,
144
+ alt: t("directory.branding.previewAlt", "{{name}} logo preview", { name: data.organizationName }),
145
+ className: "h-full w-full object-contain"
146
+ }
147
+ ) : /* @__PURE__ */ jsx(ImagePlus, { className: "size-10 text-muted-foreground", "aria-hidden": true }) }),
148
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: data.organizationName }),
149
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("directory.branding.currentScope", "Current organization") })
150
+ ] }),
151
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
152
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
153
+ /* @__PURE__ */ jsx("label", { htmlFor: "organization-logo-file", className: "text-sm font-medium", children: t("directory.branding.file.label", "Upload logo") }),
154
+ /* @__PURE__ */ jsx(
155
+ Input,
156
+ {
157
+ ref: fileInputRef,
158
+ id: "organization-logo-file",
159
+ type: "file",
160
+ accept: "image/png,image/jpeg,image/webp,image/svg+xml",
161
+ onChange: (event) => {
162
+ const file = event.currentTarget.files?.[0];
163
+ if (!file) return;
164
+ setSelectedFile(file);
165
+ }
166
+ }
167
+ ),
168
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("directory.branding.file.hint", "PNG, JPG, WebP, or SVG works best. Uploaded files are stored as organization attachments.") })
169
+ ] }),
170
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
171
+ /* @__PURE__ */ jsx("label", { htmlFor: "organization-logo-url", className: "text-sm font-medium", children: t("directory.branding.url.label", "Logo URL") }),
172
+ /* @__PURE__ */ jsx(
173
+ Input,
174
+ {
175
+ id: "organization-logo-url",
176
+ value: logoUrl,
177
+ onChange: (event) => setLogoUrl(event.currentTarget.value),
178
+ placeholder: t("directory.branding.url.placeholder", "https://example.com/logo.svg")
179
+ }
180
+ ),
181
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("directory.branding.url.hint", "Use an external image URL or leave empty to fall back to the default Open Mercato logo.") })
182
+ ] }),
183
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
184
+ /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: saving, children: [
185
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Save, { className: "mr-2 size-4", "aria-hidden": true }),
186
+ t("directory.branding.actions.save", "Save branding")
187
+ ] }),
188
+ /* @__PURE__ */ jsxs(
189
+ Button,
190
+ {
191
+ type: "button",
192
+ variant: "outline",
193
+ disabled: saving,
194
+ onClick: () => {
195
+ setSelectedFile(null);
196
+ if (fileInputRef.current) fileInputRef.current.value = "";
197
+ setLogoUrl("");
198
+ void saveBranding("", { skipUpload: true });
199
+ },
200
+ children: [
201
+ /* @__PURE__ */ jsx(RotateCcw, { className: "mr-2 size-4", "aria-hidden": true }),
202
+ t("directory.branding.actions.reset", "Use default logo")
203
+ ]
204
+ }
205
+ )
206
+ ] })
207
+ ] })
208
+ ] }) }) })
209
+ ] });
210
+ }
211
+ export {
212
+ OrganizationBrandingPage as default
213
+ };
214
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/directory/backend/directory/branding/page.tsx"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { ImagePlus, Loader2, RotateCcw, Save } from 'lucide-react'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype BrandingPayload = {\n organizationId: string\n organizationName: string\n tenantId: string\n logoUrl: string | null\n}\n\ntype UploadPayload = {\n ok: true\n item: {\n id: string\n url: string\n thumbnailUrl?: string\n }\n}\n\nconst BRANDING_API = '/api/directory/organization-branding'\nconst BRANDING_ENTITY_ID = 'directory.organization'\n\nexport default function OrganizationBrandingPage() {\n const t = useT()\n const queryClient = useQueryClient()\n const [logoUrl, setLogoUrl] = React.useState('')\n const [selectedFile, setSelectedFile] = React.useState<File | null>(null)\n const [filePreviewUrl, setFilePreviewUrl] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const fileInputRef = React.useRef<HTMLInputElement | null>(null)\n const { runMutation } = useGuardedMutation({\n contextId: 'directory.organization-branding',\n blockedMessage: t('directory.branding.errors.blocked', 'Branding save was blocked.'),\n })\n\n const { data, isLoading, error } = useQuery<BrandingPayload>({\n queryKey: ['directory-organization-branding'],\n queryFn: () => readApiResultOrThrow<BrandingPayload>(\n BRANDING_API,\n undefined,\n { errorMessage: t('directory.branding.errors.load', 'Failed to load organization branding') },\n ),\n })\n\n React.useEffect(() => {\n setLogoUrl(data?.logoUrl ?? '')\n setSelectedFile(null)\n }, [data?.logoUrl])\n\n React.useEffect(() => {\n if (!selectedFile || typeof URL === 'undefined') {\n setFilePreviewUrl(null)\n return\n }\n const nextPreviewUrl = URL.createObjectURL(selectedFile)\n setFilePreviewUrl(nextPreviewUrl)\n return () => URL.revokeObjectURL(nextPreviewUrl)\n }, [selectedFile])\n\n const currentPreviewUrl = filePreviewUrl ?? logoUrl\n\n const uploadLogo = React.useCallback(async (organizationId: string): Promise<string | null> => {\n if (!selectedFile) return null\n const form = new FormData()\n form.set('entityId', BRANDING_ENTITY_ID)\n form.set('recordId', organizationId)\n form.set('file', selectedFile)\n form.set('tags', JSON.stringify(['organization-logo']))\n\n const upload = await readApiResultOrThrow<UploadPayload>(\n '/api/attachments',\n {\n method: 'POST',\n body: form,\n },\n { errorMessage: t('directory.branding.errors.upload', 'Failed to upload logo') },\n )\n return upload?.item.thumbnailUrl ?? upload?.item.url ?? null\n }, [selectedFile, t])\n\n const saveBranding = React.useCallback(async (nextLogoUrl?: string, options?: { skipUpload?: boolean }) => {\n if (!data) return\n const shouldUpload = Boolean(selectedFile && !options?.skipUpload)\n setSaving(true)\n try {\n await runMutation({\n operation: async () => {\n const uploadedLogoUrl = shouldUpload ? await uploadLogo(data.organizationId) : null\n const resolvedLogoUrl = uploadedLogoUrl ?? nextLogoUrl ?? logoUrl.trim()\n // optimistic-lock-exempt: selected organization branding uses a scoped command endpoint without an exposed updatedAt token.\n const response = await apiCallOrThrow<BrandingPayload>(\n BRANDING_API,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ logoUrl: resolvedLogoUrl || null }),\n },\n { errorMessage: t('directory.branding.errors.save', 'Failed to update organization branding') },\n )\n return response.result\n },\n context: {\n entityId: BRANDING_ENTITY_ID,\n recordId: data.organizationId,\n operation: 'update-branding',\n },\n mutationPayload: {\n organizationId: data.organizationId,\n logoUrl: (nextLogoUrl ?? logoUrl.trim()) || null,\n hasUpload: shouldUpload,\n },\n })\n await queryClient.invalidateQueries({ queryKey: ['directory-organization-branding'] })\n window.dispatchEvent(new Event('om:refresh-sidebar'))\n setSelectedFile(null)\n if (fileInputRef.current) fileInputRef.current.value = ''\n flash(t('directory.branding.flash.saved', 'Organization branding updated'), 'success')\n } catch (err: unknown) {\n const fallback = t('directory.branding.errors.save', 'Failed to update organization branding')\n const message = err instanceof Error ? err.message : fallback\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [data, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo])\n\n const handleSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n void saveBranding()\n }, [saveBranding])\n\n if (isLoading) {\n return <LoadingMessage label={t('directory.branding.loading', 'Loading organization branding...')} />\n }\n\n if (error || !data) {\n return (\n <ErrorMessage\n label={t('directory.branding.errors.load', 'Failed to load organization branding')}\n description={error instanceof Error ? error.message : undefined}\n />\n )\n }\n\n return (\n <Page>\n <PageHeader\n title={t('directory.branding.title', 'Organization branding')}\n description={t(\n 'directory.branding.description',\n 'Set the logo used in the backend sidebar for the currently selected organization.',\n )}\n />\n <PageBody>\n <form className=\"space-y-5\" onSubmit={handleSubmit}>\n <div className=\"grid gap-5 lg:grid-cols-[260px_1fr]\">\n <div className=\"space-y-3\">\n <div className=\"flex aspect-square w-full max-w-[220px] items-center justify-center overflow-hidden rounded-lg border bg-muted/30\">\n {currentPreviewUrl ? (\n <img\n src={currentPreviewUrl}\n alt={t('directory.branding.previewAlt', '{{name}} logo preview', { name: data.organizationName })}\n className=\"h-full w-full object-contain\"\n />\n ) : (\n <ImagePlus className=\"size-10 text-muted-foreground\" aria-hidden />\n )}\n </div>\n <p className=\"text-sm font-medium text-foreground\">{data.organizationName}</p>\n <p className=\"text-xs text-muted-foreground\">\n {t('directory.branding.currentScope', 'Current organization')}\n </p>\n </div>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label htmlFor=\"organization-logo-file\" className=\"text-sm font-medium\">\n {t('directory.branding.file.label', 'Upload logo')}\n </label>\n <Input\n ref={fileInputRef}\n id=\"organization-logo-file\"\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp,image/svg+xml\"\n onChange={(event) => {\n const file = event.currentTarget.files?.[0]\n if (!file) return\n setSelectedFile(file)\n }}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('directory.branding.file.hint', 'PNG, JPG, WebP, or SVG works best. Uploaded files are stored as organization attachments.')}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label htmlFor=\"organization-logo-url\" className=\"text-sm font-medium\">\n {t('directory.branding.url.label', 'Logo URL')}\n </label>\n <Input\n id=\"organization-logo-url\"\n value={logoUrl}\n onChange={(event) => setLogoUrl(event.currentTarget.value)}\n placeholder={t('directory.branding.url.placeholder', 'https://example.com/logo.svg')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('directory.branding.url.hint', 'Use an external image URL or leave empty to fall back to the default Open Mercato logo.')}\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button type=\"submit\" disabled={saving}>\n {saving ? <Loader2 className=\"mr-2 size-4 animate-spin\" aria-hidden /> : <Save className=\"mr-2 size-4\" aria-hidden />}\n {t('directory.branding.actions.save', 'Save branding')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={saving}\n onClick={() => {\n setSelectedFile(null)\n if (fileInputRef.current) fileInputRef.current.value = ''\n setLogoUrl('')\n void saveBranding('', { skipUpload: true })\n }}\n >\n <RotateCcw className=\"mr-2 size-4\" aria-hidden />\n {t('directory.branding.actions.reset', 'Use default logo')}\n </Button>\n </div>\n </div>\n </div>\n </form>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ const brandingIcon = React.createElement(
3
+ "svg",
4
+ { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
5
+ React.createElement("rect", { x: 3, y: 5, width: 18, height: 14, rx: 2 }),
6
+ React.createElement("circle", { cx: 8, cy: 10, r: 1.5 }),
7
+ React.createElement("path", { d: "m21 15-5-5L5 21" })
8
+ );
9
+ const metadata = {
10
+ requireAuth: true,
11
+ requireFeatures: ["directory.organizations.manage"],
12
+ pageTitle: "Organization branding",
13
+ pageTitleKey: "directory.branding.nav",
14
+ pageGroup: "Directory",
15
+ pageGroupKey: "settings.sections.directory",
16
+ pageOrder: 0,
17
+ icon: brandingIcon,
18
+ pageContext: "settings",
19
+ breadcrumb: [{ label: "Organization branding", labelKey: "directory.branding.nav" }]
20
+ };
21
+ var page_meta_default = metadata;
22
+ export {
23
+ page_meta_default as default,
24
+ metadata
25
+ };
26
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/directory/backend/directory/branding/page.meta.ts"],
4
+ "sourcesContent": ["import React from 'react'\n\nconst brandingIcon = React.createElement(\n 'svg',\n { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },\n React.createElement('rect', { x: 3, y: 5, width: 18, height: 14, rx: 2 }),\n React.createElement('circle', { cx: 8, cy: 10, r: 1.5 }),\n React.createElement('path', { d: 'm21 15-5-5L5 21' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['directory.organizations.manage'],\n pageTitle: 'Organization branding',\n pageTitleKey: 'directory.branding.nav',\n pageGroup: 'Directory',\n pageGroupKey: 'settings.sections.directory',\n pageOrder: 0,\n icon: brandingIcon,\n pageContext: 'settings' as const,\n breadcrumb: [{ label: 'Organization branding', labelKey: 'directory.branding.nav' }],\n}\n\nexport default metadata\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,eAAe,MAAM;AAAA,EACzB;AAAA,EACA,EAAE,OAAO,IAAI,QAAQ,IAAI,SAAS,aAAa,MAAM,QAAQ,QAAQ,gBAAgB,aAAa,GAAG,eAAe,SAAS,gBAAgB,QAAQ;AAAA,EACrJ,MAAM,cAAc,QAAQ,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;AAAA,EACxE,MAAM,cAAc,UAAU,EAAE,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,CAAC;AAAA,EACvD,MAAM,cAAc,QAAQ,EAAE,GAAG,kBAAkB,CAAC;AACtD;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,gCAAgC;AAAA,EAClD,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY,CAAC,EAAE,OAAO,yBAAyB,UAAU,yBAAyB,CAAC;AACrF;AAEA,IAAO,oBAAQ;",
6
+ "names": []
7
+ }
@@ -82,6 +82,7 @@ function serializeOrganization(entity, custom) {
82
82
  tenantId: resolveTenantIdFromEntity(entity),
83
83
  name: entity.name,
84
84
  slug: entity.slug ?? null,
85
+ logoUrl: entity.logoUrl ?? null,
85
86
  isActive: !!entity.isActive,
86
87
  parentId: entity.parentId ?? null,
87
88
  ancestorIds: Array.isArray(entity.ancestorIds) ? [...entity.ancestorIds] : [],
@@ -101,6 +102,7 @@ function captureOrganizationSnapshots(entity, childParents, custom) {
101
102
  tenantId,
102
103
  name: entity.name,
103
104
  slug: entity.slug ?? null,
105
+ logoUrl: entity.logoUrl ?? null,
104
106
  isActive: !!entity.isActive,
105
107
  parentId: entity.parentId ?? null,
106
108
  childParents: (childParents ?? []).map((entry) => ({
@@ -236,6 +238,7 @@ const createOrganizationCommand = {
236
238
  tenant: tenantRef,
237
239
  name: parsed.name,
238
240
  slug,
241
+ logoUrl: parsed.logoUrl ?? null,
239
242
  isActive: parsed.isActive ?? true,
240
243
  parentId
241
244
  }
@@ -357,6 +360,7 @@ const createOrganizationCommand = {
357
360
  existing.deletedAt = null;
358
361
  existing.name = after.name;
359
362
  if (after.slug !== void 0) existing.slug = after.slug ?? null;
363
+ if (after.logoUrl !== void 0) existing.logoUrl = after.logoUrl ?? null;
360
364
  existing.isActive = after.isActive;
361
365
  existing.parentId = after.parentId;
362
366
  await em.flush();
@@ -368,6 +372,7 @@ const createOrganizationCommand = {
368
372
  id: after.id,
369
373
  name: after.name,
370
374
  slug: after.slug ?? null,
375
+ logoUrl: after.logoUrl ?? null,
371
376
  tenant: em.getReference(Tenant, tenantId),
372
377
  isActive: after.isActive,
373
378
  parentId: after.parentId
@@ -478,6 +483,7 @@ const updateOrganizationCommand = {
478
483
  apply: (entity) => {
479
484
  if (parsed.name !== void 0) entity.name = parsed.name;
480
485
  if (resolvedSlug !== void 0) entity.slug = resolvedSlug;
486
+ if (parsed.logoUrl !== void 0) entity.logoUrl = parsed.logoUrl ?? null;
481
487
  if (parsed.isActive !== void 0) entity.isActive = parsed.isActive;
482
488
  entity.parentId = parentId;
483
489
  }
@@ -539,7 +545,7 @@ const updateOrganizationCommand = {
539
545
  organizationId: String(result.id)
540
546
  });
541
547
  const after = serializeOrganization(result, custom);
542
- const changes = buildChanges(beforeRecord, after, ["name", "slug", "isActive", "parentId"]);
548
+ const changes = buildChanges(beforeRecord, after, ["name", "slug", "logoUrl", "isActive", "parentId"]);
543
549
  const customDiff = diffCustomFieldChanges(beforeRecord?.custom, custom);
544
550
  for (const [key, diff] of Object.entries(customDiff)) {
545
551
  changes[`cf_${key}`] = diff;
@@ -576,6 +582,7 @@ const updateOrganizationCommand = {
576
582
  apply: (entity) => {
577
583
  entity.name = before.name;
578
584
  if (before.slug !== void 0) entity.slug = before.slug;
585
+ if (before.logoUrl !== void 0) entity.logoUrl = before.logoUrl ?? null;
579
586
  entity.isActive = before.isActive;
580
587
  entity.parentId = before.parentId;
581
588
  }