import { createContext, DomainError } from "@tailor-platform/erp-kit/app"; import { createResolver, t } from "@tailor-platform/sdk"; import { idp } from "@tailor-platform/sdk/runtime"; import { getDB } from "@/generated/kysely-tailordb"; import { umModules } from "@/modules"; const PASSWORD_LENGTH = 16; const PASSWORD_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*"; function generatePassword(): string { const bytes = new Uint8Array(PASSWORD_LENGTH); crypto.getRandomValues(bytes); return Array.from(bytes, (b) => PASSWORD_CHARS[b % PASSWORD_CHARS.length]).join(""); } export default createResolver({ name: "createUser", operation: "mutation", input: { name: t.string().description("User name"), email: t.string().description("User email"), roleIds: t.string({ array: true, optional: true }).description("Role IDs to assign"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); const password = generatePassword(); const roleIds = context.input.roleIds ?? []; const result = await db .transaction() .execute(async (trx) => { const createResult = await umModules.commands.createUser( trx, { email: context.input.email, name: context.input.name, }, ctx, ); if (!createResult.ok) { switch (createResult.error.code) { case "USER_MANAGEMENT_USER_ALREADY_EXISTS": throw new DomainError(`A user with email ${context.input.email} already exists`); case "USER_MANAGEMENT_MISSING_REQUIRED_FIELD": throw new DomainError(`Missing required field: ${createResult.error.message}`); case "USER_MANAGEMENT_INVALID_EMAIL": throw new DomainError(`Invalid email format: ${context.input.email}`); case "UNAUTHENTICATED": throw new DomainError("Authentication is required"); case "INSUFFICIENT_PERMISSION": throw new DomainError("You do not have permission to perform this action"); default: throw createResult.error satisfies never; } } const activateResult = await umModules.commands.activateUser( trx, { userId: createResult.value.user.id }, ctx, ); if (!activateResult.ok) throw activateResult.error; for (const roleId of roleIds) { const assignResult = await umModules.commands.assignRoleToUser( trx, { userId: createResult.value.user.id, roleId, }, ctx, ); if (!assignResult.ok) { switch (assignResult.error.code) { case "USER_MANAGEMENT_ROLE_NOT_FOUND": throw new DomainError(`Role ${roleId} does not exist`); case "USER_MANAGEMENT_USER_NOT_FOUND": throw new DomainError(`User does not exist`); case "USER_MANAGEMENT_USER_NOT_ACTIVE": throw new DomainError("User is not in ACTIVE status"); case "USER_MANAGEMENT_ROLE_NOT_ACTIVE": throw new DomainError(`Role ${roleId} is not in ACTIVE status`); case "UNAUTHENTICATED": throw new DomainError("Authentication is required"); case "INSUFFICIENT_PERMISSION": throw new DomainError("You do not have permission to perform this action"); default: throw assignResult.error satisfies never; } } } // IDP failure rolls back the entire transaction, preventing orphaned DB records const idpClient = new idp.Client({ namespace: "default" }); await idpClient.createUser({ name: context.input.email, password }); return { id: createResult.value.user.id, name: createResult.value.user.name, email: createResult.value.user.email, status: activateResult.value.user.status, }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to create the user", { cause: err }); }); return { ...result, initialPassword: password }; }, output: t .object({ id: t.string().description("User ID"), name: t.string().description("User name"), email: t.string().description("User email"), status: t.string().description("User status"), initialPassword: t.string().description("Auto-generated initial password"), }) .description("Created user"), });