import { v } from "convex/values"; import { mutation, query } from "./_generated/server"; import { registrarAtividade } from "./logsAtividades"; import { api } from "./_generated/api"; import { Id } from "./_generated/dataModel"; /** * Listar todos os perfis customizados */ export const listarPerfisCustomizados = query({ args: {}, // Tipo inferido automaticamente pelo Convex handler: async (ctx) => { const perfis = await ctx.db.query("perfisCustomizados").collect(); // Buscar role correspondente para cada perfil const perfisComDetalhes = await Promise.all( perfis.map(async (perfil) => { const role = await ctx.db.get(perfil.roleId); const criador = await ctx.db.get(perfil.criadoPor); // Contar usuários usando este perfil const usuarios = await ctx.db .query("usuarios") .withIndex("by_role", (q) => q.eq("roleId", perfil.roleId)) .collect(); return { ...perfil, roleNome: role?.nome || "Desconhecido", criadorNome: criador?.nome || "Desconhecido", numeroUsuarios: usuarios.length, }; }) ); return perfisComDetalhes; }, }); /** * Obter perfil com permissões detalhadas */ export const obterPerfilComPermissoes = query({ args: { perfilId: v.id("perfisCustomizados"), }, returns: v.union( v.object({ perfil: v.any(), // Doc<"perfisCustomizados"> não pode ser validado diretamente role: v.any(), // Doc<"roles"> não pode ser validado diretamente permissoes: v.array(v.any()), // Doc<"permissoes">[] não pode ser validado diretamente menuPermissoes: v.array(v.any()), // Doc<"menuPermissoes">[] não pode ser validado diretamente usuarios: v.array(v.any()), // Doc<"usuarios">[] não pode ser validado diretamente }), v.null() ), handler: async (ctx, args) => { const perfil = await ctx.db.get(args.perfilId); if (!perfil) { return null; } const role = await ctx.db.get(perfil.roleId); if (!role) { return null; } // Buscar permissões do role const rolePermissoes = await ctx.db .query("rolePermissoes") .withIndex("by_role", (q) => q.eq("roleId", perfil.roleId)) .collect(); const permissoes = await Promise.all( rolePermissoes.map(async (rp) => { return await ctx.db.get(rp.permissaoId); }) ); // Buscar permissões de menu const menuPermissoes = await ctx.db .query("menuPermissoes") .withIndex("by_role", (q) => q.eq("roleId", perfil.roleId)) .collect(); // Buscar usuários usando este perfil const usuarios = await ctx.db .query("usuarios") .withIndex("by_role", (q) => q.eq("roleId", perfil.roleId)) .collect(); return { perfil, role, permissoes: permissoes.filter((p) => p !== null), menuPermissoes, usuarios, }; }, }); /** * Criar perfil customizado (apenas TI_MASTER) */ export const criarPerfilCustomizado = mutation({ args: { nome: v.string(), descricao: v.string(), nivel: v.number(), // >= 3 clonarDeRoleId: v.optional(v.id("roles")), // role para copiar permissões criadoPorId: v.id("usuarios"), }, returns: v.union( v.object({ sucesso: v.literal(true), perfilId: v.id("perfisCustomizados"), }), v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { // Validar nível (deve ser >= 3) if (args.nivel < 3) { return { sucesso: false as const, erro: "Perfis customizados devem ter nível >= 3", }; } // Verificar se nome já existe const roles = await ctx.db.query("roles").collect(); const nomeExiste = roles.some( (r) => r.nome.toLowerCase() === args.nome.toLowerCase() ); if (nomeExiste) { return { sucesso: false as const, erro: "Já existe um perfil com este nome", }; } // Criar role correspondente const roleId = await ctx.db.insert("roles", { nome: args.nome.toLowerCase().replace(/\s+/g, "_"), descricao: args.descricao, nivel: args.nivel, customizado: true, criadoPor: args.criadoPorId, editavel: true, }); // Copiar permissões se especificado if (args.clonarDeRoleId) { // Copiar permissões gerais const permissoesClonar = await ctx.db .query("rolePermissoes") .withIndex("by_role", (q) => q.eq("roleId", args.clonarDeRoleId!)) .collect(); for (const perm of permissoesClonar) { await ctx.db.insert("rolePermissoes", { roleId, permissaoId: perm.permissaoId, }); } // Copiar permissões de menu const menuPermsClonar = await ctx.db .query("menuPermissoes") .withIndex("by_role", (q) => q.eq("roleId", args.clonarDeRoleId!)) .collect(); for (const menuPerm of menuPermsClonar) { await ctx.db.insert("menuPermissoes", { roleId, menuPath: menuPerm.menuPath, podeAcessar: menuPerm.podeAcessar, podeConsultar: menuPerm.podeConsultar, podeGravar: menuPerm.podeGravar, }); } } // Criar perfil customizado const perfilId = await ctx.db.insert("perfisCustomizados", { nome: args.nome, descricao: args.descricao, nivel: args.nivel, roleId, criadoPor: args.criadoPorId, criadoEm: Date.now(), atualizadoEm: Date.now(), }); // Log de atividade await registrarAtividade( ctx, args.criadoPorId, "criar", "perfis", JSON.stringify({ perfilId, nome: args.nome, nivel: args.nivel }), perfilId ); return { sucesso: true as const, perfilId }; }, }); /** * Editar perfil customizado (apenas TI_MASTER) */ export const editarPerfilCustomizado = mutation({ args: { perfilId: v.id("perfisCustomizados"), nome: v.optional(v.string()), descricao: v.optional(v.string()), editadoPorId: v.id("usuarios"), }, returns: v.union( v.object({ sucesso: v.literal(true) }), v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { const perfil = await ctx.db.get(args.perfilId); if (!perfil) { return { sucesso: false as const, erro: "Perfil não encontrado" }; } // Atualizar perfil const updates: Partial> & { atualizadoEm: number } = { atualizadoEm: Date.now(), }; if (args.nome !== undefined) updates.nome = args.nome; if (args.descricao !== undefined) updates.descricao = args.descricao; await ctx.db.patch(args.perfilId, updates); // Atualizar role correspondente se nome mudou if (args.nome !== undefined) { await ctx.db.patch(perfil.roleId, { nome: args.nome.toLowerCase().replace(/\s+/g, "_"), }); } if (args.descricao !== undefined) { await ctx.db.patch(perfil.roleId, { descricao: args.descricao, }); } // Log de atividade await registrarAtividade( ctx, args.editadoPorId, "editar", "perfis", JSON.stringify(updates), args.perfilId ); return { sucesso: true as const }; }, }); /** * Excluir perfil customizado (apenas TI_MASTER) */ export const excluirPerfilCustomizado = mutation({ args: { perfilId: v.id("perfisCustomizados"), excluidoPorId: v.id("usuarios"), }, returns: v.union( v.object({ sucesso: v.literal(true) }), v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { const perfil = await ctx.db.get(args.perfilId); if (!perfil) { return { sucesso: false as const, erro: "Perfil não encontrado" }; } // Verificar se existem usuários usando este perfil const usuarios = await ctx.db .query("usuarios") .withIndex("by_role", (q) => q.eq("roleId", perfil.roleId)) .collect(); if (usuarios.length > 0) { return { sucesso: false as const, erro: `Não é possível excluir. ${usuarios.length} usuário(s) ainda usa(m) este perfil.`, }; } // Remover permissões associadas ao role const rolePermissoes = await ctx.db .query("rolePermissoes") .withIndex("by_role", (q) => q.eq("roleId", perfil.roleId)) .collect(); for (const rp of rolePermissoes) { await ctx.db.delete(rp._id); } // Remover permissões de menu const menuPermissoes = await ctx.db .query("menuPermissoes") .withIndex("by_role", (q) => q.eq("roleId", perfil.roleId)) .collect(); for (const mp of menuPermissoes) { await ctx.db.delete(mp._id); } // Excluir role await ctx.db.delete(perfil.roleId); // Excluir perfil await ctx.db.delete(args.perfilId); // Log de atividade await registrarAtividade( ctx, args.excluidoPorId, "excluir", "perfis", JSON.stringify({ perfilId: args.perfilId, nome: perfil.nome }), args.perfilId ); return { sucesso: true as const }; }, }); /** * Clonar perfil existente */ export const clonarPerfil = mutation({ args: { perfilOrigemId: v.id("perfisCustomizados"), novoNome: v.string(), novaDescricao: v.string(), criadoPorId: v.id("usuarios"), }, returns: v.union( v.object({ sucesso: v.literal(true), perfilId: v.id("perfisCustomizados"), }), v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { const perfilOrigem = await ctx.db.get(args.perfilOrigemId); if (!perfilOrigem) { return { sucesso: false as const, erro: "Perfil origem não encontrado" }; } // Verificar se nome já existe const roles = await ctx.db.query("roles").collect(); const nomeExiste = roles.some( (r) => r.nome.toLowerCase() === args.novoNome.toLowerCase() ); if (nomeExiste) { return { sucesso: false as const, erro: "Já existe um perfil com este nome", }; } // Criar role correspondente const roleId = await ctx.db.insert("roles", { nome: args.novoNome.toLowerCase().replace(/\s+/g, "_"), descricao: args.novaDescricao, nivel: perfilOrigem.nivel, customizado: true, criadoPor: args.criadoPorId, editavel: true, }); // Copiar permissões gerais do perfil de origem const permissoesClonar = await ctx.db .query("rolePermissoes") .withIndex("by_role", (q) => q.eq("roleId", perfilOrigem.roleId)) .collect(); for (const perm of permissoesClonar) { await ctx.db.insert("rolePermissoes", { roleId, permissaoId: perm.permissaoId, }); } // Copiar permissões de menu const menuPermsClonar = await ctx.db .query("menuPermissoes") .withIndex("by_role", (q) => q.eq("roleId", perfilOrigem.roleId)) .collect(); for (const menuPerm of menuPermsClonar) { await ctx.db.insert("menuPermissoes", { roleId, menuPath: menuPerm.menuPath, podeAcessar: menuPerm.podeAcessar, podeConsultar: menuPerm.podeConsultar, podeGravar: menuPerm.podeGravar, }); } // Criar perfil customizado const perfilId = await ctx.db.insert("perfisCustomizados", { nome: args.novoNome, descricao: args.novaDescricao, nivel: perfilOrigem.nivel, roleId, criadoPor: args.criadoPorId, criadoEm: Date.now(), atualizadoEm: Date.now(), }); // Log de atividade await registrarAtividade( ctx, args.criadoPorId, "criar", "perfis", JSON.stringify({ perfilId, nome: args.novoNome, nivel: perfilOrigem.nivel, }), perfilId ); return { sucesso: true as const, perfilId }; }, });