Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public SecurityFilterChain httpSecurity(HttpSecurity httpSecurity) throws Except

"/api/v1/auth/logout",
"/api/v1/organisations/**",
"/api/v1/newsletter-subscription/**",
"/api/v1/payment/stripe/**",
"/api/v1/accounts/**",
"api/v1/auth/2fa/**",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import hng_java_boilerplate.exception.ValidationError;
import hng_java_boilerplate.newsletter.dto.SubscribeRequest;
import hng_java_boilerplate.newsletter.dto.SubscribeResponse;
import hng_java_boilerplate.newsletter.dto.SubscribersResponse;
import hng_java_boilerplate.newsletter.service.NewsletterService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -16,10 +17,7 @@
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
Expand All @@ -42,4 +40,12 @@ public ResponseEntity<SubscribeResponse> subscribe(@RequestBody @Valid Subscribe
return ResponseEntity.status(HttpStatus.CREATED)
.body(newsletterService.subscribeToNewsletter(request));
}

@GetMapping
public ResponseEntity<SubscribersResponse> getSubscribers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
SubscribersResponse response = newsletterService.getSubscribersResponse(page, size);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hng_java_boilerplate.newsletter.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
@Builder
@AllArgsConstructor
public class SubscribersDto {
private String id;
private String email;
private LocalDateTime subscribedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package hng_java_boilerplate.newsletter.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
@Builder
@AllArgsConstructor
public class SubscribersResponse {
private List<SubscribersDto> subscribers;
private int page;
private int size;
private long totalElements;
private int totalPages;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package hng_java_boilerplate.newsletter.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "newsletters")
public class Newsletter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
import hng_java_boilerplate.exception.NotFoundException;
import hng_java_boilerplate.newsletter.dto.SubscribeRequest;
import hng_java_boilerplate.newsletter.dto.SubscribeResponse;
import hng_java_boilerplate.newsletter.dto.SubscribersDto;
import hng_java_boilerplate.newsletter.dto.SubscribersResponse;
import hng_java_boilerplate.newsletter.entity.Newsletter;
import hng_java_boilerplate.newsletter.repository.NewsletterRepository;
import hng_java_boilerplate.user.entity.User;
import hng_java_boilerplate.user.repository.UserRepository;
import hng_java_boilerplate.user.serviceImpl.EmailServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand All @@ -34,4 +39,40 @@ public SubscribeResponse subscribeToNewsletter(SubscribeRequest request) {

return new SubscribeResponse(201, "subscription successful");
}

public SubscribersResponse getSubscribersResponse(int page, int size) {
Pageable pageable = buildPageable(page, size);
Page<Newsletter> newsletterPage = newsletterRepository.findAll(pageable);
List<SubscribersDto> subscriberDto = mapNewslettersToSubscribers(newsletterPage.getContent());

return buildSubscribersResponse(newsletterPage, subscriberDto);
}

private Pageable buildPageable(int page, int size) {
return PageRequest.of(page, size, Sort.by("createdAt").descending());
}

private List<SubscribersDto> mapNewslettersToSubscribers(List<Newsletter> newsletters) {
return newsletters.stream()
.map(newsletter -> {
User user = userRepository.findById(newsletter.getUserId())
.orElseThrow(() -> new NotFoundException("User not found for subscription id: " + newsletter.getId()));
return SubscribersDto.builder()
.id(newsletter.getId())
.email(user.getEmail())
.subscribedAt(newsletter.getCreatedAt())
.build();
})
.collect(Collectors.toList());
}

private SubscribersResponse buildSubscribersResponse(Page<Newsletter> newsletterPage, List<SubscribersDto> subscriberDtos) {
return SubscribersResponse.builder()
.subscribers(subscriberDtos)
.page(newsletterPage.getNumber())
.size(newsletterPage.getSize())
.totalElements(newsletterPage.getTotalElements())
.totalPages(newsletterPage.getTotalPages())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package hng_java_boilerplate.newsletter.service;

import hng_java_boilerplate.exception.NotFoundException;
import hng_java_boilerplate.newsletter.dto.SubscribersResponse;
import hng_java_boilerplate.newsletter.entity.Newsletter;
import hng_java_boilerplate.newsletter.repository.NewsletterRepository;
import hng_java_boilerplate.user.entity.User;
import hng_java_boilerplate.user.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
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 org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class NewsletterServiceTest {

@Mock
private NewsletterRepository newsletterRepository;

@Mock
private UserRepository userRepository;

@InjectMocks
private NewsletterService newsletterService;

private Newsletter newsletter1;
private Newsletter newsletter2;
private User user1;
private User user2;

@BeforeEach
void setUp() {
user1 = new User();
user1.setId("user1");
user1.setEmail("[email protected]");

user2 = new User();
user2.setId("user2");
user2.setEmail("[email protected]");

newsletter1 = Newsletter.builder()
.id("newsletter1")
.userId("user1")
.createdAt(LocalDateTime.of(2021, 1, 1, 0, 0))
.build();

newsletter2 = Newsletter.builder()
.id("newsletter2")
.userId("user2")
.createdAt(LocalDateTime.of(2021, 2, 15, 12, 30))
.build();
}

@Test
void getSubscribersResponse_shouldReturnCorrectResponse() {
int page = 0;
int size = 10;

List<Newsletter> newsletters = List.of(newsletter1, newsletter2);
Pageable pageable = PageRequest.of(page, size);
Page<Newsletter> newsletterPage = new PageImpl<>(newsletters, pageable, newsletters.size());

when(newsletterRepository.findAll(any(Pageable.class))).thenReturn(newsletterPage);
when(userRepository.findById("user1")).thenReturn(Optional.of(user1));
when(userRepository.findById("user2")).thenReturn(Optional.of(user2));

SubscribersResponse response = newsletterService.getSubscribersResponse(page, size);

assertNotNull(response);
assertEquals(page, response.getPage());
assertEquals(size, response.getSize());
assertEquals(newsletters.size(), response.getTotalElements());
assertEquals(1, response.getTotalPages());
assertNotNull(response.getSubscribers());
assertEquals(2, response.getSubscribers().size());

boolean foundNewsletter1 = response.getSubscribers().stream()
.anyMatch(dto -> dto.getId().equals("newsletter1")
&& dto.getEmail().equals("[email protected]")
&& dto.getSubscribedAt().equals(newsletter1.getCreatedAt()));
boolean foundNewsletter2 = response.getSubscribers().stream()
.anyMatch(dto -> dto.getId().equals("newsletter2")
&& dto.getEmail().equals("[email protected]")
&& dto.getSubscribedAt().equals(newsletter2.getCreatedAt()));

assertTrue(foundNewsletter1);
assertTrue(foundNewsletter2);

verify(newsletterRepository, times(1)).findAll(any(Pageable.class));
verify(userRepository, times(1)).findById("user1");
verify(userRepository, times(1)).findById("user2");
}

@Test
void getSubscribersResponse_shouldThrowNotFoundException_whenUserNotFound() {
int page = 0;
int size = 10;

List<Newsletter> newsletters = List.of(newsletter1);
Pageable pageable = PageRequest.of(page, size);
Page<Newsletter> newsletterPage = new PageImpl<>(newsletters, pageable, newsletters.size());

when(newsletterRepository.findAll(any(Pageable.class))).thenReturn(newsletterPage);
when(userRepository.findById("user1")).thenReturn(Optional.empty());

NotFoundException exception = assertThrows(NotFoundException.class,
() -> newsletterService.getSubscribersResponse(page, size));

assertEquals("User not found for subscription id: " + newsletter1.getId(), exception.getMessage());
verify(newsletterRepository, times(1)).findAll(any(Pageable.class));
verify(userRepository, times(1)).findById("user1");
}
}
Loading