import { defineTable } from 'convex/server'; import { v } from 'convex/values'; import { ataqueCiberneticoTipo, severidadeSeguranca } from './security'; export const systemTables = { // Logs de Atividades do Sistema logsAtividades: defineTable({ usuarioId: v.id('usuarios'), acao: v.string(), recurso: v.string(), recursoId: v.optional(v.string()), detalhes: v.optional(v.string()), 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']), // 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']), // 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']), // 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']), 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(), templateCodigo: v.optional(v.string()), // Template a ser usado para email/chat criadoPor: v.id('usuarios'), criadoEm: v.number(), atualizadoEm: v.number() }).index('by_criadoEm', ['criadoEm']), // Configurações Gerais config: defineTable({ comprasSetorId: v.optional(v.id('setores')), financeiroSetorId: v.optional(v.id('setores')), juridicoSetorId: v.optional(v.id('setores')), convenioSetorId: v.optional(v.id('setores')), programasEsportivosSetorId: v.optional(v.id('setores')), comunicacaoSetorId: v.optional(v.id('setores')), tiSetorId: v.optional(v.id('setores')), criadoPor: v.id('usuarios'), atualizadoEm: v.number() }), // 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}} htmlCorpo: v.optional(v.string()), // versão HTML do corpo (com wrapper) variaveis: v.optional(v.array(v.string())), // ["motivo", "senha", etc.] categoria: v.optional(v.union(v.literal('email'), v.literal('chat'), v.literal('ambos'))), // categoria do template tags: v.optional(v.array(v.string())), // tags para organização criadoPor: v.optional(v.id('usuarios')), criadoEm: v.number() }) .index('by_codigo', ['codigo']) .index('by_tipo', ['tipo']) .index('by_criado_por', ['criadoPor']) .index('by_categoria', ['categoria']), // Configuração de Jitsi Meet configuracaoJitsi: defineTable({ domain: v.string(), // Domínio do servidor Jitsi (ex: "localhost:8443" ou "meet.example.com") appId: v.string(), // ID da aplicação Jitsi roomPrefix: v.string(), // Prefixo para nomes de salas useHttps: v.boolean(), // Usar HTTPS acceptSelfSignedCert: v.optional(v.boolean()), // Aceitar certificados autoassinados (útil para desenvolvimento) ambiente: v.optional(v.string()), // Ambiente da configuração (ex: "desenvolvimento", "staging", "producao") jwtSecret: v.optional(v.string()), // JWT Secret criptografado (usado para gerar tokens JWT) jwtAudience: v.optional(v.string()), // Audience do JWT (padrão: o próprio domínio) jwtIssuer: v.optional(v.string()), // Issuer do JWT (padrão: appId) ativo: v.boolean(), // Configuração ativa testadoEm: v.optional(v.number()), // Timestamp do último teste de conexão configuradoEm: v.optional(v.number()), // Timestamp da última configuração do servidor Docker configuradoNoServidor: v.optional(v.boolean()), // Indica se a configuração foi aplicada no servidor configuradoNoServidorEm: v.optional(v.number()), // Timestamp de quando foi configurado no servidor configuradoPor: v.id('usuarios'), // Usuário que configurou atualizadoEm: v.number(), // Timestamp de atualização jitsiConfigPath: v.optional(v.string()), // Caminho da configuração do Jitsi no servidor (ex: "~/.jitsi-meet-cfg") sshUsername: v.optional(v.string()), // Usuário SSH para acesso ao servidor sshPasswordHash: v.optional(v.string()), // Hash da senha SSH (criptografada) sshPort: v.optional(v.number()) // Porta SSH (padrão: 22) }) .index('by_ativo', ['ativo']) .index('by_ambiente', ['ambiente']) .index('by_ativo_ambiente', ['ativo', 'ambiente']), // Logs de Erros do Servidor (500, etc) errosServidor: defineTable({ statusCode: v.number(), // Código HTTP do erro (500, 502, etc) mensagem: v.string(), // Mensagem do erro stack: v.optional(v.string()), // Stack trace do erro url: v.optional(v.string()), // URL onde ocorreu o erro method: v.optional(v.string()), // Método HTTP (GET, POST, etc) ipAddress: v.optional(v.string()), // IP do cliente userAgent: v.optional(v.string()), // User agent do navegador usuarioId: v.optional(v.id('usuarios')), // Usuário autenticado (se houver) notificado: v.boolean(), // Se a equipe técnica já foi notificada notificadoEm: v.optional(v.number()), // Timestamp da notificação criadoEm: v.number() // Timestamp do erro }) .index('by_status_code', ['statusCode']) .index('by_notificado', ['notificado']) .index('by_criado_em', ['criadoEm']) .index('by_usuario', ['usuarioId']), // Configuração de APIs de Busca de Código de Barras configuracaoBuscaCodigoBarras: defineTable({ // GS1 Brasil gs1BrasilClientId: v.optional(v.string()), gs1BrasilClientSecret: v.optional(v.string()), gs1BrasilTokenUrl: v.optional(v.string()), gs1BrasilApiUrl: v.optional(v.string()), gs1BrasilAtivo: v.boolean(), // Bluesoft Cosmo bluesoftApiKey: v.optional(v.string()), bluesoftApiUrl: v.optional(v.string()), bluesoftAtivo: v.boolean(), // Product-Search.net productSearchApiKey: v.optional(v.string()), productSearchApiUrl: v.optional(v.string()), productSearchAtivo: v.boolean(), ativo: v.boolean(), configuradoPor: v.id('usuarios'), atualizadoEm: v.number() }).index('by_ativo', ['ativo']) };