121 lines
3.5 KiB
TypeScript
121 lines
3.5 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
import { randomBytes } from "crypto"
|
|
|
|
import { hashPassword } from "better-auth/crypto"
|
|
import { ConvexHttpClient } from "convex/browser"
|
|
|
|
import { api } from "@/convex/_generated/api"
|
|
import { prisma } from "@/lib/prisma"
|
|
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
|
import { assertAdminSession } from "@/lib/auth-server"
|
|
import { ROLE_OPTIONS, type RoleOption } 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"
|
|
}
|
|
|
|
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 assertAdminSession()
|
|
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 assertAdminSession()
|
|
if (!session) {
|
|
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
|
}
|
|
|
|
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 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,
|
|
},
|
|
})
|
|
|
|
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: role.toUpperCase(),
|
|
})
|
|
} catch (error) {
|
|
console.warn("Falha ao sincronizar usuário no Convex", error)
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ user, temporaryPassword: password })
|
|
}
|