Merge remote-tracking branch 'origin' into feat-pedidos

This commit is contained in:
2025-12-11 10:08:12 -03:00
194 changed files with 30374 additions and 10247 deletions

View File

@@ -4,6 +4,7 @@ import type { QueryCtx } from './_generated/server';
import { mutation, query } from './_generated/server';
import { createAuthUser, getCurrentUserFunction } from './auth';
import { registrarAtividade } from './logsAtividades';
import { api } from './_generated/api';
/**
* Helper para obter a matrícula do usuário (do funcionário se houver)
@@ -123,6 +124,116 @@ export const criar = mutation({
atualizadoEm: Date.now()
});
// Obter usuário que está criando (para enviar email e chat)
const usuarioCriador = await getCurrentUserFunction(ctx);
if (!usuarioCriador) {
// Se não conseguir obter o criador, retornar sucesso mesmo assim
return { sucesso: true as const, usuarioId };
}
// Buscar funcionário para obter matrícula se houver
let matricula = '';
if (args.funcionarioId) {
const funcionario = await ctx.db.get(args.funcionarioId);
if (funcionario?.matricula) {
matricula = funcionario.matricula;
}
}
// Preparar credenciais adicionais (matrícula se houver)
const credenciaisAdicionais = matricula
? `<li><strong>Matrícula:</strong> ${matricula}</li>`
: '';
// Obter URL do sistema
let urlSistema = process.env.SITE_URL || 'http://localhost:5173';
if (!urlSistema.match(/^https?:\/\//i)) {
urlSistema = `http://${urlSistema}`;
}
// Enviar email de boas-vindas usando template (agendado via scheduler)
try {
await ctx.scheduler.runAfter(0, api.email.enviarEmailComTemplate, {
destinatario: args.email,
destinatarioId: usuarioId,
templateCodigo: 'BEM_VINDO',
variaveis: {
nome: args.nome,
email: args.email,
credenciaisAdicionais,
senha: senhaTemporaria,
urlSistema
},
enviadoPor: usuarioCriador._id
});
} catch (error) {
// Fallback para envio direto se houver erro ao agendar ou processar o template
console.warn(
'Erro ao agendar envio de email com template BEM_VINDO, usando envio direto:',
error
);
await ctx.runMutation(api.email.enfileirarEmail, {
destinatario: args.email,
destinatarioId: usuarioId,
assunto: 'Bem-vindo ao SGSE',
corpo: `<p>Olá <strong>${args.nome}</strong>,</p>
<p>Seja bem-vindo ao <strong>SGSE - Sistema de Gerenciamento de Secretaria</strong>!</p>
<p>Seu cadastro foi realizado com sucesso.</p>
<div style='background-color: #F3F4F6; border-left: 4px solid #2563EB; padding: 15px; border-radius: 8px; margin: 20px 0;'>
<p style='margin: 0 0 10px 0;'><strong>Suas credenciais de acesso:</strong></p>
<ul style='margin: 0; padding-left: 20px;'>
<li><strong>E-mail:</strong> ${args.email}</li>
${credenciaisAdicionais}
<li><strong>Senha temporária:</strong> ${senhaTemporaria}</li>
</ul>
</div>
<p><strong>⚠️ Importante:</strong> Por favor, altere sua senha no primeiro acesso ao sistema.</p>
<p>Acesse o sistema através do link: <a href='${urlSistema}' style='color: #2563EB;'>${urlSistema}</a></p>
<p style='margin-top: 30px; color: #6B7280; font-size: 14px;'>Equipe de TI - Secretaria de Esportes</p>`,
enviadoPor: usuarioCriador._id
});
}
// Criar ou obter conversa entre criador e novo usuário
const conversasExistentes = await ctx.db
.query('conversas')
.filter((q) => q.eq(q.field('tipo'), 'individual'))
.collect();
let conversaId: Id<'conversas'> | null = null;
for (const conversa of conversasExistentes) {
if (
conversa.participantes.length === 2 &&
conversa.participantes.includes(usuarioCriador._id) &&
conversa.participantes.includes(usuarioId)
) {
conversaId = conversa._id;
break;
}
}
if (!conversaId) {
conversaId = await ctx.db.insert('conversas', {
tipo: 'individual',
participantes: [usuarioCriador._id, usuarioId],
criadoPor: usuarioCriador._id,
criadoEm: Date.now()
});
}
// Criar mensagem de chat (texto simples)
const mensagemChat = matricula
? `Bem-vindo ao SGSE! Seu cadastro foi realizado com sucesso. Suas credenciais de acesso: E-mail: ${args.email}, Matrícula: ${matricula}, Senha temporária: ${senhaTemporaria}. Por favor, altere sua senha no primeiro acesso.`
: `Bem-vindo ao SGSE! Seu cadastro foi realizado com sucesso. Suas credenciais de acesso: E-mail: ${args.email}, Senha temporária: ${senhaTemporaria}. Por favor, altere sua senha no primeiro acesso.`;
await ctx.db.insert('mensagens', {
conversaId,
remetenteId: usuarioCriador._id,
tipo: 'texto',
conteudo: mensagemChat,
enviadaEm: Date.now()
});
return { sucesso: true as const, usuarioId };
}
});
@@ -827,48 +938,108 @@ 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);
// Verificar permissão (apenas TI_MASTER)
const resetadoPor = await ctx.db.get(args.resetadoPorId);
if (!resetadoPor) {
return { sucesso: false as const, erro: 'Usuário que está resetando não encontrado' };
}
// // 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(),
// });
// Buscar a role do usuário
if (!resetadoPor.roleId) {
return { sucesso: false as const, erro: 'Usuário não possui role definida' };
}
// // Log de atividade
// await registrarAtividade(
// ctx,
// args.resetadoPorId,
// "resetar_senha",
// "usuarios",
// JSON.stringify({ usuarioId: args.usuarioId }),
// args.usuarioId
// );
const role = await ctx.db.get(resetadoPor.roleId);
if (!role) {
return { sucesso: false as const, erro: 'Role do usuário não encontrada' };
}
// return { sucesso: true as const, senhaTemporaria };
// },
// });
// Permitir TI_MASTER, TI_USUARIO e ADMIN
const rolesPermitidas = ['ti_master', 'ti_usuario', 'admin'];
if (!rolesPermitidas.includes(role.nome)) {
return {
sucesso: false as const,
erro: 'Apenas usuários de TI ou administradores podem resetar senhas'
};
}
// Gerar senha temporária se não foi fornecida
const senhaTemporaria = args.novaSenhaTemporaria || gerarSenhaTemporaria();
try {
// Nota: Better Auth gerencia senhas através do sistema de autenticação.
// A senha não é armazenada diretamente na tabela usuarios.
// Para resetar a senha, seria necessário usar a API do Better Auth,
// mas isso requer uma implementação adicional.
// Por enquanto, atualizamos apenas os campos do usuário que podemos modificar.
// Atualizar usuário (sem senhaHash, pois não existe no schema)
await ctx.db.patch(args.usuarioId, {
primeiroAcesso: true, // Força mudança de senha no próximo login
tentativasLogin: 0,
ultimaTentativaLogin: undefined,
atualizadoEm: Date.now()
});
// Desativar todas as sessões ativas
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 });
}
// Enviar email com a nova senha usando template
try {
await ctx.scheduler.runAfter(0, api.email.enviarEmailComTemplate, {
destinatario: usuario.email,
destinatarioId: args.usuarioId,
templateCodigo: 'SENHA_RESETADA',
variaveis: {
senha: senhaTemporaria
},
enviadoPor: args.resetadoPorId
});
} catch (emailError) {
console.error('Erro ao agendar envio de email:', emailError);
// Não falhar a mutation se o email falhar, apenas logar o erro
}
// Log de atividade
await registrarAtividade(
ctx,
args.resetadoPorId,
'resetar_senha',
'usuarios',
JSON.stringify({ usuarioId: args.usuarioId }),
args.usuarioId
);
return { sucesso: true as const, senhaTemporaria };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return { sucesso: false as const, erro: `Erro ao resetar senha: ${errorMessage}` };
}
}
});
// Helper para gerar senha temporária
function gerarSenhaTemporaria(): string {