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>
This commit is contained in:
parent
a4b46b08ba
commit
638faeb287
33 changed files with 139 additions and 128 deletions
2
convex/_generated/api.d.ts
vendored
2
convex/_generated/api.d.ts
vendored
|
|
@ -25,6 +25,7 @@ import type * as fields from "../fields.js";
|
|||
import type * as files from "../files.js";
|
||||
import type * as incidents from "../incidents.js";
|
||||
import type * as invites from "../invites.js";
|
||||
import type * as liveChat from "../liveChat.js";
|
||||
import type * as machines from "../machines.js";
|
||||
import type * as metrics from "../metrics.js";
|
||||
import type * as migrations from "../migrations.js";
|
||||
|
|
@ -74,6 +75,7 @@ declare const fullApi: ApiFromModules<{
|
|||
files: typeof files;
|
||||
incidents: typeof incidents;
|
||||
invites: typeof invites;
|
||||
liveChat: typeof liveChat;
|
||||
machines: typeof machines;
|
||||
metrics: typeof metrics;
|
||||
migrations: typeof migrations;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const list = query({
|
|||
let items = await ctx.db
|
||||
.query("alerts")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
if (companyId) items = items.filter((a) => a.companyId === companyId)
|
||||
if (typeof start === "number") items = items.filter((a) => a.createdAt >= start)
|
||||
if (typeof end === "number") items = items.filter((a) => a.createdAt < end)
|
||||
|
|
@ -62,7 +62,7 @@ export const managersForCompany = query({
|
|||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", companyId))
|
||||
.collect()
|
||||
.take(100)
|
||||
return users.filter((u) => (u.role ?? "").toUpperCase() === "MANAGER")
|
||||
},
|
||||
})
|
||||
|
|
@ -78,7 +78,7 @@ export const lastForCompanyBySlug = query({
|
|||
const items = await ctx.db
|
||||
.query("alerts")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
const matches = items.filter((a) => a.companyId === company._id)
|
||||
if (matches.length === 0) return null
|
||||
const last = matches.sort((a, b) => b.createdAt - a.createdAt)[0]
|
||||
|
|
@ -94,12 +94,15 @@ export const lastForCompaniesBySlugs = query({
|
|||
const alerts = await ctx.db
|
||||
.query("alerts")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
for (const slug of slugs) {
|
||||
const company = await ctx.db
|
||||
.take(100)
|
||||
// Buscar todas as companies do tenant de uma vez
|
||||
const allCompanies = await ctx.db
|
||||
.query("companies")
|
||||
.withIndex("by_tenant_slug", (q) => q.eq("tenantId", tenantId).eq("slug", slug))
|
||||
.first()
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.take(1000)
|
||||
const companiesBySlug = new Map(allCompanies.map(c => [c.slug, c]))
|
||||
for (const slug of slugs) {
|
||||
const company = companiesBySlug.get(slug)
|
||||
if (!company) {
|
||||
result[slug] = null
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const ensureDefaults = mutation({
|
|||
let existing = await ctx.db
|
||||
.query("queues")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(10);
|
||||
existing = await Promise.all(
|
||||
existing.map(async (queue) => {
|
||||
if (queue.name === "Suporte N1" || queue.slug === "suporte-n1") {
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ export const list = query({
|
|||
const categories = await ctx.db
|
||||
.query("ticketCategories")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
if (categories.length === 0) {
|
||||
return []
|
||||
|
|
@ -217,7 +217,7 @@ export const list = query({
|
|||
const subcategories = await ctx.db
|
||||
.query("ticketSubcategories")
|
||||
.withIndex("by_tenant_slug", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return categories.map((category) => ({
|
||||
id: category._id,
|
||||
|
|
@ -249,7 +249,7 @@ export const ensureDefaults = mutation({
|
|||
const existingCount = await ctx.db
|
||||
.query("ticketCategories")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
if (existingCount.length > 0) {
|
||||
return { created: 0 }
|
||||
|
|
@ -408,7 +408,7 @@ export const deleteCategory = mutation({
|
|||
const subs = await ctx.db
|
||||
.query("ticketSubcategories")
|
||||
.withIndex("by_category_order", (q) => q.eq("categoryId", categoryId))
|
||||
.collect()
|
||||
.take(100)
|
||||
for (const sub of subs) {
|
||||
await ctx.db.patch(sub._id, {
|
||||
categoryId: transferTo,
|
||||
|
|
@ -418,7 +418,7 @@ export const deleteCategory = mutation({
|
|||
const ticketsToMove = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_category", (q) => q.eq("tenantId", tenantId).eq("categoryId", categoryId))
|
||||
.collect()
|
||||
.take(100)
|
||||
for (const ticket of ticketsToMove) {
|
||||
await ctx.db.patch(ticket._id, {
|
||||
categoryId: transferTo,
|
||||
|
|
@ -437,7 +437,7 @@ export const deleteCategory = mutation({
|
|||
const subs = await ctx.db
|
||||
.query("ticketSubcategories")
|
||||
.withIndex("by_category_order", (q) => q.eq("categoryId", categoryId))
|
||||
.collect()
|
||||
.take(100)
|
||||
for (const sub of subs) {
|
||||
await ctx.db.delete(sub._id)
|
||||
}
|
||||
|
|
@ -530,7 +530,7 @@ export const deleteSubcategory = mutation({
|
|||
const tickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_subcategory", (q) => q.eq("tenantId", tenantId).eq("subcategoryId", subcategoryId))
|
||||
.collect()
|
||||
.take(100)
|
||||
for (const ticket of tickets) {
|
||||
await ctx.db.patch(ticket._id, {
|
||||
subcategoryId: transferTo,
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export const get = query({
|
|||
const records = await ctx.db
|
||||
.query("categorySlaSettings")
|
||||
.withIndex("by_tenant_category", (q) => q.eq("tenantId", tenantId).eq("categoryId", categoryId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return {
|
||||
categoryId,
|
||||
|
|
@ -119,7 +119,7 @@ export const save = mutation({
|
|||
const existing = await ctx.db
|
||||
.query("categorySlaSettings")
|
||||
.withIndex("by_tenant_category", (q) => q.eq("tenantId", tenantId).eq("categoryId", categoryId))
|
||||
.collect()
|
||||
.take(100)
|
||||
await Promise.all(existing.map((record) => ctx.db.delete(record._id)))
|
||||
|
||||
const now = Date.now()
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export const list = query({
|
|||
const templates = await ctx.db
|
||||
.query("commentTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return templates
|
||||
.filter((template) => (template.kind ?? "comment") === normalizedKind)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export const list = query({
|
|||
const companies = await ctx.db
|
||||
.query("companies")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(200)
|
||||
return companies.map((c) => ({ id: c._id, name: c.name, slug: c.slug }))
|
||||
},
|
||||
})
|
||||
|
|
@ -131,7 +131,7 @@ export const removeBySlug = mutation({
|
|||
const relatedTickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", existing._id))
|
||||
.collect()
|
||||
.take(200)
|
||||
if (relatedTickets.length > 0) {
|
||||
const companySnapshot = {
|
||||
name: existing.name,
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ export const list = query({
|
|||
const dashboards = await ctx.db
|
||||
.query("dashboards")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
const filtered = (includeArchived ? dashboards : dashboards.filter((d) => !(d.isArchived ?? false))).sort(
|
||||
(a, b) => b.updatedAt - a.updatedAt,
|
||||
|
|
@ -230,7 +230,7 @@ export const list = query({
|
|||
const widgets = await ctx.db
|
||||
.query("dashboardWidgets")
|
||||
.withIndex("by_dashboard", (q) => q.eq("dashboardId", dashboard._id))
|
||||
.collect()
|
||||
.take(100)
|
||||
return {
|
||||
...sanitizeDashboard(dashboard),
|
||||
widgetsCount: widgets.length,
|
||||
|
|
@ -256,14 +256,14 @@ export const get = query({
|
|||
const widgets = await ctx.db
|
||||
.query("dashboardWidgets")
|
||||
.withIndex("by_dashboard_order", (q) => q.eq("dashboardId", dashboardId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
widgets.sort((a, b) => a.order - b.order || a.createdAt - b.createdAt)
|
||||
|
||||
const shares = await ctx.db
|
||||
.query("dashboardShares")
|
||||
.withIndex("by_dashboard", (q) => q.eq("dashboardId", dashboardId))
|
||||
.collect()
|
||||
.take(50)
|
||||
|
||||
return {
|
||||
dashboard: sanitizeDashboard(dashboard),
|
||||
|
|
@ -457,7 +457,7 @@ export const updateLayout = mutation({
|
|||
const widgets = await ctx.db
|
||||
.query("dashboardWidgets")
|
||||
.withIndex("by_dashboard", (q) => q.eq("dashboardId", dashboardId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
const byKey = new Map<string, Doc<"dashboardWidgets">>()
|
||||
widgets.forEach((widget) => byKey.set(widget.widgetKey, widget))
|
||||
|
|
@ -518,7 +518,7 @@ export const addWidget = mutation({
|
|||
const existingWidgets = await ctx.db
|
||||
.query("dashboardWidgets")
|
||||
.withIndex("by_dashboard", (q) => q.eq("dashboardId", dashboardId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
const widgetId = await ctx.db.insert("dashboardWidgets", {
|
||||
tenantId,
|
||||
|
|
@ -617,7 +617,7 @@ export const ensureQueueSummaryWidget = mutation({
|
|||
const widgets = await ctx.db
|
||||
.query("dashboardWidgets")
|
||||
.withIndex("by_dashboard_order", (q) => q.eq("dashboardId", dashboardId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
widgets.sort((a, b) => a.order - b.order || a.createdAt - b.createdAt)
|
||||
|
||||
|
|
@ -871,7 +871,7 @@ export const upsertShare = mutation({
|
|||
const existingShares = await ctx.db
|
||||
.query("dashboardShares")
|
||||
.withIndex("by_dashboard", (q) => q.eq("dashboardId", dashboardId))
|
||||
.collect()
|
||||
.take(50)
|
||||
|
||||
const now = Date.now()
|
||||
let shareDoc = existingShares.find((share) => share.audience === audience)
|
||||
|
|
@ -917,7 +917,7 @@ export const revokeShareToken = mutation({
|
|||
const shares = await ctx.db
|
||||
.query("dashboardShares")
|
||||
.withIndex("by_dashboard", (q) => q.eq("dashboardId", dashboardId))
|
||||
.collect()
|
||||
.take(50)
|
||||
|
||||
for (const share of shares) {
|
||||
if (share.audience === "public-link") {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ async function unsetDefaults(
|
|||
const templates = await ctx.db
|
||||
.query("deviceExportTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
await Promise.all(
|
||||
templates
|
||||
|
|
@ -73,7 +73,7 @@ export const list = query({
|
|||
const templates = await ctx.db
|
||||
.query("deviceExportTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return templates
|
||||
.filter((tpl) => {
|
||||
|
|
@ -112,7 +112,7 @@ export const listForTenant = query({
|
|||
const templates = await ctx.db
|
||||
.query("deviceExportTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return templates
|
||||
.filter((tpl) => tpl.isActive !== false)
|
||||
|
|
@ -149,7 +149,7 @@ export const getDefault = query({
|
|||
.withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", companyId))
|
||||
: ctx.db.query("deviceExportTemplates").withIndex("by_tenant_default", (q) => q.eq("tenantId", tenantId).eq("isDefault", true))
|
||||
|
||||
const templates = await indexQuery.collect()
|
||||
const templates = await indexQuery.take(100)
|
||||
const candidate = templates.find((tpl) => tpl.isDefault) ?? null
|
||||
if (candidate) {
|
||||
return {
|
||||
|
|
@ -357,7 +357,7 @@ export const clearCompanyDefault = mutation({
|
|||
const templates = await ctx.db
|
||||
.query("deviceExportTemplates")
|
||||
.withIndex("by_tenant_company", (q) => q.eq("tenantId", tenantId).eq("companyId", companyId))
|
||||
.collect()
|
||||
.take(100)
|
||||
const now = Date.now()
|
||||
await Promise.all(
|
||||
templates.map((tpl) =>
|
||||
|
|
|
|||
|
|
@ -73,11 +73,11 @@ export async function ensureMobileDeviceFields(ctx: MutationCtx, tenantId: strin
|
|||
const existingMobileFields = await ctx.db
|
||||
.query("deviceFields")
|
||||
.withIndex("by_tenant_scope", (q) => q.eq("tenantId", tenantId).eq("scope", "mobile"))
|
||||
.collect();
|
||||
.take(100);
|
||||
const allFields = await ctx.db
|
||||
.query("deviceFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
const existingByKey = new Map<string, Doc<"deviceFields">>();
|
||||
existingMobileFields.forEach((field) => existingByKey.set(field.key, field));
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export const list = query({
|
|||
.query("deviceFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
|
||||
const fields = await fieldsQuery.collect()
|
||||
const fields = await fieldsQuery.take(100)
|
||||
return fields
|
||||
.filter((field) => matchesCompany(field.companyId, companyId, true))
|
||||
.filter((field) => matchesScope(field.scope, scope))
|
||||
|
|
@ -96,7 +96,7 @@ export const listForTenant = query({
|
|||
const fields = await ctx.db
|
||||
.query("deviceFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return fields
|
||||
.filter((field) => matchesCompany(field.companyId, companyId, false))
|
||||
|
|
@ -153,7 +153,7 @@ export const create = mutation({
|
|||
const existing = await ctx.db
|
||||
.query("deviceFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", args.tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
const maxOrder = existing.reduce((acc, item) => Math.max(acc, item.order ?? 0), 0)
|
||||
const now = Date.now()
|
||||
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ export const getStats = query({
|
|||
const all = await ctx.db
|
||||
.query("emprestimos")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", args.tenantId))
|
||||
.collect()
|
||||
.take(200)
|
||||
|
||||
const now = Date.now()
|
||||
const ativos = all.filter((e) => e.status === "ATIVO")
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export const list = query({
|
|||
const fields = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
return fields
|
||||
.filter((field) => {
|
||||
|
|
@ -87,7 +87,7 @@ export const listForTenant = query({
|
|||
const fields = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
return fields
|
||||
.filter((field) => {
|
||||
|
|
@ -157,7 +157,7 @@ export const create = mutation({
|
|||
const existing = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
const maxOrder = existing.reduce((acc: number, item: Doc<"ticketFields">) => Math.max(acc, item.order ?? 0), 0);
|
||||
|
||||
const now = Date.now();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const list = query({
|
|||
.query("incidents")
|
||||
.withIndex("by_tenant_updated", (q) => q.eq("tenantId", tenantId))
|
||||
.order("desc")
|
||||
.collect()
|
||||
.take(100)
|
||||
return incidents
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const list = query({
|
|||
const invites = await ctx.db
|
||||
.query("userInvites")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
return invites
|
||||
.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ async function findActiveMachineToken(ctx: QueryCtx, machineId: Id<"machines">,
|
|||
.withIndex("by_machine_revoked_expires", (q) =>
|
||||
q.eq("machineId", machineId).eq("revoked", false).gt("expiresAt", now),
|
||||
)
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return tokens.length > 0 ? tokens[0]! : null
|
||||
}
|
||||
|
|
@ -550,7 +550,7 @@ export const register = mutation({
|
|||
const candidates = await ctx.db
|
||||
.query("machines")
|
||||
.withIndex("by_tenant_hostname", (q) => q.eq("tenantId", tenantId).eq("hostname", args.hostname))
|
||||
.collect()
|
||||
.take(200)
|
||||
// Procura uma maquina com hostname igual E hardware compativel (MAC ou serial)
|
||||
for (const candidate of candidates) {
|
||||
if (matchesExistingHardware(candidate, identifiers, args.hostname)) {
|
||||
|
|
@ -643,7 +643,7 @@ export const register = mutation({
|
|||
const previousTokens = await ctx.db
|
||||
.query("machineTokens")
|
||||
.withIndex("by_machine", (q) => q.eq("machineId", machineId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
for (const token of previousTokens) {
|
||||
if (!token.revoked) {
|
||||
|
|
@ -932,7 +932,7 @@ export const listByTenant = query({
|
|||
const tenantCompanies = await ctx.db
|
||||
.query("companies")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(200)
|
||||
|
||||
const companyById = new Map<string, typeof tenantCompanies[number]>()
|
||||
const companyBySlug = new Map<string, typeof tenantCompanies[number]>()
|
||||
|
|
@ -1574,7 +1574,7 @@ export const listMachineRequesters = query({
|
|||
const tickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_machine", (q) => q.eq("tenantId", machine.tenantId).eq("machineId", args.machineId))
|
||||
.collect()
|
||||
.take(200)
|
||||
|
||||
const requestersMap = new Map<string, { email: string; name: string | null }>()
|
||||
for (const ticket of tickets) {
|
||||
|
|
@ -2131,7 +2131,7 @@ export const resetAgent = mutation({
|
|||
const tokens = await ctx.db
|
||||
.query("machineTokens")
|
||||
.withIndex("by_machine", (q) => q.eq("machineId", machineId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
const now = Date.now()
|
||||
let revokedCount = 0
|
||||
|
|
@ -2640,7 +2640,7 @@ export const remove = mutation({
|
|||
const tokens = await ctx.db
|
||||
.query("machineTokens")
|
||||
.withIndex("by_machine", (q) => q.eq("machineId", machineId))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
await Promise.all(tokens.map((token) => ctx.db.delete(token._id)))
|
||||
await ctx.db.delete(machineId)
|
||||
|
|
|
|||
|
|
@ -448,7 +448,7 @@ const metricResolvers: Record<string, MetricResolver> = {
|
|||
queueCounts.set(queueKey, (queueCounts.get(queueKey) ?? 0) + 1)
|
||||
}
|
||||
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).collect()
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50)
|
||||
const data = Array.from(queueCounts.entries()).map(([queueId, total]) => {
|
||||
const queue = queues.find((q) => String(q._id) === queueId)
|
||||
return {
|
||||
|
|
@ -470,7 +470,7 @@ const metricResolvers: Record<string, MetricResolver> = {
|
|||
const filterHas = queueFilter && queueFilter.length > 0
|
||||
const normalizeKey = (id: Id<"queues"> | null) => (id ? String(id) : "sem-fila")
|
||||
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).collect()
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50)
|
||||
const queueNameMap = new Map<string, string>()
|
||||
queues.forEach((queue) => {
|
||||
const key = String(queue._id)
|
||||
|
|
@ -593,7 +593,7 @@ const metricResolvers: Record<string, MetricResolver> = {
|
|||
stats.set(queueKey, current)
|
||||
}
|
||||
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).collect()
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50)
|
||||
const data = Array.from(stats.entries()).map(([queueId, value]) => {
|
||||
const queue = queues.find((q) => String(q._id) === queueId)
|
||||
const compliance = value.total > 0 ? value.compliant / value.total : 0
|
||||
|
|
|
|||
|
|
@ -307,21 +307,21 @@ async function getTenantUsers(ctx: QueryCtx, tenantId: string) {
|
|||
return ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(2000)
|
||||
}
|
||||
|
||||
async function getTenantQueues(ctx: QueryCtx, tenantId: string) {
|
||||
return ctx.db
|
||||
.query("queues")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(500)
|
||||
}
|
||||
|
||||
async function getTenantCompanies(ctx: QueryCtx, tenantId: string) {
|
||||
return ctx.db
|
||||
.query("companies")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(1000)
|
||||
}
|
||||
|
||||
export const exportTenantSnapshot = query({
|
||||
|
|
@ -347,7 +347,7 @@ export const exportTenantSnapshot = query({
|
|||
const tickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(5000)
|
||||
|
||||
const ticketsWithRelations = []
|
||||
|
||||
|
|
@ -355,12 +355,12 @@ export const exportTenantSnapshot = query({
|
|||
const comments = await ctx.db
|
||||
.query("ticketComments")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", ticket._id))
|
||||
.collect()
|
||||
.take(500)
|
||||
|
||||
const events = await ctx.db
|
||||
.query("ticketEvents")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", ticket._id))
|
||||
.collect()
|
||||
.take(500)
|
||||
|
||||
const requester = userMap.get(ticket.requesterId)
|
||||
const assignee = ticket.assigneeId ? userMap.get(ticket.assigneeId) : undefined
|
||||
|
|
@ -575,7 +575,7 @@ export const importPrismaSnapshot = mutation({
|
|||
const existingTenantUsers = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", snapshot.tenantId))
|
||||
.collect()
|
||||
.take(2000)
|
||||
|
||||
for (const user of existingTenantUsers) {
|
||||
const role = normalizeRole(user.role ?? null)
|
||||
|
|
@ -672,7 +672,7 @@ export const importPrismaSnapshot = mutation({
|
|||
const existingComments = await ctx.db
|
||||
.query("ticketComments")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", ticketId))
|
||||
.collect()
|
||||
.take(500)
|
||||
for (const comment of existingComments) {
|
||||
await ctx.db.delete(comment._id)
|
||||
}
|
||||
|
|
@ -680,7 +680,7 @@ export const importPrismaSnapshot = mutation({
|
|||
const existingEvents = await ctx.db
|
||||
.query("ticketEvents")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", ticketId))
|
||||
.collect()
|
||||
.take(500)
|
||||
for (const event of existingEvents) {
|
||||
await ctx.db.delete(event._id)
|
||||
}
|
||||
|
|
@ -765,7 +765,7 @@ export const backfillTicketCommentAuthorSnapshots = mutation({
|
|||
const events = await ctx.db
|
||||
.query("ticketEvents")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", comment.ticketId))
|
||||
.collect()
|
||||
.take(100)
|
||||
const matchingEvent = events.find(
|
||||
(event) => event.type === "COMMENT_ADDED" && event.createdAt === comment.createdAt,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -81,12 +81,12 @@ export const list = query({
|
|||
const queues = await ctx.db
|
||||
.query("queues")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
const teams = await ctx.db
|
||||
.query("teams")
|
||||
.withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
return queues.map((queue) => {
|
||||
const team = queue.teamId ? teams.find((item) => item._id === queue.teamId) : null;
|
||||
|
|
@ -109,13 +109,13 @@ export const summary = query({
|
|||
args: { tenantId: v.string(), viewerId: v.id("users") },
|
||||
handler: async (ctx, { tenantId, viewerId }) => {
|
||||
await requireStaff(ctx, viewerId, tenantId);
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).collect();
|
||||
const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50);
|
||||
const result = await Promise.all(
|
||||
queues.map(async (qItem) => {
|
||||
const tickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_queue", (q) => q.eq("tenantId", tenantId).eq("queueId", qItem._id))
|
||||
.collect();
|
||||
.take(50);
|
||||
let pending = 0;
|
||||
let inProgress = 0;
|
||||
let paused = 0;
|
||||
|
|
|
|||
|
|
@ -509,9 +509,9 @@ async function forEachScopedTicketByResolvedRangeChunked(
|
|||
.order("desc");
|
||||
|
||||
// Coleta tickets do chunk (o chunk ja e limitado por periodo)
|
||||
const snapshot = await query.collect();
|
||||
const snapshot = await query.take(1000);
|
||||
// Limita processamento a 1000 tickets por chunk para evitar timeout
|
||||
const limitedSnapshot = snapshot.slice(0, 1000);
|
||||
const limitedSnapshot = snapshot;
|
||||
for (const ticket of limitedSnapshot) {
|
||||
const resolvedAt = typeof ticket.resolvedAt === "number" ? ticket.resolvedAt : null;
|
||||
if (resolvedAt === null) continue;
|
||||
|
|
@ -535,11 +535,10 @@ export async function fetchOpenScopedTickets(
|
|||
// Limita a 500 tickets por status para evitar OOM
|
||||
const MAX_PER_STATUS = 500;
|
||||
for (const status of statuses) {
|
||||
const allTickets = await ctx.db
|
||||
const snapshot = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_status", (q) => q.eq("tenantId", tenantId).eq("status", status))
|
||||
.collect();
|
||||
const snapshot = allTickets.slice(0, MAX_PER_STATUS);
|
||||
.take(500);
|
||||
for (const ticket of snapshot) {
|
||||
if (!OPEN_STATUSES.has(normalizeStatus(ticket.status))) continue;
|
||||
if (scopedCompanyId && ticket.companyId !== scopedCompanyId) continue;
|
||||
|
|
@ -620,7 +619,7 @@ async function fetchCategoryMap(ctx: QueryCtx, tenantId: string) {
|
|||
const categories = await ctx.db
|
||||
.query("ticketCategories")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(500);
|
||||
const map = new Map<string, Doc<"ticketCategories">>();
|
||||
for (const category of categories) {
|
||||
map.set(String(category._id), category);
|
||||
|
|
@ -702,7 +701,7 @@ async function fetchQueues(ctx: QueryCtx, tenantId: string) {
|
|||
return ctx.db
|
||||
.query("queues")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(1000);
|
||||
}
|
||||
|
||||
type CompanySummary = {
|
||||
|
|
@ -1023,7 +1022,7 @@ export async function csatOverviewHandler(
|
|||
const events = await ctx.db
|
||||
.query("ticketEvents")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", ticket._id))
|
||||
.collect();
|
||||
.take(1000);
|
||||
|
||||
for (const event of events) {
|
||||
if (event.type !== "CSAT_RECEIVED" && event.type !== "CSAT_RATED") continue;
|
||||
|
|
@ -1420,11 +1419,10 @@ export async function agentProductivityHandler(
|
|||
|
||||
for (const [agentId, acc] of map) {
|
||||
// Limita a 1000 sessoes por agente para evitar OOM
|
||||
const allSessions = await ctx.db
|
||||
const sessions = await ctx.db
|
||||
.query("ticketWorkSessions")
|
||||
.withIndex("by_agent", (q) => q.eq("agentId", agentId as Id<"users">))
|
||||
.collect()
|
||||
const sessions = allSessions.slice(0, 1000)
|
||||
.take(1000)
|
||||
let total = 0
|
||||
for (const s of sessions) {
|
||||
const started = s.startedAt
|
||||
|
|
@ -1481,7 +1479,7 @@ export async function ticketCategoryInsightsHandler(
|
|||
const categories = await ctx.db
|
||||
.query("ticketCategories")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(500)
|
||||
|
||||
const categoriesById = new Map<Id<"ticketCategories">, Doc<"ticketCategories">>()
|
||||
for (const category of categories) {
|
||||
|
|
|
|||
|
|
@ -674,7 +674,8 @@ export default defineSchema({
|
|||
.index("by_tenant_fingerprint", ["tenantId", "fingerprint"])
|
||||
.index("by_tenant_assigned_email", ["tenantId", "assignedUserEmail"])
|
||||
.index("by_tenant_hostname", ["tenantId", "hostname"])
|
||||
.index("by_auth_email", ["authEmail"]),
|
||||
.index("by_auth_email", ["authEmail"])
|
||||
.index("by_usbPolicyStatus", ["usbPolicyStatus"]),
|
||||
|
||||
usbPolicyEvents: defineTable({
|
||||
tenantId: v.string(),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const seedDemo = mutation({
|
|||
const existingQueues = await ctx.db
|
||||
.query("queues")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
const normalizedQueues = await Promise.all(
|
||||
existingQueues.map(async (queue) => {
|
||||
|
|
@ -135,7 +135,7 @@ export const seedDemo = mutation({
|
|||
const existingTemplates = await ctx.db
|
||||
.query("commentTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
for (const definition of templateDefinitions) {
|
||||
const already = existingTemplates.find((template) => template?.title === definition.title);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const list = query({
|
|||
const items = await ctx.db
|
||||
.query("slaPolicies")
|
||||
.withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
return items.map((policy) => ({
|
||||
id: policy._id,
|
||||
|
|
|
|||
|
|
@ -28,17 +28,17 @@ export const list = query({
|
|||
const teams = await ctx.db
|
||||
.query("teams")
|
||||
.withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
const queues = await ctx.db
|
||||
.query("queues")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
return teams.map((team) => {
|
||||
const members = users
|
||||
|
|
@ -111,7 +111,7 @@ export const update = mutation({
|
|||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
const now = users
|
||||
.filter((user) => (user.teams ?? []).includes(team.name))
|
||||
|
|
@ -150,7 +150,7 @@ export const remove = mutation({
|
|||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
await Promise.all(
|
||||
users
|
||||
|
|
@ -182,7 +182,7 @@ export const setMembers = mutation({
|
|||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
const tenantUserIds = new Set(users.map((user) => user._id));
|
||||
for (const memberId of memberIds) {
|
||||
if (!tenantUserIds.has(memberId)) {
|
||||
|
|
@ -218,7 +218,7 @@ export const directory = query({
|
|||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
return users.map((user) => ({
|
||||
id: user._id,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const list = query({
|
|||
const settings = await ctx.db
|
||||
.query("ticketFormSettings")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
return settings
|
||||
.filter((setting) => !normalizedTemplate || setting.template === normalizedTemplate)
|
||||
.map((setting) => ({
|
||||
|
|
@ -143,7 +143,7 @@ async function findExisting(
|
|||
const candidates = await ctx.db
|
||||
.query("ticketFormSettings")
|
||||
.withIndex("by_tenant_template_scope", (q) => q.eq("tenantId", tenantId).eq("template", template).eq("scope", scope))
|
||||
.collect()
|
||||
.take(100)
|
||||
|
||||
return candidates.find((setting) => {
|
||||
if (scope === "tenant") return true
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export async function ensureTicketFormTemplatesForTenant(ctx: MutationCtx, tenan
|
|||
const existing = await ctx.db
|
||||
.query("ticketFormTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
let order = existing.reduce((max, tpl) => Math.max(max, tpl.order ?? 0), 0);
|
||||
const now = Date.now();
|
||||
for (const template of TICKET_FORM_CONFIG) {
|
||||
|
|
@ -102,12 +102,12 @@ async function cloneFieldsFromTemplate(ctx: MutationCtx, tenantId: string, sourc
|
|||
const sourceFields = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant_scope", (q) => q.eq("tenantId", tenantId).eq("scope", sourceKey))
|
||||
.collect();
|
||||
.take(50);
|
||||
if (sourceFields.length === 0) return;
|
||||
const ordered = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant_order", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
let order = ordered.reduce((max, field) => Math.max(max, field.order ?? 0), 0);
|
||||
const now = Date.now();
|
||||
for (const field of sourceFields.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))) {
|
||||
|
|
@ -156,7 +156,7 @@ export const list = query({
|
|||
const templates = await ctx.db
|
||||
.query("ticketFormTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
return templates
|
||||
.filter((tpl) => includeArchived || tpl.isArchived !== true)
|
||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.label.localeCompare(b.label, "pt-BR"))
|
||||
|
|
@ -174,7 +174,7 @@ export const listActive = query({
|
|||
const templates = await ctx.db
|
||||
.query("ticketFormTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
return templates
|
||||
.filter((tpl) => tpl.isArchived !== true)
|
||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.label.localeCompare(b.label, "pt-BR"))
|
||||
|
|
@ -201,7 +201,7 @@ export const create = mutation({
|
|||
const templates = await ctx.db
|
||||
.query("ticketFormTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(50);
|
||||
const order = (templates.reduce((max, tpl) => Math.max(max, tpl.order ?? 0), 0) ?? 0) + 1;
|
||||
const now = Date.now();
|
||||
const templateId = await ctx.db.insert("ticketFormTemplates", {
|
||||
|
|
|
|||
|
|
@ -635,7 +635,7 @@ async function fetchTemplateSummaries(ctx: AnyCtx, tenantId: string): Promise<Te
|
|||
const templates = await ctx.db
|
||||
.query("ticketFormTemplates")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
if (!templates.length) {
|
||||
return TICKET_FORM_CONFIG.map((template) => ({
|
||||
key: template.key,
|
||||
|
|
@ -682,7 +682,7 @@ async function fetchTicketFieldsByScopes(
|
|||
const allFields = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
const addFieldToScope = (scopeKey: string, field: Doc<"ticketFields">) => {
|
||||
const originalKey = scopeLookup.get(scopeKey);
|
||||
|
|
@ -746,7 +746,7 @@ async function fetchViewerScopedFormSettings(
|
|||
const allSettings = await ctx.db
|
||||
.query("ticketFormSettings")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
for (const setting of allSettings) {
|
||||
if (!keySet.has(setting.template)) {
|
||||
|
|
@ -813,7 +813,7 @@ async function ensureTicketFormDefaultsForTenant(ctx: MutationCtx, tenantId: str
|
|||
const existing = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant_scope", (q) => q.eq("tenantId", tenantId).eq("scope", template.key))
|
||||
.collect();
|
||||
.take(100);
|
||||
if (template.key === "admissao") {
|
||||
for (const key of OPTIONAL_ADMISSION_FIELD_KEYS) {
|
||||
const field = existing.find((f) => f.key === key);
|
||||
|
|
@ -1026,7 +1026,7 @@ async function computeAgentWorkTotals(
|
|||
const sessions = await ctx.db
|
||||
.query("ticketWorkSessions")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", ticketId))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
if (!sessions.length) {
|
||||
return [];
|
||||
|
|
@ -1435,7 +1435,7 @@ async function normalizeCustomFieldValues(
|
|||
const definitions = await ctx.db
|
||||
.query("ticketFields")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(100);
|
||||
|
||||
const scopedDefinitions = definitions.filter((definition) => {
|
||||
const fieldScope = (definition.scope ?? "all").toLowerCase();
|
||||
|
|
@ -1951,7 +1951,7 @@ export const getById = query({
|
|||
const comments = await ctx.db
|
||||
.query("ticketComments")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", id))
|
||||
.collect();
|
||||
.take(50);
|
||||
const canViewInternalComments = role === "ADMIN" || role === "AGENT";
|
||||
const visibleComments = canViewInternalComments
|
||||
? comments
|
||||
|
|
@ -1965,7 +1965,7 @@ export const getById = query({
|
|||
let timelineRecords = await ctx.db
|
||||
.query("ticketEvents")
|
||||
.withIndex("by_ticket", (q) => q.eq("ticketId", id))
|
||||
.collect();
|
||||
.take(50);
|
||||
|
||||
if (!(role === "ADMIN" || role === "AGENT")) {
|
||||
timelineRecords = timelineRecords.filter((event) => {
|
||||
|
|
@ -2317,7 +2317,7 @@ export const create = mutation({
|
|||
const queues = await ctx.db
|
||||
.query("queues")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", args.tenantId))
|
||||
.collect()
|
||||
.take(100)
|
||||
const preferred = queues.find((q) => q.slug === "chamados") || queues.find((q) => q.name === "Chamados") || null
|
||||
if (preferred) {
|
||||
resolvedQueueId = preferred._id as Id<"queues">
|
||||
|
|
@ -3085,7 +3085,7 @@ export const listChatMessages = query({
|
|||
const messages = await ctx.db
|
||||
.query("ticketChatMessages")
|
||||
.withIndex("by_ticket_created", (q) => q.eq("ticketId", ticketId))
|
||||
.collect()
|
||||
.take(50)
|
||||
|
||||
// Verificar maquina e sessao de chat ao vivo
|
||||
let liveChat: {
|
||||
|
|
|
|||
|
|
@ -196,12 +196,13 @@ export const listUsbPolicyEvents = query({
|
|||
},
|
||||
handler: async (ctx, args) => {
|
||||
const limit = args.limit ?? 10
|
||||
const maxFetch = 1000 // Limite maximo de eventos a buscar
|
||||
|
||||
let events = await ctx.db
|
||||
.query("usbPolicyEvents")
|
||||
.withIndex("by_machine_created", (q) => q.eq("machineId", args.machineId))
|
||||
.order("desc")
|
||||
.collect()
|
||||
.take(maxFetch)
|
||||
|
||||
// Aplica filtro de cursor (paginacao)
|
||||
if (args.cursor !== undefined) {
|
||||
|
|
@ -313,13 +314,11 @@ export const cleanupStalePendingPolicies = mutation({
|
|||
const cutoff = now - thresholdMs
|
||||
|
||||
// Buscar maquinas com status PENDING e appliedAt antigo
|
||||
const allMachines = await ctx.db.query("machines").collect()
|
||||
const staleMachines = allMachines.filter(
|
||||
(m) =>
|
||||
m.usbPolicyStatus === "PENDING" &&
|
||||
m.usbPolicyAppliedAt !== undefined &&
|
||||
m.usbPolicyAppliedAt < cutoff
|
||||
)
|
||||
const staleMachines = await ctx.db
|
||||
.query("machines")
|
||||
.withIndex("by_usbPolicyStatus", (q) => q.eq("usbPolicyStatus", "PENDING"))
|
||||
.filter((q) => q.lt(q.field("usbPolicyAppliedAt"), cutoff))
|
||||
.take(1000)
|
||||
|
||||
let cleaned = 0
|
||||
for (const machine of staleMachines) {
|
||||
|
|
@ -346,6 +345,6 @@ export const cleanupStalePendingPolicies = mutation({
|
|||
cleaned++
|
||||
}
|
||||
|
||||
return { cleaned, checked: allMachines.length }
|
||||
return { cleaned, checked: staleMachines.length }
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export const listAgents = query({
|
|||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(5000);
|
||||
|
||||
// Only internal staff (ADMIN/AGENT) should appear as responsáveis
|
||||
return users
|
||||
|
|
@ -128,7 +128,7 @@ export const listCustomers = query({
|
|||
const users = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
.take(5000);
|
||||
|
||||
const allowed = users.filter((user) => {
|
||||
const role = (user.role ?? "COLLABORATOR").toUpperCase()
|
||||
|
|
@ -215,7 +215,7 @@ export const deleteUser = mutation({
|
|||
const comments = await ctx.db
|
||||
.query("ticketComments")
|
||||
.withIndex("by_author", (q) => q.eq("authorId", userId))
|
||||
.collect();
|
||||
.take(10000);
|
||||
if (comments.length > 0) {
|
||||
const authorSnapshot = {
|
||||
name: user.name,
|
||||
|
|
@ -243,7 +243,7 @@ export const deleteUser = mutation({
|
|||
const requesterTickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_requester", (q) => q.eq("tenantId", user.tenantId).eq("requesterId", userId))
|
||||
.collect();
|
||||
.take(10000);
|
||||
if (requesterTickets.length > 0) {
|
||||
const requesterSnapshot = {
|
||||
name: user.name,
|
||||
|
|
@ -267,7 +267,7 @@ export const deleteUser = mutation({
|
|||
const directReports = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant_manager", (q) => q.eq("tenantId", user.tenantId).eq("managerId", userId))
|
||||
.collect();
|
||||
.take(1000);
|
||||
await Promise.all(
|
||||
directReports.map(async (report) => {
|
||||
await ctx.db.patch(report._id, { managerId: undefined });
|
||||
|
|
|
|||
|
|
@ -906,6 +906,8 @@ export type DevicesQueryItem = {
|
|||
linkedUsers?: Array<{ id: string; email: string; name: string }>
|
||||
remoteAccessEntries: DeviceRemoteAccessEntry[]
|
||||
customFields?: Array<{ fieldId?: string; fieldKey: string; label: string; type?: string; value: unknown; displayValue?: string }>
|
||||
usbPolicy?: "ALLOW_ALL" | "BLOCK_ALL" | "WHITELIST" | null
|
||||
usbPolicyStatus?: "IDLE" | "PENDING" | "APPLYING" | "APPLIED" | "FAILED" | null
|
||||
}
|
||||
|
||||
export function normalizeDeviceItem(raw: Record<string, unknown>): DevicesQueryItem {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,10 @@ export function TicketsView({ initialFilters }: TicketsViewProps = {}) {
|
|||
queuesEnabled ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip"
|
||||
)
|
||||
const queues: TicketQueueSummary[] = Array.isArray(queuesResult) ? queuesResult : []
|
||||
const agents = useQuery(api.users.listAgents, { tenantId }) as { _id: string; name: string }[] | undefined
|
||||
const agents = useQuery(
|
||||
api.users.listAgents,
|
||||
isStaff && convexUserId ? { tenantId } : "skip"
|
||||
) as { _id: string; name: string }[] | undefined
|
||||
|
||||
// Argumentos para a query paginada de tickets
|
||||
const ticketsArgs = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import { api } from "@/convex/_generated/api"
|
|||
import type { TicketCategory } from "@/lib/schemas/category"
|
||||
|
||||
export function useTicketCategories(tenantId: string) {
|
||||
const categories = useQuery(api.categories.list, { tenantId }) as TicketCategory[] | undefined
|
||||
const categories = useQuery(
|
||||
api.categories.list,
|
||||
tenantId ? { tenantId } : "skip"
|
||||
) as TicketCategory[] | undefined
|
||||
const ensureDefaults = useMutation(api.categories.ensureDefaults)
|
||||
const initializingRef = useRef(false)
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ services:
|
|||
start_period: 180s
|
||||
|
||||
convex_backend:
|
||||
image: sistema_convex_backend:1.29.2
|
||||
image: ghcr.io/get-convex/convex-backend:precompiled-2025-12-04-cc6af4c
|
||||
stop_grace_period: 10s
|
||||
stop_signal: SIGINT
|
||||
volumes:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue