import { z } from "zod" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { ensureCollaboratorAccount, ensureMachineAccount } from "@/server/machines-auth" import { createCorsPreflight, jsonWithCors } from "@/server/cors" import { prisma } from "@/lib/prisma" import { createConvexClient, ConvexConfigurationError } from "@/server/convex-client" const registerSchema = z .object({ provisioningCode: z.string().min(32), hostname: z.string().min(1), os: z.object({ name: z.string().min(1), version: z.string().optional(), architecture: z.string().optional(), }), macAddresses: z.array(z.string()).default([]), serialNumbers: z.array(z.string()).default([]), metadata: z.record(z.string(), z.unknown()).optional(), registeredBy: z.string().optional(), accessRole: z.enum(["collaborator", "manager"]).optional(), collaborator: z .object({ email: z.string().email(), name: z.string().min(1, "Informe o nome do colaborador/gestor"), }) .optional(), }) .refine( (data) => (data.macAddresses && data.macAddresses.length > 0) || (data.serialNumbers && data.serialNumbers.length > 0), { message: "Informe ao menos um MAC address ou número de série" } ) const CORS_METHODS = "POST, OPTIONS" export async function OPTIONS(request: Request) { return createCorsPreflight(request.headers.get("origin"), CORS_METHODS) } export async function POST(request: Request) { const origin = request.headers.get("origin") if (request.method !== "POST") { return jsonWithCors({ error: "Método não permitido" }, 405, origin, CORS_METHODS) } let client try { client = createConvexClient() } catch (error) { if (error instanceof ConvexConfigurationError) { return jsonWithCors({ error: error.message }, 500, origin, CORS_METHODS) } throw error } let payload try { const raw = await request.json() payload = registerSchema.parse(raw) } catch (error) { return jsonWithCors( { error: "Payload inválido", details: error instanceof Error ? error.message : String(error) }, 400, origin, CORS_METHODS ) } try { const provisioningCode = payload.provisioningCode.trim().toLowerCase() const companyRecord = await prisma.company.findFirst({ where: { provisioningCode }, select: { id: true, tenantId: true, name: true, slug: true, provisioningCode: true }, }) if (!companyRecord) { return jsonWithCors( { error: "Código de provisionamento inválido" }, 404, origin, CORS_METHODS ) } const tenantId: string = companyRecord.tenantId ?? DEFAULT_TENANT_ID const persona = payload.accessRole ?? undefined const collaborator = payload.collaborator ?? null if (persona && !collaborator) { return jsonWithCors( { error: "Informe os dados do colaborador/gestor ao definir o perfil de acesso." }, 400, origin, CORS_METHODS ) } let metadataPayload: Record | undefined = payload.metadata ? { ...(payload.metadata as Record) } : undefined if (collaborator) { const collaboratorMeta = { email: collaborator.email, name: collaborator.name ?? null, role: persona ?? "collaborator", } metadataPayload = { ...(metadataPayload ?? {}), collaborator: collaboratorMeta, } } await client.mutation(api.companies.ensureProvisioned, { tenantId, slug: companyRecord.slug, name: companyRecord.name, provisioningCode: companyRecord.provisioningCode, }) const registration = await client.mutation(api.machines.register, { provisioningCode, hostname: payload.hostname, os: payload.os, macAddresses: payload.macAddresses, serialNumbers: payload.serialNumbers, metadata: metadataPayload, registeredBy: payload.registeredBy, }) const account = await ensureMachineAccount({ machineId: registration.machineId, tenantId: registration.tenantId ?? DEFAULT_TENANT_ID, hostname: payload.hostname, machineToken: registration.machineToken, persona, }) await client.mutation(api.machines.linkAuthAccount, { machineId: registration.machineId as Id<"machines">, authUserId: account.authUserId, authEmail: account.authEmail, }) let assignedUserId: Id<"users"> | undefined if (collaborator) { const ensuredUser = await client.mutation(api.users.ensureUser, { tenantId, email: collaborator.email, name: collaborator.name ?? collaborator.email, avatarUrl: undefined, role: persona?.toUpperCase(), companyId: registration.companyId ? (registration.companyId as Id<"companies">) : undefined, }) await ensureCollaboratorAccount({ email: collaborator.email, name: collaborator.name ?? collaborator.email, tenantId, companyId: companyRecord.id, role: persona === "manager" ? "MANAGER" : "COLLABORATOR", }) if (persona) { assignedUserId = ensuredUser?._id await client.mutation(api.machines.updatePersona, { machineId: registration.machineId as Id<"machines">, persona, ...(assignedUserId ? { assignedUserId } : {}), assignedUserEmail: collaborator.email, assignedUserName: collaborator.name ?? undefined, assignedUserRole: persona === "manager" ? "MANAGER" : "COLLABORATOR", }) } else { await client.mutation(api.machines.updatePersona, { machineId: registration.machineId as Id<"machines">, persona: "", }) } } else { await client.mutation(api.machines.updatePersona, { machineId: registration.machineId as Id<"machines">, persona: "", }) } return jsonWithCors( { machineId: registration.machineId, tenantId: registration.tenantId, companyId: registration.companyId, companySlug: registration.companySlug, machineToken: registration.machineToken, machineEmail: account.authEmail, expiresAt: registration.expiresAt, persona: persona ?? null, assignedUserId: assignedUserId ?? null, collaborator: collaborator ?? null, }, { status: 201 }, origin, CORS_METHODS ) } catch (error) { console.error("[machines.register] Falha no provisionamento", error) const details = error instanceof Error ? error.message : String(error) const msg = details.toLowerCase() const isInvalidCode = msg.includes("código de provisionamento inválido") const isCompanyNotFound = msg.includes("empresa não encontrada") const isConvexError = msg.includes("convexerror") const status = isInvalidCode ? 401 : isCompanyNotFound ? 404 : isConvexError ? 400 : 500 const payload = { error: "Falha ao provisionar máquina", details } return jsonWithCors(payload, status, origin, CORS_METHODS) } }