feat: Introduce structured table definitions in convex/tables for various entities and remove the todos example table.

This commit is contained in:
2025-12-02 09:55:07 -03:00
parent 1c0bd219b2
commit 05e7f1181d
30 changed files with 2700 additions and 2535 deletions

View File

@@ -0,0 +1,172 @@
import { defineTable } from 'convex/server';
import { v } from 'convex/values';
export const authTables = {
// 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
fotoPerfil: v.optional(v.id('_storage')),
avatar: v.optional(v.string()), // URL do avatar gerado (ex: DiceBear)
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()),
temaPreferido: v.optional(v.string()) // tema de aparência escolhido pelo usuário
})
.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']),
// 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']),
configuracaoAcesso: defineTable({
chave: v.string(), // "sessao_duracao", "max_tentativas_login", etc.
valor: v.string(),
descricao: v.string()
}).index('by_chave', ['chave']),
// 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"
// Informações de Rede
ipAddress: v.optional(v.string()),
ipPublico: v.optional(v.string()),
ipLocal: v.optional(v.string()),
userAgent: v.optional(v.string()),
device: v.optional(v.string()),
browser: v.optional(v.string()),
sistema: v.optional(v.string()),
// Informações de Localização (por IP)
latitude: v.optional(v.number()),
longitude: v.optional(v.number()),
endereco: v.optional(v.string()),
cidade: v.optional(v.string()),
estado: v.optional(v.string()),
pais: v.optional(v.string()),
// Informações de Localização (GPS do navegador)
latitudeGPS: v.optional(v.number()),
longitudeGPS: v.optional(v.number()),
precisaoGPS: v.optional(v.number()),
enderecoGPS: v.optional(v.string()),
cidadeGPS: v.optional(v.string()),
estadoGPS: v.optional(v.string()),
paisGPS: v.optional(v.string()),
timestamp: v.number()
})
.index('by_usuario', ['usuarioId'])
.index('by_sucesso', ['sucesso'])
.index('by_timestamp', ['timestamp'])
.index('by_ip', ['ipAddress'])
};