refactor: remove authentication module and integrate Better Auth
- Deleted the `autenticacao.ts` file to streamline the authentication process. - Updated the `auth.ts` file to include new functions for user management and password updates. - Modified the schema to enforce the presence of `authId` for users, ensuring integration with Better Auth. - Refactored the seed process to create users with Better Auth integration, enhancing user management capabilities. - Cleaned up the `usuarios.ts` file to utilize the new authentication functions and improve code clarity.
This commit is contained in:
2
packages/backend/convex/_generated/api.d.ts
vendored
2
packages/backend/convex/_generated/api.d.ts
vendored
@@ -15,7 +15,6 @@ import type * as actions_smtp from "../actions/smtp.js";
|
||||
import type * as actions_utils_nodeCrypto from "../actions/utils/nodeCrypto.js";
|
||||
import type * as atestadosLicencas from "../atestadosLicencas.js";
|
||||
import type * as ausencias from "../ausencias.js";
|
||||
import type * as autenticacao from "../autenticacao.js";
|
||||
import type * as auth_utils from "../auth/utils.js";
|
||||
import type * as auth from "../auth.js";
|
||||
import type * as chat from "../chat.js";
|
||||
@@ -71,7 +70,6 @@ declare const fullApi: ApiFromModules<{
|
||||
"actions/utils/nodeCrypto": typeof actions_utils_nodeCrypto;
|
||||
atestadosLicencas: typeof atestadosLicencas;
|
||||
ausencias: typeof ausencias;
|
||||
autenticacao: typeof autenticacao;
|
||||
"auth/utils": typeof auth_utils;
|
||||
auth: typeof auth;
|
||||
chat: typeof chat;
|
||||
|
||||
@@ -1,834 +0,0 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query, internalMutation } from "./_generated/server";
|
||||
import {
|
||||
hashPassword,
|
||||
verifyPassword,
|
||||
generateToken,
|
||||
validarMatricula,
|
||||
validarSenha,
|
||||
} from "./auth/utils";
|
||||
import { registrarLogin } from "./logsLogin";
|
||||
import { Id, Doc } from "./_generated/dataModel";
|
||||
import type { QueryCtx, MutationCtx } from "./_generated/server";
|
||||
|
||||
/**
|
||||
* Helper para verificar se usuário está bloqueado
|
||||
*/
|
||||
async function verificarBloqueioUsuario(ctx: QueryCtx, usuarioId: Id<"usuarios">) {
|
||||
const bloqueio = await ctx.db
|
||||
.query("bloqueiosUsuarios")
|
||||
.withIndex("by_usuario", (q) => q.eq("usuarioId", usuarioId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
return bloqueio !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper para verificar rate limiting por IP
|
||||
*/
|
||||
async function verificarRateLimitIP(ctx: QueryCtx, ipAddress: string) {
|
||||
// Últimas 15 minutos
|
||||
const dataLimite = Date.now() - 15 * 60 * 1000;
|
||||
|
||||
const tentativas = await ctx.db
|
||||
.query("logsLogin")
|
||||
.withIndex("by_ip", (q) => q.eq("ipAddress", ipAddress))
|
||||
.filter((q) => q.gte(q.field("timestamp"), dataLimite))
|
||||
.collect();
|
||||
|
||||
const falhas = tentativas.filter((t) => !t.sucesso).length;
|
||||
|
||||
// Bloquear se 5 ou mais tentativas falhas em 15 minutos
|
||||
return falhas >= 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login do usuário (aceita matrícula OU email)
|
||||
*/
|
||||
export const login = mutation({
|
||||
args: {
|
||||
matriculaOuEmail: v.string(), // Aceita matrícula ou email
|
||||
senha: v.string(),
|
||||
ipAddress: v.optional(v.string()),
|
||||
userAgent: v.optional(v.string()),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
sucesso: v.literal(true),
|
||||
token: v.string(),
|
||||
usuario: v.object({
|
||||
_id: v.id("usuarios"),
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
funcionarioId: v.optional(v.id("funcionarios")),
|
||||
role: v.object({
|
||||
_id: v.id("roles"),
|
||||
nome: v.string(),
|
||||
nivel: v.number(),
|
||||
setor: v.optional(v.string()),
|
||||
}),
|
||||
primeiroAcesso: v.boolean(),
|
||||
}),
|
||||
}),
|
||||
v.object({
|
||||
sucesso: v.literal(false),
|
||||
erro: v.string(),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Verificar rate limiting por IP
|
||||
if (args.ipAddress) {
|
||||
const ipBloqueado = await verificarRateLimitIP(ctx, args.ipAddress);
|
||||
if (ipBloqueado) {
|
||||
await registrarLogin(ctx, {
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "rate_limit_excedido",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Muitas tentativas de login. Tente novamente em 15 minutos.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Determinar se é email ou matrícula
|
||||
const isEmail = args.matriculaOuEmail.includes("@");
|
||||
|
||||
// Buscar usuário
|
||||
let usuario: Doc<"usuarios"> | null = null;
|
||||
if (isEmail) {
|
||||
usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", args.matriculaOuEmail))
|
||||
.first();
|
||||
} else {
|
||||
const funcionario: Doc<"funcionarios"> | null = await ctx.db.query("funcionarios").withIndex("by_matricula", (q) => q.eq("matricula", args.matriculaOuEmail)).first();
|
||||
if (funcionario) {
|
||||
usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
|
||||
.first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!usuario) {
|
||||
await registrarLogin(ctx, {
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "usuario_inexistente",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Credenciais incorretas.",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se usuário está bloqueado
|
||||
if (
|
||||
usuario.bloqueado ||
|
||||
(await verificarBloqueioUsuario(ctx, usuario._id))
|
||||
) {
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "usuario_bloqueado",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Usuário bloqueado. Entre em contato com o TI.",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se usuário está ativo
|
||||
if (!usuario.ativo) {
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "usuario_inativo",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Usuário inativo. Entre em contato com o TI.",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar tentativas de login (bloqueio temporário)
|
||||
const tentativasRecentes = usuario.tentativasLogin || 0;
|
||||
const ultimaTentativa = usuario.ultimaTentativaLogin || 0;
|
||||
const tempoDecorrido = Date.now() - ultimaTentativa;
|
||||
const TEMPO_BLOQUEIO = 30 * 60 * 1000; // 30 minutos
|
||||
|
||||
// Se tentou 5 vezes e ainda não passou o tempo de bloqueio
|
||||
if (tentativasRecentes >= 5 && tempoDecorrido < TEMPO_BLOQUEIO) {
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "bloqueio_temporario",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
const minutosRestantes = Math.ceil(
|
||||
(TEMPO_BLOQUEIO - tempoDecorrido) / 60000
|
||||
);
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: `Conta temporariamente bloqueada. Tente novamente em ${minutosRestantes} minutos.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Resetar tentativas se passou o tempo de bloqueio
|
||||
if (tempoDecorrido > TEMPO_BLOQUEIO) {
|
||||
await ctx.db.patch(usuario._id, {
|
||||
tentativasLogin: 0,
|
||||
ultimaTentativaLogin: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// Verificar senha
|
||||
const senhaValida = await verifyPassword(args.senha, usuario.senhaHash);
|
||||
|
||||
if (!senhaValida) {
|
||||
// Incrementar tentativas
|
||||
const novasTentativas =
|
||||
tempoDecorrido > TEMPO_BLOQUEIO ? 1 : tentativasRecentes + 1;
|
||||
|
||||
await ctx.db.patch(usuario._id, {
|
||||
tentativasLogin: novasTentativas,
|
||||
ultimaTentativaLogin: Date.now(),
|
||||
});
|
||||
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "senha_incorreta",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
const tentativasRestantes = 5 - novasTentativas;
|
||||
if (tentativasRestantes > 0) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: `Credenciais incorretas. ${tentativasRestantes} tentativas restantes.`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Conta bloqueada por 30 minutos devido a múltiplas tentativas falhas.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Login bem-sucedido! Resetar tentativas
|
||||
await ctx.db.patch(usuario._id, {
|
||||
tentativasLogin: 0,
|
||||
ultimaTentativaLogin: undefined,
|
||||
});
|
||||
|
||||
// Buscar role do usuário
|
||||
const role: Doc<"roles"> | null = await ctx.db.get(usuario.roleId);
|
||||
if (!role) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Erro ao carregar permissões do usuário.",
|
||||
};
|
||||
}
|
||||
|
||||
// Gerar token de sessão
|
||||
const token = generateToken();
|
||||
const agora = Date.now();
|
||||
const expiraEm = agora + 8 * 60 * 60 * 1000; // 8 horas
|
||||
|
||||
// Criar sessão
|
||||
await ctx.db.insert("sessoes", {
|
||||
usuarioId: usuario._id,
|
||||
token,
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
criadoEm: agora,
|
||||
expiraEm,
|
||||
ativo: true,
|
||||
});
|
||||
|
||||
// Atualizar último acesso
|
||||
await ctx.db.patch(usuario._id, {
|
||||
ultimoAcesso: agora,
|
||||
atualizadoEm: agora,
|
||||
});
|
||||
|
||||
// Log de login bem-sucedido
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: true,
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
await ctx.db.insert("logsAcesso", {
|
||||
usuarioId: usuario._id,
|
||||
tipo: "login",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
detalhes: "Login realizado com sucesso",
|
||||
timestamp: agora,
|
||||
});
|
||||
|
||||
// Obter matrícula do funcionário se houver
|
||||
let matricula: string | undefined = undefined;
|
||||
if (usuario.funcionarioId) {
|
||||
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||
matricula = funcionario?.matricula;
|
||||
}
|
||||
|
||||
return {
|
||||
sucesso: true as const,
|
||||
token,
|
||||
usuario: {
|
||||
_id: usuario._id,
|
||||
matricula: matricula || "",
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
funcionarioId: usuario.funcionarioId,
|
||||
role: {
|
||||
_id: role._id,
|
||||
nome: role.nome,
|
||||
nivel: role.nivel,
|
||||
setor: role.setor,
|
||||
},
|
||||
primeiroAcesso: usuario.primeiroAcesso,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Mutation interna para login via HTTP (com IP extraído do request)
|
||||
* Usada pelo endpoint HTTP /api/login
|
||||
*/
|
||||
export const loginComIP = internalMutation({
|
||||
args: {
|
||||
matriculaOuEmail: v.string(),
|
||||
senha: v.string(),
|
||||
ipAddress: v.optional(v.string()),
|
||||
userAgent: v.optional(v.string()),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
sucesso: v.literal(true),
|
||||
token: v.string(),
|
||||
usuario: v.object({
|
||||
_id: v.id("usuarios"),
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
funcionarioId: v.optional(v.id("funcionarios")),
|
||||
role: v.object({
|
||||
_id: v.id("roles"),
|
||||
nome: v.string(),
|
||||
nivel: v.number(),
|
||||
setor: v.optional(v.string()),
|
||||
}),
|
||||
primeiroAcesso: v.boolean(),
|
||||
}),
|
||||
}),
|
||||
v.object({
|
||||
sucesso: v.literal(false),
|
||||
erro: v.string(),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Reutilizar a mesma lógica da mutation pública
|
||||
// Verificar rate limiting por IP
|
||||
if (args.ipAddress) {
|
||||
const ipBloqueado = await verificarRateLimitIP(ctx, args.ipAddress);
|
||||
if (ipBloqueado) {
|
||||
await registrarLogin(ctx, {
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "rate_limit_excedido",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Muitas tentativas de login. Tente novamente em 15 minutos.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Determinar se é email ou matrícula
|
||||
const isEmail = args.matriculaOuEmail.includes("@");
|
||||
|
||||
// Buscar usuário
|
||||
let usuario: Doc<"usuarios"> | null = null;
|
||||
if (isEmail) {
|
||||
usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", args.matriculaOuEmail))
|
||||
.first();
|
||||
} else {
|
||||
const funcionario: Doc<"funcionarios"> | null = await ctx.db.query("funcionarios").withIndex("by_matricula", (q) => q.eq("matricula", args.matriculaOuEmail)).first();
|
||||
if (funcionario) {
|
||||
usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
|
||||
.first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!usuario) {
|
||||
await registrarLogin(ctx, {
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "usuario_inexistente",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Credenciais incorretas.",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se usuário está bloqueado
|
||||
if (
|
||||
usuario.bloqueado ||
|
||||
(await verificarBloqueioUsuario(ctx, usuario._id))
|
||||
) {
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "usuario_bloqueado",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Usuário bloqueado. Entre em contato com o TI.",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se usuário está ativo
|
||||
if (!usuario.ativo) {
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "usuario_inativo",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Usuário inativo. Entre em contato com o TI.",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar tentativas de login (bloqueio temporário)
|
||||
const tentativasRecentes = usuario.tentativasLogin || 0;
|
||||
const ultimaTentativa = usuario.ultimaTentativaLogin || 0;
|
||||
const tempoDecorrido = Date.now() - ultimaTentativa;
|
||||
const TEMPO_BLOQUEIO = 30 * 60 * 1000; // 30 minutos
|
||||
|
||||
// Se tentou 5 vezes e ainda não passou o tempo de bloqueio
|
||||
if (tentativasRecentes >= 5 && tempoDecorrido < TEMPO_BLOQUEIO) {
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "bloqueio_temporario",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
const minutosRestantes = Math.ceil(
|
||||
(TEMPO_BLOQUEIO - tempoDecorrido) / 60000
|
||||
);
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: `Conta temporariamente bloqueada. Tente novamente em ${minutosRestantes} minutos.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Resetar tentativas se passou o tempo de bloqueio
|
||||
if (tempoDecorrido > TEMPO_BLOQUEIO) {
|
||||
await ctx.db.patch(usuario._id, {
|
||||
tentativasLogin: 0,
|
||||
ultimaTentativaLogin: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// Verificar senha
|
||||
const senhaValida = await verifyPassword(args.senha, usuario.senhaHash);
|
||||
|
||||
if (!senhaValida) {
|
||||
// Incrementar tentativas
|
||||
const novasTentativas =
|
||||
tempoDecorrido > TEMPO_BLOQUEIO ? 1 : tentativasRecentes + 1;
|
||||
|
||||
await ctx.db.patch(usuario._id, {
|
||||
tentativasLogin: novasTentativas,
|
||||
ultimaTentativaLogin: Date.now(),
|
||||
});
|
||||
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: false,
|
||||
motivoFalha: "senha_incorreta",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
const tentativasRestantes = 5 - novasTentativas;
|
||||
if (tentativasRestantes > 0) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: `Credenciais incorretas. ${tentativasRestantes} tentativas restantes.`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Conta bloqueada por 30 minutos devido a múltiplas tentativas falhas.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Login bem-sucedido! Resetar tentativas
|
||||
await ctx.db.patch(usuario._id, {
|
||||
tentativasLogin: 0,
|
||||
ultimaTentativaLogin: undefined,
|
||||
});
|
||||
|
||||
// Buscar role do usuário
|
||||
const role: Doc<"roles"> | null = await ctx.db.get(usuario.roleId);
|
||||
if (!role) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Erro ao carregar permissões do usuário.",
|
||||
};
|
||||
}
|
||||
|
||||
// Gerar token de sessão
|
||||
const token = generateToken();
|
||||
const agora = Date.now();
|
||||
const expiraEm = agora + 8 * 60 * 60 * 1000; // 8 horas
|
||||
|
||||
// Criar sessão
|
||||
await ctx.db.insert("sessoes", {
|
||||
usuarioId: usuario._id,
|
||||
token,
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
criadoEm: agora,
|
||||
expiraEm,
|
||||
ativo: true,
|
||||
});
|
||||
|
||||
// Atualizar último acesso
|
||||
await ctx.db.patch(usuario._id, {
|
||||
ultimoAcesso: agora,
|
||||
atualizadoEm: agora,
|
||||
});
|
||||
|
||||
// Log de login bem-sucedido
|
||||
await registrarLogin(ctx, {
|
||||
usuarioId: usuario._id,
|
||||
matriculaOuEmail: args.matriculaOuEmail,
|
||||
sucesso: true,
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
});
|
||||
|
||||
await ctx.db.insert("logsAcesso", {
|
||||
usuarioId: usuario._id,
|
||||
tipo: "login",
|
||||
ipAddress: args.ipAddress,
|
||||
userAgent: args.userAgent,
|
||||
detalhes: "Login realizado com sucesso",
|
||||
timestamp: agora,
|
||||
});
|
||||
|
||||
// Obter matrícula do funcionário se houver
|
||||
let matricula: string | undefined = undefined;
|
||||
if (usuario.funcionarioId) {
|
||||
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||
matricula = funcionario?.matricula;
|
||||
}
|
||||
|
||||
return {
|
||||
sucesso: true as const,
|
||||
token,
|
||||
usuario: {
|
||||
_id: usuario._id,
|
||||
matricula: matricula || "",
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
funcionarioId: usuario.funcionarioId,
|
||||
role: {
|
||||
_id: role._id,
|
||||
nome: role.nome,
|
||||
nivel: role.nivel,
|
||||
setor: role.setor,
|
||||
},
|
||||
primeiroAcesso: usuario.primeiroAcesso,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Logout do usuário
|
||||
*/
|
||||
export const logout = mutation({
|
||||
args: {
|
||||
token: v.string(),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar sessão
|
||||
const sessao = await ctx.db
|
||||
.query("sessoes")
|
||||
.withIndex("by_token", (q) => q.eq("token", args.token))
|
||||
.first();
|
||||
|
||||
if (sessao) {
|
||||
// Desativar sessão
|
||||
await ctx.db.patch(sessao._id, {
|
||||
ativo: false,
|
||||
});
|
||||
|
||||
// Log de logout
|
||||
await ctx.db.insert("logsAcesso", {
|
||||
usuarioId: sessao.usuarioId,
|
||||
tipo: "logout",
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Verificar se token é válido e retornar usuário
|
||||
*/
|
||||
export const verificarSessao = query({
|
||||
args: {
|
||||
token: v.string(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
valido: v.literal(true),
|
||||
usuario: v.object({
|
||||
_id: v.id("usuarios"),
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
funcionarioId: v.optional(v.id("funcionarios")),
|
||||
role: v.object({
|
||||
_id: v.id("roles"),
|
||||
nome: v.string(),
|
||||
nivel: v.number(),
|
||||
setor: v.optional(v.string()),
|
||||
}),
|
||||
primeiroAcesso: v.boolean(),
|
||||
}),
|
||||
}),
|
||||
v.object({
|
||||
valido: v.literal(false),
|
||||
motivo: v.optional(v.string()),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar sessão
|
||||
const sessao: Doc<"sessoes"> | null = await ctx.db
|
||||
.query("sessoes")
|
||||
.withIndex("by_token", (q) => q.eq("token", args.token))
|
||||
.first();
|
||||
|
||||
if (!sessao || !sessao.ativo) {
|
||||
return {
|
||||
valido: false as const,
|
||||
motivo: "Sessão não encontrada ou inativa",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se sessão expirou
|
||||
if (sessao.expiraEm < Date.now()) {
|
||||
// Não podemos fazer patch/insert em uma query
|
||||
// A expiração será tratada por uma mutation separada
|
||||
return { valido: false as const, motivo: "Sessão expirada" };
|
||||
}
|
||||
|
||||
// Buscar usuário
|
||||
const usuario: Doc<"usuarios"> | null = await ctx.db.get(sessao.usuarioId);
|
||||
if (!usuario || !usuario.ativo) {
|
||||
return {
|
||||
valido: false as const,
|
||||
motivo: "Usuário não encontrado ou inativo",
|
||||
};
|
||||
}
|
||||
|
||||
// Buscar role
|
||||
const role: Doc<"roles"> | null = await ctx.db.get(usuario.roleId);
|
||||
if (!role) {
|
||||
return { valido: false as const, motivo: "Role não encontrada" };
|
||||
}
|
||||
|
||||
// Obter matrícula do funcionário se houver
|
||||
let matricula: string | undefined = undefined;
|
||||
if (usuario.funcionarioId) {
|
||||
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||
matricula = funcionario?.matricula;
|
||||
}
|
||||
|
||||
return {
|
||||
valido: true as const,
|
||||
usuario: {
|
||||
_id: usuario._id,
|
||||
matricula: matricula || "",
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
funcionarioId: usuario.funcionarioId,
|
||||
role: {
|
||||
_id: role._id,
|
||||
nome: role.nome,
|
||||
nivel: role.nivel,
|
||||
setor: role.setor,
|
||||
},
|
||||
primeiroAcesso: usuario.primeiroAcesso,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Limpar sessões expiradas (chamada periodicamente)
|
||||
*/
|
||||
export const limparSessoesExpiradas = mutation({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
sessoesLimpas: v.number(),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const agora = Date.now();
|
||||
const sessoes = await ctx.db
|
||||
.query("sessoes")
|
||||
.withIndex("by_ativo", (q) => q.eq("ativo", true))
|
||||
.collect();
|
||||
|
||||
let sessoesLimpas = 0;
|
||||
|
||||
for (const sessao of sessoes) {
|
||||
if (sessao.expiraEm < agora) {
|
||||
await ctx.db.patch(sessao._id, { ativo: false });
|
||||
|
||||
await ctx.db.insert("logsAcesso", {
|
||||
usuarioId: sessao.usuarioId,
|
||||
tipo: "sessao_expirada",
|
||||
timestamp: agora,
|
||||
});
|
||||
|
||||
sessoesLimpas++;
|
||||
}
|
||||
}
|
||||
|
||||
return { sessoesLimpas };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Alterar senha (primeiro acesso ou reset)
|
||||
*/
|
||||
export const alterarSenha = mutation({
|
||||
args: {
|
||||
token: v.string(),
|
||||
senhaAtual: v.optional(v.string()),
|
||||
novaSenha: v.string(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Verificar sessão
|
||||
const sessao: Doc<"sessoes"> | null = await ctx.db
|
||||
.query("sessoes")
|
||||
.withIndex("by_token", (q) => q.eq("token", args.token))
|
||||
.first();
|
||||
|
||||
if (!sessao || !sessao.ativo) {
|
||||
return { sucesso: false as const, erro: "Sessão inválida" };
|
||||
}
|
||||
|
||||
const usuario: Doc<"usuarios"> | null = await ctx.db.get(sessao.usuarioId);
|
||||
if (!usuario) {
|
||||
return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
}
|
||||
|
||||
// Se não for primeiro acesso, verificar senha atual
|
||||
if (!usuario.primeiroAcesso && args.senhaAtual) {
|
||||
const senhaAtualValida = await verifyPassword(
|
||||
args.senhaAtual,
|
||||
usuario.senhaHash
|
||||
);
|
||||
if (!senhaAtualValida) {
|
||||
return { sucesso: false as const, erro: "Senha atual incorreta" };
|
||||
}
|
||||
}
|
||||
|
||||
// Validar nova senha
|
||||
if (!validarSenha(args.novaSenha)) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Senha deve ter no mínimo 8 caracteres, incluindo letras, números e símbolos",
|
||||
};
|
||||
}
|
||||
|
||||
// Gerar hash da nova senha
|
||||
const novoHash = await hashPassword(args.novaSenha);
|
||||
|
||||
// Atualizar senha
|
||||
await ctx.db.patch(usuario._id, {
|
||||
senhaHash: novoHash,
|
||||
primeiroAcesso: false,
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Log
|
||||
await ctx.db.insert("logsAcesso", {
|
||||
usuarioId: usuario._id,
|
||||
tipo: "senha_alterada",
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
return { sucesso: true as const };
|
||||
},
|
||||
});
|
||||
@@ -2,8 +2,9 @@ import { createClient, type GenericCtx } from "@convex-dev/better-auth";
|
||||
import { convex } from "@convex-dev/better-auth/plugins";
|
||||
import { components } from "./_generated/api";
|
||||
import { type DataModel } from "./_generated/dataModel";
|
||||
import { query } from "./_generated/server";
|
||||
import { mutation, MutationCtx, query, QueryCtx } from "./_generated/server";
|
||||
import { betterAuth } from "better-auth";
|
||||
import { v } from "convex/values";
|
||||
|
||||
const siteUrl = process.env.SITE_URL!;
|
||||
|
||||
@@ -55,3 +56,52 @@ export const getCurrentUser = query({
|
||||
return user;
|
||||
},
|
||||
});
|
||||
|
||||
export const getCurrentUserFunction = async (ctx: QueryCtx | MutationCtx) => {
|
||||
const authUser = await authComponent.safeGetAuthUser(ctx as any);
|
||||
if (!authUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("authId", (q) => q.eq("authId", authUser._id))
|
||||
.unique();
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
return user;
|
||||
};
|
||||
|
||||
export const createAuthUser = async (
|
||||
ctx: MutationCtx,
|
||||
args: { nome: string; email: string; password: string }
|
||||
) => {
|
||||
const { auth, headers } = await authComponent.getAuth(createAuth, ctx as any);
|
||||
|
||||
const result = await auth.api.signUpEmail({
|
||||
headers,
|
||||
body: {
|
||||
name: args.nome,
|
||||
email: args.email,
|
||||
password: args.password,
|
||||
},
|
||||
});
|
||||
|
||||
return result.user.id;
|
||||
};
|
||||
|
||||
export const updatePassword = async (
|
||||
ctx: MutationCtx,
|
||||
args: { newPassword: string; currentPassword: string }
|
||||
) => {
|
||||
const { auth, headers } = await authComponent.getAuth(createAuth, ctx as any);
|
||||
|
||||
await auth.api.changePassword({
|
||||
headers,
|
||||
body: {
|
||||
currentPassword: args.currentPassword,
|
||||
newPassword: args.newPassword,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -381,8 +381,7 @@ export default defineSchema({
|
||||
|
||||
// Sistema de Autenticação e Controle de Acesso
|
||||
usuarios: defineTable({
|
||||
authId: v.optional(v.string()),
|
||||
senhaHash: v.string(), // Senha criptografada com bcrypt
|
||||
authId: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
funcionarioId: v.optional(v.id("funcionarios")),
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { internalMutation, mutation, query } from "./_generated/server";
|
||||
import {
|
||||
action,
|
||||
internalAction,
|
||||
internalMutation,
|
||||
mutation,
|
||||
query,
|
||||
} from "./_generated/server";
|
||||
import { internal } from "./_generated/api";
|
||||
import { v } from "convex/values";
|
||||
import { hashPassword } from "./auth/utils";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
import { createAuthUser } from "./auth";
|
||||
|
||||
// Dados exportados do Convex Cloud
|
||||
const simbolosData = [
|
||||
@@ -187,83 +194,63 @@ const solicitacoesAcessoData = [
|
||||
/**
|
||||
* Seed inicial do banco de dados com os dados exportados do Convex Cloud
|
||||
*/
|
||||
export const seedDatabase = internalMutation({
|
||||
export const seedCreateRoles = internalMutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("🌱 Iniciando seed do banco de dados...");
|
||||
|
||||
// 1. Criar Roles (Perfis de Acesso)
|
||||
console.log("🔐 Criando roles...");
|
||||
// TI_MASTER - Nível 0 - Acesso total irrestrito
|
||||
const roleTIMaster = await ctx.db.insert("roles", {
|
||||
nome: "ti_master",
|
||||
descricao: "TI Master",
|
||||
nivel: 0,
|
||||
setor: "ti",
|
||||
const ensureRole = async (
|
||||
nome: string,
|
||||
descricao: string,
|
||||
nivel: number,
|
||||
setor?: string,
|
||||
editavel?: boolean
|
||||
) => {
|
||||
const existing = await ctx.db
|
||||
.query("roles")
|
||||
.withIndex("by_nome", (q) => q.eq("nome", nome))
|
||||
.first();
|
||||
if (existing) {
|
||||
console.log(` ℹ️ Role já existe: ${nome}`);
|
||||
return existing._id;
|
||||
}
|
||||
const id = await ctx.db.insert("roles", {
|
||||
nome,
|
||||
descricao,
|
||||
nivel,
|
||||
setor,
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
editavel,
|
||||
});
|
||||
console.log(" ✅ Role criada: ti_master (Nível 0 - Acesso Total)");
|
||||
console.log(` ✅ Role criada: ${nome}`);
|
||||
return id;
|
||||
};
|
||||
|
||||
// ADMIN - Nível 2 - Permissões configuráveis
|
||||
const roleAdmin = await ctx.db.insert("roles", {
|
||||
nome: "admin",
|
||||
descricao: "Administrador Geral",
|
||||
nivel: 2,
|
||||
setor: "administrativo",
|
||||
customizado: false,
|
||||
editavel: true, // Permissões configuráveis
|
||||
await ensureRole("ti_master", "TI Master", 0, "ti", false);
|
||||
await ensureRole("admin", "Administrador Geral", 2, "administrativo", true);
|
||||
await ensureRole("ti_usuario", "TI Usuário", 2, "ti", true);
|
||||
await ensureRole("rh", "Recursos Humanos", 2, "recursos_humanos", false);
|
||||
await ensureRole("financeiro", "Financeiro", 2, "financeiro", false);
|
||||
await ensureRole("usuario", "Usuário Padrão", 3, undefined, false);
|
||||
// Encadeia próxima etapa
|
||||
await ctx.scheduler.runAfter(0, internal.seed.seedCreateSimbolos, {});
|
||||
return null;
|
||||
},
|
||||
});
|
||||
console.log(" ✅ Role criada: admin (Nível 2 - Configurável)");
|
||||
|
||||
// TI_USUARIO - Nível 2 - Suporte técnico
|
||||
const roleTIUsuario = await ctx.db.insert("roles", {
|
||||
nome: "ti_usuario",
|
||||
descricao: "TI Usuário",
|
||||
nivel: 2,
|
||||
setor: "ti",
|
||||
customizado: false,
|
||||
editavel: true,
|
||||
});
|
||||
console.log(" ✅ Role criada: ti_usuario (Nível 2 - Suporte)");
|
||||
|
||||
const roleRH = await ctx.db.insert("roles", {
|
||||
nome: "rh",
|
||||
descricao: "Recursos Humanos",
|
||||
nivel: 2,
|
||||
setor: "recursos_humanos",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: rh");
|
||||
|
||||
const roleFinanceiro = await ctx.db.insert("roles", {
|
||||
nome: "financeiro",
|
||||
descricao: "Financeiro",
|
||||
nivel: 2,
|
||||
setor: "financeiro",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: financeiro");
|
||||
|
||||
const roleUsuario = await ctx.db.insert("roles", {
|
||||
nome: "usuario",
|
||||
descricao: "Usuário Padrão",
|
||||
nivel: 3,
|
||||
setor: undefined,
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: usuario (Nível 3 - Padrão)");
|
||||
|
||||
// 2. Criar Símbolos (Cargos)
|
||||
export const seedCreateSimbolos = internalMutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("💰 Criando símbolos...");
|
||||
const simbolosMap = new Map<string, Id<"simbolos">>();
|
||||
|
||||
const existentes = await ctx.db.query("simbolos").collect();
|
||||
const nomesExistentes = new Set(existentes.map((s) => s.nome));
|
||||
for (const simbolo of simbolosData) {
|
||||
const simboloId = await ctx.db.insert("simbolos", {
|
||||
if (nomesExistentes.has(simbolo.nome)) {
|
||||
console.log(` ℹ️ Símbolo já existe: ${simbolo.nome}`);
|
||||
continue;
|
||||
}
|
||||
await ctx.db.insert("simbolos", {
|
||||
nome: simbolo.nome,
|
||||
descricao: simbolo.descricao,
|
||||
tipo: simbolo.tipo,
|
||||
@@ -271,24 +258,43 @@ export const seedDatabase = internalMutation({
|
||||
repValor: simbolo.repValor || "",
|
||||
vencValor: simbolo.vencValor || "",
|
||||
});
|
||||
simbolosMap.set(simbolo.nome, simboloId);
|
||||
console.log(` ✅ Símbolo criado: ${simbolo.nome}`);
|
||||
}
|
||||
// Encadeia próxima etapa
|
||||
await ctx.scheduler.runAfter(0, internal.seed.seedCreateFuncionarios, {});
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Criar Funcionários
|
||||
export const seedCreateFuncionarios = internalMutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("👥 Criando funcionários...");
|
||||
const funcionariosMap = new Map<string, Id<"funcionarios">>();
|
||||
const simbolos = await ctx.db.query("simbolos").collect();
|
||||
const simbolosMap = new Map<string, Id<"simbolos">>();
|
||||
for (const s of simbolos) {
|
||||
simbolosMap.set(s.nome, s._id as Id<"simbolos">);
|
||||
}
|
||||
|
||||
for (const funcionario of funcionariosData) {
|
||||
const simboloId = simbolosMap.get(funcionario.simboloNome);
|
||||
if (!simboloId) {
|
||||
// Evitar duplicar por CPF
|
||||
const existente = await ctx.db
|
||||
.query("funcionarios")
|
||||
.withIndex("by_cpf", (q) => q.eq("cpf", funcionario.cpf))
|
||||
.first();
|
||||
if (existente) {
|
||||
console.log(
|
||||
` ❌ Símbolo não encontrado: ${funcionario.simboloNome}`
|
||||
` ℹ️ Funcionário já existe (CPF ${funcionario.cpf}): ${existente.nome}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const funcId = await ctx.db.insert("funcionarios", {
|
||||
const simboloId = simbolosMap.get(funcionario.simboloNome);
|
||||
if (!simboloId) {
|
||||
console.log(` ❌ Símbolo não encontrado: ${funcionario.simboloNome}`);
|
||||
continue;
|
||||
}
|
||||
await ctx.db.insert("funcionarios", {
|
||||
admissaoData: funcionario.admissaoData,
|
||||
cep: funcionario.cep,
|
||||
cidade: funcionario.cidade,
|
||||
@@ -304,36 +310,126 @@ export const seedDatabase = internalMutation({
|
||||
telefone: funcionario.telefone,
|
||||
uf: funcionario.uf,
|
||||
});
|
||||
funcionariosMap.set(funcionario.matricula, funcId);
|
||||
console.log(` ✅ Funcionário criado: ${funcionario.nome}`);
|
||||
}
|
||||
// Encadeia próxima etapa
|
||||
await ctx.scheduler.runAfter(
|
||||
0,
|
||||
internal.seed.seedCreateUsuariosParaFuncionarios,
|
||||
{}
|
||||
);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// 5. Criar usuários para os funcionários
|
||||
export const seedCreateUsuariosParaFuncionarios = internalMutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("👤 Criando usuários para funcionários...");
|
||||
// Agenda criação por funcionário para evitar timeout
|
||||
let delay = 0;
|
||||
for (const funcionario of funcionariosData) {
|
||||
const funcId = funcionariosMap.get(funcionario.matricula);
|
||||
if (!funcId) continue;
|
||||
|
||||
const senhaInicial = await hashPassword("Mudar@123");
|
||||
await ctx.db.insert("usuarios", {
|
||||
senhaHash: senhaInicial,
|
||||
await ctx.scheduler.runAfter(
|
||||
delay,
|
||||
internal.seed.seedCreateUsuarioParaFuncionario,
|
||||
{
|
||||
matricula: funcionario.matricula,
|
||||
nome: funcionario.nome,
|
||||
email: funcionario.email,
|
||||
funcionarioId: funcId as Id<"funcionarios">,
|
||||
roleId: roleUsuario,
|
||||
}
|
||||
);
|
||||
delay += 50;
|
||||
}
|
||||
// Agenda próxima etapa após as criações individuais
|
||||
await ctx.scheduler.runAfter(
|
||||
delay + 300,
|
||||
internal.seed.seedInserirSolicitacoesAcesso,
|
||||
{}
|
||||
);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
export const seedCreateUsuarioParaFuncionario = internalMutation({
|
||||
args: {
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
// Role "usuario"
|
||||
const roleUsuario = await ctx.db
|
||||
.query("roles")
|
||||
.withIndex("by_nome", (q) => q.eq("nome", "usuario"))
|
||||
.first();
|
||||
if (!roleUsuario) {
|
||||
console.log(' ❌ Role "usuario" não encontrada');
|
||||
return null;
|
||||
}
|
||||
|
||||
const funcionarioDoc = await ctx.db
|
||||
.query("funcionarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.first();
|
||||
if (!funcionarioDoc) {
|
||||
console.log(
|
||||
` ❌ Funcionário não encontrado pela matrícula: ${args.matricula}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const usuarioExistente = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", args.email))
|
||||
.first();
|
||||
if (usuarioExistente) {
|
||||
console.log(` ℹ️ Usuário já existe para ${args.email}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const authUserId = await createAuthUser(ctx, {
|
||||
nome: args.nome,
|
||||
email: args.email,
|
||||
password: "Mudar@123",
|
||||
});
|
||||
|
||||
await ctx.db.insert("usuarios", {
|
||||
authId: authUserId,
|
||||
nome: args.nome,
|
||||
email: args.email,
|
||||
funcionarioId: funcionarioDoc._id as Id<"funcionarios">,
|
||||
roleId: roleUsuario._id,
|
||||
ativo: true,
|
||||
primeiroAcesso: true,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
console.log(
|
||||
` ✅ Usuário criado: ${funcionario.nome} (senha: Mudar@123)`
|
||||
);
|
||||
}
|
||||
console.log(` ✅ Usuário criado: ${args.nome} (senha: Mudar@123)`);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// 6. Inserir solicitações de acesso
|
||||
export const seedInserirSolicitacoesAcesso = internalMutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("📋 Inserindo solicitações de acesso...");
|
||||
for (const solicitacao of solicitacoesAcessoData) {
|
||||
// Evitar duplicidade por matrícula
|
||||
const existente = await ctx.db
|
||||
.query("solicitacoesAcesso")
|
||||
.withIndex("by_matricula", (q) =>
|
||||
q.eq("matricula", solicitacao.matricula)
|
||||
)
|
||||
.first();
|
||||
if (existente) {
|
||||
console.log(
|
||||
` ℹ️ Solicitação já existe p/ matrícula ${solicitacao.matricula}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const dadosSolicitacao: {
|
||||
nome: string;
|
||||
matricula: string;
|
||||
@@ -365,10 +461,24 @@ export const seedDatabase = internalMutation({
|
||||
` ✅ Solicitação criada: ${solicitacao.nome} (${solicitacao.status})`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("✨ Seed do banco de dados concluído com sucesso!");
|
||||
console.log("✨ Seed concluído!");
|
||||
return null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const seedDatabase = internalAction({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("🌱 Iniciando seed do banco de dados (action)...");
|
||||
await ctx.runMutation(internal.seed.seedCreateRoles, {});
|
||||
await ctx.runMutation(internal.seed.seedCreateSimbolos, {});
|
||||
await ctx.runMutation(internal.seed.seedCreateFuncionarios, {});
|
||||
await ctx.runMutation(internal.seed.seedCreateUsuariosParaFuncionarios, {});
|
||||
await ctx.runMutation(internal.seed.seedInserirSolicitacoesAcesso, {});
|
||||
console.log("✨ Seed do banco de dados concluído com sucesso pela action!");
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -379,10 +489,12 @@ export const popularBanco = mutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("🌱 Executando popularBanco (wrapper público para seedDatabase)...");
|
||||
// Chama a internalMutation para reaproveitar a lógica de seed
|
||||
await ctx.runMutation(internal.seed.seedDatabase, {});
|
||||
console.log("✅ Seed concluído pelo wrapper público");
|
||||
console.log(
|
||||
"🌱 Executando popularBanco (wrapper público para seedDatabase)..."
|
||||
);
|
||||
// Agenda apenas a primeira etapa; as demais serão encadeadas internamente
|
||||
await ctx.scheduler.runAfter(0, internal.seed.seedCreateRoles, {});
|
||||
console.log("✅ Seed iniciado (etapa 1 agendada)");
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@@ -948,7 +1060,8 @@ export const verificarBanco = query({
|
||||
funcionarios: funcionarios.length,
|
||||
roles: roles.length,
|
||||
simbolos: simbolos.length,
|
||||
total: usuarios.length + funcionarios.length + roles.length + simbolos.length,
|
||||
total:
|
||||
usuarios.length + funcionarios.length + roles.length + simbolos.length,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { hashPassword, generateToken } from "./auth/utils";
|
||||
import { hashPassword } from "./auth/utils";
|
||||
import { registrarAtividade } from "./logsAtividades";
|
||||
import { Id, Doc } from "./_generated/dataModel";
|
||||
import { api } from "./_generated/api";
|
||||
import type { QueryCtx, MutationCtx } from "./_generated/server";
|
||||
import { createAuthUser, getCurrentUserFunction } from "./auth";
|
||||
|
||||
/**
|
||||
* Helper para obter a matrícula do usuário (do funcionário se houver)
|
||||
@@ -20,33 +20,6 @@ async function obterMatriculaUsuario(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper para obter usuário autenticado (Better Auth ou Sessão)
|
||||
* Usa a mesma lógica do obterPerfil para garantir consistência
|
||||
*/
|
||||
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
|
||||
// FASE 1 IMPLEMENTADA: Usa Custom Auth Provider configurado no convex.config.ts
|
||||
// O provider busca sessão por token específico (seguro) ou Better Auth
|
||||
const identity = await ctx.auth.getUserIdentity();
|
||||
let usuarioAtual = null;
|
||||
|
||||
if (identity && identity.email) {
|
||||
usuarioAtual = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
||||
.first();
|
||||
}
|
||||
|
||||
if (!usuarioAtual && identity) {
|
||||
console.error("⚠️ [getUsuarioAutenticado] Identity encontrada mas usuário não encontrado no banco:", {
|
||||
email: identity.email,
|
||||
subject: identity.subject
|
||||
});
|
||||
}
|
||||
|
||||
return usuarioAtual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associar funcionário a um usuário
|
||||
*/
|
||||
@@ -132,12 +105,17 @@ export const criar = mutation({
|
||||
return { sucesso: false as const, erro: "E-mail já cadastrado" };
|
||||
}
|
||||
|
||||
// Gerar hash da senha inicial
|
||||
const senhaHash = await hashPassword(args.senhaInicial);
|
||||
const senhaTemporaria = args.senhaInicial;
|
||||
|
||||
const authUserId = await createAuthUser(ctx, {
|
||||
nome: args.nome,
|
||||
email: args.email,
|
||||
password: senhaTemporaria,
|
||||
});
|
||||
|
||||
// Criar usuário
|
||||
const usuarioId = await ctx.db.insert("usuarios", {
|
||||
senhaHash,
|
||||
authId: authUserId,
|
||||
nome: args.nome,
|
||||
email: args.email,
|
||||
funcionarioId: args.funcionarioId,
|
||||
@@ -161,69 +139,6 @@ export const listar = query({
|
||||
matricula: v.optional(v.string()),
|
||||
ativo: v.optional(v.boolean()),
|
||||
},
|
||||
// returns: v.array(
|
||||
// v.object({
|
||||
// _id: v.id("usuarios"),
|
||||
// matricula: v.string(),
|
||||
// nome: v.string(),
|
||||
// email: v.string(),
|
||||
// ativo: v.boolean(),
|
||||
// bloqueado: v.optional(v.boolean()),
|
||||
// motivoBloqueio: v.optional(v.string()),
|
||||
// primeiroAcesso: v.boolean(),
|
||||
// ultimoAcesso: v.optional(v.number()),
|
||||
// criadoEm: v.number(),
|
||||
// role: v.union(
|
||||
// v.object({
|
||||
// _id: v.id("roles"),
|
||||
// _creationTime: v.optional(v.number()),
|
||||
// criadoPor: v.optional(v.id("usuarios")),
|
||||
// customizado: v.optional(v.boolean()),
|
||||
// descricao: v.string(),
|
||||
// editavel: v.optional(v.boolean()),
|
||||
// nome: v.string(),
|
||||
// nivel: v.number(),
|
||||
// setor: v.optional(v.string()),
|
||||
// }),
|
||||
// v.object({
|
||||
// _id: v.id("roles"),
|
||||
// _creationTime: v.optional(v.number()),
|
||||
// criadoPor: v.optional(v.id("usuarios")),
|
||||
// customizado: v.optional(v.boolean()),
|
||||
// descricao: v.literal("Perfil não encontrado"),
|
||||
// editavel: v.optional(v.boolean()),
|
||||
// nome: v.literal("erro_role_ausente"),
|
||||
// nivel: v.literal(999),
|
||||
// setor: v.optional(v.string()),
|
||||
// erro: v.literal(true),
|
||||
// })
|
||||
// ),
|
||||
// funcionario: v.optional(
|
||||
// v.object({
|
||||
// _id: v.id("funcionarios"),
|
||||
// nome: v.string(),
|
||||
// matricula: v.optional(v.string()),
|
||||
// descricaoCargo: v.optional(v.string()),
|
||||
// simboloTipo: v.union(
|
||||
// v.literal("cargo_comissionado"),
|
||||
// v.literal("funcao_gratificada")
|
||||
// ),
|
||||
// })
|
||||
// ),
|
||||
// avisos: v.optional(
|
||||
// v.array(
|
||||
// v.object({
|
||||
// tipo: v.union(
|
||||
// v.literal("erro"),
|
||||
// v.literal("aviso"),
|
||||
// v.literal("info")
|
||||
// ),
|
||||
// mensagem: v.string(),
|
||||
// })
|
||||
// )
|
||||
// ),
|
||||
// })
|
||||
// ),
|
||||
handler: async (ctx, args) => {
|
||||
let usuarios = await ctx.db.query("usuarios").collect();
|
||||
|
||||
@@ -439,34 +354,34 @@ export const alterarStatus = mutation({
|
||||
/**
|
||||
* Resetar senha do usuário
|
||||
*/
|
||||
export const resetarSenha = mutation({
|
||||
args: {
|
||||
usuarioId: v.id("usuarios"),
|
||||
novaSenha: v.string(),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const senhaHash = await hashPassword(args.novaSenha);
|
||||
// export const resetarSenha = mutation({
|
||||
// args: {
|
||||
// usuarioId: v.id("usuarios"),
|
||||
// novaSenha: v.string(),
|
||||
// },
|
||||
// returns: v.null(),
|
||||
// handler: async (ctx, args) => {
|
||||
// const senhaHash = await hashPassword(args.novaSenha);
|
||||
|
||||
await ctx.db.patch(args.usuarioId, {
|
||||
senhaHash,
|
||||
primeiroAcesso: true,
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
// await ctx.db.patch(args.usuarioId, {
|
||||
// senhaHash,
|
||||
// primeiroAcesso: true,
|
||||
// atualizadoEm: Date.now(),
|
||||
// });
|
||||
|
||||
// Desativar todas as sessões
|
||||
const sessoes = await ctx.db
|
||||
.query("sessoes")
|
||||
.withIndex("by_usuario", (q) => q.eq("usuarioId", args.usuarioId))
|
||||
.collect();
|
||||
// // Desativar todas as sessões
|
||||
// const sessoes = await ctx.db
|
||||
// .query("sessoes")
|
||||
// .withIndex("by_usuario", (q) => q.eq("usuarioId", args.usuarioId))
|
||||
// .collect();
|
||||
|
||||
for (const sessao of sessoes) {
|
||||
await ctx.db.patch(sessao._id, { ativo: false });
|
||||
}
|
||||
// for (const sessao of sessoes) {
|
||||
// await ctx.db.patch(sessao._id, { ativo: false });
|
||||
// }
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
// return null;
|
||||
// },
|
||||
// });
|
||||
|
||||
/**
|
||||
* Excluir usuário
|
||||
@@ -601,19 +516,6 @@ export const atualizarPerfil = mutation({
|
||||
.first();
|
||||
}
|
||||
|
||||
// SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado)
|
||||
if (!usuarioAtual) {
|
||||
const sessaoAtiva = await ctx.db
|
||||
.query("sessoes")
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.order("desc")
|
||||
.first();
|
||||
|
||||
if (sessaoAtiva) {
|
||||
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!usuarioAtual) throw new Error("Usuário não encontrado");
|
||||
|
||||
// Validar statusMensagem (max 100 chars)
|
||||
@@ -678,9 +580,6 @@ export const obterPerfil = query({
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
console.log("=== DEBUG obterPerfil ===");
|
||||
|
||||
// TENTAR BETTER AUTH PRIMEIRO
|
||||
const identity = await ctx.auth.getUserIdentity();
|
||||
console.log("Identity:", identity ? "encontrado" : "null");
|
||||
|
||||
@@ -693,42 +592,9 @@ export const obterPerfil = query({
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
||||
.first();
|
||||
|
||||
console.log(
|
||||
"Usuário encontrado por email:",
|
||||
usuarioAtual ? "SIM" : "NÃO"
|
||||
);
|
||||
}
|
||||
|
||||
// SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado)
|
||||
if (!usuarioAtual) {
|
||||
console.log("Buscando por sessão ativa...");
|
||||
const sessaoAtiva = await ctx.db
|
||||
.query("sessoes")
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.order("desc")
|
||||
.first();
|
||||
|
||||
console.log("Sessão ativa encontrada:", sessaoAtiva ? "SIM" : "NÃO");
|
||||
|
||||
if (sessaoAtiva) {
|
||||
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
|
||||
console.log(
|
||||
"Usuário da sessão encontrado:",
|
||||
usuarioAtual ? "SIM" : "NÃO"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!usuarioAtual) {
|
||||
console.log("❌ Nenhum usuário encontrado");
|
||||
// Listar todos os usuários para debug
|
||||
const todosUsuarios = await ctx.db.query("usuarios").collect();
|
||||
console.log("Total de usuários no banco:", todosUsuarios.length);
|
||||
console.log(
|
||||
"Emails cadastrados:",
|
||||
todosUsuarios.map((u) => u.email)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -789,7 +655,10 @@ export const listarParaChat = query({
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
// Obter usuário autenticado usando função helper compartilhada
|
||||
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
||||
const usuarioAtual = await getCurrentUserFunction(ctx);
|
||||
if (!usuarioAtual) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Buscar todos os usuários ativos
|
||||
const usuarios = await ctx.db
|
||||
@@ -888,6 +757,11 @@ export const bloquearUsuario = mutation({
|
||||
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: "Usuário não autenticado" };
|
||||
}
|
||||
|
||||
const usuario = await ctx.db.get(args.usuarioId);
|
||||
if (!usuario) {
|
||||
return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
@@ -948,6 +822,11 @@ export const desbloquearUsuario = mutation({
|
||||
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: "Usuário não autenticado" };
|
||||
}
|
||||
|
||||
const usuario = await ctx.db.get(args.usuarioId);
|
||||
if (!usuario) {
|
||||
return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
@@ -995,48 +874,48 @@ export const desbloquearUsuario = mutation({
|
||||
/**
|
||||
* Resetar senha de usuário (apenas TI_MASTER)
|
||||
*/
|
||||
export const resetarSenhaUsuario = mutation({
|
||||
args: {
|
||||
usuarioId: v.id("usuarios"),
|
||||
resetadoPorId: v.id("usuarios"),
|
||||
novaSenhaTemporaria: v.optional(v.string()), // Se não fornecer, gera automática
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true), senhaTemporaria: v.string() }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const usuario = await ctx.db.get(args.usuarioId);
|
||||
if (!usuario) {
|
||||
return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
}
|
||||
// export const resetarSenhaUsuario = mutation({
|
||||
// args: {
|
||||
// usuarioId: v.id("usuarios"),
|
||||
// resetadoPorId: v.id("usuarios"),
|
||||
// novaSenhaTemporaria: v.optional(v.string()), // Se não fornecer, gera automática
|
||||
// },
|
||||
// returns: v.union(
|
||||
// v.object({ sucesso: v.literal(true), senhaTemporaria: v.string() }),
|
||||
// v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
// ),
|
||||
// handler: async (ctx, args) => {
|
||||
// const usuario = await ctx.db.get(args.usuarioId);
|
||||
// if (!usuario) {
|
||||
// return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
// }
|
||||
|
||||
// Gerar senha temporária se não foi fornecida
|
||||
const senhaTemporaria = args.novaSenhaTemporaria || gerarSenhaTemporaria();
|
||||
const senhaHash = await hashPassword(senhaTemporaria);
|
||||
// // Gerar senha temporária se não foi fornecida
|
||||
// const senhaTemporaria = args.novaSenhaTemporaria || gerarSenhaTemporaria();
|
||||
// const senhaHash = await hashPassword(senhaTemporaria);
|
||||
|
||||
// Atualizar usuário
|
||||
await ctx.db.patch(args.usuarioId, {
|
||||
senhaHash,
|
||||
primeiroAcesso: true, // Força mudança de senha no próximo login
|
||||
tentativasLogin: 0,
|
||||
ultimaTentativaLogin: undefined,
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
// // Atualizar usuário
|
||||
// await ctx.db.patch(args.usuarioId, {
|
||||
// senhaHash,
|
||||
// primeiroAcesso: true, // Força mudança de senha no próximo login
|
||||
// tentativasLogin: 0,
|
||||
// ultimaTentativaLogin: undefined,
|
||||
// atualizadoEm: Date.now(),
|
||||
// });
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.resetadoPorId,
|
||||
"resetar_senha",
|
||||
"usuarios",
|
||||
JSON.stringify({ usuarioId: args.usuarioId }),
|
||||
args.usuarioId
|
||||
);
|
||||
// // Log de atividade
|
||||
// await registrarAtividade(
|
||||
// ctx,
|
||||
// args.resetadoPorId,
|
||||
// "resetar_senha",
|
||||
// "usuarios",
|
||||
// JSON.stringify({ usuarioId: args.usuarioId }),
|
||||
// args.usuarioId
|
||||
// );
|
||||
|
||||
return { sucesso: true as const, senhaTemporaria };
|
||||
},
|
||||
});
|
||||
// return { sucesso: true as const, senhaTemporaria };
|
||||
// },
|
||||
// });
|
||||
|
||||
// Helper para gerar senha temporária
|
||||
function gerarSenhaTemporaria(): string {
|
||||
@@ -1066,6 +945,11 @@ export const editarUsuario = mutation({
|
||||
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: "Usuário não autenticado" };
|
||||
}
|
||||
|
||||
const usuario = await ctx.db.get(args.usuarioId);
|
||||
if (!usuario) {
|
||||
return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
@@ -1153,7 +1037,12 @@ export const criarAdminMaster = mutation({
|
||||
}
|
||||
|
||||
const senhaTemporaria = args.senha || gerarSenhaTemporaria();
|
||||
const senhaHash = await hashPassword(senhaTemporaria);
|
||||
|
||||
const authUserId = await createAuthUser(ctx, {
|
||||
nome: args.nome,
|
||||
email: args.email,
|
||||
password: senhaTemporaria,
|
||||
});
|
||||
|
||||
// Verificar se email já existe
|
||||
const existentePorEmail = await ctx.db
|
||||
@@ -1164,11 +1053,11 @@ export const criarAdminMaster = mutation({
|
||||
// Promove usuário existente por email
|
||||
await ctx.db.patch(existentePorEmail._id, {
|
||||
nome: args.nome,
|
||||
senhaHash,
|
||||
roleId: roleTIMaster._id,
|
||||
ativo: true,
|
||||
primeiroAcesso: true,
|
||||
atualizadoEm: Date.now(),
|
||||
authId: authUserId,
|
||||
});
|
||||
return {
|
||||
sucesso: true as const,
|
||||
@@ -1179,7 +1068,7 @@ export const criarAdminMaster = mutation({
|
||||
|
||||
// Criar novo usuário TI Master
|
||||
const usuarioId = await ctx.db.insert("usuarios", {
|
||||
senhaHash,
|
||||
authId: authUserId,
|
||||
nome: args.nome,
|
||||
email: args.email,
|
||||
roleId: roleTIMaster._id,
|
||||
@@ -1192,191 +1081,3 @@ export const criarAdminMaster = mutation({
|
||||
return { sucesso: true as const, usuarioId, senhaTemporaria };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Desativar usuário logicamente (soft delete - apenas TI_MASTER)
|
||||
*/
|
||||
export const excluirUsuarioLogico = mutation({
|
||||
args: {
|
||||
usuarioId: v.id("usuarios"),
|
||||
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 usuario = await ctx.db.get(args.usuarioId);
|
||||
if (!usuario) {
|
||||
return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
}
|
||||
|
||||
// Marcar como inativo
|
||||
await ctx.db.patch(args.usuarioId, {
|
||||
ativo: false,
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Desativar todas as sessões
|
||||
const sessoes = await ctx.db
|
||||
.query("sessoes")
|
||||
.withIndex("by_usuario", (q) => q.eq("usuarioId", args.usuarioId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.collect();
|
||||
|
||||
for (const sessao of sessoes) {
|
||||
await ctx.db.patch(sessao._id, { ativo: false });
|
||||
}
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.excluidoPorId,
|
||||
"excluir",
|
||||
"usuarios",
|
||||
JSON.stringify({ usuarioId: args.usuarioId }),
|
||||
args.usuarioId
|
||||
);
|
||||
|
||||
return { sucesso: true as const };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Criar usuário completo com permissões (TI_MASTER)
|
||||
*/
|
||||
export const criarUsuarioCompleto = mutation({
|
||||
args: {
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
roleId: v.id("roles"),
|
||||
setor: v.optional(v.string()),
|
||||
senhaInicial: v.optional(v.string()),
|
||||
criadoPorId: v.id("usuarios"),
|
||||
enviarEmailBoasVindas: v.optional(v.boolean()),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
sucesso: v.literal(true),
|
||||
usuarioId: v.id("usuarios"),
|
||||
senhaTemporaria: v.string(),
|
||||
}),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Verificar se email já existe
|
||||
const emailExistente = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", args.email))
|
||||
.first();
|
||||
|
||||
if (emailExistente) {
|
||||
return { sucesso: false as const, erro: "E-mail já cadastrado" };
|
||||
}
|
||||
|
||||
// Gerar senha inicial se não fornecida
|
||||
const senhaTemporaria = args.senhaInicial || gerarSenhaTemporaria();
|
||||
const senhaHash = await hashPassword(senhaTemporaria);
|
||||
|
||||
// Criar usuário
|
||||
const usuarioId = await ctx.db.insert("usuarios", {
|
||||
senhaHash,
|
||||
nome: args.nome,
|
||||
email: args.email,
|
||||
roleId: args.roleId,
|
||||
setor: args.setor,
|
||||
ativo: true,
|
||||
primeiroAcesso: true,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.criadoPorId,
|
||||
"criar",
|
||||
"usuarios",
|
||||
JSON.stringify({ usuarioId, nome: args.nome }),
|
||||
usuarioId
|
||||
);
|
||||
|
||||
// TODO: Se enviarEmailBoasVindas = true, enfileirar email
|
||||
// Isso será implementado quando criarmos o sistema de emails
|
||||
|
||||
return { sucesso: true as const, usuarioId, senhaTemporaria };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Criar (ou garantir) um usuário ADMIN padrão
|
||||
*/
|
||||
export const criarAdminPadrao = mutation({
|
||||
args: {
|
||||
nome: v.optional(v.string()),
|
||||
email: v.optional(v.string()),
|
||||
senha: v.optional(v.string()),
|
||||
},
|
||||
returns: v.object({
|
||||
sucesso: v.boolean(),
|
||||
usuarioId: v.optional(v.id("usuarios")),
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
const nome = args.nome ?? "Administrador Geral";
|
||||
const email = args.email ?? "admin@sgse.pe.gov.br";
|
||||
const senha = args.senha ?? "Admin@123";
|
||||
|
||||
// Garantir role ADMIN (nível 2)
|
||||
let roleAdmin = await ctx.db
|
||||
.query("roles")
|
||||
.withIndex("by_nome", (q) => q.eq("nome", "admin"))
|
||||
.first();
|
||||
if (!roleAdmin) {
|
||||
const roleId = await ctx.db.insert("roles", {
|
||||
nome: "admin",
|
||||
descricao: "Administrador Geral",
|
||||
nivel: 2,
|
||||
setor: "administrativo",
|
||||
customizado: false,
|
||||
editavel: true,
|
||||
});
|
||||
roleAdmin = await ctx.db.get(roleId);
|
||||
}
|
||||
|
||||
if (!roleAdmin) return { sucesso: false };
|
||||
|
||||
// Verificar se já existe por email
|
||||
const existentePorEmail = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", email))
|
||||
.first();
|
||||
|
||||
const senhaHash = await hashPassword(senha);
|
||||
|
||||
if (existentePorEmail) {
|
||||
await ctx.db.patch(existentePorEmail._id, {
|
||||
nome,
|
||||
email,
|
||||
senhaHash,
|
||||
roleId: roleAdmin._id,
|
||||
ativo: true,
|
||||
primeiroAcesso: false,
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
return { sucesso: true, usuarioId: existentePorEmail._id };
|
||||
}
|
||||
|
||||
const usuarioId = await ctx.db.insert("usuarios", {
|
||||
senhaHash,
|
||||
nome,
|
||||
email,
|
||||
roleId: roleAdmin._id,
|
||||
ativo: true,
|
||||
primeiroAcesso: false,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
return { sucesso: true, usuarioId };
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user