diff --git a/src/main/java/com/aselimov/NS/controller/NotificationController.java b/src/main/java/com/aselimov/NS/controller/NotificationController.java index 8d6fb96..c9e6968 100644 --- a/src/main/java/com/aselimov/NS/controller/NotificationController.java +++ b/src/main/java/com/aselimov/NS/controller/NotificationController.java @@ -1,20 +1,71 @@ package com.aselimov.NS.controller; -import com.aselimov.NS.model.Notification; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.aselimov.NS.model.Notification; +import com.aselimov.NS.service.NotificationService; @RestController @RequestMapping("/api/notifications") public class NotificationController { + private final NotificationService notificationService; + + @Autowired + public NotificationController(final NotificationService notificationService) { + this.notificationService = notificationService; + } + @PostMapping - public ResponseEntity createNotification(@RequestBody Notification notification) { - return ResponseEntity.ok("Notification received: " + notification.getSubject()); + public ResponseEntity createNotification(@RequestBody final Notification notification) { + final Notification savedNotification = notificationService.saveNotification(notification); + return new ResponseEntity(savedNotification, HttpStatus.CREATED); } @GetMapping("/health") public ResponseEntity healthCheck() { return ResponseEntity.ok("Notification service running..."); } + + @GetMapping + public ResponseEntity> getAllNotifications() { + return ResponseEntity.ok(notificationService.getAllNotifications()); + } + + @GetMapping("/{id}") + public ResponseEntity getNotificationById(@PathVariable final String id) { + return ResponseEntity.ok(notificationService.getNotificationByIdOrThrow(id)); + } + + @GetMapping("/status/{status}") + public ResponseEntity> getNotificationsByStatus( + @PathVariable final Notification.NotificationStatus status) { + return ResponseEntity.ok(notificationService.getNotificationsByStatus(status)); + } + + @PutMapping("/{id}/status") + public ResponseEntity updateNotificationStatus(@PathVariable final String id, + @RequestParam final Notification.NotificationStatus status) { + notificationService.updateNotificationStatus(id, status); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteNotification(@PathVariable String id) { + notificationService.deleteNotification(id); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/aselimov/NS/exception/GlobalExceptionHandler.java b/src/main/java/com/aselimov/NS/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..ff08c26 --- /dev/null +++ b/src/main/java/com/aselimov/NS/exception/GlobalExceptionHandler.java @@ -0,0 +1,32 @@ +package com.aselimov.NS.exception; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(NotificationNotFoundException.class) + public ResponseEntity handleNotificationNotFoundException(NotificationNotFoundException ex) { + Map body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("message", ex.getMessage()); + + return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGlobalException(Exception ex) { + Map body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("message", "An unexpected error occured"); + body.put("error", ex.getMessage()); + + return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/com/aselimov/NS/exception/NotificationNotFoundException.java b/src/main/java/com/aselimov/NS/exception/NotificationNotFoundException.java new file mode 100644 index 0000000..5764a1f --- /dev/null +++ b/src/main/java/com/aselimov/NS/exception/NotificationNotFoundException.java @@ -0,0 +1,7 @@ +package com.aselimov.NS.exception; + +public class NotificationNotFoundException extends RuntimeException { + public NotificationNotFoundException(String id) { + super("Notification not found with id:" + id); + } +} diff --git a/src/main/java/com/aselimov/NS/repository/NotificationRepository.java b/src/main/java/com/aselimov/NS/repository/NotificationRepository.java new file mode 100644 index 0000000..5938c19 --- /dev/null +++ b/src/main/java/com/aselimov/NS/repository/NotificationRepository.java @@ -0,0 +1,19 @@ +package com.aselimov.NS.repository; + +import com.aselimov.NS.model.Notification; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface NotificationRepository extends MongoRepository { + // Find notifications by status + List findByStatus(Notification.NotificationStatus status); + + // Find notifications by type + List findByType(Notification.NotificationType type); + + // Find notifications by recipient + List findByRecipient(String recipient); +} diff --git a/src/main/java/com/aselimov/NS/service/NotificationService.java b/src/main/java/com/aselimov/NS/service/NotificationService.java new file mode 100644 index 0000000..f8495ed --- /dev/null +++ b/src/main/java/com/aselimov/NS/service/NotificationService.java @@ -0,0 +1,68 @@ +package com.aselimov.NS.service; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.aselimov.NS.exception.NotificationNotFoundException; +import com.aselimov.NS.model.Notification; +import com.aselimov.NS.repository.NotificationRepository; + +@Service +public class NotificationService { + private final NotificationRepository notificationRepository; + + @Autowired + public NotificationService(NotificationRepository notificationRepository) { + this.notificationRepository = notificationRepository; + } + + public Notification saveNotification(Notification notification) { + // Set timestamps if not present + if (notification.getCreatedAt() == null) { + notification.setCreatedAt(LocalDateTime.now()); + } + notification.setUpdatedAt(LocalDateTime.now()); + + // Ensure status is set + if (notification.getStatus() == null) { + notification.setStatus(Notification.NotificationStatus.PENDING); + } + + return notificationRepository.save(notification); + } + + public List getAllNotifications() { + return notificationRepository.findAll(); + } + + public Notification getNotificationByIdOrThrow(String id) { + return notificationRepository.findById(id).orElseThrow(() -> new NotificationNotFoundException(id)); + } + + public List getNotificationsByStatus(Notification.NotificationStatus status) { + return notificationRepository.findByStatus(status); + } + + public List getNotificationsByType(Notification.NotificationType type) { + return notificationRepository.findByType(type); + } + + public List getNotificationsByRecipient(String recipient) { + return notificationRepository.findByRecipient(recipient); + } + + public void updateNotificationStatus(String id, Notification.NotificationStatus status) { + notificationRepository.findById(id).ifPresent(notification -> { + notification.setStatus(status); + notification.setUpdatedAt(LocalDateTime.now()); + notificationRepository.save(notification); + }); + } + + public void deleteNotification(String id) { + notificationRepository.deleteById(id); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 87e4853..45129d0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,10 @@ spring.application.name=NS server.port=8080 -spring.data.mongodb.uri=mongodb://localhost:27017/notification-system + +spring.data.mongodb.host=localhost +spring.data.mongodb.port=27017 +spring.data.mongodb.database=notification-system + spring.kafka.bootstrap-servers=localhost:9092 spring.kafka.consumer.group-id=notification-group diff --git a/src/test/java/com/aselimov/NS/repository/NotificationRepositoryTest.java b/src/test/java/com/aselimov/NS/repository/NotificationRepositoryTest.java new file mode 100644 index 0000000..69d6728 --- /dev/null +++ b/src/test/java/com/aselimov/NS/repository/NotificationRepositoryTest.java @@ -0,0 +1,101 @@ +package com.aselimov.NS.repository; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDateTime; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; +import org.springframework.test.context.ActiveProfiles; + +import com.aselimov.NS.model.Notification; +import com.aselimov.NS.model.Notification.NotificationType; + +@DataMongoTest +@ActiveProfiles("test") +public class NotificationRepositoryTest { + + @BeforeEach + public void setup() { + notificationRepository.deleteAll(); + } + + public NotificationRepository getNotificationRepository() { + return notificationRepository; + } + + @Autowired + private NotificationRepository notificationRepository; + + @Test + public void shouldSaveNotification() { + // Given + final Notification notification = new Notification(); + notification.setType(Notification.NotificationType.EMAIL); + notification.setRecipient("test@example.com"); + notification.setSubject("Test Subject"); + notification.setContent("Test Content"); + notification.setStatus(Notification.NotificationStatus.PENDING); + notification.setCreatedAt(LocalDateTime.now()); + notification.setUpdatedAt(LocalDateTime.now()); + + // When + Notification savedNotification = notificationRepository.save(notification); + + // Then + assert (savedNotification.getId() != null) : "Notification shouldn't be null"; + assertEquals(Notification.NotificationType.EMAIL, savedNotification.getType()); + assertEquals("test@example.com", savedNotification.getRecipient()); + } + + @Test + public void shouldFindByStatus() { + // Given + Notification notification1 = new Notification(); + notification1.setType(Notification.NotificationType.EMAIL); + notification1.setRecipient("test1@example.com"); + notification1.setStatus(Notification.NotificationStatus.PENDING); + notification1.setCreatedAt(LocalDateTime.now()); + notification1.setUpdatedAt(LocalDateTime.now()); + + Notification notification2 = new Notification(); + notification2.setType(Notification.NotificationType.SMS); + notification2.setRecipient("test2@example.com"); + notification2.setStatus(Notification.NotificationStatus.SENT); + notification2.setCreatedAt(LocalDateTime.now()); + notification2.setUpdatedAt(LocalDateTime.now()); + + notificationRepository.saveAll(List.of(notification1, notification2)); + + // When + List pendingNotifications = notificationRepository + .findByStatus(Notification.NotificationStatus.PENDING); + + // Then + assertEquals(1, pendingNotifications.size()); + assertEquals("test1@example.com", pendingNotifications.get(0).getRecipient()); + } + + @Test + public void shouldFindByType() { + // Given + Notification notification = new Notification(); + notification.setType(Notification.NotificationType.PUSH); + notification.setRecipient("test@example.com"); + notification.setStatus(Notification.NotificationStatus.PENDING); + notification.setCreatedAt(LocalDateTime.now()); + notification.setUpdatedAt(LocalDateTime.now()); + + notificationRepository.save(notification); + + // When + List pushNotifications = notificationRepository.findByType(NotificationType.PUSH); + + // Then + assertEquals(1, pushNotifications.size()); + assertEquals(NotificationType.PUSH, pushNotifications.get(0).getType()); + } +} diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..d661ae2 --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1 @@ +spring.data.mongodb.database=notification-system-test