685 lines
16 KiB
TypeScript
685 lines
16 KiB
TypeScript
import { query, mutation, internalQuery } from './_generated/server';
|
||
import { v } from 'convex/values';
|
||
import type { Doc } from './_generated/dataModel';
|
||
import { getCurrentUserFunction } from './auth';
|
||
|
||
// Catálogo de permissões base para seed controlado via mutation
|
||
const PERMISSOES_BASE = {
|
||
permissoes: [
|
||
// Funcionários
|
||
{
|
||
nome: 'funcionarios.dashboard',
|
||
recurso: 'funcionarios',
|
||
acao: 'dashboard',
|
||
descricao: 'Acessar o painel de funcionários'
|
||
},
|
||
{
|
||
nome: 'funcionarios.ver',
|
||
recurso: 'funcionarios',
|
||
acao: 'ver',
|
||
descricao: 'Visualizar detalhes de funcionários'
|
||
},
|
||
{
|
||
nome: 'funcionarios.listar',
|
||
recurso: 'funcionarios',
|
||
acao: 'listar',
|
||
descricao: 'Listar funcionários'
|
||
},
|
||
{
|
||
nome: 'funcionarios.criar',
|
||
recurso: 'funcionarios',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos funcionários'
|
||
},
|
||
{
|
||
nome: 'funcionarios.editar',
|
||
recurso: 'funcionarios',
|
||
acao: 'editar',
|
||
descricao: 'Editar dados de funcionários'
|
||
},
|
||
{
|
||
nome: 'funcionarios.excluir',
|
||
recurso: 'funcionarios',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir funcionários'
|
||
},
|
||
{
|
||
nome: 'funcionarios.aprovar_ausencias',
|
||
recurso: 'funcionarios',
|
||
acao: 'aprovar_ausencias',
|
||
descricao: 'Aprovar ausências de funcionários'
|
||
},
|
||
{
|
||
nome: 'funcionarios.aprovar_ferias',
|
||
recurso: 'funcionarios',
|
||
acao: 'aprovar_ferias',
|
||
descricao: 'Aprovar férias de funcionários'
|
||
},
|
||
// Símbolos
|
||
{
|
||
nome: 'simbolos.dashboard',
|
||
recurso: 'simbolos',
|
||
acao: 'dashboard',
|
||
descricao: 'Acessar o painel de símbolos'
|
||
},
|
||
{
|
||
nome: 'simbolos.ver',
|
||
recurso: 'simbolos',
|
||
acao: 'ver',
|
||
descricao: 'Visualizar detalhes de símbolos'
|
||
},
|
||
{
|
||
nome: 'simbolos.listar',
|
||
recurso: 'simbolos',
|
||
acao: 'listar',
|
||
descricao: 'Listar símbolos'
|
||
},
|
||
{
|
||
nome: 'simbolos.criar',
|
||
recurso: 'simbolos',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos símbolos'
|
||
},
|
||
{
|
||
nome: 'simbolos.editar',
|
||
recurso: 'simbolos',
|
||
acao: 'editar',
|
||
descricao: 'Editar símbolos'
|
||
},
|
||
{
|
||
nome: 'simbolos.excluir',
|
||
recurso: 'simbolos',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir símbolos'
|
||
},
|
||
// TI - Usuários
|
||
{
|
||
nome: 'ti_usuarios.listar',
|
||
recurso: 'ti_usuarios',
|
||
acao: 'listar',
|
||
descricao: 'Listar usuários do sistema'
|
||
},
|
||
{
|
||
nome: 'ti_usuarios.criar',
|
||
recurso: 'ti_usuarios',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos usuários de acesso'
|
||
},
|
||
{
|
||
nome: 'ti_usuarios.editar',
|
||
recurso: 'ti_usuarios',
|
||
acao: 'editar',
|
||
descricao: 'Editar usuários de acesso'
|
||
},
|
||
{
|
||
nome: 'ti_usuarios.bloquear',
|
||
recurso: 'ti_usuarios',
|
||
acao: 'bloquear',
|
||
descricao: 'Bloquear ou desbloquear usuários'
|
||
},
|
||
// TI - Perfis
|
||
{
|
||
nome: 'ti_perfis.listar',
|
||
recurso: 'ti_perfis',
|
||
acao: 'listar',
|
||
descricao: 'Listar perfis de acesso'
|
||
},
|
||
{
|
||
nome: 'ti_perfis.criar',
|
||
recurso: 'ti_perfis',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos perfis de acesso'
|
||
},
|
||
{
|
||
nome: 'ti_perfis.editar',
|
||
recurso: 'ti_perfis',
|
||
acao: 'editar',
|
||
descricao: 'Editar perfis de acesso'
|
||
},
|
||
// TI - Painel de Permissões
|
||
{
|
||
nome: 'ti_painel_permissoes.gerenciar',
|
||
recurso: 'ti_painel_permissoes',
|
||
acao: 'gerenciar',
|
||
descricao: 'Gerenciar matriz de permissões por perfil'
|
||
},
|
||
// TI - Solicitações de Acesso
|
||
{
|
||
nome: 'ti_solicitacoes_acesso.ver',
|
||
recurso: 'ti_solicitacoes_acesso',
|
||
acao: 'ver',
|
||
descricao: 'Visualizar solicitações de acesso'
|
||
},
|
||
{
|
||
nome: 'ti_solicitacoes_acesso.aprovar',
|
||
recurso: 'ti_solicitacoes_acesso',
|
||
acao: 'aprovar',
|
||
descricao: 'Aprovar solicitações de acesso'
|
||
},
|
||
{
|
||
nome: 'ti_solicitacoes_acesso.reprovar',
|
||
recurso: 'ti_solicitacoes_acesso',
|
||
acao: 'reprovar',
|
||
descricao: 'Reprovar solicitações de acesso'
|
||
},
|
||
// TI - Configurações de E-mail
|
||
{
|
||
nome: 'ti_configuracoes_email.configurar',
|
||
recurso: 'ti_configuracoes_email',
|
||
acao: 'configurar',
|
||
descricao: 'Configurar parâmetros de envio de e-mail'
|
||
},
|
||
// TI - Monitoramento
|
||
{
|
||
nome: 'ti_monitoramento.ver',
|
||
recurso: 'ti_monitoramento',
|
||
acao: 'ver',
|
||
descricao: 'Acessar painel de monitoramento geral'
|
||
},
|
||
{
|
||
nome: 'ti_monitoramento_emails.ver',
|
||
recurso: 'ti_monitoramento_emails',
|
||
acao: 'ver',
|
||
descricao: 'Acessar monitoramento de envio de e-mails'
|
||
},
|
||
// TI - Notificações
|
||
{
|
||
nome: 'ti_notificacoes.configurar',
|
||
recurso: 'ti_notificacoes',
|
||
acao: 'configurar',
|
||
descricao: 'Configurar notificações do sistema'
|
||
},
|
||
// TI - Times
|
||
{
|
||
nome: 'ti_times.gerenciar',
|
||
recurso: 'ti_times',
|
||
acao: 'gerenciar',
|
||
descricao: 'Gerenciar times/equipes de TI'
|
||
},
|
||
// TI - Painel Administrativo
|
||
{
|
||
nome: 'ti_painel_administrativo.ver',
|
||
recurso: 'ti_painel_administrativo',
|
||
acao: 'ver',
|
||
descricao: 'Acessar painel administrativo de TI'
|
||
},
|
||
// Financeiro
|
||
{
|
||
nome: 'financeiro.ver',
|
||
recurso: 'financeiro',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de financeiro'
|
||
},
|
||
// Controladoria
|
||
{
|
||
nome: 'controladoria.ver',
|
||
recurso: 'controladoria',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de controladoria'
|
||
},
|
||
// Licitações
|
||
{
|
||
nome: 'licitacoes.ver',
|
||
recurso: 'licitacoes',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de licitações'
|
||
},
|
||
{
|
||
nome: 'contratos.listar',
|
||
recurso: 'contratos',
|
||
acao: 'listar',
|
||
descricao: 'Listar contratos'
|
||
},
|
||
{
|
||
nome: 'contratos.criar',
|
||
recurso: 'contratos',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos contratos'
|
||
},
|
||
{
|
||
nome: 'contratos.editar',
|
||
recurso: 'contratos',
|
||
acao: 'editar',
|
||
descricao: 'Editar contratos'
|
||
},
|
||
{
|
||
nome: 'contratos.excluir',
|
||
recurso: 'contratos',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir contratos'
|
||
},
|
||
{
|
||
nome: 'contratos.ver',
|
||
recurso: 'contratos',
|
||
acao: 'ver',
|
||
descricao: 'Visualizar detalhes de contratos'
|
||
},
|
||
// Compras
|
||
{
|
||
nome: 'compras.ver',
|
||
recurso: 'compras',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de compras'
|
||
},
|
||
// Jurídico
|
||
{
|
||
nome: 'juridico.ver',
|
||
recurso: 'juridico',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo jurídico'
|
||
},
|
||
// Comunicação
|
||
{
|
||
nome: 'comunicacao.ver',
|
||
recurso: 'comunicacao',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de comunicação'
|
||
},
|
||
// Programas Esportivos
|
||
{
|
||
nome: 'programas_esportivos.ver',
|
||
recurso: 'programas_esportivos',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de programas esportivos'
|
||
},
|
||
// Secretaria Executiva
|
||
{
|
||
nome: 'secretaria_executiva.ver',
|
||
recurso: 'secretaria_executiva',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de secretaria executiva'
|
||
},
|
||
// Gestão de Pessoas
|
||
{
|
||
nome: 'gestao_pessoas.ver',
|
||
recurso: 'gestao_pessoas',
|
||
acao: 'ver',
|
||
descricao: 'Acessar telas do módulo de gestão de pessoas'
|
||
},
|
||
// Setores
|
||
{
|
||
nome: 'setores.listar',
|
||
recurso: 'setores',
|
||
acao: 'listar',
|
||
descricao: 'Listar setores'
|
||
},
|
||
{
|
||
nome: 'setores.criar',
|
||
recurso: 'setores',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos setores'
|
||
},
|
||
{
|
||
nome: 'setores.editar',
|
||
recurso: 'setores',
|
||
acao: 'editar',
|
||
descricao: 'Editar setores'
|
||
},
|
||
{
|
||
nome: 'setores.excluir',
|
||
recurso: 'setores',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir setores'
|
||
},
|
||
// Flow Templates
|
||
{
|
||
nome: 'fluxos.templates.listar',
|
||
recurso: 'fluxos_templates',
|
||
acao: 'listar',
|
||
descricao: 'Listar templates de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.templates.criar',
|
||
recurso: 'fluxos_templates',
|
||
acao: 'criar',
|
||
descricao: 'Criar templates de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.templates.editar',
|
||
recurso: 'fluxos_templates',
|
||
acao: 'editar',
|
||
descricao: 'Editar templates de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.templates.excluir',
|
||
recurso: 'fluxos_templates',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir templates de fluxo'
|
||
},
|
||
// Flow Instances
|
||
{
|
||
nome: 'fluxos.instancias.listar',
|
||
recurso: 'fluxos_instancias',
|
||
acao: 'listar',
|
||
descricao: 'Listar instâncias de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.instancias.criar',
|
||
recurso: 'fluxos_instancias',
|
||
acao: 'criar',
|
||
descricao: 'Criar instâncias de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.instancias.ver',
|
||
recurso: 'fluxos_instancias',
|
||
acao: 'ver',
|
||
descricao: 'Visualizar detalhes de instâncias de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.instancias.atualizar_status',
|
||
recurso: 'fluxos_instancias',
|
||
acao: 'atualizar_status',
|
||
descricao: 'Atualizar status de instâncias de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.instancias.atribuir',
|
||
recurso: 'fluxos_instancias',
|
||
acao: 'atribuir',
|
||
descricao: 'Atribuir responsáveis em instâncias de fluxo'
|
||
},
|
||
// Flow Documents
|
||
{
|
||
nome: 'fluxos.documentos.listar',
|
||
recurso: 'fluxos_documentos',
|
||
acao: 'listar',
|
||
descricao: 'Listar documentos de fluxo'
|
||
},
|
||
{
|
||
nome: 'fluxos.documentos.upload',
|
||
recurso: 'fluxos_documentos',
|
||
acao: 'upload',
|
||
descricao: 'Fazer upload de documentos em fluxos'
|
||
},
|
||
{
|
||
nome: 'fluxos.documentos.excluir',
|
||
recurso: 'fluxos_documentos',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir documentos de fluxos'
|
||
},
|
||
// Pedidos
|
||
{
|
||
nome: 'pedidos.listar',
|
||
recurso: 'pedidos',
|
||
acao: 'listar',
|
||
descricao: 'Listar pedidos'
|
||
},
|
||
{
|
||
nome: 'pedidos.criar',
|
||
recurso: 'pedidos',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos pedidos'
|
||
},
|
||
{
|
||
nome: 'pedidos.ver',
|
||
recurso: 'pedidos',
|
||
acao: 'ver',
|
||
descricao: 'Visualizar detalhes de pedidos'
|
||
},
|
||
{
|
||
nome: 'pedidos.editar_status',
|
||
recurso: 'pedidos',
|
||
acao: 'editar_status',
|
||
descricao: 'Alterar status de pedidos'
|
||
},
|
||
{
|
||
nome: 'pedidos.adicionar_item',
|
||
recurso: 'pedidos',
|
||
acao: 'adicionar_item',
|
||
descricao: 'Adicionar itens ao pedido'
|
||
},
|
||
{
|
||
nome: 'pedidos.remover_item',
|
||
recurso: 'pedidos',
|
||
acao: 'remover_item',
|
||
descricao: 'Remover itens do pedido'
|
||
},
|
||
// Produtos
|
||
{
|
||
nome: 'produtos.listar',
|
||
recurso: 'produtos',
|
||
acao: 'listar',
|
||
descricao: 'Listar produtos'
|
||
},
|
||
{
|
||
nome: 'produtos.criar',
|
||
recurso: 'produtos',
|
||
acao: 'criar',
|
||
descricao: 'Criar novos produtos'
|
||
},
|
||
{
|
||
nome: 'produtos.editar',
|
||
recurso: 'produtos',
|
||
acao: 'editar',
|
||
descricao: 'Editar produtos'
|
||
},
|
||
{
|
||
nome: 'produtos.excluir',
|
||
recurso: 'produtos',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir produtos'
|
||
},
|
||
// Ações
|
||
{
|
||
nome: 'acoes.listar',
|
||
recurso: 'acoes',
|
||
acao: 'listar',
|
||
descricao: 'Listar ações'
|
||
},
|
||
{
|
||
nome: 'acoes.criar',
|
||
recurso: 'acoes',
|
||
acao: 'criar',
|
||
descricao: 'Criar novas ações'
|
||
},
|
||
{
|
||
nome: 'acoes.editar',
|
||
recurso: 'acoes',
|
||
acao: 'editar',
|
||
descricao: 'Editar ações'
|
||
},
|
||
{
|
||
nome: 'acoes.excluir',
|
||
recurso: 'acoes',
|
||
acao: 'excluir',
|
||
descricao: 'Excluir ações'
|
||
},
|
||
// Configuração Compras
|
||
{
|
||
nome: 'config.compras.gerenciar',
|
||
recurso: 'config',
|
||
acao: 'gerenciar_compras',
|
||
descricao: 'Gerenciar configurações de compras'
|
||
}
|
||
]
|
||
} as const;
|
||
|
||
export const listarRecursosEAcoes = query({
|
||
args: {},
|
||
returns: v.array(
|
||
v.object({
|
||
recurso: v.string(),
|
||
acoes: v.array(v.string())
|
||
})
|
||
),
|
||
handler: async (ctx) => {
|
||
const permissoes = await ctx.db.query('permissoes').collect();
|
||
|
||
const recursos: Record<string, Set<string>> = {};
|
||
for (const perm of permissoes) {
|
||
const set = (recursos[perm.recurso] ||= new Set<string>());
|
||
set.add(perm.acao);
|
||
}
|
||
|
||
return Object.entries(recursos).map(([recurso, acoes]) => ({
|
||
recurso,
|
||
acoes: Array.from(acoes).sort()
|
||
}));
|
||
}
|
||
});
|
||
|
||
export const listarPermissoesAcoesPorRole = query({
|
||
args: { roleId: v.id('roles') },
|
||
returns: v.array(
|
||
v.object({
|
||
recurso: v.string(),
|
||
acoes: v.array(v.string())
|
||
})
|
||
),
|
||
handler: async (ctx, args) => {
|
||
// Buscar vínculos permissao<-role
|
||
const rolePerms = await ctx.db
|
||
.query('rolePermissoes')
|
||
.withIndex('by_role', (q) => q.eq('roleId', args.roleId))
|
||
.collect();
|
||
|
||
// Carregar documentos de permissões vinculadas a este role
|
||
const actionsByResource: Record<string, Set<string>> = {};
|
||
for (const rp of rolePerms) {
|
||
const perm = await ctx.db.get(rp.permissaoId);
|
||
if (!perm) continue;
|
||
const set = (actionsByResource[perm.recurso] ||= new Set<string>());
|
||
set.add(perm.acao);
|
||
}
|
||
|
||
return Object.entries(actionsByResource).map(([recurso, acoes]) => ({
|
||
recurso,
|
||
acoes: Array.from(acoes).sort()
|
||
}));
|
||
}
|
||
});
|
||
|
||
export const atualizarPermissaoAcao = mutation({
|
||
args: {
|
||
roleId: v.id('roles'),
|
||
recurso: v.string(),
|
||
acao: v.string(),
|
||
conceder: v.boolean()
|
||
},
|
||
returns: v.null(),
|
||
handler: async (ctx, args) => {
|
||
// Buscar documento de permissão (recurso+acao)
|
||
const permissao = await ctx.db
|
||
.query('permissoes')
|
||
.withIndex('by_recurso_e_acao', (q) => q.eq('recurso', args.recurso).eq('acao', args.acao))
|
||
.first();
|
||
|
||
if (!permissao) return null;
|
||
|
||
// Verificar vínculo atual
|
||
const existente = await ctx.db
|
||
.query('rolePermissoes')
|
||
.withIndex('by_role', (q) => q.eq('roleId', args.roleId))
|
||
.collect();
|
||
|
||
const vinculo = existente.find((rp) => rp.permissaoId === permissao!._id);
|
||
|
||
if (args.conceder) {
|
||
if (!vinculo) {
|
||
await ctx.db.insert('rolePermissoes', {
|
||
roleId: args.roleId,
|
||
permissaoId: permissao._id
|
||
});
|
||
}
|
||
} else {
|
||
if (vinculo) {
|
||
await ctx.db.delete(vinculo._id);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
});
|
||
|
||
export const seedPermissoesBase = mutation({
|
||
args: {},
|
||
returns: v.null(),
|
||
handler: async (ctx) => {
|
||
console.log('🔐 Seed de permissões base...');
|
||
|
||
for (const perm of PERMISSOES_BASE.permissoes) {
|
||
const existente = await ctx.db
|
||
.query('permissoes')
|
||
.withIndex('by_nome', (q) => q.eq('nome', perm.nome))
|
||
.first();
|
||
|
||
if (existente) {
|
||
console.log(` ℹ️ Permissão já existe: ${perm.nome}`);
|
||
continue;
|
||
}
|
||
|
||
await ctx.db.insert('permissoes', {
|
||
nome: perm.nome,
|
||
descricao: perm.descricao,
|
||
recurso: perm.recurso,
|
||
acao: perm.acao
|
||
});
|
||
console.log(` ✅ Permissão criada: ${perm.nome}`);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
});
|
||
|
||
export const verificarAcao = query({
|
||
args: {
|
||
usuarioId: v.id('usuarios'),
|
||
recurso: v.string(),
|
||
acao: v.string()
|
||
},
|
||
returns: v.null(),
|
||
handler: async (ctx, args) => {
|
||
const usuario = await ctx.db.get(args.usuarioId);
|
||
if (!usuario) throw new Error('acesso_negado');
|
||
|
||
const role = await ctx.db.get(usuario.roleId);
|
||
if (!role) throw new Error('acesso_negado');
|
||
|
||
// Níveis administrativos têm acesso total
|
||
if (role.nivel <= 1) return null;
|
||
|
||
// Encontrar permissão
|
||
const permissao = await ctx.db
|
||
.query('permissoes')
|
||
.withIndex('by_recurso_e_acao', (q) => q.eq('recurso', args.recurso).eq('acao', args.acao))
|
||
.first();
|
||
if (!permissao) throw new Error('acesso_negado');
|
||
|
||
const hasLink = await ctx.db
|
||
.query('rolePermissoes')
|
||
.withIndex('by_role', (q) => q.eq('roleId', usuario.roleId))
|
||
.collect();
|
||
const permitido = hasLink.some((rp) => rp.permissaoId === permissao!._id);
|
||
if (!permitido) throw new Error('acesso_negado');
|
||
return null;
|
||
}
|
||
});
|
||
|
||
export const assertPermissaoAcaoAtual = internalQuery({
|
||
args: {
|
||
recurso: v.string(),
|
||
acao: v.string()
|
||
},
|
||
returns: v.null(),
|
||
handler: async (ctx, args) => {
|
||
const usuarioAtual: Doc<'usuarios'> | null = (await getCurrentUserFunction(ctx)) ?? null;
|
||
if (!usuarioAtual) throw new Error('acesso_negado');
|
||
|
||
const role = await ctx.db.get(usuarioAtual.roleId);
|
||
if (!role) throw new Error('acesso_negado');
|
||
if (role.nivel <= 1) return null;
|
||
|
||
const permissao = await ctx.db
|
||
.query('permissoes')
|
||
.withIndex('by_recurso_e_acao', (q) => q.eq('recurso', args.recurso).eq('acao', args.acao))
|
||
.first();
|
||
if (!permissao) throw new Error('acesso_negado');
|
||
|
||
const links = await ctx.db
|
||
.query('rolePermissoes')
|
||
.withIndex('by_role', (q) => q.eq('roleId', role._id))
|
||
.collect();
|
||
const ok = links.some((rp) => rp.permissaoId === permissao!._id);
|
||
if (!ok) throw new Error('acesso_negado');
|
||
return null;
|
||
}
|
||
});
|