280 lines
8.4 KiB
TypeScript
280 lines
8.4 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()),
|
|
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"]),
|
|
|
|
companies: defineTable({
|
|
tenantId: v.string(),
|
|
name: v.string(),
|
|
slug: 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()),
|
|
createdAt: v.number(),
|
|
updatedAt: v.number(),
|
|
})
|
|
.index("by_tenant_slug", ["tenantId", "slug"])
|
|
.index("by_tenant", ["tenantId"]),
|
|
|
|
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"),
|
|
assigneeId: v.optional(v.id("users")),
|
|
companyId: v.optional(v.id("companies")),
|
|
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_company", ["tenantId", "companyId"])
|
|
.index("by_tenant", ["tenantId"]),
|
|
|
|
ticketComments: defineTable({
|
|
ticketId: v.id("tickets"),
|
|
authorId: v.id("users"),
|
|
visibility: v.string(), // PUBLIC | INTERNAL
|
|
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()),
|
|
})
|
|
)
|
|
),
|
|
createdAt: v.number(),
|
|
updatedAt: v.number(),
|
|
}).index("by_ticket", ["ticketId"]),
|
|
|
|
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(),
|
|
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"]),
|
|
|
|
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"]),
|
|
|
|
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()),
|
|
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()),
|
|
createdAt: v.number(),
|
|
updatedAt: v.number(),
|
|
registeredBy: v.optional(v.string()),
|
|
})
|
|
.index("by_tenant", ["tenantId"])
|
|
.index("by_tenant_company", ["tenantId", "companyId"])
|
|
.index("by_tenant_fingerprint", ["tenantId", "fingerprint"]),
|
|
|
|
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"]),
|
|
});
|