Skip to content
Merged
2 changes: 2 additions & 0 deletions Contributors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Tachtwitch
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package hng_java_boilerplate.newsletter.controller;


import hng_java_boilerplate.exception.BadRequestException;
import hng_java_boilerplate.newsletter.dto.DeleteRequest;
import hng_java_boilerplate.newsletter.dto.SubscribeRequest;
import hng_java_boilerplate.newsletter.dto.SubscribeResponse;
import hng_java_boilerplate.newsletter.entity.Newsletter;
import hng_java_boilerplate.newsletter.service.NewsletterService;
import hng_java_boilerplate.user.dto.response.Response;

import hng_java_boilerplate.categories.dto.CategoryDto;
import hng_java_boilerplate.exception.ErrorResponseDto;
import hng_java_boilerplate.exception.ValidationError;
Expand All @@ -13,16 +22,31 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.ResponseEnti
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/newsletter")

import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/newsletter-subscription")
@Tag(name = "NewsLetter", description = "controller for newsletter")

public class NewsletterController {
private final NewsletterService newsletterService;

Expand All @@ -41,11 +65,34 @@ public ResponseEntity<SubscribeResponse> subscribe(@RequestBody @Valid Subscribe
.body(newsletterService.subscribeToNewsletter(request));
}


@GetMapping("/user/{userId}")
public ResponseEntity<Page<Newsletter>> getNewslettersByUserId(@PathVariable String userId, @PageableDefault(sort = "user_id", direction = Sort.Direction.DESC)Pageable pageable) {
return ResponseEntity.ok(newsletterService.findNewsletterByUserId(userId,pageable));
}

@GetMapping("/date/{date}")
public ResponseEntity<Page<Newsletter>> getNewslettersAfterDate(@PathVariable LocalDateTime date, @PageableDefault(sort = "created_at",direction = Sort.Direction.DESC)Pageable pageable) {
return ResponseEntity.ok(newsletterService.findNewsletterByCreatedAtAfter(date,pageable));
}

@PreAuthorize("hasRole('ROLE_SUPER_ADMIN')")
@DeleteMapping("/delete/{userId}")
public ResponseEntity<?> deleteNewslettersById(@Valid @RequestBody DeleteRequest request) {
String user_id = request.getUser_id();
if(user_id.isEmpty()){
throw new BadRequestException("user id is required");
}else {
Response<?> response = newsletterService.deleteNewsletterByUserId(user_id);
return ResponseEntity.status(HttpStatus.OK).body(response);
}

@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,10 @@
package hng_java_boilerplate.newsletter.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class DeleteRequest {
private String user_id;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package hng_java_boilerplate.newsletter.entity;

import hng_java_boilerplate.user.entity.User;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
Expand All @@ -15,15 +16,26 @@
@Entity
@Table(name = "newsletters")
public class Newsletter {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;

@ManyToOne
@JoinColumn(name = "user_id")
private User user;

@Column(nullable = false)
private String userId;
@CreationTimestamp
private String title;

@Column(nullable = false)
private String content;

@CreationTimestamp
@Column(name = "created_at",nullable = false)
private LocalDateTime createdAt;
@Column

@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
package hng_java_boilerplate.newsletter.repository;


import hng_java_boilerplate.newsletter.entity.Newsletter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDateTime;

public interface NewsletterRepository extends JpaRepository<Newsletter, String> {
}

Page<Newsletter> findByUser_Id(String userId, Pageable page);

@Query("SELECT n FROM Newsletter n WHERE n.createdAt > :date")
<Optional> Page<Newsletter> findNewsletterByCreatedAtAfter(@Param("date") LocalDateTime date, Pageable page);

void deleteByUser_Id(String userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
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.dto.response.Response;
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.Page;
import org.springframework.data.domain.Pageable;

import org.springframework.data.domain.*;

import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
Expand All @@ -30,7 +36,7 @@ public SubscribeResponse subscribeToNewsletter(SubscribeRequest request) {
.orElseThrow(() -> new NotFoundException("user not found with email"));

Newsletter newsletter = new Newsletter();
newsletter.setUserId(user.getId());
newsletter.setUser(user);
newsletter.setCreatedAt(LocalDateTime.now());
newsletter.setUpdatedAt(LocalDateTime.now());
newsletterRepository.saveAndFlush(newsletter);
Expand All @@ -40,6 +46,19 @@ public SubscribeResponse subscribeToNewsletter(SubscribeRequest request) {
return new SubscribeResponse(201, "subscription successful");
}


public Page<Newsletter> findNewsletterByUserId(String userId, Pageable pageable){
return newsletterRepository.findByUser_Id(userId,pageable);
}

public Page<Newsletter> findNewsletterByCreatedAtAfter(LocalDateTime date, Pageable pageable){
return newsletterRepository.findNewsletterByCreatedAtAfter(date,pageable);
}

public Response<?> deleteNewsletterByUserId(String userId){
newsletterRepository.deleteByUser_Id(userId);
return Response.builder().status_code("success").message("Newsletter deleted successfully.").build();

public SubscribersResponse getSubscribersResponse(int page, int size) {
Pageable pageable = buildPageable(page, size);
Page<Newsletter> newsletterPage = newsletterRepository.findAll(pageable);
Expand Down Expand Up @@ -74,5 +93,6 @@ private SubscribersResponse buildSubscribersResponse(Page<Newsletter> newsletter
.totalElements(newsletterPage.getTotalElements())
.totalPages(newsletterPage.getTotalPages())
.build();

}
}
13 changes: 13 additions & 0 deletions src/main/java/hng_java_boilerplate/user/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hng_java_boilerplate.user.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import hng_java_boilerplate.newsletter.entity.Newsletter;
import hng_java_boilerplate.organisation.entity.Organisation;
import hng_java_boilerplate.plans.entity.Plan;
import hng_java_boilerplate.product.entity.Product;
Expand Down Expand Up @@ -122,4 +123,16 @@ public boolean isCredentialsNonExpired() {
public boolean isEnabled() {
return this.isEnabled;
}

@JsonIgnore
@OneToMany(mappedBy = "user",cascade = CascadeType.ALL)
private List<Newsletter> myNewsletters;

@ManyToMany
@JoinTable(
name = "subscribers",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "newsletter_id")
)
private List<Newsletter> newsletters;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE newsletters
ADD COLUMN title VARCHAR(255),
ADD COLUMN content TEXT
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE newsletters
DROP COLUMN user_id
15 changes: 15 additions & 0 deletions src/main/resources/db/migration/V51__create_subscription_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ALTER TABLE newsletters
ADD COLUMN user_id VARCHAR(50),
ADD CONSTRAINT fk_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
ON UPDATE CASCADE;

CREATE TABLE subscribers(
user_id VARCHAR(50),
newsletter_id VARCHAR(50),
PRIMARY KEY (user_id, newsletter_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (newsletter_id) REFERENCES newsletters(id),
subscribed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package hng_java_boilerplate.newsletter.unit_test;

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.dto.response.Response;
import hng_java_boilerplate.user.entity.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
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.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.*;

public class NewsletterTest {

@InjectMocks
private NewsletterService newsletterService;
@Mock
private NewsletterRepository newsletterRepository;

private Newsletter newsletter1;
private Newsletter newsletter2;

@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
User user = new User();
user.setId("U1");
user.setName("John Doe");
user.setEmail("johndoe@example.com");
user.setCreatedAt(LocalDateTime.now());

newsletter1 = new Newsletter();
newsletter1.setUser(user);
newsletter1.setCreatedAt(LocalDateTime.now());
newsletter1.setId("1");
newsletter1.setTitle("Newsletter test");
newsletter1.setUpdatedAt(LocalDateTime.now());
newsletter1.setContent("this a test content for the newsletter");

newsletter2 = new Newsletter();
newsletter1.setUser(user);
newsletter2.setCreatedAt(LocalDateTime.now());
newsletter2.setId("2");
newsletter2.setUpdatedAt(LocalDateTime.now());
newsletter2.setTitle("Newsletter test2");
newsletter2.setContent("this a second test content for the newsletter");
}

@Test
void testFindByUserId(){
List<Newsletter> newsletters = Arrays.asList(newsletter1,newsletter2);
Page<Newsletter> page = new PageImpl<>(newsletters);
Pageable pageable = PageRequest.of(0,1);

when(newsletterRepository.findByUser_Id("U1",pageable)).thenReturn(page);

Page<Newsletter> result = newsletterService.findNewsletterByUserId(newsletter1.getUser().getId(),pageable);

assertNotNull(result);
assertEquals(1,result.getTotalPages());
verify(newsletterRepository, times(1)).findByUser_Id(newsletter1.getUser().getId(),pageable);
}

@Test
void testFindByCreatedAfter(){
List<Newsletter> newsletters = Arrays.asList(newsletter1,newsletter2);
Page<Newsletter> page = new PageImpl<>(newsletters,PageRequest.of(0,1),2);
LocalDateTime date = LocalDateTime.parse("2025-02-28T11:44:32.180026100");
when(newsletterRepository.findNewsletterByCreatedAtAfter(date,page.getPageable())).thenReturn(page);

Page<Newsletter> result = newsletterService.findNewsletterByCreatedAtAfter(date,page.getPageable());

assertNotNull(result);
verify(newsletterRepository, times(1)).findNewsletterByCreatedAtAfter(date,page.getPageable());
}

@Test
void testDeleteByUserId(){
String userId = newsletter1.getUser().getId();

Response<?> response = newsletterService.deleteNewsletterByUserId(userId);

assertEquals("success", response.getStatus_code());
assertEquals("Newsletter deleted successfully.", response.getMessage());
verify(newsletterRepository, times(1)).deleteByUser_Id(userId);
}
}
Loading