diff --git a/backend/src/main/java/com/openisle/controller/UserController.java b/backend/src/main/java/com/openisle/controller/UserController.java index 01afbabb6..c3c737ce4 100644 --- a/backend/src/main/java/com/openisle/controller/UserController.java +++ b/backend/src/main/java/com/openisle/controller/UserController.java @@ -105,6 +105,17 @@ public class UserController { .collect(java.util.stream.Collectors.toList()); } + @GetMapping("/{identifier}/subscribed-posts") + public java.util.List subscribedPosts(@PathVariable("identifier") String identifier, + @RequestParam(value = "limit", required = false) Integer limit) { + int l = limit != null ? limit : defaultPostsLimit; + User user = userService.findByIdentifier(identifier).orElseThrow(); + return subscriptionService.getSubscribedPosts(user.getUsername()).stream() + .limit(l) + .map(userMapper::toMetaDto) + .collect(java.util.stream.Collectors.toList()); + } + @GetMapping("/{identifier}/replies") public java.util.List userReplies(@PathVariable("identifier") String identifier, @RequestParam(value = "limit", required = false) Integer limit) { diff --git a/backend/src/main/java/com/openisle/service/SubscriptionService.java b/backend/src/main/java/com/openisle/service/SubscriptionService.java index 841fad659..f6429ff31 100644 --- a/backend/src/main/java/com/openisle/service/SubscriptionService.java +++ b/backend/src/main/java/com/openisle/service/SubscriptionService.java @@ -107,6 +107,11 @@ public class SubscriptionService { return commentSubRepo.findByComment(c).stream().map(CommentSubscription::getUser).toList(); } + public List getSubscribedPosts(String username) { + User user = userRepo.findByUsername(username).orElseThrow(); + return postSubRepo.findByUser(user).stream().map(PostSubscription::getPost).toList(); + } + public long countSubscribers(String username) { User user = userRepo.findByUsername(username).orElseThrow(); diff --git a/backend/src/test/java/com/openisle/controller/UserControllerTest.java b/backend/src/test/java/com/openisle/controller/UserControllerTest.java index 4af99507c..22d48bd2c 100644 --- a/backend/src/test/java/com/openisle/controller/UserControllerTest.java +++ b/backend/src/test/java/com/openisle/controller/UserControllerTest.java @@ -136,6 +136,30 @@ class UserControllerTest { .andExpect(jsonPath("$[0].title").value("hello")); } + @Test + void listSubscribedPosts() throws Exception { + User user = new User(); + user.setUsername("bob"); + com.openisle.model.Category cat = new com.openisle.model.Category(); + cat.setName("tech"); + com.openisle.model.Post post = new com.openisle.model.Post(); + post.setId(6L); + post.setTitle("fav"); + post.setCreatedAt(java.time.LocalDateTime.now()); + post.setCategory(cat); + post.setAuthor(user); + Mockito.when(userService.findByIdentifier("bob")).thenReturn(Optional.of(user)); + Mockito.when(subscriptionService.getSubscribedPosts("bob")).thenReturn(java.util.List.of(post)); + PostMetaDto meta = new PostMetaDto(); + meta.setId(6L); + meta.setTitle("fav"); + Mockito.when(userMapper.toMetaDto(post)).thenReturn(meta); + + mockMvc.perform(get("/api/users/bob/subscribed-posts")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].title").value("fav")); + } + @Test void listUserReplies() throws Exception { User user = new User(); diff --git a/frontend_nuxt/pages/users/[id].vue b/frontend_nuxt/pages/users/[id].vue index 973a95b68..5852806ec 100644 --- a/frontend_nuxt/pages/users/[id].vue +++ b/frontend_nuxt/pages/users/[id].vue @@ -94,6 +94,13 @@
关注
+
+ +
收藏
+
+
+
+ + + +
+
暂无收藏文章
+
+
@@ -352,6 +376,7 @@ const user = ref({}) const hotPosts = ref([]) const hotReplies = ref([]) const hotTags = ref([]) +const favoritePosts = ref([]) const timelineItems = ref([]) const timelineFilter = ref('all') const filteredTimelineItems = computed(() => { @@ -369,7 +394,7 @@ const subscribed = ref(false) const isLoading = ref(true) const tabLoading = ref(false) const selectedTab = ref( - ['summary', 'timeline', 'following', 'achievements'].includes(route.query.tab) + ['summary', 'timeline', 'following', 'favorites', 'achievements'].includes(route.query.tab) ? route.query.tab : 'summary', ) @@ -472,6 +497,16 @@ const fetchFollowUsers = async () => { followings.value = followingRes.ok ? await followingRes.json() : [] } +const fetchFavorites = async () => { + const res = await fetch(`${API_BASE_URL}/api/users/${username}/subscribed-posts`) + if (res.ok) { + const data = await res.json() + favoritePosts.value = data.map((p) => ({ icon: 'fas fa-bookmark', post: p })) + } else { + favoritePosts.value = [] + } +} + const loadSummary = async () => { tabLoading.value = true await fetchSummary() @@ -490,6 +525,12 @@ const loadFollow = async () => { tabLoading.value = false } +const loadFavorites = async () => { + tabLoading.value = true + await fetchFavorites() + tabLoading.value = false +} + const fetchAchievements = async () => { const res = await fetch(`${API_BASE_URL}/api/medals?userId=${user.value.id}`) if (res.ok) { @@ -578,6 +619,8 @@ const init = async () => { await loadTimeline() } else if (selectedTab.value === 'following') { await loadFollow() + } else if (selectedTab.value === 'favorites') { + await loadFavorites() } else if (selectedTab.value === 'achievements') { await loadAchievements() } @@ -596,6 +639,8 @@ watch(selectedTab, async (val) => { await loadTimeline() } else if (val === 'following' && followers.value.length === 0 && followings.value.length === 0) { await loadFollow() + } else if (val === 'favorites' && favoritePosts.value.length === 0) { + await loadFavorites() } else if (val === 'achievements' && medals.value.length === 0) { await loadAchievements() }