@open-mercato/core 0.6.7-develop.6775.1.c2313bb8a3 → 0.6.7-develop.6784.1.f80b9afce5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +3 -3
- package/agentic/standalone-guide.md +13 -0
- package/dist/generated/entities/organization/index.js +2 -0
- package/dist/generated/entities/organization/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +1 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/cli.js +1 -1
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +4 -6
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +29 -44
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/product-seo/widget.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.js.map +2 -2
- package/dist/modules/communication_channels/lib/access-control.js +2 -2
- package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
- package/dist/modules/customer_accounts/api/invitations/accept.js +1 -2
- package/dist/modules/customer_accounts/api/invitations/accept.js.map +2 -2
- package/dist/modules/customer_accounts/api/login.js +4 -2
- package/dist/modules/customer_accounts/api/login.js.map +2 -2
- package/dist/modules/customer_accounts/api/magic-link/verify.js +4 -2
- package/dist/modules/customer_accounts/api/magic-link/verify.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/feature-check.js +5 -4
- package/dist/modules/customer_accounts/api/portal/feature-check.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/nav.js +6 -4
- package/dist/modules/customer_accounts/api/portal/nav.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/profile.js +5 -1
- package/dist/modules/customer_accounts/api/portal/profile.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js +3 -3
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuth.js +7 -2
- package/dist/modules/customer_accounts/lib/customerAuth.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +2 -1
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerRbacService.js +12 -3
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
- package/dist/modules/customer_accounts/setup.js +6 -0
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +1 -1
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/lib/visibilityFilter.js +6 -4
- package/dist/modules/customers/lib/visibilityFilter.js.map +2 -2
- package/dist/modules/dashboards/api/layout/[itemId]/route.js +6 -3
- package/dist/modules/dashboards/api/layout/[itemId]/route.js.map +2 -2
- package/dist/modules/dashboards/api/layout/route.js +22 -9
- package/dist/modules/dashboards/api/layout/route.js.map +2 -2
- package/dist/modules/dashboards/api/roles/widgets/route.js +9 -3
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +9 -3
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/catalog.js +6 -3
- package/dist/modules/dashboards/api/widgets/catalog.js.map +2 -2
- package/dist/modules/dashboards/lib/access.js +5 -3
- package/dist/modules/dashboards/lib/access.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +10 -3
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/api/organizations/route.js +7 -0
- package/dist/modules/directory/api/organizations/route.js.map +3 -3
- package/dist/modules/directory/backend/directory/branding/page.js +31 -8
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/commands/organizations.js +8 -1
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/directory/data/entities.js +4 -0
- package/dist/modules/directory/data/entities.js.map +2 -2
- package/dist/modules/directory/data/validators.js +2 -0
- package/dist/modules/directory/data/validators.js.map +2 -2
- package/dist/modules/directory/migrations/Migration20260626145500_directory_logo_preserve_aspect_ratio.js +13 -0
- package/dist/modules/directory/migrations/Migration20260626145500_directory_logo_preserve_aspect_ratio.js.map +7 -0
- package/dist/modules/entities/lib/entityAcl.js +47 -15
- package/dist/modules/entities/lib/entityAcl.js.map +3 -3
- package/dist/modules/inbox_ops/ai-tools.js +5 -2
- package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
- package/dist/modules/messages/lib/routeHelpers.js +1 -3
- package/dist/modules/messages/lib/routeHelpers.js.map +2 -2
- package/dist/modules/notifications/lib/notificationRecipients.js +6 -7
- package/dist/modules/notifications/lib/notificationRecipients.js.map +2 -2
- package/dist/modules/query_index/di.js +3 -0
- package/dist/modules/query_index/di.js.map +2 -2
- package/dist/modules/query_index/lib/coverage.js +2 -2
- package/dist/modules/query_index/lib/coverage.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +83 -17
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +13 -7
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/query_index/subscribers/coverage_refresh.js +4 -0
- package/dist/modules/query_index/subscribers/coverage_refresh.js.map +2 -2
- package/dist/modules/query_index/subscribers/delete_one.js +53 -1
- package/dist/modules/query_index/subscribers/delete_one.js.map +3 -3
- package/dist/modules/query_index/subscribers/upsert_one.js +60 -0
- package/dist/modules/query_index/subscribers/upsert_one.js.map +3 -3
- package/dist/modules/sales/commands/documents.js +17 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/staff/api/interceptors.js +5 -3
- package/dist/modules/staff/api/interceptors.js.map +2 -2
- package/dist/modules/sync_excel/api/upload/route.js +15 -2
- package/dist/modules/sync_excel/api/upload/route.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +7 -7
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/generated/entities/organization/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +7 -7
- package/src/modules/auth/AGENTS.md +5 -3
- package/src/modules/auth/cli.ts +1 -1
- package/src/modules/auth/lib/backendChrome.tsx +5 -11
- package/src/modules/auth/lib/setup-app.ts +1 -0
- package/src/modules/auth/services/rbacService.ts +41 -46
- package/src/modules/catalog/widgets/injection/product-seo/widget.ts +1 -1
- package/src/modules/communication_channels/lib/access-control.ts +3 -3
- package/src/modules/customer_accounts/AGENTS.md +3 -3
- package/src/modules/customer_accounts/api/invitations/accept.ts +1 -2
- package/src/modules/customer_accounts/api/login.ts +4 -2
- package/src/modules/customer_accounts/api/magic-link/verify.ts +4 -2
- package/src/modules/customer_accounts/api/portal/feature-check.ts +5 -7
- package/src/modules/customer_accounts/api/portal/nav.ts +5 -3
- package/src/modules/customer_accounts/api/portal/profile.ts +5 -1
- package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +3 -3
- package/src/modules/customer_accounts/lib/customerAuth.ts +8 -2
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +1 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +16 -3
- package/src/modules/customer_accounts/setup.ts +7 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +1 -3
- package/src/modules/customers/lib/visibilityFilter.ts +7 -5
- package/src/modules/dashboards/api/layout/[itemId]/route.ts +6 -3
- package/src/modules/dashboards/api/layout/route.ts +22 -9
- package/src/modules/dashboards/api/roles/widgets/route.ts +9 -3
- package/src/modules/dashboards/api/users/widgets/route.ts +9 -3
- package/src/modules/dashboards/api/widgets/catalog.ts +6 -3
- package/src/modules/dashboards/lib/access.ts +5 -3
- package/src/modules/directory/api/organization-branding/route.ts +12 -1
- package/src/modules/directory/api/organizations/route.ts +7 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +34 -9
- package/src/modules/directory/commands/organizations.ts +9 -1
- package/src/modules/directory/data/entities.ts +3 -0
- package/src/modules/directory/data/validators.ts +2 -0
- package/src/modules/directory/i18n/de.json +3 -1
- package/src/modules/directory/i18n/en.json +3 -1
- package/src/modules/directory/i18n/es.json +3 -1
- package/src/modules/directory/i18n/pl.json +3 -1
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +18 -1
- package/src/modules/directory/migrations/Migration20260626145500_directory_logo_preserve_aspect_ratio.ts +13 -0
- package/src/modules/entities/lib/entityAcl.ts +47 -18
- package/src/modules/inbox_ops/ai-tools.ts +5 -3
- package/src/modules/messages/lib/routeHelpers.ts +4 -6
- package/src/modules/notifications/lib/notificationRecipients.ts +6 -8
- package/src/modules/query_index/di.ts +6 -0
- package/src/modules/query_index/lib/coverage.ts +9 -6
- package/src/modules/query_index/lib/indexer.ts +105 -19
- package/src/modules/query_index/lib/search-tokens.ts +19 -8
- package/src/modules/query_index/subscribers/coverage_refresh.ts +4 -0
- package/src/modules/query_index/subscribers/delete_one.ts +65 -1
- package/src/modules/query_index/subscribers/upsert_one.ts +68 -1
- package/src/modules/sales/commands/documents.ts +27 -0
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
- package/src/modules/staff/api/interceptors.ts +6 -4
- package/src/modules/sync_excel/api/upload/route.ts +15 -2
- package/src/modules/workflows/lib/activity-executor.ts +12 -10
|
@@ -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(100).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 updatedAtByOrgId = 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 updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : 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 updatedAt: updatedAtByOrgId.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", "updatedAtByOrgId", "logoUrlByOrgId", "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(100).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 logoPreserveAspectRatio: !!org.logoPreserveAspectRatio,\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 updatedAtByOrgId = new Map<string, string | null>()\n const logoUrlByOrgId = new Map<string, string | null>()\n const logoPreserveAspectRatioByOrgId = new Map<string, boolean>()\n for (const org of allOrgs) {\n slugByOrgId.set(String(org.id), org.slug ?? null)\n updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null)\n logoUrlByOrgId.set(String(org.id), org.logoUrl ?? null)\n logoPreserveAspectRatioByOrgId.set(String(org.id), !!org.logoPreserveAspectRatio)\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 updatedAt: updatedAtByOrgId.get(recordId) ?? null,\n logoUrl: logoUrlByOrgId.get(recordId) ?? null,\n logoPreserveAspectRatio: logoPreserveAspectRatioByOrgId.get(recordId) ?? false,\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 logoPreserveAspectRatioByOrgId = new Map<string, boolean>()\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 logoPreserveAspectRatioByOrgId.set(String(org.id), !!org.logoPreserveAspectRatio)\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 logoPreserveAspectRatio: logoPreserveAspectRatioByOrgId.get(recordId) ?? false,\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", "updatedAtByOrgId", "logoUrlByOrgId", "logoPreserveAspectRatioByOrgId", "rows", "total", "pageSize", "page", "start", "paged", "recordIds", "tenantIdByRecord", "organizationIdByRecord", "cfByOrg", "totalPages"]
|
|
7
7
|
}
|
|
@@ -12,6 +12,7 @@ import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimi
|
|
|
12
12
|
import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
|
|
13
13
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
14
14
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
15
|
+
import { SwitchField } from "@open-mercato/ui/primitives/switch-field";
|
|
15
16
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
16
17
|
const BRANDING_API = "/api/directory/organization-branding";
|
|
17
18
|
const BRANDING_ENTITY_ID = "directory.organization";
|
|
@@ -19,6 +20,7 @@ function OrganizationBrandingPage() {
|
|
|
19
20
|
const t = useT();
|
|
20
21
|
const queryClient = useQueryClient();
|
|
21
22
|
const [logoUrl, setLogoUrl] = React.useState("");
|
|
23
|
+
const [logoPreserveAspectRatio, setLogoPreserveAspectRatio] = React.useState(false);
|
|
22
24
|
const [selectedFile, setSelectedFile] = React.useState(null);
|
|
23
25
|
const [filePreviewUrl, setFilePreviewUrl] = React.useState(null);
|
|
24
26
|
const [saving, setSaving] = React.useState(false);
|
|
@@ -37,8 +39,9 @@ function OrganizationBrandingPage() {
|
|
|
37
39
|
});
|
|
38
40
|
React.useEffect(() => {
|
|
39
41
|
setLogoUrl(data?.logoUrl ?? "");
|
|
42
|
+
setLogoPreserveAspectRatio(data?.logoPreserveAspectRatio ?? false);
|
|
40
43
|
setSelectedFile(null);
|
|
41
|
-
}, [data?.logoUrl]);
|
|
44
|
+
}, [data?.logoPreserveAspectRatio, data?.logoUrl]);
|
|
42
45
|
React.useEffect(() => {
|
|
43
46
|
if (!selectedFile || typeof URL === "undefined") {
|
|
44
47
|
setFilePreviewUrl(null);
|
|
@@ -64,11 +67,12 @@ function OrganizationBrandingPage() {
|
|
|
64
67
|
},
|
|
65
68
|
{ errorMessage: t("directory.branding.errors.upload", "Failed to upload logo") }
|
|
66
69
|
);
|
|
67
|
-
return upload?.item.
|
|
70
|
+
return upload?.item.url ?? upload?.item.thumbnailUrl ?? null;
|
|
68
71
|
}, [selectedFile, t]);
|
|
69
72
|
const saveBranding = React.useCallback(async (nextLogoUrl, options) => {
|
|
70
73
|
if (!data) return;
|
|
71
74
|
const shouldUpload = Boolean(selectedFile && !options?.skipUpload);
|
|
75
|
+
const resolvedLogoPreserveAspectRatio = options?.logoPreserveAspectRatio ?? logoPreserveAspectRatio;
|
|
72
76
|
setSaving(true);
|
|
73
77
|
try {
|
|
74
78
|
await runMutation({
|
|
@@ -82,7 +86,10 @@ function OrganizationBrandingPage() {
|
|
|
82
86
|
{
|
|
83
87
|
method: "PUT",
|
|
84
88
|
headers: { "content-type": "application/json" },
|
|
85
|
-
body: JSON.stringify({
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
logoUrl: resolvedLogoUrl || null,
|
|
91
|
+
logoPreserveAspectRatio: resolvedLogoPreserveAspectRatio
|
|
92
|
+
})
|
|
86
93
|
},
|
|
87
94
|
{ errorMessage: t("directory.branding.errors.save", "Failed to update organization branding") }
|
|
88
95
|
)
|
|
@@ -97,6 +104,7 @@ function OrganizationBrandingPage() {
|
|
|
97
104
|
mutationPayload: {
|
|
98
105
|
organizationId: data.organizationId,
|
|
99
106
|
logoUrl: (nextLogoUrl ?? logoUrl.trim()) || null,
|
|
107
|
+
logoPreserveAspectRatio: resolvedLogoPreserveAspectRatio,
|
|
100
108
|
hasUpload: shouldUpload
|
|
101
109
|
}
|
|
102
110
|
});
|
|
@@ -113,7 +121,7 @@ function OrganizationBrandingPage() {
|
|
|
113
121
|
} finally {
|
|
114
122
|
setSaving(false);
|
|
115
123
|
}
|
|
116
|
-
}, [data, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo]);
|
|
124
|
+
}, [data, logoPreserveAspectRatio, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo]);
|
|
117
125
|
const handleSubmit = React.useCallback((event) => {
|
|
118
126
|
event.preventDefault();
|
|
119
127
|
void saveBranding();
|
|
@@ -148,7 +156,7 @@ function OrganizationBrandingPage() {
|
|
|
148
156
|
{
|
|
149
157
|
src: currentPreviewUrl,
|
|
150
158
|
alt: t("directory.branding.previewAlt", "{{name}} logo preview", { name: data.organizationName }),
|
|
151
|
-
className:
|
|
159
|
+
className: `h-full w-full ${logoPreserveAspectRatio ? "object-contain" : "rounded-full object-cover"}`
|
|
152
160
|
}
|
|
153
161
|
) : /* @__PURE__ */ jsx(ImagePlus, { className: "size-10 text-muted-foreground", "aria-hidden": true }) }),
|
|
154
162
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: data.organizationName }),
|
|
@@ -163,7 +171,7 @@ function OrganizationBrandingPage() {
|
|
|
163
171
|
ref: fileInputRef,
|
|
164
172
|
id: "organization-logo-file",
|
|
165
173
|
type: "file",
|
|
166
|
-
accept: "image/png,image/jpeg,image/webp
|
|
174
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
167
175
|
onChange: (event) => {
|
|
168
176
|
const file = event.currentTarget.files?.[0];
|
|
169
177
|
if (!file) return;
|
|
@@ -171,7 +179,7 @@ function OrganizationBrandingPage() {
|
|
|
171
179
|
}
|
|
172
180
|
}
|
|
173
181
|
),
|
|
174
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("directory.branding.file.hint", "PNG, JPG,
|
|
182
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("directory.branding.file.hint", "PNG, JPG, or WebP works best. Uploaded files are stored as organization attachments.") })
|
|
175
183
|
] }),
|
|
176
184
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
177
185
|
/* @__PURE__ */ jsx("label", { htmlFor: "organization-logo-url", className: "text-sm font-medium", children: t("directory.branding.url.label", "Logo URL") }),
|
|
@@ -186,6 +194,20 @@ function OrganizationBrandingPage() {
|
|
|
186
194
|
),
|
|
187
195
|
/* @__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.") })
|
|
188
196
|
] }),
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
SwitchField,
|
|
199
|
+
{
|
|
200
|
+
id: "organization-logo-preserve-aspect-ratio",
|
|
201
|
+
label: t("directory.branding.aspectRatio.label", "Keep the aspect ratio"),
|
|
202
|
+
description: t(
|
|
203
|
+
"directory.branding.aspectRatio.description",
|
|
204
|
+
"When disabled, the sidebar crops the logo into the standard icon shape."
|
|
205
|
+
),
|
|
206
|
+
checked: logoPreserveAspectRatio,
|
|
207
|
+
disabled: saving,
|
|
208
|
+
onCheckedChange: setLogoPreserveAspectRatio
|
|
209
|
+
}
|
|
210
|
+
),
|
|
189
211
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
190
212
|
/* @__PURE__ */ jsxs(Button, { type: "submit", disabled: saving, children: [
|
|
191
213
|
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 }),
|
|
@@ -201,7 +223,8 @@ function OrganizationBrandingPage() {
|
|
|
201
223
|
setSelectedFile(null);
|
|
202
224
|
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
203
225
|
setLogoUrl("");
|
|
204
|
-
|
|
226
|
+
setLogoPreserveAspectRatio(false);
|
|
227
|
+
void saveBranding("", { skipUpload: true, logoPreserveAspectRatio: false });
|
|
205
228
|
},
|
|
206
229
|
children: [
|
|
207
230
|
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-2 size-4", "aria-hidden": true }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\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 updatedAt: 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 const response = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(data.updatedAt),\n () => 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 )\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 if (surfaceRecordConflict(err, t)) return\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": ";
|
|
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, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { SwitchField } from '@open-mercato/ui/primitives/switch-field'\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 logoPreserveAspectRatio: boolean\n updatedAt: 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 [logoPreserveAspectRatio, setLogoPreserveAspectRatio] = React.useState(false)\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 setLogoPreserveAspectRatio(data?.logoPreserveAspectRatio ?? false)\n setSelectedFile(null)\n }, [data?.logoPreserveAspectRatio, 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.url ?? upload?.item.thumbnailUrl ?? null\n }, [selectedFile, t])\n\n const saveBranding = React.useCallback(async (\n nextLogoUrl?: string,\n options?: { skipUpload?: boolean; logoPreserveAspectRatio?: boolean },\n ) => {\n if (!data) return\n const shouldUpload = Boolean(selectedFile && !options?.skipUpload)\n const resolvedLogoPreserveAspectRatio = options?.logoPreserveAspectRatio ?? logoPreserveAspectRatio\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 const response = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(data.updatedAt),\n () => apiCallOrThrow<BrandingPayload>(\n BRANDING_API,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n logoUrl: resolvedLogoUrl || null,\n logoPreserveAspectRatio: resolvedLogoPreserveAspectRatio,\n }),\n },\n { errorMessage: t('directory.branding.errors.save', 'Failed to update organization branding') },\n ),\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 logoPreserveAspectRatio: resolvedLogoPreserveAspectRatio,\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 if (surfaceRecordConflict(err, t)) return\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, logoPreserveAspectRatio, 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 ${logoPreserveAspectRatio ? 'object-contain' : 'rounded-full object-cover'}`}\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\"\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, or WebP 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 <SwitchField\n id=\"organization-logo-preserve-aspect-ratio\"\n label={t('directory.branding.aspectRatio.label', 'Keep the aspect ratio')}\n description={t(\n 'directory.branding.aspectRatio.description',\n 'When disabled, the sidebar crops the logo into the standard icon shape.',\n )}\n checked={logoPreserveAspectRatio}\n disabled={saving}\n onCheckedChange={setLogoPreserveAspectRatio}\n />\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 setLogoPreserveAspectRatio(false)\n void saveBranding('', { skipUpload: true, logoPreserveAspectRatio: false })\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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -83,6 +83,7 @@ function serializeOrganization(entity, custom) {
|
|
|
83
83
|
name: entity.name,
|
|
84
84
|
slug: entity.slug ?? null,
|
|
85
85
|
logoUrl: entity.logoUrl ?? null,
|
|
86
|
+
logoPreserveAspectRatio: !!entity.logoPreserveAspectRatio,
|
|
86
87
|
isActive: !!entity.isActive,
|
|
87
88
|
parentId: entity.parentId ?? null,
|
|
88
89
|
ancestorIds: Array.isArray(entity.ancestorIds) ? [...entity.ancestorIds] : [],
|
|
@@ -103,6 +104,7 @@ function captureOrganizationSnapshots(entity, childParents, custom) {
|
|
|
103
104
|
name: entity.name,
|
|
104
105
|
slug: entity.slug ?? null,
|
|
105
106
|
logoUrl: entity.logoUrl ?? null,
|
|
107
|
+
logoPreserveAspectRatio: !!entity.logoPreserveAspectRatio,
|
|
106
108
|
isActive: !!entity.isActive,
|
|
107
109
|
parentId: entity.parentId ?? null,
|
|
108
110
|
childParents: (childParents ?? []).map((entry) => ({
|
|
@@ -239,6 +241,7 @@ const createOrganizationCommand = {
|
|
|
239
241
|
name: parsed.name,
|
|
240
242
|
slug,
|
|
241
243
|
logoUrl: parsed.logoUrl ?? null,
|
|
244
|
+
logoPreserveAspectRatio: parsed.logoPreserveAspectRatio ?? false,
|
|
242
245
|
isActive: parsed.isActive ?? true,
|
|
243
246
|
parentId
|
|
244
247
|
}
|
|
@@ -361,6 +364,7 @@ const createOrganizationCommand = {
|
|
|
361
364
|
existing.name = after.name;
|
|
362
365
|
if (after.slug !== void 0) existing.slug = after.slug ?? null;
|
|
363
366
|
if (after.logoUrl !== void 0) existing.logoUrl = after.logoUrl ?? null;
|
|
367
|
+
if (after.logoPreserveAspectRatio !== void 0) existing.logoPreserveAspectRatio = !!after.logoPreserveAspectRatio;
|
|
364
368
|
existing.isActive = after.isActive;
|
|
365
369
|
existing.parentId = after.parentId;
|
|
366
370
|
await em.flush();
|
|
@@ -373,6 +377,7 @@ const createOrganizationCommand = {
|
|
|
373
377
|
name: after.name,
|
|
374
378
|
slug: after.slug ?? null,
|
|
375
379
|
logoUrl: after.logoUrl ?? null,
|
|
380
|
+
logoPreserveAspectRatio: after.logoPreserveAspectRatio ?? false,
|
|
376
381
|
tenant: em.getReference(Tenant, tenantId),
|
|
377
382
|
isActive: after.isActive,
|
|
378
383
|
parentId: after.parentId
|
|
@@ -484,6 +489,7 @@ const updateOrganizationCommand = {
|
|
|
484
489
|
if (parsed.name !== void 0) entity.name = parsed.name;
|
|
485
490
|
if (resolvedSlug !== void 0) entity.slug = resolvedSlug;
|
|
486
491
|
if (parsed.logoUrl !== void 0) entity.logoUrl = parsed.logoUrl ?? null;
|
|
492
|
+
if (parsed.logoPreserveAspectRatio !== void 0) entity.logoPreserveAspectRatio = parsed.logoPreserveAspectRatio;
|
|
487
493
|
if (parsed.isActive !== void 0) entity.isActive = parsed.isActive;
|
|
488
494
|
entity.parentId = parentId;
|
|
489
495
|
}
|
|
@@ -545,7 +551,7 @@ const updateOrganizationCommand = {
|
|
|
545
551
|
organizationId: String(result.id)
|
|
546
552
|
});
|
|
547
553
|
const after = serializeOrganization(result, custom);
|
|
548
|
-
const changes = buildChanges(beforeRecord, after, ["name", "slug", "logoUrl", "isActive", "parentId"]);
|
|
554
|
+
const changes = buildChanges(beforeRecord, after, ["name", "slug", "logoUrl", "logoPreserveAspectRatio", "isActive", "parentId"]);
|
|
549
555
|
const customDiff = diffCustomFieldChanges(beforeRecord?.custom, custom);
|
|
550
556
|
for (const [key, diff] of Object.entries(customDiff)) {
|
|
551
557
|
changes[`cf_${key}`] = diff;
|
|
@@ -583,6 +589,7 @@ const updateOrganizationCommand = {
|
|
|
583
589
|
entity.name = before.name;
|
|
584
590
|
if (before.slug !== void 0) entity.slug = before.slug;
|
|
585
591
|
if (before.logoUrl !== void 0) entity.logoUrl = before.logoUrl ?? null;
|
|
592
|
+
if (before.logoPreserveAspectRatio !== void 0) entity.logoPreserveAspectRatio = !!before.logoPreserveAspectRatio;
|
|
586
593
|
entity.isActive = before.isActive;
|
|
587
594
|
entity.parentId = before.parentId;
|
|
588
595
|
}
|