diff --git a/cloud/start-server.sh b/cloud/start-server.sh
index dc2a203c..431a9c9c 100644
--- a/cloud/start-server.sh
+++ b/cloud/start-server.sh
@@ -17,5 +17,7 @@ docker run -d --rm \
-e POSTGRES_HOST=$POSTGRES_HOST \
-e CORS_ALLOWED_ORIGINS=$CORS_ALLOWED_ORIGINS \
-e SERVER_SERVLET_CONTEXT_PATH=$SERVER_SERVLET_CONTEXT_PATH \
+ -e MAILGUN_APIKEY=$MAILGUN_APIKEY \
+ -e SECURITY_KEY=$SECURITY_KEY \
-e BUILD=ghcr.io/ricardo-campos-org/react-typescript-todolist/server:$PR \
ghcr.io/ricardo-campos-org/react-typescript-todolist/server:$PR
diff --git a/server/pom.xml b/server/pom.xml
index 091fe8f8..638eb34b 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -100,10 +100,6 @@
org.springframework.boot
spring-boot-starter-security
-
- org.springframework.boot
- spring-boot-starter-mail
-
diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java
index 4d44d230..ae691bb8 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java
@@ -1,36 +1,29 @@
package br.com.tasknoteapp.server.service;
import br.com.tasknoteapp.server.entity.UserEntity;
-import br.com.tasknoteapp.server.exception.MailServiceException;
import br.com.tasknoteapp.server.templates.MailgunTemplate;
import br.com.tasknoteapp.server.templates.MailgunTemplateEmailChanged;
import br.com.tasknoteapp.server.templates.MailgunTemplateResetPwd;
import br.com.tasknoteapp.server.templates.MailgunTemplateResetPwdConfirm;
import br.com.tasknoteapp.server.templates.MailgunTemplateSignUp;
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-import java.util.Base64;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.restclient.RestTemplateBuilder;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.client.RestClient;
/** This service handles email messages for Mailgun. */
@Service
public class MailgunEmailService {
private static final Logger logger = LoggerFactory.getLogger(MailgunEmailService.class.getName());
- private final RestTemplate restTemplate;
+ private final RestClient restClient;
private final String targetEnv;
private final String domain;
private String senderEmail;
@@ -42,23 +35,37 @@ public class MailgunEmailService {
* @param domain The domain to send email from.
* @param sender The from option.
* @param targetEnv The environment.
- * @param templateBuilder The template builder.
+ * @param restClientBuilder The rest client builder.
*/
public MailgunEmailService(
@Value("${mailgun.api-key}") String apiKey,
@Value("${mailgun.domain}") String domain,
@Value("${mailgun.sender-email}") String sender,
@Value("${br.com.tasknote.server.target-env}") String targetEnv,
- RestTemplateBuilder templateBuilder) {
+ RestClient.Builder restClientBuilder) {
this.domain = domain;
this.senderEmail = sender;
this.targetEnv = targetEnv;
- this.restTemplate =
- templateBuilder
- .connectTimeout(Duration.ofSeconds(5))
- .readTimeout(Duration.ofSeconds(10))
- .defaultHeader(HttpHeaders.AUTHORIZATION, basicAuth("api", apiKey))
- .build();
+
+ if (apiKey != null && apiKey.length() > 6) {
+ logger.info(
+ "Mailgun API Key loaded: {}...{}",
+ apiKey.substring(0, 3),
+ apiKey.substring(apiKey.length() - 3));
+ } else {
+ logger.warn("Mailgun API Key is missing or too short!");
+ }
+
+ this.restClient = restClientBuilder
+ .baseUrl("https://api.mailgun.net/v3/" + domain)
+ .defaultStatusHandler(HttpStatusCode::isError, (request, response) -> {
+ logger.error(
+ "Mailgun API Error: {} {}",
+ response.getStatusCode(),
+ response.getStatusText());
+ })
+ .defaultHeaders(headers -> headers.setBasicAuth("api", apiKey))
+ .build();
}
/**
@@ -141,16 +148,11 @@ public void sendEmailChangedNotification(UserEntity user, String oldEmail) {
*
* @param to The target email address.
* @param subject The message subject.
- * @param textBody The message text body to be displayed.
- * @param htmlBody The message html body to be rendered.
+ * @param template The mailgun template.
*/
private void sendEmail(String to, String subject, MailgunTemplate template) {
- String url = "https://api.mailgun.net/v3/" + domain + "/messages";
String from = "TaskNote App <" + senderEmail + ">";
- HttpHeaders headers = new HttpHeaders();
- headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-
MultiValueMap mailData = new LinkedMultiValueMap<>();
mailData.add("from", from);
mailData.add("to", to);
@@ -164,26 +166,22 @@ private void sendEmail(String to, String subject, MailgunTemplate template) {
logger.info("JSON template variables: {}", template.getVariableValuesJson());
}
- HttpEntity> request = new HttpEntity<>(mailData, headers);
-
try {
- ResponseEntity response = restTemplate.postForEntity(url, request, String.class);
-
- if (!response.getStatusCode().is2xxSuccessful()) {
- throw new MailServiceException("Failed to send email: " + response.getStatusCode());
- }
+ restClient.post()
+ .uri("/messages")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .body(mailData)
+ .retrieve()
+ .toBodilessEntity();
logger.info("Email message send successfully.");
} catch (HttpClientErrorException ex) {
- logger.error("Unable to send email: {} - {}", ex.getMessage(), ex.getCause());
+ logger.error("Unable to send email: {} - {}", ex.getMessage(), ex.getStatusCode());
+ } catch (Exception ex) {
+ logger.error("Unexpected error sending email: {}", ex.getMessage());
}
}
- private String basicAuth(String username, String password) {
- String auth = username + ":" + password;
- return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
- }
-
private String getBaseUrl() {
if ("development".equals(targetEnv) || Objects.isNull(targetEnv)) {
return "http://localhost:5000";
diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java
index 9afb562c..0c58e88c 100644
--- a/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java
+++ b/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java
@@ -2,7 +2,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -13,34 +12,43 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.http.HttpStatusCode;
-import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.client.RestClient;
-/** Test class generated by Copilot. I had to tweak it, to make it work. */
+/** Test class for MailgunEmailService using RestClient. */
@ExtendWith(MockitoExtension.class)
class MailgunEmailServiceTest {
- @Mock private RestTemplate restTemplate;
- @Mock private RestTemplateBuilder restTemplateBuilder;
+ @Mock private RestClient restClient;
+ @Mock private RestClient.Builder restClientBuilder;
+ @Mock private RestClient.RequestBodyUriSpec requestBodyUriSpec;
+ @Mock private RestClient.RequestBodySpec requestBodySpec;
+ @Mock private RestClient.ResponseSpec responseSpec;
private MailgunEmailService mailgunEmailService;
@BeforeEach
void setUp() {
- when(restTemplateBuilder.connectTimeout(any())).thenReturn(restTemplateBuilder);
- when(restTemplateBuilder.readTimeout(any())).thenReturn(restTemplateBuilder);
- when(restTemplateBuilder.defaultHeader(any(), any())).thenReturn(restTemplateBuilder);
- when(restTemplateBuilder.build()).thenReturn(restTemplate);
+ when(restClientBuilder.baseUrl(anyString())).thenReturn(restClientBuilder);
+ when(restClientBuilder.defaultStatusHandler(any(), any())).thenReturn(restClientBuilder);
+ when(restClientBuilder.defaultHeaders(any())).thenReturn(restClientBuilder);
+ when(restClientBuilder.build()).thenReturn(restClient);
String apiKey = "abx123";
String domain = "domain.com";
String sender = "no-reply@domain.com";
String target = "development";
mailgunEmailService =
- new MailgunEmailService(apiKey, domain, sender, target, restTemplateBuilder);
+ new MailgunEmailService(apiKey, domain, sender, target, restClientBuilder);
+ }
+
+ private void setupMockChain() {
+ when(restClient.post()).thenReturn(requestBodyUriSpec);
+ when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodySpec);
+ when(requestBodySpec.contentType(any())).thenReturn(requestBodySpec);
+ when(requestBodySpec.body(any())).thenReturn(requestBodySpec);
+ when(requestBodySpec.retrieve()).thenReturn(responseSpec);
}
@Test
@@ -49,12 +57,11 @@ void testSendResetPassword() {
user.setEmail("test@example.com");
user.setResetToken("reset-token");
- when(restTemplate.postForEntity(anyString(), any(), eq(String.class)))
- .thenReturn(ResponseEntity.ok("Success"));
+ setupMockChain();
mailgunEmailService.sendResetPassword(user);
- verify(restTemplate, times(1)).postForEntity(anyString(), any(), eq(String.class));
+ verify(restClient, times(1)).post();
}
@Test
@@ -62,12 +69,11 @@ void testSendPasswordResetConfirmation() {
UserEntity user = new UserEntity();
user.setEmail("test@example.com");
- when(restTemplate.postForEntity(anyString(), any(), eq(String.class)))
- .thenReturn(ResponseEntity.ok("Success"));
+ setupMockChain();
mailgunEmailService.sendPasswordResetConfirmation(user);
- verify(restTemplate, times(1)).postForEntity(anyString(), any(), eq(String.class));
+ verify(restClient, times(1)).post();
}
@Test
@@ -76,12 +82,11 @@ void testSendNewUser() {
user.setEmail("test@example.com");
user.setEmailUuid(java.util.UUID.randomUUID());
- when(restTemplate.postForEntity(anyString(), any(), eq(String.class)))
- .thenReturn(ResponseEntity.ok("Success"));
+ setupMockChain();
mailgunEmailService.sendNewUser(user);
- verify(restTemplate, times(1)).postForEntity(anyString(), any(), eq(String.class));
+ verify(restClient, times(1)).post();
}
@Test
@@ -90,12 +95,13 @@ void testSendEmailHandlesHttpClientErrorException() {
user.setEmail("test@example.com");
user.setResetToken("reset-token");
- when(restTemplate.postForEntity(anyString(), any(), eq(String.class)))
+ setupMockChain();
+ when(responseSpec.toBodilessEntity())
.thenThrow(new HttpClientErrorException(HttpStatusCode.valueOf(400)));
mailgunEmailService.sendResetPassword(user);
- verify(restTemplate, times(1)).postForEntity(anyString(), any(), eq(String.class));
+ verify(restClient, times(1)).post();
}
@Test
@@ -103,13 +109,12 @@ void testSendEmailChanged() {
UserEntity user = new UserEntity();
user.setEmail("test@example.com");
- when(restTemplate.postForEntity(anyString(), any(), eq(String.class)))
- .thenReturn(ResponseEntity.ok("Success"));
+ setupMockChain();
String oldEmail = "old@example.com";
mailgunEmailService.sendEmailChangedNotification(user, oldEmail);
- verify(restTemplate, times(1)).postForEntity(anyString(), any(), eq(String.class));
+ verify(restClient, times(1)).post();
}
}