import { v } from 'convex/values'; import type { Id } from './_generated/dataModel'; import { internalMutation, mutation, query } from './_generated/server'; import { getCurrentUserFunction } from './auth'; /** * Listar todas as roles */ export const listar = query({ args: {}, handler: async (ctx) => { return await ctx.db.query('roles').collect(); } }); /** * Buscar role por ID */ export const buscarPorId = query({ args: { roleId: v.id('roles') }, returns: v.union( v.object({ _id: v.id('roles'), nome: v.string(), descricao: v.string(), admin: v.optional(v.boolean()) }), v.null() ), handler: async (ctx, args) => { return await ctx.db.get(args.roleId); } }); const slugify = (value: string) => value .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, '_') .replace(/^_+|_+$/g, '') .replace(/_{2,}/g, '_'); export const criar = mutation({ args: { nome: v.string(), descricao: v.string(), copiarDeRoleId: v.optional(v.id('roles')) }, returns: v.union( v.object({ sucesso: v.literal(true), roleId: v.id('roles') }), 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 nomeNormalizado = slugify(args.nome); if (!nomeNormalizado) { return { sucesso: false as const, erro: 'nome_invalido' }; } 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' }; } let permissoesParaCopiar: Array> = []; if (args.copiarDeRoleId) { const roleOrigem = await ctx.db.get(args.copiarDeRoleId); if (!roleOrigem) { return { sucesso: false as const, erro: 'role_origem_nao_encontrada' }; } const permissoesOrigem = await ctx.db .query('rolePermissoes') .withIndex('by_role', (q) => q.eq('roleId', args.copiarDeRoleId!)) .collect(); permissoesParaCopiar = permissoesOrigem.map((item) => item.permissaoId); } // 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(), admin: false }); if (permissoesParaCopiar.length > 0) { for (const permissaoId of permissoesParaCopiar) { await ctx.db.insert('rolePermissoes', { roleId, permissaoId }); } } return { sucesso: true as const, roleId }; } }); /** * Editar uma role existente * Apenas admins podem editar roles */ 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) { // 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, { admin: isAdmin }); } return null; } });