fix: remove duplicacao de comentario na troca de responsavel e corrige avatar
- Remove criacao automatica de comentario ao trocar responsavel (ja aparece na timeline) - Adiciona migration removeAssigneeChangeComments para limpar comentarios antigos - Adiciona campos description, type, options, answer ao schema de checklist no mapper - Cria mutation updateAvatar no Convex para sincronizar avatar com snapshots - Atualiza rota /api/profile/avatar para sincronizar com Convex ao adicionar/remover foto 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
59e9298d61
commit
9d1908a5aa
6 changed files with 193 additions and 78 deletions
|
|
@ -1043,3 +1043,81 @@ export const backfillTicketSnapshots = mutation({
|
|||
return { processed }
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Migration para remover comentarios duplicados de troca de responsavel.
|
||||
* Esses comentarios eram criados automaticamente ao trocar o responsavel,
|
||||
* mas essa informacao ja aparece na linha do tempo (ticketEvents).
|
||||
* O comentario segue o padrao: "<p><strong>Responsável atualizado:</strong>..."
|
||||
*/
|
||||
export const removeAssigneeChangeComments = mutation({
|
||||
args: {
|
||||
tenantId: v.optional(v.string()),
|
||||
limit: v.optional(v.number()),
|
||||
dryRun: v.optional(v.boolean()),
|
||||
},
|
||||
handler: async (ctx, { tenantId, limit, dryRun }) => {
|
||||
const effectiveDryRun = Boolean(dryRun)
|
||||
const effectiveLimit = limit && limit > 0 ? Math.min(limit, 500) : 500
|
||||
|
||||
// Busca comentarios internos que contenham o padrao de troca de responsavel
|
||||
const comments = tenantId && tenantId.trim().length > 0
|
||||
? await ctx.db.query("ticketComments").take(5000)
|
||||
: await ctx.db.query("ticketComments").take(5000)
|
||||
|
||||
// Filtrar comentarios que sao de troca de responsavel
|
||||
const assigneeChangePattern = "<p><strong>Responsável atualizado:</strong>"
|
||||
const toDelete = comments.filter((comment) => {
|
||||
if (comment.visibility !== "INTERNAL") return false
|
||||
if (typeof comment.body !== "string") return false
|
||||
return comment.body.includes(assigneeChangePattern)
|
||||
})
|
||||
|
||||
// Filtrar por tenant se especificado
|
||||
let filtered = toDelete
|
||||
if (tenantId && tenantId.trim().length > 0) {
|
||||
const ticketIds = new Set<string>()
|
||||
const tickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.take(10000)
|
||||
for (const t of tickets) {
|
||||
ticketIds.add(t._id)
|
||||
}
|
||||
filtered = toDelete.filter((c) => ticketIds.has(c.ticketId))
|
||||
}
|
||||
|
||||
const limitedComments = filtered.slice(0, effectiveLimit)
|
||||
let deleted = 0
|
||||
let eventsDeleted = 0
|
||||
|
||||
for (const comment of limitedComments) {
|
||||
if (!effectiveDryRun) {
|
||||
// Deletar o evento COMMENT_ADDED correspondente
|
||||
const events = await ctx.db
|
||||
.query("ticketEvents")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", comment.ticketId))
|
||||
.take(500)
|
||||
const matchingEvent = events.find(
|
||||
(event) =>
|
||||
event.type === "COMMENT_ADDED" &&
|
||||
Math.abs(event.createdAt - comment.createdAt) < 1000, // mesmo timestamp (tolerancia de 1s)
|
||||
)
|
||||
if (matchingEvent) {
|
||||
await ctx.db.delete(matchingEvent._id)
|
||||
eventsDeleted += 1
|
||||
}
|
||||
await ctx.db.delete(comment._id)
|
||||
}
|
||||
deleted += 1
|
||||
}
|
||||
|
||||
return {
|
||||
dryRun: effectiveDryRun,
|
||||
totalFound: filtered.length,
|
||||
deleted,
|
||||
eventsDeleted,
|
||||
remaining: filtered.length - deleted,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue