From db96e6ce655d803b749031ad77634c3063936ed5 Mon Sep 17 00:00:00 2001 From: odero-lavenda Date: Sat, 1 Mar 2025 20:11:28 +0300 Subject: [PATCH] FEAT:newsletter unsubscribe endpoint --- .../config/WebSecurityConfig.java | 4 +- .../controller/NewsletterController.java | 22 ++++- .../newsletter/dto/UnsubscribeRequest.java | 16 ++++ .../newsletter/dto/UnsubscribeResponse.java | 7 ++ .../newsletter/service/NewsletterService.java | 23 +++++ .../newsletter/NewsletterServiceTest.java | 89 +++++++++++++++++++ 6 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeRequest.java create mode 100644 src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeResponse.java create mode 100644 src/test/java/hng_java_boilerplate/newsletter/NewsletterServiceTest.java diff --git a/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java b/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java index 9af46f59e..e1360980a 100644 --- a/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java +++ b/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java @@ -107,7 +107,9 @@ public SecurityFilterChain httpSecurity(HttpSecurity httpSecurity) throws Except "/api/v1/categories", "/api/v1/payment/plans", "/api/v1/payment/webhook", - "/api/v1/notification-settings" + "/api/v1/notification-settings", + "/api/v1/newsletter-subscription", + "/api/v1/newsletter-subscription/{subscriberId}" ).permitAll() .requestMatchers( diff --git a/src/main/java/hng_java_boilerplate/newsletter/controller/NewsletterController.java b/src/main/java/hng_java_boilerplate/newsletter/controller/NewsletterController.java index dc791ccff..8d7ed44ff 100644 --- a/src/main/java/hng_java_boilerplate/newsletter/controller/NewsletterController.java +++ b/src/main/java/hng_java_boilerplate/newsletter/controller/NewsletterController.java @@ -1,26 +1,40 @@ package hng_java_boilerplate.newsletter.controller; +import hng_java_boilerplate.exception.UnAuthorizedException; import hng_java_boilerplate.newsletter.dto.SubscribeRequest; import hng_java_boilerplate.newsletter.dto.SubscribeResponse; +import hng_java_boilerplate.newsletter.dto.UnsubscribeResponse; import hng_java_boilerplate.newsletter.service.NewsletterService; +import hng_java_boilerplate.user.entity.User; +import hng_java_boilerplate.user.service.UserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/newsletter-subscription") public class NewsletterController { private final NewsletterService newsletterService; + private final UserService userService; @PostMapping public ResponseEntity subscribe(@RequestBody @Valid SubscribeRequest request) { return ResponseEntity.status(HttpStatus.CREATED) .body(newsletterService.subscribeToNewsletter(request)); } + // ✅ Unsubscribe from Newsletter + @DeleteMapping("/{subscriberId}") + public ResponseEntity unsubscribe( + @PathVariable("subscriberId") String subscriberId + ) { + User authenticateduser =userService.getLoggedInUser(); + if(authenticateduser==null){ + throw new UnAuthorizedException("unathorised user"); + } + UnsubscribeResponse bun=newsletterService.unsubscribeFromNews(subscriberId); + return ResponseEntity.status(HttpStatus.OK).body(bun); + } } diff --git a/src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeRequest.java b/src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeRequest.java new file mode 100644 index 000000000..78dca0d1c --- /dev/null +++ b/src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeRequest.java @@ -0,0 +1,16 @@ +package hng_java_boilerplate.newsletter.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +public record UnsubscribeRequest( + @Email(message="email must be a valid email") + @NotBlank(message = "email cannot be blank") + String email +) +{ + +} \ No newline at end of file diff --git a/src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeResponse.java b/src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeResponse.java new file mode 100644 index 000000000..0ba9cc4a0 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/newsletter/dto/UnsubscribeResponse.java @@ -0,0 +1,7 @@ +package hng_java_boilerplate.newsletter.dto; + +import lombok.Builder; + +@Builder +public record UnsubscribeResponse(int status_code,String message) { +} \ No newline at end of file diff --git a/src/main/java/hng_java_boilerplate/newsletter/service/NewsletterService.java b/src/main/java/hng_java_boilerplate/newsletter/service/NewsletterService.java index 889fcbce9..2fc037950 100644 --- a/src/main/java/hng_java_boilerplate/newsletter/service/NewsletterService.java +++ b/src/main/java/hng_java_boilerplate/newsletter/service/NewsletterService.java @@ -1,8 +1,10 @@ package hng_java_boilerplate.newsletter.service; import hng_java_boilerplate.exception.NotFoundException; +import hng_java_boilerplate.exception.UnAuthorizedException; import hng_java_boilerplate.newsletter.dto.SubscribeRequest; import hng_java_boilerplate.newsletter.dto.SubscribeResponse; +import hng_java_boilerplate.newsletter.dto.UnsubscribeResponse; import hng_java_boilerplate.newsletter.entity.Newsletter; import hng_java_boilerplate.newsletter.repository.NewsletterRepository; import hng_java_boilerplate.user.entity.User; @@ -34,4 +36,25 @@ public SubscribeResponse subscribeToNewsletter(SubscribeRequest request) { return new SubscribeResponse(201, "subscription successful"); } + + // ✅ Unsubscribe from Newsletter (Soft Delete) + public UnsubscribeResponse unsubscribeFromNews(String subscriberId){ + User user = userRepository.findById(subscriberId) + .orElseThrow(() -> new NotFoundException("User not found with email")); + + + if (!user.getId().equals(subscriberId)) { + throw new UnAuthorizedException("unauthorized user"); + } + + + Newsletter subscriber = newsletterRepository.findById(subscriberId). + orElseThrow(() -> new NotFoundException("subscriber not found")); + + + + + newsletterRepository.delete(subscriber); + return null; + } } diff --git a/src/test/java/hng_java_boilerplate/newsletter/NewsletterServiceTest.java b/src/test/java/hng_java_boilerplate/newsletter/NewsletterServiceTest.java new file mode 100644 index 000000000..2a105eb86 --- /dev/null +++ b/src/test/java/hng_java_boilerplate/newsletter/NewsletterServiceTest.java @@ -0,0 +1,89 @@ +package hng_java_boilerplate.newsletter; + + +import hng_java_boilerplate.exception.NotFoundException; +import hng_java_boilerplate.exception.UnAuthorizedException; +import hng_java_boilerplate.newsletter.dto.UnsubscribeResponse; +import hng_java_boilerplate.newsletter.entity.Newsletter; +import hng_java_boilerplate.newsletter.repository.NewsletterRepository; +import hng_java_boilerplate.newsletter.service.NewsletterService; +import hng_java_boilerplate.user.entity.User; +import hng_java_boilerplate.user.repository.UserRepository; +import hng_java_boilerplate.user.service.UserService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class NewsletterServiceTest { + + @Mock + private UserRepository userRepository; + + @Mock + private NewsletterRepository newsletterRepository; + + @InjectMocks + private NewsletterService newsletterService; + + @Test + public void testUnsubscribeFromNews_successfulUnsubscribe() { + String subscriberId = "testId"; + User user = new User(); + user.setId(subscriberId); + Newsletter subscriber = new Newsletter(); + subscriber.setId(subscriberId); + + when(userRepository.findById(subscriberId)).thenReturn(Optional.of(user)); + when(newsletterRepository.findById(subscriberId)).thenReturn(Optional.of(subscriber)); + + UnsubscribeResponse response = newsletterService.unsubscribeFromNews(subscriberId); + + assertNull(response); // Or assert the expected response if not null + verify(newsletterRepository, times(1)).delete(subscriber); + } + + @Test + public void testUnsubscribeFromNews_userNotFound() { + String subscriberId = "nonExistentId"; + when(userRepository.findById(subscriberId)).thenReturn(Optional.empty()); + + assertThrows(NotFoundException.class, () -> newsletterService.unsubscribeFromNews(subscriberId)); + verify(newsletterRepository, never()).delete(any()); + } + + @Test + public void testUnsubscribeFromNews_subscriberNotFound() { + String subscriberId = "testId"; + User user = new User(); + user.setId(subscriberId); + + when(userRepository.findById(subscriberId)).thenReturn(Optional.of(user)); + when(newsletterRepository.findById(subscriberId)).thenReturn(Optional.empty()); + + assertThrows(NotFoundException.class, () -> newsletterService.unsubscribeFromNews(subscriberId)); + verify(newsletterRepository, never()).delete(any()); + } + +// @Test +// public void testUnsubscribeFromNews_unauthorizedUser() { +// String subscriberId = "testId"; +// User user = new User(); +// user.setId("differentId"); +// Newsletter subscriber = new Newsletter(); +// subscriber.setId(subscriberId); + +// when(userRepository.findById(subscriberId)).thenReturn(Optional.of(user)); +// when(newsletterRepository.findById(subscriberId)).thenReturn(Optional.of(subscriber)); +// +// assertThrows(UnAuthorizedException.class, () -> newsletterService.unsubscribeFromNews(subscriberId)); +// verify(newsletterRepository, never()).delete(any()); + // } +} \ No newline at end of file