refactor: Remove dedicated role management page and update authentication, roles, and permission handling across backend and frontend.

This commit is contained in:
2025-12-05 14:29:34 -03:00
parent c8d717b315
commit 69f32a342c
16 changed files with 358 additions and 958 deletions

View File

@@ -25,8 +25,7 @@ export const buscarPorId = query({
_id: v.id('roles'),
nome: v.string(),
descricao: v.string(),
nivel: v.number(),
setor: v.optional(v.string())
admin: v.optional(v.boolean())
}),
v.null()
),
@@ -49,8 +48,6 @@ export const criar = mutation({
args: {
nome: v.string(),
descricao: v.string(),
nivel: v.number(),
setor: v.optional(v.string()),
copiarDeRoleId: v.optional(v.id('roles'))
},
returns: v.union(
@@ -64,7 +61,7 @@ export const criar = mutation({
}
const roleAtual = await ctx.db.get(usuarioAtual.roleId);
if (!roleAtual || roleAtual.nivel > 1) {
if (!roleAtual || roleAtual.admin !== true) {
return { sucesso: false as const, erro: 'sem_permissao' };
}
@@ -98,20 +95,12 @@ export const criar = mutation({
permissoesParaCopiar = permissoesOrigem.map((item) => item.permissaoId);
}
// Agora só existem níveis 0 e 1.
// 0 = máximo (acesso total), 1 = administrativo (também com acesso total).
// Qualquer valor informado diferente de 0 é normalizado para 1.
const nivelNormalizado = Math.round(args.nivel) <= 0 ? 0 : 1;
const setor = args.setor?.trim();
// Novos perfis criados NÃO são admin por padrão
// O campo admin só pode ser alterado posteriormente por um admin existente
const roleId = await ctx.db.insert('roles', {
nome: nomeNormalizado,
descricao: args.descricao.trim() || args.nome.trim(),
nivel: nivelNormalizado,
setor: setor && setor.length > 0 ? setor : undefined,
customizado: true,
criadoPor: usuarioAtual._id,
editavel: true
admin: false
});
if (permissoesParaCopiar.length > 0) {
@@ -128,21 +117,140 @@ export const criar = mutation({
});
/**
* Migração de níveis de roles para o novo modelo (apenas 0 e 1).
* - Mantém níveis 0 e 1 como estão.
* - Converte qualquer nível > 1 para 1.
* Editar uma role existente
* Apenas admins podem editar roles
*/
export const migrarNiveisRoles = internalMutation({
export const editar = mutation({
args: {
roleId: v.id('roles'),
nome: v.optional(v.string()),
descricao: v.optional(v.string())
},
returns: v.union(
v.object({ sucesso: v.literal(true) }),
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args) => {
const usuarioAtual = await getCurrentUserFunction(ctx);
if (!usuarioAtual) {
return { sucesso: false as const, erro: 'nao_autenticado' };
}
const roleAtual = await ctx.db.get(usuarioAtual.roleId);
if (!roleAtual || roleAtual.admin !== true) {
return { sucesso: false as const, erro: 'sem_permissao' };
}
const roleParaEditar = await ctx.db.get(args.roleId);
if (!roleParaEditar) {
return { sucesso: false as const, erro: 'role_nao_encontrada' };
}
// Se estiver alterando o nome, verificar se já existe
if (args.nome) {
const nomeNormalizado = slugify(args.nome);
if (!nomeNormalizado) {
return { sucesso: false as const, erro: 'nome_invalido' };
}
if (nomeNormalizado !== roleParaEditar.nome) {
const existente = await ctx.db
.query('roles')
.withIndex('by_nome', (q) => q.eq('nome', nomeNormalizado))
.unique();
if (existente) {
return { sucesso: false as const, erro: 'nome_ja_utilizado' };
}
}
await ctx.db.patch(args.roleId, { nome: nomeNormalizado });
}
if (args.descricao !== undefined) {
await ctx.db.patch(args.roleId, { descricao: args.descricao.trim() });
}
return { sucesso: true as const };
}
});
/**
* Excluir uma role
* Apenas admins podem excluir roles
* Não pode excluir role que tenha usuários vinculados
*/
export const excluir = mutation({
args: {
roleId: v.id('roles')
},
returns: v.union(
v.object({ sucesso: v.literal(true) }),
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args) => {
const usuarioAtual = await getCurrentUserFunction(ctx);
if (!usuarioAtual) {
return { sucesso: false as const, erro: 'nao_autenticado' };
}
const roleAtual = await ctx.db.get(usuarioAtual.roleId);
if (!roleAtual || roleAtual.admin !== true) {
return { sucesso: false as const, erro: 'sem_permissao' };
}
const roleParaExcluir = await ctx.db.get(args.roleId);
if (!roleParaExcluir) {
return { sucesso: false as const, erro: 'role_nao_encontrada' };
}
// Verificar se existem usuários vinculados
const usuariosVinculados = await ctx.db
.query('usuarios')
.withIndex('by_role', (q) => q.eq('roleId', args.roleId))
.first();
if (usuariosVinculados) {
return { sucesso: false as const, erro: 'role_possui_usuarios' };
}
// Excluir permissões vinculadas primeiro
const permissoesVinculadas = await ctx.db
.query('rolePermissoes')
.withIndex('by_role', (q) => q.eq('roleId', args.roleId))
.collect();
for (const permissao of permissoesVinculadas) {
await ctx.db.delete(permissao._id);
}
// Excluir a role
await ctx.db.delete(args.roleId);
return { sucesso: true as const };
}
});
/**
* Migração de roles para o novo modelo com campo admin.
* - Perfis com nivel === 0 tornam-se admin: true
* - Todos os outros tornam-se admin: false
*/
export const migrarParaAdmin = internalMutation({
args: {},
returns: v.null(),
handler: async (ctx) => {
const roles = await ctx.db.query('roles').collect();
for (const role of roles) {
if (role.nivel <= 1) continue;
// Se já tem o campo admin definido, pula
if (role.admin !== undefined) continue;
// Perfis que eram nivel 0 (ti_master, admin) tornam-se admin: true
const isAdmin = (role as unknown as { nivel?: number }).nivel === 0;
await ctx.db.patch(role._id, {
nivel: 1
admin: isAdmin
});
}