sistema-de-chamados/convex/invites.ts
esdrasrenan 638faeb287 fix(convex): corrigir memory leak com .collect() sem limite e adicionar otimizacoes
Problema: Convex backend consumindo 16GB+ de RAM causando OOM kills

Correcoes aplicadas:
- Substituido todos os .collect() por .take(LIMIT) em 27+ arquivos
- Adicionado indice by_usbPolicyStatus para otimizar query de maquinas
- Corrigido N+1 problem em alerts.ts usando Map lookup
- Corrigido full table scan em usbPolicy.ts
- Corrigido subscription leaks no frontend (tickets-view, use-ticket-categories)
- Atualizado versao do Convex backend para precompiled-2025-12-04-cc6af4c

Arquivos principais modificados:
- convex/*.ts - limites em todas as queries .collect()
- convex/schema.ts - novo indice by_usbPolicyStatus
- convex/alerts.ts - N+1 fix com Map
- convex/usbPolicy.ts - uso do novo indice
- src/components/tickets/tickets-view.tsx - skip condicional
- src/hooks/use-ticket-categories.ts - skip condicional

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 21:41:30 -03:00

115 lines
3.4 KiB
TypeScript

import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { requireAdmin } from "./rbac";
export const list = query({
args: { tenantId: v.string(), viewerId: v.id("users") },
handler: async (ctx, { tenantId, viewerId }) => {
await requireAdmin(ctx, viewerId, tenantId);
const invites = await ctx.db
.query("userInvites")
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
.take(100);
return invites
.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))
.map((invite) => ({
id: invite._id,
inviteId: invite.inviteId,
email: invite.email,
name: invite.name ?? null,
role: invite.role,
status: invite.status,
token: invite.token,
expiresAt: invite.expiresAt,
createdAt: invite.createdAt,
createdById: invite.createdById ?? null,
acceptedAt: invite.acceptedAt ?? null,
acceptedById: invite.acceptedById ?? null,
revokedAt: invite.revokedAt ?? null,
revokedById: invite.revokedById ?? null,
revokedReason: invite.revokedReason ?? null,
}));
},
});
export const sync = mutation({
args: {
tenantId: v.string(),
inviteId: v.string(),
email: v.string(),
name: v.optional(v.string()),
role: v.string(),
status: v.string(),
token: v.string(),
expiresAt: v.number(),
createdAt: v.number(),
createdById: v.optional(v.string()),
acceptedAt: v.optional(v.number()),
acceptedById: v.optional(v.string()),
revokedAt: v.optional(v.number()),
revokedById: v.optional(v.string()),
revokedReason: v.optional(v.string()),
},
handler: async (ctx, args) => {
const existing = await ctx.db
.query("userInvites")
.withIndex("by_invite", (q) => q.eq("tenantId", args.tenantId).eq("inviteId", args.inviteId))
.first();
if (!existing) {
const id = await ctx.db.insert("userInvites", {
tenantId: args.tenantId,
inviteId: args.inviteId,
email: args.email,
name: args.name,
role: args.role,
status: args.status,
token: args.token,
expiresAt: args.expiresAt,
createdAt: args.createdAt,
createdById: args.createdById,
acceptedAt: args.acceptedAt,
acceptedById: args.acceptedById,
revokedAt: args.revokedAt,
revokedById: args.revokedById,
revokedReason: args.revokedReason,
});
return await ctx.db.get(id);
}
await ctx.db.patch(existing._id, {
email: args.email,
name: args.name,
role: args.role,
status: args.status,
token: args.token,
expiresAt: args.expiresAt,
createdAt: args.createdAt,
createdById: args.createdById,
acceptedAt: args.acceptedAt,
acceptedById: args.acceptedById,
revokedAt: args.revokedAt,
revokedById: args.revokedById,
revokedReason: args.revokedReason,
});
return await ctx.db.get(existing._id);
},
});
export const remove = mutation({
args: { tenantId: v.string(), inviteId: v.string() },
handler: async (ctx, { tenantId, inviteId }) => {
const existing = await ctx.db
.query("userInvites")
.withIndex("by_invite", (q) => q.eq("tenantId", tenantId).eq("inviteId", inviteId))
.first();
if (existing) {
await ctx.db.delete(existing._id);
}
},
});