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