feat: improve requester combobox and admin cleanup flows
This commit is contained in:
parent
788f6928a1
commit
37c32149a6
13 changed files with 923 additions and 180 deletions
160
src/app/api/admin/users/cleanup/route.ts
Normal file
160
src/app/api/admin/users/cleanup/route.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import { NextResponse } from "next/server"
|
||||
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { isAdmin } from "@/lib/authz"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { createConvexClient } from "@/server/convex-client"
|
||||
|
||||
const DEFAULT_KEEP_EMAILS = ["renan.pac@paulicon.com.br"]
|
||||
|
||||
type CleanupSummary = {
|
||||
removedPortalUserIds: string[]
|
||||
removedPortalEmails: string[]
|
||||
removedAuthUserIds: string[]
|
||||
removedConvexUserIds: string[]
|
||||
removedTicketIds: string[]
|
||||
convictTicketsDeleted: number
|
||||
keepEmails: string[]
|
||||
}
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
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 remover dados." }, { status: 403 })
|
||||
}
|
||||
|
||||
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
|
||||
const sessionEmail = session.user.email?.toLowerCase()
|
||||
if (!sessionEmail) {
|
||||
return NextResponse.json({ error: "Administrador sem e-mail associado." }, { status: 400 })
|
||||
}
|
||||
|
||||
const body = await request.json().catch(() => ({})) as { keepEmails?: string[] }
|
||||
const keepEmailsInput = Array.isArray(body.keepEmails) ? body.keepEmails : []
|
||||
const keepEmailsSet = new Set<string>()
|
||||
DEFAULT_KEEP_EMAILS.forEach((email) => keepEmailsSet.add(email.toLowerCase()))
|
||||
keepEmailsInput
|
||||
.map((email) => (typeof email === "string" ? email.trim().toLowerCase() : ""))
|
||||
.filter((email) => email.length > 0)
|
||||
.forEach((email) => keepEmailsSet.add(email))
|
||||
|
||||
keepEmailsSet.add(sessionEmail)
|
||||
const viewerEmail = sessionEmail
|
||||
|
||||
const portalUsers = await prisma.user.findMany({
|
||||
where: { tenantId },
|
||||
select: { id: true, email: true },
|
||||
})
|
||||
|
||||
const portalToRemove = portalUsers.filter((user) => !keepEmailsSet.has(user.email.toLowerCase()))
|
||||
const portalIdsToRemove = portalToRemove.map((user) => user.id)
|
||||
const portalEmailsToRemove = portalToRemove.map((user) => user.email.toLowerCase())
|
||||
|
||||
const responseSummary: CleanupSummary = {
|
||||
removedPortalUserIds: portalIdsToRemove,
|
||||
removedPortalEmails: portalEmailsToRemove,
|
||||
removedAuthUserIds: [],
|
||||
removedConvexUserIds: [],
|
||||
removedTicketIds: [],
|
||||
convictTicketsDeleted: 0,
|
||||
keepEmails: Array.from(keepEmailsSet),
|
||||
}
|
||||
|
||||
if (portalIdsToRemove.length > 0) {
|
||||
const ticketsToRemove = await prisma.ticket.findMany({
|
||||
where: {
|
||||
tenantId,
|
||||
OR: [{ requesterId: { in: portalIdsToRemove } }, { assigneeId: { in: portalIdsToRemove } }],
|
||||
},
|
||||
select: { id: true },
|
||||
})
|
||||
responseSummary.removedTicketIds = ticketsToRemove.map((ticket) => ticket.id)
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
if (ticketsToRemove.length > 0) {
|
||||
await tx.ticket.deleteMany({
|
||||
where: { id: { in: ticketsToRemove.map((ticket) => ticket.id) } },
|
||||
})
|
||||
}
|
||||
|
||||
if (portalEmailsToRemove.length > 0) {
|
||||
const authUsers = await tx.authUser.findMany({
|
||||
where: { email: { in: portalEmailsToRemove } },
|
||||
select: { id: true },
|
||||
})
|
||||
if (authUsers.length > 0) {
|
||||
const authIds = authUsers.map((item) => item.id)
|
||||
responseSummary.removedAuthUserIds = authIds
|
||||
await tx.authSession.deleteMany({ where: { userId: { in: authIds } } })
|
||||
await tx.authAccount.deleteMany({ where: { userId: { in: authIds } } })
|
||||
await tx.authUser.deleteMany({ where: { id: { in: authIds } } })
|
||||
}
|
||||
}
|
||||
|
||||
await tx.user.deleteMany({
|
||||
where: { id: { in: portalIdsToRemove } },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const client = createConvexClient()
|
||||
const actor =
|
||||
(await client.query(api.users.findByEmail, { tenantId, email: sessionEmail })) ??
|
||||
(await client.mutation(api.users.ensureUser, {
|
||||
tenantId,
|
||||
email: sessionEmail,
|
||||
name: session.user.name ?? sessionEmail,
|
||||
}))
|
||||
|
||||
const actorId = actor?._id as Id<"users"> | undefined
|
||||
if (actorId) {
|
||||
const convexCustomers = await client.query(api.users.listCustomers, {
|
||||
tenantId,
|
||||
viewerId: actorId,
|
||||
})
|
||||
|
||||
const convexToRemove = convexCustomers.filter(
|
||||
(customer) => !keepEmailsSet.has(customer.email.toLowerCase()),
|
||||
)
|
||||
|
||||
const removedConvexIds: Id<"users">[] = []
|
||||
for (const customer of convexToRemove) {
|
||||
const userId = customer.id as Id<"users">
|
||||
try {
|
||||
await client.mutation(api.users.deleteUser, { userId, actorId })
|
||||
removedConvexIds.push(userId)
|
||||
} catch (error) {
|
||||
console.error("[users.cleanup] Falha ao remover usuário do Convex", customer.email, error)
|
||||
}
|
||||
}
|
||||
responseSummary.removedConvexUserIds = removedConvexIds.map((id) => id as unknown as string)
|
||||
|
||||
if (removedConvexIds.length > 0) {
|
||||
try {
|
||||
const result = await client.mutation(api.tickets.purgeTicketsForUsers, {
|
||||
tenantId,
|
||||
actorId,
|
||||
userIds: removedConvexIds,
|
||||
})
|
||||
responseSummary.convictTicketsDeleted = typeof result?.deleted === "number" ? result.deleted : 0
|
||||
} catch (error) {
|
||||
console.error("[users.cleanup] Falha ao remover tickets no Convex", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[users.cleanup] Convex indisponível", error)
|
||||
}
|
||||
|
||||
return NextResponse.json(responseSummary)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue