Atualiza portal e admin com bloqueio de máquinas desativadas

This commit is contained in:
Esdras Renan 2025-10-18 00:02:15 -03:00
parent e5085962e9
commit 630110bf3a
31 changed files with 1756 additions and 244 deletions

View file

@ -0,0 +1,156 @@
import { NextResponse } from "next/server"
import { prisma } from "@/lib/prisma"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { assertStaffSession } from "@/lib/auth-server"
import { isAdmin } from "@/lib/authz"
export const runtime = "nodejs"
const ALLOWED_ROLES = ["MANAGER", "COLLABORATOR"] as const
type AllowedRole = (typeof ALLOWED_ROLES)[number]
function normalizeRole(role?: string | null): AllowedRole {
const normalized = (role ?? "COLLABORATOR").toUpperCase()
return ALLOWED_ROLES.includes(normalized as AllowedRole) ? (normalized as AllowedRole) : "COLLABORATOR"
}
export async function GET() {
const session = await assertStaffSession()
if (!session) {
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
}
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
const users = await prisma.user.findMany({
where: {
tenantId,
role: { in: [...ALLOWED_ROLES] },
},
include: {
company: {
select: {
id: true,
name: true,
},
},
},
orderBy: { createdAt: "desc" },
})
const emails = users.map((user) => user.email)
const authUsers = await prisma.authUser.findMany({
where: { email: { in: emails } },
select: {
id: true,
email: true,
updatedAt: true,
createdAt: true,
},
})
const sessions = await prisma.authSession.findMany({
where: { userId: { in: authUsers.map((authUser) => authUser.id) } },
orderBy: { updatedAt: "desc" },
select: {
userId: true,
updatedAt: true,
},
})
const sessionByUserId = new Map<string, Date>()
for (const sessionRow of sessions) {
if (!sessionByUserId.has(sessionRow.userId)) {
sessionByUserId.set(sessionRow.userId, sessionRow.updatedAt)
}
}
const authByEmail = new Map<string, { id: string; updatedAt: Date; createdAt: Date }>()
for (const authUser of authUsers) {
authByEmail.set(authUser.email.toLowerCase(), {
id: authUser.id,
updatedAt: authUser.updatedAt,
createdAt: authUser.createdAt,
})
}
const items = users.map((user) => {
const auth = authByEmail.get(user.email.toLowerCase())
const lastSeenAt = auth ? sessionByUserId.get(auth.id) ?? auth.updatedAt : null
return {
id: user.id,
email: user.email,
name: user.name,
role: normalizeRole(user.role),
companyId: user.companyId,
companyName: user.company?.name ?? null,
tenantId: user.tenantId,
createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt.toISOString(),
authUserId: auth?.id ?? null,
lastSeenAt: lastSeenAt ? lastSeenAt.toISOString() : null,
}
})
return NextResponse.json({ items })
}
export async function DELETE(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 excluir clientes." }, { status: 403 })
}
const json = await request.json().catch(() => null)
const ids = Array.isArray(json?.ids) ? (json.ids as string[]) : []
if (ids.length === 0) {
return NextResponse.json({ error: "Nenhum cliente selecionado." }, { status: 400 })
}
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
const users = await prisma.user.findMany({
where: {
id: { in: ids },
tenantId,
role: { in: [...ALLOWED_ROLES] },
},
select: {
id: true,
email: true,
},
})
if (users.length === 0) {
return NextResponse.json({ deletedIds: [] })
}
const emails = users.map((user) => user.email.toLowerCase())
const authUsers = await prisma.authUser.findMany({
where: {
email: { in: emails },
},
select: {
id: true,
},
})
const authUserIds = authUsers.map((authUser) => authUser.id)
await prisma.$transaction(async (tx) => {
if (authUserIds.length > 0) {
await tx.authSession.deleteMany({ where: { userId: { in: authUserIds } } })
await tx.authAccount.deleteMany({ where: { userId: { in: authUserIds } } })
await tx.authUser.deleteMany({ where: { id: { in: authUserIds } } })
}
await tx.user.deleteMany({ where: { id: { in: users.map((user) => user.id) } } })
})
return NextResponse.json({ deletedIds: users.map((user) => user.id) })
}

View file

@ -0,0 +1,61 @@
import { NextResponse } from "next/server"
import { z } from "zod"
import { ConvexHttpClient } from "convex/browser"
import { assertAuthenticatedSession } from "@/lib/auth-server"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { api } from "@/convex/_generated/api"
export const runtime = "nodejs"
const schema = z.object({
machineId: z.string().min(1),
active: z.boolean(),
})
export async function POST(request: Request) {
const session = await assertAuthenticatedSession()
if (!session) {
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
}
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL
if (!convexUrl) {
return NextResponse.json({ error: "Convex não configurado" }, { status: 500 })
}
const payload = await request.json().catch(() => null)
const parsed = schema.safeParse(payload)
if (!parsed.success) {
return NextResponse.json({ error: "Payload inválido", details: parsed.error.flatten() }, { status: 400 })
}
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
try {
const convex = new ConvexHttpClient(convexUrl)
const ensured = await convex.mutation(api.users.ensureUser, {
tenantId,
email: session.user.email,
name: session.user.name ?? session.user.email,
avatarUrl: session.user.avatarUrl ?? undefined,
role: session.user.role.toUpperCase(),
})
const actorId = ensured?._id
if (!actorId) {
return NextResponse.json({ error: "Falha ao obter ID do usuário no Convex" }, { status: 500 })
}
const client = convex as unknown as { mutation: (name: string, args: unknown) => Promise<unknown> }
await client.mutation("machines:toggleActive", {
machineId: parsed.data.machineId,
actorId,
active: parsed.data.active,
})
return NextResponse.json({ ok: true })
} catch (error) {
console.error("[machines.toggleActive] Falha ao atualizar status", error)
return NextResponse.json({ error: "Falha ao atualizar status da máquina" }, { status: 500 })
}
}