diff --git a/application/src/main/java/com/pablotj/restemailbridge/application/usecase/SendEmailUseCase.java b/application/src/main/java/com/pablotj/restemailbridge/application/usecase/SendEmailUseCase.java index c98c574..67a9279 100644 --- a/application/src/main/java/com/pablotj/restemailbridge/application/usecase/SendEmailUseCase.java +++ b/application/src/main/java/com/pablotj/restemailbridge/application/usecase/SendEmailUseCase.java @@ -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. + *
+ * 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); } -} +} \ No newline at end of file diff --git a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/config/EmailConfigurationAdapter.java b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/EmailConfigurationAdapter.java similarity index 73% rename from infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/config/EmailConfigurationAdapter.java rename to infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/EmailConfigurationAdapter.java index cff4675..8a09bdc 100644 --- a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/config/EmailConfigurationAdapter.java +++ b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/EmailConfigurationAdapter.java @@ -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"; } } \ No newline at end of file diff --git a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/GmailOAuth2MailService.java b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/GmailOAuth2MailService.java index 5cb8221..2a00b93 100644 --- a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/GmailOAuth2MailService.java +++ b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/GmailOAuth2MailService.java @@ -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. + *
+ * 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
+ * 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();
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/config/GmailOAuth2Properties.java b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/config/GmailOAuth2Properties.java
new file mode 100644
index 0000000..cd7e555
--- /dev/null
+++ b/infrastructure/src/main/java/com/pablotj/restemailbridge/infrastructure/mail/config/GmailOAuth2Properties.java
@@ -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
+) {}