sistema-de-chamados/web/scripts/import-convex-to-prisma.mjs

292 lines
8.5 KiB
JavaScript

import { PrismaClient } from "@prisma/client"
import { ConvexHttpClient } from "convex/browser"
const prisma = new PrismaClient()
const tenantId = process.env.SYNC_TENANT_ID || "tenant-atlas"
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL || "http://127.0.0.1:3210"
const secret = process.env.CONVEX_SYNC_SECRET
const STAFF_ROSTER = [
{ email: "admin@sistema.dev", name: "Administrador", role: "ADMIN" },
{ email: "gabriel.oliveira@rever.com.br", name: "Gabriel Oliveira", role: "AGENT" },
{ email: "george.araujo@rever.com.br", name: "George Araujo", role: "AGENT" },
{ email: "hugo.soares@rever.com.br", name: "Hugo Soares", role: "AGENT" },
{ email: "julio@rever.com.br", name: "Julio Cesar", role: "AGENT" },
{ email: "lorena@rever.com.br", name: "Lorena Magalhães", role: "AGENT" },
{ email: "renan.pac@paulicon.com.br", name: "Rever", role: "AGENT" },
{ email: "thiago.medeiros@rever.com.br", name: "Thiago Medeiros", role: "AGENT" },
{ email: "weslei@rever.com.br", name: "Weslei Magalhães", role: "AGENT" },
]
const rawDefaultAssigneeEmail = process.env.SYNC_DEFAULT_ASSIGNEE || "gabriel.oliveira@rever.com.br"
if (!secret) {
console.error("CONVEX_SYNC_SECRET não configurado. Configure no .env.")
process.exit(1)
}
const allowedRoles = new Set(["ADMIN", "MANAGER", "AGENT", "COLLABORATOR", "CUSTOMER"])
const client = new ConvexHttpClient(convexUrl)
function normalizeEmail(email) {
if (!email) return null
return email.trim().toLowerCase()
}
const defaultAssigneeEmail = normalizeEmail(rawDefaultAssigneeEmail)
function toDate(value) {
if (!value && value !== 0) return null
return new Date(value)
}
async function upsertUsers(snapshotUsers) {
const map = new Map()
for (const user of snapshotUsers) {
const normalizedEmail = normalizeEmail(user.email)
if (!normalizedEmail) continue
const normalizedRole = (user.role ?? "CUSTOMER").toUpperCase()
const role = allowedRoles.has(normalizedRole) ? normalizedRole : "CUSTOMER"
const record = await prisma.user.upsert({
where: { email: normalizedEmail },
update: {
name: user.name ?? normalizedEmail,
role,
tenantId,
avatarUrl: user.avatarUrl ?? null,
},
create: {
email: normalizedEmail,
name: user.name ?? normalizedEmail,
role,
tenantId,
avatarUrl: user.avatarUrl ?? null,
},
})
map.set(normalizedEmail, record.id)
}
for (const staff of STAFF_ROSTER) {
const normalizedEmail = normalizeEmail(staff.email)
if (!normalizedEmail) continue
const record = await prisma.user.upsert({
where: { email: normalizedEmail },
update: {
name: staff.name,
role: staff.role,
tenantId,
},
create: {
email: normalizedEmail,
name: staff.name,
role: staff.role,
tenantId,
avatarUrl: null,
},
})
map.set(normalizedEmail, record.id)
}
const allowedStaffEmails = new Set(STAFF_ROSTER.map((staff) => normalizeEmail(staff.email)).filter(Boolean))
const removableStaff = await prisma.user.findMany({
where: {
tenantId,
role: { in: ["ADMIN", "MANAGER", "AGENT", "COLLABORATOR"] },
email: {
notIn: Array.from(allowedStaffEmails),
},
},
})
const fallbackAssigneeId = defaultAssigneeEmail ? map.get(defaultAssigneeEmail) ?? null : null
for (const staff of removableStaff) {
if (fallbackAssigneeId) {
await prisma.ticket.updateMany({
where: { tenantId, assigneeId: staff.id },
data: { assigneeId: fallbackAssigneeId },
})
await prisma.ticketComment.updateMany({
where: { authorId: staff.id },
data: { authorId: fallbackAssigneeId },
})
}
await prisma.user.update({
where: { id: staff.id },
data: {
role: "CUSTOMER",
},
})
}
return map
}
async function upsertQueues(snapshotQueues) {
const map = new Map()
for (const queue of snapshotQueues) {
if (!queue.slug) continue
const record = await prisma.queue.upsert({
where: {
tenantId_slug: {
tenantId,
slug: queue.slug,
},
},
update: {
name: queue.name,
},
create: {
tenantId,
name: queue.name,
slug: queue.slug,
},
})
map.set(queue.slug, record.id)
}
return map
}
async function upsertTickets(snapshotTickets, userMap, queueMap) {
let created = 0
let updated = 0
const fallbackAssigneeId = defaultAssigneeEmail ? userMap.get(defaultAssigneeEmail) ?? null : null
for (const ticket of snapshotTickets) {
if (!ticket.requesterEmail) continue
const requesterId = userMap.get(normalizeEmail(ticket.requesterEmail))
if (!requesterId) continue
const queueId = ticket.queueSlug ? queueMap.get(ticket.queueSlug) ?? null : null
const desiredAssigneeEmail = defaultAssigneeEmail || normalizeEmail(ticket.assigneeEmail)
const assigneeId = desiredAssigneeEmail ? userMap.get(desiredAssigneeEmail) || fallbackAssigneeId || null : fallbackAssigneeId || null
const existing = await prisma.ticket.findFirst({
where: {
tenantId,
reference: ticket.reference,
},
})
const data = {
subject: ticket.subject,
summary: ticket.summary ?? null,
status: (ticket.status ?? "NEW").toUpperCase(),
priority: (ticket.priority ?? "MEDIUM").toUpperCase(),
channel: (ticket.channel ?? "MANUAL").toUpperCase(),
queueId,
requesterId,
assigneeId,
dueAt: toDate(ticket.dueAt),
firstResponseAt: toDate(ticket.firstResponseAt),
resolvedAt: toDate(ticket.resolvedAt),
closedAt: toDate(ticket.closedAt),
createdAt: toDate(ticket.createdAt) ?? new Date(),
updatedAt: toDate(ticket.updatedAt) ?? new Date(),
}
let ticketRecord
if (existing) {
ticketRecord = await prisma.ticket.update({
where: { id: existing.id },
data,
})
updated += 1
} else {
ticketRecord = await prisma.ticket.create({
data: {
tenantId,
reference: ticket.reference,
...data,
},
})
created += 1
}
await prisma.ticketComment.deleteMany({ where: { ticketId: ticketRecord.id } })
await prisma.ticketEvent.deleteMany({ where: { ticketId: ticketRecord.id } })
const commentsData = ticket.comments
.map((comment) => {
const authorId = comment.authorEmail ? userMap.get(normalizeEmail(comment.authorEmail)) : null
if (!authorId) {
return null
}
return {
ticketId: ticketRecord.id,
authorId,
visibility: (comment.visibility ?? "INTERNAL").toUpperCase(),
body: comment.body ?? "",
attachments: null,
createdAt: toDate(comment.createdAt) ?? new Date(),
updatedAt: toDate(comment.updatedAt) ?? new Date(),
}
})
.filter(Boolean)
if (commentsData.length > 0) {
await prisma.ticketComment.createMany({ data: commentsData })
}
const eventsData = ticket.events.map((event) => ({
ticketId: ticketRecord.id,
type: event.type ?? "UNKNOWN",
payload: event.payload ?? {},
createdAt: toDate(event.createdAt) ?? new Date(),
}))
if (eventsData.length > 0) {
await prisma.ticketEvent.createMany({ data: eventsData })
}
}
return { created, updated }
}
async function run() {
console.log("Baixando snapshot do Convex...")
const snapshot = await client.query("migrations:exportTenantSnapshot", {
secret,
tenantId,
})
console.log(`Usuários recebidos: ${snapshot.users.length}`)
console.log(`Filas recebidas: ${snapshot.queues.length}`)
console.log(`Tickets recebidos: ${snapshot.tickets.length}`)
console.log("Sincronizando usuários no Prisma...")
const userMap = await upsertUsers(snapshot.users)
console.log(`Usuários ativos no mapa: ${userMap.size}`)
console.log("Sincronizando filas no Prisma...")
const queueMap = await upsertQueues(snapshot.queues)
console.log(`Filas ativas no mapa: ${queueMap.size}`)
console.log("Sincronizando tickets no Prisma...")
const ticketStats = await upsertTickets(snapshot.tickets, userMap, queueMap)
console.log(`Tickets criados: ${ticketStats.created}`)
console.log(`Tickets atualizados: ${ticketStats.updated}`)
}
run()
.catch((error) => {
console.error("Falha ao importar dados do Convex para Prisma", error)
process.exitCode = 1
})
.finally(async () => {
await prisma.$disconnect()
})