Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package ject.componote.domain.design.application;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import ject.componote.domain.auth.model.AuthPrincipal;
import ject.componote.domain.common.dto.response.PageResponse;
import ject.componote.domain.design.dao.DesignSystemRepository;
import ject.componote.domain.design.dao.filter.DesignFilterRepository;
import ject.componote.domain.design.dao.link.DesignLinkRepository;
import ject.componote.domain.design.domain.Design;
import ject.componote.domain.design.domain.DesignSystem;
import ject.componote.domain.design.domain.filter.DesignFilter;
import ject.componote.domain.design.domain.link.DesignLink;
import ject.componote.domain.design.dto.search.request.DesignSystemSearchRequest;
import ject.componote.domain.design.dto.search.response.DesignSystemSearchResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j // 로그 추가
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand All @@ -31,18 +31,29 @@ public class DesignSystemService {
public PageResponse<DesignSystemSearchResponse> searchDesignSystem(final AuthPrincipal authPrincipal,
final DesignSystemSearchRequest request,
final Pageable pageable) {
log.info("🔎 [START] searchDesignSystem() 호출 - AuthPrincipal: {}, Request: {}, Pageable: {}", authPrincipal, request, pageable);

// 1️⃣ 필터 조건에 맞는 디자인 ID 조회
List<Long> filteredDesignIds = getFilteredDesignIds(request);
log.info("📝 [FILTER] 필터링된 Design ID 목록: {}", filteredDesignIds);

// 2️⃣ 조건에 맞는 디자인 목록 조회
Page<Design> designs = searchByConditions(authPrincipal, request.keyword(), filteredDesignIds, pageable);
log.info("📌 [DESIGNS] 조회된 Design 개수: {}, 내용: {}", designs.getTotalElements(), designs.getContent());

List<Long> designIds = designs.getContent().stream().map(Design::getId).collect(Collectors.toList());

// 3️⃣ DesignFilter 조회
Map<Long, List<DesignFilter>> filtersMap = designSystemRepository.findFiltersByDesignIds(designIds)
.stream().collect(Collectors.groupingBy(DesignFilter::getDesignId));
log.info("🔗 [FILTER MAP] DesignFilter 개수: {}, 데이터: {}", filtersMap.size(), filtersMap);

// 4️⃣ DesignLink 조회
Map<Long, List<DesignLink>> linksMap = designSystemRepository.findLinksByDesignIds(designIds)
.stream().collect(Collectors.groupingBy(DesignLink::getDesignId));
log.info("🌍 [LINK MAP] DesignLink 개수: {}, 데이터: {}", linksMap.size(), linksMap);

// 5️⃣ 최종 변환
Page<DesignSystemSearchResponse> responsePage = designs.map(design -> {
List<DesignFilter> filters = filtersMap.getOrDefault(design.getId(), List.of());
List<DesignLink> links = linksMap.getOrDefault(design.getId(), List.of());
Expand All @@ -51,35 +62,58 @@ public PageResponse<DesignSystemSearchResponse> searchDesignSystem(final AuthPri
return DesignSystemSearchResponse.from(designSystem);
});

log.info("✅ [RESULT] 최종 반환되는 데이터 개수: {}, 내용: {}", responsePage.getTotalElements(), responsePage.getContent());

return PageResponse.from(responsePage);
}

private Page<Design> searchByConditions(AuthPrincipal authPrincipal, String keyword, List<Long> filteredDesignIds, Pageable pageable) {
log.info("🔍 [SEARCH CONDITIONS] AuthPrincipal: {}, keyword: {}, filteredDesignIds: {}, pageable: {}",
authPrincipal, keyword, filteredDesignIds, pageable);

Page<Design> result;

if (authPrincipal != null) {
return !filteredDesignIds.isEmpty()
? designSystemRepository.findAllByIdInAndBookmarkStatus(authPrincipal.id(), filteredDesignIds, pageable)
: designSystemRepository.findByKeywordAndBookmarkStatus(authPrincipal.id(), keyword, pageable);
if (!filteredDesignIds.isEmpty()) {
result = designSystemRepository.findAllByIdInAndBookmarkStatus(authPrincipal.id(), filteredDesignIds, pageable);
} else if (keyword != null && !keyword.isBlank()) {
result = designSystemRepository.findByKeywordAndBookmarkStatus(authPrincipal.id(), keyword, pageable);
} else {
result = designSystemRepository.findAllByBookmarkStatus(authPrincipal.id(), pageable); // 🔥 수정된 부분: keyword가 없으면 전체 조회
}
} else {
if (!filteredDesignIds.isEmpty()) {
result = designSystemRepository.findAllByIdIn(filteredDesignIds, pageable);
} else if (keyword != null && !keyword.isBlank()) {
result = designSystemRepository.findByKeyword(keyword, pageable);
} else {
result = designSystemRepository.findAll(pageable); // 🔥 수정된 부분: keyword가 없으면 전체 조회
}
}
return !filteredDesignIds.isEmpty()
? designSystemRepository.findAllByIdIn(filteredDesignIds, pageable)
: designSystemRepository.findByKeyword(keyword, pageable);

log.info("📄 [SEARCH RESULT] 조회된 Design 개수: {}, 내용: {}", result.getTotalElements(), result.getContent());
return result;
}

private List<Long> getFilteredDesignIds(DesignSystemSearchRequest request) {
if (request.filters() == null || request.filters().isEmpty()) {
log.info("🚫 [FILTER] 필터가 없으므로 모든 디자인 포함");
return List.of();
}

return request.filters().stream()
List<Long> result = request.filters().stream()
.flatMap(filter -> {
List<Long> designIds = designSystemRepository.findAllDesignIdByCondition(
filter.parseType(),
filter.values() != null ? filter.values() : List.of()
);
log.info("📌 [FILTER] {} 필터로 검색된 Design ID: {}", filter.parseType(), designIds);
return designIds.stream();
})
.distinct()
.collect(Collectors.toList());
}

log.info("✅ [FINAL FILTERED IDs] 최종 필터링된 Design ID 목록: {}", result);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,12 @@ public interface DesignSystemRepository extends JpaRepository<Design, Long> {
countQuery = "SELECT COUNT(d) FROM Design d WHERE d.id IN :designIds")
Page<Design> findAllByIdIn(@Param("designIds") List<Long> designIds, Pageable pageable);

@Query(value = "SELECT DISTINCT d FROM Design d WHERE d.summary.name LIKE %:keyword%",
countQuery = "SELECT COUNT(d) FROM Design d WHERE d.summary.name LIKE %:keyword%")
Page<Design> findByKeyword(@Param("keyword") String keyword, Pageable pageable);

@Query(value = "SELECT DISTINCT d FROM Design d WHERE d.id IN :designIds " +
"AND EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)",
countQuery = "SELECT COUNT(d) FROM Design d WHERE d.id IN :designIds " +
"AND EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)")
Page<Design> findAllByIdInAndBookmarkStatus(@Param("userId") Long userId, @Param("designIds") List<Long> designIds, Pageable pageable);

@Query(value = "SELECT DISTINCT d FROM Design d WHERE d.summary.name LIKE %:keyword% " +
"AND EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)",
countQuery = "SELECT COUNT(d) FROM Design d WHERE d.summary.name LIKE %:keyword% " +
"AND EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)")
Page<Design> findByKeywordAndBookmarkStatus(@Param("userId") Long userId, @Param("keyword") String keyword, Pageable pageable);


@Query("SELECT df FROM DesignFilter df WHERE df.designId IN :designIds")
List<DesignFilter> findFiltersByDesignIds(@Param("designIds") List<Long> designIds);

Expand All @@ -44,4 +33,27 @@ public interface DesignSystemRepository extends JpaRepository<Design, Long> {
"WHERE df.type = :type AND df.value IN :values")
List<Long> findAllDesignIdByCondition(@Param("type") FilterType type, @Param("values") List<String> values);

@Query(value = "SELECT DISTINCT d FROM Design d",
countQuery = "SELECT COUNT(d) FROM Design d")
Page<Design> findAll(Pageable pageable);

@Query(value = "SELECT DISTINCT d FROM Design d WHERE (:keyword IS NULL OR d.summary.name LIKE %:keyword%)",
countQuery = "SELECT COUNT(d) FROM Design d WHERE (:keyword IS NULL OR d.summary.name LIKE %:keyword%)")
Page<Design> findByKeyword(@Param("keyword") String keyword, Pageable pageable);

@Query(value = "SELECT DISTINCT d FROM Design d WHERE (:keyword IS NULL OR d.summary.name LIKE %:keyword%) " +
"AND EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)",
countQuery = "SELECT COUNT(d) FROM Design d WHERE (:keyword IS NULL OR d.summary.name LIKE %:keyword%) " +
"AND EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)")
Page<Design> findByKeywordAndBookmarkStatus(@Param("userId") Long userId, @Param("keyword") String keyword, Pageable pageable);

@Query(value = "SELECT DISTINCT d FROM Design d " +
"WHERE EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)",
countQuery = "SELECT COUNT(d) FROM Design d " +
"WHERE EXISTS (SELECT 1 FROM Bookmark b WHERE b.memberId = :userId AND b.resourceId = d.id)")
Page<Design> findAllByBookmarkStatus(@Param("userId") Long userId, Pageable pageable);




}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@
@Getter
public enum FilterType {
PLATFORM(List.of("GITHUB", "FIGMA", "STORYBOOK", "ZEROHEIGHT")),
TECHNOLOGY(List.of("REACT", "ANGULAR", "VUE")),
CONTENT(List.of("DESIGN_TOKEN", "CODE_EXAMPLES", "ACCESSIBILITY_GUIDE")),
TECHNOLOGY(List.of("ANGULAR", "NONE", "CSS", "CSS_IN_JS", "CSS_MODULES", "HTML",
"REACT", "SASS", "STIMULUS", "SVELTE", "TAILWIND_CSS", "TWIG", "VANILLA_JS", "VUE",
"WEB_COMPONENTS")),
/*
DESIGN_TOKEN: 디자인 토큰, ICON: 아이콘, OPENSOURCE: 오픈소스, EXAMPLE: 용례,
BRAND_PRINCIPLES: 브랜드 원칙, ACCESSIBILITY_INFORMATION: 접근성 안내,
VOICE_AND_TONE: 보이스와 톤, CODE_EXAMPLE: 코드 예제
*/
CONTENT(List.of("DESIGN_TOKEN", "ICON", "OPENSOURCE", "EXAMPLE", "BRAND_PRINCIPLES",
"ACCESSIBILITY_INFORMATION", "VOICE_AND_TONE", "CODE_EXAMPLE")),
DEVICE(List.of("DESKTOP", "MOBILE"));

private final List<String> values;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public enum LinkType {
FIGMA("^https://www\\.figma\\.com/.+"),
SLACK("^https://[a-zA-Z0-9]+\\.slack\\.com/.+"),
CODE_PEN("^https://codepen\\.io/.+"),
WEBSITE(".*"),
ZEROHEIGHT("^https://base\\.uber\\.com/.+"),
STORYBOOK("^https://[a-zA-Z0-9.-]+\\.storybook\\.com/.+|^https://[a-zA-Z0-9.-]+\\.delldesignsystem\\.com/.+"),
ETC(".*"); // ETC는 모든 URL 허용

private final String regex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ public record DesignSystemSearchResponse(
) {
public static DesignSystemSearchResponse from(DesignSystem designSystem) {
return new DesignSystemSearchResponse(
designSystem.getDesign().getSummary().getName(),
designSystem.getDesign().getSummary().getOrganization(),
designSystem.getDesign().getSummary().getDescription(),
designSystem.getFilters().getFilters().stream()
.map(filter -> new DesignFilterSearchResponse(filter.getType().name(), List.of(filter.getValue())))
.collect(Collectors.toList()),
designSystem.getLinks().getLinks().stream()
.map(link -> new DesignLinkResponse(link.getType().name().toLowerCase(), link.getUrl().getValue()))
.collect(Collectors.toList())
designSystem.getDesign().getSummary().getName(),
designSystem.getDesign().getSummary().getOrganization(),
designSystem.getDesign().getSummary().getDescription(),
designSystem.getFilters().getFilters().stream()
.map(filter -> new DesignFilterSearchResponse(filter.getType().name(), List.of(filter.getValue())))
.collect(Collectors.toList()),
designSystem.getLinks().getLinks().stream()
.filter(link -> link.getUrl() != null) // ✅ URL이 null이 아닌 경우만 처리
.map(link -> new DesignLinkResponse(
link.getType().name().toLowerCase(),
link.getUrl().getValue() // ✅ null이면 빈 문자열 처리
))
.collect(Collectors.toList())
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//package ject.componote.domain.design;
//
//import ject.componote.domain.auth.model.AuthPrincipal;
//import ject.componote.domain.common.dto.response.PageResponse;
//import ject.componote.domain.design.application.DesignSystemService;
//import ject.componote.domain.design.dao.DesignSystemRepository;
//import ject.componote.domain.design.domain.Design;
//import ject.componote.domain.design.dto.search.request.DesignSystemSearchRequest;
//import ject.componote.domain.design.dto.search.response.DesignSystemSearchResponse;
//import ject.componote.fixture.DesignFixture;
//import ject.componote.fixture.MemberFixture;
//import org.junit.jupiter.api.BeforeEach;
//import org.junit.jupiter.api.DisplayName;
//import org.junit.jupiter.api.extension.ExtendWith;
//import org.junit.jupiter.params.ParameterizedTest;
//import org.junit.jupiter.params.provider.MethodSource;
//import org.mockito.InjectMocks;
//import org.mockito.Mock;
//import org.mockito.junit.jupiter.MockitoExtension;
//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.util.Collections;
//import java.util.List;
//import java.util.stream.Stream;
//
//import static org.assertj.core.api.Assertions.assertThat;
//import static org.mockito.Mockito.*;
//
//@ExtendWith(MockitoExtension.class)
//class DesignSystemServiceTest {
//
// @Mock
// DesignSystemRepository designSystemRepository;
//
// @InjectMocks
// DesignSystemService designSystemService;
//
// AuthPrincipal authPrincipal;
//
// @BeforeEach
// public void init() {
// authPrincipal = AuthPrincipal.from(MemberFixture.KIM.생성(1L));
// }
//
// static Stream<SearchInput> provideSearchInputs() {
// final AuthPrincipal authPrincipal = AuthPrincipal.from(MemberFixture.KIM.생성(1L));
// return Stream.of(
// new SearchInput("비회원", null, "검색어"),
// new SearchInput("회원", authPrincipal, "검색어")
// );
// }
//
// @ParameterizedTest
// @MethodSource("provideSearchInputs")
// @DisplayName("디자인 시스템 검색 성공")
// public void searchDesignSystem_Success(final SearchInput input) {
// // given
// final Pageable pageable = PageRequest.of(0, 10);
// final Design design = DesignFixture.기본_디자인_생성();
// final Page<Design> designs = new PageImpl<>(List.of(design), pageable, 1);
// final DesignSystemSearchRequest request = new DesignSystemSearchRequest(input.keyword, null);
//
// when(designSystemRepository.findByKeyword(anyString(), any(Pageable.class)))
// .thenReturn(designs);
//
// // when
// PageResponse<DesignSystemSearchResponse> response = designSystemService.searchDesignSystem(input.authPrincipal, request, pageable);
//
// // then
// assertThat(response.getTotalElements()).isEqualTo(1);
//
// // ✅ 실제로 해당 메서드가 호출되었는지 검증
// verify(designSystemRepository).findByKeyword(anyString(), any(Pageable.class));
// }
//
// @ParameterizedTest
// @MethodSource("provideSearchInputs")
// @DisplayName("디자인 시스템 검색 - 검색 결과 없음")
// public void searchDesignSystem_EmptyResult(final SearchInput input) {
// // given
// final Pageable pageable = PageRequest.of(0, 10);
// final DesignSystemSearchRequest request = new DesignSystemSearchRequest(input.keyword, null);
// final Page<Design> emptyPage = new PageImpl<>(Collections.emptyList(), pageable, 0);
//
// when(designSystemRepository.findByKeyword(anyString(), any(Pageable.class)))
// .thenReturn(emptyPage);
//
// // when
// PageResponse<DesignSystemSearchResponse> response = designSystemService.searchDesignSystem(input.authPrincipal, request, pageable);
//
// // then
// assertThat(response.getTotalElements()).isEqualTo(0);
// verify(designSystemRepository).findByKeyword(anyString(), any(Pageable.class));
// }
//
// @ParameterizedTest
// @MethodSource("provideSearchInputs")
// @DisplayName("디자인 시스템 검색 - 필터 적용 후 검색")
// public void searchDesignSystem_WithFilters(final SearchInput input) {
// // given
// final Pageable pageable = PageRequest.of(0, 10);
// final List<Long> filteredDesignIds = List.of(1L, 2L);
// final DesignSystemSearchRequest request = new DesignSystemSearchRequest(input.keyword, List.of());
// final Page<Design> designs = new PageImpl<>(List.of(DesignFixture.기본_디자인_생성()), pageable, 1);
//
// when(designSystemRepository.findAllByIdIn(anyList(), any(Pageable.class)))
// .thenReturn(designs);
//
// // when
// PageResponse<DesignSystemSearchResponse> response = designSystemService.searchDesignSystem(input.authPrincipal, request, pageable);
//
// // then
// assertThat(response.getTotalElements()).isEqualTo(1);
// verify(designSystemRepository).findAllByIdIn(anyList(), any(Pageable.class));
// }
//
// static class SearchInput {
// String displayName;
// AuthPrincipal authPrincipal;
// String keyword;
//
// public SearchInput(final String displayName,
// final AuthPrincipal authPrincipal,
// final String keyword) {
// this.displayName = displayName;
// this.authPrincipal = authPrincipal;
// this.keyword = keyword;
// }
// }
//}
Loading
Loading