From a1083412e1f91e5c7aebfab6512eb8686425b71f Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl Date: Sat, 14 Mar 2026 19:51:06 +0800 Subject: [PATCH] fix(stars): keep favorites data in sync --- .../skillhub/service/MySkillAppService.java | 25 +++++- .../service/MySkillAppServiceTest.java | 89 +++++++++++++++++++ web/src/features/social/use-star.ts | 1 + 3 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 server/skillhub-app/src/test/java/com/iflytek/skillhub/service/MySkillAppServiceTest.java diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java index 6f2684f0..219a5727 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java @@ -7,6 +7,7 @@ import com.iflytek.skillhub.domain.skill.SkillVersion; import com.iflytek.skillhub.domain.skill.SkillVersionRepository; import com.iflytek.skillhub.domain.social.SkillStarRepository; import com.iflytek.skillhub.dto.SkillSummaryResponse; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.data.domain.PageRequest; @@ -19,6 +20,7 @@ import java.util.stream.Collectors; @Service public class MySkillAppService { + private static final int STAR_PAGE_SIZE = 200; private final SkillRepository skillRepository; private final NamespaceRepository namespaceRepository; @@ -68,10 +70,7 @@ public class MySkillAppService { } public List listMyStars(String userId) { - List stars = skillStarRepository.findByUserId( - userId, - PageRequest.of(0, 200) - ).getContent(); + List stars = loadAllStars(userId); List skillIds = stars.stream() .map(com.iflytek.skillhub.domain.social.SkillStar::getSkillId) @@ -111,6 +110,24 @@ public class MySkillAppService { .toList(); } + private List loadAllStars(String userId) { + List stars = new java.util.ArrayList<>(); + int pageNumber = 0; + + while (true) { + Page page = skillStarRepository.findByUserId( + userId, + PageRequest.of(pageNumber, STAR_PAGE_SIZE) + ); + stars.addAll(page.getContent()); + + if (!page.hasNext()) { + return stars; + } + pageNumber++; + } + } + private SkillSummaryResponse toSummaryResponse( Skill skill, Map versionsById, diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/MySkillAppServiceTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/MySkillAppServiceTest.java new file mode 100644 index 00000000..dcaa01d5 --- /dev/null +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/MySkillAppServiceTest.java @@ -0,0 +1,89 @@ +package com.iflytek.skillhub.service; + +import com.iflytek.skillhub.domain.namespace.Namespace; +import com.iflytek.skillhub.domain.namespace.NamespaceRepository; +import com.iflytek.skillhub.domain.skill.Skill; +import com.iflytek.skillhub.domain.skill.SkillRepository; +import com.iflytek.skillhub.domain.skill.SkillVersion; +import com.iflytek.skillhub.domain.skill.SkillVersionRepository; +import com.iflytek.skillhub.domain.skill.SkillVisibility; +import com.iflytek.skillhub.domain.social.SkillStar; +import com.iflytek.skillhub.domain.social.SkillStarRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.util.ReflectionTestUtils; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class MySkillAppServiceTest { + + @Mock + private SkillRepository skillRepository; + + @Mock + private NamespaceRepository namespaceRepository; + + @Mock + private SkillVersionRepository skillVersionRepository; + + @Mock + private SkillStarRepository skillStarRepository; + + private MySkillAppService service; + + @BeforeEach + void setUp() { + service = new MySkillAppService(skillRepository, namespaceRepository, skillVersionRepository, skillStarRepository); + } + + @Test + void listMyStars_loadsAllPagesOfStars() { + SkillStar firstStar = new SkillStar(1L, "user-1"); + ReflectionTestUtils.setField(firstStar, "createdAt", LocalDateTime.of(2026, 3, 14, 10, 0)); + SkillStar secondStar = new SkillStar(2L, "user-1"); + ReflectionTestUtils.setField(secondStar, "createdAt", LocalDateTime.of(2026, 3, 14, 11, 0)); + + given(skillStarRepository.findByUserId("user-1", PageRequest.of(0, 200))) + .willReturn(new PageImpl<>(List.of(firstStar), PageRequest.of(0, 200), 201)); + given(skillStarRepository.findByUserId("user-1", PageRequest.of(1, 200))) + .willReturn(new PageImpl<>(List.of(secondStar), PageRequest.of(1, 200), 201)); + + Skill firstSkill = new Skill(1L, "first-skill", "user-1", SkillVisibility.PUBLIC); + firstSkill.setDisplayName("First Skill"); + firstSkill.setSummary("first summary"); + ReflectionTestUtils.setField(firstSkill, "id", 1L); + ReflectionTestUtils.setField(firstSkill, "starCount", 1); + ReflectionTestUtils.setField(firstSkill, "namespaceId", 101L); + ReflectionTestUtils.setField(firstSkill, "updatedAt", LocalDateTime.of(2026, 3, 14, 10, 0)); + + Skill secondSkill = new Skill(2L, "second-skill", "user-1", SkillVisibility.PUBLIC); + secondSkill.setDisplayName("Second Skill"); + secondSkill.setSummary("second summary"); + ReflectionTestUtils.setField(secondSkill, "id", 2L); + ReflectionTestUtils.setField(secondSkill, "starCount", 2); + ReflectionTestUtils.setField(secondSkill, "namespaceId", 101L); + ReflectionTestUtils.setField(secondSkill, "updatedAt", LocalDateTime.of(2026, 3, 14, 11, 0)); + + given(skillRepository.findByIdIn(List.of(1L, 2L))).willReturn(List.of(firstSkill, secondSkill)); + given(namespaceRepository.findByIdIn(List.of(101L))).willReturn(List.of(new Namespace("team-ai", "Team AI", "user-1"))); + + var stars = service.listMyStars("user-1"); + + assertThat(stars).hasSize(2); + assertThat(stars.get(0).slug()).isEqualTo("second-skill"); + assertThat(stars.get(1).slug()).isEqualTo("first-skill"); + } +} diff --git a/web/src/features/social/use-star.ts b/web/src/features/social/use-star.ts index c4106756..f5ab55e1 100644 --- a/web/src/features/social/use-star.ts +++ b/web/src/features/social/use-star.ts @@ -47,6 +47,7 @@ export function useToggleStar(skillId: number) { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['skills', skillId, 'star'] }) queryClient.invalidateQueries({ queryKey: ['skills'] }) + queryClient.invalidateQueries({ queryKey: ['skills', 'stars'] }) }, }) }