Files
sgse-app/packages/backend/convex/schema.ts
killer-cf f021e96eb4 feat: enhance cybersecurity features and add ticket management components
- Introduced new components for managing tickets, including TicketForm, TicketCard, and TicketTimeline, to streamline the ticketing process.
- Added a new SlaChart component for visualizing SLA data.
- Implemented a CybersecurityWizcard component for enhanced security monitoring and reporting.
- Updated routing to replace the "Solicitar Acesso" page with "Abrir Chamado" for improved user navigation.
- Integrated rate limiting functionality to enhance security measures.
- Added a comprehensive test report for the cybersecurity system, detailing various attack simulations and their outcomes.
- Included new scripts for security testing and environment setup to facilitate automated security assessments.
2025-11-17 16:54:43 -03:00

1350 lines
42 KiB
TypeScript

import { defineSchema, defineTable } from "convex/server";
import { Infer, v } from "convex/values";
export const simboloTipo = v.union(
v.literal("cargo_comissionado"),
v.literal("funcao_gratificada")
);
export type SimboloTipo = Infer<typeof simboloTipo>;
export const ataqueCiberneticoTipo = v.union(
v.literal("phishing"),
v.literal("malware"),
v.literal("ransomware"),
v.literal("brute_force"),
v.literal("credential_stuffing"),
v.literal("sql_injection"),
v.literal("xss"),
v.literal("path_traversal"),
v.literal("command_injection"),
v.literal("nosql_injection"),
v.literal("xxe"),
v.literal("man_in_the_middle"),
v.literal("ddos"),
v.literal("engenharia_social"),
v.literal("cve_exploit"),
v.literal("apt"),
v.literal("zero_day"),
v.literal("supply_chain"),
v.literal("fileless_malware"),
v.literal("polymorphic_malware"),
v.literal("ransomware_lateral"),
v.literal("deepfake_phishing"),
v.literal("adversarial_ai"),
v.literal("side_channel"),
v.literal("firmware_bootloader"),
v.literal("bec"),
v.literal("botnet"),
v.literal("ot_ics"),
v.literal("quantum_attack")
);
export type AtaqueCiberneticoTipo = Infer<typeof ataqueCiberneticoTipo>;
export const severidadeSeguranca = v.union(
v.literal("informativo"),
v.literal("baixo"),
v.literal("moderado"),
v.literal("alto"),
v.literal("critico")
);
export type SeveridadeSeguranca = Infer<typeof severidadeSeguranca>;
export const statusEventoSeguranca = v.union(
v.literal("detectado"),
v.literal("investigando"),
v.literal("contido"),
v.literal("falso_positivo"),
v.literal("escalado"),
v.literal("resolvido")
);
export type StatusEventoSeguranca = Infer<typeof statusEventoSeguranca>;
export const sensorSegurancaTipo = v.union(
v.literal("network"),
v.literal("endpoint"),
v.literal("application"),
v.literal("gateway"),
v.literal("ot"),
v.literal("honeypot")
);
export type SensorSegurancaTipo = Infer<typeof sensorSegurancaTipo>;
export const sensorSegurancaStatus = v.union(
v.literal("ativo"),
v.literal("inativo"),
v.literal("degradado"),
v.literal("manutencao")
);
export type SensorSegurancaStatus = Infer<typeof sensorSegurancaStatus>;
export const threatIntelTipo = v.union(
v.literal("open_source"),
v.literal("commercial"),
v.literal("internal"),
v.literal("gov"),
v.literal("research")
);
export const threatIntelFormato = v.union(
v.literal("json"),
v.literal("stix"),
v.literal("csv"),
v.literal("text"),
v.literal("custom")
);
export const acaoIncidenteTipo = v.union(
v.literal("block_ip"),
v.literal("unblock_ip"),
v.literal("block_port"),
v.literal("liberar_porta"),
v.literal("notificar"),
v.literal("isolar_host"),
v.literal("gerar_relatorio"),
v.literal("criar_ticket"),
v.literal("ajuste_regra"),
v.literal("custom")
);
export const acaoIncidenteStatus = v.union(
v.literal("pendente"),
v.literal("executando"),
v.literal("concluido"),
v.literal("falhou")
);
export const reportStatus = v.union(
v.literal("pendente"),
v.literal("processando"),
v.literal("concluido"),
v.literal("falhou")
);
export default defineSchema({
todos: defineTable({
text: v.string(),
completed: v.boolean(),
}),
empresas: defineTable({
nome: v.string(),
cnpj: v.string(),
telefone: v.string(),
email: v.string(),
descricao: v.optional(v.string()),
criadoPor: v.optional(v.id("usuarios")),
})
.index("by_nome", ["nome"])
.index("by_cnpj", ["cnpj"]),
contatosEmpresa: defineTable({
empresaId: v.id("empresas"),
nome: v.string(),
funcao: v.string(),
email: v.string(),
telefone: v.string(),
adicionadoPor: v.optional(v.id("usuarios")),
descricao: v.optional(v.string()),
})
.index("by_empresa", ["empresaId"])
.index("by_email", ["email"]),
funcionarios: defineTable({
// Campos obrigatórios existentes
nome: v.string(),
nascimento: v.string(),
rg: v.string(),
cpf: v.string(),
endereco: v.string(),
cep: v.string(),
cidade: v.string(),
uf: v.string(),
telefone: v.string(),
email: v.string(),
matricula: v.optional(v.string()),
admissaoData: v.optional(v.string()),
desligamentoData: v.optional(v.string()),
simboloId: v.id("simbolos"),
simboloTipo: simboloTipo,
gestorId: v.optional(v.id("usuarios")),
statusFerias: v.optional(
v.union(v.literal("ativo"), v.literal("em_ferias"))
),
// Regime de trabalho (para cálculo correto de férias)
regimeTrabalho: v.optional(
v.union(
v.literal("clt"), // CLT - Consolidação das Leis do Trabalho
v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco
v.literal("estatutario_federal"), // Servidor Público Federal
v.literal("estatutario_municipal") // Servidor Público Municipal
)
),
// Dados Pessoais Adicionais (opcionais)
nomePai: v.optional(v.string()),
nomeMae: v.optional(v.string()),
naturalidade: v.optional(v.string()),
naturalidadeUF: v.optional(v.string()),
sexo: v.optional(
v.union(v.literal("masculino"), v.literal("feminino"), v.literal("outro"))
),
estadoCivil: v.optional(
v.union(
v.literal("solteiro"),
v.literal("casado"),
v.literal("divorciado"),
v.literal("viuvo"),
v.literal("uniao_estavel")
)
),
nacionalidade: v.optional(v.string()),
// Documentos Pessoais
rgOrgaoExpedidor: v.optional(v.string()),
rgDataEmissao: v.optional(v.string()),
carteiraProfissionalNumero: v.optional(v.string()),
carteiraProfissionalSerie: v.optional(v.string()),
carteiraProfissionalDataEmissao: v.optional(v.string()),
reservistaNumero: v.optional(v.string()),
reservistaSerie: v.optional(v.string()),
tituloEleitorNumero: v.optional(v.string()),
tituloEleitorZona: v.optional(v.string()),
tituloEleitorSecao: v.optional(v.string()),
pisNumero: v.optional(v.string()),
// Formação e Saúde
grauInstrucao: v.optional(
v.union(
v.literal("fundamental"),
v.literal("medio"),
v.literal("superior"),
v.literal("pos_graduacao"),
v.literal("mestrado"),
v.literal("doutorado")
)
),
formacao: v.optional(v.string()),
formacaoRegistro: v.optional(v.string()),
grupoSanguineo: v.optional(
v.union(v.literal("A"), v.literal("B"), v.literal("AB"), v.literal("O"))
),
fatorRH: v.optional(v.union(v.literal("positivo"), v.literal("negativo"))),
// Cargo e Vínculo
descricaoCargo: v.optional(v.string()),
nomeacaoPortaria: v.optional(v.string()),
nomeacaoData: v.optional(v.string()),
nomeacaoDOE: v.optional(v.string()),
pertenceOrgaoPublico: v.optional(v.boolean()),
orgaoOrigem: v.optional(v.string()),
aposentado: v.optional(
v.union(v.literal("nao"), v.literal("funape_ipsep"), v.literal("inss"))
),
// Dados Bancários
contaBradescoNumero: v.optional(v.string()),
contaBradescoDV: v.optional(v.string()),
contaBradescoAgencia: v.optional(v.string()),
// Documentos Anexos (Storage IDs)
certidaoAntecedentesPF: v.optional(v.id("_storage")),
certidaoAntecedentesJFPE: v.optional(v.id("_storage")),
certidaoAntecedentesSDS: v.optional(v.id("_storage")),
certidaoAntecedentesTJPE: v.optional(v.id("_storage")),
certidaoImprobidade: v.optional(v.id("_storage")),
rgFrente: v.optional(v.id("_storage")),
rgVerso: v.optional(v.id("_storage")),
cpfFrente: v.optional(v.id("_storage")),
cpfVerso: v.optional(v.id("_storage")),
situacaoCadastralCPF: v.optional(v.id("_storage")),
tituloEleitorFrente: v.optional(v.id("_storage")),
tituloEleitorVerso: v.optional(v.id("_storage")),
comprovanteVotacao: v.optional(v.id("_storage")),
carteiraProfissionalFrente: v.optional(v.id("_storage")),
carteiraProfissionalVerso: v.optional(v.id("_storage")),
comprovantePIS: v.optional(v.id("_storage")),
certidaoRegistroCivil: v.optional(v.id("_storage")),
certidaoNascimentoDependentes: v.optional(v.id("_storage")),
cpfDependentes: v.optional(v.id("_storage")),
reservistaDoc: v.optional(v.id("_storage")),
comprovanteEscolaridade: v.optional(v.id("_storage")),
comprovanteResidencia: v.optional(v.id("_storage")),
comprovanteContaBradesco: v.optional(v.id("_storage")),
// Dependentes do funcionário (uploads opcionais)
dependentes: v.optional(
v.array(
v.object({
parentesco: v.union(
v.literal("filho"),
v.literal("filha"),
v.literal("conjuge"),
v.literal("outro")
),
nome: v.string(),
cpf: v.string(),
nascimento: v.string(),
documentoId: v.optional(v.id("_storage")),
// Benefícios/declarações por dependente
salarioFamilia: v.optional(v.boolean()),
impostoRenda: v.optional(v.boolean()),
})
)
),
// Declarações (Storage IDs)
declaracaoAcumulacaoCargo: v.optional(v.id("_storage")),
declaracaoDependentesIR: v.optional(v.id("_storage")),
declaracaoIdoneidade: v.optional(v.id("_storage")),
termoNepotismo: v.optional(v.id("_storage")),
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
})
.index("by_matricula", ["matricula"])
.index("by_nome", ["nome"])
.index("by_simboloId", ["simboloId"])
.index("by_simboloTipo", ["simboloTipo"])
.index("by_cpf", ["cpf"])
.index("by_rg", ["rg"])
.index("by_gestor", ["gestorId"]),
atestados: defineTable({
funcionarioId: v.id("funcionarios"),
tipo: v.union(
v.literal("atestado_medico"),
v.literal("declaracao_comparecimento")
),
dataInicio: v.string(),
dataFim: v.string(),
cid: v.optional(v.string()), // Apenas para atestado médico
observacoes: v.optional(v.string()),
documentoId: v.optional(v.id("_storage")),
criadoPor: v.id("usuarios"),
criadoEm: v.number(),
})
.index("by_funcionario", ["funcionarioId"])
.index("by_tipo", ["tipo"])
.index("by_data_inicio", ["dataInicio"])
.index("by_funcionario_and_tipo", ["funcionarioId", "tipo"]),
licencas: defineTable({
funcionarioId: v.id("funcionarios"),
tipo: v.union(v.literal("maternidade"), v.literal("paternidade")),
dataInicio: v.string(),
dataFim: v.string(),
documentoId: v.optional(v.id("_storage")),
observacoes: v.optional(v.string()),
licencaOriginalId: v.optional(v.id("licencas")), // Para prorrogações
ehProrrogacao: v.boolean(),
criadoPor: v.id("usuarios"),
criadoEm: v.number(),
})
.index("by_funcionario", ["funcionarioId"])
.index("by_tipo", ["tipo"])
.index("by_data_inicio", ["dataInicio"])
.index("by_licenca_original", ["licencaOriginalId"])
.index("by_funcionario_and_tipo", ["funcionarioId", "tipo"]),
ferias: defineTable({
funcionarioId: v.id("funcionarios"),
anoReferencia: v.number(),
dataInicio: v.string(),
dataFim: v.string(),
diasFerias: v.number(),
status: v.union(
v.literal("aguardando_aprovacao"),
v.literal("aprovado"),
v.literal("reprovado"),
v.literal("data_ajustada_aprovada"),
v.literal("EmFérias")
),
gestorId: v.optional(v.id("usuarios")),
observacao: v.optional(v.string()),
motivoReprovacao: v.optional(v.string()),
dataAprovacao: v.optional(v.number()),
dataReprovacao: v.optional(v.number()),
diasAbono: v.number(),
historicoAlteracoes: v.optional(
v.array(
v.object({
data: v.number(),
usuarioId: v.id("usuarios"),
acao: v.string(),
})
)
),
})
.index("by_funcionario", ["funcionarioId"])
.index("by_funcionario_and_ano", ["funcionarioId", "anoReferencia"])
.index("by_funcionario_and_status", ["funcionarioId", "status"])
.index("by_status", ["status"])
.index("by_ano", ["anoReferencia"]),
notificacoesFerias: defineTable({
destinatarioId: v.id("usuarios"),
feriasId: v.id("ferias"),
tipo: v.union(
v.literal("nova_solicitacao"),
v.literal("aprovado"),
v.literal("reprovado"),
v.literal("data_ajustada")
),
lida: v.boolean(),
mensagem: v.string(),
})
.index("by_destinatario", ["destinatarioId"])
.index("by_destinatario_and_lida", ["destinatarioId", "lida"]),
// Solicitações de Ausências
solicitacoesAusencias: defineTable({
funcionarioId: v.id("funcionarios"),
dataInicio: v.string(),
dataFim: v.string(),
motivo: v.string(),
status: v.union(
v.literal("aguardando_aprovacao"),
v.literal("aprovado"),
v.literal("reprovado")
),
gestorId: v.optional(v.id("usuarios")),
dataAprovacao: v.optional(v.number()),
dataReprovacao: v.optional(v.number()),
motivoReprovacao: v.optional(v.string()),
observacao: v.optional(v.string()),
criadoEm: v.number(),
})
.index("by_funcionario", ["funcionarioId"])
.index("by_status", ["status"])
.index("by_funcionario_and_status", ["funcionarioId", "status"]),
notificacoesAusencias: defineTable({
destinatarioId: v.id("usuarios"),
solicitacaoAusenciaId: v.id("solicitacoesAusencias"),
tipo: v.union(
v.literal("nova_solicitacao"),
v.literal("aprovado"),
v.literal("reprovado")
),
lida: v.boolean(),
mensagem: v.string(),
})
.index("by_destinatario", ["destinatarioId"])
.index("by_destinatario_and_lida", ["destinatarioId", "lida"]),
times: defineTable({
nome: v.string(),
descricao: v.optional(v.string()),
gestorId: v.id("usuarios"),
gestorSuperiorId: v.optional(v.id("usuarios")),
ativo: v.boolean(),
cor: v.optional(v.string()), // Cor para identificação visual
})
.index("by_gestor", ["gestorId"])
.index("by_gestor_superior", ["gestorSuperiorId"]),
timesMembros: defineTable({
timeId: v.id("times"),
funcionarioId: v.id("funcionarios"),
dataEntrada: v.number(),
dataSaida: v.optional(v.number()),
ativo: v.boolean(),
})
.index("by_time", ["timeId"])
.index("by_funcionario", ["funcionarioId"])
.index("by_time_and_ativo", ["timeId", "ativo"]),
cursos: defineTable({
funcionarioId: v.id("funcionarios"),
descricao: v.string(),
data: v.string(),
certificadoId: v.optional(v.id("_storage")),
}).index("by_funcionario", ["funcionarioId"]),
simbolos: defineTable({
nome: v.string(),
tipo: simboloTipo,
descricao: v.string(),
vencValor: v.string(),
repValor: v.string(),
valor: v.string(),
}),
solicitacoesAcesso: defineTable({
nome: v.string(),
matricula: v.string(),
email: v.string(),
telefone: v.string(),
status: v.union(
v.literal("pendente"),
v.literal("aprovado"),
v.literal("rejeitado")
),
dataSolicitacao: v.number(),
dataResposta: v.optional(v.number()),
observacoes: v.optional(v.string()),
})
.index("by_status", ["status"])
.index("by_matricula", ["matricula"])
.index("by_email", ["email"]),
// Sistema de Autenticação e Controle de Acesso
usuarios: defineTable({
authId: v.string(),
nome: v.string(),
email: v.string(),
funcionarioId: v.optional(v.id("funcionarios")),
roleId: v.id("roles"),
ativo: v.boolean(),
primeiroAcesso: v.boolean(),
ultimoAcesso: v.optional(v.number()),
criadoEm: v.number(),
atualizadoEm: v.number(),
// Controle de Bloqueio e Segurança
bloqueado: v.optional(v.boolean()),
motivoBloqueio: v.optional(v.string()),
dataBloqueio: v.optional(v.number()),
tentativasLogin: v.optional(v.number()), // contador de tentativas falhas
ultimaTentativaLogin: v.optional(v.number()), // timestamp da última tentativa
// Campos de Chat e Perfil
avatar: v.optional(v.string()), // "avatar-1" até "avatar-15" ou storageId
fotoPerfil: v.optional(v.id("_storage")),
setor: v.optional(v.string()),
statusMensagem: v.optional(v.string()), // max 100 chars
statusPresenca: v.optional(
v.union(
v.literal("online"),
v.literal("offline"),
v.literal("ausente"),
v.literal("externo"),
v.literal("em_reuniao")
)
),
ultimaAtividade: v.optional(v.number()), // timestamp
notificacoesAtivadas: v.optional(v.boolean()),
somNotificacao: v.optional(v.boolean()),
})
.index("by_email", ["email"])
.index("by_role", ["roleId"])
.index("by_ativo", ["ativo"])
.index("by_status_presenca", ["statusPresenca"])
.index("by_bloqueado", ["bloqueado"])
.index("by_funcionarioId", ["funcionarioId"])
.index("authId", ["authId"]),
roles: defineTable({
nome: v.string(), // "admin", "ti_master", "ti_usuario", "usuario_avancado", "usuario"
descricao: v.string(),
nivel: v.number(), // 0 = admin, 1 = ti_master, 2 = ti_usuario, 3+ = customizado
setor: v.optional(v.string()), // "ti", "rh", "financeiro", etc.
customizado: v.optional(v.boolean()), // se é um perfil customizado criado por TI_MASTER
criadoPor: v.optional(v.id("usuarios")), // usuário TI_MASTER que criou este perfil
editavel: v.optional(v.boolean()), // se pode ser editado (false para roles fixas)
})
.index("by_nome", ["nome"])
.index("by_nivel", ["nivel"])
.index("by_setor", ["setor"])
.index("by_customizado", ["customizado"]),
permissoes: defineTable({
nome: v.string(), // "funcionarios.criar", "simbolos.editar", etc.
descricao: v.string(),
recurso: v.string(), // "funcionarios", "simbolos", "usuarios", etc.
acao: v.string(), // "criar", "ler", "editar", "excluir"
})
.index("by_recurso", ["recurso"])
.index("by_recurso_e_acao", ["recurso", "acao"])
.index("by_nome", ["nome"]),
rolePermissoes: defineTable({
roleId: v.id("roles"),
permissaoId: v.id("permissoes"),
})
.index("by_role", ["roleId"])
.index("by_permissao", ["permissaoId"]),
sessoes: defineTable({
usuarioId: v.id("usuarios"),
token: v.string(),
ipAddress: v.optional(v.string()),
userAgent: v.optional(v.string()),
criadoEm: v.number(),
expiraEm: v.number(),
ativo: v.boolean(),
})
.index("by_usuario", ["usuarioId"])
.index("by_token", ["token"])
.index("by_ativo", ["ativo"])
.index("by_expiracao", ["expiraEm"]),
logsAcesso: defineTable({
usuarioId: v.id("usuarios"),
tipo: v.union(
v.literal("login"),
v.literal("logout"),
v.literal("acesso_negado"),
v.literal("senha_alterada"),
v.literal("sessao_expirada")
),
ipAddress: v.optional(v.string()),
userAgent: v.optional(v.string()),
detalhes: v.optional(v.string()),
timestamp: v.number(),
})
.index("by_usuario", ["usuarioId"])
.index("by_tipo", ["tipo"])
.index("by_timestamp", ["timestamp"]),
// Logs de Login Detalhados
logsLogin: defineTable({
usuarioId: v.optional(v.id("usuarios")), // pode ser null se falha antes de identificar usuário
matriculaOuEmail: v.string(), // tentativa de login
sucesso: v.boolean(),
motivoFalha: v.optional(v.string()), // "senha_incorreta", "usuario_bloqueado", "usuario_inexistente"
ipAddress: v.optional(v.string()),
userAgent: v.optional(v.string()),
device: v.optional(v.string()),
browser: v.optional(v.string()),
sistema: v.optional(v.string()),
timestamp: v.number(),
})
.index("by_usuario", ["usuarioId"])
.index("by_sucesso", ["sucesso"])
.index("by_timestamp", ["timestamp"])
.index("by_ip", ["ipAddress"]),
// Logs de Atividades
logsAtividades: defineTable({
usuarioId: v.id("usuarios"),
acao: v.string(), // "criar", "editar", "excluir", "bloquear", "desbloquear", etc.
recurso: v.string(), // "funcionarios", "simbolos", "usuarios", "perfis", etc.
recursoId: v.optional(v.string()), // ID do recurso afetado
detalhes: v.optional(v.string()), // JSON com detalhes da ação
timestamp: v.number(),
})
.index("by_usuario", ["usuarioId"])
.index("by_acao", ["acao"])
.index("by_recurso", ["recurso"])
.index("by_timestamp", ["timestamp"])
.index("by_recurso_id", ["recurso", "recursoId"]),
// Histórico de Bloqueios
bloqueiosUsuarios: defineTable({
usuarioId: v.id("usuarios"),
motivo: v.string(),
bloqueadoPor: v.id("usuarios"), // ID do TI_MASTER que bloqueou
dataInicio: v.number(),
dataFim: v.optional(v.number()), // quando foi desbloqueado
desbloqueadoPor: v.optional(v.id("usuarios")),
ativo: v.boolean(), // se é o bloqueio atual ativo
})
.index("by_usuario", ["usuarioId"])
.index("by_bloqueado_por", ["bloqueadoPor"])
.index("by_ativo", ["ativo"])
.index("by_data_inicio", ["dataInicio"]),
// Perfis Customizados
// Templates de Mensagens
templatesMensagens: defineTable({
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
nome: v.string(),
tipo: v.union(
v.literal("sistema"), // predefinido, não editável
v.literal("customizado") // criado por TI_MASTER
),
titulo: v.string(),
corpo: v.string(), // pode ter variáveis {{variavel}}
variaveis: v.optional(v.array(v.string())), // ["motivo", "senha", etc.]
criadoPor: v.optional(v.id("usuarios")),
criadoEm: v.number(),
})
.index("by_codigo", ["codigo"])
.index("by_tipo", ["tipo"])
.index("by_criado_por", ["criadoPor"]),
// Configuração de Email/SMTP
configuracaoEmail: defineTable({
servidor: v.string(), // smtp.gmail.com
porta: v.number(), // 587, 465, etc.
usuario: v.string(),
senhaHash: v.string(), // senha criptografada reversível (AES-GCM) - necessário para descriptografar e usar no SMTP
emailRemetente: v.string(),
nomeRemetente: v.string(),
usarSSL: v.boolean(),
usarTLS: v.boolean(),
ativo: v.boolean(),
testadoEm: v.optional(v.number()),
configuradoPor: v.id("usuarios"),
atualizadoEm: v.number(),
}).index("by_ativo", ["ativo"]),
// Fila de Emails
notificacoesEmail: defineTable({
destinatario: v.string(), // email
destinatarioId: v.optional(v.id("usuarios")),
assunto: v.string(),
corpo: v.string(), // HTML ou texto
templateId: v.optional(v.id("templatesMensagens")),
status: v.union(
v.literal("pendente"),
v.literal("enviando"),
v.literal("enviado"),
v.literal("falha")
),
tentativas: v.number(),
ultimaTentativa: v.optional(v.number()),
erroDetalhes: v.optional(v.string()),
enviadoPor: v.id("usuarios"),
criadoEm: v.number(),
enviadoEm: v.optional(v.number()),
agendadaPara: v.optional(v.number()), // timestamp para agendamento
})
.index("by_status", ["status"])
.index("by_destinatario", ["destinatarioId"])
.index("by_enviado_por", ["enviadoPor"])
.index("by_criado_em", ["criadoEm"])
.index("by_agendamento", ["agendadaPara"]),
configuracaoAcesso: defineTable({
chave: v.string(), // "sessao_duracao", "max_tentativas_login", etc.
valor: v.string(),
descricao: v.string(),
}).index("by_chave", ["chave"]),
// Rate Limiting de Emails
rateLimitEmails: defineTable({
remetenteId: v.id("usuarios"),
timestamp: v.number(),
contador: v.number(), // quantidade de emails enviados neste período
periodo: v.union(
v.literal("minuto"), // último minuto
v.literal("hora") // última hora
),
})
.index("by_remetente_periodo", ["remetenteId", "periodo", "timestamp"])
.index("by_timestamp", ["timestamp"]),
// Sistema de Chat
conversas: defineTable({
tipo: v.union(
v.literal("individual"),
v.literal("grupo"),
v.literal("sala_reuniao")
),
nome: v.optional(v.string()), // nome do grupo/sala
avatar: v.optional(v.string()), // avatar do grupo/sala
participantes: v.array(v.id("usuarios")), // IDs dos participantes
administradores: v.optional(v.array(v.id("usuarios"))), // IDs dos administradores (apenas para sala_reuniao)
ultimaMensagem: v.optional(v.string()),
ultimaMensagemTimestamp: v.optional(v.number()),
ultimaMensagemRemetenteId: v.optional(v.id("usuarios")), // ID do remetente da última mensagem
criadoPor: v.id("usuarios"),
criadoEm: v.number(),
})
.index("by_criado_por", ["criadoPor"])
.index("by_tipo", ["tipo"])
.index("by_ultima_mensagem", ["ultimaMensagemTimestamp"]),
mensagens: defineTable({
conversaId: v.id("conversas"),
remetenteId: v.id("usuarios"),
tipo: v.union(
v.literal("texto"),
v.literal("arquivo"),
v.literal("imagem")
),
conteudo: v.string(), // texto ou nome do arquivo
conteudoBusca: v.optional(v.string()), // versão normalizada para busca
arquivoId: v.optional(v.id("_storage")),
arquivoNome: v.optional(v.string()),
arquivoTamanho: v.optional(v.number()),
arquivoTipo: v.optional(v.string()),
linkPreview: v.optional(
v.object({
url: v.string(),
titulo: v.optional(v.string()),
descricao: v.optional(v.string()),
imagem: v.optional(v.string()),
site: v.optional(v.string()),
})
),
reagiuPor: v.optional(
v.array(
v.object({
usuarioId: v.id("usuarios"),
emoji: v.string(),
})
)
),
mencoes: v.optional(v.array(v.id("usuarios"))),
respostaPara: v.optional(v.id("mensagens")), // ID da mensagem que está respondendo
agendadaPara: v.optional(v.number()), // timestamp
enviadaEm: v.number(),
editadaEm: v.optional(v.number()),
deletada: v.optional(v.boolean()),
lidaPor: v.optional(v.array(v.id("usuarios"))), // IDs dos usuários que leram a mensagem
})
.index("by_conversa", ["conversaId", "enviadaEm"])
.index("by_remetente", ["remetenteId"])
.index("by_agendamento", ["agendadaPara"])
.index("by_resposta", ["respostaPara"]),
leituras: defineTable({
conversaId: v.id("conversas"),
usuarioId: v.id("usuarios"),
ultimaMensagemLida: v.id("mensagens"),
lidaEm: v.number(),
})
.index("by_conversa_usuario", ["conversaId", "usuarioId"])
.index("by_usuario", ["usuarioId"]),
notificacoes: defineTable({
usuarioId: v.id("usuarios"),
tipo: v.union(
v.literal("nova_mensagem"),
v.literal("mencao"),
v.literal("grupo_criado"),
v.literal("adicionado_grupo"),
v.literal("alerta_seguranca")
),
conversaId: v.optional(v.id("conversas")),
mensagemId: v.optional(v.id("mensagens")),
remetenteId: v.optional(v.id("usuarios")),
titulo: v.string(),
descricao: v.string(),
lida: v.boolean(),
criadaEm: v.number(),
})
.index("by_usuario", ["usuarioId", "lida", "criadaEm"])
.index("by_usuario_lida", ["usuarioId", "lida"]),
digitando: defineTable({
conversaId: v.id("conversas"),
usuarioId: v.id("usuarios"),
iniciouEm: v.number(),
})
.index("by_conversa", ["conversaId", "iniciouEm"])
.index("by_usuario", ["usuarioId"]),
// Push Notifications
pushSubscriptions: defineTable({
usuarioId: v.id("usuarios"),
endpoint: v.string(), // URL do serviço de push
keys: v.object({
p256dh: v.string(), // Chave pública
auth: v.string(), // Chave de autenticação
}),
userAgent: v.optional(v.string()),
criadoEm: v.number(),
ultimaAtividade: v.number(),
ativo: v.boolean(),
})
.index("by_usuario", ["usuarioId", "ativo"])
.index("by_endpoint", ["endpoint"]),
// Preferências de Notificação por Conversa
preferenciasNotificacaoConversa: defineTable({
usuarioId: v.id("usuarios"),
conversaId: v.id("conversas"),
pushAtivado: v.boolean(), // Receber push notifications
emailAtivado: v.boolean(), // Receber emails quando offline
somAtivado: v.boolean(), // Tocar som
silenciado: v.boolean(), // Silenciar completamente
apenasMencoes: v.boolean(), // Notificar apenas quando mencionado
criadoEm: v.number(),
atualizadoEm: v.number(),
})
.index("by_usuario_conversa", ["usuarioId", "conversaId"])
.index("by_conversa", ["conversaId"]),
// Tabelas de Monitoramento do Sistema
systemMetrics: defineTable({
timestamp: v.number(),
// Métricas de Sistema
cpuUsage: v.optional(v.number()),
memoryUsage: v.optional(v.number()),
networkLatency: v.optional(v.number()),
storageUsed: v.optional(v.number()),
// Métricas de Aplicação
usuariosOnline: v.optional(v.number()),
mensagensPorMinuto: v.optional(v.number()),
tempoRespostaMedio: v.optional(v.number()),
errosCount: v.optional(v.number()),
}).index("by_timestamp", ["timestamp"]),
alertConfigurations: defineTable({
metricName: v.string(),
threshold: v.number(),
operator: v.union(
v.literal(">"),
v.literal("<"),
v.literal(">="),
v.literal("<="),
v.literal("==")
),
enabled: v.boolean(),
notifyByEmail: v.boolean(),
notifyByChat: v.boolean(),
createdBy: v.id("usuarios"),
lastModified: v.number(),
}).index("by_enabled", ["enabled"]),
alertHistory: defineTable({
configId: v.id("alertConfigurations"),
metricName: v.string(),
metricValue: v.number(),
threshold: v.number(),
timestamp: v.number(),
status: v.union(v.literal("triggered"), v.literal("resolved")),
notificationsSent: v.object({
email: v.boolean(),
chat: v.boolean(),
}),
})
.index("by_timestamp", ["timestamp"])
.index("by_status", ["status"])
.index("by_config", ["configId", "timestamp"]),
tickets: defineTable({
numero: v.string(),
titulo: v.string(),
descricao: v.string(),
tipo: v.union(
v.literal("reclamacao"),
v.literal("elogio"),
v.literal("sugestao"),
v.literal("chamado")
),
categoria: v.optional(v.string()),
status: v.union(
v.literal("aberto"),
v.literal("em_andamento"),
v.literal("aguardando_usuario"),
v.literal("resolvido"),
v.literal("encerrado"),
v.literal("cancelado")
),
prioridade: v.union(
v.literal("baixa"),
v.literal("media"),
v.literal("alta"),
v.literal("critica")
),
solicitanteId: v.id("usuarios"),
solicitanteNome: v.string(),
solicitanteEmail: v.string(),
responsavelId: v.optional(v.id("usuarios")),
setorResponsavel: v.optional(v.string()),
slaConfigId: v.optional(v.id("slaConfigs")),
conversaId: v.optional(v.id("conversas")),
prazoResposta: v.optional(v.number()),
prazoConclusao: v.optional(v.number()),
prazoEncerramento: v.optional(v.number()),
timeline: v.optional(
v.array(
v.object({
etapa: v.string(),
status: v.union(
v.literal("pendente"),
v.literal("em_andamento"),
v.literal("concluido"),
v.literal("vencido")
),
prazo: v.optional(v.number()),
concluidoEm: v.optional(v.number()),
observacao: v.optional(v.string()),
})
)
),
alertasEmitidos: v.optional(
v.array(
v.object({
tipo: v.union(
v.literal("resposta"),
v.literal("conclusao"),
v.literal("encerramento")
),
emitidoEm: v.number(),
})
)
),
anexos: v.optional(
v.array(
v.object({
arquivoId: v.id("_storage"),
nome: v.optional(v.string()),
tipo: v.optional(v.string()),
tamanho: v.optional(v.number()),
})
)
),
tags: v.optional(v.array(v.string())),
canalOrigem: v.optional(v.string()),
ultimaInteracaoEm: v.number(),
criadoEm: v.number(),
atualizadoEm: v.number(),
})
.index("by_numero", ["numero"])
.index("by_status", ["status"])
.index("by_solicitante", ["solicitanteId", "status"])
.index("by_responsavel", ["responsavelId", "status"])
.index("by_setor", ["setorResponsavel", "status"]),
ticketInteractions: defineTable({
ticketId: v.id("tickets"),
autorId: v.optional(v.id("usuarios")),
origem: v.union(
v.literal("usuario"),
v.literal("ti"),
v.literal("sistema")
),
tipo: v.union(
v.literal("mensagem"),
v.literal("status"),
v.literal("anexo"),
v.literal("alerta")
),
conteudo: v.string(),
anexos: v.optional(
v.array(
v.object({
arquivoId: v.id("_storage"),
nome: v.optional(v.string()),
tipo: v.optional(v.string()),
tamanho: v.optional(v.number()),
})
)
),
statusAnterior: v.optional(
v.union(
v.literal("aberto"),
v.literal("em_andamento"),
v.literal("aguardando_usuario"),
v.literal("resolvido"),
v.literal("encerrado"),
v.literal("cancelado")
)
),
statusNovo: v.optional(
v.union(
v.literal("aberto"),
v.literal("em_andamento"),
v.literal("aguardando_usuario"),
v.literal("resolvido"),
v.literal("encerrado"),
v.literal("cancelado")
)
),
visibilidade: v.union(
v.literal("publico"),
v.literal("interno")
),
criadoEm: v.number(),
})
.index("by_ticket", ["ticketId"])
.index("by_ticket_type", ["ticketId", "tipo"])
.index("by_autor", ["autorId"]),
slaConfigs: defineTable({
nome: v.string(),
descricao: v.optional(v.string()),
prioridade: v.optional(
v.union(
v.literal("baixa"),
v.literal("media"),
v.literal("alta"),
v.literal("critica")
)
),
tempoRespostaHoras: v.number(),
tempoConclusaoHoras: v.number(),
tempoEncerramentoHoras: v.optional(v.number()),
alertaAntecedenciaHoras: v.number(),
ativo: v.boolean(),
criadoPor: v.id("usuarios"),
atualizadoPor: v.optional(v.id("usuarios")),
criadoEm: v.number(),
atualizadoEm: v.number(),
})
.index("by_ativo", ["ativo"])
.index("by_prioridade", ["prioridade", "ativo"])
.index("by_nome", ["nome"]),
ticketAssignments: defineTable({
ticketId: v.id("tickets"),
responsavelId: v.id("usuarios"),
atribuidoPor: v.id("usuarios"),
motivo: v.optional(v.string()),
ativo: v.boolean(),
criadoEm: v.number(),
encerradoEm: v.optional(v.number()),
})
.index("by_ticket", ["ticketId", "ativo"])
.index("by_responsavel", ["responsavelId", "ativo"]),
// Sistema de Segurança Cibernética
networkSensors: defineTable({
nome: v.string(),
tipo: sensorSegurancaTipo,
status: sensorSegurancaStatus,
escopo: v.optional(v.string()),
ipMonitorado: v.optional(v.string()),
hostname: v.optional(v.string()),
regioes: v.optional(v.array(v.string())),
portasMonitoradas: v.optional(v.array(v.number())),
protocolos: v.optional(v.array(v.string())),
capacidades: v.optional(v.array(v.string())),
ultimaSincronizacao: v.number(),
ultimoHeartbeat: v.optional(v.number()),
latenciaMs: v.optional(v.number()),
errosConsecutivos: v.optional(v.number()),
agenteVersao: v.optional(v.string()),
notas: v.optional(v.string()),
})
.index("by_tipo", ["tipo"])
.index("by_status", ["status"])
.index("by_hostname", ["hostname"]),
ipReputation: defineTable({
indicador: v.string(),
categoria: v.union(
v.literal("ip"),
v.literal("dominio"),
v.literal("hash"),
v.literal("email")
),
reputacao: v.number(), // -100 (malicioso) até 100 (confiável)
severidadeMax: severidadeSeguranca,
whitelist: v.boolean(),
blacklist: v.boolean(),
ocorrencias: v.number(),
primeiroRegistro: v.number(),
ultimoRegistro: v.number(),
bloqueadoAte: v.optional(v.number()),
origem: v.optional(v.string()),
comentarios: v.optional(v.string()),
classificacoes: v.optional(v.array(v.string())),
ultimaAcaoId: v.optional(v.id("incidentActions")),
})
.index("by_indicador", ["indicador"])
.index("by_reputacao", ["reputacao"])
.index("by_blacklist", ["blacklist"])
.index("by_whitelist", ["whitelist"]),
portRules: defineTable({
porta: v.number(),
protocolo: v.union(
v.literal("tcp"),
v.literal("udp"),
v.literal("icmp"),
v.literal("quic"),
v.literal("any")
),
acao: v.union(
v.literal("permitir"),
v.literal("bloquear"),
v.literal("monitorar"),
v.literal("rate_limit")
),
temporario: v.boolean(),
severidadeMin: severidadeSeguranca,
duracaoSegundos: v.optional(v.number()),
expiraEm: v.optional(v.number()),
criadoPor: v.id("usuarios"),
atualizadoPor: v.optional(v.id("usuarios")),
criadoEm: v.number(),
atualizadoEm: v.number(),
notas: v.optional(v.string()),
tags: v.optional(v.array(v.string())),
listaReferencia: v.optional(v.id("ipReputation")),
})
.index("by_porta_protocolo", ["porta", "protocolo"])
.index("by_acao", ["acao"])
.index("by_expiracao", ["expiraEm"]),
threatIntelFeeds: defineTable({
nomeFonte: v.string(),
tipo: threatIntelTipo,
formato: threatIntelFormato,
url: v.optional(v.string()),
ativo: v.boolean(),
prioridade: v.union(
v.literal("baixa"),
v.literal("media"),
v.literal("alta"),
v.literal("critica")
),
ultimaSincronizacao: v.optional(v.number()),
entradasProcessadas: v.optional(v.number()),
errosConsecutivos: v.optional(v.number()),
autenticacaoNecessaria: v.optional(v.boolean()),
configuracao: v.optional(
v.object({
tokenId: v.optional(v.id("_storage")),
escopo: v.optional(v.string()),
})
),
criadoPor: v.id("usuarios"),
atualizadoPor: v.optional(v.id("usuarios")),
criadoEm: v.number(),
atualizadoEm: v.number(),
})
.index("by_tipo", ["tipo"])
.index("by_ativo", ["ativo"])
.index("by_prioridade", ["prioridade"]),
securityEvents: defineTable({
referencia: v.string(),
timestamp: v.number(),
tipoAtaque: ataqueCiberneticoTipo,
severidade: severidadeSeguranca,
status: statusEventoSeguranca,
descricao: v.string(),
origemIp: v.optional(v.string()),
origemRegiao: v.optional(v.string()),
origemAsn: v.optional(v.string()),
destinoIp: v.optional(v.string()),
destinoPorta: v.optional(v.number()),
protocolo: v.optional(v.string()),
transporte: v.optional(v.string()),
sensorId: v.optional(v.id("networkSensors")),
detectadoPor: v.optional(v.string()),
mitreTechnique: v.optional(v.string()),
geolocalizacao: v.optional(
v.object({
pais: v.optional(v.string()),
regiao: v.optional(v.string()),
cidade: v.optional(v.string()),
latitude: v.optional(v.number()),
longitude: v.optional(v.number()),
})
),
fingerprint: v.optional(
v.object({
userAgent: v.optional(v.string()),
deviceId: v.optional(v.string()),
ja3: v.optional(v.string()),
tlsVersion: v.optional(v.string()),
})
),
indicadores: v.optional(
v.array(
v.object({
tipo: v.string(),
valor: v.string(),
confianca: v.optional(v.number()),
})
)
),
metricas: v.optional(
v.object({
pps: v.optional(v.number()),
bps: v.optional(v.number()),
rpm: v.optional(v.number()),
errosPorSegundo: v.optional(v.number()),
hostsAfetados: v.optional(v.number()),
})
),
correlacoes: v.optional(v.array(v.id("securityEvents"))),
referenciasExternas: v.optional(v.array(v.string())),
tags: v.optional(v.array(v.string())),
criadoPor: v.optional(v.id("usuarios")),
atualizadoEm: v.number(),
})
.index("by_referencia", ["referencia"])
.index("by_timestamp", ["timestamp"])
.index("by_tipo", ["tipoAtaque", "timestamp"])
.index("by_severidade", ["severidade", "timestamp"])
.index("by_status", ["status", "timestamp"]),
incidentActions: defineTable({
eventoId: v.id("securityEvents"),
tipo: acaoIncidenteTipo,
origem: v.union(v.literal("automatico"), v.literal("manual")),
status: acaoIncidenteStatus,
executadoPor: v.optional(v.id("usuarios")),
detalhes: v.optional(v.string()),
resultado: v.optional(v.string()),
relacionadoA: v.optional(v.id("ipReputation")),
criadoEm: v.number(),
atualizadoEm: v.number(),
})
.index("by_evento", ["eventoId", "status"])
.index("by_tipo", ["tipo", "status"]),
reportRequests: defineTable({
solicitanteId: v.id("usuarios"),
filtros: v.object({
dataInicio: v.number(),
dataFim: v.number(),
severidades: v.optional(v.array(severidadeSeguranca)),
tiposAtaque: v.optional(v.array(ataqueCiberneticoTipo)),
incluirIndicadores: v.optional(v.boolean()),
incluirMetricas: v.optional(v.boolean()),
incluirAcoes: v.optional(v.boolean()),
}),
status: reportStatus,
resultadoId: v.optional(v.id("_storage")),
observacoes: v.optional(v.string()),
criadoEm: v.number(),
atualizadoEm: v.number(),
concluidoEm: v.optional(v.number()),
erro: v.optional(v.string()),
})
.index("by_status", ["status"])
.index("by_solicitante", ["solicitanteId", "status"])
.index("by_criado_em", ["criadoEm"]),
rateLimitConfig: defineTable({
nome: v.string(),
tipo: v.union(
v.literal("ip"),
v.literal("usuario"),
v.literal("endpoint"),
v.literal("global")
),
identificador: v.optional(v.string()),
limite: v.number(),
janelaSegundos: v.number(),
estrategia: v.union(
v.literal("fixed_window"),
v.literal("sliding_window"),
v.literal("token_bucket")
),
acaoExcedido: v.union(
v.literal("bloquear"),
v.literal("throttle"),
v.literal("alertar")
),
bloqueioTemporarioSegundos: v.optional(v.number()),
ativo: v.boolean(),
prioridade: v.number(),
criadoPor: v.id("usuarios"),
atualizadoPor: v.optional(v.id("usuarios")),
criadoEm: v.number(),
atualizadoEm: v.number(),
notas: v.optional(v.string()),
tags: v.optional(v.array(v.string()))
})
.index("by_tipo_identificador", ["tipo", "identificador"])
.index("by_ativo", ["ativo"])
.index("by_prioridade", ["prioridade"])
,
alertConfigs: defineTable({
nome: v.string(),
canais: v.object({
email: v.boolean(),
chat: v.boolean(),
}),
emails: v.array(v.string()),
chatUsers: v.array(v.string()),
severidadeMin: severidadeSeguranca,
tiposAtaque: v.optional(v.array(ataqueCiberneticoTipo)),
reenvioMin: v.number(),
criadoPor: v.id("usuarios"),
criadoEm: v.number(),
atualizadoEm: v.number(),
})
.index("by_criadoEm", ["criadoEm"])
});