import { ConvexError, v } from "convex/values" import { mutation, query } from "./_generated/server" import type { Id } from "./_generated/dataModel" import { requireStaff } from "./rbac" const DEFAULT_STATUS = "investigating" function timelineId() { if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { return crypto.randomUUID() } return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}` } export const list = query({ args: { tenantId: v.string(), viewerId: v.id("users") }, handler: async (ctx, { tenantId, viewerId }) => { await requireStaff(ctx, viewerId, tenantId) const incidents = await ctx.db .query("incidents") .withIndex("by_tenant_updated", (q) => q.eq("tenantId", tenantId)) .order("desc") .take(100) return incidents }, }) export const createIncident = mutation({ args: { tenantId: v.string(), actorId: v.id("users"), title: v.string(), severity: v.string(), impactSummary: v.optional(v.string()), affectedQueues: v.optional(v.array(v.string())), initialUpdate: v.optional(v.string()), }, handler: async (ctx, { tenantId, actorId, title, severity, impactSummary, affectedQueues, initialUpdate }) => { const viewer = await requireStaff(ctx, actorId, tenantId) const normalizedTitle = title.trim() if (normalizedTitle.length < 3) { throw new ConvexError("Informe um título válido para o incidente") } const now = Date.now() const timelineEntry = { id: timelineId(), authorId: actorId, authorName: viewer.user.name ?? viewer.user.email ?? "Equipe", message: initialUpdate?.trim().length ? initialUpdate.trim() : "Incidente registrado.", type: "created", createdAt: now, } const id = await ctx.db.insert("incidents", { tenantId, title: normalizedTitle, status: DEFAULT_STATUS, severity, impactSummary: impactSummary?.trim() || undefined, affectedQueues: affectedQueues ?? [], ownerId: actorId, ownerName: viewer.user.name ?? undefined, ownerEmail: viewer.user.email ?? undefined, startedAt: now, updatedAt: now, resolvedAt: undefined, timeline: [timelineEntry], }) return id }, }) export const updateIncidentStatus = mutation({ args: { tenantId: v.string(), actorId: v.id("users"), incidentId: v.id("incidents"), status: v.string(), }, handler: async (ctx, { tenantId, actorId, incidentId, status }) => { const viewer = await requireStaff(ctx, actorId, tenantId) const incident = await ctx.db.get(incidentId) if (!incident || incident.tenantId !== tenantId) { throw new ConvexError("Incidente não encontrado") } const now = Date.now() const timeline = [ ...(incident.timeline ?? []), { id: timelineId(), authorId: actorId, authorName: viewer.user.name ?? viewer.user.email ?? "Equipe", message: `Status atualizado para ${status}`, type: "status", createdAt: now, }, ] await ctx.db.patch(incidentId, { status, updatedAt: now, resolvedAt: status === "resolved" ? now : incident.resolvedAt ?? undefined, timeline, }) }, }) export const bulkUpdateIncidentStatus = mutation({ args: { tenantId: v.string(), actorId: v.id("users"), incidentIds: v.array(v.id("incidents")), status: v.string(), }, handler: async (ctx, { tenantId, actorId, incidentIds, status }) => { const viewer = await requireStaff(ctx, actorId, tenantId) const now = Date.now() for (const incidentId of incidentIds) { const incident = await ctx.db.get(incidentId) if (!incident || incident.tenantId !== tenantId) continue const timeline = [ ...(incident.timeline ?? []), { id: timelineId(), authorId: actorId, authorName: viewer.user.name ?? viewer.user.email ?? "Equipe", message: `Status atualizado em massa para ${status}`, type: "status", createdAt: now, }, ] await ctx.db.patch(incidentId, { status, updatedAt: now, resolvedAt: status === "resolved" ? now : incident.resolvedAt ?? undefined, timeline, }) } }, }) export const addIncidentUpdate = mutation({ args: { tenantId: v.string(), actorId: v.id("users"), incidentId: v.id("incidents"), message: v.string(), status: v.optional(v.string()), }, handler: async (ctx, { tenantId, actorId, incidentId, message, status }) => { const viewer = await requireStaff(ctx, actorId, tenantId) const incident = await ctx.db.get(incidentId) if (!incident || incident.tenantId !== tenantId) { throw new ConvexError("Incidente não encontrado") } const trimmed = message.trim() if (trimmed.length < 3) { throw new ConvexError("Descreva a atualização do incidente") } const now = Date.now() const timeline = [ ...(incident.timeline ?? []), { id: timelineId(), authorId: actorId, authorName: viewer.user.name ?? viewer.user.email ?? "Equipe", message: trimmed, type: "update", createdAt: now, }, ] await ctx.db.patch(incidentId, { timeline, status: status ?? incident.status, updatedAt: now, resolvedAt: status === "resolved" ? now : incident.resolvedAt ?? undefined, }) }, })