import { v } from 'convex/values'; import { mutation, query } from './_generated/server'; /** * Listar logs de acesso com filtros */ export const listar = query({ args: { usuarioId: v.optional(v.id('usuarios')), tipo: v.optional( v.union( v.literal('login'), v.literal('logout'), v.literal('acesso_negado'), v.literal('senha_alterada'), v.literal('sessao_expirada') ) ), dataInicio: v.optional(v.number()), dataFim: v.optional(v.number()), limite: v.optional(v.number()) }, returns: v.array( v.object({ _id: v.id('logsAcesso'), 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(), usuario: v.optional( v.object({ _id: v.id('usuarios'), matricula: v.string(), nome: v.string() }) ) }) ), handler: async (ctx, args) => { let logs; // Filtrar por usuário if (args.usuarioId !== undefined) { const usuarioId = args.usuarioId; // TypeScript agora sabe que não é undefined logs = await ctx.db .query('logsAcesso') .withIndex('by_usuario', (q) => q.eq('usuarioId', usuarioId)) .collect(); } else { logs = await ctx.db.query('logsAcesso').withIndex('by_timestamp').collect(); } // Filtrar por tipo if (args.tipo) { logs = logs.filter((log) => log.tipo === args.tipo); } // Filtrar por data if (args.dataInicio) { logs = logs.filter((log) => log.timestamp >= args.dataInicio!); } if (args.dataFim) { logs = logs.filter((log) => log.timestamp <= args.dataFim!); } // Ordenar por timestamp decrescente logs.sort((a, b) => b.timestamp - a.timestamp); // Limitar resultados if (args.limite) { logs = logs.slice(0, args.limite); } // Buscar informações dos usuários const resultado = []; for (const log of logs) { let usuario; if (log.usuarioId) { const user = await ctx.db.get(log.usuarioId); if (user) { let matricula: string | undefined; if (user.funcionarioId) { const funcionario = await ctx.db.get(user.funcionarioId); matricula = funcionario?.matricula; } usuario = { _id: user._id, matricula: matricula || '', nome: user.nome }; } } resultado.push({ _id: log._id, tipo: log.tipo, ipAddress: log.ipAddress, userAgent: log.userAgent, detalhes: log.detalhes, timestamp: log.timestamp, usuario }); } return resultado; } }); /** * Obter estatísticas de acessos */ export const estatisticas = query({ args: { dataInicio: v.optional(v.number()), dataFim: v.optional(v.number()) }, returns: v.object({ totalLogins: v.number(), totalLogouts: v.number(), totalAcessosNegados: v.number(), totalSenhasAlteradas: v.number(), totalSessoesExpiradas: v.number(), loginsPorDia: v.array( v.object({ data: v.string(), quantidade: v.number() }) ) }), handler: async (ctx, args) => { let logs = await ctx.db.query('logsAcesso').collect(); // Filtrar por data if (args.dataInicio) { logs = logs.filter((log) => log.timestamp >= args.dataInicio!); } if (args.dataFim) { logs = logs.filter((log) => log.timestamp <= args.dataFim!); } // Contar por tipo const totalLogins = logs.filter((log) => log.tipo === 'login').length; const totalLogouts = logs.filter((log) => log.tipo === 'logout').length; const totalAcessosNegados = logs.filter((log) => log.tipo === 'acesso_negado').length; const totalSenhasAlteradas = logs.filter((log) => log.tipo === 'senha_alterada').length; const totalSessoesExpiradas = logs.filter((log) => log.tipo === 'sessao_expirada').length; // Agrupar logins por dia const loginsPorDiaMap = new Map(); const loginsOnly = logs.filter((log) => log.tipo === 'login'); for (const log of loginsOnly) { const data = new Date(log.timestamp).toISOString().split('T')[0]; loginsPorDiaMap.set(data, (loginsPorDiaMap.get(data) || 0) + 1); } const loginsPorDia = Array.from(loginsPorDiaMap.entries()) .map(([data, quantidade]) => ({ data, quantidade })) .sort((a, b) => a.data.localeCompare(b.data)); return { totalLogins, totalLogouts, totalAcessosNegados, totalSenhasAlteradas, totalSessoesExpiradas, loginsPorDia }; } }); /** * Limpar logs antigos (apenas TI) */ export const limpar = mutation({ args: { dataLimite: v.number() // Excluir logs anteriores a esta data }, returns: v.object({ excluidos: v.number() }), handler: async (ctx, args) => { const logs = await ctx.db.query('logsAcesso').withIndex('by_timestamp').collect(); const logsAntigos = logs.filter((log) => log.timestamp < args.dataLimite); for (const log of logsAntigos) { await ctx.db.delete(log._id); } return { excluidos: logsAntigos.length }; } }); /** * Limpar todos os logs (apenas TI) */ export const limparTodos = mutation({ args: {}, returns: v.object({ excluidos: v.number() }), handler: async (ctx) => { const logs = await ctx.db.query('logsAcesso').collect(); for (const log of logs) { await ctx.db.delete(log._id); } return { excluidos: logs.length }; } });