From df7ca776520ccf2198c76fe5da3af19cb8fb04b5 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:07:27 +0800 Subject: [PATCH] feat: add paginated notification API and frontend support --- .../controller/NotificationController.java | 12 +- .../repository/NotificationRepository.java | 5 + .../openisle/service/NotificationService.java | 15 +- .../NotificationControllerTest.java | 19 +- .../service/NotificationServiceTest.java | 7 +- frontend_nuxt/pages/message.vue | 31 +- frontend_nuxt/utils/notification.js | 352 +++++++++--------- 7 files changed, 256 insertions(+), 185 deletions(-) diff --git a/backend/src/main/java/com/openisle/controller/NotificationController.java b/backend/src/main/java/com/openisle/controller/NotificationController.java index d25d2a808..1149bff37 100644 --- a/backend/src/main/java/com/openisle/controller/NotificationController.java +++ b/backend/src/main/java/com/openisle/controller/NotificationController.java @@ -23,9 +23,17 @@ public class NotificationController { private final NotificationMapper notificationMapper; @GetMapping - public List list(@RequestParam(value = "read", required = false) Boolean read, + public List list(@RequestParam(value = "page", defaultValue = "0") int page, Authentication auth) { - return notificationService.listNotifications(auth.getName(), read).stream() + return notificationService.listNotifications(auth.getName(), null, page).stream() + .map(notificationMapper::toDto) + .collect(Collectors.toList()); + } + + @GetMapping("/unread") + public List listUnread(@RequestParam(value = "page", defaultValue = "0") int page, + Authentication auth) { + return notificationService.listNotifications(auth.getName(), false, page).stream() .map(notificationMapper::toDto) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/com/openisle/repository/NotificationRepository.java b/backend/src/main/java/com/openisle/repository/NotificationRepository.java index 1e897b3a0..04a2b944f 100644 --- a/backend/src/main/java/com/openisle/repository/NotificationRepository.java +++ b/backend/src/main/java/com/openisle/repository/NotificationRepository.java @@ -5,6 +5,8 @@ import com.openisle.model.User; import com.openisle.model.Post; import com.openisle.model.Comment; import com.openisle.model.NotificationType; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -13,6 +15,9 @@ import java.util.List; public interface NotificationRepository extends JpaRepository { List findByUserOrderByCreatedAtDesc(User user); List findByUserAndReadOrderByCreatedAtDesc(User user, boolean read); + + Page findByUserOrderByCreatedAtDesc(User user, Pageable pageable); + Page findByUserAndReadOrderByCreatedAtDesc(User user, boolean read, Pageable pageable); long countByUserAndRead(User user, boolean read); List findByPost(Post post); List findByComment(Comment comment); diff --git a/backend/src/main/java/com/openisle/service/NotificationService.java b/backend/src/main/java/com/openisle/service/NotificationService.java index 6ecf571e8..d8d39594f 100644 --- a/backend/src/main/java/com/openisle/service/NotificationService.java +++ b/backend/src/main/java/com/openisle/service/NotificationService.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.ArrayList; import java.util.concurrent.Executor; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; /** Service for creating and retrieving notifications. */ @Service @@ -181,16 +183,21 @@ public class NotificationService { } public List listNotifications(String username, Boolean read) { + return listNotifications(username, read, 0); + } + + public List listNotifications(String username, Boolean read, int page) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); Set disabled = user.getDisabledNotificationTypes(); - List list; + Page p; + Pageable pageable = org.springframework.data.domain.PageRequest.of(page, 50); if (read == null) { - list = notificationRepository.findByUserOrderByCreatedAtDesc(user); + p = notificationRepository.findByUserOrderByCreatedAtDesc(user, pageable); } else { - list = notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, read); + p = notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, read, pageable); } - return list.stream().filter(n -> !disabled.contains(n.getType())).collect(Collectors.toList()); + return p.getContent().stream().filter(n -> !disabled.contains(n.getType())).collect(Collectors.toList()); } public void markRead(String username, List ids) { diff --git a/backend/src/test/java/com/openisle/controller/NotificationControllerTest.java b/backend/src/test/java/com/openisle/controller/NotificationControllerTest.java index 6b74440a1..1702f26d3 100644 --- a/backend/src/test/java/com/openisle/controller/NotificationControllerTest.java +++ b/backend/src/test/java/com/openisle/controller/NotificationControllerTest.java @@ -45,7 +45,7 @@ class NotificationControllerTest { p.setId(2L); n.setPost(p); n.setCreatedAt(LocalDateTime.now()); - when(notificationService.listNotifications("alice", null)) + when(notificationService.listNotifications("alice", null, 0)) .thenReturn(List.of(n)); NotificationDto dto = new NotificationDto(); @@ -62,6 +62,23 @@ class NotificationControllerTest { .andExpect(jsonPath("$[0].post.id").value(2)); } + @Test + void listUnreadNotifications() throws Exception { + Notification n = new Notification(); + n.setId(3L); + n.setType(NotificationType.POST_VIEWED); + when(notificationService.listNotifications("alice", false, 0)).thenReturn(List.of(n)); + + NotificationDto dto = new NotificationDto(); + dto.setId(3L); + when(notificationMapper.toDto(n)).thenReturn(dto); + + mockMvc.perform(get("/api/notifications/unread") + .principal(new UsernamePasswordAuthenticationToken("alice", "p"))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(3)); + } + @Test void markReadEndpoint() throws Exception { mockMvc.perform(post("/api/notifications/read") diff --git a/backend/src/test/java/com/openisle/service/NotificationServiceTest.java b/backend/src/test/java/com/openisle/service/NotificationServiceTest.java index a01d3c95b..0e7048a17 100644 --- a/backend/src/test/java/com/openisle/service/NotificationServiceTest.java +++ b/backend/src/test/java/com/openisle/service/NotificationServiceTest.java @@ -65,12 +65,15 @@ class NotificationServiceTest { when(uRepo.findByUsername("bob")).thenReturn(Optional.of(user)); Notification n = new Notification(); - when(nRepo.findByUserOrderByCreatedAtDesc(user)).thenReturn(List.of(n)); + org.springframework.data.domain.Page page = + new org.springframework.data.domain.PageImpl<>(List.of(n)); + when(nRepo.findByUserOrderByCreatedAtDesc(eq(user), any(org.springframework.data.domain.Pageable.class))) + .thenReturn(page); List list = service.listNotifications("bob", null); assertEquals(1, list.size()); - verify(nRepo).findByUserOrderByCreatedAtDesc(user); + verify(nRepo).findByUserOrderByCreatedAtDesc(eq(user), any(org.springframework.data.domain.Pageable.class)); } @Test diff --git a/frontend_nuxt/pages/message.vue b/frontend_nuxt/pages/message.vue index 461d8f4bb..fcc8f379b 100644 --- a/frontend_nuxt/pages/message.vue +++ b/frontend_nuxt/pages/message.vue @@ -53,13 +53,13 @@ -
- +
+ +