import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; const gridLayoutItem = v.object({ i: v.string(), x: v.number(), y: v.number(), w: v.number(), h: v.number(), minW: v.optional(v.number()), minH: v.optional(v.number()), static: v.optional(v.boolean()), }); const widgetLayout = v.object({ x: v.number(), y: v.number(), w: v.number(), h: v.number(), minW: v.optional(v.number()), minH: v.optional(v.number()), static: v.optional(v.boolean()), }); const tvSection = v.object({ id: v.string(), title: v.optional(v.string()), description: v.optional(v.string()), widgetKeys: v.array(v.string()), durationSeconds: v.optional(v.number()), }); export default defineSchema({ users: defineTable({ tenantId: v.string(), name: v.string(), email: v.string(), role: v.optional(v.string()), jobTitle: v.optional(v.string()), managerId: v.optional(v.id("users")), avatarUrl: v.optional(v.string()), teams: v.optional(v.array(v.string())), companyId: v.optional(v.id("companies")), }) .index("by_tenant_email", ["tenantId", "email"]) .index("by_tenant_role", ["tenantId", "role"]) .index("by_tenant", ["tenantId"]) .index("by_tenant_company", ["tenantId", "companyId"]) .index("by_tenant_manager", ["tenantId", "managerId"]), companies: defineTable({ tenantId: v.string(), name: v.string(), slug: v.string(), provisioningCode: v.optional(v.string()), isAvulso: v.optional(v.boolean()), contractedHoursPerMonth: v.optional(v.number()), cnpj: v.optional(v.string()), domain: v.optional(v.string()), phone: v.optional(v.string()), description: v.optional(v.string()), address: v.optional(v.string()), legalName: v.optional(v.string()), tradeName: v.optional(v.string()), stateRegistration: v.optional(v.string()), stateRegistrationType: v.optional(v.string()), primaryCnae: v.optional(v.string()), timezone: v.optional(v.string()), businessHours: v.optional(v.any()), supportEmail: v.optional(v.string()), billingEmail: v.optional(v.string()), contactPreferences: v.optional(v.any()), clientDomains: v.optional(v.array(v.string())), communicationChannels: v.optional(v.any()), fiscalAddress: v.optional(v.any()), hasBranches: v.optional(v.boolean()), regulatedEnvironments: v.optional(v.array(v.string())), privacyPolicyAccepted: v.optional(v.boolean()), privacyPolicyReference: v.optional(v.string()), privacyPolicyMetadata: v.optional(v.any()), contracts: v.optional(v.any()), contacts: v.optional(v.any()), locations: v.optional(v.any()), sla: v.optional(v.any()), tags: v.optional(v.array(v.string())), customFields: v.optional(v.any()), notes: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), }) .index("by_tenant_slug", ["tenantId", "slug"]) .index("by_tenant", ["tenantId"]) .index("by_provisioning_code", ["provisioningCode"]), alerts: defineTable({ tenantId: v.string(), companyId: v.optional(v.id("companies")), companyName: v.string(), usagePct: v.number(), threshold: v.number(), range: v.string(), recipients: v.array(v.string()), createdAt: v.number(), deliveredCount: v.number(), }) .index("by_tenant_created", ["tenantId", "createdAt"]) .index("by_tenant", ["tenantId"]), dashboards: defineTable({ tenantId: v.string(), name: v.string(), description: v.optional(v.string()), aspectRatio: v.optional(v.string()), theme: v.optional(v.string()), filters: v.optional(v.any()), layout: v.optional(v.array(gridLayoutItem)), sections: v.optional(v.array(tvSection)), tvIntervalSeconds: v.optional(v.number()), readySelector: v.optional(v.string()), createdBy: v.id("users"), updatedBy: v.optional(v.id("users")), createdAt: v.number(), updatedAt: v.number(), isArchived: v.optional(v.boolean()), }) .index("by_tenant", ["tenantId"]) .index("by_tenant_created", ["tenantId", "createdAt"]), dashboardWidgets: defineTable({ tenantId: v.string(), dashboardId: v.id("dashboards"), widgetKey: v.string(), title: v.optional(v.string()), type: v.string(), config: v.any(), layout: v.optional(widgetLayout), order: v.number(), createdBy: v.id("users"), updatedBy: v.optional(v.id("users")), createdAt: v.number(), updatedAt: v.number(), isHidden: v.optional(v.boolean()), }) .index("by_dashboard", ["dashboardId"]) .index("by_dashboard_order", ["dashboardId", "order"]) .index("by_dashboard_key", ["dashboardId", "widgetKey"]) .index("by_tenant", ["tenantId"]), metricDefinitions: defineTable({ tenantId: v.string(), key: v.string(), name: v.string(), description: v.optional(v.string()), version: v.number(), definition: v.optional(v.any()), createdBy: v.id("users"), updatedBy: v.optional(v.id("users")), createdAt: v.number(), updatedAt: v.number(), tags: v.optional(v.array(v.string())), }) .index("by_tenant_key", ["tenantId", "key"]) .index("by_tenant", ["tenantId"]), dashboardShares: defineTable({ tenantId: v.string(), dashboardId: v.id("dashboards"), audience: v.string(), token: v.optional(v.string()), expiresAt: v.optional(v.number()), canEdit: v.boolean(), createdBy: v.id("users"), createdAt: v.number(), lastAccessAt: v.optional(v.number()), }) .index("by_dashboard", ["dashboardId"]) .index("by_token", ["token"]) .index("by_tenant", ["tenantId"]), queues: defineTable({ tenantId: v.string(), name: v.string(), slug: v.string(), teamId: v.optional(v.id("teams")), }) .index("by_tenant_slug", ["tenantId", "slug"]) .index("by_tenant", ["tenantId"]), teams: defineTable({ tenantId: v.string(), name: v.string(), description: v.optional(v.string()), }).index("by_tenant_name", ["tenantId", "name"]), slaPolicies: defineTable({ tenantId: v.string(), name: v.string(), description: v.optional(v.string()), timeToFirstResponse: v.optional(v.number()), // minutes timeToResolution: v.optional(v.number()), // minutes }).index("by_tenant_name", ["tenantId", "name"]), tickets: defineTable({ tenantId: v.string(), reference: v.number(), subject: v.string(), summary: v.optional(v.string()), description: v.optional(v.string()), status: v.string(), priority: v.string(), channel: v.string(), queueId: v.optional(v.id("queues")), categoryId: v.optional(v.id("ticketCategories")), subcategoryId: v.optional(v.id("ticketSubcategories")), requesterId: v.id("users"), requesterSnapshot: v.optional( v.object({ name: v.string(), email: v.optional(v.string()), avatarUrl: v.optional(v.string()), teams: v.optional(v.array(v.string())), }) ), assigneeId: v.optional(v.id("users")), assigneeSnapshot: v.optional( v.object({ name: v.string(), email: v.optional(v.string()), avatarUrl: v.optional(v.string()), teams: v.optional(v.array(v.string())), }) ), companyId: v.optional(v.id("companies")), companySnapshot: v.optional( v.object({ name: v.string(), slug: v.optional(v.string()), isAvulso: v.optional(v.boolean()), }) ), machineId: v.optional(v.id("machines")), machineSnapshot: v.optional( v.object({ hostname: v.optional(v.string()), persona: v.optional(v.string()), assignedUserName: v.optional(v.string()), assignedUserEmail: v.optional(v.string()), status: v.optional(v.string()), }) ), working: v.optional(v.boolean()), slaPolicyId: v.optional(v.id("slaPolicies")), slaSnapshot: v.optional( v.object({ categoryId: v.optional(v.id("ticketCategories")), categoryName: v.optional(v.string()), priority: v.optional(v.string()), responseTargetMinutes: v.optional(v.number()), responseMode: v.optional(v.string()), solutionTargetMinutes: v.optional(v.number()), solutionMode: v.optional(v.string()), alertThreshold: v.optional(v.number()), pauseStatuses: v.optional(v.array(v.string())), }) ), slaResponseDueAt: v.optional(v.number()), slaSolutionDueAt: v.optional(v.number()), slaResponseStatus: v.optional(v.string()), slaSolutionStatus: v.optional(v.string()), slaPausedAt: v.optional(v.number()), slaPausedBy: v.optional(v.string()), slaPausedMs: v.optional(v.number()), dueAt: v.optional(v.number()), // ms since epoch firstResponseAt: v.optional(v.number()), resolvedAt: v.optional(v.number()), closedAt: v.optional(v.number()), updatedAt: v.number(), createdAt: v.number(), tags: v.optional(v.array(v.string())), customFields: v.optional( v.array( v.object({ fieldId: v.id("ticketFields"), fieldKey: v.string(), label: v.string(), type: v.string(), value: v.any(), displayValue: v.optional(v.string()), }) ) ), csatScore: v.optional(v.number()), csatMaxScore: v.optional(v.number()), csatComment: v.optional(v.string()), csatRatedAt: v.optional(v.number()), csatRatedBy: v.optional(v.id("users")), csatAssigneeId: v.optional(v.id("users")), csatAssigneeSnapshot: v.optional( v.object({ name: v.string(), email: v.optional(v.string()), avatarUrl: v.optional(v.string()), teams: v.optional(v.array(v.string())), }) ), formTemplate: v.optional(v.string()), formTemplateLabel: v.optional(v.string()), relatedTicketIds: v.optional(v.array(v.id("tickets"))), resolvedWithTicketId: v.optional(v.id("tickets")), reopenDeadline: v.optional(v.number()), reopenedAt: v.optional(v.number()), chatEnabled: v.optional(v.boolean()), totalWorkedMs: v.optional(v.number()), internalWorkedMs: v.optional(v.number()), externalWorkedMs: v.optional(v.number()), activeSessionId: v.optional(v.id("ticketWorkSessions")), }) .index("by_tenant_status", ["tenantId", "status"]) .index("by_tenant_queue", ["tenantId", "queueId"]) .index("by_tenant_assignee", ["tenantId", "assigneeId"]) .index("by_tenant_reference", ["tenantId", "reference"]) .index("by_tenant_requester", ["tenantId", "requesterId"]) .index("by_tenant_company", ["tenantId", "companyId"]) .index("by_tenant_machine", ["tenantId", "machineId"]) .index("by_tenant", ["tenantId"]) .index("by_tenant_created", ["tenantId", "createdAt"]) .index("by_tenant_resolved", ["tenantId", "resolvedAt"]) .index("by_tenant_company_created", ["tenantId", "companyId", "createdAt"]) .index("by_tenant_company_resolved", ["tenantId", "companyId", "resolvedAt"]), ticketComments: defineTable({ ticketId: v.id("tickets"), authorId: v.id("users"), visibility: v.string(), // PUBLIC | INTERNAL body: v.string(), authorSnapshot: v.optional( v.object({ name: v.string(), email: v.optional(v.string()), avatarUrl: v.optional(v.string()), teams: v.optional(v.array(v.string())), }) ), attachments: v.optional( v.array( v.object({ storageId: v.id("_storage"), name: v.string(), size: v.optional(v.number()), type: v.optional(v.string()), }) ) ), createdAt: v.number(), updatedAt: v.number(), }) .index("by_ticket", ["ticketId"]) .index("by_author", ["authorId"]), ticketEvents: defineTable({ ticketId: v.id("tickets"), type: v.string(), payload: v.optional(v.any()), createdAt: v.number(), }).index("by_ticket", ["ticketId"]), ticketChatMessages: defineTable({ ticketId: v.id("tickets"), authorId: v.id("users"), authorSnapshot: v.optional( v.object({ name: v.string(), email: v.optional(v.string()), avatarUrl: v.optional(v.string()), teams: v.optional(v.array(v.string())), }) ), body: v.string(), attachments: v.optional( v.array( v.object({ storageId: v.id("_storage"), name: v.string(), size: v.optional(v.number()), type: v.optional(v.string()), }) ) ), notifiedAt: v.optional(v.number()), createdAt: v.number(), updatedAt: v.number(), tenantId: v.string(), companyId: v.optional(v.id("companies")), readBy: v.optional( v.array( v.object({ userId: v.id("users"), readAt: v.number(), }) ) ), }) .index("by_ticket_created", ["ticketId", "createdAt"]) .index("by_tenant_created", ["tenantId", "createdAt"]), commentTemplates: defineTable({ tenantId: v.string(), kind: v.optional(v.string()), title: v.string(), body: v.string(), createdBy: v.id("users"), updatedBy: v.optional(v.id("users")), createdAt: v.number(), updatedAt: v.number(), }) .index("by_tenant", ["tenantId"]) .index("by_tenant_title", ["tenantId", "title"]) .index("by_tenant_kind", ["tenantId", "kind"]), ticketWorkSessions: defineTable({ ticketId: v.id("tickets"), agentId: v.id("users"), workType: v.optional(v.string()), // INTERNAL | EXTERNAL startedAt: v.number(), stoppedAt: v.optional(v.number()), durationMs: v.optional(v.number()), pauseReason: v.optional(v.string()), pauseNote: v.optional(v.string()), }) .index("by_ticket", ["ticketId"]) .index("by_ticket_agent", ["ticketId", "agentId"]) .index("by_agent", ["agentId"]), ticketCategories: defineTable({ tenantId: v.string(), name: v.string(), slug: v.string(), description: v.optional(v.string()), order: v.number(), createdAt: v.number(), updatedAt: v.number(), }) .index("by_tenant_slug", ["tenantId", "slug"]) .index("by_tenant_order", ["tenantId", "order"]) .index("by_tenant", ["tenantId"]), ticketSubcategories: defineTable({ tenantId: v.string(), categoryId: v.id("ticketCategories"), name: v.string(), slug: v.string(), order: v.number(), createdAt: v.number(), updatedAt: v.number(), }) .index("by_category_order", ["categoryId", "order"]) .index("by_category_slug", ["categoryId", "slug"]) .index("by_tenant_slug", ["tenantId", "slug"]), categorySlaSettings: defineTable({ tenantId: v.string(), categoryId: v.id("ticketCategories"), priority: v.string(), responseTargetMinutes: v.optional(v.number()), responseMode: v.optional(v.string()), solutionTargetMinutes: v.optional(v.number()), solutionMode: v.optional(v.string()), alertThreshold: v.optional(v.number()), pauseStatuses: v.optional(v.array(v.string())), calendarType: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), actorId: v.optional(v.id("users")), }) .index("by_tenant_category_priority", ["tenantId", "categoryId", "priority"]) .index("by_tenant_category", ["tenantId", "categoryId"]), ticketFields: defineTable({ tenantId: v.string(), key: v.string(), label: v.string(), type: v.string(), description: v.optional(v.string()), required: v.boolean(), order: v.number(), options: v.optional( v.array( v.object({ value: v.string(), label: v.string(), }) ) ), scope: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), }) .index("by_tenant_key", ["tenantId", "key"]) .index("by_tenant_order", ["tenantId", "order"]) .index("by_tenant_scope", ["tenantId", "scope"]) .index("by_tenant", ["tenantId"]), ticketFormSettings: defineTable({ tenantId: v.string(), template: v.string(), scope: v.string(), // tenant | company | user companyId: v.optional(v.id("companies")), userId: v.optional(v.id("users")), enabled: v.boolean(), createdAt: v.number(), updatedAt: v.number(), actorId: v.optional(v.id("users")), }) .index("by_tenant_template_scope", ["tenantId", "template", "scope"]) .index("by_tenant_template_company", ["tenantId", "template", "companyId"]) .index("by_tenant_template_user", ["tenantId", "template", "userId"]) .index("by_tenant", ["tenantId"]), ticketFormTemplates: defineTable({ tenantId: v.string(), key: v.string(), label: v.string(), description: v.optional(v.string()), defaultEnabled: v.optional(v.boolean()), baseTemplateKey: v.optional(v.string()), isSystem: v.optional(v.boolean()), isArchived: v.optional(v.boolean()), order: v.number(), createdAt: v.number(), updatedAt: v.number(), createdBy: v.optional(v.id("users")), updatedBy: v.optional(v.id("users")), }) .index("by_tenant", ["tenantId"]) .index("by_tenant_key", ["tenantId", "key"]) .index("by_tenant_active", ["tenantId", "isArchived"]), userInvites: defineTable({ tenantId: v.string(), inviteId: v.string(), email: v.string(), name: v.optional(v.string()), role: v.string(), status: v.string(), token: v.string(), expiresAt: v.number(), createdAt: v.number(), createdById: v.optional(v.string()), acceptedAt: v.optional(v.number()), acceptedById: v.optional(v.string()), revokedAt: v.optional(v.number()), revokedById: v.optional(v.string()), revokedReason: v.optional(v.string()), }) .index("by_tenant", ["tenantId"]) .index("by_token", ["tenantId", "token"]) .index("by_invite", ["tenantId", "inviteId"]), machines: defineTable({ tenantId: v.string(), companyId: v.optional(v.id("companies")), companySlug: v.optional(v.string()), authUserId: v.optional(v.string()), authEmail: v.optional(v.string()), persona: v.optional(v.string()), assignedUserId: v.optional(v.id("users")), assignedUserEmail: v.optional(v.string()), assignedUserName: v.optional(v.string()), assignedUserRole: v.optional(v.string()), linkedUserIds: v.optional(v.array(v.id("users"))), hostname: v.string(), osName: v.string(), osVersion: v.optional(v.string()), architecture: v.optional(v.string()), macAddresses: v.array(v.string()), serialNumbers: v.array(v.string()), fingerprint: v.string(), metadata: v.optional(v.any()), displayName: v.optional(v.string()), deviceType: v.optional(v.string()), devicePlatform: v.optional(v.string()), deviceProfile: v.optional(v.any()), managementMode: v.optional(v.string()), customFields: v.optional( v.array( v.object({ fieldId: v.id("deviceFields"), fieldKey: v.string(), label: v.string(), type: v.string(), value: v.any(), displayValue: v.optional(v.string()), }) ) ), lastHeartbeatAt: v.optional(v.number()), status: v.optional(v.string()), isActive: v.optional(v.boolean()), createdAt: v.number(), updatedAt: v.number(), registeredBy: v.optional(v.string()), remoteAccess: v.optional(v.any()), }) .index("by_tenant", ["tenantId"]) .index("by_tenant_company", ["tenantId", "companyId"]) .index("by_tenant_fingerprint", ["tenantId", "fingerprint"]) .index("by_tenant_assigned_email", ["tenantId", "assignedUserEmail"]) .index("by_auth_email", ["authEmail"]), machineAlerts: defineTable({ tenantId: v.string(), machineId: v.id("machines"), companyId: v.optional(v.id("companies")), kind: v.string(), message: v.string(), severity: v.string(), createdAt: v.number(), }) .index("by_machine_created", ["machineId", "createdAt"]) .index("by_tenant_created", ["tenantId", "createdAt"]) .index("by_tenant_machine", ["tenantId", "machineId"]), machineTokens: defineTable({ tenantId: v.string(), machineId: v.id("machines"), tokenHash: v.string(), expiresAt: v.number(), revoked: v.boolean(), createdAt: v.number(), lastUsedAt: v.optional(v.number()), usageCount: v.optional(v.number()), type: v.optional(v.string()), }) .index("by_token_hash", ["tokenHash"]) .index("by_machine", ["machineId"]) .index("by_tenant_machine", ["tenantId", "machineId"]) .index("by_machine_created", ["machineId", "createdAt"]) .index("by_machine_revoked_expires", ["machineId", "revoked", "expiresAt"]), deviceFields: defineTable({ tenantId: v.string(), key: v.string(), label: v.string(), description: v.optional(v.string()), type: v.string(), required: v.optional(v.boolean()), options: v.optional( v.array( v.object({ value: v.string(), label: v.string(), }) ) ), scope: v.optional(v.string()), companyId: v.optional(v.id("companies")), order: v.number(), createdAt: v.number(), updatedAt: v.number(), createdBy: v.optional(v.id("users")), updatedBy: v.optional(v.id("users")), }) .index("by_tenant_order", ["tenantId", "order"]) .index("by_tenant_key", ["tenantId", "key"]) .index("by_tenant_company", ["tenantId", "companyId"]) .index("by_tenant_scope", ["tenantId", "scope"]) .index("by_tenant", ["tenantId"]), deviceExportTemplates: defineTable({ tenantId: v.string(), name: v.string(), slug: v.string(), description: v.optional(v.string()), columns: v.array( v.object({ key: v.string(), label: v.optional(v.string()), }) ), filters: v.optional(v.any()), companyId: v.optional(v.id("companies")), isDefault: v.optional(v.boolean()), isActive: v.optional(v.boolean()), createdBy: v.optional(v.id("users")), updatedBy: v.optional(v.id("users")), createdAt: v.number(), updatedAt: v.number(), }) .index("by_tenant_slug", ["tenantId", "slug"]) .index("by_tenant_company", ["tenantId", "companyId"]) .index("by_tenant_default", ["tenantId", "isDefault"]) .index("by_tenant", ["tenantId"]), });