refactor(core): reorganize code and implement logging

This commit is contained in:
Pablo de la Torre Jamardo 2025-09-13 12:31:35 +02:00
parent 6445278e9f
commit 45a98daae0
4 changed files with 213 additions and 7 deletions

View File

@ -5,20 +5,47 @@ import com.pablotj.restemailbridge.application.port.EmailConfigurationPort;
import com.pablotj.restemailbridge.domain.model.Email;
import com.pablotj.restemailbridge.domain.repository.EmailRepository;
import com.pablotj.restemailbridge.domain.service.EmailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Use case for sending emails.
* <p>
* Retrieves the default recipient from EmailConfigurationPort, sends the email using EmailService,
* and persists it via EmailRepository.
*/
public class SendEmailUseCase {
private static final Logger log = LoggerFactory.getLogger(SendEmailUseCase.class);
private final EmailConfigurationPort emailConfigurationPort;
private final EmailService emailService;
private final EmailRepository emailRepository;
public SendEmailUseCase(EmailConfigurationPort emailConfigurationPort, EmailService emailService, EmailRepository emailRepository) {
/**
* Constructor injecting required ports.
*
* @param emailConfigurationPort Port to retrieve configuration
* @param emailService Service to send emails
* @param emailRepository Repository to persist emails
*/
public SendEmailUseCase(EmailConfigurationPort emailConfigurationPort,
EmailService emailService,
EmailRepository emailRepository) {
this.emailConfigurationPort = emailConfigurationPort;
this.emailService = emailService;
this.emailRepository = emailRepository;
}
/**
* Handles sending an email based on the provided DTO.
*
* @param emailDTO DTO containing from, subject, and body
*/
public void handle(EmailDTO emailDTO) {
String to = emailConfigurationPort.getDefaultRecipient();
log.info("Sending email from {} to {}", emailDTO.from(), to);
Email email = emailService.sendEmail(
Email.builder()
.from(emailDTO.from())
@ -27,6 +54,8 @@ public class SendEmailUseCase {
.body(emailDTO.body())
.build()
);
emailRepository.save(email);
emailRepository.save(email); // Persist the sent email
log.info("Email successfully sent and persisted to repository for recipient {}", to);
}
}
}

View File

@ -1,4 +1,4 @@
package com.pablotj.restemailbridge.infrastructure.config;
package com.pablotj.restemailbridge.infrastructure.mail;
import com.pablotj.restemailbridge.application.port.EmailConfigurationPort;
import org.springframework.stereotype.Component;
@ -7,6 +7,6 @@ import org.springframework.stereotype.Component;
public class EmailConfigurationAdapter implements EmailConfigurationPort {
@Override
public String getDefaultRecipient() {
return "1234@1234.com";
return "pablodelatorree@gmail.com";
}
}

View File

@ -1,15 +1,182 @@
package com.pablotj.restemailbridge.infrastructure.mail;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.GmailScopes;
import com.google.api.services.gmail.model.Message;
import com.pablotj.restemailbridge.domain.model.Email;
import com.pablotj.restemailbridge.domain.service.EmailService;
import com.pablotj.restemailbridge.infrastructure.exception.GmailConfigurationException;
import com.pablotj.restemailbridge.infrastructure.exception.GmailInitializationException;
import com.pablotj.restemailbridge.infrastructure.exception.GmailSendErrorException;
import com.pablotj.restemailbridge.infrastructure.mail.config.GmailOAuth2Properties;
import jakarta.annotation.PostConstruct;
import jakarta.mail.MessagingException;
import jakarta.mail.Session;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
/**
* Gmail implementation of the EmailService using OAuth2 authentication.
* <p>
* Handles sending emails via Gmail API and manages credentials stored in a local token folder.
*/
@Component
public class GmailOAuth2MailService implements EmailService {
private static final Logger log = LoggerFactory.getLogger(GmailOAuth2MailService.class);
private static final String APPLICATION_NAME = "MailServiceApi";
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_SEND);
private static final String TOKENS_DIRECTORY_PATH = "/app/tokens";
private final GmailOAuth2Properties properties;
private Gmail service;
/**
* Constructor injecting Gmail OAuth2 properties.
*
* @param properties OAuth2 configuration for Gmail
*/
public GmailOAuth2MailService(GmailOAuth2Properties properties) {
this.properties = properties;
}
/**
* Initializes the Gmail client with OAuth2 credentials.
* <p>
* Loads the token from the token folder, sets up the OAuth2 flow,
* and initializes the Gmail API client.
*
* @throws GmailConfigurationException if token folder or credentials are missing
* @throws GmailInitializationException if any other initialization error occurs
*/
@PostConstruct
public void init() {
try {
log.info("Initializing Gmail OAuth2 client...");
GoogleClientSecrets clientSecrets = new GoogleClientSecrets()
.setInstalled(
new GoogleClientSecrets.Details()
.setClientId(properties.clientId())
.setClientSecret(properties.clientSecret())
.setRedirectUris(Collections.singletonList(properties.redirectUri()))
);
File tokenFolder = new File(TOKENS_DIRECTORY_PATH);
if (!tokenFolder.exists() || !tokenFolder.isDirectory()) {
log.error("Token folder not found: {}", TOKENS_DIRECTORY_PATH);
throw new GmailConfigurationException("Token folder missing: " + TOKENS_DIRECTORY_PATH);
}
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
GoogleNetHttpTransport.newTrustedTransport(),
JSON_FACTORY,
clientSecrets,
SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(tokenFolder))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
if (credential == null) {
log.error("No stored credentials found. Generate tokens first.");
throw new GmailConfigurationException("No stored credentials found. Generate tokens first.");
}
service = new Gmail.Builder(GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
log.info("Gmail OAuth2 client initialized successfully.");
} catch (Exception e) {
if (e instanceof GmailConfigurationException configEx) {
throw configEx;
}
log.error("Failed to initialize Gmail client");
throw new GmailInitializationException("Failed to initialize Gmail client", e);
}
}
/**
* Sends an email using the Gmail API.
*
* @param email Email object containing recipient, sender, subject, and body
* @return The same Email object after sending
* @throws GmailSendErrorException if sending fails
*/
@Override
public Email sendEmail(Email email) {
System.out.println("Sending email " + email.getSubject() + " to " + email.getTo());
try {
log.info("Sending email to {}", email.getTo());
MimeMessage message = createEmail(email.getTo(), email.getFrom(), email.getSubject(), email.getBody());
sendMessage(service, message);
log.info("Email sent successfully to {}", email.getTo());
} catch (Exception e) {
log.error("Failed to send email to {}", email.getTo());
throw new GmailSendErrorException("Failed to send email", e);
}
return email;
}
}
/**
* Creates a MimeMessage from the given parameters.
*
* @param to Recipient email
* @param from Sender email
* @param subject Email subject
* @param bodyText Email body
* @return MimeMessage ready to be sent
* @throws MessagingException if creation fails
*/
private static MimeMessage createEmail(String to, String from, String subject, String bodyText) throws MessagingException {
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
MimeMessage email = new MimeMessage(session);
email.setReplyTo(new jakarta.mail.Address[]{new InternetAddress(from)});
email.setFrom(new InternetAddress(from));
email.addRecipient(jakarta.mail.Message.RecipientType.TO, new InternetAddress(to));
email.setSubject(subject);
email.setText(bodyText);
return email;
}
/**
* Sends the MimeMessage using the Gmail API.
*
* @param service Gmail client
* @param email MimeMessage to send
* @throws MessagingException if message creation fails
* @throws IOException if API call fails
*/
private static void sendMessage(Gmail service, MimeMessage email) throws MessagingException, IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
email.writeTo(buffer);
String encodedEmail = Base64.getUrlEncoder().encodeToString(buffer.toByteArray());
Message message = new Message();
message.setRaw(encodedEmail);
service.users().messages().send("me", message).execute();
}
}

View File

@ -0,0 +1,10 @@
package com.pablotj.restemailbridge.infrastructure.mail.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "gmail.oauth2")
public record GmailOAuth2Properties(
String clientId,
String clientSecret,
String redirectUri
) {}