381 lines
12 KiB
TypeScript
381 lines
12 KiB
TypeScript
import { defineSchema, defineTable } from "convex/server";
|
|
import { v } from "convex/values";
|
|
|
|
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"]),
|
|
|
|
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")),
|
|
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()),
|
|
})
|
|
)
|
|
),
|
|
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"]),
|
|
|
|
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"]),
|
|
|
|
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"]),
|
|
|
|
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(),
|
|
})
|
|
)
|
|
),
|
|
createdAt: v.number(),
|
|
updatedAt: v.number(),
|
|
})
|
|
.index("by_tenant_key", ["tenantId", "key"])
|
|
.index("by_tenant_order", ["tenantId", "order"])
|
|
.index("by_tenant", ["tenantId"]),
|
|
|
|
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()),
|
|
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"]),
|
|
});
|