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 = undefined; if (log.usuarioId) { const user = await ctx.db.get(log.usuarioId); if (user) { let matricula: string | undefined = 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 }; }, });