sistema-de-chamados/src/app/api/machines/register/route.ts

217 lines
7.1 KiB
TypeScript

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<string, unknown> | undefined = payload.metadata
? { ...(payload.metadata as Record<string, unknown>) }
: 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)
}
}