feat(master): reformular configuracao de email com IMAP/SMTP, teste de conexao e sincronizacao de respostas
This commit is contained in:
@@ -54,7 +54,7 @@ public final class Main {
|
||||
AuthHandlers authHandlers = new AuthHandlers(config, dataSourceProvider, jwtService);
|
||||
AuthMiddleware authMiddleware = new AuthMiddleware(jwtService, apiKeyService);
|
||||
TenantDataSourceProvider tenantDataSourceProvider = new TenantDataSourceProvider(config, dataSourceProvider);
|
||||
MasterHandlers masterHandlers = new MasterHandlers(dataSourceProvider, new TenantProvisioner(config), evolutionApiClient, config.jwtSecret());
|
||||
MasterHandlers masterHandlers = new MasterHandlers(dataSourceProvider, new TenantProvisioner(config), evolutionApiClient, config.jwtSecret(), emailClient, tenantDataSourceProvider);
|
||||
ClienteHandlers clienteHandlers = new ClienteHandlers(tenantDataSourceProvider, dataSourceProvider, Path.of("/app/uploads"), evolutionApiClient, emailClient);
|
||||
FinanceiroHandlers financeiroHandlers = new FinanceiroHandlers(dataSourceProvider, abacatePayClient);
|
||||
AbacatePayWebhookHandler abacatePayWebhookHandler = new AbacatePayWebhookHandler(dataSourceProvider, abacatePayClient);
|
||||
@@ -69,6 +69,17 @@ public final class Main {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}, 2, 5, TimeUnit.SECONDS);
|
||||
|
||||
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
Map<String, String> cfg = clienteHandlers.carregarConfigEmail();
|
||||
int sync = emailClient.sincronizarRespostasEmail(cfg, tenantDataSourceProvider, dataSourceProvider);
|
||||
if (sync > 0) System.out.println("Sincronizadas " + sync + " respostas de e-mail");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Erro na sincronização periódica de e-mails: " + e.getMessage());
|
||||
}
|
||||
}, 10, 60, TimeUnit.SECONDS);
|
||||
|
||||
System.out.println("Mensageiro de envios iniciado");
|
||||
new CountDownLatch(1).await();
|
||||
return;
|
||||
|
||||
@@ -1,36 +1,352 @@
|
||||
package com.seconectai.condocontab.integracao;
|
||||
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.PasswordAuthentication;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.Transport;
|
||||
import com.seconectai.condocontab.config.DataSourceProvider;
|
||||
import com.seconectai.condocontab.tenant.TenantDataSourceProvider;
|
||||
import com.seconectai.condocontab.util.Json;
|
||||
import jakarta.mail.*;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
|
||||
public final class EmailClient {
|
||||
|
||||
public void enviar(Map<String, String> smtp, String destino, String assunto, String texto) throws MessagingException {
|
||||
if (smtp == null || smtp.getOrDefault("smtp_host", "").isBlank()) return;
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.host", smtp.get("smtp_host"));
|
||||
props.put("mail.smtp.port", smtp.getOrDefault("smtp_port", "587"));
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.starttls.enable", smtp.getOrDefault("smtp_starttls", "true"));
|
||||
Properties props = buildSmtpProperties(smtp);
|
||||
String user = smtp.getOrDefault("smtp_user", "");
|
||||
String pass = smtp.getOrDefault("smtp_password", "");
|
||||
Session session = Session.getInstance(props, new jakarta.mail.Authenticator() {
|
||||
@Override protected PasswordAuthentication getPasswordAuthentication() {
|
||||
|
||||
Session session = Session.getInstance(props, new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(user, pass);
|
||||
}
|
||||
});
|
||||
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(smtp.getOrDefault("smtp_from", user)));
|
||||
String from = smtp.getOrDefault("smtp_from", user);
|
||||
if (from == null || from.isBlank()) from = user;
|
||||
message.setFrom(new InternetAddress(from));
|
||||
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(destino));
|
||||
message.setSubject(assunto == null ? "Aviso" : assunto, "UTF-8");
|
||||
message.setSubject(assunto == null ? "Notificação" : assunto, "UTF-8");
|
||||
message.setText(texto == null ? "" : texto, "UTF-8");
|
||||
Transport.send(message);
|
||||
}
|
||||
|
||||
public Map<String, Object> testarSmtp(Map<String, String> smtp) {
|
||||
if (smtp == null || smtp.getOrDefault("smtp_host", "").isBlank()) {
|
||||
return Map.of("ok", false, "mensagem", "Servidor SMTP não configurado.");
|
||||
}
|
||||
try {
|
||||
Properties props = buildSmtpProperties(smtp);
|
||||
String user = smtp.getOrDefault("smtp_user", "");
|
||||
String pass = smtp.getOrDefault("smtp_password", "");
|
||||
String portStr = smtp.getOrDefault("smtp_port", "465").replaceAll("\\D", "");
|
||||
int port = portStr.isBlank() ? 465 : Integer.parseInt(portStr);
|
||||
String host = smtp.get("smtp_host");
|
||||
|
||||
boolean isSsl = "ssl_tls".equalsIgnoreCase(smtp.getOrDefault("smtp_security", "ssl_tls")) || port == 465;
|
||||
Session session = Session.getInstance(props, null);
|
||||
Transport transport = session.getTransport(isSsl ? "smtps" : "smtp");
|
||||
try {
|
||||
transport.connect(host, port, user, pass);
|
||||
return Map.of("ok", true, "mensagem", "Conexão SMTP estabelecida com sucesso.");
|
||||
} finally {
|
||||
if (transport.isConnected()) {
|
||||
transport.close();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Map.of("ok", false, "mensagem", "Falha na conexão SMTP: " + (e.getMessage() != null ? e.getMessage() : e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> testarImap(Map<String, String> imap) {
|
||||
if (imap == null || imap.getOrDefault("imap_host", "").isBlank()) {
|
||||
return Map.of("ok", false, "mensagem", "Servidor IMAP não configurado.");
|
||||
}
|
||||
try {
|
||||
Properties props = buildImapProperties(imap);
|
||||
String user = imap.getOrDefault("imap_user", "");
|
||||
String pass = imap.getOrDefault("imap_password", "");
|
||||
String portStr = imap.getOrDefault("imap_port", "993").replaceAll("\\D", "");
|
||||
int port = portStr.isBlank() ? 993 : Integer.parseInt(portStr);
|
||||
String host = imap.get("imap_host");
|
||||
|
||||
boolean isSsl = "ssl_tls".equalsIgnoreCase(imap.getOrDefault("imap_security", "ssl_tls")) || port == 993;
|
||||
Session session = Session.getInstance(props, null);
|
||||
Store store = session.getStore(isSsl ? "imaps" : "imap");
|
||||
try {
|
||||
store.connect(host, port, user, pass);
|
||||
Folder inbox = store.getFolder("INBOX");
|
||||
inbox.open(Folder.READ_ONLY);
|
||||
int count = inbox.getMessageCount();
|
||||
inbox.close(false);
|
||||
return Map.of("ok", true, "mensagem", "Conexão IMAP estabelecida com sucesso (" + count + " mensagens na caixa de entrada).");
|
||||
} finally {
|
||||
if (store.isConnected()) {
|
||||
store.close();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Map.of("ok", false, "mensagem", "Falha na conexão IMAP: " + (e.getMessage() != null ? e.getMessage() : e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public int sincronizarRespostasEmail(Map<String, String> config, TenantDataSourceProvider tenantDs, DataSourceProvider masterDs) {
|
||||
if (config == null || config.getOrDefault("imap_host", "").isBlank()) return 0;
|
||||
int importados = 0;
|
||||
try {
|
||||
Properties props = buildImapProperties(config);
|
||||
String user = config.getOrDefault("imap_user", "");
|
||||
String pass = config.getOrDefault("imap_password", "");
|
||||
if (user.isBlank() || pass.isBlank()) return 0;
|
||||
|
||||
String portStr = config.getOrDefault("imap_port", "993").replaceAll("\\D", "");
|
||||
int port = portStr.isBlank() ? 993 : Integer.parseInt(portStr);
|
||||
String host = config.get("imap_host");
|
||||
boolean isSsl = "ssl_tls".equalsIgnoreCase(config.getOrDefault("imap_security", "ssl_tls")) || port == 993;
|
||||
|
||||
Session session = Session.getInstance(props, null);
|
||||
Store store = session.getStore(isSsl ? "imaps" : "imap");
|
||||
try {
|
||||
store.connect(host, port, user, pass);
|
||||
Folder inbox = store.getFolder("INBOX");
|
||||
inbox.open(Folder.READ_ONLY);
|
||||
|
||||
int total = inbox.getMessageCount();
|
||||
if (total <= 0) {
|
||||
inbox.close(false);
|
||||
return 0;
|
||||
}
|
||||
int start = Math.max(1, total - 49);
|
||||
Message[] msgs = inbox.getMessages(start, total);
|
||||
|
||||
List<Long> empresaIds = new ArrayList<>();
|
||||
try (Connection m = masterDs.getMasterConnection();
|
||||
PreparedStatement ps = m.prepareStatement("SELECT e.id FROM empresas e JOIN pg_database d ON d.datname=e.database_name WHERE e.status='ativa'")) {
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) empresaIds.add(rs.getLong(1));
|
||||
}
|
||||
}
|
||||
|
||||
for (Message msg : msgs) {
|
||||
Address[] fromArr = msg.getFrom();
|
||||
if (fromArr == null || fromArr.length == 0) continue;
|
||||
String senderEmail = "";
|
||||
String senderName = "";
|
||||
if (fromArr[0] instanceof InternetAddress ia) {
|
||||
senderEmail = ia.getAddress() != null ? ia.getAddress().trim().toLowerCase() : "";
|
||||
senderName = ia.getPersonal() != null ? ia.getPersonal() : senderEmail;
|
||||
} else {
|
||||
senderEmail = fromArr[0].toString().trim().toLowerCase();
|
||||
senderName = senderEmail;
|
||||
}
|
||||
if (senderEmail.isBlank()) continue;
|
||||
|
||||
String[] msgIdHeaders = msg.getHeader("Message-ID");
|
||||
String messageId = (msgIdHeaders != null && msgIdHeaders.length > 0) ? msgIdHeaders[0] : ("<email-" + msg.getMessageNumber() + "-" + (msg.getSentDate() != null ? msg.getSentDate().getTime() : System.currentTimeMillis()) + ">");
|
||||
String subject = msg.getSubject() != null ? msg.getSubject() : "";
|
||||
String body = "";
|
||||
try {
|
||||
body = getTextFromPart(msg);
|
||||
} catch (Exception ignored) {}
|
||||
if (body.length() > 4000) body = body.substring(0, 4000);
|
||||
|
||||
Timestamp dataRecebida = msg.getSentDate() != null ? new Timestamp(msg.getSentDate().getTime()) : Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/Sao_Paulo")));
|
||||
|
||||
for (Long empId : empresaIds) {
|
||||
try (Connection tc = tenantDs.getConnection(empId)) {
|
||||
try (PreparedStatement checkPs = tc.prepareStatement("SELECT count(*) FROM respostas WHERE payload LIKE ?")) {
|
||||
checkPs.setString(1, "%" + messageId + "%");
|
||||
try (ResultSet rs = checkPs.executeQuery()) {
|
||||
if (rs.next() && rs.getInt(1) > 0) continue;
|
||||
}
|
||||
}
|
||||
|
||||
Long condId = null;
|
||||
Long contatoId = null;
|
||||
String tipoOrigem = "email";
|
||||
String telOrigem = senderEmail;
|
||||
String nomeOrigem = senderName;
|
||||
|
||||
try (PreparedStatement cPs = tc.prepareStatement("SELECT c.id, c.nome, c.telefone, cc.condominio_id, cc.tipo FROM contatos c JOIN contato_condominio cc ON cc.contato_id=c.id WHERE lower(c.email)=? LIMIT 1")) {
|
||||
cPs.setString(1, senderEmail);
|
||||
try (ResultSet rs = cPs.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
contatoId = rs.getLong("id");
|
||||
condId = rs.getLong("condominio_id");
|
||||
tipoOrigem = rs.getString("tipo");
|
||||
if (rs.getString("nome") != null && !rs.getString("nome").isBlank()) nomeOrigem = rs.getString("nome");
|
||||
if (rs.getString("telefone") != null && !rs.getString("telefone").isBlank()) telOrigem = rs.getString("telefone");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (condId == null) {
|
||||
try (PreparedStatement ePs = tc.prepareStatement("SELECT id, nome, telefone FROM destinatarios_extra WHERE lower(email)=? AND status<>'excluido' LIMIT 1")) {
|
||||
ePs.setString(1, senderEmail);
|
||||
try (ResultSet rs = ePs.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
tipoOrigem = "extra";
|
||||
if (rs.getString("nome") != null && !rs.getString("nome").isBlank()) nomeOrigem = rs.getString("nome");
|
||||
if (rs.getString("telefone") != null && !rs.getString("telefone").isBlank()) telOrigem = rs.getString("telefone");
|
||||
try (PreparedStatement c1 = tc.prepareStatement("SELECT id FROM condominios ORDER BY id LIMIT 1");
|
||||
ResultSet r1 = c1.executeQuery()) {
|
||||
if (r1.next()) condId = r1.getLong(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (condId != null) {
|
||||
Map<String, Object> payloadMap = new LinkedHashMap<>();
|
||||
payloadMap.put("message_id", messageId);
|
||||
payloadMap.put("subject", subject);
|
||||
payloadMap.put("from", senderEmail);
|
||||
payloadMap.put("date", dataRecebida.toString());
|
||||
String payloadJson = Json.write(payloadMap);
|
||||
|
||||
try (PreparedStatement ins = tc.prepareStatement("INSERT INTO respostas (condominio_id, contato_id, tipo_origem, nome_origem, telefone_origem, resposta_texto, mensagem_original, payload, origem, status, recebido_em) VALUES (?,?,?,?,?,?,?,?,?,'recebida',?)")) {
|
||||
ins.setLong(1, condId);
|
||||
if (contatoId != null) ins.setLong(2, contatoId); else ins.setNull(2, Types.BIGINT);
|
||||
ins.setString(3, tipoOrigem);
|
||||
ins.setString(4, nomeOrigem);
|
||||
ins.setString(5, telOrigem);
|
||||
ins.setString(6, body.isBlank() ? subject : body);
|
||||
ins.setString(7, subject);
|
||||
ins.setString(8, payloadJson);
|
||||
ins.setString(9, "email");
|
||||
ins.setTimestamp(10, dataRecebida);
|
||||
ins.executeUpdate();
|
||||
importados++;
|
||||
}
|
||||
}
|
||||
} catch (Exception err) {
|
||||
System.err.println("Erro ao sincronizar email para empresa " + empId + ": " + err.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
inbox.close(false);
|
||||
} finally {
|
||||
if (store.isConnected()) store.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Falha ao sincronizar respostas por e-mail via IMAP: " + e.getMessage());
|
||||
}
|
||||
return importados;
|
||||
}
|
||||
|
||||
private String getTextFromPart(Part p) throws Exception {
|
||||
if (p.isMimeType("text/plain")) {
|
||||
Object content = p.getContent();
|
||||
return content != null ? content.toString() : "";
|
||||
}
|
||||
if (p.isMimeType("text/html")) {
|
||||
Object content = p.getContent();
|
||||
String html = content != null ? content.toString() : "";
|
||||
return html.replaceAll("<[^>]*>", " ").replaceAll("\\s+", " ").trim();
|
||||
}
|
||||
if (p.isMimeType("multipart/*")) {
|
||||
Multipart mp = (Multipart) p.getContent();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
BodyPart bp = mp.getBodyPart(i);
|
||||
String sub = getTextFromPart(bp);
|
||||
if (sub != null && !sub.isBlank()) sb.append(sub).append("\n");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static Properties buildSmtpProperties(Map<String, String> smtp) {
|
||||
Properties props = new Properties();
|
||||
String host = smtp.getOrDefault("smtp_host", "");
|
||||
String port = smtp.getOrDefault("smtp_port", "465");
|
||||
String security = smtp.getOrDefault("smtp_security", "").toLowerCase();
|
||||
boolean allowInsecure = "true".equalsIgnoreCase(smtp.get("smtp_allow_insecure_ssl"));
|
||||
|
||||
props.put("mail.smtp.host", host);
|
||||
props.put("mail.smtp.port", port);
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.connectiontimeout", "8000");
|
||||
props.put("mail.smtp.timeout", "8000");
|
||||
props.put("mail.smtp.writetimeout", "8000");
|
||||
|
||||
if (security.isBlank()) {
|
||||
if ("465".equals(port)) security = "ssl_tls";
|
||||
else if ("true".equalsIgnoreCase(smtp.get("smtp_starttls"))) security = "starttls";
|
||||
else security = "ssl_tls";
|
||||
}
|
||||
|
||||
if ("ssl_tls".equals(security)) {
|
||||
props.put("mail.smtp.ssl.enable", "true");
|
||||
props.put("mail.smtp.socketFactory.port", port);
|
||||
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
props.put("mail.smtp.socketFactory.fallback", "false");
|
||||
props.put("mail.smtps.ssl.enable", "true");
|
||||
props.put("mail.smtps.socketFactory.port", port);
|
||||
props.put("mail.smtps.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
props.put("mail.smtps.socketFactory.fallback", "false");
|
||||
} else if ("starttls".equals(security)) {
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
props.put("mail.smtp.starttls.required", "true");
|
||||
} else {
|
||||
props.put("mail.smtp.starttls.enable", "false");
|
||||
props.put("mail.smtp.ssl.enable", "false");
|
||||
}
|
||||
|
||||
if (allowInsecure) {
|
||||
props.put("mail.smtp.ssl.trust", "*");
|
||||
props.put("mail.smtps.ssl.trust", "*");
|
||||
props.put("mail.smtp.ssl.checkserveridentity", "false");
|
||||
props.put("mail.smtps.ssl.checkserveridentity", "false");
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
public static Properties buildImapProperties(Map<String, String> imap) {
|
||||
Properties props = new Properties();
|
||||
String host = imap.getOrDefault("imap_host", "");
|
||||
String port = imap.getOrDefault("imap_port", "993");
|
||||
String security = imap.getOrDefault("imap_security", "").toLowerCase();
|
||||
boolean allowInsecure = "true".equalsIgnoreCase(imap.get("imap_allow_insecure_ssl"));
|
||||
|
||||
props.put("mail.imap.host", host);
|
||||
props.put("mail.imap.port", port);
|
||||
props.put("mail.imap.connectiontimeout", "8000");
|
||||
props.put("mail.imap.timeout", "8000");
|
||||
props.put("mail.imaps.connectiontimeout", "8000");
|
||||
props.put("mail.imaps.timeout", "8000");
|
||||
|
||||
if (security.isBlank()) {
|
||||
security = "993".equals(port) ? "ssl_tls" : "starttls";
|
||||
}
|
||||
|
||||
if ("ssl_tls".equals(security)) {
|
||||
props.put("mail.imap.ssl.enable", "true");
|
||||
props.put("mail.imap.socketFactory.port", port);
|
||||
props.put("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
props.put("mail.imaps.ssl.enable", "true");
|
||||
props.put("mail.imaps.socketFactory.port", port);
|
||||
props.put("mail.imaps.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
} else if ("starttls".equals(security)) {
|
||||
props.put("mail.imap.starttls.enable", "true");
|
||||
props.put("mail.imap.starttls.required", "true");
|
||||
}
|
||||
|
||||
if (allowInsecure) {
|
||||
props.put("mail.imap.ssl.trust", "*");
|
||||
props.put("mail.imaps.ssl.trust", "*");
|
||||
props.put("mail.imap.ssl.checkserveridentity", "false");
|
||||
props.put("mail.imaps.ssl.checkserveridentity", "false");
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import com.seconectai.condocontab.auth.PasswordHasher;
|
||||
import com.seconectai.condocontab.auth.ApiKeyService;
|
||||
import com.seconectai.condocontab.config.DataSourceProvider;
|
||||
import com.seconectai.condocontab.http.HttpResponses;
|
||||
import com.seconectai.condocontab.integracao.EmailClient;
|
||||
import com.seconectai.condocontab.integracao.EvolutionApiClient;
|
||||
import com.seconectai.condocontab.master.dao.EmpresaDao;
|
||||
import com.seconectai.condocontab.master.dao.FuncionarioDao;
|
||||
import com.seconectai.condocontab.master.model.Empresa;
|
||||
import com.seconectai.condocontab.master.model.Funcionario;
|
||||
import com.seconectai.condocontab.tenant.TenantDataSourceProvider;
|
||||
import com.seconectai.condocontab.tenant.provisioning.TenantProvisioner;
|
||||
import com.seconectai.condocontab.util.SimpleJson;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
@@ -31,12 +33,20 @@ public final class MasterHandlers implements HttpHandler {
|
||||
private final TenantProvisioner tenantProvisioner;
|
||||
private final EvolutionApiClient evolutionApiClient;
|
||||
private final String apiKeySecret;
|
||||
private final EmailClient emailClient;
|
||||
private final TenantDataSourceProvider tenantDs;
|
||||
|
||||
public MasterHandlers(DataSourceProvider dataSourceProvider, TenantProvisioner tenantProvisioner, EvolutionApiClient evolutionApiClient, String apiKeySecret) {
|
||||
public MasterHandlers(DataSourceProvider dataSourceProvider, TenantProvisioner tenantProvisioner, EvolutionApiClient evolutionApiClient, String apiKeySecret, EmailClient emailClient, TenantDataSourceProvider tenantDs) {
|
||||
this.dataSourceProvider = dataSourceProvider;
|
||||
this.tenantProvisioner = tenantProvisioner;
|
||||
this.evolutionApiClient = evolutionApiClient;
|
||||
this.apiKeySecret = apiKeySecret;
|
||||
this.emailClient = emailClient != null ? emailClient : new EmailClient();
|
||||
this.tenantDs = tenantDs;
|
||||
}
|
||||
|
||||
public MasterHandlers(DataSourceProvider dataSourceProvider, TenantProvisioner tenantProvisioner, EvolutionApiClient evolutionApiClient, String apiKeySecret) {
|
||||
this(dataSourceProvider, tenantProvisioner, evolutionApiClient, apiKeySecret, new EmailClient(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,6 +149,14 @@ public final class MasterHandlers implements HttpHandler {
|
||||
reiniciarEvolutionInstanciaPorNome(ex, parts[1]);
|
||||
return;
|
||||
}
|
||||
if (parts.length == 2 && "configuracoes".equals(parts[0]) && "testar-email".equals(parts[1]) && "POST".equals(method)) {
|
||||
testarEmail(ex);
|
||||
return;
|
||||
}
|
||||
if (parts.length == 2 && "configuracoes".equals(parts[0]) && "sincronizar-email".equals(parts[1]) && "POST".equals(method)) {
|
||||
sincronizarEmail(ex);
|
||||
return;
|
||||
}
|
||||
if (parts.length == 1 && "configuracoes".equals(parts[0])) {
|
||||
if ("GET".equals(method)) listarConfiguracoes(ex);
|
||||
else if ("PUT".equals(method)) salvarConfiguracoes(ex);
|
||||
@@ -679,7 +697,7 @@ public final class MasterHandlers implements HttpHandler {
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
String chave = rs.getString("chave");
|
||||
Object valor = Set.of("smtp_password", "abacatepay_api_key", "abacatepay_webhook_secret").contains(chave) ? "********" : rs.getString("valor");
|
||||
Object valor = Set.of("smtp_password", "imap_password", "abacatepay_api_key", "abacatepay_webhook_secret").contains(chave) ? "********" : rs.getString("valor");
|
||||
cfg.put(chave, valor);
|
||||
Map<String,Object> row = new LinkedHashMap<>(); row.put("chave", chave); row.put("valor", valor); row.put("atualizado_em", rs.getObject("atualizado_em")); rows.add(row);
|
||||
}
|
||||
@@ -690,13 +708,17 @@ public final class MasterHandlers implements HttpHandler {
|
||||
|
||||
private void salvarConfiguracoes(HttpExchange ex) throws Exception {
|
||||
Map<String,String> b = body(ex);
|
||||
Set<String> permitidas = Set.of("smtp_host","smtp_port","smtp_user","smtp_password","smtp_from","smtp_starttls","abacatepay_api_key","abacatepay_webhook_secret");
|
||||
Set<String> permitidas = Set.of(
|
||||
"smtp_host","smtp_port","smtp_user","smtp_password","smtp_from","smtp_starttls","smtp_security","smtp_allow_insecure_ssl",
|
||||
"imap_host","imap_port","imap_user","imap_password","imap_security","imap_allow_insecure_ssl",
|
||||
"abacatepay_api_key","abacatepay_webhook_secret"
|
||||
);
|
||||
try (Connection conn = dataSourceProvider.getMasterConnection()) {
|
||||
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO configuracoes_plataforma (chave, valor, atualizado_em) VALUES (?, ?, now()) ON CONFLICT (chave) DO UPDATE SET valor=EXCLUDED.valor, atualizado_em=now()")) {
|
||||
for (String chave : permitidas) {
|
||||
if (!b.containsKey(chave)) continue;
|
||||
String valor = b.get(chave);
|
||||
if (Set.of("smtp_password", "abacatepay_api_key", "abacatepay_webhook_secret").contains(chave) && (valor == null || valor.isBlank() || "********".equals(valor))) continue;
|
||||
if (Set.of("smtp_password", "imap_password", "abacatepay_api_key", "abacatepay_webhook_secret").contains(chave) && (valor == null || valor.isBlank() || "********".equals(valor))) continue;
|
||||
ps.setString(1, chave); ps.setString(2, valor); ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
@@ -705,6 +727,40 @@ public final class MasterHandlers implements HttpHandler {
|
||||
listarConfiguracoes(ex);
|
||||
}
|
||||
|
||||
private void testarEmail(HttpExchange ex) throws Exception {
|
||||
Map<String, String> b = body(ex);
|
||||
Map<String, String> merged = carregarConfigEmailCompleta();
|
||||
for (Map.Entry<String, String> entry : b.entrySet()) {
|
||||
String val = entry.getValue();
|
||||
if (val != null && !val.isBlank() && !"********".equals(val)) {
|
||||
merged.put(entry.getKey(), val);
|
||||
}
|
||||
}
|
||||
Map<String, Object> smtpRes = emailClient.testarSmtp(merged);
|
||||
Map<String, Object> imapRes = emailClient.testarImap(merged);
|
||||
boolean ok = Boolean.TRUE.equals(smtpRes.get("ok")) && Boolean.TRUE.equals(imapRes.get("ok"));
|
||||
HttpResponses.json(ex, 200, Map.of("ok", ok, "smtp", smtpRes, "imap", imapRes));
|
||||
}
|
||||
|
||||
private void sincronizarEmail(HttpExchange ex) throws Exception {
|
||||
Map<String, String> cfg = carregarConfigEmailCompleta();
|
||||
int count = emailClient.sincronizarRespostasEmail(cfg, tenantDs, dataSourceProvider);
|
||||
HttpResponses.json(ex, 200, Map.of("ok", true, "importados", count, "mensagem", count > 0 ? (count + " resposta(s) importada(s) com sucesso.") : "Nenhuma nova resposta encontrada na caixa de entrada."));
|
||||
}
|
||||
|
||||
private Map<String, String> carregarConfigEmailCompleta() throws SQLException {
|
||||
try (Connection conn = dataSourceProvider.getMasterConnection();
|
||||
PreparedStatement ps = conn.prepareStatement("SELECT chave, valor FROM configuracoes_plataforma WHERE chave LIKE 'smtp_%' OR chave LIKE 'imap_%'")) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
map.put(rs.getString("chave"), rs.getString("valor"));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
private void criarEmpresa(HttpExchange ex) throws Exception {
|
||||
Map<String, String> body = body(ex);
|
||||
String nome = required(body, "nome");
|
||||
|
||||
@@ -579,7 +579,8 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
private void logEnvio(long emp,long cond,long aviso,long contato,String nome,String canal,String status,String erro)throws SQLException{try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("INSERT INTO logs_envio_avisos (empresa_id,condominio_id,aviso_id,contato_id,contato_nome,canal,status,erro_detalhe) VALUES (?,?,?,?,?,?,?,?)")){ps.setLong(1,emp);ps.setLong(2,cond);ps.setLong(3,aviso);ps.setLong(4,contato);ps.setString(5,nome);ps.setString(6,canal);ps.setString(7,status);ps.setString(8,erro);ps.executeUpdate();}}
|
||||
private boolean funcionalidadeAtiva(long emp,String chave)throws SQLException{try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("SELECT COALESCE(ef.ativa,false) FROM funcionalidades f LEFT JOIN empresa_funcionalidades ef ON ef.funcionalidade_id=f.id AND ef.empresa_id=? WHERE f.chave=?")){ps.setLong(1,emp);ps.setString(2,chave);try(ResultSet rs=ps.executeQuery()){return rs.next()&&rs.getBoolean(1);}}}
|
||||
private Map<String,Object> evolutionAprovada(long emp)throws SQLException{try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("SELECT * FROM evolution_instancias WHERE empresa_id=? AND status='aprovada' ORDER BY aprovado_em DESC,id DESC LIMIT 1")){ps.setLong(1,emp);try(ResultSet rs=ps.executeQuery()){return rs.next()?resultMap(rs):null;}}}
|
||||
private Map<String,String> smtpConfig()throws SQLException{try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("SELECT chave,valor FROM configuracoes_plataforma WHERE chave LIKE 'smtp_%'")){Map<String,String> out=new HashMap<>();try(ResultSet rs=ps.executeQuery()){while(rs.next())out.put(rs.getString(1),rs.getString(2));}return out;}}
|
||||
public Map<String,String> carregarConfigEmail()throws SQLException{return smtpConfig();}
|
||||
private Map<String,String> smtpConfig()throws SQLException{try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("SELECT chave,valor FROM configuracoes_plataforma WHERE chave LIKE 'smtp_%' OR chave LIKE 'imap_%'")){Map<String,String> out=new HashMap<>();try(ResultSet rs=ps.executeQuery()){while(rs.next())out.put(rs.getString(1),rs.getString(2));}return out;}}
|
||||
private Map<String,Object> funcionario(long emp,long id)throws SQLException{try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("SELECT id,nome,email,telefone FROM funcionarios WHERE id=? AND empresa_id=?")){ps.setLong(1,id);ps.setLong(2,emp);try(ResultSet rs=ps.executeQuery()){return rs.next()?resultMap(rs):null;}}}
|
||||
private String telefoneWhatsapp(Object raw){String d=EvolutionApiClient.onlyDigits(str(raw));if((d.length()==10||d.length()==11)&&!d.startsWith("55"))return "55"+d;return d;}
|
||||
private String limite(String v){if(v==null)return null;return v.length()>900?v.substring(0,900):v;}
|
||||
|
||||
@@ -1607,3 +1607,164 @@ th {
|
||||
height: 28px;
|
||||
stroke-width: 2.5;
|
||||
}
|
||||
|
||||
/* Configuração de E-mail (IMAP / SMTP) */
|
||||
.email-config-panel {
|
||||
padding: 36px 40px;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--line, #d8e1ea);
|
||||
border-radius: var(--radius-lg, 14px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.email-config-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.email-config-title {
|
||||
font-size: 32px;
|
||||
font-weight: 300;
|
||||
color: #374151;
|
||||
margin: 0 0 8px 0;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.email-config-subtitle {
|
||||
font-size: 15px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.email-config-columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 48px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.email-config-columns::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 1px;
|
||||
background: #e5e7eb;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.email-config-panel {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
.email-config-columns {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.email-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.email-section-heading {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
|
||||
.email-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.email-field > label {
|
||||
font-size: 13.5px;
|
||||
color: #4b5563;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.email-field input,
|
||||
.email-field select {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
background: #ffffff;
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
}
|
||||
|
||||
.email-field input:focus,
|
||||
.email-field select:focus {
|
||||
border-color: var(--brand, #0f766e);
|
||||
box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.15);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.email-security-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.email-security-wrap select {
|
||||
width: auto;
|
||||
min-width: 130px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.email-checkbox-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13.5px;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.email-checkbox-label input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
accent-color: var(--brand, #0f766e);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.email-from-wrap {
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.email-actions-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.email-status-feedback-box {
|
||||
margin-top: 16px;
|
||||
padding: 14px 18px;
|
||||
border-radius: var(--radius, 8px);
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -16,23 +16,120 @@
|
||||
<h1>Configurações</h1>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<h2>E-mail</h2>
|
||||
<form id="email-form" class="grid cols">
|
||||
<div class="field"><label>Host</label><input name="smtp_host"></div>
|
||||
<div class="field"><label>Porta</label><input name="smtp_port" type="number"></div>
|
||||
<div class="field"><label>Usuário</label><input name="smtp_user"></div>
|
||||
<div class="field"><label>Senha</label><input name="smtp_password" type="password"></div>
|
||||
<div class="field"><label>Remetente</label><input name="smtp_from"></div>
|
||||
<div class="field"><label>StartTLS</label>
|
||||
<select name="smtp_starttls">
|
||||
<option value="true">Sim</option>
|
||||
<option value="false">Não</option>
|
||||
</select>
|
||||
<section class="email-config-panel">
|
||||
<div class="email-config-header">
|
||||
<h2 class="email-config-title">Configurar Conta</h2>
|
||||
<p class="email-config-subtitle">Preencha as configurações de IMAP e SMTP abaixo para conectar sua conta.</p>
|
||||
</div>
|
||||
|
||||
<form id="email-form">
|
||||
<div class="email-config-columns">
|
||||
<!-- Coluna 1: Email Recebido (IMAP) -->
|
||||
<div class="email-column">
|
||||
<h3 class="email-section-heading">Email Recebido (IMAP):</h3>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Server:</label>
|
||||
<input name="imap_host" placeholder="imap.seconectai.com.br">
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Porta:</label>
|
||||
<select name="imap_port" id="imap-port-select">
|
||||
<option value="993">993</option>
|
||||
<option value="143">143</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Segurança:</label>
|
||||
<div class="email-security-wrap">
|
||||
<select name="imap_security" id="imap-sec-select">
|
||||
<option value="ssl_tls">SSL / TLS</option>
|
||||
<option value="starttls">STARTTLS</option>
|
||||
<option value="none">Nenhum</option>
|
||||
</select>
|
||||
<label class="email-checkbox-label">
|
||||
<input type="checkbox" name="imap_allow_insecure_ssl" value="true">
|
||||
Permitir SSL inseguro
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Username:</label>
|
||||
<input name="imap_user" placeholder="no_reply@seconectai.com.br">
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Senha:</label>
|
||||
<input name="imap_password" type="password" placeholder="••••••••••••">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Coluna 2: Email de saída (SMTP) -->
|
||||
<div class="email-column">
|
||||
<h3 class="email-section-heading">Email de saída (SMTP):</h3>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Server:</label>
|
||||
<input name="smtp_host" placeholder="smtp.seconectai.com.br">
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Porta:</label>
|
||||
<select name="smtp_port" id="smtp-port-select">
|
||||
<option value="465">465</option>
|
||||
<option value="587">587</option>
|
||||
<option value="25">25</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Segurança:</label>
|
||||
<div class="email-security-wrap">
|
||||
<select name="smtp_security" id="smtp-sec-select">
|
||||
<option value="ssl_tls">SSL / TLS</option>
|
||||
<option value="starttls">STARTTLS</option>
|
||||
<option value="none">Nenhum</option>
|
||||
</select>
|
||||
<label class="email-checkbox-label">
|
||||
<input type="checkbox" name="smtp_allow_insecure_ssl" value="true">
|
||||
Permitir SSL inseguro
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Username:</label>
|
||||
<input name="smtp_user" placeholder="no_reply@seconectai.com.br">
|
||||
</div>
|
||||
|
||||
<div class="email-field">
|
||||
<label>Senha:</label>
|
||||
<input name="smtp_password" type="password" placeholder="••••••••••••">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn">Salvar e-mail</button>
|
||||
|
||||
<!-- Remetente opcional -->
|
||||
<div class="email-from-wrap">
|
||||
<div class="email-field">
|
||||
<label>Remetente de envio (From opcional):</label>
|
||||
<input name="smtp_from" placeholder="Ex: no_reply@seconectai.com.br ou Notificações <no_reply@seconectai.com.br>">
|
||||
<small style="color: var(--muted); font-size: 12px; margin-top: 4px;">Se deixado em branco, o Username SMTP será utilizado como remetente.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="email-actions-footer">
|
||||
<button class="btn" type="submit" id="btn-salvar-email">Salvar configurações</button>
|
||||
<button class="btn secondary" type="button" onclick="testarEConectarEmail()" id="btn-testar-email">Testar e Conectar</button>
|
||||
<button class="btn secondary" type="button" onclick="sincronizarEmailsAgora()" id="btn-sync-email">Sincronizar Respostas Agora</button>
|
||||
<span id="email-header-status-badges" style="margin-left: auto; display: flex; gap: 8px;"></span>
|
||||
</div>
|
||||
|
||||
<div id="email-status-feedback" class="email-status-feedback-box hidden"></div>
|
||||
</form>
|
||||
<div id="email-rows"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
|
||||
@@ -24,10 +24,20 @@ const Icons = {
|
||||
check: '<svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="20 6 9 17 4 12"></polyline></svg>'
|
||||
};
|
||||
|
||||
const emailKeys = ['smtp_from', 'smtp_host', 'smtp_password', 'smtp_port', 'smtp_starttls', 'smtp_user'];
|
||||
function esc(v){return String(v??'').replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c]))}
|
||||
|
||||
function getEmailFormData() {
|
||||
const data = Util.formData(emailForm);
|
||||
data.imap_allow_insecure_ssl = emailForm.elements['imap_allow_insecure_ssl']?.checked ? 'true' : 'false';
|
||||
data.smtp_allow_insecure_ssl = emailForm.elements['smtp_allow_insecure_ssl']?.checked ? 'true' : 'false';
|
||||
return data;
|
||||
}
|
||||
|
||||
const emailKeys = ['imap_host', 'imap_port', 'imap_security', 'imap_user', 'smtp_host', 'smtp_port', 'smtp_security', 'smtp_user', 'smtp_from'];
|
||||
const abacateKeys = ['abacatepay_api_key', 'abacatepay_webhook_secret'];
|
||||
|
||||
function renderConfig(target, cfg, keys, labels) {
|
||||
if (!target) return;
|
||||
target.innerHTML = keys.map(k => `<p><strong>${labels[k] || k}</strong>: ${cfg[k] || ''}</p>`).join('');
|
||||
}
|
||||
|
||||
@@ -91,17 +101,16 @@ async function load() {
|
||||
const d = await Api.get('/api/master/configuracoes');
|
||||
const cfg = d.configuracoes || {};
|
||||
for (const [k, v] of Object.entries(cfg)) {
|
||||
if (emailForm.elements[k]) emailForm.elements[k].value = v;
|
||||
if (abacateForm.elements[k]) abacateForm.elements[k].value = v;
|
||||
if (emailForm?.elements[k]) {
|
||||
if (emailForm.elements[k].type === 'checkbox') {
|
||||
emailForm.elements[k].checked = (v === 'true' || v === true);
|
||||
} else {
|
||||
emailForm.elements[k].value = v;
|
||||
}
|
||||
}
|
||||
if (abacateForm?.elements[k]) abacateForm.elements[k].value = v;
|
||||
}
|
||||
renderConfig(emailRows, cfg, emailKeys, {
|
||||
smtp_from: 'Remetente',
|
||||
smtp_host: 'Host',
|
||||
smtp_password: 'Senha',
|
||||
smtp_port: 'Porta',
|
||||
smtp_starttls: 'StartTLS',
|
||||
smtp_user: 'Usuário'
|
||||
});
|
||||
if (emailRows) renderConfig(emailRows, cfg, emailKeys, {});
|
||||
renderConfig(abacateRows, cfg, abacateKeys, {
|
||||
abacatepay_api_key: 'API Key',
|
||||
abacatepay_webhook_secret: 'Webhook Secret'
|
||||
@@ -300,15 +309,127 @@ async function rejeitar(emp, id) {
|
||||
|
||||
emailForm.onsubmit = async e => {
|
||||
e.preventDefault();
|
||||
const btn = document.querySelector('#btn-salvar-email');
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Salvando...';
|
||||
}
|
||||
try {
|
||||
await Api.put('/api/master/configuracoes', Util.formData(emailForm));
|
||||
Util.toast('Configurações de e-mail salvas');
|
||||
load();
|
||||
const payload = getEmailFormData();
|
||||
await Api.put('/api/master/configuracoes', payload);
|
||||
Util.toast('Configurações de e-mail salvas com sucesso!');
|
||||
await load();
|
||||
testarEConectarEmail();
|
||||
} catch (err) {
|
||||
Util.toast(err.message, true);
|
||||
} finally {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Salvar configurações';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.testarEConectarEmail = async () => {
|
||||
const btn = document.querySelector('#btn-testar-email');
|
||||
const feedback = document.querySelector('#email-status-feedback');
|
||||
const headerBadges = document.querySelector('#email-header-status-badges');
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Testando conexão...';
|
||||
}
|
||||
if (feedback) {
|
||||
feedback.classList.remove('hidden');
|
||||
feedback.innerHTML = '<span class="muted">Testando conexões SMTP e IMAP...</span>';
|
||||
}
|
||||
try {
|
||||
const payload = getEmailFormData();
|
||||
const res = await Api.post('/api/master/configuracoes/testar-email', payload);
|
||||
const smtpOk = !!res?.smtp?.ok;
|
||||
const imapOk = !!res?.imap?.ok;
|
||||
|
||||
if (headerBadges) {
|
||||
headerBadges.innerHTML = `
|
||||
<span class="badge ${smtpOk ? 'ok' : 'warn'}">SMTP: ${smtpOk ? 'Conectado' : 'Erro'}</span>
|
||||
<span class="badge ${imapOk ? 'ok' : 'warn'}">IMAP: ${imapOk ? 'Conectado' : 'Erro'}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
if (feedback) {
|
||||
feedback.classList.remove('hidden');
|
||||
feedback.innerHTML = `
|
||||
<div style="display: flex; gap: 12px; align-items: center; margin-bottom: 8px;">
|
||||
<strong>Status da Conexão:</strong>
|
||||
<span class="badge ${smtpOk ? 'ok' : 'warn'}">SMTP ${smtpOk ? 'Conectado' : 'Falhou'}</span>
|
||||
<span class="badge ${imapOk ? 'ok' : 'warn'}">IMAP ${imapOk ? 'Conectado' : 'Falhou'}</span>
|
||||
</div>
|
||||
<div style="font-size: 13.5px; line-height: 1.6;">
|
||||
<p style="margin: 4px 0;"><strong>Email de saída (SMTP):</strong> ${esc(res?.smtp?.mensagem || '')}</p>
|
||||
<p style="margin: 4px 0;"><strong>Email recebido (IMAP):</strong> ${esc(res?.imap?.mensagem || '')}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (res?.ok) {
|
||||
Util.toast('Conexão de e-mail (SMTP e IMAP) estabelecida com sucesso!');
|
||||
} else {
|
||||
Util.toast('Falha ao conectar em um ou mais servidores de e-mail.', true);
|
||||
}
|
||||
} catch (err) {
|
||||
if (feedback) {
|
||||
feedback.classList.remove('hidden');
|
||||
feedback.innerHTML = `<span style="color:var(--danger)">Erro ao testar conexão: ${esc(err.message)}</span>`;
|
||||
}
|
||||
Util.toast(err.message, true);
|
||||
} finally {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Testar e Conectar';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.sincronizarEmailsAgora = async () => {
|
||||
const btn = document.querySelector('#btn-sync-email');
|
||||
const feedback = document.querySelector('#email-status-feedback');
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Sincronizando...';
|
||||
}
|
||||
try {
|
||||
const res = await Api.post('/api/master/configuracoes/sincronizar-email', {});
|
||||
Util.toast(res?.mensagem || 'E-mails sincronizados com sucesso.');
|
||||
if (feedback) {
|
||||
feedback.classList.remove('hidden');
|
||||
feedback.innerHTML = `<p style="margin: 0; color: var(--brand-strong);"><strong>Sincronização de Respostas:</strong> ${esc(res?.mensagem || '')}</p>`;
|
||||
}
|
||||
} catch (err) {
|
||||
Util.toast(err.message, true);
|
||||
} finally {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Sincronizar Respostas Agora';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.querySelector('#imap-port-select')?.addEventListener('change', e => {
|
||||
const val = e.target.value;
|
||||
const sec = document.querySelector('#imap-sec-select');
|
||||
if (!sec) return;
|
||||
if (val === '993') sec.value = 'ssl_tls';
|
||||
else if (val === '143') sec.value = 'starttls';
|
||||
});
|
||||
|
||||
document.querySelector('#smtp-port-select')?.addEventListener('change', e => {
|
||||
const val = e.target.value;
|
||||
const sec = document.querySelector('#smtp-sec-select');
|
||||
if (!sec) return;
|
||||
if (val === '465') sec.value = 'ssl_tls';
|
||||
else if (val === '587') sec.value = 'starttls';
|
||||
else if (val === '25') sec.value = 'none';
|
||||
});
|
||||
|
||||
abacateForm.onsubmit = async e => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user