sistema-de-chamados/src/app/api/admin/users/route.ts

148 lines
4.3 KiB
TypeScript

import { NextResponse } from "next/server"
import { randomBytes } from "crypto"
import { hashPassword } from "better-auth/crypto"
import { ConvexHttpClient } from "convex/browser"
import type { UserRole } from "@prisma/client"
import { api } from "@/convex/_generated/api"
import { prisma } from "@/lib/prisma"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { assertStaffSession } from "@/lib/auth-server"
import { ROLE_OPTIONS, type RoleOption, isAdmin } from "@/lib/authz"
export const runtime = "nodejs"
function normalizeRole(input: string | null | undefined): RoleOption {
const role = (input ?? "agent").toLowerCase() as RoleOption
return (ROLE_OPTIONS as readonly string[]).includes(role) ? role : "agent"
}
const USER_ROLE_OPTIONS: ReadonlyArray<UserRole> = ["ADMIN", "MANAGER", "AGENT", "COLLABORATOR"]
function mapToUserRole(role: RoleOption): UserRole {
const candidate = role.toUpperCase() as UserRole
return USER_ROLE_OPTIONS.includes(candidate) ? candidate : "AGENT"
}
function generatePassword(length = 12) {
const bytes = randomBytes(length)
return Array.from(bytes)
.map((byte) => (byte % 36).toString(36))
.join("")
}
export async function GET() {
const session = await assertStaffSession()
if (!session) {
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
}
const users = await prisma.authUser.findMany({
orderBy: { createdAt: "desc" },
select: {
id: true,
email: true,
name: true,
role: true,
tenantId: true,
createdAt: true,
updatedAt: true,
},
})
return NextResponse.json({ users })
}
export async function POST(request: Request) {
const session = await assertStaffSession()
if (!session) {
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
}
if (!isAdmin(session.user.role)) {
return NextResponse.json({ error: "Apenas administradores podem criar usuários" }, { status: 403 })
}
const payload = await request.json().catch(() => null)
if (!payload || typeof payload !== "object") {
return NextResponse.json({ error: "Payload inválido" }, { status: 400 })
}
const emailInput = typeof payload.email === "string" ? payload.email.trim().toLowerCase() : ""
const nameInput = typeof payload.name === "string" ? payload.name.trim() : ""
const roleInput = typeof payload.role === "string" ? payload.role : undefined
const tenantInput = typeof payload.tenantId === "string" ? payload.tenantId.trim() : undefined
if (!emailInput || !emailInput.includes("@")) {
return NextResponse.json({ error: "Informe um e-mail válido" }, { status: 400 })
}
const role = normalizeRole(roleInput)
const tenantId = tenantInput || session.user.tenantId || DEFAULT_TENANT_ID
const userRole = mapToUserRole(role)
const existing = await prisma.authUser.findUnique({ where: { email: emailInput } })
if (existing) {
return NextResponse.json({ error: "Já existe um usuário com este e-mail" }, { status: 409 })
}
const password = generatePassword()
const hashedPassword = await hashPassword(password)
const user = await prisma.authUser.create({
data: {
email: emailInput,
name: nameInput || emailInput,
role,
tenantId,
accounts: {
create: {
providerId: "credential",
accountId: emailInput,
password: hashedPassword,
},
},
},
select: {
id: true,
email: true,
name: true,
role: true,
tenantId: true,
createdAt: true,
},
})
await prisma.user.upsert({
where: { email: user.email },
update: {
name: user.name ?? user.email,
role: userRole,
tenantId,
},
create: {
email: user.email,
name: user.name ?? user.email,
role: userRole,
tenantId,
},
})
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL
if (convexUrl) {
try {
const convex = new ConvexHttpClient(convexUrl)
await convex.mutation(api.users.ensureUser, {
tenantId,
email: emailInput,
name: nameInput || emailInput,
avatarUrl: undefined,
role: userRole,
})
} catch (error) {
console.warn("Falha ao sincronizar usuário no Convex", error)
}
}
return NextResponse.json({ user, temporaryPassword: password })
}