2222import DiffLens .back_end .domain .search .repository .FilterRepository ;
2323import DiffLens .back_end .domain .search .repository .SearchFilterRepository ;
2424import DiffLens .back_end .domain .search .repository .SearchHistoryRepository ;
25+ import DiffLens .back_end .domain .panel .repository .projection .PanelWithRawDataDTO ;
26+ import DiffLens .back_end .domain .search .service .interfaces .SearchPanelService ;
27+ import DiffLens .back_end .global .dto .ResponsePageDTO ;
2528import DiffLens .back_end .global .fastapi .FastApiService ;
29+ import DiffLens .back_end .global .fastapi .dto .request .FastLibraryChartRequestDTO ;
30+ import DiffLens .back_end .global .fastapi .dto .response .FastChartResponseDTO ;
31+ import DiffLens .back_end .global .fastapi .dto .response .FastLibraryChartResponseDTO ;
2632import DiffLens .back_end .global .fastapi .dto .response .FastLibraryCompareResponseDTO ;
2733import DiffLens .back_end .global .responses .code .status .error .ErrorStatus ;
2834import DiffLens .back_end .global .responses .exception .handler .ErrorHandler ;
2935import lombok .RequiredArgsConstructor ;
36+ import org .springframework .data .domain .Page ;
37+ import org .springframework .data .domain .PageRequest ;
38+ import org .springframework .data .domain .Pageable ;
3039import org .springframework .stereotype .Service ;
3140import org .springframework .transaction .annotation .Transactional ;
3241
@@ -46,6 +55,7 @@ public class LibraryService {
4655 private final FastApiService fastApiService ;
4756 private final SearchFilterRepository searchFilterRepository ;
4857 private final FilterRepository filterRepository ;
58+ private final SearchPanelService searchPanelService ;
4959
5060 @ Transactional
5161 public LibraryCreateResult createLibrary (LibraryRequestDto .Create request , Member member ) {
@@ -647,6 +657,169 @@ private void createLibraryPanels(Library library, List<String> panelIds) {
647657 libraryPanelRepository .saveAll (libraryPanels );
648658 }
649659
660+ /**
661+ * 라이브러리 대시보드 조회 (차트 포함)
662+ */
663+ @ Transactional (readOnly = true )
664+ public LibraryResponseDTO .LibraryDashboard getLibraryDashboard (Long libraryId , Member member ) {
665+ // 1. 라이브러리 조회 및 권한 검증
666+ Library library = libraryRepository .findById (libraryId )
667+ .orElseThrow (() -> new ErrorHandler (ErrorStatus .BAD_REQUEST ));
668+
669+ if (!library .getMember ().getId ().equals (member .getId ())) {
670+ throw new ErrorHandler (ErrorStatus .FORBIDDEN );
671+ }
672+
673+ // 2. 패널 ID 배열 조회
674+ List <String > panelIds = library .getPanelIds ();
675+ if (panelIds == null || panelIds .isEmpty ()) {
676+ throw new ErrorHandler (ErrorStatus .BAD_REQUEST );
677+ }
678+
679+ // 3. 서브서버 API 호출
680+ FastLibraryChartRequestDTO request = FastLibraryChartRequestDTO .builder ()
681+ .panelIds (panelIds )
682+ .libraryName (library .getLibraryName ())
683+ .build ();
684+
685+ FastLibraryChartResponseDTO .LibraryChartResponse chartResponse = fastApiService
686+ .getChartsFromLibrary (request );
687+
688+ // 4. 차트 데이터 변환
689+ LibraryResponseDTO .LibraryDashboard .ChartData mainChart = convertToChartData (
690+ chartResponse .getMainChart ());
691+ List <LibraryResponseDTO .LibraryDashboard .ChartData > subCharts = chartResponse .getSubCharts ()
692+ .stream ()
693+ .map (this ::convertToChartData )
694+ .toList ();
695+
696+ // 5. 응답 구성
697+ return LibraryResponseDTO .LibraryDashboard .builder ()
698+ .libraryId (library .getId ())
699+ .libraryName (library .getLibraryName ())
700+ .panelCount (panelIds .size ())
701+ .mainChart (mainChart )
702+ .subCharts (subCharts )
703+ .build ();
704+ }
705+
706+ /**
707+ * 라이브러리 패널 목록 조회 (페이징, 일치율 없음)
708+ */
709+ @ Transactional (readOnly = true )
710+ public LibraryResponseDTO .LibraryPanels getLibraryPanels (Long libraryId , Integer pageNum , Integer size ,
711+ Member member ) {
712+ // 1. 페이지 번호 예외처리
713+ if (pageNum < 1 ) {
714+ throw new ErrorHandler (ErrorStatus .PAGE_NO_INVALID );
715+ }
716+
717+ // 2. 라이브러리 조회 및 권한 검증
718+ Library library = libraryRepository .findById (libraryId )
719+ .orElseThrow (() -> new ErrorHandler (ErrorStatus .BAD_REQUEST ));
720+
721+ if (!library .getMember ().getId ().equals (member .getId ())) {
722+ throw new ErrorHandler (ErrorStatus .FORBIDDEN );
723+ }
724+
725+ // 3. 패널 ID 배열 조회
726+ List <String > panelIds = library .getPanelIds ();
727+ if (panelIds == null || panelIds .isEmpty ()) {
728+ return LibraryResponseDTO .LibraryPanels .builder ()
729+ .keys (List .of ("respondent_id" , "gender" , "age" , "residence" ,
730+ "personal_income" ))
731+ .values (List .of ())
732+ .pageInfo (ResponsePageDTO .OffsetLimitPageInfo .builder ()
733+ .offset (0 )
734+ .currentPage (1 )
735+ .currentPageCount (0 )
736+ .totalPageCount (0 )
737+ .limit (size )
738+ .totalCount (0L )
739+ .hasNext (false )
740+ .hasPrevious (false )
741+ .build ())
742+ .build ();
743+ }
744+
745+ // 4. 페이징을 위한 Pageable 객체 생성
746+ Pageable pageable = PageRequest .of (pageNum - 1 , size );
747+
748+ // 5. PanelId 목록을 이용해서 Panel 조회
749+ Page <PanelWithRawDataDTO > panelDtoList = searchPanelService .getPanelDtoList (panelIds , pageable );
750+
751+ // 6. 페이지 범위 초과 검사
752+ if (pageNum > panelDtoList .getTotalPages () && panelDtoList .getTotalPages () > 0 ) {
753+ throw new ErrorHandler (ErrorStatus .PAGE_NO_EXCEED );
754+ }
755+
756+ // 7. Panel 목록을 응답 형식으로 변환 (일치율 없음)
757+ List <LibraryResponseDTO .LibraryPanels .PanelResponseValues > values = panelDtoList .stream ()
758+ .map (panel -> LibraryResponseDTO .LibraryPanels .PanelResponseValues .builder ()
759+ .respondentId (panel .getId ())
760+ .gender (panel .getGender () != null ? panel .getGender ().getDisplayValue ()
761+ : null )
762+ .age (panel .getAge () != null ? panel .getAge ().toString () : null )
763+ .residence (panel .getResidence ())
764+ .personalIncome (panel .getPersonalIncome ())
765+ .build ())
766+ .toList ();
767+
768+ // 8. 페이징 정보 생성
769+ ResponsePageDTO .OffsetLimitPageInfo pageInfo = ResponsePageDTO .OffsetLimitPageInfo
770+ .from (panelDtoList );
771+
772+ return LibraryResponseDTO .LibraryPanels .builder ()
773+ .keys (List .of ("respondent_id" , "gender" , "age" , "residence" , "personal_income" ))
774+ .values (values )
775+ .pageInfo (pageInfo )
776+ .build ();
777+ }
778+
779+ /**
780+ * FastAPI ChartData를 LibraryDashboard ChartData로 변환
781+ */
782+ private LibraryResponseDTO .LibraryDashboard .ChartData convertToChartData (
783+ FastChartResponseDTO .ChartData fastChartData ) {
784+ if (fastChartData == null ) {
785+ return null ;
786+ }
787+
788+ List <LibraryResponseDTO .LibraryDashboard .ChartDataPoint > dataPoints = fastChartData .getData ()
789+ .stream ()
790+ .map (this ::convertToChartDataPoint )
791+ .toList ();
792+
793+ return LibraryResponseDTO .LibraryDashboard .ChartData .builder ()
794+ .chartType (fastChartData .getChartType ())
795+ .metric (fastChartData .getMetric ())
796+ .title (fastChartData .getTitle ())
797+ .reasoning (fastChartData .getReasoning ())
798+ .data (dataPoints )
799+ .build ();
800+ }
801+
802+ /**
803+ * FastAPI ChartDataPoint를 LibraryDashboard ChartDataPoint로 변환
804+ */
805+ private LibraryResponseDTO .LibraryDashboard .ChartDataPoint convertToChartDataPoint (
806+ FastChartResponseDTO .ChartDataPoint fastDataPoint ) {
807+ if (fastDataPoint == null ) {
808+ return null ;
809+ }
810+
811+ return LibraryResponseDTO .LibraryDashboard .ChartDataPoint .builder ()
812+ .category (fastDataPoint .getCategory ())
813+ .value (fastDataPoint .getValue ())
814+ .male (fastDataPoint .getMale ())
815+ .maleMax (fastDataPoint .getMaleMax ())
816+ .female (fastDataPoint .getFemale ())
817+ .femaleMax (fastDataPoint .getFemaleMax ())
818+ .id (fastDataPoint .getId ())
819+ .name (fastDataPoint .getName ())
820+ .build ();
821+ }
822+
650823 // 라이브러리 생성 결과를 담는 내부 클래스
651824 @ lombok .Getter
652825 @ lombok .AllArgsConstructor
0 commit comments