437 lines
11 KiB
TypeScript
437 lines
11 KiB
TypeScript
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: {},
|
|
returns: v.array(v.any()),
|
|
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(),
|
|
role: v.any(),
|
|
permissoes: v.array(v.any()),
|
|
menuPermissoes: v.array(v.any()),
|
|
usuarios: v.array(v.any()),
|
|
}),
|
|
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: any = {
|
|
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 as any,
|
|
args.criadoPorId,
|
|
"criar",
|
|
"perfis",
|
|
JSON.stringify({
|
|
perfilId,
|
|
nome: args.novoNome,
|
|
nivel: perfilOrigem.nivel,
|
|
}),
|
|
perfilId
|
|
);
|
|
|
|
return { sucesso: true as const, perfilId };
|
|
},
|
|
});
|