Files
sgse-app/packages/backend/convex/configuracaoEmail.ts
deyvisonwanderley e6105ae8ea fix: update email configuration handling and improve type safety
- Changed the mutation for testing SMTP connection to use an action for better handling.
- Introduced an internal mutation to update the test timestamp for email configurations.
- Enhanced type safety by specifying document types for user and session queries.
- Improved error handling in the SMTP connection test to provide clearer feedback on failures.
2025-11-04 01:59:08 -03:00

230 lines
6.7 KiB
TypeScript

import { v } from "convex/values";
import { mutation, query, action, internalMutation } from "./_generated/server";
import { encryptSMTPPassword } from "./auth/utils";
import { registrarAtividade } from "./logsAtividades";
import { api, internal } from "./_generated/api";
/**
* Obter configuração de email ativa (senha mascarada)
*/
export const obterConfigEmail = query({
args: {},
handler: async (ctx) => {
const config = await ctx.db
.query("configuracaoEmail")
.withIndex("by_ativo", (q) => q.eq("ativo", true))
.first();
if (!config) {
return null;
}
// Retornar config com senha mascarada
return {
_id: config._id,
servidor: config.servidor,
porta: config.porta,
usuario: config.usuario,
senhaHash: "********", // Mascarar senha
emailRemetente: config.emailRemetente,
nomeRemetente: config.nomeRemetente,
usarSSL: config.usarSSL,
usarTLS: config.usarTLS,
ativo: config.ativo,
testadoEm: config.testadoEm,
atualizadoEm: config.atualizadoEm,
};
},
});
/**
* Salvar configuração de email (apenas TI_MASTER)
*/
export const salvarConfigEmail = mutation({
args: {
servidor: v.string(),
porta: v.number(),
usuario: v.string(),
senha: v.string(),
emailRemetente: v.string(),
nomeRemetente: v.string(),
usarSSL: v.boolean(),
usarTLS: v.boolean(),
configuradoPorId: v.id("usuarios"),
},
returns: v.union(
v.object({ sucesso: v.literal(true), configId: v.id("configuracaoEmail") }),
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args) => {
// Validar email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(args.emailRemetente)) {
return { sucesso: false as const, erro: "Email remetente inválido" };
}
// Validar porta
if (args.porta < 1 || args.porta > 65535) {
return { sucesso: false as const, erro: "Porta deve ser um número entre 1 e 65535" };
}
// Buscar config ativa anterior para manter senha se não fornecida
const configAtiva = await ctx.db
.query("configuracaoEmail")
.withIndex("by_ativo", (q) => q.eq("ativo", true))
.first();
// Determinar senhaHash: usar nova senha se fornecida, senão manter a atual
let senhaHash: string;
if (args.senha && args.senha.trim().length > 0) {
// Nova senha fornecida, criptografar usando criptografia reversível (AES)
senhaHash = await encryptSMTPPassword(args.senha);
} else if (configAtiva) {
// Senha não fornecida, manter a atual (já criptografada)
senhaHash = configAtiva.senhaHash;
} else {
// Sem senha e sem config existente - erro
return { sucesso: false as const, erro: "Senha é obrigatória para nova configuração" };
}
// Desativar config anterior
const configsAntigas = await ctx.db
.query("configuracaoEmail")
.withIndex("by_ativo", (q) => q.eq("ativo", true))
.collect();
for (const config of configsAntigas) {
await ctx.db.patch(config._id, { ativo: false });
}
// Criar nova config
const configId = await ctx.db.insert("configuracaoEmail", {
servidor: args.servidor,
porta: args.porta,
usuario: args.usuario,
senhaHash,
emailRemetente: args.emailRemetente,
nomeRemetente: args.nomeRemetente,
usarSSL: args.usarSSL,
usarTLS: args.usarTLS,
ativo: true,
configuradoPor: args.configuradoPorId,
atualizadoEm: Date.now(),
});
// Log de atividade
await registrarAtividade(
ctx,
args.configuradoPorId,
"configurar",
"email",
JSON.stringify({ servidor: args.servidor, porta: args.porta }),
configId
);
return { sucesso: true as const, configId };
},
});
/**
* Mutation interna para atualizar testadoEm
*/
export const atualizarTestadoEm = internalMutation({
args: {
configId: v.id("configuracaoEmail"),
},
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.patch(args.configId, {
testadoEm: Date.now(),
});
return null;
},
});
/**
* Testar conexão SMTP (action que chama action real)
*/
export const testarConexaoSMTP = action({
args: {
servidor: v.string(),
porta: v.number(),
usuario: v.string(),
senha: v.string(),
usarSSL: v.boolean(),
usarTLS: v.boolean(),
},
returns: v.union(
v.object({ sucesso: v.literal(true) }),
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args): Promise<{ sucesso: true } | { sucesso: false; erro: string }> => {
// Validações básicas
if (!args.servidor || args.servidor.trim().length === 0) {
return { sucesso: false as const, erro: "Servidor SMTP não pode estar vazio" };
}
if (!args.porta || args.porta < 1 || args.porta > 65535) {
return { sucesso: false as const, erro: "Porta inválida. Deve ser entre 1 e 65535" };
}
if (!args.usuario || args.usuario.trim().length === 0) {
return { sucesso: false as const, erro: "Usuário não pode estar vazio" };
}
if (!args.senha || args.senha.trim().length === 0) {
return { sucesso: false as const, erro: "Senha não pode estar vazia" };
}
// Validação de SSL/TLS mutuamente exclusivos
if (args.usarSSL && args.usarTLS) {
return { sucesso: false as const, erro: "SSL e TLS não podem estar habilitados simultaneamente" };
}
// Chamar action de teste real (que usa nodemailer)
try {
const resultado: { sucesso: true } | { sucesso: false; erro: string } = await ctx.runAction(api.actions.smtp.testarConexao, {
servidor: args.servidor,
porta: args.porta,
usuario: args.usuario,
senha: args.senha,
usarSSL: args.usarSSL,
usarTLS: args.usarTLS,
});
// Se o teste foi bem-sucedido e há uma config ativa, atualizar testadoEm
if (resultado.sucesso) {
const configAtiva = await ctx.runQuery(api.configuracaoEmail.obterConfigEmail, {});
if (configAtiva) {
await ctx.runMutation(internal.configuracaoEmail.atualizarTestadoEm, {
configId: configAtiva._id,
});
}
}
return resultado;
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
sucesso: false as const,
erro: errorMessage || "Erro ao conectar com o servidor SMTP"
};
}
},
});
/**
* Marcar que a configuração foi testada com sucesso
*/
export const marcarConfigTestada = mutation({
args: {
configId: v.id("configuracaoEmail"),
},
handler: async (ctx, args) => {
await ctx.db.patch(args.configId, {
testadoEm: Date.now(),
});
},
});