217 lines
7.1 KiB
TypeScript
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)
|
|
}
|
|
}
|