Implement company provisioning codes and session tweaks

This commit is contained in:
Esdras Renan 2025-10-15 20:45:25 -03:00
parent 0fb9bf59b2
commit 2cba553efa
28 changed files with 1407 additions and 534 deletions

View file

@ -5,15 +5,13 @@ import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel"
import { env } from "@/lib/env"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { ensureMachineAccount } from "@/server/machines-auth"
import { ensureCollaboratorAccount, ensureMachineAccount } from "@/server/machines-auth"
import { createCorsPreflight, jsonWithCors } from "@/server/cors"
import { normalizeSlug } from "@/lib/slug"
import { prisma } from "@/lib/prisma"
const registerSchema = z
.object({
provisioningSecret: z.string().min(1),
tenantId: z.string().optional(),
companySlug: z.string().optional(),
provisioningCode: z.string().min(32),
hostname: z.string().min(1),
os: z.object({
name: z.string().min(1),
@ -67,13 +65,25 @@ export async function POST(request: Request) {
}
const client = new ConvexHttpClient(convexUrl)
let normalizedCompanySlug: string | undefined
try {
const tenantId = payload.tenantId ?? DEFAULT_TENANT_ID
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,
request.headers.get("origin"),
CORS_METHODS
)
}
const tenantId = companyRecord.tenantId ?? DEFAULT_TENANT_ID
const persona = payload.accessRole ?? undefined
const collaborator = payload.collaborator ?? null
normalizedCompanySlug = normalizeSlug(payload.companySlug)
if (persona && !collaborator) {
return jsonWithCors(
@ -99,10 +109,15 @@ export async function POST(request: Request) {
}
}
const registration = await client.mutation(api.machines.register, {
provisioningSecret: payload.provisioningSecret,
await client.mutation(api.companies.ensureProvisioned, {
tenantId,
companySlug: normalizedCompanySlug,
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,
@ -126,26 +141,39 @@ export async function POST(request: Request) {
})
let assignedUserId: Id<"users"> | undefined
if (persona && collaborator) {
if (collaborator) {
const ensuredUser = (await client.mutation(api.users.ensureUser, {
tenantId,
email: collaborator.email,
name: collaborator.name ?? collaborator.email,
avatarUrl: undefined,
role: persona.toUpperCase(),
role: persona?.toUpperCase(),
companyId: registration.companyId ? (registration.companyId as Id<"companies">) : undefined,
})) as { _id?: Id<"users"> } | null
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",
await ensureCollaboratorAccount({
email: collaborator.email,
name: collaborator.name ?? collaborator.email,
tenantId,
companyId: companyRecord.id,
})
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">,
@ -174,18 +202,11 @@ export async function POST(request: Request) {
console.error("[machines.register] Falha no provisionamento", error)
const details = error instanceof Error ? error.message : String(error)
const msg = details.toLowerCase()
// Mapear alguns erros "esperados" para códigos adequados
// - empresa inválida → 404
// - segredo inválido → 401
// - demais ConvexError → 400
const isInvalidCode = msg.includes("código de provisionamento inválido")
const isCompanyNotFound = msg.includes("empresa não encontrada")
const isInvalidSecret = msg.includes("código de provisionamento inválido")
const isConvexError = msg.includes("convexerror")
const status = isCompanyNotFound ? 404 : isInvalidSecret ? 401 : isConvexError ? 400 : 500
const payload = { error: "Falha ao provisionar máquina", details } as Record<string, unknown>
if (isCompanyNotFound && normalizedCompanySlug) {
payload["companySlug"] = normalizedCompanySlug
}
const status = isInvalidCode ? 401 : isCompanyNotFound ? 404 : isConvexError ? 400 : 500
const payload = { error: "Falha ao provisionar máquina", details }
return jsonWithCors(payload, status, request.headers.get("origin"), CORS_METHODS)
}
}