Merge remote-tracking branch 'origin' into feat-pedidos
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user