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

240 lines
8.3 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server"
import { cookies } from "next/headers"
import { ConvexHttpClient } from "convex/browser"
import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel"
import { env } from "@/lib/env"
import { assertAuthenticatedSession } from "@/lib/auth-server"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
const MACHINE_CTX_COOKIE = "machine_ctx"
// Força runtime Node.js para leitura consistente de cookies de sessão
export const runtime = "nodejs"
type CollaboratorMetadata = {
email: string
name: string | null
role: "collaborator" | "manager" | null
}
function decodeMachineCookie(value: string) {
try {
const json = Buffer.from(value, "base64url").toString("utf8")
return JSON.parse(json) as {
machineId: string
persona: string | null
assignedUserId: string | null
assignedUserEmail: string | null
assignedUserName: string | null
assignedUserRole: string | null
}
} catch {
return null
}
}
function encodeMachineCookie(payload: {
machineId: string
persona: string | null
assignedUserId: string | null
assignedUserEmail: string | null
assignedUserName: string | null
assignedUserRole: string | null
}) {
return Buffer.from(JSON.stringify(payload)).toString("base64url")
}
function extractCollaboratorFromMetadata(metadata: unknown): CollaboratorMetadata | null {
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
return null
}
const record = metadata as Record<string, unknown>
const raw = record["collaborator"]
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return null
}
const base = raw as Record<string, unknown>
const emailValue = base["email"]
if (typeof emailValue !== "string") {
return null
}
const email = emailValue.trim().toLowerCase()
if (!email) {
return null
}
const nameValue = base["name"]
const roleValue = base["role"]
const name = typeof nameValue === "string" ? (nameValue.trim() || null) : null
const normalizedRole =
typeof roleValue === "string" ? roleValue.trim().toLowerCase() : null
const role =
normalizedRole === "manager"
? "manager"
: normalizedRole === "collaborator"
? "collaborator"
: null
return {
email,
name,
role,
}
}
export async function GET(request: NextRequest) {
const session = await assertAuthenticatedSession()
if (!session || session.user?.role !== "machine") {
return NextResponse.json({ error: "Sessão de máquina não encontrada." }, { status: 403 })
}
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
if (!convexUrl) {
return NextResponse.json({ error: "Convex não configurado." }, { status: 500 })
}
const client = new ConvexHttpClient(convexUrl)
const cookieStore = await cookies()
const cookieValue = cookieStore.get(MACHINE_CTX_COOKIE)?.value ?? null
const decoded = cookieValue ? decodeMachineCookie(cookieValue) : null
let machineId: Id<"machines"> | null = decoded?.machineId ? (decoded.machineId as Id<"machines">) : null
if (!machineId) {
try {
const lookup = (await client.query(api.machines.findByAuthEmail, {
authEmail: session.user.email.toLowerCase(),
})) as { id: string } | null
if (!lookup?.id) {
return NextResponse.json({ error: "Máquina não vinculada à sessão atual." }, { status: 404 })
}
machineId = lookup.id as Id<"machines">
} catch (error) {
console.error("[machines.session] Falha ao localizar máquina por e-mail", error)
return NextResponse.json({ error: "Não foi possível localizar a máquina." }, { status: 500 })
}
}
try {
let context = (await client.query(api.machines.getContext, {
machineId,
})) as {
id: string
tenantId: string
companyId: string | null
companySlug: string | null
persona: string | null
assignedUserId: string | null
assignedUserEmail: string | null
assignedUserName: string | null
assignedUserRole: string | null
metadata: Record<string, unknown> | null
authEmail: string | null
}
const metadataCollaborator = extractCollaboratorFromMetadata(context.metadata)
let ensuredAssignedUserId = context.assignedUserId
let ensuredAssignedUserEmail = context.assignedUserEmail ?? metadataCollaborator?.email ?? null
let ensuredAssignedUserName = context.assignedUserName ?? metadataCollaborator?.name ?? null
let ensuredAssignedUserRole =
context.assignedUserRole ??
(metadataCollaborator?.role ? metadataCollaborator.role.toUpperCase() : null)
let ensuredPersona = context.persona ?? metadataCollaborator?.role ?? null
if (!ensuredAssignedUserId && ensuredAssignedUserEmail) {
try {
const personaRoleCandidate = ensuredPersona ?? ensuredAssignedUserRole ?? "collaborator"
const personaRole = personaRoleCandidate.toString().toLowerCase()
const normalizedPersona = personaRole === "manager" ? "manager" : "collaborator"
const assignedRole = normalizedPersona === "manager" ? "MANAGER" : "COLLABORATOR"
const ensuredUser = (await client.mutation(api.users.ensureUser, {
tenantId: context.tenantId ?? DEFAULT_TENANT_ID,
email: ensuredAssignedUserEmail,
name: ensuredAssignedUserName ?? ensuredAssignedUserEmail,
role: normalizedPersona.toUpperCase(),
companyId: context.companyId ? (context.companyId as Id<"companies">) : undefined,
})) as {
_id?: Id<"users">
name?: string | null
role?: string | null
} | null
if (ensuredUser?._id) {
ensuredAssignedUserId = ensuredUser._id as string
ensuredAssignedUserName = ensuredUser.name ?? ensuredAssignedUserName ?? ensuredAssignedUserEmail
ensuredAssignedUserRole = ensuredUser.role ?? ensuredAssignedUserRole ?? assignedRole
ensuredPersona = normalizedPersona
await client.mutation(api.machines.updatePersona, {
machineId: machineId as Id<"machines">,
persona: normalizedPersona,
assignedUserId: ensuredUser._id as Id<"users">,
assignedUserEmail: ensuredAssignedUserEmail,
assignedUserName: ensuredAssignedUserName ?? undefined,
assignedUserRole: (ensuredAssignedUserRole ?? assignedRole).toUpperCase(),
})
context = (await client.query(api.machines.getContext, {
machineId,
})) as typeof context
ensuredAssignedUserId = context.assignedUserId ?? ensuredAssignedUserId
ensuredAssignedUserEmail = context.assignedUserEmail ?? ensuredAssignedUserEmail
ensuredAssignedUserName = context.assignedUserName ?? ensuredAssignedUserName
ensuredAssignedUserRole = context.assignedUserRole ?? ensuredAssignedUserRole
ensuredPersona = context.persona ?? ensuredPersona
}
} catch (error) {
console.error("[machines.session] Falha ao garantir usuário vinculado", error)
}
}
const resolvedPersona =
context.persona ??
ensuredPersona ??
(ensuredAssignedUserRole ? ensuredAssignedUserRole.toLowerCase() : null)
const responsePayload = {
machineId: context.id,
persona: resolvedPersona,
assignedUserId: ensuredAssignedUserId,
assignedUserEmail: ensuredAssignedUserEmail,
assignedUserName: ensuredAssignedUserName,
assignedUserRole: ensuredAssignedUserRole,
}
const response = NextResponse.json({
machine: {
...context,
persona: resolvedPersona,
assignedUserEmail: ensuredAssignedUserEmail ?? null,
assignedUserId: ensuredAssignedUserId ?? null,
assignedUserName: ensuredAssignedUserName ?? null,
assignedUserRole: ensuredAssignedUserRole ?? null,
},
cookie: responsePayload,
})
const isSecure = request.nextUrl.protocol === "https:"
response.cookies.set({
name: MACHINE_CTX_COOKIE,
value: encodeMachineCookie(responsePayload),
httpOnly: true,
sameSite: "lax",
secure: isSecure,
path: "/",
maxAge: 60 * 60 * 24 * 30,
})
return response
} catch (error) {
console.error("[machines.session] Falha ao obter contexto da máquina", error)
return NextResponse.json({ error: "Falha ao obter contexto da máquina." }, { status: 500 })
}
}