feat: SSE para chat desktop, rate limiting, retry, testes e atualizacao de stack

- Implementa Server-Sent Events (SSE) para chat no desktop com fallback HTTP
- Adiciona rate limiting nas APIs de chat (poll, messages, sessions)
- Adiciona retry com backoff exponencial para mutations
- Cria testes para modulo liveChat (20 testes)
- Corrige testes de SMTP (unit tests para extractEnvelopeAddress)
- Adiciona indice by_status_lastActivity para cron de sessoes inativas
- Atualiza stack: Bun 1.3.4, React 19, recharts 3, noble/hashes 2, etc

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-07 16:29:18 -03:00
parent 0e0bd9a49c
commit d01c37522f
19 changed files with 1465 additions and 443 deletions

View file

@ -3,8 +3,8 @@ import { action, mutation, query, type MutationCtx, type QueryCtx } from "./_gen
import { ConvexError } from "convex/values"
import { api } from "./_generated/api"
import type { Doc, Id } from "./_generated/dataModel"
import { sha256 } from "@noble/hashes/sha256"
import { bytesToHex as toHex } from "@noble/hashes/utils"
import { sha256 } from "@noble/hashes/sha2.js"
import { bytesToHex as toHex } from "@noble/hashes/utils.js"
// ============================================
// HELPERS
@ -728,14 +728,11 @@ export const autoEndInactiveSessions = mutation({
// Limitar a 50 sessões por execução para evitar timeout do cron (30s)
const maxSessionsPerRun = 50
// Buscar sessões ativas com inatividade > 5 minutos (com limite)
// Buscar sessões ativas com inatividade > 5 minutos (usando índice otimizado)
const inactiveSessions = await ctx.db
.query("liveChatSessions")
.filter((q) =>
q.and(
q.eq(q.field("status"), "ACTIVE"),
q.lt(q.field("lastActivityAt"), cutoffTime)
)
.withIndex("by_status_lastActivity", (q) =>
q.eq("status", "ACTIVE").lt("lastActivityAt", cutoffTime)
)
.take(maxSessionsPerRun)

View file

@ -3,8 +3,8 @@ import { mutation, query } from "./_generated/server"
import { api } from "./_generated/api"
import { paginationOptsValidator } from "convex/server"
import { ConvexError, v, Infer } from "convex/values"
import { sha256 } from "@noble/hashes/sha256"
import { randomBytes } from "@noble/hashes/utils"
import { sha256 } from "@noble/hashes/sha2.js"
import { randomBytes } from "@noble/hashes/utils.js"
import type { Doc, Id } from "./_generated/dataModel"
import type { MutationCtx, QueryCtx } from "./_generated/server"
import { normalizeStatus } from "./tickets"

View file

@ -1,4 +1,4 @@
import { randomBytes } from "@noble/hashes/utils"
import { randomBytes } from "@noble/hashes/utils.js"
import { ConvexError, v } from "convex/values"
import { mutation, query } from "./_generated/server"

View file

@ -433,7 +433,8 @@ export default defineSchema({
.index("by_ticket", ["ticketId"])
.index("by_machine_status", ["machineId", "status"])
.index("by_tenant_machine", ["tenantId", "machineId"])
.index("by_tenant_status", ["tenantId", "status"]),
.index("by_tenant_status", ["tenantId", "status"])
.index("by_status_lastActivity", ["status", "lastActivityAt"]),
commentTemplates: defineTable({
tenantId: v.string(),

View file

@ -1,7 +1,7 @@
import { v } from "convex/values"
import { mutation, query } from "./_generated/server"
import type { Id, Doc } from "./_generated/dataModel"
import { sha256 } from "@noble/hashes/sha256"
import { sha256 } from "@noble/hashes/sha2.js"
const DEFAULT_TENANT_ID = "default"