@open-mercato/core 0.6.2-develop.3446.1.bd060c6017 → 0.6.2-develop.3461.1.605f31c2c9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/users/route.js +3 -6
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +2 -1
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/commands/users.js +5 -8
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/entities.js.map +1 -1
- package/dist/modules/auth/lib/displayName.js +10 -0
- package/dist/modules/auth/lib/displayName.js.map +7 -0
- package/package.json +7 -7
- package/src/modules/auth/api/users/route.ts +3 -6
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -1
- package/src/modules/auth/backend/users/create/page.tsx +2 -1
- package/src/modules/auth/commands/users.ts +5 -8
- package/src/modules/auth/data/entities.ts +1 -1
- package/src/modules/auth/lib/displayName.ts +6 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -16,6 +16,7 @@ import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern
|
|
|
16
16
|
import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
|
|
17
17
|
import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
|
|
18
18
|
import { sql } from "kysely";
|
|
19
|
+
import { normalizeDisplayNameInput } from "@open-mercato/core/modules/auth/lib/displayName";
|
|
19
20
|
const querySchema = z.object({
|
|
20
21
|
id: z.string().uuid().optional(),
|
|
21
22
|
page: z.coerce.number().min(1).default(1),
|
|
@@ -28,12 +29,8 @@ const querySchema = z.object({
|
|
|
28
29
|
const rawBodySchema = z.object({}).passthrough();
|
|
29
30
|
const passwordSchema = buildPasswordSchema();
|
|
30
31
|
const displayNameSchema = z.preprocess(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const trimmed = value.trim();
|
|
34
|
-
return trimmed.length ? trimmed : void 0;
|
|
35
|
-
},
|
|
36
|
-
z.string().trim().min(1).max(120).optional()
|
|
32
|
+
normalizeDisplayNameInput,
|
|
33
|
+
z.string().trim().min(1).max(120).nullable().optional()
|
|
37
34
|
);
|
|
38
35
|
const userCreateSchema = z.object({
|
|
39
36
|
email: z.string().email(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/auth/api/users/route.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { User, Role, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { userCrudEvents, userCrudIndexer } from '@open-mercato/core/modules/auth/commands/users'\nimport { assertActorCanGrantRoleTokens } from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'\nimport { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'\nimport { sql } from 'kysely'\n\nconst querySchema = z.object({\n id: z.string().uuid().optional(),\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 name: z.string().optional(),\n organizationId: z.string().uuid().optional(),\n roleIds: z.array(z.string().uuid()).optional(),\n}).passthrough()\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst passwordSchema = buildPasswordSchema()\n\nconst displayNameSchema = z.preprocess(\n (value) => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n },\n z.string().trim().min(1).max(120).optional(),\n)\n\nconst userCreateSchema = z.object({\n email: z.string().email(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n sendInviteEmail: z.boolean().optional(),\n organizationId: z.string().uuid(),\n roles: z.array(z.string()).optional(),\n}).refine(\n (data) => data.password || data.sendInviteEmail,\n { message: 'Either password or sendInviteEmail is required', path: ['password'] },\n)\n\nconst userUpdateSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email().optional(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n organizationId: z.string().uuid().optional(),\n roles: z.array(z.string()).optional(),\n})\n\nconst userListItemSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email(),\n name: z.string().nullable(),\n organizationId: z.string().uuid().nullable(),\n organizationName: z.string().nullable(),\n tenantId: z.string().uuid().nullable(),\n tenantName: z.string().nullable(),\n roles: z.array(z.string()),\n roleIds: z.array(z.string().uuid()).optional(),\n})\n\nconst userListResponseSchema = z.object({\n items: z.array(userListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n isSuperAdmin: z.boolean().optional(),\n})\n\nconst okResponseSchema = z.object({ ok: z.literal(true) })\n\nconst errorResponseSchema = z.object({ error: z.string() })\n\ntype CrudInput = Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.users.list'] },\n POST: { requireAuth: true, requireFeatures: ['auth.users.create'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.users.edit'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.users.delete'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: User,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n actions: {\n create: {\n commandId: 'auth.users.create',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: ({ result }) => ({\n id: String(result.user.id),\n ...(result.warning ? { _warning: result.warning } : {}),\n }),\n status: 201,\n },\n update: {\n commandId: 'auth.users.update',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'auth.users.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const url = new URL(req.url)\n const rawRoleIds = url.searchParams.getAll('roleId').filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n name: url.searchParams.get('name') || undefined,\n organizationId: url.searchParams.get('organizationId') || undefined,\n roleIds: rawRoleIds.length ? rawRoleIds : undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n let isSuperAdmin = false\n try {\n if (auth.sub) {\n const rbacService = container.resolve('rbacService') as any\n const acl = await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n isSuperAdmin = !!acl?.isSuperAdmin\n }\n } catch (err) {\n console.error('users: failed to resolve rbac', err)\n }\n const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data\n const filters: any[] = [{ deletedAt: null }]\n const actorTenantId = auth.tenantId ? String(auth.tenantId) : null\n if (!isSuperAdmin) {\n if (!actorTenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n filters.push({ tenantId: actorTenantId })\n }\n if (organizationId) filters.push({ organizationId })\n const trimmedName = typeof name === 'string' ? name.trim() : ''\n if (trimmedName) {\n filters.push({ name: { $ilike: `%${escapeLikePattern(trimmedName)}%` } })\n }\n let idFilter: Set<string> | null = id ? new Set([id]) : null\n if (Array.isArray(roleIds) && roleIds.length > 0) {\n const uniqueRoleIds = Array.from(new Set(roleIds))\n const linksForRoles = await em.find(UserRole, { role: { $in: uniqueRoleIds as any } } as any)\n const roleUserIds = new Set<string>()\n for (const link of linksForRoles) {\n const uid = String((link as any).user?.id || (link as any).user || '')\n if (uid) roleUserIds.add(uid)\n }\n if (roleUserIds.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n if (idFilter) {\n for (const uid of Array.from(idFilter)) {\n if (!roleUserIds.has(uid)) idFilter.delete(uid)\n }\n } else {\n idFilter = roleUserIds\n }\n if (!idFilter || idFilter.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n }\n const trimmedSearch = typeof search === 'string' ? search.trim() : ''\n if (trimmedSearch) {\n // Email is encrypted at rest, so plaintext search must go through search_tokens.\n const tenantScope: string | null | undefined = isSuperAdmin ? undefined : auth.tenantId ?? null\n const searchFilters: any[] = []\n\n const matchedIds = await findUserIdsBySearchTokens(em, E.auth.user, trimmedSearch, tenantScope)\n if (matchedIds && matchedIds.length) {\n searchFilters.push({ id: { $in: matchedIds as any } })\n }\n\n const searchPattern = `%${escapeLikePattern(trimmedSearch)}%`\n const organizationSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n organizationSearchFilters.push({ tenant: tenantScope })\n }\n const matchingOrganizations = await em.find(\n Organization,\n organizationSearchFilters.length > 1 ? { $and: organizationSearchFilters } : organizationSearchFilters[0],\n )\n const matchingOrganizationIds = matchingOrganizations\n .map((org) => (org?.id ? String(org.id) : null))\n .filter((orgId): orgId is string => typeof orgId === 'string' && orgId.length > 0)\n if (matchingOrganizationIds.length) {\n searchFilters.push({ organizationId: { $in: matchingOrganizationIds as any } })\n }\n\n const roleSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n roleSearchFilters.push({ $or: [{ tenantId: tenantScope }, { tenantId: null }] })\n }\n const matchingRoles = await em.find(\n Role,\n roleSearchFilters.length > 1 ? { $and: roleSearchFilters } : roleSearchFilters[0],\n )\n const matchingRoleIds = matchingRoles\n .map((role) => (role?.id ? String(role.id) : null))\n .filter((roleId): roleId is string => typeof roleId === 'string' && roleId.length > 0)\n if (matchingRoleIds.length) {\n const roleSearchLinks = await em.find(\n UserRole,\n { role: { $in: matchingRoleIds as any } } as any,\n )\n const matchingRoleUserIds = Array.from(new Set(\n roleSearchLinks\n .map((link) => {\n const userRef = (link as any).user\n const userId = userRef?.id ?? userRef\n return userId ? String(userId) : null\n })\n .filter((userId): userId is string => typeof userId === 'string' && userId.length > 0),\n ))\n if (matchingRoleUserIds.length) {\n searchFilters.push({ id: { $in: matchingRoleUserIds as any } })\n }\n }\n\n if (!searchFilters.length) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n\n filters.push(searchFilters.length > 1 ? { $or: searchFilters } : searchFilters[0])\n }\n if (idFilter && idFilter.size) {\n filters.push({ id: { $in: Array.from(idFilter) as any } })\n } else if (id) {\n filters.push({ id })\n }\n const where = filters.length > 1 ? { $and: filters } : filters[0]\n const [rows, count] = await em.findAndCount(User, where, { limit: pageSize, offset: (page - 1) * pageSize })\n const userIds = rows.map((u: any) => u.id)\n const links = userIds.length\n ? await findWithDecryption(\n em,\n UserRole,\n { user: { $in: userIds as any } } as any,\n { populate: ['role'] },\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n : []\n const roleMap: Record<string, string[]> = {}\n const roleIdMap: Record<string, string[]> = {}\n for (const l of links) {\n const uid = String((l as any).user?.id || (l as any).user)\n const rname = String((l as any).role?.name || '')\n const rid = String((l as any).role?.id ?? '')\n if (!roleMap[uid]) roleMap[uid] = []\n if (!roleIdMap[uid]) roleIdMap[uid] = []\n if (rname) roleMap[uid].push(rname)\n if (rid) roleIdMap[uid].push(rid)\n }\n const orgIds = rows\n .map((u: any) => (u.organizationId ? String(u.organizationId) : null))\n .filter((id): id is string => !!id)\n const uniqueOrgIds = Array.from(new Set(orgIds))\n let orgMap: Record<string, string> = {}\n if (uniqueOrgIds.length) {\n const organizations = await em.find(\n Organization,\n { id: { $in: uniqueOrgIds as any }, deletedAt: null },\n )\n orgMap = organizations.reduce<Record<string, string>>((acc, org) => {\n const orgId = org?.id ? String(org.id) : null\n if (!orgId) return acc\n const rawName = (org as any)?.name\n const orgName = typeof rawName === 'string' && rawName.length > 0 ? rawName : orgId\n acc[orgId] = orgName\n return acc\n }, {})\n }\n const tenantIds = rows\n .map((u: any) => (u.tenantId ? String(u.tenantId) : null))\n .filter((id): id is string => !!id)\n const uniqueTenantIds = Array.from(new Set(tenantIds))\n let tenantMap: Record<string, string> = {}\n if (uniqueTenantIds.length) {\n const tenants = await em.find(\n Tenant,\n { id: { $in: uniqueTenantIds as any }, deletedAt: null },\n )\n tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tenantId = tenant?.id ? String(tenant.id) : null\n if (!tenantId) return acc\n const rawName = (tenant as any)?.name\n const tenantName = typeof rawName === 'string' && rawName.length > 0 ? rawName : tenantId\n acc[tenantId] = tenantName\n return acc\n }, {})\n }\n const tenantByUser: Record<string, string | null> = {}\n const organizationByUser: Record<string, string | null> = {}\n for (const u of rows) {\n const uid = String(u.id)\n tenantByUser[uid] = u.tenantId ? String(u.tenantId) : null\n organizationByUser[uid] = u.organizationId ? String(u.organizationId) : null\n }\n const cfByUser = userIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.auth.user,\n recordIds: userIds.map(String),\n tenantIdByRecord: tenantByUser,\n organizationIdByRecord: organizationByUser,\n tenantFallbacks: auth.tenantId ? [auth.tenantId] : [],\n })\n : {}\n\n const items = rows.map((u: any) => {\n const uid = String(u.id)\n const orgId = u.organizationId ? String(u.organizationId) : null\n return {\n id: uid,\n email: String(u.email),\n name: u.name ? String(u.name) : null,\n organizationId: orgId,\n organizationName: orgId ? orgMap[orgId] ?? orgId : null,\n tenantId: u.tenantId ? String(u.tenantId) : null,\n tenantName: u.tenantId ? tenantMap[String(u.tenantId)] ?? String(u.tenantId) : null,\n roles: roleMap[uid] || [],\n roleIds: roleIdMap[uid] || [],\n hasPassword: !!u.passwordHash,\n ...(cfByUser[uid] || {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'auth.user',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: parsed.data,\n accessType: id ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total: count, totalPages, isSuperAdmin })\n}\n\nexport const POST = async (req: Request) => {\n return crud.POST(req)\n}\n\nexport const PUT = async (req: Request) => {\n return crud.PUT(req)\n}\n\nexport const DELETE = crud.DELETE\n\nasync function findUserIdsBySearchTokens(\n em: EntityManager,\n entityType: string,\n search: string,\n tenantScope: string | null | undefined,\n): Promise<string[] | null> {\n const trimmed = search.trim()\n if (!trimmed) return null\n const searchConfig = resolveSearchConfig()\n if (!searchConfig.enabled) return []\n const { hashes } = tokenizeText(trimmed, searchConfig)\n if (!hashes.length) return []\n\n const db = (em as any).getKysely() as any\n let query = db\n .selectFrom('search_tokens')\n .select('entity_id')\n .where('entity_type', '=', entityType)\n .where('token_hash', 'in', hashes)\n .groupBy('entity_id')\n .having(sql<boolean>`count(distinct token_hash) >= ${hashes.length}`)\n if (tenantScope !== undefined) {\n query = query.where(sql<boolean>`tenant_id is not distinct from ${tenantScope}`)\n }\n const rows = (await query.execute()) as Array<{ entity_id?: unknown }>\n return rows\n .map((row) => (typeof row.entity_id === 'string' ? row.entity_id : null))\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n}\n\nasync function assertCanAssignRoles(req: Request, roles: unknown, payload: Record<string, unknown>) {\n if (!Array.isArray(roles)) return\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const tenantId = await resolveTargetTenantIdForRoleGrant(em, payload, auth.tenantId ?? null)\n await assertActorCanGrantRoleTokens({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId,\n organizationId: auth.orgId ?? null,\n roleTokens: roles,\n })\n}\n\nasync function resolveTargetTenantIdForRoleGrant(\n em: EntityManager,\n payload: Record<string, unknown>,\n fallbackTenantId: string | null,\n): Promise<string | null> {\n const organizationId = typeof payload.organizationId === 'string' ? payload.organizationId : null\n if (organizationId) {\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId },\n )\n return organization?.tenant?.id ? String(organization.tenant.id) : fallbackTenantId\n }\n\n const userId = typeof payload.id === 'string' ? payload.id : null\n if (userId) {\n const user = await findOneWithDecryption(\n em,\n User,\n { id: userId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n return user?.tenantId ? String(user.tenantId) : fallbackTenantId\n }\n\n return fallbackTenantId\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'User management',\n methods: {\n GET: {\n summary: 'List users',\n description:\n 'Returns users for the current tenant. Search matches email, organization name, and role name. Super administrators may scope the response via organization or role filters.',\n query: querySchema,\n responses: [\n { status: 200, description: 'User collection', schema: userListResponseSchema },\n ],\n },\n POST: {\n summary: 'Create user',\n description: 'Creates a new confirmed user within the specified organization, optional display name, and optional roles.',\n requestBody: {\n contentType: 'application/json',\n schema: userCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'User created',\n schema: z.object({ id: z.string().uuid() }),\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or duplicate email', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update user',\n description: 'Updates profile fields including display name, organization assignment, credentials, or role memberships.',\n requestBody: {\n contentType: 'application/json',\n schema: userUpdateSchema,\n },\n responses: [\n { status: 200, description: 'User updated', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete user',\n description: 'Deletes a user by identifier. Undo support is provided via the command bus.',\n query: z.object({ id: z.string().uuid().describe('User identifier') }),\n responses: [\n { status: 200, description: 'User deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'User cannot be deleted', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,eAAe,qBAAqB;AAC7C,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,MAAM,MAAM,gBAAgB;AAErC,SAAS,cAAc,cAAc;AACrC,SAAS,SAAS;AAClB,SAAS,6BAA6B;AAEtC,SAAS,gBAAgB,uBAAuB;AAChD,SAAS,qCAAqC;AAC9C,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,WAAW;AAEpB,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC/B,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,OAAO,EAAE,SAAS;AAAA,EAC1B,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC3C,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS;AAC/C,CAAC,EAAE,YAAY;AAEf,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,iBAAiB,oBAAoB;AAE3C,MAAM,oBAAoB,EAAE;AAAA,EAC1B,CAAC,UAAU;AACT,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,UAAM,UAAU,MAAM,KAAK;AAC3B,WAAO,QAAQ,SAAS,UAAU;AAAA,EACpC;AAAA,EACA,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAC7C;AAEA,MAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,OAAO,EAAE,OAAO,EAAE,MAAM;AAAA,EACxB,MAAM;AAAA,EACN,UAAU,eAAe,SAAS;AAAA,EAClC,iBAAiB,EAAE,QAAQ,EAAE,SAAS;AAAA,EACtC,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,EAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACtC,CAAC,EAAE;AAAA,EACD,CAAC,SAAS,KAAK,YAAY,KAAK;AAAA,EAChC,EAAE,SAAS,kDAAkD,MAAM,CAAC,UAAU,EAAE;AAClF;AAEA,MAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS;AAAA,EACnC,MAAM;AAAA,EACN,UAAU,eAAe,SAAS;AAAA,EAClC,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC3C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACtC,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO,EAAE,MAAM;AAAA,EACxB,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC3C,kBAAkB,EAAE,OAAO,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACrC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EACzB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS;AAC/C,CAAC;AAED,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,OAAO,EAAE,MAAM,kBAAkB;AAAA,EACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACpC,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACtC,cAAc,EAAE,QAAQ,EAAE,SAAS;AACrC,CAAC;AAED,MAAM,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AAEzD,MAAM,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAI1D,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AAAA,EAClE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACtE;AAEO,MAAM,WAAW;AAExB,MAAM,OAAO,cAA6D;AAAA,EACxE,UAAU;AAAA,EACV,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,OAAO,EAAE,QAAQ,IAAI,MAAM;AACnC,YAAI,IAAI,SAAS;AACf,gBAAM,qBAAqB,IAAI,SAAS,OAAO,OAAO,MAAM;AAAA,QAC9D;AACA,eAAO;AAAA,MACT;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,IAAI,OAAO,OAAO,KAAK,EAAE;AAAA,QACzB,GAAI,OAAO,UAAU,EAAE,UAAU,OAAO,QAAQ,IAAI,CAAC;AAAA,MACvD;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM;AACnC,YAAI,IAAI,SAAS;AACf,gBAAM,qBAAqB,IAAI,SAAS,OAAO,OAAO,MAAM;AAAA,QAC9D;AACA,eAAO;AAAA,MACT;AAAA,MACA,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;AAED,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,EAAE,CAAC;AAC1E,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,aAAa,IAAI,aAAa,OAAO,QAAQ,EAAE,OAAO,CAACA,QAAqB,OAAOA,QAAO,YAAYA,IAAG,KAAK,EAAE,SAAS,CAAC;AAChI,QAAM,SAAS,YAAY,UAAU;AAAA,IACnC,IAAI,IAAI,aAAa,IAAI,IAAI,KAAK;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,gBAAgB,IAAI,aAAa,IAAI,gBAAgB,KAAK;AAAA,IAC1D,SAAS,WAAW,SAAS,aAAa;AAAA,EAC5C,CAAC;AACD,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,EAAE,CAAC;AACpF,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAM,UAAU,QAAQ,IAAI;AAClC,MAAI,eAAe;AACnB,MAAI;AACF,QAAI,KAAK,KAAK;AACZ,YAAM,cAAc,UAAU,QAAQ,aAAa;AACnD,YAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK,EAAE,UAAU,KAAK,YAAY,MAAM,gBAAgB,KAAK,SAAS,KAAK,CAAC;AACvH,qBAAe,CAAC,CAAC,KAAK;AAAA,IACxB;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,iCAAiC,GAAG;AAAA,EACpD;AACA,QAAM,EAAE,IAAI,MAAM,UAAU,QAAQ,MAAM,gBAAgB,QAAQ,IAAI,OAAO;AAC7E,QAAM,UAAiB,CAAC,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAM,gBAAgB,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAC9D,MAAI,CAAC,cAAc;AACjB,QAAI,CAAC,eAAe;AAClB,aAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,GAAG,aAAa,CAAC;AAAA,IAC/E;AACA,YAAQ,KAAK,EAAE,UAAU,cAAc,CAAC;AAAA,EAC1C;AACA,MAAI,eAAgB,SAAQ,KAAK,EAAE,eAAe,CAAC;AACnD,QAAM,cAAc,OAAO,SAAS,WAAW,KAAK,KAAK,IAAI;AAC7D,MAAI,aAAa;AACf,YAAQ,KAAK,EAAE,MAAM,EAAE,QAAQ,IAAI,kBAAkB,WAAW,CAAC,IAAI,EAAE,CAAC;AAAA,EAC1E;AACA,MAAI,WAA+B,KAAK,oBAAI,IAAI,CAAC,EAAE,CAAC,IAAI;AACxD,MAAI,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,GAAG;AAChD,UAAM,gBAAgB,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC;AACjD,UAAM,gBAAgB,MAAM,GAAG,KAAK,UAAU,EAAE,MAAM,EAAE,KAAK,cAAqB,EAAE,CAAQ;AAC5F,UAAM,cAAc,oBAAI,IAAY;AACpC,eAAW,QAAQ,eAAe;AAChC,YAAM,MAAM,OAAQ,KAAa,MAAM,MAAO,KAAa,QAAQ,EAAE;AACrE,UAAI,IAAK,aAAY,IAAI,GAAG;AAAA,IAC9B;AACA,QAAI,YAAY,SAAS,EAAG,QAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,EAAE,CAAC;AAC3F,QAAI,UAAU;AACZ,iBAAW,OAAO,MAAM,KAAK,QAAQ,GAAG;AACtC,YAAI,CAAC,YAAY,IAAI,GAAG,EAAG,UAAS,OAAO,GAAG;AAAA,MAChD;AAAA,IACF,OAAO;AACL,iBAAW;AAAA,IACb;AACA,QAAI,CAAC,YAAY,SAAS,SAAS,EAAG,QAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,EAAE,CAAC;AAAA,EACvG;AACA,QAAM,gBAAgB,OAAO,WAAW,WAAW,OAAO,KAAK,IAAI;AACnE,MAAI,eAAe;AAEjB,UAAM,cAAyC,eAAe,SAAY,KAAK,YAAY;AAC3F,UAAM,gBAAuB,CAAC;AAE9B,UAAM,aAAa,MAAM,0BAA0B,IAAI,EAAE,KAAK,MAAM,eAAe,WAAW;AAC9F,QAAI,cAAc,WAAW,QAAQ;AACnC,oBAAc,KAAK,EAAE,IAAI,EAAE,KAAK,WAAkB,EAAE,CAAC;AAAA,IACvD;AAEA,UAAM,gBAAgB,IAAI,kBAAkB,aAAa,CAAC;AAC1D,UAAM,4BAAmC;AAAA,MACvC,EAAE,WAAW,KAAK;AAAA,MAClB,EAAE,MAAM,EAAE,QAAQ,cAAc,EAAE;AAAA,IACpC;AACA,QAAI,aAAa;AACf,gCAA0B,KAAK,EAAE,QAAQ,YAAY,CAAC;AAAA,IACxD;AACA,UAAM,wBAAwB,MAAM,GAAG;AAAA,MACrC;AAAA,MACA,0BAA0B,SAAS,IAAI,EAAE,MAAM,0BAA0B,IAAI,0BAA0B,CAAC;AAAA,IAC1G;AACA,UAAM,0BAA0B,sBAC7B,IAAI,CAAC,QAAS,KAAK,KAAK,OAAO,IAAI,EAAE,IAAI,IAAK,EAC9C,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AACnF,QAAI,wBAAwB,QAAQ;AAClC,oBAAc,KAAK,EAAE,gBAAgB,EAAE,KAAK,wBAA+B,EAAE,CAAC;AAAA,IAChF;AAEA,UAAM,oBAA2B;AAAA,MAC/B,EAAE,WAAW,KAAK;AAAA,MAClB,EAAE,MAAM,EAAE,QAAQ,cAAc,EAAE;AAAA,IACpC;AACA,QAAI,aAAa;AACf,wBAAkB,KAAK,EAAE,KAAK,CAAC,EAAE,UAAU,YAAY,GAAG,EAAE,UAAU,KAAK,CAAC,EAAE,CAAC;AAAA,IACjF;AACA,UAAM,gBAAgB,MAAM,GAAG;AAAA,MAC7B;AAAA,MACA,kBAAkB,SAAS,IAAI,EAAE,MAAM,kBAAkB,IAAI,kBAAkB,CAAC;AAAA,IAClF;AACA,UAAM,kBAAkB,cACrB,IAAI,CAAC,SAAU,MAAM,KAAK,OAAO,KAAK,EAAE,IAAI,IAAK,EACjD,OAAO,CAAC,WAA6B,OAAO,WAAW,YAAY,OAAO,SAAS,CAAC;AACvF,QAAI,gBAAgB,QAAQ;AAC1B,YAAM,kBAAkB,MAAM,GAAG;AAAA,QAC/B;AAAA,QACA,EAAE,MAAM,EAAE,KAAK,gBAAuB,EAAE;AAAA,MAC1C;AACA,YAAM,sBAAsB,MAAM,KAAK,IAAI;AAAA,QACzC,gBACG,IAAI,CAAC,SAAS;AACb,gBAAM,UAAW,KAAa;AAC9B,gBAAM,SAAS,SAAS,MAAM;AAC9B,iBAAO,SAAS,OAAO,MAAM,IAAI;AAAA,QACnC,CAAC,EACA,OAAO,CAAC,WAA6B,OAAO,WAAW,YAAY,OAAO,SAAS,CAAC;AAAA,MACzF,CAAC;AACD,UAAI,oBAAoB,QAAQ;AAC9B,sBAAc,KAAK,EAAE,IAAI,EAAE,KAAK,oBAA2B,EAAE,CAAC;AAAA,MAChE;AAAA,IACF;AAEA,QAAI,CAAC,cAAc,QAAQ;AACzB,aAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,GAAG,aAAa,CAAC;AAAA,IAC/E;AAEA,YAAQ,KAAK,cAAc,SAAS,IAAI,EAAE,KAAK,cAAc,IAAI,cAAc,CAAC,CAAC;AAAA,EACnF;AACA,MAAI,YAAY,SAAS,MAAM;AAC7B,YAAQ,KAAK,EAAE,IAAI,EAAE,KAAK,MAAM,KAAK,QAAQ,EAAS,EAAE,CAAC;AAAA,EAC3D,WAAW,IAAI;AACb,YAAQ,KAAK,EAAE,GAAG,CAAC;AAAA,EACrB;AACA,QAAM,QAAQ,QAAQ,SAAS,IAAI,EAAE,MAAM,QAAQ,IAAI,QAAQ,CAAC;AAChE,QAAM,CAAC,MAAM,KAAK,IAAI,MAAM,GAAG,aAAa,MAAM,OAAO,EAAE,OAAO,UAAU,SAAS,OAAO,KAAK,SAAS,CAAC;AAC3G,QAAM,UAAU,KAAK,IAAI,CAAC,MAAW,EAAE,EAAE;AACzC,QAAM,QAAQ,QAAQ,SAClB,MAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,EAAE,MAAM,EAAE,KAAK,QAAe,EAAE;AAAA,IAChC,EAAE,UAAU,CAAC,MAAM,EAAE;AAAA,IACrB,EAAE,UAAU,KAAK,YAAY,MAAM,gBAAgB,KAAK,SAAS,KAAK;AAAA,EACxE,IACA,CAAC;AACL,QAAM,UAAoC,CAAC;AAC3C,QAAM,YAAsC,CAAC;AAC7C,aAAW,KAAK,OAAO;AACrB,UAAM,MAAM,OAAQ,EAAU,MAAM,MAAO,EAAU,IAAI;AACzD,UAAM,QAAQ,OAAQ,EAAU,MAAM,QAAQ,EAAE;AAChD,UAAM,MAAM,OAAQ,EAAU,MAAM,MAAM,EAAE;AAC5C,QAAI,CAAC,QAAQ,GAAG,EAAG,SAAQ,GAAG,IAAI,CAAC;AACnC,QAAI,CAAC,UAAU,GAAG,EAAG,WAAU,GAAG,IAAI,CAAC;AACvC,QAAI,MAAO,SAAQ,GAAG,EAAE,KAAK,KAAK;AAClC,QAAI,IAAK,WAAU,GAAG,EAAE,KAAK,GAAG;AAAA,EAClC;AACA,QAAM,SAAS,KACZ,IAAI,CAAC,MAAY,EAAE,iBAAiB,OAAO,EAAE,cAAc,IAAI,IAAK,EACpE,OAAO,CAACA,QAAqB,CAAC,CAACA,GAAE;AACpC,QAAM,eAAe,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC;AAC/C,MAAI,SAAiC,CAAC;AACtC,MAAI,aAAa,QAAQ;AACvB,UAAM,gBAAgB,MAAM,GAAG;AAAA,MAC7B;AAAA,MACA,EAAE,IAAI,EAAE,KAAK,aAAoB,GAAG,WAAW,KAAK;AAAA,IACtD;AACA,aAAS,cAAc,OAA+B,CAAC,KAAK,QAAQ;AAClE,YAAM,QAAQ,KAAK,KAAK,OAAO,IAAI,EAAE,IAAI;AACzC,UAAI,CAAC,MAAO,QAAO;AACnB,YAAM,UAAW,KAAa;AAC9B,YAAM,UAAU,OAAO,YAAY,YAAY,QAAQ,SAAS,IAAI,UAAU;AAC9E,UAAI,KAAK,IAAI;AACb,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACP;AACA,QAAM,YAAY,KACf,IAAI,CAAC,MAAY,EAAE,WAAW,OAAO,EAAE,QAAQ,IAAI,IAAK,EACxD,OAAO,CAACA,QAAqB,CAAC,CAACA,GAAE;AACpC,QAAM,kBAAkB,MAAM,KAAK,IAAI,IAAI,SAAS,CAAC;AACrD,MAAI,YAAoC,CAAC;AACzC,MAAI,gBAAgB,QAAQ;AAC1B,UAAM,UAAU,MAAM,GAAG;AAAA,MACvB;AAAA,MACA,EAAE,IAAI,EAAE,KAAK,gBAAuB,GAAG,WAAW,KAAK;AAAA,IACzD;AACA,gBAAY,QAAQ,OAA+B,CAAC,KAAK,WAAW;AAClE,YAAM,WAAW,QAAQ,KAAK,OAAO,OAAO,EAAE,IAAI;AAClD,UAAI,CAAC,SAAU,QAAO;AACtB,YAAM,UAAW,QAAgB;AACjC,YAAM,aAAa,OAAO,YAAY,YAAY,QAAQ,SAAS,IAAI,UAAU;AACjF,UAAI,QAAQ,IAAI;AAChB,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACP;AACA,QAAM,eAA8C,CAAC;AACrD,QAAM,qBAAoD,CAAC;AAC3D,aAAW,KAAK,MAAM;AACpB,UAAM,MAAM,OAAO,EAAE,EAAE;AACvB,iBAAa,GAAG,IAAI,EAAE,WAAW,OAAO,EAAE,QAAQ,IAAI;AACtD,uBAAmB,GAAG,IAAI,EAAE,iBAAiB,OAAO,EAAE,cAAc,IAAI;AAAA,EAC1E;AACA,QAAM,WAAW,QAAQ,SACrB,MAAM,sBAAsB;AAAA,IAC1B;AAAA,IACA,UAAU,EAAE,KAAK;AAAA,IACjB,WAAW,QAAQ,IAAI,MAAM;AAAA,IAC7B,kBAAkB;AAAA,IAClB,wBAAwB;AAAA,IACxB,iBAAiB,KAAK,WAAW,CAAC,KAAK,QAAQ,IAAI,CAAC;AAAA,EACtD,CAAC,IACD,CAAC;AAEL,QAAM,QAAQ,KAAK,IAAI,CAAC,MAAW;AACjC,UAAM,MAAM,OAAO,EAAE,EAAE;AACvB,UAAM,QAAQ,EAAE,iBAAiB,OAAO,EAAE,cAAc,IAAI;AAC5D,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,OAAO,EAAE,KAAK;AAAA,MACrB,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,IAAI;AAAA,MAChC,gBAAgB;AAAA,MAChB,kBAAkB,QAAQ,OAAO,KAAK,KAAK,QAAQ;AAAA,MACnD,UAAU,EAAE,WAAW,OAAO,EAAE,QAAQ,IAAI;AAAA,MAC5C,YAAY,EAAE,WAAW,UAAU,OAAO,EAAE,QAAQ,CAAC,KAAK,OAAO,EAAE,QAAQ,IAAI;AAAA,MAC/E,OAAO,QAAQ,GAAG,KAAK,CAAC;AAAA,MACxB,SAAS,UAAU,GAAG,KAAK,CAAC;AAAA,MAC5B,aAAa,CAAC,CAAC,EAAE;AAAA,MACjB,GAAI,SAAS,GAAG,KAAK,CAAC;AAAA,IACxB;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,UAAU,KAAK,YAAY;AAAA,IAC3B,OAAO,OAAO;AAAA,IACd,YAAY,KAAK,cAAc;AAAA,EACjC,CAAC;AACD,SAAO,aAAa,KAAK,EAAE,OAAO,OAAO,OAAO,YAAY,aAAa,CAAC;AAC5E;AAEO,MAAM,OAAO,OAAO,QAAiB;AAC1C,SAAO,KAAK,KAAK,GAAG;AACtB;AAEO,MAAM,MAAM,OAAO,QAAiB;AACzC,SAAO,KAAK,IAAI,GAAG;AACrB;AAEO,MAAM,SAAS,KAAK;AAE3B,eAAe,0BACb,IACA,YACA,QACA,aAC0B;AAC1B,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,eAAe,oBAAoB;AACzC,MAAI,CAAC,aAAa,QAAS,QAAO,CAAC;AACnC,QAAM,EAAE,OAAO,IAAI,aAAa,SAAS,YAAY;AACrD,MAAI,CAAC,OAAO,OAAQ,QAAO,CAAC;AAE5B,QAAM,KAAM,GAAW,UAAU;AACjC,MAAI,QAAQ,GACT,WAAW,eAAe,EAC1B,OAAO,WAAW,EAClB,MAAM,eAAe,KAAK,UAAU,EACpC,MAAM,cAAc,MAAM,MAAM,EAChC,QAAQ,WAAW,EACnB,OAAO,oCAA6C,OAAO,MAAM,EAAE;AACtE,MAAI,gBAAgB,QAAW;AAC7B,YAAQ,MAAM,MAAM,qCAA8C,WAAW,EAAE;AAAA,EACjF;AACA,QAAM,OAAQ,MAAM,MAAM,QAAQ;AAClC,SAAO,KACJ,IAAI,CAAC,QAAS,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY,IAAK,EACvE,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AACzE;AAEA,eAAe,qBAAqB,KAAc,OAAgB,SAAkC;AAClG,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG;AAC3B,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,IAAK,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AACtE,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,WAAW,MAAM,kCAAkC,IAAI,SAAS,KAAK,YAAY,IAAI;AAC3F,QAAM,8BAA8B;AAAA,IAClC;AAAA,IACA,aAAa,UAAU,QAAQ,aAAa;AAAA,IAC5C,aAAa,KAAK;AAAA,IAClB;AAAA,IACA,gBAAgB,KAAK,SAAS;AAAA,IAC9B,YAAY;AAAA,EACd,CAAC;AACH;AAEA,eAAe,kCACb,IACA,SACA,kBACwB;AACxB,QAAM,iBAAiB,OAAO,QAAQ,mBAAmB,WAAW,QAAQ,iBAAiB;AAC7F,MAAI,gBAAgB;AAClB,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MACA,EAAE,IAAI,eAAe;AAAA,MACrB,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,MACvB,EAAE,UAAU,MAAM,eAAe;AAAA,IACnC;AACA,WAAO,cAAc,QAAQ,KAAK,OAAO,aAAa,OAAO,EAAE,IAAI;AAAA,EACrE;AAEA,QAAM,SAAS,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK;AAC7D,MAAI,QAAQ;AACV,UAAM,OAAO,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,MACA,EAAE,IAAI,QAAQ,WAAW,KAAK;AAAA,MAC9B,CAAC;AAAA,MACD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,IACzC;AACA,WAAO,MAAM,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,EAClD;AAEA,SAAO;AACT;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,uBAAuB;AAAA,MAChF;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,QAC5C;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,sCAAsC,QAAQ,oBAAoB;AAAA,QAC9F,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,QACxE,EAAE,QAAQ,KAAK,aAAa,wCAAwC,QAAQ,oBAAoB;AAAA,MAClG;AAAA,IACF;AAAA,IACA,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,iBAAiB;AAAA,MACvE;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,oBAAoB;AAAA,QAC3E,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,QACxE,EAAE,QAAQ,KAAK,aAAa,wCAAwC,QAAQ,oBAAoB;AAAA,QAChG,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,oBAAoB;AAAA,MAC5E;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;AAAA,MACrE,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,iBAAiB;AAAA,MACvE;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,oBAAoB;AAAA,QAClF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,QACxE,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,oBAAoB;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { User, Role, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { userCrudEvents, userCrudIndexer } from '@open-mercato/core/modules/auth/commands/users'\nimport { assertActorCanGrantRoleTokens } from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'\nimport { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'\nimport { sql } from 'kysely'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\n\nconst querySchema = z.object({\n id: z.string().uuid().optional(),\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 name: z.string().optional(),\n organizationId: z.string().uuid().optional(),\n roleIds: z.array(z.string().uuid()).optional(),\n}).passthrough()\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst passwordSchema = buildPasswordSchema()\n\nconst displayNameSchema = z.preprocess(\n normalizeDisplayNameInput,\n z.string().trim().min(1).max(120).nullable().optional(),\n)\n\nconst userCreateSchema = z.object({\n email: z.string().email(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n sendInviteEmail: z.boolean().optional(),\n organizationId: z.string().uuid(),\n roles: z.array(z.string()).optional(),\n}).refine(\n (data) => data.password || data.sendInviteEmail,\n { message: 'Either password or sendInviteEmail is required', path: ['password'] },\n)\n\nconst userUpdateSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email().optional(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n organizationId: z.string().uuid().optional(),\n roles: z.array(z.string()).optional(),\n})\n\nconst userListItemSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email(),\n name: z.string().nullable(),\n organizationId: z.string().uuid().nullable(),\n organizationName: z.string().nullable(),\n tenantId: z.string().uuid().nullable(),\n tenantName: z.string().nullable(),\n roles: z.array(z.string()),\n roleIds: z.array(z.string().uuid()).optional(),\n})\n\nconst userListResponseSchema = z.object({\n items: z.array(userListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n isSuperAdmin: z.boolean().optional(),\n})\n\nconst okResponseSchema = z.object({ ok: z.literal(true) })\n\nconst errorResponseSchema = z.object({ error: z.string() })\n\ntype CrudInput = Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.users.list'] },\n POST: { requireAuth: true, requireFeatures: ['auth.users.create'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.users.edit'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.users.delete'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: User,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n actions: {\n create: {\n commandId: 'auth.users.create',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: ({ result }) => ({\n id: String(result.user.id),\n ...(result.warning ? { _warning: result.warning } : {}),\n }),\n status: 201,\n },\n update: {\n commandId: 'auth.users.update',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'auth.users.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const url = new URL(req.url)\n const rawRoleIds = url.searchParams.getAll('roleId').filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n name: url.searchParams.get('name') || undefined,\n organizationId: url.searchParams.get('organizationId') || undefined,\n roleIds: rawRoleIds.length ? rawRoleIds : undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n let isSuperAdmin = false\n try {\n if (auth.sub) {\n const rbacService = container.resolve('rbacService') as any\n const acl = await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n isSuperAdmin = !!acl?.isSuperAdmin\n }\n } catch (err) {\n console.error('users: failed to resolve rbac', err)\n }\n const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data\n const filters: any[] = [{ deletedAt: null }]\n const actorTenantId = auth.tenantId ? String(auth.tenantId) : null\n if (!isSuperAdmin) {\n if (!actorTenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n filters.push({ tenantId: actorTenantId })\n }\n if (organizationId) filters.push({ organizationId })\n const trimmedName = typeof name === 'string' ? name.trim() : ''\n if (trimmedName) {\n filters.push({ name: { $ilike: `%${escapeLikePattern(trimmedName)}%` } })\n }\n let idFilter: Set<string> | null = id ? new Set([id]) : null\n if (Array.isArray(roleIds) && roleIds.length > 0) {\n const uniqueRoleIds = Array.from(new Set(roleIds))\n const linksForRoles = await em.find(UserRole, { role: { $in: uniqueRoleIds as any } } as any)\n const roleUserIds = new Set<string>()\n for (const link of linksForRoles) {\n const uid = String((link as any).user?.id || (link as any).user || '')\n if (uid) roleUserIds.add(uid)\n }\n if (roleUserIds.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n if (idFilter) {\n for (const uid of Array.from(idFilter)) {\n if (!roleUserIds.has(uid)) idFilter.delete(uid)\n }\n } else {\n idFilter = roleUserIds\n }\n if (!idFilter || idFilter.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n }\n const trimmedSearch = typeof search === 'string' ? search.trim() : ''\n if (trimmedSearch) {\n // Email is encrypted at rest, so plaintext search must go through search_tokens.\n const tenantScope: string | null | undefined = isSuperAdmin ? undefined : auth.tenantId ?? null\n const searchFilters: any[] = []\n\n const matchedIds = await findUserIdsBySearchTokens(em, E.auth.user, trimmedSearch, tenantScope)\n if (matchedIds && matchedIds.length) {\n searchFilters.push({ id: { $in: matchedIds as any } })\n }\n\n const searchPattern = `%${escapeLikePattern(trimmedSearch)}%`\n const organizationSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n organizationSearchFilters.push({ tenant: tenantScope })\n }\n const matchingOrganizations = await em.find(\n Organization,\n organizationSearchFilters.length > 1 ? { $and: organizationSearchFilters } : organizationSearchFilters[0],\n )\n const matchingOrganizationIds = matchingOrganizations\n .map((org) => (org?.id ? String(org.id) : null))\n .filter((orgId): orgId is string => typeof orgId === 'string' && orgId.length > 0)\n if (matchingOrganizationIds.length) {\n searchFilters.push({ organizationId: { $in: matchingOrganizationIds as any } })\n }\n\n const roleSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n roleSearchFilters.push({ $or: [{ tenantId: tenantScope }, { tenantId: null }] })\n }\n const matchingRoles = await em.find(\n Role,\n roleSearchFilters.length > 1 ? { $and: roleSearchFilters } : roleSearchFilters[0],\n )\n const matchingRoleIds = matchingRoles\n .map((role) => (role?.id ? String(role.id) : null))\n .filter((roleId): roleId is string => typeof roleId === 'string' && roleId.length > 0)\n if (matchingRoleIds.length) {\n const roleSearchLinks = await em.find(\n UserRole,\n { role: { $in: matchingRoleIds as any } } as any,\n )\n const matchingRoleUserIds = Array.from(new Set(\n roleSearchLinks\n .map((link) => {\n const userRef = (link as any).user\n const userId = userRef?.id ?? userRef\n return userId ? String(userId) : null\n })\n .filter((userId): userId is string => typeof userId === 'string' && userId.length > 0),\n ))\n if (matchingRoleUserIds.length) {\n searchFilters.push({ id: { $in: matchingRoleUserIds as any } })\n }\n }\n\n if (!searchFilters.length) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n\n filters.push(searchFilters.length > 1 ? { $or: searchFilters } : searchFilters[0])\n }\n if (idFilter && idFilter.size) {\n filters.push({ id: { $in: Array.from(idFilter) as any } })\n } else if (id) {\n filters.push({ id })\n }\n const where = filters.length > 1 ? { $and: filters } : filters[0]\n const [rows, count] = await em.findAndCount(User, where, { limit: pageSize, offset: (page - 1) * pageSize })\n const userIds = rows.map((u: any) => u.id)\n const links = userIds.length\n ? await findWithDecryption(\n em,\n UserRole,\n { user: { $in: userIds as any } } as any,\n { populate: ['role'] },\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n : []\n const roleMap: Record<string, string[]> = {}\n const roleIdMap: Record<string, string[]> = {}\n for (const l of links) {\n const uid = String((l as any).user?.id || (l as any).user)\n const rname = String((l as any).role?.name || '')\n const rid = String((l as any).role?.id ?? '')\n if (!roleMap[uid]) roleMap[uid] = []\n if (!roleIdMap[uid]) roleIdMap[uid] = []\n if (rname) roleMap[uid].push(rname)\n if (rid) roleIdMap[uid].push(rid)\n }\n const orgIds = rows\n .map((u: any) => (u.organizationId ? String(u.organizationId) : null))\n .filter((id): id is string => !!id)\n const uniqueOrgIds = Array.from(new Set(orgIds))\n let orgMap: Record<string, string> = {}\n if (uniqueOrgIds.length) {\n const organizations = await em.find(\n Organization,\n { id: { $in: uniqueOrgIds as any }, deletedAt: null },\n )\n orgMap = organizations.reduce<Record<string, string>>((acc, org) => {\n const orgId = org?.id ? String(org.id) : null\n if (!orgId) return acc\n const rawName = (org as any)?.name\n const orgName = typeof rawName === 'string' && rawName.length > 0 ? rawName : orgId\n acc[orgId] = orgName\n return acc\n }, {})\n }\n const tenantIds = rows\n .map((u: any) => (u.tenantId ? String(u.tenantId) : null))\n .filter((id): id is string => !!id)\n const uniqueTenantIds = Array.from(new Set(tenantIds))\n let tenantMap: Record<string, string> = {}\n if (uniqueTenantIds.length) {\n const tenants = await em.find(\n Tenant,\n { id: { $in: uniqueTenantIds as any }, deletedAt: null },\n )\n tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tenantId = tenant?.id ? String(tenant.id) : null\n if (!tenantId) return acc\n const rawName = (tenant as any)?.name\n const tenantName = typeof rawName === 'string' && rawName.length > 0 ? rawName : tenantId\n acc[tenantId] = tenantName\n return acc\n }, {})\n }\n const tenantByUser: Record<string, string | null> = {}\n const organizationByUser: Record<string, string | null> = {}\n for (const u of rows) {\n const uid = String(u.id)\n tenantByUser[uid] = u.tenantId ? String(u.tenantId) : null\n organizationByUser[uid] = u.organizationId ? String(u.organizationId) : null\n }\n const cfByUser = userIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.auth.user,\n recordIds: userIds.map(String),\n tenantIdByRecord: tenantByUser,\n organizationIdByRecord: organizationByUser,\n tenantFallbacks: auth.tenantId ? [auth.tenantId] : [],\n })\n : {}\n\n const items = rows.map((u: any) => {\n const uid = String(u.id)\n const orgId = u.organizationId ? String(u.organizationId) : null\n return {\n id: uid,\n email: String(u.email),\n name: u.name ? String(u.name) : null,\n organizationId: orgId,\n organizationName: orgId ? orgMap[orgId] ?? orgId : null,\n tenantId: u.tenantId ? String(u.tenantId) : null,\n tenantName: u.tenantId ? tenantMap[String(u.tenantId)] ?? String(u.tenantId) : null,\n roles: roleMap[uid] || [],\n roleIds: roleIdMap[uid] || [],\n hasPassword: !!u.passwordHash,\n ...(cfByUser[uid] || {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'auth.user',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: parsed.data,\n accessType: id ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total: count, totalPages, isSuperAdmin })\n}\n\nexport const POST = async (req: Request) => {\n return crud.POST(req)\n}\n\nexport const PUT = async (req: Request) => {\n return crud.PUT(req)\n}\n\nexport const DELETE = crud.DELETE\n\nasync function findUserIdsBySearchTokens(\n em: EntityManager,\n entityType: string,\n search: string,\n tenantScope: string | null | undefined,\n): Promise<string[] | null> {\n const trimmed = search.trim()\n if (!trimmed) return null\n const searchConfig = resolveSearchConfig()\n if (!searchConfig.enabled) return []\n const { hashes } = tokenizeText(trimmed, searchConfig)\n if (!hashes.length) return []\n\n const db = (em as any).getKysely() as any\n let query = db\n .selectFrom('search_tokens')\n .select('entity_id')\n .where('entity_type', '=', entityType)\n .where('token_hash', 'in', hashes)\n .groupBy('entity_id')\n .having(sql<boolean>`count(distinct token_hash) >= ${hashes.length}`)\n if (tenantScope !== undefined) {\n query = query.where(sql<boolean>`tenant_id is not distinct from ${tenantScope}`)\n }\n const rows = (await query.execute()) as Array<{ entity_id?: unknown }>\n return rows\n .map((row) => (typeof row.entity_id === 'string' ? row.entity_id : null))\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n}\n\nasync function assertCanAssignRoles(req: Request, roles: unknown, payload: Record<string, unknown>) {\n if (!Array.isArray(roles)) return\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const tenantId = await resolveTargetTenantIdForRoleGrant(em, payload, auth.tenantId ?? null)\n await assertActorCanGrantRoleTokens({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId,\n organizationId: auth.orgId ?? null,\n roleTokens: roles,\n })\n}\n\nasync function resolveTargetTenantIdForRoleGrant(\n em: EntityManager,\n payload: Record<string, unknown>,\n fallbackTenantId: string | null,\n): Promise<string | null> {\n const organizationId = typeof payload.organizationId === 'string' ? payload.organizationId : null\n if (organizationId) {\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId },\n )\n return organization?.tenant?.id ? String(organization.tenant.id) : fallbackTenantId\n }\n\n const userId = typeof payload.id === 'string' ? payload.id : null\n if (userId) {\n const user = await findOneWithDecryption(\n em,\n User,\n { id: userId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n return user?.tenantId ? String(user.tenantId) : fallbackTenantId\n }\n\n return fallbackTenantId\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'User management',\n methods: {\n GET: {\n summary: 'List users',\n description:\n 'Returns users for the current tenant. Search matches email, organization name, and role name. Super administrators may scope the response via organization or role filters.',\n query: querySchema,\n responses: [\n { status: 200, description: 'User collection', schema: userListResponseSchema },\n ],\n },\n POST: {\n summary: 'Create user',\n description: 'Creates a new confirmed user within the specified organization, optional display name, and optional roles.',\n requestBody: {\n contentType: 'application/json',\n schema: userCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'User created',\n schema: z.object({ id: z.string().uuid() }),\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or duplicate email', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update user',\n description: 'Updates profile fields including display name, organization assignment, credentials, or role memberships.',\n requestBody: {\n contentType: 'application/json',\n schema: userUpdateSchema,\n },\n responses: [\n { status: 200, description: 'User updated', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete user',\n description: 'Deletes a user by identifier. Undo support is provided via the command bus.',\n query: z.object({ id: z.string().uuid().describe('User identifier') }),\n responses: [\n { status: 200, description: 'User deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'User cannot be deleted', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["id"]
|
|
7
7
|
}
|
|
@@ -18,6 +18,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
|
18
18
|
import { extractCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields-client";
|
|
19
19
|
import { formatPasswordRequirements, getPasswordPolicy } from "@open-mercato/shared/lib/auth/passwordPolicy";
|
|
20
20
|
import { UserConsentsPanel } from "@open-mercato/core/modules/auth/components/UserConsentsPanel";
|
|
21
|
+
import { normalizeDisplayNameInput } from "@open-mercato/core/modules/auth/lib/displayName";
|
|
21
22
|
function TenantAwareOrganizationSelectInput({
|
|
22
23
|
fieldId,
|
|
23
24
|
value,
|
|
@@ -354,7 +355,7 @@ function EditUserPage({ params }) {
|
|
|
354
355
|
const payload = {
|
|
355
356
|
id: id ? String(id) : "",
|
|
356
357
|
email: values.email,
|
|
357
|
-
name:
|
|
358
|
+
name: normalizeDisplayNameInput(values.name),
|
|
358
359
|
password: values.password && values.password.trim() ? values.password : void 0,
|
|
359
360
|
organizationId: values.organizationId ? values.organizationId : void 0,
|
|
360
361
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/auth/backend/users/%5Bid%5D/edit/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { AclEditor, type AclData } from '@open-mercato/core/modules/auth/components/AclEditor'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { WidgetVisibilityEditor, type WidgetVisibilityEditorHandle } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'\n\ntype EditUserFormValues = {\n email: string\n name: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype LoadedUser = {\n id: string\n email: string\n name: string | null\n organizationId: string | null\n tenantId: string | null\n tenantName: string | null\n organizationName: string | null\n roles: string[]\n roleIds: string[]\n hasPassword: boolean\n}\n\ntype UserApiItem = {\n id?: string | null\n email?: string | null\n name?: string | null\n organizationId?: string | null\n tenantId?: string | null\n tenantName?: string | null\n organizationName?: string | null\n roles?: unknown\n roleIds?: unknown\n hasPassword?: boolean\n}\n\ntype UserListResponse = {\n items?: UserApiItem[]\n isSuperAdmin?: boolean\n}\n\ntype FeatureCheckResponse = {\n ok?: boolean\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n includeInactiveIds={includeInactiveIds}\n tenantId={tenantId}\n />\n )\n}\n\nexport default function EditUserPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n const [initialUser, setInitialUser] = React.useState<LoadedUser | null>(null)\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [canEditOrgs, setCanEditOrgs] = React.useState(false)\n const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })\n const [customFieldValues, setCustomFieldValues] = React.useState<Record<string, unknown>>({})\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const widgetEditorRef = React.useRef<WidgetVisibilityEditorHandle | null>(null)\n const [resendingInvite, setResendingInvite] = React.useState(false)\n\n const handleResendInvite = React.useCallback(async () => {\n if (!id) return\n setResendingInvite(true)\n try {\n const { ok, result } = await apiCall<{ ok?: boolean; warning?: string }>('/api/auth/users/resend-invite', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n })\n if (ok) {\n if (result?.warning === 'invite_email_failed') {\n flash(tRef.current('auth.users.flash.inviteEmailFailed', 'Invite token created but the email could not be sent. Please check your email provider configuration.'), 'warning')\n } else {\n flash(tRef.current('auth.users.flash.inviteSent', 'Invitation email sent'), 'success')\n }\n }\n } catch (err) {\n console.error('Failed to resend invite:', err)\n flash(tRef.current('auth.users.form.errors.inviteResend', 'Failed to send invitation email'), 'error')\n } finally {\n setResendingInvite(false)\n }\n }, [id])\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n if (!id) {\n setLoading(false)\n setError(tRef.current('auth.users.form.errors.noId', 'No user ID provided'))\n return\n }\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n setCustomFieldValues({})\n try {\n const { ok, result } = await apiCall<UserListResponse>(\n `/api/auth/users?id=${encodeURIComponent(String(id))}&page=1&pageSize=1`,\n )\n if (!ok) throw new Error('load_failed')\n const item = Array.isArray(result?.items) ? result?.items?.[0] : undefined\n if (!cancelled) {\n setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n setActorResolved(true)\n if (!item) {\n setError(tRef.current('auth.users.form.errors.notFound', 'User not found'))\n setCustomFieldValues({})\n setInitialUser(null)\n setSelectedTenantId(null)\n } else {\n const roleNames = Array.isArray(item.roles)\n ? item.roles\n .map((role) => (typeof role === 'string' ? role : role == null ? '' : String(role)))\n .filter((role) => role.trim().length > 0)\n : []\n const roleIds = Array.isArray(item.roleIds)\n ? (item.roleIds as string[]).filter((rid) => typeof rid === 'string' && rid.trim().length > 0)\n : []\n setInitialUser({\n id: item.id ? String(item.id) : String(id),\n email: item.email ? String(item.email) : '',\n name: item.name ? String(item.name) : null,\n organizationId: item.organizationId ? String(item.organizationId) : null,\n tenantId: item.tenantId ? String(item.tenantId) : null,\n tenantName: item.tenantName ? String(item.tenantName) : null,\n organizationName: item.organizationName ? String(item.organizationName) : null,\n roles: roleNames,\n roleIds: roleIds.length > 0 ? roleIds : roleNames,\n hasPassword: item.hasPassword !== false,\n })\n setSelectedTenantId(item.tenantId ? String(item.tenantId) : null)\n const custom = extractCustomFieldEntries(item as Record<string, unknown>)\n setCustomFieldValues(custom)\n }\n }\n } catch (err) {\n console.error('Failed to load user:', err)\n if (!cancelled) setError(tRef.current('auth.users.form.errors.load', 'Failed to load user data'))\n if (!cancelled) setCustomFieldValues({})\n if (!cancelled) setActorResolved(true)\n }\n if (!cancelled) setLoading(false)\n try {\n const featureCheck = await apiCall<FeatureCheckResponse>(\n '/api/auth/feature-check',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['directory.organizations.view'] }),\n },\n { fallback: { ok: false } },\n )\n if (!cancelled) setCanEditOrgs(Boolean(featureCheck.result?.ok))\n } catch (err) {\n console.error('Failed to check features:', err)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id])\n\n const selectedOrgId = initialUser?.organizationId ? String(initialUser.organizationId) : null\n const preloadedTenants = React.useMemo(() => {\n if (!selectedTenantId) return null\n const name = initialUser?.tenantId === selectedTenantId\n ? (initialUser?.tenantName ?? selectedTenantId)\n : selectedTenantId\n return [{ id: selectedTenantId, name, isActive: true }]\n }, [initialUser, selectedTenantId])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const userHasPassword = initialUser?.hasPassword !== false\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n { id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },\n {\n id: 'password',\n label: userHasPassword\n ? t('auth.users.form.field.newPassword', 'New Password')\n : t('auth.users.form.field.setPassword', 'Set Password'),\n type: 'password' as const,\n description: [\n userHasPassword\n ? t('auth.users.form.field.passwordChangeHint', 'Leave blank to keep current password')\n : t('auth.users.form.field.passwordInviteHint', 'Optionally set a password for this user (they were invited via email)'),\n passwordDescription,\n ].filter(Boolean).join('. '),\n },\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n setAclData({ isSuperAdmin: false, features: [], organizations: null })\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n required\n tenants={preloadedTenants}\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? (value.length > 0 ? value : null) : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n includeInactiveIds={selectedOrgId ? [selectedOrgId] : undefined}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t, userHasPassword])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = ['email', 'name', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) base.splice(2, 0, 'tenantId')\n return base\n }, [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (id\n ? (\n <AclEditor\n kind=\"user\"\n targetId={String(id)}\n canEditOrganizations={canEditOrgs}\n value={aclData}\n onChange={setAclData}\n userRoles={initialUser?.roles || []}\n currentUserIsSuperAdmin={actorIsSuperAdmin}\n tenantId={selectedTenantId ?? null}\n />\n )\n : null),\n },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (id && initialUser\n ? (\n <WidgetVisibilityEditor\n kind=\"user\"\n targetId={String(id)}\n tenantId={selectedTenantId ?? null}\n organizationId={initialUser?.organizationId ?? null}\n ref={widgetEditorRef}\n />\n ) : null\n ),\n },\n {\n id: 'consents',\n title: t('auth.users.form.group.consents', 'Consents'),\n column: 2,\n component: () => (id ? <UserConsentsPanel userId={String(id)} /> : null),\n },\n ], [aclData, actorIsSuperAdmin, canEditOrgs, detailFieldIds, id, initialUser, selectedTenantId, t])\n\n const initialValues = React.useMemo(() => {\n if (initialUser) {\n return {\n email: initialUser.email,\n name: initialUser.name ?? '',\n password: '',\n tenantId: initialUser.tenantId,\n organizationId: initialUser.organizationId,\n roles: initialUser.roleIds,\n ...customFieldValues,\n }\n }\n return {\n email: '',\n name: '',\n password: '',\n tenantId: selectedTenantId ?? null,\n organizationId: null,\n roles: [],\n ...customFieldValues,\n }\n }, [initialUser, customFieldValues, selectedTenantId])\n\n return (\n <Page>\n <PageBody>\n {error && (\n <div className=\"p-4 mb-4 bg-red-50 border border-red-200 rounded text-red-800\">\n {error}\n </div>\n )}\n <CrudForm<EditUserFormValues>\n title={t('auth.users.form.title.edit', 'Edit User')}\n backHref=\"/backend/users\"\n versionHistory={{ resourceKind: 'auth.user', resourceId: id ? String(id) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('auth.users.form.loading', 'Loading user data...')}\n submitLabel={t('auth.users.form.action.save', 'Save')}\n cancelHref=\"/backend/users\"\n extraActions={id && !userHasPassword ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={resendingInvite}\n onClick={handleResendInvite}\n >\n {resendingInvite\n ? t('auth.users.form.action.resendingInvite', 'Sending...')\n : t('auth.users.form.action.resendInvite', 'Resend Invite')}\n </Button>\n ) : undefined}\n successRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.updated', 'User saved'))}&type=success`}\n onSubmit={async (values) => {\n if (!id) return\n const customFields = collectCustomFieldValues(values)\n const payload = {\n id: id ? String(id) : '',\n email: values.email,\n name: typeof values.name === 'string' && values.name.trim().length ? values.name.trim() : undefined,\n password: values.password && values.password.trim() ? values.password : undefined,\n organizationId: values.organizationId ? values.organizationId : undefined,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n await updateCrud('auth/users', payload)\n await updateCrud('auth/users/acl', { userId: id, ...aclData }, {\n errorMessage: t('auth.users.form.errors.aclUpdate', 'Failed to update user access control'),\n })\n await widgetEditorRef.current?.save()\n try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}\n }}\n onDelete={async () => {\n await deleteCrud('auth/users', String(id), {\n errorMessage: t('auth.users.form.errors.delete', 'Failed to delete user'),\n })\n }}\n deleteRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.deleted', 'User deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { AclEditor, type AclData } from '@open-mercato/core/modules/auth/components/AclEditor'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { WidgetVisibilityEditor, type WidgetVisibilityEditorHandle } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\n\ntype EditUserFormValues = {\n email: string\n name: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype LoadedUser = {\n id: string\n email: string\n name: string | null\n organizationId: string | null\n tenantId: string | null\n tenantName: string | null\n organizationName: string | null\n roles: string[]\n roleIds: string[]\n hasPassword: boolean\n}\n\ntype UserApiItem = {\n id?: string | null\n email?: string | null\n name?: string | null\n organizationId?: string | null\n tenantId?: string | null\n tenantName?: string | null\n organizationName?: string | null\n roles?: unknown\n roleIds?: unknown\n hasPassword?: boolean\n}\n\ntype UserListResponse = {\n items?: UserApiItem[]\n isSuperAdmin?: boolean\n}\n\ntype FeatureCheckResponse = {\n ok?: boolean\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n includeInactiveIds={includeInactiveIds}\n tenantId={tenantId}\n />\n )\n}\n\nexport default function EditUserPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n const [initialUser, setInitialUser] = React.useState<LoadedUser | null>(null)\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [canEditOrgs, setCanEditOrgs] = React.useState(false)\n const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })\n const [customFieldValues, setCustomFieldValues] = React.useState<Record<string, unknown>>({})\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const widgetEditorRef = React.useRef<WidgetVisibilityEditorHandle | null>(null)\n const [resendingInvite, setResendingInvite] = React.useState(false)\n\n const handleResendInvite = React.useCallback(async () => {\n if (!id) return\n setResendingInvite(true)\n try {\n const { ok, result } = await apiCall<{ ok?: boolean; warning?: string }>('/api/auth/users/resend-invite', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n })\n if (ok) {\n if (result?.warning === 'invite_email_failed') {\n flash(tRef.current('auth.users.flash.inviteEmailFailed', 'Invite token created but the email could not be sent. Please check your email provider configuration.'), 'warning')\n } else {\n flash(tRef.current('auth.users.flash.inviteSent', 'Invitation email sent'), 'success')\n }\n }\n } catch (err) {\n console.error('Failed to resend invite:', err)\n flash(tRef.current('auth.users.form.errors.inviteResend', 'Failed to send invitation email'), 'error')\n } finally {\n setResendingInvite(false)\n }\n }, [id])\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n if (!id) {\n setLoading(false)\n setError(tRef.current('auth.users.form.errors.noId', 'No user ID provided'))\n return\n }\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n setCustomFieldValues({})\n try {\n const { ok, result } = await apiCall<UserListResponse>(\n `/api/auth/users?id=${encodeURIComponent(String(id))}&page=1&pageSize=1`,\n )\n if (!ok) throw new Error('load_failed')\n const item = Array.isArray(result?.items) ? result?.items?.[0] : undefined\n if (!cancelled) {\n setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n setActorResolved(true)\n if (!item) {\n setError(tRef.current('auth.users.form.errors.notFound', 'User not found'))\n setCustomFieldValues({})\n setInitialUser(null)\n setSelectedTenantId(null)\n } else {\n const roleNames = Array.isArray(item.roles)\n ? item.roles\n .map((role) => (typeof role === 'string' ? role : role == null ? '' : String(role)))\n .filter((role) => role.trim().length > 0)\n : []\n const roleIds = Array.isArray(item.roleIds)\n ? (item.roleIds as string[]).filter((rid) => typeof rid === 'string' && rid.trim().length > 0)\n : []\n setInitialUser({\n id: item.id ? String(item.id) : String(id),\n email: item.email ? String(item.email) : '',\n name: item.name ? String(item.name) : null,\n organizationId: item.organizationId ? String(item.organizationId) : null,\n tenantId: item.tenantId ? String(item.tenantId) : null,\n tenantName: item.tenantName ? String(item.tenantName) : null,\n organizationName: item.organizationName ? String(item.organizationName) : null,\n roles: roleNames,\n roleIds: roleIds.length > 0 ? roleIds : roleNames,\n hasPassword: item.hasPassword !== false,\n })\n setSelectedTenantId(item.tenantId ? String(item.tenantId) : null)\n const custom = extractCustomFieldEntries(item as Record<string, unknown>)\n setCustomFieldValues(custom)\n }\n }\n } catch (err) {\n console.error('Failed to load user:', err)\n if (!cancelled) setError(tRef.current('auth.users.form.errors.load', 'Failed to load user data'))\n if (!cancelled) setCustomFieldValues({})\n if (!cancelled) setActorResolved(true)\n }\n if (!cancelled) setLoading(false)\n try {\n const featureCheck = await apiCall<FeatureCheckResponse>(\n '/api/auth/feature-check',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['directory.organizations.view'] }),\n },\n { fallback: { ok: false } },\n )\n if (!cancelled) setCanEditOrgs(Boolean(featureCheck.result?.ok))\n } catch (err) {\n console.error('Failed to check features:', err)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id])\n\n const selectedOrgId = initialUser?.organizationId ? String(initialUser.organizationId) : null\n const preloadedTenants = React.useMemo(() => {\n if (!selectedTenantId) return null\n const name = initialUser?.tenantId === selectedTenantId\n ? (initialUser?.tenantName ?? selectedTenantId)\n : selectedTenantId\n return [{ id: selectedTenantId, name, isActive: true }]\n }, [initialUser, selectedTenantId])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const userHasPassword = initialUser?.hasPassword !== false\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n { id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },\n {\n id: 'password',\n label: userHasPassword\n ? t('auth.users.form.field.newPassword', 'New Password')\n : t('auth.users.form.field.setPassword', 'Set Password'),\n type: 'password' as const,\n description: [\n userHasPassword\n ? t('auth.users.form.field.passwordChangeHint', 'Leave blank to keep current password')\n : t('auth.users.form.field.passwordInviteHint', 'Optionally set a password for this user (they were invited via email)'),\n passwordDescription,\n ].filter(Boolean).join('. '),\n },\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n setAclData({ isSuperAdmin: false, features: [], organizations: null })\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n required\n tenants={preloadedTenants}\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? (value.length > 0 ? value : null) : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n includeInactiveIds={selectedOrgId ? [selectedOrgId] : undefined}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t, userHasPassword])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = ['email', 'name', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) base.splice(2, 0, 'tenantId')\n return base\n }, [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (id\n ? (\n <AclEditor\n kind=\"user\"\n targetId={String(id)}\n canEditOrganizations={canEditOrgs}\n value={aclData}\n onChange={setAclData}\n userRoles={initialUser?.roles || []}\n currentUserIsSuperAdmin={actorIsSuperAdmin}\n tenantId={selectedTenantId ?? null}\n />\n )\n : null),\n },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (id && initialUser\n ? (\n <WidgetVisibilityEditor\n kind=\"user\"\n targetId={String(id)}\n tenantId={selectedTenantId ?? null}\n organizationId={initialUser?.organizationId ?? null}\n ref={widgetEditorRef}\n />\n ) : null\n ),\n },\n {\n id: 'consents',\n title: t('auth.users.form.group.consents', 'Consents'),\n column: 2,\n component: () => (id ? <UserConsentsPanel userId={String(id)} /> : null),\n },\n ], [aclData, actorIsSuperAdmin, canEditOrgs, detailFieldIds, id, initialUser, selectedTenantId, t])\n\n const initialValues = React.useMemo(() => {\n if (initialUser) {\n return {\n email: initialUser.email,\n name: initialUser.name ?? '',\n password: '',\n tenantId: initialUser.tenantId,\n organizationId: initialUser.organizationId,\n roles: initialUser.roleIds,\n ...customFieldValues,\n }\n }\n return {\n email: '',\n name: '',\n password: '',\n tenantId: selectedTenantId ?? null,\n organizationId: null,\n roles: [],\n ...customFieldValues,\n }\n }, [initialUser, customFieldValues, selectedTenantId])\n\n return (\n <Page>\n <PageBody>\n {error && (\n <div className=\"p-4 mb-4 bg-red-50 border border-red-200 rounded text-red-800\">\n {error}\n </div>\n )}\n <CrudForm<EditUserFormValues>\n title={t('auth.users.form.title.edit', 'Edit User')}\n backHref=\"/backend/users\"\n versionHistory={{ resourceKind: 'auth.user', resourceId: id ? String(id) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('auth.users.form.loading', 'Loading user data...')}\n submitLabel={t('auth.users.form.action.save', 'Save')}\n cancelHref=\"/backend/users\"\n extraActions={id && !userHasPassword ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={resendingInvite}\n onClick={handleResendInvite}\n >\n {resendingInvite\n ? t('auth.users.form.action.resendingInvite', 'Sending...')\n : t('auth.users.form.action.resendInvite', 'Resend Invite')}\n </Button>\n ) : undefined}\n successRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.updated', 'User saved'))}&type=success`}\n onSubmit={async (values) => {\n if (!id) return\n const customFields = collectCustomFieldValues(values)\n const payload = {\n id: id ? String(id) : '',\n email: values.email,\n name: normalizeDisplayNameInput(values.name),\n password: values.password && values.password.trim() ? values.password : undefined,\n organizationId: values.organizationId ? values.organizationId : undefined,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n await updateCrud('auth/users', payload)\n await updateCrud('auth/users/acl', { userId: id, ...aclData }, {\n errorMessage: t('auth.users.form.errors.aclUpdate', 'Failed to update user access control'),\n })\n await widgetEditorRef.current?.save()\n try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}\n }}\n onDelete={async () => {\n await deleteCrud('auth/users', String(id), {\n errorMessage: t('auth.users.form.errors.delete', 'Failed to delete user'),\n })\n }}\n deleteRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.deleted', 'User deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAmGI,cAqTE,YArTF;AAlGJ,YAAY,WAAW;AACvB,SAAS,SAAS;AAClB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAA0E;AACnF,SAAS,eAAe;AACxB,SAAS,YAAY,kBAAkB;AACvC,SAAS,gCAAgC;AACzC,SAAS,iBAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,8BAAiE;AAC1E,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,iCAAiC;AAC1C,SAAS,4BAA4B,yBAAyB;AAC9D,SAAS,yBAAyB;AAClC,SAAS,iCAAiC;AAsD1C,SAAS,mCAAmC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuC;AACrC,QAAM,gBAAgB,MAAM,OAAsB,QAAQ;AAC1D,QAAM,cAAc,MAAM,OAAO,KAAK;AACtC,QAAM,eAAe,MAAM,YAAY,CAAC,SAAwB;AAC9D,aAAS,QAAQ,IAAI;AAAA,EACvB,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,YAAY,SAAS;AACxB,kBAAY,UAAU;AACtB,oBAAc,UAAU;AACxB;AAAA,IACF;AACA,QAAI,cAAc,YAAY,UAAU;AACtC,oBAAc,UAAU;AACxB,eAAS,IAAI;AAAA,IACf;AAAA,EACF,GAAG,CAAC,UAAU,QAAQ,CAAC;AAEvB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI;AAAA,MACJ;AAAA,MACA,UAAU;AAAA,MACV,UAAQ;AAAA,MACR,oBAAkB;AAAA,MAClB,WAAU;AAAA,MACV;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;AAEe,SAAR,aAA8B,EAAE,OAAO,GAAiC;AAC7E,QAAM,KAAK,QAAQ;AACnB,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,MAAM,OAAO,CAAC;AAC3B,OAAK,UAAU;AACf,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAA4B,IAAI;AAC5E,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAwB,IAAI;AAClF,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAC1D,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAkB,EAAE,cAAc,OAAO,UAAU,CAAC,GAAG,eAAe,KAAK,CAAC;AAChH,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAkC,CAAC,CAAC;AAC5F,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,KAAK;AAC9D,QAAM,kBAAkB,MAAM,OAA4C,IAAI;AAC9E,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,KAAK;AAElE,QAAM,qBAAqB,MAAM,YAAY,YAAY;AACvD,QAAI,CAAC,GAAI;AACT,uBAAmB,IAAI;AACvB,QAAI;AACF,YAAM,EAAE,IAAI,OAAO,IAAI,MAAM,QAA4C,iCAAiC;AAAA,QACxG,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,GAAG,CAAC;AAAA,MAC7B,CAAC;AACD,UAAI,IAAI;AACN,YAAI,QAAQ,YAAY,uBAAuB;AAC7C,gBAAM,KAAK,QAAQ,sCAAsC,uGAAuG,GAAG,SAAS;AAAA,QAC9K,OAAO;AACL,gBAAM,KAAK,QAAQ,+BAA+B,uBAAuB,GAAG,SAAS;AAAA,QACvF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,4BAA4B,GAAG;AAC7C,YAAM,KAAK,QAAQ,uCAAuC,iCAAiC,GAAG,OAAO;AAAA,IACvG,UAAE;AACA,yBAAmB,KAAK;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,EAAE,CAAC;AACP,QAAM,iBAAiB,MAAM,QAAQ,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAClE,QAAM,uBAAuB,MAAM;AAAA,IACjC,MAAM,2BAA2B,gBAAgB,CAAC;AAAA,IAClD,CAAC,gBAAgB,CAAC;AAAA,EACpB;AACA,QAAM,sBAAsB,MAAM,QAAQ,MACxC,uBACI,EAAE,mCAAmC,yCAAyC,EAAE,cAAc,qBAAqB,CAAC,IACpH,QACH,CAAC,sBAAsB,CAAC,CAAC;AAE5B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,IAAI;AACP,iBAAW,KAAK;AAChB,eAAS,KAAK,QAAQ,+BAA+B,qBAAqB,CAAC;AAC3E;AAAA,IACF;AACA,QAAI,YAAY;AAChB,mBAAe,OAAO;AACpB,iBAAW,IAAI;AACf,eAAS,IAAI;AACb,2BAAqB,CAAC,CAAC;AACvB,UAAI;AACF,cAAM,EAAE,IAAI,OAAO,IAAI,MAAM;AAAA,UAC3B,sBAAsB,mBAAmB,OAAO,EAAE,CAAC,CAAC;AAAA,QACtD;AACA,YAAI,CAAC,GAAI,OAAM,IAAI,MAAM,aAAa;AACtC,cAAM,OAAO,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC,IAAI;AACjE,YAAI,CAAC,WAAW;AACd,+BAAqB,QAAQ,QAAQ,YAAY,CAAC;AAClD,2BAAiB,IAAI;AACrB,cAAI,CAAC,MAAM;AACT,qBAAS,KAAK,QAAQ,mCAAmC,gBAAgB,CAAC;AAC1E,iCAAqB,CAAC,CAAC;AACvB,2BAAe,IAAI;AACnB,gCAAoB,IAAI;AAAA,UAC1B,OAAO;AACL,kBAAM,YAAY,MAAM,QAAQ,KAAK,KAAK,IACtC,KAAK,MACF,IAAI,CAAC,SAAU,OAAO,SAAS,WAAW,OAAO,QAAQ,OAAO,KAAK,OAAO,IAAI,CAAE,EAClF,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,SAAS,CAAC,IAC1C,CAAC;AACL,kBAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IACrC,KAAK,QAAqB,OAAO,CAAC,QAAQ,OAAO,QAAQ,YAAY,IAAI,KAAK,EAAE,SAAS,CAAC,IAC3F,CAAC;AACL,2BAAe;AAAA,cACb,IAAI,KAAK,KAAK,OAAO,KAAK,EAAE,IAAI,OAAO,EAAE;AAAA,cACzC,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,IAAI;AAAA,cACzC,MAAM,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AAAA,cACtC,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,cACpE,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,cAClD,YAAY,KAAK,aAAa,OAAO,KAAK,UAAU,IAAI;AAAA,cACxD,kBAAkB,KAAK,mBAAmB,OAAO,KAAK,gBAAgB,IAAI;AAAA,cAC1E,OAAO;AAAA,cACP,SAAS,QAAQ,SAAS,IAAI,UAAU;AAAA,cACxC,aAAa,KAAK,gBAAgB;AAAA,YACpC,CAAC;AACD,gCAAoB,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI,IAAI;AAChE,kBAAM,SAAS,0BAA0B,IAA+B;AACxE,iCAAqB,MAAM;AAAA,UAC7B;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,wBAAwB,GAAG;AACzC,YAAI,CAAC,UAAW,UAAS,KAAK,QAAQ,+BAA+B,0BAA0B,CAAC;AAChG,YAAI,CAAC,UAAW,sBAAqB,CAAC,CAAC;AACvC,YAAI,CAAC,UAAW,kBAAiB,IAAI;AAAA,MACvC;AACA,UAAI,CAAC,UAAW,YAAW,KAAK;AAChC,UAAI;AACF,cAAM,eAAe,MAAM;AAAA,UACzB;AAAA,UACA;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC,8BAA8B,EAAE,CAAC;AAAA,UACrE;AAAA,UACA,EAAE,UAAU,EAAE,IAAI,MAAM,EAAE;AAAA,QAC5B;AACA,YAAI,CAAC,UAAW,gBAAe,QAAQ,aAAa,QAAQ,EAAE,CAAC;AAAA,MACjE,SAAS,KAAK;AACZ,gBAAQ,MAAM,6BAA6B,GAAG;AAAA,MAChD;AAAA,IACF;AACA,SAAK;AACL,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAK;AAAA,EAClC,GAAG,CAAC,EAAE,CAAC;AAEP,QAAM,gBAAgB,aAAa,iBAAiB,OAAO,YAAY,cAAc,IAAI;AACzF,QAAM,mBAAmB,MAAM,QAAQ,MAAM;AAC3C,QAAI,CAAC,iBAAkB,QAAO;AAC9B,UAAM,OAAO,aAAa,aAAa,mBAClC,aAAa,cAAc,mBAC5B;AACJ,WAAO,CAAC,EAAE,IAAI,kBAAkB,MAAM,UAAU,KAAK,CAAC;AAAA,EACxD,GAAG,CAAC,aAAa,gBAAgB,CAAC;AAKlC,QAAM,kBAAkB,MAAM,YAAY,OAAO,UAA+C;AAC9F,QAAI,CAAC,cAAe,QAAO,CAAC;AAC5B,QAAI,mBAAmB;AACrB,UAAI,CAAC,iBAAkB,QAAO,CAAC;AAC/B,aAAO,iBAAiB,OAAO,EAAE,UAAU,iBAAiB,CAAC;AAAA,IAC/D;AACA,WAAO,iBAAiB,KAAK;AAAA,EAC/B,GAAG,CAAC,mBAAmB,eAAe,gBAAgB,CAAC;AAEvD,QAAM,kBAAkB,aAAa,gBAAgB;AACrD,QAAM,SAAsB,MAAM,QAAQ,MAAM;AAC9C,UAAM,QAAqB;AAAA,MACzB,EAAE,IAAI,SAAS,OAAO,EAAE,+BAA+B,OAAO,GAAG,MAAM,QAAQ,UAAU,KAAK;AAAA,MAC9F,EAAE,IAAI,QAAQ,OAAO,EAAE,8BAA8B,cAAc,GAAG,MAAM,OAAO;AAAA,MACnF;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,kBACH,EAAE,qCAAqC,cAAc,IACrD,EAAE,qCAAqC,cAAc;AAAA,QACzD,MAAM;AAAA,QACN,aAAa;AAAA,UACX,kBACI,EAAE,4CAA4C,sCAAsC,IACpF,EAAE,4CAA4C,uEAAuE;AAAA,UACzH;AAAA,QACF,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAAA,MAC7B;AAAA,IACF;AACA,QAAI,mBAAmB;AACrB,YAAM,KAAK;AAAA,QACT,IAAI;AAAA,QACJ,OAAO,EAAE,gCAAgC,QAAQ;AAAA,QACjD,MAAM;AAAA,QACN,UAAU;AAAA,QACV,WAAW,CAAC,EAAE,OAAO,SAAS,MAAM;AAClC,gBAAM,kBAAkB,OAAO,UAAU,WACrC,QACC,OAAO,qBAAqB,WAAW,mBAAmB;AAC/D,iBACE;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO;AAAA,cACP,UAAU,CAAC,SAAS;AAClB,sBAAM,WAAW,QAAQ;AACzB,yBAAS,QAAQ;AACjB,oCAAoB,QAAQ;AAC5B,2BAAW,EAAE,cAAc,OAAO,UAAU,CAAC,GAAG,eAAe,KAAK,CAAC;AAAA,cACvE;AAAA,cACA,oBAAkB;AAAA,cAClB,WAAU;AAAA,cACV,UAAQ;AAAA,cACR,SAAS;AAAA;AAAA,UACX;AAAA,QAEJ;AAAA,MACF,CAAC;AAAA,IACH;AACA,UAAM,KAAK;AAAA,MACT,IAAI;AAAA,MACJ,OAAO,EAAE,sCAAsC,cAAc;AAAA,MAC7D,MAAM;AAAA,MACN,UAAU;AAAA,MACV,WAAW,CAAC,EAAE,IAAAA,KAAI,OAAO,SAAS,MAAM;AACtC,cAAM,kBAAkB,OAAO,UAAU,WAAY,MAAM,SAAS,IAAI,QAAQ,OAAQ;AACxF,eACE;AAAA,UAAC;AAAA;AAAA,YACC,SAASA;AAAA,YACT,OAAO;AAAA,YACP,UAAU,CAAC,SAAS,SAAS,QAAQ,IAAI;AAAA,YACzC,UAAU;AAAA,YACV,oBAAoB,gBAAgB,CAAC,aAAa,IAAI;AAAA;AAAA,QACxD;AAAA,MAEJ;AAAA,IACF,CAAC;AACD,UAAM,KAAK,EAAE,IAAI,SAAS,OAAO,EAAE,+BAA+B,OAAO,GAAG,MAAM,QAAQ,aAAa,gBAAgB,CAAC;AACxH,WAAO;AAAA,EACT,GAAG,CAAC,mBAAmB,iBAAiB,qBAAqB,kBAAkB,eAAe,kBAAkB,GAAG,eAAe,CAAC;AAEnI,QAAM,iBAAiB,MAAM,QAAQ,MAAM;AACzC,UAAM,OAAiB,CAAC,SAAS,QAAQ,YAAY,kBAAkB,OAAO;AAC9E,QAAI,kBAAmB,MAAK,OAAO,GAAG,GAAG,UAAU;AACnD,WAAO;AAAA,EACT,GAAG,CAAC,iBAAiB,CAAC;AAEtB,QAAM,SAA0B,MAAM,QAAQ,MAAM;AAAA,IAClD,EAAE,IAAI,WAAW,OAAO,EAAE,iCAAiC,SAAS,GAAG,QAAQ,GAAG,QAAQ,eAAe;AAAA,IACzG,EAAE,IAAI,UAAU,OAAO,EAAE,sCAAsC,aAAa,GAAG,QAAQ,GAAG,MAAM,eAAe;AAAA,IAC/G;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,gCAAgC,QAAQ;AAAA,MACjD,QAAQ;AAAA,MACR,WAAW,MAAO,KAEd;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAU,OAAO,EAAE;AAAA,UACnB,sBAAsB;AAAA,UACtB,OAAO;AAAA,UACP,UAAU;AAAA,UACV,WAAW,aAAa,SAAS,CAAC;AAAA,UAClC,yBAAyB;AAAA,UACzB,UAAU,oBAAoB;AAAA;AAAA,MAChC,IAEA;AAAA,IACN;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,iCAAiC,mBAAmB;AAAA,MAC7D,QAAQ;AAAA,MACR,WAAW,MAAO,MAAM,cAEpB;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAU,OAAO,EAAE;AAAA,UACnB,UAAU,oBAAoB;AAAA,UAC9B,gBAAgB,aAAa,kBAAkB;AAAA,UAC/C,KAAK;AAAA;AAAA,MACP,IACE;AAAA,IAER;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,kCAAkC,UAAU;AAAA,MACrD,QAAQ;AAAA,MACR,WAAW,MAAO,KAAK,oBAAC,qBAAkB,QAAQ,OAAO,EAAE,GAAG,IAAK;AAAA,IACrE;AAAA,EACF,GAAG,CAAC,SAAS,mBAAmB,aAAa,gBAAgB,IAAI,aAAa,kBAAkB,CAAC,CAAC;AAElG,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,aAAa;AACf,aAAO;AAAA,QACL,OAAO,YAAY;AAAA,QACnB,MAAM,YAAY,QAAQ;AAAA,QAC1B,UAAU;AAAA,QACV,UAAU,YAAY;AAAA,QACtB,gBAAgB,YAAY;AAAA,QAC5B,OAAO,YAAY;AAAA,QACnB,GAAG;AAAA,MACL;AAAA,IACF;AACA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU,oBAAoB;AAAA,MAC9B,gBAAgB;AAAA,MAChB,OAAO,CAAC;AAAA,MACR,GAAG;AAAA,IACL;AAAA,EACF,GAAG,CAAC,aAAa,mBAAmB,gBAAgB,CAAC;AAErD,SACE,oBAAC,QACC,+BAAC,YACE;AAAA,aACC,oBAAC,SAAI,WAAU,iEACZ,iBACH;AAAA,IAEF;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,8BAA8B,WAAW;AAAA,QAClD,UAAS;AAAA,QACT,gBAAgB,EAAE,cAAc,aAAa,YAAY,KAAK,OAAO,EAAE,IAAI,GAAG;AAAA,QAC9E;AAAA,QACA;AAAA,QACA,UAAU,EAAE,KAAK;AAAA,QACjB;AAAA,QACA,WAAW;AAAA,QACX,gBAAgB,EAAE,2BAA2B,sBAAsB;AAAA,QACnE,aAAa,EAAE,+BAA+B,MAAM;AAAA,QACpD,YAAW;AAAA,QACX,cAAc,MAAM,CAAC,kBACnB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,UAAU;AAAA,YACV,SAAS;AAAA,YAER,4BACG,EAAE,0CAA0C,YAAY,IACxD,EAAE,uCAAuC,eAAe;AAAA;AAAA,QAC9D,IACE;AAAA,QACJ,iBAAiB,wBAAwB,mBAAmB,EAAE,4BAA4B,YAAY,CAAC,CAAC;AAAA,QACxG,UAAU,OAAO,WAAW;AAC1B,cAAI,CAAC,GAAI;AACT,gBAAM,eAAe,yBAAyB,MAAM;AACpD,gBAAM,UAAU;AAAA,YACd,IAAI,KAAK,OAAO,EAAE,IAAI;AAAA,YACtB,OAAO,OAAO;AAAA,YACd,MAAM,0BAA0B,OAAO,IAAI;AAAA,YAC3C,UAAU,OAAO,YAAY,OAAO,SAAS,KAAK,IAAI,OAAO,WAAW;AAAA,YACxE,gBAAgB,OAAO,iBAAiB,OAAO,iBAAiB;AAAA,YAChE,OAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC;AAAA,YACrD,GAAI,OAAO,KAAK,YAAY,EAAE,SAAS,EAAE,aAAa,IAAI,CAAC;AAAA,UAC7D;AACA,gBAAM,WAAW,cAAc,OAAO;AACtC,gBAAM,WAAW,kBAAkB,EAAE,QAAQ,IAAI,GAAG,QAAQ,GAAG;AAAA,YAC7D,cAAc,EAAE,oCAAoC,sCAAsC;AAAA,UAC5F,CAAC;AACD,gBAAM,gBAAgB,SAAS,KAAK;AACpC,cAAI;AAAE,mBAAO,cAAc,IAAI,MAAM,oBAAoB,CAAC;AAAA,UAAE,QAAQ;AAAA,UAAC;AAAA,QACvE;AAAA,QACA,UAAU,YAAY;AACpB,gBAAM,WAAW,cAAc,OAAO,EAAE,GAAG;AAAA,YACzC,cAAc,EAAE,iCAAiC,uBAAuB;AAAA,UAC1E,CAAC;AAAA,QACH;AAAA,QACA,gBAAgB,wBAAwB,mBAAmB,EAAE,4BAA4B,cAAc,CAAC,CAAC;AAAA;AAAA,IAC3G;AAAA,KACF,GACF;AAEJ;",
|
|
6
6
|
"names": ["id"]
|
|
7
7
|
}
|
|
@@ -15,6 +15,7 @@ import { RadioGroup } from "@open-mercato/ui/primitives/radio";
|
|
|
15
15
|
import { RadioField } from "@open-mercato/ui/primitives/radio-field";
|
|
16
16
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
17
17
|
import { formatPasswordRequirements, getPasswordPolicy } from "@open-mercato/shared/lib/auth/passwordPolicy";
|
|
18
|
+
import { normalizeDisplayNameInput } from "@open-mercato/core/modules/auth/lib/displayName";
|
|
18
19
|
function TenantAwareOrganizationSelectInput({
|
|
19
20
|
fieldId,
|
|
20
21
|
value,
|
|
@@ -278,7 +279,7 @@ function CreateUserPage() {
|
|
|
278
279
|
const customFields = collectCustomFieldValues(values);
|
|
279
280
|
const payload = {
|
|
280
281
|
email: values.email,
|
|
281
|
-
name:
|
|
282
|
+
name: normalizeDisplayNameInput(values.name),
|
|
282
283
|
organizationId: values.organizationId ? values.organizationId : null,
|
|
283
284
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
284
285
|
...Object.keys(customFields).length ? { customFields } : {}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/auth/backend/users/create/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup } from '@open-mercato/ui/primitives/radio'\nimport { RadioField } from '@open-mercato/ui/primitives/radio-field'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\ntype CreateUserFormValues = {\n email: string\n name: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype UserListResponse = {\n isSuperAdmin?: boolean\n}\n\ntype WidgetCatalogResponse = {\n items?: Array<{ id?: string | null; title?: string | null; description?: string | null }>\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n tenantId={tenantId}\n includeInactiveIds={includeInactiveIds}\n />\n )\n}\n\nexport default function CreateUserPage() {\n const t = useT()\n const [widgetCatalog, setWidgetCatalog] = React.useState<Array<{ id: string; title: string; description: string | null }>>([])\n const [widgetLoading, setWidgetLoading] = React.useState(true)\n const [widgetError, setWidgetError] = React.useState<string | null>(null)\n const [widgetMode, setWidgetMode] = React.useState<'inherit' | 'override'>('inherit')\n const [selectedWidgets, setSelectedWidgets] = React.useState<string[]>([])\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const [sendInviteEmail, setSendInviteEmail] = React.useState(false)\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadCatalog() {\n setWidgetLoading(true)\n setWidgetError(null)\n try {\n const { ok, result } = await apiCall<WidgetCatalogResponse>('/api/dashboards/widgets/catalog')\n if (!ok) throw new Error('request_failed')\n if (!cancelled) {\n const rawItems: unknown[] = Array.isArray(result?.items) ? result?.items ?? [] : []\n const normalized = rawItems\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const idValue = entry.id\n const titleValue = entry.title\n const descriptionValue = entry.description\n const id = typeof idValue === 'string' ? idValue : null\n if (!id || !id.length) return null\n const title = typeof titleValue === 'string' && titleValue.length > 0 ? titleValue : id\n const description = typeof descriptionValue === 'string' && descriptionValue.length > 0 ? descriptionValue : null\n return { id, title, description }\n })\n .filter((item): item is { id: string; title: string; description: string | null } => item !== null)\n setWidgetCatalog(normalized)\n }\n } catch (err) {\n console.error('Failed to load dashboard widget catalog', err)\n if (!cancelled) {\n setWidgetError(t(\n 'auth.users.widgets.errors.load',\n 'Unable to load dashboard widgets. You can configure them later from the user page.',\n ))\n }\n } finally {\n if (!cancelled) setWidgetLoading(false)\n }\n }\n loadCatalog()\n return () => { cancelled = true }\n }, [t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<UserListResponse>('/api/auth/users?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch (err) {\n console.error('Failed to resolve actor super admin flag', err)\n } finally {\n if (!cancelled) setActorResolved(true)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const toggleWidget = React.useCallback((id: string) => {\n setSelectedWidgets((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n { id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },\n {\n id: 'sendInviteEmail',\n label: t('auth.users.form.field.sendInviteEmail', 'Send password setup link via email'),\n type: 'custom',\n component: () => (\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n className=\"size-4\"\n checked={sendInviteEmail}\n onChange={(e) => setSendInviteEmail(e.target.checked)}\n />\n {t('auth.users.form.field.sendInviteEmailHint', 'Invite user to set their own password via a secure email link')}\n </label>\n ),\n },\n ...(!sendInviteEmail ? [{\n id: 'password',\n label: t('auth.users.form.field.password', 'Password'),\n type: 'password' as const,\n required: true,\n description: passwordDescription,\n }] : []),\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n required\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? value : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, selectedTenantId, sendInviteEmail, t])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = sendInviteEmail\n ? ['email', 'name', 'sendInviteEmail', 'organizationId', 'roles']\n : ['email', 'name', 'sendInviteEmail', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) {\n const orgIdx = base.indexOf('organizationId')\n base.splice(orgIdx, 0, 'tenantId')\n }\n return base\n }, [actorIsSuperAdmin, sendInviteEmail])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (\n <div className=\"text-sm text-muted-foreground\">\n {t('auth.users.form.aclHint', 'ACL can be edited after creating the user.')}\n </div>\n ),\n },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (\n <DashboardWidgetSelector\n catalog={widgetCatalog}\n loading={widgetLoading}\n error={widgetError}\n mode={widgetMode}\n onModeChange={setWidgetMode}\n selected={selectedWidgets}\n onToggle={toggleWidget}\n />\n ),\n },\n ], [detailFieldIds, t, widgetCatalog, widgetError, widgetLoading, widgetMode, selectedWidgets, toggleWidget])\n\n const initialValues = React.useMemo<Partial<CreateUserFormValues>>(\n () => ({\n email: '',\n name: '',\n password: '',\n tenantId: null,\n organizationId: null,\n roles: [],\n }),\n [],\n )\n\n return (\n <Page>\n <PageBody>\n <CrudForm<CreateUserFormValues>\n title={t('auth.users.form.title.create', 'Create User')}\n backHref=\"/backend/users\"\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n submitLabel={t('auth.users.form.action.create', 'Create')}\n cancelHref=\"/backend/users\"\n successRedirect={`/backend/users?flash=${encodeURIComponent(\n sendInviteEmail\n ? t('auth.users.flash.createdWithInvite', 'User created and invitation sent')\n : t('auth.users.flash.created', 'User created')\n )}&type=success`}\n onSubmit={async (values) => {\n const customFields = collectCustomFieldValues(values)\n const payload: Record<string, unknown> = {\n email: values.email,\n name: typeof values.name === 'string' && values.name.trim().length ? values.name.trim() : undefined,\n organizationId: values.organizationId ? values.organizationId : null,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n if (sendInviteEmail) {\n payload.sendInviteEmail = true\n } else {\n payload.password = values.password\n }\n if (actorIsSuperAdmin) {\n const rawTenant = typeof values.tenantId === 'string' ? values.tenantId.trim() : null\n payload.tenantId = rawTenant && rawTenant.length ? rawTenant : null\n }\n const { result: created } = await createCrud<{ id?: string; _warning?: string }>('auth/users', payload)\n const newUserId = typeof created?.id === 'string' ? created.id : null\n if (created?._warning === 'invite_email_failed') {\n const msg = t('auth.users.flash.createdEmailFailed', 'User created but invitation email could not be sent. You can resend it from the user page.')\n window.location.href = `/backend/users?flash=${encodeURIComponent(msg)}&type=warning`\n return\n }\n\n if (widgetMode === 'override' && newUserId) {\n await updateCrud('dashboards/users/widgets', {\n userId: newUserId,\n mode: 'override',\n widgetIds: selectedWidgets,\n organizationId: values.organizationId ? values.organizationId : null,\n tenantId: actorIsSuperAdmin\n ? (typeof values.tenantId === 'string' && values.tenantId.length ? values.tenantId : null)\n : null,\n }, {\n errorMessage: t('auth.users.form.errors.widgetsAssign', 'Failed to assign dashboard widgets to the new user'),\n })\n }\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\nfunction DashboardWidgetSelector({\n catalog,\n loading,\n error,\n mode,\n onModeChange,\n selected,\n onToggle,\n}: {\n catalog: Array<{ id: string; title: string; description: string | null }>\n loading: boolean\n error: string | null\n mode: 'inherit' | 'override'\n onModeChange: (mode: 'inherit' | 'override') => void\n selected: string[]\n onToggle: (id: string) => void\n}) {\n const t = useT()\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('auth.users.widgets.loading', 'Loading widgets\u2026')}\n </div>\n )\n }\n\n return (\n <div className=\"space-y-3\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n {!error && (\n <>\n <RadioGroup\n className=\"flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\"\n value={mode}\n onValueChange={(next) => onModeChange(next as 'inherit' | 'override')}\n >\n <RadioField\n value=\"inherit\"\n label={t('auth.users.widgets.mode.inherit', 'Inherit from roles')}\n />\n <RadioField\n value=\"override\"\n label={t('auth.users.widgets.mode.override', 'Override for this user')}\n />\n </RadioGroup>\n {mode === 'override' && (\n <div className=\"space-y-2\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => onToggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{widget.title}</div>\n {widget.description ? <div className=\"text-xs text-muted-foreground\">{widget.description}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n {mode === 'inherit' && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('auth.users.widgets.mode.hint', 'New users inherit widgets from their assigned roles. Override to pick a custom set.')}\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup } from '@open-mercato/ui/primitives/radio'\nimport { RadioField } from '@open-mercato/ui/primitives/radio-field'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\n\ntype CreateUserFormValues = {\n email: string\n name: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype UserListResponse = {\n isSuperAdmin?: boolean\n}\n\ntype WidgetCatalogResponse = {\n items?: Array<{ id?: string | null; title?: string | null; description?: string | null }>\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n tenantId={tenantId}\n includeInactiveIds={includeInactiveIds}\n />\n )\n}\n\nexport default function CreateUserPage() {\n const t = useT()\n const [widgetCatalog, setWidgetCatalog] = React.useState<Array<{ id: string; title: string; description: string | null }>>([])\n const [widgetLoading, setWidgetLoading] = React.useState(true)\n const [widgetError, setWidgetError] = React.useState<string | null>(null)\n const [widgetMode, setWidgetMode] = React.useState<'inherit' | 'override'>('inherit')\n const [selectedWidgets, setSelectedWidgets] = React.useState<string[]>([])\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const [sendInviteEmail, setSendInviteEmail] = React.useState(false)\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadCatalog() {\n setWidgetLoading(true)\n setWidgetError(null)\n try {\n const { ok, result } = await apiCall<WidgetCatalogResponse>('/api/dashboards/widgets/catalog')\n if (!ok) throw new Error('request_failed')\n if (!cancelled) {\n const rawItems: unknown[] = Array.isArray(result?.items) ? result?.items ?? [] : []\n const normalized = rawItems\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const idValue = entry.id\n const titleValue = entry.title\n const descriptionValue = entry.description\n const id = typeof idValue === 'string' ? idValue : null\n if (!id || !id.length) return null\n const title = typeof titleValue === 'string' && titleValue.length > 0 ? titleValue : id\n const description = typeof descriptionValue === 'string' && descriptionValue.length > 0 ? descriptionValue : null\n return { id, title, description }\n })\n .filter((item): item is { id: string; title: string; description: string | null } => item !== null)\n setWidgetCatalog(normalized)\n }\n } catch (err) {\n console.error('Failed to load dashboard widget catalog', err)\n if (!cancelled) {\n setWidgetError(t(\n 'auth.users.widgets.errors.load',\n 'Unable to load dashboard widgets. You can configure them later from the user page.',\n ))\n }\n } finally {\n if (!cancelled) setWidgetLoading(false)\n }\n }\n loadCatalog()\n return () => { cancelled = true }\n }, [t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<UserListResponse>('/api/auth/users?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch (err) {\n console.error('Failed to resolve actor super admin flag', err)\n } finally {\n if (!cancelled) setActorResolved(true)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const toggleWidget = React.useCallback((id: string) => {\n setSelectedWidgets((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n { id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },\n {\n id: 'sendInviteEmail',\n label: t('auth.users.form.field.sendInviteEmail', 'Send password setup link via email'),\n type: 'custom',\n component: () => (\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n className=\"size-4\"\n checked={sendInviteEmail}\n onChange={(e) => setSendInviteEmail(e.target.checked)}\n />\n {t('auth.users.form.field.sendInviteEmailHint', 'Invite user to set their own password via a secure email link')}\n </label>\n ),\n },\n ...(!sendInviteEmail ? [{\n id: 'password',\n label: t('auth.users.form.field.password', 'Password'),\n type: 'password' as const,\n required: true,\n description: passwordDescription,\n }] : []),\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n required\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? value : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, selectedTenantId, sendInviteEmail, t])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = sendInviteEmail\n ? ['email', 'name', 'sendInviteEmail', 'organizationId', 'roles']\n : ['email', 'name', 'sendInviteEmail', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) {\n const orgIdx = base.indexOf('organizationId')\n base.splice(orgIdx, 0, 'tenantId')\n }\n return base\n }, [actorIsSuperAdmin, sendInviteEmail])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (\n <div className=\"text-sm text-muted-foreground\">\n {t('auth.users.form.aclHint', 'ACL can be edited after creating the user.')}\n </div>\n ),\n },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (\n <DashboardWidgetSelector\n catalog={widgetCatalog}\n loading={widgetLoading}\n error={widgetError}\n mode={widgetMode}\n onModeChange={setWidgetMode}\n selected={selectedWidgets}\n onToggle={toggleWidget}\n />\n ),\n },\n ], [detailFieldIds, t, widgetCatalog, widgetError, widgetLoading, widgetMode, selectedWidgets, toggleWidget])\n\n const initialValues = React.useMemo<Partial<CreateUserFormValues>>(\n () => ({\n email: '',\n name: '',\n password: '',\n tenantId: null,\n organizationId: null,\n roles: [],\n }),\n [],\n )\n\n return (\n <Page>\n <PageBody>\n <CrudForm<CreateUserFormValues>\n title={t('auth.users.form.title.create', 'Create User')}\n backHref=\"/backend/users\"\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n submitLabel={t('auth.users.form.action.create', 'Create')}\n cancelHref=\"/backend/users\"\n successRedirect={`/backend/users?flash=${encodeURIComponent(\n sendInviteEmail\n ? t('auth.users.flash.createdWithInvite', 'User created and invitation sent')\n : t('auth.users.flash.created', 'User created')\n )}&type=success`}\n onSubmit={async (values) => {\n const customFields = collectCustomFieldValues(values)\n const payload: Record<string, unknown> = {\n email: values.email,\n name: normalizeDisplayNameInput(values.name),\n organizationId: values.organizationId ? values.organizationId : null,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n if (sendInviteEmail) {\n payload.sendInviteEmail = true\n } else {\n payload.password = values.password\n }\n if (actorIsSuperAdmin) {\n const rawTenant = typeof values.tenantId === 'string' ? values.tenantId.trim() : null\n payload.tenantId = rawTenant && rawTenant.length ? rawTenant : null\n }\n const { result: created } = await createCrud<{ id?: string; _warning?: string }>('auth/users', payload)\n const newUserId = typeof created?.id === 'string' ? created.id : null\n if (created?._warning === 'invite_email_failed') {\n const msg = t('auth.users.flash.createdEmailFailed', 'User created but invitation email could not be sent. You can resend it from the user page.')\n window.location.href = `/backend/users?flash=${encodeURIComponent(msg)}&type=warning`\n return\n }\n\n if (widgetMode === 'override' && newUserId) {\n await updateCrud('dashboards/users/widgets', {\n userId: newUserId,\n mode: 'override',\n widgetIds: selectedWidgets,\n organizationId: values.organizationId ? values.organizationId : null,\n tenantId: actorIsSuperAdmin\n ? (typeof values.tenantId === 'string' && values.tenantId.length ? values.tenantId : null)\n : null,\n }, {\n errorMessage: t('auth.users.form.errors.widgetsAssign', 'Failed to assign dashboard widgets to the new user'),\n })\n }\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\nfunction DashboardWidgetSelector({\n catalog,\n loading,\n error,\n mode,\n onModeChange,\n selected,\n onToggle,\n}: {\n catalog: Array<{ id: string; title: string; description: string | null }>\n loading: boolean\n error: string | null\n mode: 'inherit' | 'override'\n onModeChange: (mode: 'inherit' | 'override') => void\n selected: string[]\n onToggle: (id: string) => void\n}) {\n const t = useT()\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('auth.users.widgets.loading', 'Loading widgets\u2026')}\n </div>\n )\n }\n\n return (\n <div className=\"space-y-3\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n {!error && (\n <>\n <RadioGroup\n className=\"flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\"\n value={mode}\n onValueChange={(next) => onModeChange(next as 'inherit' | 'override')}\n >\n <RadioField\n value=\"inherit\"\n label={t('auth.users.widgets.mode.inherit', 'Inherit from roles')}\n />\n <RadioField\n value=\"override\"\n label={t('auth.users.widgets.mode.override', 'Override for this user')}\n />\n </RadioGroup>\n {mode === 'override' && (\n <div className=\"space-y-2\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => onToggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{widget.title}</div>\n {widget.description ? <div className=\"text-xs text-muted-foreground\">{widget.description}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n {mode === 'inherit' && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('auth.users.widgets.mode.hint', 'New users inherit widgets from their assigned roles. Override to pick a custom set.')}\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -32,14 +32,11 @@ import InviteUserEmail from "@open-mercato/core/modules/auth/emails/InviteUserEm
|
|
|
32
32
|
import { INVITE_TOKEN_TTL_MS } from "@open-mercato/core/modules/auth/lib/inviteToken";
|
|
33
33
|
import { getSecurityEmailBaseUrl } from "@open-mercato/shared/lib/url";
|
|
34
34
|
import { generateAuthToken, hashAuthToken } from "@open-mercato/core/modules/auth/lib/tokenHash";
|
|
35
|
+
import { normalizeDisplayNameInput } from "@open-mercato/core/modules/auth/lib/displayName";
|
|
35
36
|
const passwordSchema = buildPasswordSchema();
|
|
36
37
|
const displayNameSchema = z.preprocess(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const trimmed = value.trim();
|
|
40
|
-
return trimmed.length ? trimmed : void 0;
|
|
41
|
-
},
|
|
42
|
-
z.string().trim().min(1).max(120).optional()
|
|
38
|
+
normalizeDisplayNameInput,
|
|
39
|
+
z.string().trim().min(1).max(120).nullable().optional()
|
|
43
40
|
);
|
|
44
41
|
const createSchema = z.object({
|
|
45
42
|
email: z.string().email(),
|
|
@@ -494,7 +491,7 @@ const updateUserCommand = {
|
|
|
494
491
|
entity.organizationId = before.organizationId ?? null;
|
|
495
492
|
entity.tenantId = before.tenantId ?? null;
|
|
496
493
|
entity.passwordHash = before.passwordHash ?? null;
|
|
497
|
-
entity.name = before.name ??
|
|
494
|
+
entity.name = before.name ?? null;
|
|
498
495
|
entity.isConfirmed = before.isConfirmed;
|
|
499
496
|
}
|
|
500
497
|
});
|
|
@@ -609,7 +606,7 @@ const deleteUserCommand = {
|
|
|
609
606
|
user.organizationId = before.organizationId ?? null;
|
|
610
607
|
user.tenantId = before.tenantId ?? null;
|
|
611
608
|
user.passwordHash = before.passwordHash ?? null;
|
|
612
|
-
user.name = before.name ??
|
|
609
|
+
user.name = before.name ?? null;
|
|
613
610
|
user.isConfirmed = before.isConfirmed;
|
|
614
611
|
await em.flush();
|
|
615
612
|
} else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/commands/users.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { UniqueConstraintViolationException } from '@mikro-orm/core'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { User, UserRole, Role, UserAcl, Session, PasswordReset } from '@open-mercato/core/modules/auth/data/entities'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { z } from 'zod'\nimport {\n loadCustomFieldSnapshot,\n buildCustomFieldResetMap,\n diffCustomFieldChanges,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'\nimport { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'\nimport notificationTypes from '@open-mercato/core/modules/auth/notifications'\nimport { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport InviteUserEmail from '@open-mercato/core/modules/auth/emails/InviteUserEmail'\nimport { INVITE_TOKEN_TTL_MS } from '@open-mercato/core/modules/auth/lib/inviteToken'\nimport { getSecurityEmailBaseUrl } from '@open-mercato/shared/lib/url'\nimport { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'\n\ntype SerializedUser = {\n email: string\n organizationId: string | null\n tenantId: string | null\n roles: string[]\n name: string | null\n isConfirmed: boolean\n custom?: Record<string, unknown>\n}\n\ntype UserAclSnapshot = {\n tenantId: string\n features: string[] | null\n isSuperAdmin: boolean\n organizations: string[] | null\n}\n\ntype UserUndoSnapshot = {\n id: string\n email: string\n organizationId: string | null\n tenantId: string | null\n passwordHash: string | null\n name: string | null\n isConfirmed: boolean\n roles: string[]\n acls: UserAclSnapshot[]\n custom?: Record<string, unknown>\n}\n\ntype UserSnapshots = {\n view: SerializedUser\n undo: UserUndoSnapshot\n}\n\nconst passwordSchema = buildPasswordSchema()\n\nconst displayNameSchema = z.preprocess(\n (value) => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n },\n z.string().trim().min(1).max(120).optional(),\n)\n\nconst createSchema = z.object({\n email: z.string().email(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n sendInviteEmail: z.boolean().optional(),\n organizationId: z.string().uuid(),\n roles: z.array(z.string()).optional(),\n}).refine(\n (data) => data.password || data.sendInviteEmail,\n { message: 'Either password or sendInviteEmail is required', path: ['password'] },\n)\n\nconst updateSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email().optional(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n organizationId: z.string().uuid().optional(),\n roles: z.array(z.string()).optional(),\n})\n\nexport const userCrudEvents: CrudEventsConfig = {\n module: 'auth',\n entity: 'user',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nexport const userCrudIndexer: CrudIndexerConfig = {\n entityType: E.auth.user,\n buildUpsertPayload: (ctx) => ({\n entityType: E.auth.user,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n buildDeletePayload: (ctx) => ({\n entityType: E.auth.user,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nasync function notifyRoleChanges(\n ctx: CommandRuntimeContext,\n user: User,\n assignedRoles: string[],\n revokedRoles: string[],\n): Promise<void> {\n const tenantId = user.tenantId ? String(user.tenantId) : null\n if (!tenantId) return\n const organizationId = user.organizationId ? String(user.organizationId) : null\n\n try {\n const notificationService = resolveNotificationService(ctx.container)\n if (assignedRoles.length) {\n const assignedType = notificationTypes.find((type) => type.type === 'auth.role.assigned')\n if (assignedType) {\n const notificationInput = buildNotificationFromType(assignedType, {\n recipientUserId: String(user.id),\n sourceEntityType: 'auth:user',\n sourceEntityId: String(user.id),\n })\n await notificationService.create(notificationInput, { tenantId, organizationId })\n }\n }\n\n if (revokedRoles.length) {\n const revokedType = notificationTypes.find((type) => type.type === 'auth.role.revoked')\n if (revokedType) {\n const notificationInput = buildNotificationFromType(revokedType, {\n recipientUserId: String(user.id),\n sourceEntityType: 'auth:user',\n sourceEntityId: String(user.id),\n })\n await notificationService.create(notificationInput, { tenantId, organizationId })\n }\n }\n } catch (err) {\n console.error('[auth.users.roles] Failed to create notification:', err)\n }\n}\n\ntype CreateUserResult = { user: User; warning?: 'invite_email_failed' }\n\nconst createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResult> = {\n id: 'auth.users.create',\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(createSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: parsed.organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId: parsed.organizationId },\n )\n if (!organization) throw new CrudHttpError(400, { error: 'Organization not found' })\n\n const emailHash = computeEmailHash(parsed.email)\n const duplicate = await findOneWithDecryption(em, User, { $or: [{ email: parsed.email }, { emailHash }], deletedAt: null } as any, {}, { tenantId: null, organizationId: null })\n if (duplicate) await throwDuplicateEmailError()\n\n let passwordHash: string | null = null\n if (parsed.password) {\n const { hash } = await import('bcryptjs')\n passwordHash = await hash(parsed.password, 10)\n }\n const tenantId = organization.tenant?.id ? String(organization.tenant.id) : null\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let user: User\n try {\n user = await de.createOrmEntity({\n entity: User,\n data: {\n email: parsed.email,\n name: parsed.name,\n emailHash,\n passwordHash,\n isConfirmed: true,\n organizationId: parsed.organizationId,\n tenantId,\n },\n })\n } catch (error) {\n if (isUniqueViolation(error)) await throwDuplicateEmailError()\n throw error\n }\n\n let assignedRoles: string[] = []\n if (Array.isArray(parsed.roles) && parsed.roles.length) {\n await syncUserRoles(em, user, parsed.roles, tenantId)\n assignedRoles = await loadUserRoleNames(em, String(user.id))\n }\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: tenantId,\n values: custom,\n })\n\n let inviteEmailSent = false\n if (parsed.sendInviteEmail) {\n const inviteResult = await sendInviteToUser(em, user)\n inviteEmailSent = inviteResult.emailSent\n }\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: user,\n identifiers: {\n id: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n if (assignedRoles.length && !parsed.sendInviteEmail) {\n await notifyRoleChanges(ctx, user, assignedRoles, [])\n }\n\n const warning = (parsed.sendInviteEmail && !inviteEmailSent) ? 'invite_email_failed' as const : undefined\n\n return { user, warning }\n },\n captureAfter: async (_input, { user }, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const roles = await loadUserRoleNames(em, String(user.id))\n const custom = await loadUserCustomSnapshot(\n em,\n String(user.id),\n user.tenantId ? String(user.tenantId) : null,\n user.organizationId ? String(user.organizationId) : null\n )\n return serializeUser(user, roles, custom)\n },\n buildLog: async ({ result: { user }, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const roles = await loadUserRoleNames(em, String(user.id))\n const custom = await loadUserCustomSnapshot(\n em,\n String(user.id),\n user.tenantId ? String(user.tenantId) : null,\n user.organizationId ? String(user.organizationId) : null\n )\n const snapshot = captureUserSnapshots(user, roles, undefined, custom)\n return {\n actionLabel: translate('auth.audit.users.create', 'Create user'),\n resourceKind: 'auth.user',\n resourceId: String(user.id),\n tenantId: user.tenantId ? String(user.tenantId) : null,\n snapshotAfter: snapshot.view,\n payload: {\n undo: {\n after: snapshot.undo,\n },\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const userId = typeof logEntry?.resourceId === 'string' ? logEntry.resourceId : null\n if (!userId) return\n const snapshot = logEntry?.snapshotAfter as SerializedUser | undefined\n const em = (ctx.container.resolve('em') as EntityManager)\n await em.nativeDelete(UserAcl, { user: userId })\n await em.nativeDelete(UserRole, { user: userId })\n await em.nativeDelete(Session, { user: userId })\n await em.nativeDelete(PasswordReset, { user: userId })\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n if (snapshot?.custom && Object.keys(snapshot.custom).length) {\n const reset = buildCustomFieldResetMap(undefined, snapshot.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: userId,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n values: reset,\n notify: false,\n })\n }\n }\n const removed = await de.deleteOrmEntity({\n entity: User,\n where: { id: userId, deletedAt: null } as FilterQuery<User>,\n soft: false,\n })\n\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: removed,\n identifiers: {\n id: userId,\n organizationId: snapshot?.organizationId ?? null,\n tenantId: snapshot?.tenantId ?? null,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n await invalidateUserCache(ctx, userId)\n },\n}\n\nasync function sendInviteToUser(\n em: EntityManager,\n user: User,\n): Promise<{ emailSent: boolean }> {\n const rawToken = generateAuthToken()\n const tokenHash = hashAuthToken(rawToken)\n const expiresAt = new Date(Date.now() + INVITE_TOKEN_TTL_MS)\n const row = em.create(PasswordReset, { user, token: tokenHash, expiresAt, createdAt: new Date() })\n await em.persist(row).flush()\n\n const base = getSecurityEmailBaseUrl()\n const inviteUrl = `${base}/reset/${rawToken}`\n\n const { translate } = await resolveTranslations()\n const subject = translate('auth.email.invite.subject', 'You have been invited')\n const copy = {\n preview: translate('auth.email.invite.preview', 'Set up your account'),\n title: translate('auth.email.invite.title', 'You have been invited'),\n body: translate('auth.email.invite.body', 'An administrator has created an account for you. Click the link below to set your password. This link will expire in 48 hours.'),\n cta: translate('auth.email.invite.cta', 'Set up your password'),\n hint: translate('auth.email.invite.hint', 'If you did not expect this invitation, you can safely ignore this email.'),\n }\n\n let emailSent = true\n try {\n await sendEmail({ to: user.email, subject, react: InviteUserEmail({ inviteUrl, copy }) })\n } catch (err) {\n console.error('[auth.users.invite] Failed to send invitation email:', err)\n emailSent = false\n }\n\n return { emailSent }\n}\n\nfunction isUniqueViolation(error: unknown): boolean {\n if (error instanceof UniqueConstraintViolationException) return true\n if (!error || typeof error !== 'object') return false\n const code = (error as { code?: string }).code\n if (code === '23505') return true\n const messageRaw = (error as { message?: string })?.message\n const message = typeof messageRaw === 'string' ? messageRaw : ''\n return message.toLowerCase().includes('duplicate key')\n}\n\nconst updateUserCommand: CommandHandler<Record<string, unknown>, User> = {\n id: 'auth.users.update',\n async prepare(rawInput, ctx) {\n const { parsed } = parseWithCustomFields(updateSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const existing = await findOneWithDecryption(em, User, { id: parsed.id, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n if (!existing) throw new CrudHttpError(404, { error: 'User not found' })\n const roles = await loadUserRoleNames(em, parsed.id)\n const acls = await loadUserAclSnapshots(em, parsed.id)\n const custom = await loadUserCustomSnapshot(\n em,\n parsed.id,\n existing.tenantId ? String(existing.tenantId) : null,\n existing.organizationId ? String(existing.organizationId) : null\n )\n return { before: captureUserSnapshots(existing, roles, acls, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const rolesBefore = Array.isArray(parsed.roles)\n ? await loadUserRoleNames(em, parsed.id)\n : null\n\n if (parsed.email !== undefined) {\n const emailHash = computeEmailHash(parsed.email)\n const duplicate = await findOneWithDecryption(\n em,\n User,\n {\n $or: [{ email: parsed.email }, { emailHash }],\n deletedAt: null,\n id: { $ne: parsed.id } as any,\n } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n if (duplicate) await throwDuplicateEmailError()\n }\n\n let hashed: string | null = null\n let emailHash: string | null = null\n if (parsed.password) {\n const { hash } = await import('bcryptjs')\n hashed = await hash(parsed.password, 10)\n }\n if (parsed.email !== undefined) {\n emailHash = computeEmailHash(parsed.email)\n }\n\n let tenantId: string | null | undefined\n if (parsed.organizationId !== undefined) {\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: parsed.organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId: parsed.organizationId ?? null },\n )\n if (!organization) throw new CrudHttpError(400, { error: 'Organization not found' })\n tenantId = organization.tenant?.id ? String(organization.tenant.id) : null\n }\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let user: User | null\n try {\n user = await de.updateOrmEntity({\n entity: User,\n where: { id: parsed.id, deletedAt: null } as FilterQuery<User>,\n apply: (entity) => {\n if (parsed.email !== undefined) {\n entity.email = parsed.email\n entity.emailHash = emailHash\n }\n if (parsed.name !== undefined) {\n entity.name = parsed.name\n }\n if (parsed.organizationId !== undefined) {\n entity.organizationId = parsed.organizationId\n entity.tenantId = tenantId ?? null\n }\n if (hashed) entity.passwordHash = hashed\n },\n })\n } catch (error) {\n if (isUniqueViolation(error)) await throwDuplicateEmailError()\n throw error\n }\n if (!user) throw new CrudHttpError(404, { error: 'User not found' })\n\n if (hashed) {\n await em.nativeDelete(Session, { user: parsed.id })\n }\n\n if (Array.isArray(parsed.roles)) {\n await syncUserRoles(em, user, parsed.roles, user.tenantId ? String(user.tenantId) : tenantId ?? null)\n }\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : tenantId ?? null,\n values: custom,\n })\n\n const identifiers = {\n id: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : tenantId ?? null,\n }\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: user,\n identifiers,\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n if (Array.isArray(parsed.roles) && rolesBefore) {\n const rolesAfter = await loadUserRoleNames(em, String(user.id))\n const { assigned, revoked } = diffRoleChanges(rolesBefore, rolesAfter)\n if (assigned.length || revoked.length) {\n await notifyRoleChanges(ctx, user, assigned, revoked)\n }\n }\n\n await invalidateUserCache(ctx, parsed.id)\n\n return user\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const roles = await loadUserRoleNames(em, String(result.id))\n const custom = await loadUserCustomSnapshot(\n em,\n String(result.id),\n result.tenantId ? String(result.tenantId) : null,\n result.organizationId ? String(result.organizationId) : null\n )\n return serializeUser(result, roles, custom)\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as UserSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterRoles = await loadUserRoleNames(em, String(result.id))\n const afterCustom = await loadUserCustomSnapshot(\n em,\n String(result.id),\n result.tenantId ? String(result.tenantId) : null,\n result.organizationId ? String(result.organizationId) : null\n )\n const afterSnapshots = captureUserSnapshots(result, afterRoles, undefined, afterCustom)\n const after = afterSnapshots.view\n const changes = buildChanges(before ?? null, after as Record<string, unknown>, ['email', 'organizationId', 'tenantId', 'name', 'isConfirmed'])\n if (before && !arrayEquals(before.roles, afterRoles)) {\n changes.roles = { from: before.roles, to: afterRoles }\n }\n const customDiff = diffCustomFieldChanges(before?.custom, afterCustom)\n for (const [key, diff] of Object.entries(customDiff)) {\n changes[`cf_${key}`] = diff\n }\n return {\n actionLabel: translate('auth.audit.users.update', 'Update user'),\n resourceKind: 'auth.user',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n changes,\n snapshotBefore: before ?? null,\n snapshotAfter: after,\n payload: {\n undo: {\n before: beforeUndo,\n after: afterSnapshots.undo,\n },\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<UndoPayload<UserUndoSnapshot>>(logEntry)\n const before = payload?.before\n const after = payload?.after\n if (!before) return\n const userId = before.id\n const em = (ctx.container.resolve('em') as EntityManager)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const updated = await de.updateOrmEntity({\n entity: User,\n where: { id: userId, deletedAt: null } as FilterQuery<User>,\n apply: (entity) => {\n entity.email = before.email\n entity.organizationId = before.organizationId ?? null\n entity.tenantId = before.tenantId ?? null\n entity.passwordHash = before.passwordHash ?? null\n entity.name = before.name ?? undefined\n entity.isConfirmed = before.isConfirmed\n },\n })\n\n if (updated) {\n await syncUserRoles(em, updated, before.roles, before.tenantId)\n await em.flush()\n }\n\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: before.id,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updated,\n identifiers: {\n id: before.id,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n await invalidateUserCache(ctx, userId)\n },\n}\n\nconst deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, User> = {\n id: 'auth.users.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'User id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const existing = await findOneWithDecryption(em, User, { id, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n if (!existing) return {}\n const roles = await loadUserRoleNames(em, id)\n const acls = await loadUserAclSnapshots(em, id)\n const custom = await loadUserCustomSnapshot(\n em,\n id,\n existing.tenantId ? String(existing.tenantId) : null,\n existing.organizationId ? String(existing.organizationId) : null\n )\n return { before: captureUserSnapshots(existing, roles, acls, custom) }\n },\n async execute(input, ctx) {\n const id = requireId(input, 'User id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n\n await em.nativeDelete(UserAcl, { user: id })\n await em.nativeDelete(UserRole, { user: id })\n await em.nativeDelete(Session, { user: id })\n await em.nativeDelete(PasswordReset, { user: id })\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const user = await de.deleteOrmEntity({\n entity: User,\n where: { id, deletedAt: null } as FilterQuery<User>,\n soft: false,\n })\n if (!user) throw new CrudHttpError(404, { error: 'User not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: user,\n identifiers: {\n id: String(id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : null,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n await invalidateUserCache(ctx, id)\n\n return user\n },\n buildLog: async ({ snapshots, input, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as UserSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const id = requireId(input, 'User id required')\n return {\n actionLabel: translate('auth.audit.users.delete', 'Delete user'),\n resourceKind: 'auth.user',\n resourceId: id,\n snapshotBefore: before ?? null,\n tenantId: before?.tenantId ?? null,\n payload: {\n undo: {\n before: beforeUndo,\n },\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<UndoPayload<UserUndoSnapshot>>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager)\n let user = await findOneWithDecryption(em, User, { id: before.id }, {}, { tenantId: null, organizationId: null })\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n\n if (user) {\n if (user.deletedAt) {\n user.deletedAt = null\n }\n user.email = before.email\n user.organizationId = before.organizationId ?? null\n user.tenantId = before.tenantId ?? null\n user.passwordHash = before.passwordHash ?? null\n user.name = before.name ?? undefined\n user.isConfirmed = before.isConfirmed\n await em.flush()\n } else {\n user = await de.createOrmEntity({\n entity: User,\n data: {\n id: before.id,\n email: before.email,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n passwordHash: before.passwordHash ?? null,\n name: before.name ?? null,\n isConfirmed: before.isConfirmed,\n },\n })\n }\n\n if (!user) return\n\n await em.nativeDelete(UserRole, { user: before.id })\n await syncUserRoles(em, user, before.roles, before.tenantId)\n\n await restoreUserAcls(em, user, before.acls)\n\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: before.id,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n\n await invalidateUserCache(ctx, before.id)\n },\n}\n\nregisterCommand(createUserCommand)\nregisterCommand(updateUserCommand)\nregisterCommand(deleteUserCommand)\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nasync function resolveRole(\n em: EntityManager,\n value: string,\n normalizedTenantId: string | null,\n): Promise<Role | null> {\n if (UUID_RE.test(value)) {\n const where: Record<string, unknown> = { id: value }\n if (normalizedTenantId !== null) {\n where.tenantId = normalizedTenantId\n }\n return findOneWithDecryption(em, Role, where as any, {}, { tenantId: normalizedTenantId, organizationId: null })\n }\n return findOneWithDecryption(em, Role, { name: value, tenantId: normalizedTenantId }, {}, { tenantId: normalizedTenantId, organizationId: null })\n}\n\nasync function syncUserRoles(em: EntityManager, user: User, desiredRoles: string[], tenantId: string | null) {\n const unique = Array.from(new Set(desiredRoles.map((role) => role.trim()).filter(Boolean)))\n const normalizedTenantId = normalizeTenantId(tenantId ?? null) ?? null\n\n const resolvedRoles: Role[] = []\n const missingRoles: string[] = []\n for (const value of unique) {\n const role = await resolveRole(em, value, normalizedTenantId)\n if (!role) {\n missingRoles.push(value)\n } else {\n resolvedRoles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((n) => `\"${n}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n const desiredIds = new Set(resolvedRoles.map((r) => String(r.id)))\n const currentLinks = await findWithDecryption(em, UserRole, { user }, {}, { tenantId: null, organizationId: null })\n const currentRoleIds = new Map(\n currentLinks.map((link) => {\n const roleId = String(link.role?.id ?? (link.role as unknown as string) ?? '')\n return [roleId, link] as const\n }),\n )\n\n for (const [roleId, link] of currentRoleIds.entries()) {\n if (!desiredIds.has(roleId) && link) {\n em.remove(link)\n }\n }\n\n for (const role of resolvedRoles) {\n if (!currentRoleIds.has(String(role.id))) {\n em.persist(em.create(UserRole, { user, role, createdAt: new Date() }))\n }\n }\n\n await em.flush()\n}\n\nasync function loadUserRoleNames(em: EntityManager, userId: string): Promise<string[]> {\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as unknown as User },\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const names = links\n .map((link) => link.role?.name ?? '')\n .filter((name): name is string => !!name)\n return Array.from(new Set(names)).sort((a, b) => a.localeCompare(b))\n}\n\nfunction serializeUser(user: User, roles: string[], custom?: Record<string, unknown> | null): SerializedUser {\n const payload: SerializedUser = {\n email: String(user.email ?? ''),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : null,\n roles,\n name: user.name ? String(user.name) : null,\n isConfirmed: Boolean(user.isConfirmed),\n }\n if (custom && Object.keys(custom).length) payload.custom = custom\n return payload\n}\n\nfunction captureUserSnapshots(\n user: User,\n roles: string[],\n acls: UserAclSnapshot[] = [],\n custom?: Record<string, unknown> | null\n): UserSnapshots {\n return {\n view: serializeUser(user, roles, custom),\n undo: {\n id: String(user.id),\n email: String(user.email ?? ''),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : null,\n passwordHash: user.passwordHash ? String(user.passwordHash) : null,\n name: user.name ? String(user.name) : null,\n isConfirmed: Boolean(user.isConfirmed),\n roles: [...roles],\n acls,\n ...(custom && Object.keys(custom).length ? { custom } : {}),\n },\n }\n}\n\nasync function loadUserAclSnapshots(em: EntityManager, userId: string): Promise<UserAclSnapshot[]> {\n const list = await findWithDecryption(em, UserAcl, { user: userId as unknown as User }, {}, { tenantId: null, organizationId: null })\n return list.map((acl) => ({\n tenantId: String(acl.tenantId),\n features: Array.isArray(acl.featuresJson) ? [...acl.featuresJson] : null,\n isSuperAdmin: Boolean(acl.isSuperAdmin),\n organizations: Array.isArray(acl.organizationsJson) ? [...acl.organizationsJson] : null,\n }))\n}\n\nasync function restoreUserAcls(em: EntityManager, user: User, acls: UserAclSnapshot[]) {\n await em.nativeDelete(UserAcl, { user: String(user.id) })\n for (const acl of acls) {\n const entity = em.create(UserAcl, {\n user,\n tenantId: acl.tenantId,\n featuresJson: acl.features ?? null,\n isSuperAdmin: acl.isSuperAdmin,\n organizationsJson: acl.organizations ?? null,\n createdAt: new Date(),\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\nasync function loadUserCustomSnapshot(\n em: EntityManager,\n id: string,\n tenantId: string | null,\n organizationId: string | null\n): Promise<Record<string, unknown>> {\n return await loadCustomFieldSnapshot(em, {\n entityId: E.auth.user,\n recordId: id,\n tenantId,\n organizationId,\n })\n}\n\nasync function invalidateUserCache(ctx: CommandRuntimeContext, userId: string) {\n try {\n const rbacService = ctx.container.resolve('rbacService') as { invalidateUserCache: (uid: string) => Promise<void> }\n await rbacService.invalidateUserCache(userId)\n } catch {\n // RBAC not available\n }\n\n try {\n const cache = ctx.container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<void> }\n if (cache?.deleteByTags) await cache.deleteByTags([`rbac:user:${userId}`])\n } catch {\n // cache not available\n }\n}\n\nfunction diffRoleChanges(before: string[], after: string[]) {\n const beforeSet = new Set(before)\n const afterSet = new Set(after)\n const assigned = after.filter((role) => !beforeSet.has(role))\n const revoked = before.filter((role) => !afterSet.has(role))\n return { assigned, revoked }\n}\n\nfunction arrayEquals(left: string[] | undefined, right: string[]): boolean {\n if (!left) return false\n if (left.length !== right.length) return false\n return left.every((value, idx) => value === right[idx])\n}\n\nasync function throwDuplicateEmailError(): Promise<never> {\n const { translate } = await resolveTranslations()\n const message = translate('auth.users.errors.emailExists', 'Email already in use')\n throw new CrudHttpError(400, {\n error: message,\n fieldErrors: { email: message },\n details: [{ path: ['email'], message, code: 'duplicate', origin: 'validation' }],\n })\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAI9B,SAAS,2BAA2B;AACpC,SAAS,0CAA0C;AAEnD,SAAS,MAAM,UAAU,MAAM,SAAS,SAAS,qBAAqB;AACtE,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA4C;AACrD,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,iCAAiC;AAC1C,SAAS,kCAAkC;AAC3C,OAAO,uBAAuB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,OAAO,qBAAqB;AAC5B,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,qBAAqB;AAqCjD,MAAM,iBAAiB,oBAAoB;AAE3C,MAAM,oBAAoB,EAAE;AAAA,EAC1B,CAAC,UAAU;AACT,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,UAAM,UAAU,MAAM,KAAK;AAC3B,WAAO,QAAQ,SAAS,UAAU;AAAA,EACpC;AAAA,EACA,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAC7C;AAEA,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,OAAO,EAAE,OAAO,EAAE,MAAM;AAAA,EACxB,MAAM;AAAA,EACN,UAAU,eAAe,SAAS;AAAA,EAClC,iBAAiB,EAAE,QAAQ,EAAE,SAAS;AAAA,EACtC,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,EAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACtC,CAAC,EAAE;AAAA,EACD,CAAC,SAAS,KAAK,YAAY,KAAK;AAAA,EAChC,EAAE,SAAS,kDAAkD,MAAM,CAAC,UAAU,EAAE;AAClF;AAEA,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS;AAAA,EACnC,MAAM;AAAA,EACN,UAAU,eAAe,SAAS;AAAA,EAClC,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC3C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACtC,CAAC;AAEM,MAAM,iBAAmC;AAAA,EAC9C,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,cAAc,CAAC,SAAS;AAAA,IACtB,IAAI,IAAI,YAAY;AAAA,IACpB,gBAAgB,IAAI,YAAY;AAAA,IAChC,UAAU,IAAI,YAAY;AAAA,EAC5B;AACF;AAEO,MAAM,kBAAqC;AAAA,EAChD,YAAY,EAAE,KAAK;AAAA,EACnB,oBAAoB,CAAC,SAAS;AAAA,IAC5B,YAAY,EAAE,KAAK;AAAA,IACnB,UAAU,IAAI,YAAY;AAAA,IAC1B,gBAAgB,IAAI,YAAY;AAAA,IAChC,UAAU,IAAI,YAAY;AAAA,EAC5B;AAAA,EACA,oBAAoB,CAAC,SAAS;AAAA,IAC5B,YAAY,EAAE,KAAK;AAAA,IACnB,UAAU,IAAI,YAAY;AAAA,IAC1B,gBAAgB,IAAI,YAAY;AAAA,IAChC,UAAU,IAAI,YAAY;AAAA,EAC5B;AACF;AAEA,eAAe,kBACb,KACA,MACA,eACA,cACe;AACf,QAAM,WAAW,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AACzD,MAAI,CAAC,SAAU;AACf,QAAM,iBAAiB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAE3E,MAAI;AACF,UAAM,sBAAsB,2BAA2B,IAAI,SAAS;AACpE,QAAI,cAAc,QAAQ;AACxB,YAAM,eAAe,kBAAkB,KAAK,CAAC,SAAS,KAAK,SAAS,oBAAoB;AACxF,UAAI,cAAc;AAChB,cAAM,oBAAoB,0BAA0B,cAAc;AAAA,UAChE,iBAAiB,OAAO,KAAK,EAAE;AAAA,UAC/B,kBAAkB;AAAA,UAClB,gBAAgB,OAAO,KAAK,EAAE;AAAA,QAChC,CAAC;AACD,cAAM,oBAAoB,OAAO,mBAAmB,EAAE,UAAU,eAAe,CAAC;AAAA,MAClF;AAAA,IACF;AAEA,QAAI,aAAa,QAAQ;AACvB,YAAM,cAAc,kBAAkB,KAAK,CAAC,SAAS,KAAK,SAAS,mBAAmB;AACtF,UAAI,aAAa;AACf,cAAM,oBAAoB,0BAA0B,aAAa;AAAA,UAC/D,iBAAiB,OAAO,KAAK,EAAE;AAAA,UAC/B,kBAAkB;AAAA,UAClB,gBAAgB,OAAO,KAAK,EAAE;AAAA,QAChC,CAAC;AACD,cAAM,oBAAoB,OAAO,mBAAmB,EAAE,UAAU,eAAe,CAAC;AAAA,MAClF;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,qDAAqD,GAAG;AAAA,EACxE;AACF;AAIA,MAAM,oBAA+E;AAAA,EACnF,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,QAAQ,OAAO,IAAI,sBAAsB,cAAc,QAAQ;AACvE,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AAEtC,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MACA,EAAE,IAAI,OAAO,eAAe;AAAA,MAC5B,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,MACvB,EAAE,UAAU,MAAM,gBAAgB,OAAO,eAAe;AAAA,IAC1D;AACA,QAAI,CAAC,aAAc,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,yBAAyB,CAAC;AAEnF,UAAM,YAAY,iBAAiB,OAAO,KAAK;AAC/C,UAAM,YAAY,MAAM,sBAAsB,IAAI,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,UAAU,CAAC,GAAG,WAAW,KAAK,GAAU,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAC/K,QAAI,UAAW,OAAM,yBAAyB;AAE9C,QAAI,eAA8B;AAClC,QAAI,OAAO,UAAU;AACnB,YAAM,EAAE,KAAK,IAAI,MAAM,OAAO,UAAU;AACxC,qBAAe,MAAM,KAAK,OAAO,UAAU,EAAE;AAAA,IAC/C;AACA,UAAM,WAAW,aAAa,QAAQ,KAAK,OAAO,aAAa,OAAO,EAAE,IAAI;AAE5E,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,GAAG,gBAAgB;AAAA,QAC9B,QAAQ;AAAA,QACR,MAAM;AAAA,UACJ,OAAO,OAAO;AAAA,UACd,MAAM,OAAO;AAAA,UACb;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,gBAAgB,OAAO;AAAA,UACvB;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,kBAAkB,KAAK,EAAG,OAAM,yBAAyB;AAC7D,YAAM;AAAA,IACR;AAEA,QAAI,gBAA0B,CAAC;AAC/B,QAAI,MAAM,QAAQ,OAAO,KAAK,KAAK,OAAO,MAAM,QAAQ;AACtD,YAAM,cAAc,IAAI,MAAM,OAAO,OAAO,QAAQ;AACpD,sBAAgB,MAAM,kBAAkB,IAAI,OAAO,KAAK,EAAE,CAAC;AAAA,IAC7D;AAEA,UAAM,qBAAqB;AAAA,MACzB,YAAY;AAAA,MACZ,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO,KAAK,EAAE;AAAA,MACxB,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,MACpE;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAED,QAAI,kBAAkB;AACtB,QAAI,OAAO,iBAAiB;AAC1B,YAAM,eAAe,MAAM,iBAAiB,IAAI,IAAI;AACpD,wBAAkB,aAAa;AAAA,IACjC;AAEA,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO,KAAK,EAAE;AAAA,QAClB,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,QACpE;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,QAAI,cAAc,UAAU,CAAC,OAAO,iBAAiB;AACnD,YAAM,kBAAkB,KAAK,MAAM,eAAe,CAAC,CAAC;AAAA,IACtD;AAEA,UAAM,UAAW,OAAO,mBAAmB,CAAC,kBAAmB,wBAAiC;AAEhG,WAAO,EAAE,MAAM,QAAQ;AAAA,EACzB;AAAA,EACA,cAAc,OAAO,QAAQ,EAAE,KAAK,GAAG,QAAQ;AAC7C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,QAAQ,MAAM,kBAAkB,IAAI,OAAO,KAAK,EAAE,CAAC;AACzD,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA,OAAO,KAAK,EAAE;AAAA,MACd,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,MACxC,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,IACtD;AACA,WAAO,cAAc,MAAM,OAAO,MAAM;AAAA,EAC1C;AAAA,EACA,UAAU,OAAO,EAAE,QAAQ,EAAE,KAAK,GAAG,IAAI,MAAM;AAC7C,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,QAAQ,MAAM,kBAAkB,IAAI,OAAO,KAAK,EAAE,CAAC;AACzD,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA,OAAO,KAAK,EAAE;AAAA,MACd,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,MACxC,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,IACtD;AACA,UAAM,WAAW,qBAAqB,MAAM,OAAO,QAAW,MAAM;AACpE,WAAO;AAAA,MACL,aAAa,UAAU,2BAA2B,aAAa;AAAA,MAC/D,cAAc;AAAA,MACd,YAAY,OAAO,KAAK,EAAE;AAAA,MAC1B,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,MAClD,eAAe,SAAS;AAAA,MACxB,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,OAAO,SAAS;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,SAAS,OAAO,UAAU,eAAe,WAAW,SAAS,aAAa;AAChF,QAAI,CAAC,OAAQ;AACb,UAAM,WAAW,UAAU;AAC3B,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,UAAM,GAAG,aAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,UAAM,GAAG,aAAa,UAAU,EAAE,MAAM,OAAO,CAAC;AAChD,UAAM,GAAG,aAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,UAAM,GAAG,aAAa,eAAe,EAAE,MAAM,OAAO,CAAC;AAErD,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAI,UAAU,UAAU,OAAO,KAAK,SAAS,MAAM,EAAE,QAAQ;AAC3D,YAAM,QAAQ,yBAAyB,QAAW,SAAS,MAAM;AACjE,UAAI,OAAO,KAAK,KAAK,EAAE,QAAQ;AAC7B,cAAM,qBAAqB;AAAA,UACzB,YAAY;AAAA,UACZ,UAAU,EAAE,KAAK;AAAA,UACjB,UAAU;AAAA,UACV,gBAAgB,SAAS;AAAA,UACzB,UAAU,SAAS;AAAA,UACnB,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,UAAU,MAAM,GAAG,gBAAgB;AAAA,MACvC,QAAQ;AAAA,MACR,OAAO,EAAE,IAAI,QAAQ,WAAW,KAAK;AAAA,MACrC,MAAM;AAAA,IACR,CAAC;AAED,UAAM,wBAAwB;AAAA,MAC5B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI;AAAA,QACJ,gBAAgB,UAAU,kBAAkB;AAAA,QAC5C,UAAU,UAAU,YAAY;AAAA,MAClC;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,UAAM,oBAAoB,KAAK,MAAM;AAAA,EACvC;AACF;AAEA,eAAe,iBACb,IACA,MACiC;AACjC,QAAM,WAAW,kBAAkB;AACnC,QAAM,YAAY,cAAc,QAAQ;AACxC,QAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,mBAAmB;AAC3D,QAAM,MAAM,GAAG,OAAO,eAAe,EAAE,MAAM,OAAO,WAAW,WAAW,WAAW,oBAAI,KAAK,EAAE,CAAC;AACjG,QAAM,GAAG,QAAQ,GAAG,EAAE,MAAM;AAE5B,QAAM,OAAO,wBAAwB;AACrC,QAAM,YAAY,GAAG,IAAI,UAAU,QAAQ;AAE3C,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAM,UAAU,UAAU,6BAA6B,uBAAuB;AAC9E,QAAM,OAAO;AAAA,IACX,SAAS,UAAU,6BAA6B,qBAAqB;AAAA,IACrE,OAAO,UAAU,2BAA2B,uBAAuB;AAAA,IACnE,MAAM,UAAU,0BAA0B,gIAAgI;AAAA,IAC1K,KAAK,UAAU,yBAAyB,sBAAsB;AAAA,IAC9D,MAAM,UAAU,0BAA0B,0EAA0E;AAAA,EACtH;AAEA,MAAI,YAAY;AAChB,MAAI;AACF,UAAM,UAAU,EAAE,IAAI,KAAK,OAAO,SAAS,OAAO,gBAAgB,EAAE,WAAW,KAAK,CAAC,EAAE,CAAC;AAAA,EAC1F,SAAS,KAAK;AACZ,YAAQ,MAAM,wDAAwD,GAAG;AACzE,gBAAY;AAAA,EACd;AAEA,SAAO,EAAE,UAAU;AACrB;AAEA,SAAS,kBAAkB,OAAyB;AAClD,MAAI,iBAAiB,mCAAoC,QAAO;AAChE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,OAAQ,MAA4B;AAC1C,MAAI,SAAS,QAAS,QAAO;AAC7B,QAAM,aAAc,OAAgC;AACpD,QAAM,UAAU,OAAO,eAAe,WAAW,aAAa;AAC9D,SAAO,QAAQ,YAAY,EAAE,SAAS,eAAe;AACvD;AAEA,MAAM,oBAAmE;AAAA,EACvE,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,OAAO,IAAI,sBAAsB,cAAc,QAAQ;AAC/D,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,UAAM,WAAW,MAAM,sBAAsB,IAAI,MAAM,EAAE,IAAI,OAAO,IAAI,WAAW,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AACvI,QAAI,CAAC,SAAU,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AACvE,UAAM,QAAQ,MAAM,kBAAkB,IAAI,OAAO,EAAE;AACnD,UAAM,OAAO,MAAM,qBAAqB,IAAI,OAAO,EAAE;AACrD,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA,OAAO;AAAA,MACP,SAAS,WAAW,OAAO,SAAS,QAAQ,IAAI;AAAA,MAChD,SAAS,iBAAiB,OAAO,SAAS,cAAc,IAAI;AAAA,IAC9D;AACA,WAAO,EAAE,QAAQ,qBAAqB,UAAU,OAAO,MAAM,MAAM,EAAE;AAAA,EACvE;AAAA,EACA,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,QAAQ,OAAO,IAAI,sBAAsB,cAAc,QAAQ;AACvE,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,UAAM,cAAc,MAAM,QAAQ,OAAO,KAAK,IAC1C,MAAM,kBAAkB,IAAI,OAAO,EAAE,IACrC;AAEJ,QAAI,OAAO,UAAU,QAAW;AAC9B,YAAMA,aAAY,iBAAiB,OAAO,KAAK;AAC/C,YAAM,YAAY,MAAM;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,UACE,KAAK,CAAC,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,WAAAA,WAAU,CAAC;AAAA,UAC5C,WAAW;AAAA,UACX,IAAI,EAAE,KAAK,OAAO,GAAG;AAAA,QACvB;AAAA,QACA,CAAC;AAAA,QACD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,MACzC;AACA,UAAI,UAAW,OAAM,yBAAyB;AAAA,IAChD;AAEA,QAAI,SAAwB;AAC5B,QAAI,YAA2B;AAC/B,QAAI,OAAO,UAAU;AACnB,YAAM,EAAE,KAAK,IAAI,MAAM,OAAO,UAAU;AACxC,eAAS,MAAM,KAAK,OAAO,UAAU,EAAE;AAAA,IACzC;AACA,QAAI,OAAO,UAAU,QAAW;AAC9B,kBAAY,iBAAiB,OAAO,KAAK;AAAA,IAC3C;AAEA,QAAI;AACJ,QAAI,OAAO,mBAAmB,QAAW;AACvC,YAAM,eAAe,MAAM;AAAA,QACzB;AAAA,QACA;AAAA,QACA,EAAE,IAAI,OAAO,eAAe;AAAA,QAC5B,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,QACvB,EAAE,UAAU,MAAM,gBAAgB,OAAO,kBAAkB,KAAK;AAAA,MAClE;AACA,UAAI,CAAC,aAAc,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,yBAAyB,CAAC;AACnF,iBAAW,aAAa,QAAQ,KAAK,OAAO,aAAa,OAAO,EAAE,IAAI;AAAA,IACxE;AAEA,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,GAAG,gBAAgB;AAAA,QAC9B,QAAQ;AAAA,QACR,OAAO,EAAE,IAAI,OAAO,IAAI,WAAW,KAAK;AAAA,QACxC,OAAO,CAAC,WAAW;AACjB,cAAI,OAAO,UAAU,QAAW;AAC9B,mBAAO,QAAQ,OAAO;AACtB,mBAAO,YAAY;AAAA,UACrB;AACA,cAAI,OAAO,SAAS,QAAW;AAC7B,mBAAO,OAAO,OAAO;AAAA,UACvB;AACA,cAAI,OAAO,mBAAmB,QAAW;AACvC,mBAAO,iBAAiB,OAAO;AAC/B,mBAAO,WAAW,YAAY;AAAA,UAChC;AACA,cAAI,OAAQ,QAAO,eAAe;AAAA,QACpC;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,kBAAkB,KAAK,EAAG,OAAM,yBAAyB;AAC7D,YAAM;AAAA,IACR;AACA,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAEnE,QAAI,QAAQ;AACV,YAAM,GAAG,aAAa,SAAS,EAAE,MAAM,OAAO,GAAG,CAAC;AAAA,IACpD;AAEA,QAAI,MAAM,QAAQ,OAAO,KAAK,GAAG;AAC/B,YAAM,cAAc,IAAI,MAAM,OAAO,OAAO,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI,YAAY,IAAI;AAAA,IACtG;AAEA,UAAM,qBAAqB;AAAA,MACzB,YAAY;AAAA,MACZ,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO,KAAK,EAAE;AAAA,MACxB,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,MACpE,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI,YAAY;AAAA,MAC9D,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,cAAc;AAAA,MAClB,IAAI,OAAO,KAAK,EAAE;AAAA,MAClB,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,MACpE,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI,YAAY;AAAA,IAChE;AAEA,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,QAAI,MAAM,QAAQ,OAAO,KAAK,KAAK,aAAa;AAC9C,YAAM,aAAa,MAAM,kBAAkB,IAAI,OAAO,KAAK,EAAE,CAAC;AAC9D,YAAM,EAAE,UAAU,QAAQ,IAAI,gBAAgB,aAAa,UAAU;AACrE,UAAI,SAAS,UAAU,QAAQ,QAAQ;AACrC,cAAM,kBAAkB,KAAK,MAAM,UAAU,OAAO;AAAA,MACtD;AAAA,IACF;AAEA,UAAM,oBAAoB,KAAK,OAAO,EAAE;AAExC,WAAO;AAAA,EACT;AAAA,EACA,cAAc,OAAO,QAAQ,QAAQ,QAAQ;AAC3C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,QAAQ,MAAM,kBAAkB,IAAI,OAAO,OAAO,EAAE,CAAC;AAC3D,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA,OAAO,OAAO,EAAE;AAAA,MAChB,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,MAC5C,OAAO,iBAAiB,OAAO,OAAO,cAAc,IAAI;AAAA,IAC1D;AACA,WAAO,cAAc,QAAQ,OAAO,MAAM;AAAA,EAC5C;AAAA,EACA,UAAU,OAAO,EAAE,QAAQ,WAAW,IAAI,MAAM;AAC9C,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,kBAAkB,UAAU;AAClC,UAAM,SAAS,iBAAiB;AAChC,UAAM,aAAa,iBAAiB,QAAQ;AAC5C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,aAAa,MAAM,kBAAkB,IAAI,OAAO,OAAO,EAAE,CAAC;AAChE,UAAM,cAAc,MAAM;AAAA,MACxB;AAAA,MACA,OAAO,OAAO,EAAE;AAAA,MAChB,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,MAC5C,OAAO,iBAAiB,OAAO,OAAO,cAAc,IAAI;AAAA,IAC1D;AACA,UAAM,iBAAiB,qBAAqB,QAAQ,YAAY,QAAW,WAAW;AACtF,UAAM,QAAQ,eAAe;AAC7B,UAAM,UAAU,aAAa,UAAU,MAAM,OAAkC,CAAC,SAAS,kBAAkB,YAAY,QAAQ,aAAa,CAAC;AAC7I,QAAI,UAAU,CAAC,YAAY,OAAO,OAAO,UAAU,GAAG;AACpD,cAAQ,QAAQ,EAAE,MAAM,OAAO,OAAO,IAAI,WAAW;AAAA,IACvD;AACA,UAAM,aAAa,uBAAuB,QAAQ,QAAQ,WAAW;AACrE,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AACpD,cAAQ,MAAM,GAAG,EAAE,IAAI;AAAA,IACzB;AACA,WAAO;AAAA,MACL,aAAa,UAAU,2BAA2B,aAAa;AAAA,MAC/D,cAAc;AAAA,MACd,YAAY,OAAO,OAAO,EAAE;AAAA,MAC5B,UAAU,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,MACtD;AAAA,MACA,gBAAgB,UAAU;AAAA,MAC1B,eAAe;AAAA,MACf,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,QAAQ;AAAA,UACR,OAAO,eAAe;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAAkD,QAAQ;AAC1E,UAAM,SAAS,SAAS;AACxB,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,OAAQ;AACb,UAAM,SAAS,OAAO;AACtB,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,UAAU,MAAM,GAAG,gBAAgB;AAAA,MACvC,QAAQ;AAAA,MACR,OAAO,EAAE,IAAI,QAAQ,WAAW,KAAK;AAAA,MACrC,OAAO,CAAC,WAAW;AACjB,eAAO,QAAQ,OAAO;AACtB,eAAO,iBAAiB,OAAO,kBAAkB;AACjD,eAAO,WAAW,OAAO,YAAY;AACrC,eAAO,eAAe,OAAO,gBAAgB;AAC7C,eAAO,OAAO,OAAO,QAAQ;AAC7B,eAAO,cAAc,OAAO;AAAA,MAC9B;AAAA,IACF,CAAC;AAED,QAAI,SAAS;AACX,YAAM,cAAc,IAAI,SAAS,OAAO,OAAO,OAAO,QAAQ;AAC9D,YAAM,GAAG,MAAM;AAAA,IACjB;AAEA,UAAM,QAAQ,yBAAyB,OAAO,QAAQ,OAAO,MAAM;AACnE,QAAI,OAAO,KAAK,KAAK,EAAE,QAAQ;AAC7B,YAAM,qBAAqB;AAAA,QACzB,YAAY;AAAA,QACZ,UAAU,EAAE,KAAK;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO,kBAAkB;AAAA,QACzC,UAAU,OAAO,YAAY;AAAA,QAC7B,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAEA,UAAM,wBAAwB;AAAA,MAC5B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO;AAAA,QACX,gBAAgB,OAAO,kBAAkB;AAAA,QACzC,UAAU,OAAO,YAAY;AAAA,MAC/B;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,UAAM,oBAAoB,KAAK,MAAM;AAAA,EACvC;AACF;AAEA,MAAM,oBAA+G;AAAA,EACnH,IAAI;AAAA,EACJ,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,KAAK,UAAU,OAAO,kBAAkB;AAC9C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,UAAM,WAAW,MAAM,sBAAsB,IAAI,MAAM,EAAE,IAAI,WAAW,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAC5H,QAAI,CAAC,SAAU,QAAO,CAAC;AACvB,UAAM,QAAQ,MAAM,kBAAkB,IAAI,EAAE;AAC5C,UAAM,OAAO,MAAM,qBAAqB,IAAI,EAAE;AAC9C,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA,SAAS,WAAW,OAAO,SAAS,QAAQ,IAAI;AAAA,MAChD,SAAS,iBAAiB,OAAO,SAAS,cAAc,IAAI;AAAA,IAC9D;AACA,WAAO,EAAE,QAAQ,qBAAqB,UAAU,OAAO,MAAM,MAAM,EAAE;AAAA,EACvE;AAAA,EACA,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,KAAK,UAAU,OAAO,kBAAkB;AAC9C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AAEtC,UAAM,GAAG,aAAa,SAAS,EAAE,MAAM,GAAG,CAAC;AAC3C,UAAM,GAAG,aAAa,UAAU,EAAE,MAAM,GAAG,CAAC;AAC5C,UAAM,GAAG,aAAa,SAAS,EAAE,MAAM,GAAG,CAAC;AAC3C,UAAM,GAAG,aAAa,eAAe,EAAE,MAAM,GAAG,CAAC;AAEjD,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,OAAO,MAAM,GAAG,gBAAgB;AAAA,MACpC,QAAQ;AAAA,MACR,OAAO,EAAE,IAAI,WAAW,KAAK;AAAA,MAC7B,MAAM;AAAA,IACR,CAAC;AACD,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAEnE,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO,EAAE;AAAA,QACb,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,QACpE,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,MACpD;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,UAAM,oBAAoB,KAAK,EAAE;AAEjC,WAAO;AAAA,EACT;AAAA,EACA,UAAU,OAAO,EAAE,WAAW,OAAO,IAAI,MAAM;AAC7C,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,kBAAkB,UAAU;AAClC,UAAM,SAAS,iBAAiB;AAChC,UAAM,aAAa,iBAAiB,QAAQ;AAC5C,UAAM,KAAK,UAAU,OAAO,kBAAkB;AAC9C,WAAO;AAAA,MACL,aAAa,UAAU,2BAA2B,aAAa;AAAA,MAC/D,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,gBAAgB,UAAU;AAAA,MAC1B,UAAU,QAAQ,YAAY;AAAA,MAC9B,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAAkD,QAAQ;AAC1E,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,OAAQ;AACb,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,QAAI,OAAO,MAAM,sBAAsB,IAAI,MAAM,EAAE,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAChH,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAE9C,QAAI,MAAM;AACR,UAAI,KAAK,WAAW;AAClB,aAAK,YAAY;AAAA,MACnB;AACA,WAAK,QAAQ,OAAO;AACpB,WAAK,iBAAiB,OAAO,kBAAkB;AAC/C,WAAK,WAAW,OAAO,YAAY;AACnC,WAAK,eAAe,OAAO,gBAAgB;AAC3C,WAAK,OAAO,OAAO,QAAQ;AAC3B,WAAK,cAAc,OAAO;AAC1B,YAAM,GAAG,MAAM;AAAA,IACjB,OAAO;AACL,aAAO,MAAM,GAAG,gBAAgB;AAAA,QAC9B,QAAQ;AAAA,QACR,MAAM;AAAA,UACJ,IAAI,OAAO;AAAA,UACX,OAAO,OAAO;AAAA,UACd,gBAAgB,OAAO,kBAAkB;AAAA,UACzC,UAAU,OAAO,YAAY;AAAA,UAC7B,cAAc,OAAO,gBAAgB;AAAA,UACrC,MAAM,OAAO,QAAQ;AAAA,UACrB,aAAa,OAAO;AAAA,QACtB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,CAAC,KAAM;AAEX,UAAM,GAAG,aAAa,UAAU,EAAE,MAAM,OAAO,GAAG,CAAC;AACnD,UAAM,cAAc,IAAI,MAAM,OAAO,OAAO,OAAO,QAAQ;AAE3D,UAAM,gBAAgB,IAAI,MAAM,OAAO,IAAI;AAE3C,UAAM,QAAQ,yBAAyB,OAAO,QAAQ,MAAS;AAC/D,QAAI,OAAO,KAAK,KAAK,EAAE,QAAQ;AAC7B,YAAM,qBAAqB;AAAA,QACzB,YAAY;AAAA,QACZ,UAAU,EAAE,KAAK;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO,kBAAkB;AAAA,QACzC,UAAU,OAAO,YAAY;AAAA,QAC7B,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAEA,UAAM,oBAAoB,KAAK,OAAO,EAAE;AAAA,EAC1C;AACF;AAEA,gBAAgB,iBAAiB;AACjC,gBAAgB,iBAAiB;AACjC,gBAAgB,iBAAiB;AAEjC,MAAM,UAAU;AAEhB,eAAe,YACb,IACA,OACA,oBACsB;AACtB,MAAI,QAAQ,KAAK,KAAK,GAAG;AACvB,UAAM,QAAiC,EAAE,IAAI,MAAM;AACnD,QAAI,uBAAuB,MAAM;AAC/B,YAAM,WAAW;AAAA,IACnB;AACA,WAAO,sBAAsB,IAAI,MAAM,OAAc,CAAC,GAAG,EAAE,UAAU,oBAAoB,gBAAgB,KAAK,CAAC;AAAA,EACjH;AACA,SAAO,sBAAsB,IAAI,MAAM,EAAE,MAAM,OAAO,UAAU,mBAAmB,GAAG,CAAC,GAAG,EAAE,UAAU,oBAAoB,gBAAgB,KAAK,CAAC;AAClJ;AAEA,eAAe,cAAc,IAAmB,MAAY,cAAwB,UAAyB;AAC3G,QAAM,SAAS,MAAM,KAAK,IAAI,IAAI,aAAa,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC;AAC1F,QAAM,qBAAqB,kBAAkB,YAAY,IAAI,KAAK;AAElE,QAAM,gBAAwB,CAAC;AAC/B,QAAM,eAAyB,CAAC;AAChC,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,MAAM,YAAY,IAAI,OAAO,kBAAkB;AAC5D,QAAI,CAAC,MAAM;AACT,mBAAa,KAAK,KAAK;AAAA,IACzB,OAAO;AACL,oBAAc,KAAK,IAAI;AAAA,IACzB;AAAA,EACF;AAEA,MAAI,aAAa,QAAQ;AACvB,UAAM,SAAS,aAAa,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AAC1D,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,sBAAsB,MAAM,GAAG,CAAC;AAAA,EACxE;AAEA,QAAM,aAAa,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,OAAO,EAAE,EAAE,CAAC,CAAC;AACjE,QAAM,eAAe,MAAM,mBAAmB,IAAI,UAAU,EAAE,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAClH,QAAM,iBAAiB,IAAI;AAAA,IACzB,aAAa,IAAI,CAAC,SAAS;AACzB,YAAM,SAAS,OAAO,KAAK,MAAM,MAAO,KAAK,QAA8B,EAAE;AAC7E,aAAO,CAAC,QAAQ,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,aAAW,CAAC,QAAQ,IAAI,KAAK,eAAe,QAAQ,GAAG;AACrD,QAAI,CAAC,WAAW,IAAI,MAAM,KAAK,MAAM;AACnC,SAAG,OAAO,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,aAAW,QAAQ,eAAe;AAChC,QAAI,CAAC,eAAe,IAAI,OAAO,KAAK,EAAE,CAAC,GAAG;AACxC,SAAG,QAAQ,GAAG,OAAO,UAAU,EAAE,MAAM,MAAM,WAAW,oBAAI,KAAK,EAAE,CAAC,CAAC;AAAA,IACvE;AAAA,EACF;AAEA,QAAM,GAAG,MAAM;AACjB;AAEA,eAAe,kBAAkB,IAAmB,QAAmC;AACrF,QAAM,QAAQ,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA,EAAE,MAAM,OAA0B;AAAA,IAClC,EAAE,UAAU,CAAC,MAAM,EAAE;AAAA,IACrB,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,EACzC;AACA,QAAM,QAAQ,MACX,IAAI,CAAC,SAAS,KAAK,MAAM,QAAQ,EAAE,EACnC,OAAO,CAAC,SAAyB,CAAC,CAAC,IAAI;AAC1C,SAAO,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACrE;AAEA,SAAS,cAAc,MAAY,OAAiB,QAAyD;AAC3G,QAAM,UAA0B;AAAA,IAC9B,OAAO,OAAO,KAAK,SAAS,EAAE;AAAA,IAC9B,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,IACpE,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,IAClD;AAAA,IACA,MAAM,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AAAA,IACtC,aAAa,QAAQ,KAAK,WAAW;AAAA,EACvC;AACA,MAAI,UAAU,OAAO,KAAK,MAAM,EAAE,OAAQ,SAAQ,SAAS;AAC3D,SAAO;AACT;AAEA,SAAS,qBACP,MACA,OACA,OAA0B,CAAC,GAC3B,QACe;AACf,SAAO;AAAA,IACL,MAAM,cAAc,MAAM,OAAO,MAAM;AAAA,IACvC,MAAM;AAAA,MACJ,IAAI,OAAO,KAAK,EAAE;AAAA,MAClB,OAAO,OAAO,KAAK,SAAS,EAAE;AAAA,MAC9B,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,IAAI;AAAA,MACpE,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,MAClD,cAAc,KAAK,eAAe,OAAO,KAAK,YAAY,IAAI;AAAA,MAC9D,MAAM,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AAAA,MACtC,aAAa,QAAQ,KAAK,WAAW;AAAA,MACrC,OAAO,CAAC,GAAG,KAAK;AAAA,MAChB;AAAA,MACA,GAAI,UAAU,OAAO,KAAK,MAAM,EAAE,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,IAC3D;AAAA,EACF;AACF;AAEA,eAAe,qBAAqB,IAAmB,QAA4C;AACjG,QAAM,OAAO,MAAM,mBAAmB,IAAI,SAAS,EAAE,MAAM,OAA0B,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AACpI,SAAO,KAAK,IAAI,CAAC,SAAS;AAAA,IACxB,UAAU,OAAO,IAAI,QAAQ;AAAA,IAC7B,UAAU,MAAM,QAAQ,IAAI,YAAY,IAAI,CAAC,GAAG,IAAI,YAAY,IAAI;AAAA,IACpE,cAAc,QAAQ,IAAI,YAAY;AAAA,IACtC,eAAe,MAAM,QAAQ,IAAI,iBAAiB,IAAI,CAAC,GAAG,IAAI,iBAAiB,IAAI;AAAA,EACrF,EAAE;AACJ;AAEA,eAAe,gBAAgB,IAAmB,MAAY,MAAyB;AACrF,QAAM,GAAG,aAAa,SAAS,EAAE,MAAM,OAAO,KAAK,EAAE,EAAE,CAAC;AACxD,aAAW,OAAO,MAAM;AACtB,UAAM,SAAS,GAAG,OAAO,SAAS;AAAA,MAChC;AAAA,MACA,UAAU,IAAI;AAAA,MACd,cAAc,IAAI,YAAY;AAAA,MAC9B,cAAc,IAAI;AAAA,MAClB,mBAAmB,IAAI,iBAAiB;AAAA,MACxC,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC;AACD,OAAG,QAAQ,MAAM;AAAA,EACnB;AACA,QAAM,GAAG,MAAM;AACjB;AAEA,eAAe,uBACb,IACA,IACA,UACA,gBACkC;AAClC,SAAO,MAAM,wBAAwB,IAAI;AAAA,IACvC,UAAU,EAAE,KAAK;AAAA,IACjB,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAe,oBAAoB,KAA4B,QAAgB;AAC7E,MAAI;AACF,UAAM,cAAc,IAAI,UAAU,QAAQ,aAAa;AACvD,UAAM,YAAY,oBAAoB,MAAM;AAAA,EAC9C,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,QAAQ,IAAI,UAAU,QAAQ,OAAO;AAC3C,QAAI,OAAO,aAAc,OAAM,MAAM,aAAa,CAAC,aAAa,MAAM,EAAE,CAAC;AAAA,EAC3E,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,gBAAgB,QAAkB,OAAiB;AAC1D,QAAM,YAAY,IAAI,IAAI,MAAM;AAChC,QAAM,WAAW,IAAI,IAAI,KAAK;AAC9B,QAAM,WAAW,MAAM,OAAO,CAAC,SAAS,CAAC,UAAU,IAAI,IAAI,CAAC;AAC5D,QAAM,UAAU,OAAO,OAAO,CAAC,SAAS,CAAC,SAAS,IAAI,IAAI,CAAC;AAC3D,SAAO,EAAE,UAAU,QAAQ;AAC7B;AAEA,SAAS,YAAY,MAA4B,OAA0B;AACzE,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,KAAK,WAAW,MAAM,OAAQ,QAAO;AACzC,SAAO,KAAK,MAAM,CAAC,OAAO,QAAQ,UAAU,MAAM,GAAG,CAAC;AACxD;AAEA,eAAe,2BAA2C;AACxD,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAM,UAAU,UAAU,iCAAiC,sBAAsB;AACjF,QAAM,IAAI,cAAc,KAAK;AAAA,IAC3B,OAAO;AAAA,IACP,aAAa,EAAE,OAAO,QAAQ;AAAA,IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,MAAM,aAAa,QAAQ,aAAa,CAAC;AAAA,EACjF,CAAC;AACH;",
|
|
4
|
+
"sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { UniqueConstraintViolationException } from '@mikro-orm/core'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { User, UserRole, Role, UserAcl, Session, PasswordReset } from '@open-mercato/core/modules/auth/data/entities'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { z } from 'zod'\nimport {\n loadCustomFieldSnapshot,\n buildCustomFieldResetMap,\n diffCustomFieldChanges,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'\nimport { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'\nimport notificationTypes from '@open-mercato/core/modules/auth/notifications'\nimport { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport InviteUserEmail from '@open-mercato/core/modules/auth/emails/InviteUserEmail'\nimport { INVITE_TOKEN_TTL_MS } from '@open-mercato/core/modules/auth/lib/inviteToken'\nimport { getSecurityEmailBaseUrl } from '@open-mercato/shared/lib/url'\nimport { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\n\ntype SerializedUser = {\n email: string\n organizationId: string | null\n tenantId: string | null\n roles: string[]\n name: string | null\n isConfirmed: boolean\n custom?: Record<string, unknown>\n}\n\ntype UserAclSnapshot = {\n tenantId: string\n features: string[] | null\n isSuperAdmin: boolean\n organizations: string[] | null\n}\n\ntype UserUndoSnapshot = {\n id: string\n email: string\n organizationId: string | null\n tenantId: string | null\n passwordHash: string | null\n name: string | null\n isConfirmed: boolean\n roles: string[]\n acls: UserAclSnapshot[]\n custom?: Record<string, unknown>\n}\n\ntype UserSnapshots = {\n view: SerializedUser\n undo: UserUndoSnapshot\n}\n\nconst passwordSchema = buildPasswordSchema()\n\nconst displayNameSchema = z.preprocess(\n normalizeDisplayNameInput,\n z.string().trim().min(1).max(120).nullable().optional(),\n)\n\nconst createSchema = z.object({\n email: z.string().email(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n sendInviteEmail: z.boolean().optional(),\n organizationId: z.string().uuid(),\n roles: z.array(z.string()).optional(),\n}).refine(\n (data) => data.password || data.sendInviteEmail,\n { message: 'Either password or sendInviteEmail is required', path: ['password'] },\n)\n\nconst updateSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email().optional(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n organizationId: z.string().uuid().optional(),\n roles: z.array(z.string()).optional(),\n})\n\nexport const userCrudEvents: CrudEventsConfig = {\n module: 'auth',\n entity: 'user',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nexport const userCrudIndexer: CrudIndexerConfig = {\n entityType: E.auth.user,\n buildUpsertPayload: (ctx) => ({\n entityType: E.auth.user,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n buildDeletePayload: (ctx) => ({\n entityType: E.auth.user,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nasync function notifyRoleChanges(\n ctx: CommandRuntimeContext,\n user: User,\n assignedRoles: string[],\n revokedRoles: string[],\n): Promise<void> {\n const tenantId = user.tenantId ? String(user.tenantId) : null\n if (!tenantId) return\n const organizationId = user.organizationId ? String(user.organizationId) : null\n\n try {\n const notificationService = resolveNotificationService(ctx.container)\n if (assignedRoles.length) {\n const assignedType = notificationTypes.find((type) => type.type === 'auth.role.assigned')\n if (assignedType) {\n const notificationInput = buildNotificationFromType(assignedType, {\n recipientUserId: String(user.id),\n sourceEntityType: 'auth:user',\n sourceEntityId: String(user.id),\n })\n await notificationService.create(notificationInput, { tenantId, organizationId })\n }\n }\n\n if (revokedRoles.length) {\n const revokedType = notificationTypes.find((type) => type.type === 'auth.role.revoked')\n if (revokedType) {\n const notificationInput = buildNotificationFromType(revokedType, {\n recipientUserId: String(user.id),\n sourceEntityType: 'auth:user',\n sourceEntityId: String(user.id),\n })\n await notificationService.create(notificationInput, { tenantId, organizationId })\n }\n }\n } catch (err) {\n console.error('[auth.users.roles] Failed to create notification:', err)\n }\n}\n\ntype CreateUserResult = { user: User; warning?: 'invite_email_failed' }\n\nconst createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResult> = {\n id: 'auth.users.create',\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(createSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: parsed.organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId: parsed.organizationId },\n )\n if (!organization) throw new CrudHttpError(400, { error: 'Organization not found' })\n\n const emailHash = computeEmailHash(parsed.email)\n const duplicate = await findOneWithDecryption(em, User, { $or: [{ email: parsed.email }, { emailHash }], deletedAt: null } as any, {}, { tenantId: null, organizationId: null })\n if (duplicate) await throwDuplicateEmailError()\n\n let passwordHash: string | null = null\n if (parsed.password) {\n const { hash } = await import('bcryptjs')\n passwordHash = await hash(parsed.password, 10)\n }\n const tenantId = organization.tenant?.id ? String(organization.tenant.id) : null\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let user: User\n try {\n user = await de.createOrmEntity({\n entity: User,\n data: {\n email: parsed.email,\n name: parsed.name,\n emailHash,\n passwordHash,\n isConfirmed: true,\n organizationId: parsed.organizationId,\n tenantId,\n },\n })\n } catch (error) {\n if (isUniqueViolation(error)) await throwDuplicateEmailError()\n throw error\n }\n\n let assignedRoles: string[] = []\n if (Array.isArray(parsed.roles) && parsed.roles.length) {\n await syncUserRoles(em, user, parsed.roles, tenantId)\n assignedRoles = await loadUserRoleNames(em, String(user.id))\n }\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: tenantId,\n values: custom,\n })\n\n let inviteEmailSent = false\n if (parsed.sendInviteEmail) {\n const inviteResult = await sendInviteToUser(em, user)\n inviteEmailSent = inviteResult.emailSent\n }\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: user,\n identifiers: {\n id: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n if (assignedRoles.length && !parsed.sendInviteEmail) {\n await notifyRoleChanges(ctx, user, assignedRoles, [])\n }\n\n const warning = (parsed.sendInviteEmail && !inviteEmailSent) ? 'invite_email_failed' as const : undefined\n\n return { user, warning }\n },\n captureAfter: async (_input, { user }, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const roles = await loadUserRoleNames(em, String(user.id))\n const custom = await loadUserCustomSnapshot(\n em,\n String(user.id),\n user.tenantId ? String(user.tenantId) : null,\n user.organizationId ? String(user.organizationId) : null\n )\n return serializeUser(user, roles, custom)\n },\n buildLog: async ({ result: { user }, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const roles = await loadUserRoleNames(em, String(user.id))\n const custom = await loadUserCustomSnapshot(\n em,\n String(user.id),\n user.tenantId ? String(user.tenantId) : null,\n user.organizationId ? String(user.organizationId) : null\n )\n const snapshot = captureUserSnapshots(user, roles, undefined, custom)\n return {\n actionLabel: translate('auth.audit.users.create', 'Create user'),\n resourceKind: 'auth.user',\n resourceId: String(user.id),\n tenantId: user.tenantId ? String(user.tenantId) : null,\n snapshotAfter: snapshot.view,\n payload: {\n undo: {\n after: snapshot.undo,\n },\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const userId = typeof logEntry?.resourceId === 'string' ? logEntry.resourceId : null\n if (!userId) return\n const snapshot = logEntry?.snapshotAfter as SerializedUser | undefined\n const em = (ctx.container.resolve('em') as EntityManager)\n await em.nativeDelete(UserAcl, { user: userId })\n await em.nativeDelete(UserRole, { user: userId })\n await em.nativeDelete(Session, { user: userId })\n await em.nativeDelete(PasswordReset, { user: userId })\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n if (snapshot?.custom && Object.keys(snapshot.custom).length) {\n const reset = buildCustomFieldResetMap(undefined, snapshot.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: userId,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n values: reset,\n notify: false,\n })\n }\n }\n const removed = await de.deleteOrmEntity({\n entity: User,\n where: { id: userId, deletedAt: null } as FilterQuery<User>,\n soft: false,\n })\n\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: removed,\n identifiers: {\n id: userId,\n organizationId: snapshot?.organizationId ?? null,\n tenantId: snapshot?.tenantId ?? null,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n await invalidateUserCache(ctx, userId)\n },\n}\n\nasync function sendInviteToUser(\n em: EntityManager,\n user: User,\n): Promise<{ emailSent: boolean }> {\n const rawToken = generateAuthToken()\n const tokenHash = hashAuthToken(rawToken)\n const expiresAt = new Date(Date.now() + INVITE_TOKEN_TTL_MS)\n const row = em.create(PasswordReset, { user, token: tokenHash, expiresAt, createdAt: new Date() })\n await em.persist(row).flush()\n\n const base = getSecurityEmailBaseUrl()\n const inviteUrl = `${base}/reset/${rawToken}`\n\n const { translate } = await resolveTranslations()\n const subject = translate('auth.email.invite.subject', 'You have been invited')\n const copy = {\n preview: translate('auth.email.invite.preview', 'Set up your account'),\n title: translate('auth.email.invite.title', 'You have been invited'),\n body: translate('auth.email.invite.body', 'An administrator has created an account for you. Click the link below to set your password. This link will expire in 48 hours.'),\n cta: translate('auth.email.invite.cta', 'Set up your password'),\n hint: translate('auth.email.invite.hint', 'If you did not expect this invitation, you can safely ignore this email.'),\n }\n\n let emailSent = true\n try {\n await sendEmail({ to: user.email, subject, react: InviteUserEmail({ inviteUrl, copy }) })\n } catch (err) {\n console.error('[auth.users.invite] Failed to send invitation email:', err)\n emailSent = false\n }\n\n return { emailSent }\n}\n\nfunction isUniqueViolation(error: unknown): boolean {\n if (error instanceof UniqueConstraintViolationException) return true\n if (!error || typeof error !== 'object') return false\n const code = (error as { code?: string }).code\n if (code === '23505') return true\n const messageRaw = (error as { message?: string })?.message\n const message = typeof messageRaw === 'string' ? messageRaw : ''\n return message.toLowerCase().includes('duplicate key')\n}\n\nconst updateUserCommand: CommandHandler<Record<string, unknown>, User> = {\n id: 'auth.users.update',\n async prepare(rawInput, ctx) {\n const { parsed } = parseWithCustomFields(updateSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const existing = await findOneWithDecryption(em, User, { id: parsed.id, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n if (!existing) throw new CrudHttpError(404, { error: 'User not found' })\n const roles = await loadUserRoleNames(em, parsed.id)\n const acls = await loadUserAclSnapshots(em, parsed.id)\n const custom = await loadUserCustomSnapshot(\n em,\n parsed.id,\n existing.tenantId ? String(existing.tenantId) : null,\n existing.organizationId ? String(existing.organizationId) : null\n )\n return { before: captureUserSnapshots(existing, roles, acls, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const rolesBefore = Array.isArray(parsed.roles)\n ? await loadUserRoleNames(em, parsed.id)\n : null\n\n if (parsed.email !== undefined) {\n const emailHash = computeEmailHash(parsed.email)\n const duplicate = await findOneWithDecryption(\n em,\n User,\n {\n $or: [{ email: parsed.email }, { emailHash }],\n deletedAt: null,\n id: { $ne: parsed.id } as any,\n } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n if (duplicate) await throwDuplicateEmailError()\n }\n\n let hashed: string | null = null\n let emailHash: string | null = null\n if (parsed.password) {\n const { hash } = await import('bcryptjs')\n hashed = await hash(parsed.password, 10)\n }\n if (parsed.email !== undefined) {\n emailHash = computeEmailHash(parsed.email)\n }\n\n let tenantId: string | null | undefined\n if (parsed.organizationId !== undefined) {\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: parsed.organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId: parsed.organizationId ?? null },\n )\n if (!organization) throw new CrudHttpError(400, { error: 'Organization not found' })\n tenantId = organization.tenant?.id ? String(organization.tenant.id) : null\n }\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let user: User | null\n try {\n user = await de.updateOrmEntity({\n entity: User,\n where: { id: parsed.id, deletedAt: null } as FilterQuery<User>,\n apply: (entity) => {\n if (parsed.email !== undefined) {\n entity.email = parsed.email\n entity.emailHash = emailHash\n }\n if (parsed.name !== undefined) {\n entity.name = parsed.name\n }\n if (parsed.organizationId !== undefined) {\n entity.organizationId = parsed.organizationId\n entity.tenantId = tenantId ?? null\n }\n if (hashed) entity.passwordHash = hashed\n },\n })\n } catch (error) {\n if (isUniqueViolation(error)) await throwDuplicateEmailError()\n throw error\n }\n if (!user) throw new CrudHttpError(404, { error: 'User not found' })\n\n if (hashed) {\n await em.nativeDelete(Session, { user: parsed.id })\n }\n\n if (Array.isArray(parsed.roles)) {\n await syncUserRoles(em, user, parsed.roles, user.tenantId ? String(user.tenantId) : tenantId ?? null)\n }\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : tenantId ?? null,\n values: custom,\n })\n\n const identifiers = {\n id: String(user.id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : tenantId ?? null,\n }\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: user,\n identifiers,\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n if (Array.isArray(parsed.roles) && rolesBefore) {\n const rolesAfter = await loadUserRoleNames(em, String(user.id))\n const { assigned, revoked } = diffRoleChanges(rolesBefore, rolesAfter)\n if (assigned.length || revoked.length) {\n await notifyRoleChanges(ctx, user, assigned, revoked)\n }\n }\n\n await invalidateUserCache(ctx, parsed.id)\n\n return user\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const roles = await loadUserRoleNames(em, String(result.id))\n const custom = await loadUserCustomSnapshot(\n em,\n String(result.id),\n result.tenantId ? String(result.tenantId) : null,\n result.organizationId ? String(result.organizationId) : null\n )\n return serializeUser(result, roles, custom)\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as UserSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterRoles = await loadUserRoleNames(em, String(result.id))\n const afterCustom = await loadUserCustomSnapshot(\n em,\n String(result.id),\n result.tenantId ? String(result.tenantId) : null,\n result.organizationId ? String(result.organizationId) : null\n )\n const afterSnapshots = captureUserSnapshots(result, afterRoles, undefined, afterCustom)\n const after = afterSnapshots.view\n const changes = buildChanges(before ?? null, after as Record<string, unknown>, ['email', 'organizationId', 'tenantId', 'name', 'isConfirmed'])\n if (before && !arrayEquals(before.roles, afterRoles)) {\n changes.roles = { from: before.roles, to: afterRoles }\n }\n const customDiff = diffCustomFieldChanges(before?.custom, afterCustom)\n for (const [key, diff] of Object.entries(customDiff)) {\n changes[`cf_${key}`] = diff\n }\n return {\n actionLabel: translate('auth.audit.users.update', 'Update user'),\n resourceKind: 'auth.user',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n changes,\n snapshotBefore: before ?? null,\n snapshotAfter: after,\n payload: {\n undo: {\n before: beforeUndo,\n after: afterSnapshots.undo,\n },\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<UndoPayload<UserUndoSnapshot>>(logEntry)\n const before = payload?.before\n const after = payload?.after\n if (!before) return\n const userId = before.id\n const em = (ctx.container.resolve('em') as EntityManager)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const updated = await de.updateOrmEntity({\n entity: User,\n where: { id: userId, deletedAt: null } as FilterQuery<User>,\n apply: (entity) => {\n entity.email = before.email\n entity.organizationId = before.organizationId ?? null\n entity.tenantId = before.tenantId ?? null\n entity.passwordHash = before.passwordHash ?? null\n entity.name = before.name ?? null\n entity.isConfirmed = before.isConfirmed\n },\n })\n\n if (updated) {\n await syncUserRoles(em, updated, before.roles, before.tenantId)\n await em.flush()\n }\n\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: before.id,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updated,\n identifiers: {\n id: before.id,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n await invalidateUserCache(ctx, userId)\n },\n}\n\nconst deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, User> = {\n id: 'auth.users.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'User id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const existing = await findOneWithDecryption(em, User, { id, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n if (!existing) return {}\n const roles = await loadUserRoleNames(em, id)\n const acls = await loadUserAclSnapshots(em, id)\n const custom = await loadUserCustomSnapshot(\n em,\n id,\n existing.tenantId ? String(existing.tenantId) : null,\n existing.organizationId ? String(existing.organizationId) : null\n )\n return { before: captureUserSnapshots(existing, roles, acls, custom) }\n },\n async execute(input, ctx) {\n const id = requireId(input, 'User id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n\n await em.nativeDelete(UserAcl, { user: id })\n await em.nativeDelete(UserRole, { user: id })\n await em.nativeDelete(Session, { user: id })\n await em.nativeDelete(PasswordReset, { user: id })\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const user = await de.deleteOrmEntity({\n entity: User,\n where: { id, deletedAt: null } as FilterQuery<User>,\n soft: false,\n })\n if (!user) throw new CrudHttpError(404, { error: 'User not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: user,\n identifiers: {\n id: String(id),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : null,\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n })\n\n await invalidateUserCache(ctx, id)\n\n return user\n },\n buildLog: async ({ snapshots, input, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as UserSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const id = requireId(input, 'User id required')\n return {\n actionLabel: translate('auth.audit.users.delete', 'Delete user'),\n resourceKind: 'auth.user',\n resourceId: id,\n snapshotBefore: before ?? null,\n tenantId: before?.tenantId ?? null,\n payload: {\n undo: {\n before: beforeUndo,\n },\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<UndoPayload<UserUndoSnapshot>>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager)\n let user = await findOneWithDecryption(em, User, { id: before.id }, {}, { tenantId: null, organizationId: null })\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n\n if (user) {\n if (user.deletedAt) {\n user.deletedAt = null\n }\n user.email = before.email\n user.organizationId = before.organizationId ?? null\n user.tenantId = before.tenantId ?? null\n user.passwordHash = before.passwordHash ?? null\n user.name = before.name ?? null\n user.isConfirmed = before.isConfirmed\n await em.flush()\n } else {\n user = await de.createOrmEntity({\n entity: User,\n data: {\n id: before.id,\n email: before.email,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n passwordHash: before.passwordHash ?? null,\n name: before.name ?? null,\n isConfirmed: before.isConfirmed,\n },\n })\n }\n\n if (!user) return\n\n await em.nativeDelete(UserRole, { user: before.id })\n await syncUserRoles(em, user, before.roles, before.tenantId)\n\n await restoreUserAcls(em, user, before.acls)\n\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.user,\n recordId: before.id,\n organizationId: before.organizationId ?? null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n\n await invalidateUserCache(ctx, before.id)\n },\n}\n\nregisterCommand(createUserCommand)\nregisterCommand(updateUserCommand)\nregisterCommand(deleteUserCommand)\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nasync function resolveRole(\n em: EntityManager,\n value: string,\n normalizedTenantId: string | null,\n): Promise<Role | null> {\n if (UUID_RE.test(value)) {\n const where: Record<string, unknown> = { id: value }\n if (normalizedTenantId !== null) {\n where.tenantId = normalizedTenantId\n }\n return findOneWithDecryption(em, Role, where as any, {}, { tenantId: normalizedTenantId, organizationId: null })\n }\n return findOneWithDecryption(em, Role, { name: value, tenantId: normalizedTenantId }, {}, { tenantId: normalizedTenantId, organizationId: null })\n}\n\nasync function syncUserRoles(em: EntityManager, user: User, desiredRoles: string[], tenantId: string | null) {\n const unique = Array.from(new Set(desiredRoles.map((role) => role.trim()).filter(Boolean)))\n const normalizedTenantId = normalizeTenantId(tenantId ?? null) ?? null\n\n const resolvedRoles: Role[] = []\n const missingRoles: string[] = []\n for (const value of unique) {\n const role = await resolveRole(em, value, normalizedTenantId)\n if (!role) {\n missingRoles.push(value)\n } else {\n resolvedRoles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((n) => `\"${n}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n const desiredIds = new Set(resolvedRoles.map((r) => String(r.id)))\n const currentLinks = await findWithDecryption(em, UserRole, { user }, {}, { tenantId: null, organizationId: null })\n const currentRoleIds = new Map(\n currentLinks.map((link) => {\n const roleId = String(link.role?.id ?? (link.role as unknown as string) ?? '')\n return [roleId, link] as const\n }),\n )\n\n for (const [roleId, link] of currentRoleIds.entries()) {\n if (!desiredIds.has(roleId) && link) {\n em.remove(link)\n }\n }\n\n for (const role of resolvedRoles) {\n if (!currentRoleIds.has(String(role.id))) {\n em.persist(em.create(UserRole, { user, role, createdAt: new Date() }))\n }\n }\n\n await em.flush()\n}\n\nasync function loadUserRoleNames(em: EntityManager, userId: string): Promise<string[]> {\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as unknown as User },\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const names = links\n .map((link) => link.role?.name ?? '')\n .filter((name): name is string => !!name)\n return Array.from(new Set(names)).sort((a, b) => a.localeCompare(b))\n}\n\nfunction serializeUser(user: User, roles: string[], custom?: Record<string, unknown> | null): SerializedUser {\n const payload: SerializedUser = {\n email: String(user.email ?? ''),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : null,\n roles,\n name: user.name ? String(user.name) : null,\n isConfirmed: Boolean(user.isConfirmed),\n }\n if (custom && Object.keys(custom).length) payload.custom = custom\n return payload\n}\n\nfunction captureUserSnapshots(\n user: User,\n roles: string[],\n acls: UserAclSnapshot[] = [],\n custom?: Record<string, unknown> | null\n): UserSnapshots {\n return {\n view: serializeUser(user, roles, custom),\n undo: {\n id: String(user.id),\n email: String(user.email ?? ''),\n organizationId: user.organizationId ? String(user.organizationId) : null,\n tenantId: user.tenantId ? String(user.tenantId) : null,\n passwordHash: user.passwordHash ? String(user.passwordHash) : null,\n name: user.name ? String(user.name) : null,\n isConfirmed: Boolean(user.isConfirmed),\n roles: [...roles],\n acls,\n ...(custom && Object.keys(custom).length ? { custom } : {}),\n },\n }\n}\n\nasync function loadUserAclSnapshots(em: EntityManager, userId: string): Promise<UserAclSnapshot[]> {\n const list = await findWithDecryption(em, UserAcl, { user: userId as unknown as User }, {}, { tenantId: null, organizationId: null })\n return list.map((acl) => ({\n tenantId: String(acl.tenantId),\n features: Array.isArray(acl.featuresJson) ? [...acl.featuresJson] : null,\n isSuperAdmin: Boolean(acl.isSuperAdmin),\n organizations: Array.isArray(acl.organizationsJson) ? [...acl.organizationsJson] : null,\n }))\n}\n\nasync function restoreUserAcls(em: EntityManager, user: User, acls: UserAclSnapshot[]) {\n await em.nativeDelete(UserAcl, { user: String(user.id) })\n for (const acl of acls) {\n const entity = em.create(UserAcl, {\n user,\n tenantId: acl.tenantId,\n featuresJson: acl.features ?? null,\n isSuperAdmin: acl.isSuperAdmin,\n organizationsJson: acl.organizations ?? null,\n createdAt: new Date(),\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\nasync function loadUserCustomSnapshot(\n em: EntityManager,\n id: string,\n tenantId: string | null,\n organizationId: string | null\n): Promise<Record<string, unknown>> {\n return await loadCustomFieldSnapshot(em, {\n entityId: E.auth.user,\n recordId: id,\n tenantId,\n organizationId,\n })\n}\n\nasync function invalidateUserCache(ctx: CommandRuntimeContext, userId: string) {\n try {\n const rbacService = ctx.container.resolve('rbacService') as { invalidateUserCache: (uid: string) => Promise<void> }\n await rbacService.invalidateUserCache(userId)\n } catch {\n // RBAC not available\n }\n\n try {\n const cache = ctx.container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<void> }\n if (cache?.deleteByTags) await cache.deleteByTags([`rbac:user:${userId}`])\n } catch {\n // cache not available\n }\n}\n\nfunction diffRoleChanges(before: string[], after: string[]) {\n const beforeSet = new Set(before)\n const afterSet = new Set(after)\n const assigned = after.filter((role) => !beforeSet.has(role))\n const revoked = before.filter((role) => !afterSet.has(role))\n return { assigned, revoked }\n}\n\nfunction arrayEquals(left: string[] | undefined, right: string[]): boolean {\n if (!left) return false\n if (left.length !== right.length) return false\n return left.every((value, idx) => value === right[idx])\n}\n\nasync function throwDuplicateEmailError(): Promise<never> {\n const { translate } = await resolveTranslations()\n const message = translate('auth.users.errors.emailExists', 'Email already in use')\n throw new CrudHttpError(400, {\n error: message,\n fieldErrors: { email: message },\n details: [{ path: ['email'], message, code: 'duplicate', origin: 'validation' }],\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["emailHash"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/data/entities.ts"],
|
|
4
|
-
"sourcesContent": ["import { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\n@Entity({ tableName: 'users' })\nexport class User {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text', unique: true })\n email!: string\n\n @Property({ name: 'email_hash', type: 'text', nullable: true })\n @Index({ name: 'users_email_hash_idx' })\n emailHash?: string | null\n\n @Property({ type: 'text', nullable: true })\n name?: string\n\n @Property({ name: 'password_hash', type: 'text', nullable: true })\n passwordHash?: string | null\n\n @Property({ name: 'is_confirmed', type: 'boolean', default: true })\n isConfirmed: boolean = true\n\n @Property({ name: 'last_login_at', type: Date, nullable: true })\n lastLoginAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n@Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'roles' })\n@Unique({ properties: ['tenantId', 'name'] })\nexport class Role {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`user_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class UserSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'role_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`role_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class RoleSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sidebar_variants' })\n// Uniqueness is enforced by a partial unique index (`sidebar_variants_active_name_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class SidebarVariant {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'is_active', type: 'boolean', default: false })\n isActive: boolean = false\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_roles' })\nexport class UserRole {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sessions' })\nexport class Session {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'last_used_at', type: Date, nullable: true })\n lastUsedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'password_resets' })\nexport class PasswordReset {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'used_at', type: Date, nullable: true })\n usedAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Role-level ACL\n@Entity({ tableName: 'role_acls' })\nexport class RoleAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, user with this role can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant; null/empty means all organizations\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Per-user ACL override\n@Entity({ tableName: 'user_acls' })\nexport class UserAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, this user can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant; null/empty means all organizations\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_consents' })\n@Unique({ properties: ['userId', 'tenantId', 'consentType'] })\nexport class UserConsent {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'consent_type', type: 'text' })\n consentType!: string\n\n @Property({ name: 'is_granted', type: 'boolean', default: false })\n isGranted: boolean = false\n\n @Property({ name: 'granted_at', type: Date, nullable: true })\n grantedAt?: Date | null\n\n @Property({ name: 'withdrawn_at', type: Date, nullable: true })\n withdrawnAt?: Date | null\n\n @Property({ type: 'text', nullable: true })\n source?: string | null\n\n @Property({ name: 'ip_address', type: 'text', nullable: true })\n ipAddress?: string | null\n\n @Property({ name: 'integrity_hash', type: 'text', nullable: true })\n integrityHash?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\n@Entity({ tableName: 'users' })\nexport class User {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text', unique: true })\n email!: string\n\n @Property({ name: 'email_hash', type: 'text', nullable: true })\n @Index({ name: 'users_email_hash_idx' })\n emailHash?: string | null\n\n @Property({ type: 'text', nullable: true })\n name?: string | null\n\n @Property({ name: 'password_hash', type: 'text', nullable: true })\n passwordHash?: string | null\n\n @Property({ name: 'is_confirmed', type: 'boolean', default: true })\n isConfirmed: boolean = true\n\n @Property({ name: 'last_login_at', type: Date, nullable: true })\n lastLoginAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n@Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'roles' })\n@Unique({ properties: ['tenantId', 'name'] })\nexport class Role {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`user_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class UserSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'role_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`role_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class RoleSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sidebar_variants' })\n// Uniqueness is enforced by a partial unique index (`sidebar_variants_active_name_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class SidebarVariant {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'is_active', type: 'boolean', default: false })\n isActive: boolean = false\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_roles' })\nexport class UserRole {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sessions' })\nexport class Session {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'last_used_at', type: Date, nullable: true })\n lastUsedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'password_resets' })\nexport class PasswordReset {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'used_at', type: Date, nullable: true })\n usedAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Role-level ACL\n@Entity({ tableName: 'role_acls' })\nexport class RoleAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, user with this role can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant; null/empty means all organizations\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Per-user ACL override\n@Entity({ tableName: 'user_acls' })\nexport class UserAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, this user can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant; null/empty means all organizations\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_consents' })\n@Unique({ properties: ['userId', 'tenantId', 'consentType'] })\nexport class UserConsent {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'consent_type', type: 'text' })\n consentType!: string\n\n @Property({ name: 'is_granted', type: 'boolean', default: false })\n isGranted: boolean = false\n\n @Property({ name: 'granted_at', type: Date, nullable: true })\n grantedAt?: Date | null\n\n @Property({ name: 'withdrawn_at', type: Date, nullable: true })\n withdrawnAt?: Date | null\n\n @Property({ type: 'text', nullable: true })\n source?: string | null\n\n @Property({ name: 'ip_address', type: 'text', nullable: true })\n ipAddress?: string | null\n\n @Property({ name: 'integrity_hash', type: 'text', nullable: true })\n integrityHash?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function normalizeDisplayNameInput(value) {
|
|
2
|
+
if (value === null) return null;
|
|
3
|
+
if (typeof value !== "string") return value;
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
normalizeDisplayNameInput
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=displayName.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/auth/lib/displayName.ts"],
|
|
4
|
+
"sourcesContent": ["export function normalizeDisplayNameInput(value: unknown): unknown {\n if (value === null) return null\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,0BAA0B,OAAyB;AACjE,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.2-develop.
|
|
3
|
+
"version": "0.6.2-develop.3461.1.605f31c2c9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -242,16 +242,16 @@
|
|
|
242
242
|
"zod": "^4.3.6"
|
|
243
243
|
},
|
|
244
244
|
"peerDependencies": {
|
|
245
|
-
"@open-mercato/ai-assistant": "0.6.2-develop.
|
|
246
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
247
|
-
"@open-mercato/ui": "0.6.2-develop.
|
|
245
|
+
"@open-mercato/ai-assistant": "0.6.2-develop.3461.1.605f31c2c9",
|
|
246
|
+
"@open-mercato/shared": "0.6.2-develop.3461.1.605f31c2c9",
|
|
247
|
+
"@open-mercato/ui": "0.6.2-develop.3461.1.605f31c2c9",
|
|
248
248
|
"react": "^19.0.0",
|
|
249
249
|
"react-dom": "^19.0.0"
|
|
250
250
|
},
|
|
251
251
|
"devDependencies": {
|
|
252
|
-
"@open-mercato/ai-assistant": "0.6.2-develop.
|
|
253
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
254
|
-
"@open-mercato/ui": "0.6.2-develop.
|
|
252
|
+
"@open-mercato/ai-assistant": "0.6.2-develop.3461.1.605f31c2c9",
|
|
253
|
+
"@open-mercato/shared": "0.6.2-develop.3461.1.605f31c2c9",
|
|
254
|
+
"@open-mercato/ui": "0.6.2-develop.3461.1.605f31c2c9",
|
|
255
255
|
"@testing-library/dom": "^10.4.1",
|
|
256
256
|
"@testing-library/jest-dom": "^6.9.1",
|
|
257
257
|
"@testing-library/react": "^16.3.1",
|
|
@@ -20,6 +20,7 @@ import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern
|
|
|
20
20
|
import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
21
21
|
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
22
22
|
import { sql } from 'kysely'
|
|
23
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
23
24
|
|
|
24
25
|
const querySchema = z.object({
|
|
25
26
|
id: z.string().uuid().optional(),
|
|
@@ -36,12 +37,8 @@ const rawBodySchema = z.object({}).passthrough()
|
|
|
36
37
|
const passwordSchema = buildPasswordSchema()
|
|
37
38
|
|
|
38
39
|
const displayNameSchema = z.preprocess(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const trimmed = value.trim()
|
|
42
|
-
return trimmed.length ? trimmed : undefined
|
|
43
|
-
},
|
|
44
|
-
z.string().trim().min(1).max(120).optional(),
|
|
40
|
+
normalizeDisplayNameInput,
|
|
41
|
+
z.string().trim().min(1).max(120).nullable().optional(),
|
|
45
42
|
)
|
|
46
43
|
|
|
47
44
|
const userCreateSchema = z.object({
|
|
@@ -17,6 +17,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
17
17
|
import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
|
|
18
18
|
import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
19
19
|
import { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'
|
|
20
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
20
21
|
|
|
21
22
|
type EditUserFormValues = {
|
|
22
23
|
email: string
|
|
@@ -442,7 +443,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
442
443
|
const payload = {
|
|
443
444
|
id: id ? String(id) : '',
|
|
444
445
|
email: values.email,
|
|
445
|
-
name:
|
|
446
|
+
name: normalizeDisplayNameInput(values.name),
|
|
446
447
|
password: values.password && values.password.trim() ? values.password : undefined,
|
|
447
448
|
organizationId: values.organizationId ? values.organizationId : undefined,
|
|
448
449
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
@@ -14,6 +14,7 @@ import { RadioGroup } from '@open-mercato/ui/primitives/radio'
|
|
|
14
14
|
import { RadioField } from '@open-mercato/ui/primitives/radio-field'
|
|
15
15
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
16
16
|
import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
17
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
17
18
|
|
|
18
19
|
type CreateUserFormValues = {
|
|
19
20
|
email: string
|
|
@@ -326,7 +327,7 @@ export default function CreateUserPage() {
|
|
|
326
327
|
const customFields = collectCustomFieldValues(values)
|
|
327
328
|
const payload: Record<string, unknown> = {
|
|
328
329
|
email: values.email,
|
|
329
|
-
name:
|
|
330
|
+
name: normalizeDisplayNameInput(values.name),
|
|
330
331
|
organizationId: values.organizationId ? values.organizationId : null,
|
|
331
332
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
332
333
|
...(Object.keys(customFields).length ? { customFields } : {}),
|
|
@@ -37,6 +37,7 @@ import InviteUserEmail from '@open-mercato/core/modules/auth/emails/InviteUserEm
|
|
|
37
37
|
import { INVITE_TOKEN_TTL_MS } from '@open-mercato/core/modules/auth/lib/inviteToken'
|
|
38
38
|
import { getSecurityEmailBaseUrl } from '@open-mercato/shared/lib/url'
|
|
39
39
|
import { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'
|
|
40
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
40
41
|
|
|
41
42
|
type SerializedUser = {
|
|
42
43
|
email: string
|
|
@@ -76,12 +77,8 @@ type UserSnapshots = {
|
|
|
76
77
|
const passwordSchema = buildPasswordSchema()
|
|
77
78
|
|
|
78
79
|
const displayNameSchema = z.preprocess(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const trimmed = value.trim()
|
|
82
|
-
return trimmed.length ? trimmed : undefined
|
|
83
|
-
},
|
|
84
|
-
z.string().trim().min(1).max(120).optional(),
|
|
80
|
+
normalizeDisplayNameInput,
|
|
81
|
+
z.string().trim().min(1).max(120).nullable().optional(),
|
|
85
82
|
)
|
|
86
83
|
|
|
87
84
|
const createSchema = z.object({
|
|
@@ -589,7 +586,7 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
589
586
|
entity.organizationId = before.organizationId ?? null
|
|
590
587
|
entity.tenantId = before.tenantId ?? null
|
|
591
588
|
entity.passwordHash = before.passwordHash ?? null
|
|
592
|
-
entity.name = before.name ??
|
|
589
|
+
entity.name = before.name ?? null
|
|
593
590
|
entity.isConfirmed = before.isConfirmed
|
|
594
591
|
},
|
|
595
592
|
})
|
|
@@ -715,7 +712,7 @@ const deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?
|
|
|
715
712
|
user.organizationId = before.organizationId ?? null
|
|
716
713
|
user.tenantId = before.tenantId ?? null
|
|
717
714
|
user.passwordHash = before.passwordHash ?? null
|
|
718
|
-
user.name = before.name ??
|
|
715
|
+
user.name = before.name ?? null
|
|
719
716
|
user.isConfirmed = before.isConfirmed
|
|
720
717
|
await em.flush()
|
|
721
718
|
} else {
|