55import com .moongeul .backend .api .bookshelf .dto .DoneReadCalendarResponseDTO ;
66import com .moongeul .backend .api .bookshelf .dto .DoneReadBookshelfItemDTO ;
77import com .moongeul .backend .api .bookshelf .dto .DoneReadBookshelfResponseDTO ;
8+ import com .moongeul .backend .api .bookshelf .dto .DoneReadRatingRangeCountDTO ;
9+ import com .moongeul .backend .api .bookshelf .dto .DoneReadRatingSummaryResponseDTO ;
810import com .moongeul .backend .api .bookshelf .entity .DoneReadBookshelf ;
911import com .moongeul .backend .api .bookshelf .repository .DoneReadBookshelfRepository ;
1012import com .moongeul .backend .api .member .entity .Member ;
1113import com .moongeul .backend .api .member .repository .MemberRepository ;
14+ import com .moongeul .backend .api .post .dto .CategoryPostListResponseDTO ;
15+ import com .moongeul .backend .api .post .dto .PostDTO ;
1216import com .moongeul .backend .api .post .entity .Post ;
17+ import com .moongeul .backend .api .post .entity .Quote ;
1318import com .moongeul .backend .api .post .repository .PostRepository ;
19+ import com .moongeul .backend .api .post .repository .QuoteRepository ;
20+ import com .moongeul .backend .common .exception .BadRequestException ;
1421import com .moongeul .backend .common .exception .NotFoundException ;
1522import com .moongeul .backend .common .response .ErrorStatus ;
1623import lombok .RequiredArgsConstructor ;
1724import lombok .extern .slf4j .Slf4j ;
1825import org .springframework .data .domain .Page ;
1926import org .springframework .data .domain .PageRequest ;
2027import org .springframework .data .domain .Pageable ;
28+ import org .springframework .data .domain .Sort ;
2129import org .springframework .stereotype .Service ;
2230import org .springframework .transaction .annotation .Transactional ;
2331
2432import java .time .LocalDate ;
2533import java .time .LocalDateTime ;
2634import java .time .YearMonth ;
35+ import java .util .ArrayList ;
2736import java .util .LinkedHashMap ;
2837import java .util .List ;
2938import java .util .Map ;
39+ import java .util .stream .Collectors ;
3040
3141@ Slf4j
3242@ Service
@@ -36,6 +46,13 @@ public class DoneReadBookshelfService {
3646 private final DoneReadBookshelfRepository doneReadBookshelfRepository ;
3747 private final MemberRepository memberRepository ;
3848 private final PostRepository postRepository ;
49+ private final QuoteRepository quoteRepository ;
50+ private static final String [] RATING_RANGES = {
51+ "1.0~1.4" , "1.5~1.9" , "2.0~2.4" , "2.5~2.9" ,
52+ "3.0~3.4" , "3.5~3.9" , "4.0~4.4" , "4.5~5.0"
53+ };
54+ private static final double [] RANGE_STARTS = {1.0 , 1.5 , 2.0 , 2.5 , 3.0 , 3.5 , 4.0 , 4.5 };
55+ private static final double [] RANGE_ENDS = {1.4 , 1.9 , 2.4 , 2.9 , 3.4 , 3.9 , 4.4 , 5.0 };
3956
4057 @ Transactional (readOnly = true )
4158 public DoneReadBookshelfResponseDTO getDoneReadBooks (String email , Integer page , Integer size ) {
@@ -137,6 +154,75 @@ public DoneReadCalendarResponseDTO getDoneReadCalendar(String email, Integer yea
137154 .build ();
138155 }
139156
157+ // ์ฝ์ ์ฑ
๋ณ์ ์์ฝ ์กฐํ
158+ @ Transactional (readOnly = true )
159+ public DoneReadRatingSummaryResponseDTO getDoneReadRatingSummary (String email ) {
160+
161+ Member member = memberRepository .findByEmail (email )
162+ .orElseThrow (() -> new NotFoundException (ErrorStatus .USER_NOTFOUND_EXCEPTION .getMessage ()));
163+
164+ long totalBooks = doneReadBookshelfRepository .countByMember (member );
165+ List <Double > ratings = postRepository .findRatingsByMember (member );
166+
167+ int [] counts = new int [RATING_RANGES .length ];
168+ for (Double rating : ratings ) {
169+ if (rating == null ) {
170+ continue ;
171+ }
172+
173+ for (int i = 0 ; i < RATING_RANGES .length ; i ++) {
174+ if (rating >= RANGE_STARTS [i ] && rating <= RANGE_ENDS [i ] + 1e-9 ) {
175+ counts [i ]++;
176+ break ;
177+ }
178+ }
179+ }
180+
181+ List <DoneReadRatingRangeCountDTO > data = new ArrayList <>();
182+ for (int i = 0 ; i < RATING_RANGES .length ; i ++) {
183+ data .add (DoneReadRatingRangeCountDTO .builder ()
184+ .range (RATING_RANGES [i ])
185+ .count (counts [i ])
186+ .build ());
187+ }
188+
189+ return DoneReadRatingSummaryResponseDTO .builder ()
190+ .totalBooks (totalBooks )
191+ .data (data )
192+ .build ();
193+ }
194+
195+ // ์ฝ์ ์ฑ
๋ณ์ ๊ตฌ๊ฐ ์์ธ ์กฐํ
196+ @ Transactional (readOnly = true )
197+ public CategoryPostListResponseDTO getDoneReadRatingDetail (String email , String range , String sortBy , Integer page , Integer size ) {
198+
199+ Member member = memberRepository .findByEmail (email )
200+ .orElseThrow (() -> new NotFoundException (ErrorStatus .USER_NOTFOUND_EXCEPTION .getMessage ()));
201+
202+ int rangeIndex = findRangeIndex (range );
203+ Pageable pageable = PageRequest .of (page - 1 , size , resolveSort (sortBy ));
204+
205+ Page <Post > postPage = postRepository .findByMemberAndRatingBetween (
206+ member ,
207+ RANGE_STARTS [rangeIndex ],
208+ RANGE_ENDS [rangeIndex ],
209+ pageable
210+ );
211+
212+ List <PostDTO > postList = postPage .getContent ().stream ()
213+ .map (this ::convertToPostDTO )
214+ .collect (Collectors .toList ());
215+
216+ return CategoryPostListResponseDTO .builder ()
217+ .total (postPage .getTotalElements ())
218+ .page (page )
219+ .size (size )
220+ .totalPages (postPage .getTotalPages ())
221+ .isLast (postPage .isLast ())
222+ .data (postList )
223+ .build ();
224+ }
225+
140226 private DoneReadBookshelfItemDTO convertToItemDTO (DoneReadBookshelf doneReadBookshelf ) {
141227 Book book = doneReadBookshelf .getArticle ().getBook ();
142228
@@ -152,6 +238,86 @@ private DoneReadBookshelfItemDTO convertToItemDTO(DoneReadBookshelf doneReadBook
152238 .build ();
153239 }
154240
241+ private PostDTO convertToPostDTO (Post post ) {
242+
243+ Book book = post .getBook ();
244+
245+ PostDTO .MemberInfo memberInfo = PostDTO .MemberInfo .builder ()
246+ .memberId (post .getMember ().getId ())
247+ .nickname (post .getMember ().getNickname ())
248+ .profileImage (post .getMember ().getProfileImage ())
249+ .readingTasteType (post .getMember ().getReadingTasteType ())
250+ .build ();
251+
252+ PostDTO .BookInfo bookInfo = PostDTO .BookInfo .builder ()
253+ .isbn (book .getIsbn ())
254+ .bookImage (book .getBookImage ())
255+ .title (book .getTitle ())
256+ .author (book .getAuthor ())
257+ .publisher (book .getPublisher ())
258+ .pubdate (book .getPubdate ())
259+ .ratingAverage (book .getRatingAverage ())
260+ .build ();
261+
262+ List <Quote > quotes = quoteRepository .findByPostId (post .getId ());
263+ List <PostDTO .QuoteDTO > quoteDTOList = quotes .stream ()
264+ .map (quote -> PostDTO .QuoteDTO .builder ()
265+ .quoteContent (quote .getQuoteContent ())
266+ .pageNumber (quote .getPageNumber ())
267+ .build ())
268+ .collect (Collectors .toList ());
269+
270+ PostDTO .LikesCnt likesCnt = PostDTO .LikesCnt .builder ()
271+ .relatableCount (post .getRelatableCount ())
272+ .sameTasteCount (post .getSameTasteCount ())
273+ .impressiveExpressionCount (post .getImpressiveExpressionCount ())
274+ .wantToReadCount (post .getWantToReadCount ())
275+ .helpfulCount (post .getHelpfulCount ())
276+ .build ();
277+
278+ return PostDTO .builder ()
279+ .postId (post .getId ())
280+ .memberInfo (memberInfo )
281+ .created (post .getCreatedAt ())
282+ .bookInfo (bookInfo )
283+ .rating (post .getRating ())
284+ .content (post .getContent ())
285+ .readDate (post .getReadDate ())
286+ .quotesCnt (quoteDTOList .size ())
287+ .quotes (quoteDTOList )
288+ .likesCnt (likesCnt )
289+ .build ();
290+ }
291+
292+ private Sort resolveSort (String sortBy ) {
293+ if (sortBy == null ) {
294+ return Sort .by (Sort .Order .desc ("createdAt" ));
295+ }
296+
297+ return switch (sortBy .toUpperCase ()) {
298+ case "OLDEST" -> Sort .by (Sort .Order .asc ("createdAt" ));
299+ case "RATING_HIGH" -> Sort .by (Sort .Order .desc ("rating" ), Sort .Order .desc ("createdAt" ));
300+ case "RATING_LOW" -> Sort .by (Sort .Order .asc ("rating" ), Sort .Order .desc ("createdAt" ));
301+ case "LATEST" -> Sort .by (Sort .Order .desc ("createdAt" ));
302+ default -> Sort .by (Sort .Order .desc ("createdAt" ));
303+ };
304+ }
305+
306+ private int findRangeIndex (String range ) {
307+ if (range == null ) {
308+ throw new BadRequestException (ErrorStatus .INVALID_RATING_RANGE_EXCEPTION .getMessage ());
309+ }
310+
311+ String normalizedRange = range .replace (" " , "" );
312+ for (int i = 0 ; i < RATING_RANGES .length ; i ++) {
313+ if (RATING_RANGES [i ].equals (normalizedRange )) {
314+ return i ;
315+ }
316+ }
317+
318+ throw new BadRequestException (ErrorStatus .INVALID_RATING_RANGE_EXCEPTION .getMessage ());
319+ }
320+
155321 private static class CalendarDayAggregate {
156322 private Long postId ;
157323 private String isbn ;
0 commit comments