sistema-de-chamados/convex/deviceFieldDefaults.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

131 lines
3.4 KiB
TypeScript

"use server";
import type { MutationCtx } from "./_generated/server";
import type { Doc } from "./_generated/dataModel";
const DEFAULT_MOBILE_DEVICE_FIELDS: Array<{
key: string;
label: string;
type: "text" | "select";
description?: string;
options?: Array<{ value: string; label: string }>;
}> = [
{
key: "mobile_identificacao",
label: "Identificação interna",
type: "text",
description: "Como o time reconhece este dispositivo (ex.: iPhone da Ana).",
},
{
key: "mobile_ram",
label: "Memória RAM",
type: "text",
},
{
key: "mobile_storage",
label: "Armazenamento (HD/SSD)",
type: "text",
},
{
key: "mobile_cpu",
label: "Processador",
type: "text",
},
{
key: "mobile_hostname",
label: "Hostname",
type: "text",
},
{
key: "mobile_patrimonio",
label: "Patrimônio",
type: "text",
},
{
key: "mobile_observacoes",
label: "Observações",
type: "text",
},
{
key: "mobile_situacao",
label: "Situação do equipamento",
type: "select",
options: [
{ value: "em_uso", label: "Em uso" },
{ value: "reserva", label: "Reserva" },
{ value: "manutencao", label: "Em manutenção" },
{ value: "inativo", label: "Inativo" },
],
},
{
key: "mobile_cargo",
label: "Cargo",
type: "text",
},
{
key: "mobile_setor",
label: "Setor",
type: "text",
},
];
export async function ensureMobileDeviceFields(ctx: MutationCtx, tenantId: string) {
const existingMobileFields = await ctx.db
.query("deviceFields")
.withIndex("by_tenant_scope", (q) => q.eq("tenantId", tenantId).eq("scope", "mobile"))
.take(100);
const allFields = await ctx.db
.query("deviceFields")
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
.take(100);
const existingByKey = new Map<string, Doc<"deviceFields">>();
existingMobileFields.forEach((field) => existingByKey.set(field.key, field));
let order = allFields.reduce((max, field) => Math.max(max, field.order ?? 0), 0);
const now = Date.now();
for (const definition of DEFAULT_MOBILE_DEVICE_FIELDS) {
const current = existingByKey.get(definition.key);
if (current) {
const updates: Partial<Doc<"deviceFields">> = {};
if ((current.label ?? "").trim() !== definition.label) {
updates.label = definition.label;
}
if ((current.description ?? "") !== (definition.description ?? "")) {
updates.description = definition.description ?? undefined;
}
const existingOptions = JSON.stringify(current.options ?? null);
const desiredOptions = JSON.stringify(definition.options ?? null);
if (existingOptions !== desiredOptions) {
updates.options = definition.options ?? undefined;
}
if (current.type !== definition.type) {
updates.type = definition.type;
}
if (Object.keys(updates).length) {
await ctx.db.patch(current._id, {
...updates,
updatedAt: now,
});
}
continue;
}
order += 1;
await ctx.db.insert("deviceFields", {
tenantId,
key: definition.key,
label: definition.label,
description: definition.description ?? undefined,
type: definition.type,
required: false,
options: definition.options ?? undefined,
scope: "mobile",
companyId: undefined,
order,
createdAt: now,
updatedAt: now,
});
}
}