diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 8dae31a..d6ef58d 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -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; diff --git a/convex/alerts.ts b/convex/alerts.ts index 1930571..ded3f4b 100644 --- a/convex/alerts.ts +++ b/convex/alerts.ts @@ -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() + .take(100) + // Buscar todas as companies do tenant de uma vez + const allCompanies = await ctx.db + .query("companies") + .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 = await ctx.db - .query("companies") - .withIndex("by_tenant_slug", (q) => q.eq("tenantId", tenantId).eq("slug", slug)) - .first() + const company = companiesBySlug.get(slug) if (!company) { result[slug] = null continue diff --git a/convex/bootstrap.ts b/convex/bootstrap.ts index e6031bf..78191f9 100644 --- a/convex/bootstrap.ts +++ b/convex/bootstrap.ts @@ -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") { diff --git a/convex/categories.ts b/convex/categories.ts index 60f16f9..b3dea52 100644 --- a/convex/categories.ts +++ b/convex/categories.ts @@ -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, diff --git a/convex/categorySlas.ts b/convex/categorySlas.ts index 09086a5..e7d9e69 100644 --- a/convex/categorySlas.ts +++ b/convex/categorySlas.ts @@ -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() diff --git a/convex/commentTemplates.ts b/convex/commentTemplates.ts index 713f5a0..5fbcc06 100644 --- a/convex/commentTemplates.ts +++ b/convex/commentTemplates.ts @@ -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) diff --git a/convex/companies.ts b/convex/companies.ts index 16266e4..61d9933 100644 --- a/convex/companies.ts +++ b/convex/companies.ts @@ -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, diff --git a/convex/dashboards.ts b/convex/dashboards.ts index 9569e5e..d36a734 100644 --- a/convex/dashboards.ts +++ b/convex/dashboards.ts @@ -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>() 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") { diff --git a/convex/deviceExportTemplates.ts b/convex/deviceExportTemplates.ts index c3e9327..4eb7fb3 100644 --- a/convex/deviceExportTemplates.ts +++ b/convex/deviceExportTemplates.ts @@ -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) => diff --git a/convex/deviceFieldDefaults.ts b/convex/deviceFieldDefaults.ts index 97c09da..ffaabab 100644 --- a/convex/deviceFieldDefaults.ts +++ b/convex/deviceFieldDefaults.ts @@ -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>(); existingMobileFields.forEach((field) => existingByKey.set(field.key, field)); diff --git a/convex/deviceFields.ts b/convex/deviceFields.ts index 61d591f..1a024f9 100644 --- a/convex/deviceFields.ts +++ b/convex/deviceFields.ts @@ -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() diff --git a/convex/emprestimos.ts b/convex/emprestimos.ts index 3b3985e..7136ba4 100644 --- a/convex/emprestimos.ts +++ b/convex/emprestimos.ts @@ -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") diff --git a/convex/fields.ts b/convex/fields.ts index 38d6863..7156ada 100644 --- a/convex/fields.ts +++ b/convex/fields.ts @@ -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(); diff --git a/convex/incidents.ts b/convex/incidents.ts index a7ffdb0..bc1a9bc 100644 --- a/convex/incidents.ts +++ b/convex/incidents.ts @@ -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 }, }) diff --git a/convex/invites.ts b/convex/invites.ts index 54aebc4..f71cc6f 100644 --- a/convex/invites.ts +++ b/convex/invites.ts @@ -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)) diff --git a/convex/machines.ts b/convex/machines.ts index 42a8b15..0863694 100644 --- a/convex/machines.ts +++ b/convex/machines.ts @@ -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() const companyBySlug = new Map() @@ -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() 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) diff --git a/convex/metrics.ts b/convex/metrics.ts index 490b288..0a5e12a 100644 --- a/convex/metrics.ts +++ b/convex/metrics.ts @@ -448,7 +448,7 @@ const metricResolvers: Record = { 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 = { 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() queues.forEach((queue) => { const key = String(queue._id) @@ -593,7 +593,7 @@ const metricResolvers: Record = { 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 diff --git a/convex/migrations.ts b/convex/migrations.ts index 0e6b94a..c90f580 100644 --- a/convex/migrations.ts +++ b/convex/migrations.ts @@ -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, ) diff --git a/convex/queues.ts b/convex/queues.ts index b18d25b..3b6ce8d 100644 --- a/convex/queues.ts +++ b/convex/queues.ts @@ -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; diff --git a/convex/reports.ts b/convex/reports.ts index 928f998..5c18044 100644 --- a/convex/reports.ts +++ b/convex/reports.ts @@ -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>(); 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, Doc<"ticketCategories">>() for (const category of categories) { diff --git a/convex/schema.ts b/convex/schema.ts index c93bfbd..f0b6f28 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -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(), diff --git a/convex/seed.ts b/convex/seed.ts index a26f12b..7fadc95 100644 --- a/convex/seed.ts +++ b/convex/seed.ts @@ -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); diff --git a/convex/slas.ts b/convex/slas.ts index 5cfdc6a..32ab0a5 100644 --- a/convex/slas.ts +++ b/convex/slas.ts @@ -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, diff --git a/convex/teams.ts b/convex/teams.ts index f856e0d..b5dccb2 100644 --- a/convex/teams.ts +++ b/convex/teams.ts @@ -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, diff --git a/convex/ticketFormSettings.ts b/convex/ticketFormSettings.ts index e85d794..f26e785 100644 --- a/convex/ticketFormSettings.ts +++ b/convex/ticketFormSettings.ts @@ -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 diff --git a/convex/ticketFormTemplates.ts b/convex/ticketFormTemplates.ts index 92c25ed..d2a7579 100644 --- a/convex/ticketFormTemplates.ts +++ b/convex/ticketFormTemplates.ts @@ -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", { diff --git a/convex/tickets.ts b/convex/tickets.ts index 6621bca..e8ad68e 100644 --- a/convex/tickets.ts +++ b/convex/tickets.ts @@ -635,7 +635,7 @@ async function fetchTemplateSummaries(ctx: AnyCtx, tenantId: string): Promise 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: { diff --git a/convex/usbPolicy.ts b/convex/usbPolicy.ts index 4cb95b7..99b7fa4 100644 --- a/convex/usbPolicy.ts +++ b/convex/usbPolicy.ts @@ -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 } }, }) diff --git a/convex/users.ts b/convex/users.ts index a97752c..e435da6 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -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 }); diff --git a/src/components/admin/devices/admin-devices-overview.tsx b/src/components/admin/devices/admin-devices-overview.tsx index bcfe3f1..cdd5d90 100644 --- a/src/components/admin/devices/admin-devices-overview.tsx +++ b/src/components/admin/devices/admin-devices-overview.tsx @@ -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): DevicesQueryItem { diff --git a/src/components/tickets/tickets-view.tsx b/src/components/tickets/tickets-view.tsx index 63540c9..0ff3fe8 100644 --- a/src/components/tickets/tickets-view.tsx +++ b/src/components/tickets/tickets-view.tsx @@ -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(() => { diff --git a/src/hooks/use-ticket-categories.ts b/src/hooks/use-ticket-categories.ts index 59fe890..1018b9f 100644 --- a/src/hooks/use-ticket-categories.ts +++ b/src/hooks/use-ticket-categories.ts @@ -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) diff --git a/stack.yml b/stack.yml index 667d74f..04d1ad8 100644 --- a/stack.yml +++ b/stack.yml @@ -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: