From a1e042c5fdcea31e4545244579792e71f2fe1e12 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Tue, 7 Nov 2023 21:52:22 +0900 Subject: [PATCH 01/14] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=B3=80=EA=B2=BD=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/anime/service/AnimeService.java | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java index bc084708..509cd371 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -1,85 +1,91 @@ package io.oduck.api.domain.anime.service; +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.Sort; +import static io.oduck.api.domain.anime.dto.AnimeRes.DetailResult; +import static io.oduck.api.domain.anime.dto.AnimeRes.SearchResult; + import io.oduck.api.domain.anime.dto.SearchFilterDsl; import io.oduck.api.global.common.OrderDirection; import io.oduck.api.global.common.SliceResponse; -import static io.oduck.api.domain.anime.dto.AnimeReq.*; -import static io.oduck.api.domain.anime.dto.AnimeRes.DetailResult; -import static io.oduck.api.domain.anime.dto.AnimeRes.SearchResult; - public interface AnimeService { /** - * 애니 저장 로직 - * @return AnimeId + * 애니 저장 로직이다. */ void save(PostReq req); /** - * 애니 상세 조회 로직 - * @param animeId - * @return AnimeRes + * 애니 상세 조회 로직이다. + * @param animeId 애니의 고유 식별자; + * @return DetailResult 애니 응답 dto; */ DetailResult getAnimeById(Long animeId); /** - * 애니 검색 결과 조회 - * @param query - * @param cursor - * @param sort - * @param order - * @param size - * @param searchFilterDsl - * @return SliceResponse + * 애니 검색 결과 조회이다. + * @param query 제목 검색 내용; + * @param cursor 마지막 애니의 제목; + * @param sort 정렬 기준; + * @param order 정렬 방향; + * @param size 불러올 검색 결과 크기; + * @param searchFilterDsl 검색 필터 dto; + * @return SliceResponse non-offset 페이징 dto */ SliceResponse getAnimesByCondition(String query, String cursor, Sort sort, OrderDirection order, int size, SearchFilterDsl searchFilterDsl); /** - * 애니 수정 로직 - * @param animeId - * @param req + * 애니 수정 로직이다. + * @param animeId 애니의 고유 식별자; + * @param patchReq 애니 수정 dto; */ - void update(Long animeId, PatchAnimeReq req); + void update(Long animeId, PatchAnimeReq patchReq); /** - * 애니 원작 작가 수정 로직 - * @param animeId - * @param originalAuthors + * 애니 원작 작가 수정 로직이다. + * @param animeId 애니의 고유 식별자; + * @param patchReq 애니 원작 작가 수정 dto; */ - void updateAnimeOriginalAuthors(Long animeId, PatchOriginalAuthorIdsReq originalAuthors); + void updateAnimeOriginalAuthors(Long animeId, PatchOriginalAuthorIdsReq patchReq); /** - * 애니 스튜디오 수정 로직 - * @param animeId - * @param patchReq + * 애니 스튜디오 수정 로직이다. + * @param animeId 애니의 고유 식별자; + * @param patchReq 애니 스튜디오 수정 dto; */ void updateAnimeStudios(Long animeId, PatchStudioIdsReq patchReq); /** - * 애니 성우 수정 로직 - * @param animeId - * @param patchReq + * 애니 성우 수정 로직이다. + * @param animeId 애니의 고유 식별자; + * @param patchReq 애니 성우 수정 dto; */ void updateAnimeVoiceActors(Long animeId, PatchVoiceActorIdsReq patchReq); /** - * 애니 장르 수정 로직 - * @param animeId - * @param patchReq + * 애니 장르 수정 로직이다. + * @param animeId 애니의 고유 식별자; + * @param patchReq 애니 장르 수정 dto; */ void updateAnimeGenres(Long animeId, PatchGenreIdsReq patchReq); /** - * 애니 시리즈 수정 로직 - * @param animeId - * @param patchReq + * 애니 시리즈 수정 로직이다. + * @param animeId 애니의 고유 식별자; + * @param patchReq 애니 시리즈 수정 dto; */ void updateSeries(Long animeId, PatchSeriesIdReq patchReq); /** - * 애니 삭제 로직 - * @param animeId + * 애니 삭제 로직이다. + * @param animeId 애니의 고유 식별자; */ void delete(Long animeId); } From 00e38f098d298f276448744352944a0836b695b5 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Tue, 7 Nov 2023 21:52:38 +0900 Subject: [PATCH 02/14] =?UTF-8?q?refactor:=20update=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=EC=9D=98=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/anime/service/AnimeServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index acb6cb13..51d19f82 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -164,12 +164,12 @@ private boolean isIdNull(Long id) { } @Override - public void update(Long animeId, PatchAnimeReq req) { + public void update(Long animeId, PatchAnimeReq patchReq) { Anime anime = findAnime(animeId); anime.update( - req.getTitle(), req.getSummary(), req.getBroadcastType(), req.getEpisodeCount(), req.getThumbnail(), req.getYear(), - req.getQuarter(), req.getRating(), req.getStatus(), req.isReleased() + patchReq.getTitle(), patchReq.getSummary(), patchReq.getBroadcastType(), patchReq.getEpisodeCount(), patchReq.getThumbnail(), patchReq.getYear(), + patchReq.getQuarter(), patchReq.getRating(), patchReq.getStatus(), patchReq.isReleased() ); } From 3c4af562917975c0a2f111df4ee18a38550656f1 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:32:41 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20=EC=BB=A8=EB=B2=84=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/config/WebConfig.java | 3 +++ .../converter/StringToAdminAnimeSortConverter.java | 13 +++++++++++++ .../converter/StringToAdminQueryTypeConverter.java | 11 +++++++++++ .../global/converter/StringToStatusConverter.java | 11 +++++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/converter/StringToAdminAnimeSortConverter.java create mode 100644 src/main/java/io/oduck/api/global/converter/StringToAdminQueryTypeConverter.java create mode 100644 src/main/java/io/oduck/api/global/converter/StringToStatusConverter.java diff --git a/src/main/java/io/oduck/api/global/config/WebConfig.java b/src/main/java/io/oduck/api/global/config/WebConfig.java index 6d66a2ec..6b7eaa6b 100644 --- a/src/main/java/io/oduck/api/global/config/WebConfig.java +++ b/src/main/java/io/oduck/api/global/config/WebConfig.java @@ -22,6 +22,9 @@ public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToBookmarkSortConverter()); registry.addConverter(new StringToShortReviewConverter()); registry.addConverter(new StringToAnimeSortConverter()); + registry.addConverter(new StringToAdminQueryTypeConverter()); + registry.addConverter(new StringToStatusConverter()); + registry.addConverter(new StringToAdminAnimeSortConverter()); } @Override diff --git a/src/main/java/io/oduck/api/global/converter/StringToAdminAnimeSortConverter.java b/src/main/java/io/oduck/api/global/converter/StringToAdminAnimeSortConverter.java new file mode 100644 index 00000000..684832af --- /dev/null +++ b/src/main/java/io/oduck/api/global/converter/StringToAdminAnimeSortConverter.java @@ -0,0 +1,13 @@ +package io.oduck.api.global.converter; + + +import static io.oduck.api.domain.admin.dto.AdminReq.Sort; + +import org.springframework.core.convert.converter.Converter; + +public class StringToAdminAnimeSortConverter implements Converter { + @Override + public Sort convert(String sort) { + return Sort.valueOf(sort.toUpperCase()); + } +} diff --git a/src/main/java/io/oduck/api/global/converter/StringToAdminQueryTypeConverter.java b/src/main/java/io/oduck/api/global/converter/StringToAdminQueryTypeConverter.java new file mode 100644 index 00000000..f46eea90 --- /dev/null +++ b/src/main/java/io/oduck/api/global/converter/StringToAdminQueryTypeConverter.java @@ -0,0 +1,11 @@ +package io.oduck.api.global.converter; + +import io.oduck.api.domain.admin.dto.AdminReq.QueryType; +import org.springframework.core.convert.converter.Converter; + +public class StringToAdminQueryTypeConverter implements Converter { + @Override + public QueryType convert(String type) { + return QueryType.valueOf(type.toUpperCase()); + } +} diff --git a/src/main/java/io/oduck/api/global/converter/StringToStatusConverter.java b/src/main/java/io/oduck/api/global/converter/StringToStatusConverter.java new file mode 100644 index 00000000..6328e860 --- /dev/null +++ b/src/main/java/io/oduck/api/global/converter/StringToStatusConverter.java @@ -0,0 +1,11 @@ +package io.oduck.api.global.converter; + +import io.oduck.api.domain.anime.entity.Status; +import org.springframework.core.convert.converter.Converter; + +public class StringToStatusConverter implements Converter { + @Override + public Status convert(String type) { + return Status.valueOf(type.toUpperCase()); + } +} From 0bccd1e7fc1ca95af2a879d0b0a169a2f609e728 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:37:22 +0900 Subject: [PATCH 04/14] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=95=A0=EB=8B=88=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20status=EC=9D=98=20=EC=83=81=ED=83=9C=EB=8F=84=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anime/controller/AnimeController.java | 7 ++- .../api/domain/anime/dto/SearchFilterDsl.java | 6 ++- .../repository/AnimeRepositoryCustomImpl.java | 53 ++++++++++--------- .../anime/service/AnimeServiceImpl.java | 4 +- 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java index 239f76c4..cb09998a 100644 --- a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java +++ b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java @@ -4,6 +4,7 @@ import io.oduck.api.domain.anime.dto.SearchFilterDsl; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Status; import io.oduck.api.domain.anime.service.AnimeService; import io.oduck.api.global.common.OrderDirection; import io.oduck.api.global.common.SliceResponse; @@ -57,9 +58,11 @@ public ResponseEntity getAnimesBySearchCondition( List quarters = searchFilter.getQuarters(); validateDuplicateQuarters(quarters); - SearchFilterDsl searchFilterDsl = new SearchFilterDsl(genreIds, broadcastTypes, episodeCountEnums, years, quarters); + List statuses = searchFilter.getStatuses(); - SliceResponse res = animeService.getAnimesByCondition(query, cursor, sort, + SearchFilterDsl searchFilterDsl = new SearchFilterDsl(genreIds, broadcastTypes, episodeCountEnums, years, quarters, statuses); + + SliceResponse res = animeService.getSliceByCondition(query, cursor, sort, order, size, searchFilterDsl); return ResponseEntity.ok(res); } diff --git a/src/main/java/io/oduck/api/domain/anime/dto/SearchFilterDsl.java b/src/main/java/io/oduck/api/domain/anime/dto/SearchFilterDsl.java index 64747548..ae43225c 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/SearchFilterDsl.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/SearchFilterDsl.java @@ -4,6 +4,7 @@ import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Status; import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; @@ -16,9 +17,10 @@ public class SearchFilterDsl { List episodeCountEnums; List years; List quarters; + List statuses; public SearchFilterDsl(List genreIds, List broadcastTypes, - List episodeCountEnums, List years, List quarters) { + List episodeCountEnums, List years, List quarters, List statuses) { this.genreIds = genreIds; this.broadcastTypes = broadcastTypes; this.episodeCountEnums = episodeCountEnums; @@ -29,5 +31,7 @@ public SearchFilterDsl(List genreIds, List broadcastTypes, .collect(Collectors.toList()); this.quarters = quarters; + + this.statuses = statuses; } } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java index 8b495286..d7a4d044 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java @@ -36,32 +36,33 @@ public class AnimeRepositoryCustomImpl implements AnimeRepositoryCustom{ private final JPAQueryFactory queryFactory; @Override - public Slice findAnimesByCondition(String query, String cursor, Pageable pageable, SearchFilterDsl searchFilterDsl) { + public Slice findSliceByCondition(String query, String cursor, Pageable pageable, SearchFilterDsl searchFilterDsl) { JPAQuery jpaQuery = queryFactory - .select( - Projections.constructor( - SearchResult.class, - anime.id, - anime.title, - anime.thumbnail, - anime.starRatingScoreTotal, - anime.starRatingCount - ) + .select( + Projections.constructor( + SearchResult.class, + anime.id, + anime.title, + anime.thumbnail, + anime.starRatingScoreTotal, + anime.starRatingCount ) - .from(anime) - .leftJoin(anime.animeGenres, animeGenre) - .where( - titleEq(query), - genreIdsIn(searchFilterDsl.getGenreIds()), - broadcastTypesIn(searchFilterDsl.getBroadcastTypes()), - compareEpisodeCount(searchFilterDsl.getEpisodeCountEnums()), - yearFilters(searchFilterDsl.getQuarters(), searchFilterDsl.getYears()), - cursorCondition(cursor, pageable), - isReleased(true), - notDeleted() - ) - .groupBy(anime.id) - .limit(pageable.getPageSize()); + ) + .from(anime) + .leftJoin(anime.animeGenres, animeGenre) + .where( + titleEq(query), + genreIdsIn(searchFilterDsl.getGenreIds()), + broadcastTypesIn(searchFilterDsl.getBroadcastTypes()), + compareEpisodeCount(searchFilterDsl.getEpisodeCountEnums()), + yearFilters(searchFilterDsl.getQuarters(), searchFilterDsl.getYears()), + statusIn(searchFilterDsl.getStatuses()), + cursorCondition(cursor, pageable), + isReleased(true), + notDeleted() + ) + .groupBy(anime.id) + .limit(pageable.getPageSize()); return fetchSliceByCursor(sortPath(anime), jpaQuery, pageable); } @@ -95,6 +96,10 @@ private List sortPath(QAnime anime) { return path; } + private BooleanExpression statusIn(List statuses) { + return isListEmpty(statuses) ? null : anime.status.in(statuses); + } + private BooleanExpression genreIdsIn(List genreIds) { return isListEmpty(genreIds) ? null : animeGenre.genre.id.in(genreIds); diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index 51d19f82..400b91fc 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -133,8 +133,8 @@ public DetailResult getAnimeById(Long animeId) { @Override @Transactional(readOnly = true) - public SliceResponse getAnimesByCondition(String query, String cursor, Sort sort, OrderDirection order, int size, SearchFilterDsl searchFilterDsl) { - Slice slice = animeRepository.findAnimesByCondition( + public SliceResponse getSliceByCondition(String query, String cursor, Sort sort, OrderDirection order, int size, SearchFilterDsl searchFilterDsl) { + Slice slice = animeRepository.findSliceByCondition( query, cursor, PagingUtils.applyPageableForNonOffset( From 91525f30632f6565d3519b86cc320836aa9356c0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:38:37 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=EB=A6=AC=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=96=B4=EB=93=9C=EB=AF=BC=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/AnimeRepositoryCustom.java | 9 +- .../repository/AnimeRepositoryCustomImpl.java | 92 ++++++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustom.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustom.java index 9dac6709..ffb5135f 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustom.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustom.java @@ -1,11 +1,18 @@ package io.oduck.api.domain.anime.repository; +import io.oduck.api.domain.admin.dto.AdminReq.QueryType; +import io.oduck.api.domain.admin.dto.AdminReq.SearchFilter; +import io.oduck.api.domain.admin.dto.AdminRes; import io.oduck.api.domain.anime.dto.SearchFilterDsl; +import io.oduck.api.global.common.PageResponse; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import static io.oduck.api.domain.anime.dto.AnimeRes.SearchResult; public interface AnimeRepositoryCustom { - Slice findAnimesByCondition(String query, String cursor, Pageable pageable, SearchFilterDsl searchFilterDsl); + Slice findSliceByCondition(String query, String cursor, Pageable pageable, SearchFilterDsl searchFilterDsl); + + PageResponse findPageByCondition(String query, QueryType queryType, + Pageable pageable, SearchFilter searchFilter); } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java index d7a4d044..cab73299 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepositoryCustomImpl.java @@ -13,10 +13,16 @@ import com.querydsl.core.util.StringUtils; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; +import io.oduck.api.domain.admin.dto.AdminReq.QueryType; +import io.oduck.api.domain.admin.dto.AdminReq.SearchFilter; +import io.oduck.api.domain.admin.dto.AdminRes; import io.oduck.api.domain.anime.dto.SearchFilterDsl; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.QAnime; import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Status; +import io.oduck.api.global.common.PageResponse; +import io.oduck.api.global.utils.QueryDslUtils; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -67,6 +73,87 @@ public Slice findSliceByCondition(String query, String cursor, Pag return fetchSliceByCursor(sortPath(anime), jpaQuery, pageable); } + @Override + public PageResponse findPageByCondition(String query, + QueryType queryType, Pageable pageable, SearchFilter searchFilter) { + JPAQuery jpaQuery = queryFactory + .select( + Projections.constructor( + AdminRes.SearchResult.class, + anime.id, + anime.title, + anime.thumbnail, + anime.year, + anime.quarter, + anime.isReleased, + anime.status, + anime.createdAt, + anime.series.id, + anime.series.title, + anime.bookmarkCount, + anime.starRatingScoreTotal, + anime.starRatingCount, + anime.reviewCount, + anime.viewCount + ) + ) + .from(anime) + .leftJoin(anime.series) + .where( + queryEq(query, queryType), + isReleased(searchFilter.getIsReleased()), + yearsIn(searchFilter.getYears()), + statusIn(searchFilter.getStatuses()), + notDeleted() + ); + + //TODO: 페이징 요청마다 total 쿼리를 구함 -> 디비 부하 + Long total = queryFactory + .select( + anime.count() + ) + .from(anime) + .where( + queryEq(query, queryType), + isReleased(searchFilter.getIsReleased()), + yearsIn(searchFilter.getYears()), + statusIn(searchFilter.getStatuses()), + notDeleted() + ) + .fetchOne(); + + return PageResponse.of( + QueryDslUtils.fetchPage( + sortPath(anime), jpaQuery, total, pageable + ) + ); + } + + private BooleanExpression queryEq(String query, QueryType queryType) { + if(StringUtils.isNullOrEmpty(query)) { + return null; + } + + if(queryType == null) { + queryType = QueryType.TITLE; + } + + // 쿼리 타입 계산 + if(queryType == QueryType.TITLE) { + return titleEq(query); + } else if(queryType == QueryType.SERIES) { + return anime.series.title.contains(query); + } else { + // 그 외 모두 아이디 검색으로 로직 수행 + try { + return anime.id.eq(Long.parseLong(query)); + // 숫자가 아닌 문자열이 올 경우 null 반환 + } catch (NumberFormatException e) { + return null; + } + } + } + private BooleanExpression yearFilters(List quarters, List years) { BooleanExpression firstExpression = currentYearsAndQuarters(quarters); BooleanExpression secondExpression = yearsIn(years); @@ -86,8 +173,9 @@ private BooleanExpression yearFilters(List quarters, List year return firstExpression.or(secondExpression); } - private BooleanExpression isReleased(boolean isReleased) { - return anime.isReleased.eq(isReleased); + private BooleanExpression isReleased(Boolean isReleased) { + + return isReleased == null ? null : anime.isReleased.eq(isReleased); } private List sortPath(QAnime anime) { From ad6cfebd453bae86f49d955783f5e22099cb0a49 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:39:16 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=96=B4=EB=93=9C=EB=AF=BC=20=EC=95=A0=EB=8B=88=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/anime/service/AnimeService.java | 22 ++++++++++++++++++- .../anime/service/AnimeServiceImpl.java | 21 ++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java index 509cd371..0acf9e83 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -11,8 +11,13 @@ import static io.oduck.api.domain.anime.dto.AnimeRes.DetailResult; import static io.oduck.api.domain.anime.dto.AnimeRes.SearchResult; +import io.oduck.api.domain.admin.dto.AdminReq; +import io.oduck.api.domain.admin.dto.AdminReq.QueryType; +import io.oduck.api.domain.admin.dto.AdminReq.SearchFilter; +import io.oduck.api.domain.admin.dto.AdminRes; import io.oduck.api.domain.anime.dto.SearchFilterDsl; import io.oduck.api.global.common.OrderDirection; +import io.oduck.api.global.common.PageResponse; import io.oduck.api.global.common.SliceResponse; public interface AnimeService { @@ -39,7 +44,22 @@ public interface AnimeService { * @param searchFilterDsl 검색 필터 dto; * @return SliceResponse non-offset 페이징 dto */ - SliceResponse getAnimesByCondition(String query, String cursor, Sort sort, OrderDirection order, int size, SearchFilterDsl searchFilterDsl); + SliceResponse getSliceByCondition(String query, String cursor, Sort sort, OrderDirection order, int size, SearchFilterDsl searchFilterDsl); + + /** + * 애니 검색 결과 조회이다. + * + * @param query 제목 검색 내용; + * @param queryType 검색의 타입이며 타입에 따라 쿼리의 질의가 다름; + * @param page 현재 페이지의 번호; + * @param size 하나의 페이지에 포함될 콘텐츠 사이즈; + * @param sort + * @param order + * @param searchFilter 검색 필터; + * @return PageResponse offset 페이징 dto + */ + PageResponse getPageByCondition(String query, QueryType queryType, int page, int size, + AdminReq.Sort sort, OrderDirection order, SearchFilter searchFilter); /** * 애니 수정 로직이다. diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index 400b91fc..3a4ecca0 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -1,5 +1,8 @@ package io.oduck.api.domain.anime.service; +import io.oduck.api.domain.admin.dto.AdminReq; +import io.oduck.api.domain.admin.dto.AdminReq.QueryType; +import io.oduck.api.domain.admin.dto.AdminRes; import io.oduck.api.domain.anime.dto.AnimeVoiceActorReq; import io.oduck.api.domain.anime.dto.SearchFilterDsl; import io.oduck.api.domain.anime.entity.*; @@ -15,6 +18,7 @@ import io.oduck.api.domain.voiceActor.entity.VoiceActor; import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; import io.oduck.api.global.common.OrderDirection; +import io.oduck.api.global.common.PageResponse; import io.oduck.api.global.common.SliceResponse; import io.oduck.api.global.exception.NotFoundException; import io.oduck.api.global.utils.PagingUtils; @@ -150,6 +154,23 @@ public SliceResponse getSliceByCondition(String query, String curs return SliceResponse.of(slice, items, sort.getSort()); } + @Override + @Transactional(readOnly = true) + public PageResponse getPageByCondition(String query, QueryType queryType, + int page, int size, AdminReq.Sort sort, OrderDirection order, AdminReq.SearchFilter searchFilter) { + return animeRepository.findPageByCondition( + query, + queryType, + PagingUtils.applyPageableForOffset( + page, + sort.getSort(), + order.getOrder(), + size + ), + searchFilter + ); + } + private Series findSeriesWhenIdNotNull(Long seriesId, boolean isSeriesIdNull) { if(isSeriesIdNull == true){ return null; From 41be5ee2cf69fbf5e75f91b1f833512df20ea44a Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:39:29 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=96=B4=EB=93=9C=EB=AF=BC=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java index d1098298..90a96f98 100644 --- a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java +++ b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java @@ -1,5 +1,10 @@ package io.oduck.api.domain.admin.controller; +import static io.oduck.api.domain.admin.dto.AdminReq.SearchFilter; + +import io.oduck.api.domain.admin.dto.AdminReq; +import io.oduck.api.domain.admin.dto.AdminReq.QueryType; +import io.oduck.api.domain.admin.dto.AdminRes; import io.oduck.api.domain.anime.dto.AnimeReq; import io.oduck.api.domain.anime.service.AnimeService; import io.oduck.api.domain.genre.dto.GenreReq; @@ -16,7 +21,12 @@ import io.oduck.api.domain.voiceActor.dto.VoiceActorReq; import io.oduck.api.domain.voiceActor.dto.VoiceActorRes; import io.oduck.api.domain.voiceActor.service.VoiceActorService; +import io.oduck.api.global.common.OrderDirection; +import io.oduck.api.global.common.PageResponse; +import io.oduck.api.global.exception.BadRequestException; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -29,6 +39,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Validated @@ -54,17 +65,51 @@ public ResponseEntity postAnime(@RequestBody @Valid AnimeReq.PostReq req return ResponseEntity.ok().build(); } - // 관리자 애니 조회 (isReleased = false도 조회) -// @GetMapping("/animes") -// public ResponseEntity getAnimes( -// Pageable pageable, String query, SearchCondition condition -// ){ -// -// //TODO: 애니 조회 로직 구현. -// Page res = adminAnimeService.getAnimes(pageable, query, condition); -// return ResponseEntity -// .ok(PageResponse.of(null)); -// } + // 관리자 애니 조회 + @GetMapping("/animes") + public ResponseEntity getAnimes( + @RequestParam(required = false) String query, + QueryType queryType, + @RequestParam(required = false, defaultValue = "latest") AdminReq.Sort sort, + @RequestParam(required = false, defaultValue = "DESC") OrderDirection order, + @RequestParam(required = false, defaultValue = "1") int page, + @RequestParam(required = false, defaultValue = "20") @Min(1) @Max(100) int size, + SearchFilter searchFilter + ){ + + validateQueryLength(query, 50); + + int validatedPage = validatePage(page); + + PageResponse res = animeService.getPageByCondition( + query, + queryType, + validatedPage, + size, + sort, + order, + searchFilter + ); + + return ResponseEntity.ok(res); + } + + private void validateQueryLength(String query, int maxLength) { + if(query != null) { + if(query.length() > maxLength){ + throw new BadRequestException("글자수는 50자를 넘을 수 없습니다."); + } + } + } + + private int validatePage(int page) { + // 클라이언트는 페이지 번호를 1부터 시작, 서버에서는 0부터 시작 + if(page <= 1) { + return 0; + }else { + return page - 1; + } + } //애니 관련 수정 @PatchMapping("/animes/{animeId}") @@ -77,7 +122,6 @@ public ResponseEntity patchAnime( return ResponseEntity.noContent().build(); } - // 애니 삭제 @DeleteMapping("/animes/{animeId}") public ResponseEntity deleteAnime(@PathVariable Long animeId) { From 245189610f6edec6469d4f4d15a353e9ea47dc63 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:39:53 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=20=EC=9D=91=EB=8B=B5=20dto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/admin/dto/AdminReq.java | 42 ++++++++++++++ .../oduck/api/domain/admin/dto/AdminRes.java | 57 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/admin/dto/AdminReq.java create mode 100644 src/main/java/io/oduck/api/domain/admin/dto/AdminRes.java diff --git a/src/main/java/io/oduck/api/domain/admin/dto/AdminReq.java b/src/main/java/io/oduck/api/domain/admin/dto/AdminReq.java new file mode 100644 index 00000000..372f7f2d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/admin/dto/AdminReq.java @@ -0,0 +1,42 @@ +package io.oduck.api.domain.admin.dto; + +import io.oduck.api.domain.anime.entity.Status; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +public class AdminReq { + + @Getter + @AllArgsConstructor + public static class SearchFilter { + private List years; + private List statuses; + private Boolean isReleased; + } + + @Getter + @AllArgsConstructor + public enum QueryType { + TITLE("title"), + SERIES("series"), + ID("id"); + + private final String type; + } + + @Getter + @AllArgsConstructor + public enum Sort { + LATEST("createdAt"), + SERIES("series"), + TITLE("title"), + BOOKMARK_COUNT("bookmarkCount"), + SCORE_TOTAL("starRatingScoreTotal"), + SCORE_COUNT("starRatingCount"), + REVIEW_COUNT("reviewCount"), + VIEW_COUNT("viewCount"); + + private final String sort; + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/admin/dto/AdminRes.java b/src/main/java/io/oduck/api/domain/admin/dto/AdminRes.java new file mode 100644 index 00000000..f6d5f659 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/admin/dto/AdminRes.java @@ -0,0 +1,57 @@ +package io.oduck.api.domain.admin.dto; + +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Status; +import java.time.LocalDateTime; +import lombok.Getter; + +public class AdminRes { + + @Getter + public static class SearchResult { + private Long id; + private String title; + private String thumbnail; + private int year; + private Quarter quarter; + private Boolean isReleased; + private Status status; + private LocalDateTime createdAt; + private Long seriesId; + private String seriesTitle; + private Long bookmarkCount; + private Long starRatingScoreTotal; + private Long starRatingCount; + private double starRatingAvg; + private Long reviewCount; + private Long viewCount; + + public SearchResult(Long id, String title, String thumbnail, int year, Quarter quarter, + boolean isReleased, Status status, LocalDateTime createdAt, Long seriesId, String seriesTitle, + Long bookmarkCount, Long starRatingScoreTotal, Long starRatingCount, Long reviewCount, Long viewCount) { + this.id = id; + this.title = title; + this.thumbnail = thumbnail; + this.year = year; + this.quarter = quarter; + this.isReleased = isReleased; + this.status = status; + this.createdAt = createdAt; + this.seriesId = seriesId; + this.seriesTitle = seriesTitle; + this.bookmarkCount = bookmarkCount; + this.starRatingScoreTotal = starRatingScoreTotal; + this.starRatingCount = starRatingCount; + this.starRatingAvg = calculateAvg(starRatingScoreTotal, starRatingCount); + this.reviewCount = reviewCount; + this.viewCount = viewCount; + } + + private double calculateAvg(Long starRatingScoreTotal, Long starRatingCount) { + if(starRatingCount <= 0) { + return 0; + } + return starRatingScoreTotal / starRatingCount; + } + } +} From 1ef85ce094ed77941e3d3af9b3fc4be385fc6c97 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:40:31 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20PageResponse=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=97=90=EC=84=9C=20=EC=9D=BC=EB=B0=98=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=A6=AC=ED=84=B4=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/common/PageResponse.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/global/common/PageResponse.java b/src/main/java/io/oduck/api/global/common/PageResponse.java index 38f2d126..e3564dce 100644 --- a/src/main/java/io/oduck/api/global/common/PageResponse.java +++ b/src/main/java/io/oduck/api/global/common/PageResponse.java @@ -9,12 +9,17 @@ public class PageResponse { private final List items; - private final PageInfo pageInfo; + private int page; + private int size; + private long totalElements; + private int totalPages; public PageResponse(Page page) { this.items = page.getContent(); - this.pageInfo = new PageInfo(page.getNumber() + 1, - page.getSize(), page.getTotalElements(), page.getTotalPages()); + this.page = page.getNumber() + 1; + this.size = page.getSize(); + this.totalElements = page.getTotalElements(); + this.totalPages = page.getTotalPages(); } public static PageResponse of(Page page) { From c4249b0fa17466cb7e5424d4e53843819c4f9cc2 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:41:46 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=95=20fet?= =?UTF-8?q?ch=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/utils/QueryDslUtils.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java index 3787f8da..2cff6bff 100644 --- a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java +++ b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java @@ -8,6 +8,8 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; @@ -30,6 +32,17 @@ public static Slice fetchSliceByCursor(List paths, JPAQuery quer return new SliceImpl<>(content, pageable, isHasNext(pageSize, content)); } + public static Page fetchPage(List paths, JPAQuery query, long total, Pageable pageable) { + Sort sort = pageable.getSort(); + + List content = query + .orderBy(getAllOrderSpecifiers(sort, paths)) + .limit(pageable.getPageSize()) + .offset(pageable.getOffset()) + .fetch(); + return new PageImpl<>(content, pageable, total); + } + // sort.order 객체로 OrderSpecifier를 생성하여 반환. public static OrderSpecifier[] getAllOrderSpecifiers(Sort sort, List paths) { List orders = convertToDslOrder(sort, paths); From c7ad9b7d9664090df8f2dbf289582dab9595e243 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:42:32 +0900 Subject: [PATCH 11/14] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=95=A0=EB=8B=88=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?status=20=EB=B0=98=EC=98=81=20=EB=B3=80=EA=B2=BD=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/unit/anime/repository/AnimeRepositoryTest.java | 6 ++++-- .../oduck/api/unit/anime/service/AnimeServiceTest.java | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java index 2aa8c516..31e85434 100644 --- a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java @@ -508,6 +508,8 @@ class GetAnime{ List episodeCountEnums = new ArrayList<>(); List years = new ArrayList<>(); List quarters = new ArrayList<>(); + List statuses = new ArrayList<>(); + @Test @DisplayName("애니 조회 성공") void getAnimes() { @@ -524,10 +526,10 @@ void getAnimes() { order.getOrder() ); - SearchFilterDsl searchFilter = new SearchFilterDsl(genreIds, broadcastTypes, episodeCountEnums, years, quarters); + SearchFilterDsl searchFilter = new SearchFilterDsl(genreIds, broadcastTypes, episodeCountEnums, years, quarters, statuses); //when - Slice animes = animeRepository.findAnimesByCondition( + Slice animes = animeRepository.findSliceByCondition( query, cursor, pageable, searchFilter ); diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index 078d620b..e24da070 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -42,6 +42,7 @@ import io.oduck.api.domain.anime.entity.AnimeVoiceActor; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Status; import io.oduck.api.domain.anime.repository.AnimeGenreRepository; import io.oduck.api.domain.anime.repository.AnimeOriginalAuthorRepository; import io.oduck.api.domain.anime.repository.AnimeRepository; @@ -120,6 +121,7 @@ class GetAnime{ List episodeCountEnums = new ArrayList<>(); List years = new ArrayList<>(); List quarters = new ArrayList<>(); + List statuses = new ArrayList<>(); @Test @DisplayName("애니 제목 검색") @@ -130,7 +132,7 @@ void getAnimes() { int size = 10; Sort sort = Sort.LATEST; OrderDirection order = OrderDirection.DESC; - SearchFilterDsl searchFilter = new SearchFilterDsl(genreIds, broadcastTypes, episodeCountEnums, years, quarters); + SearchFilterDsl searchFilter = new SearchFilterDsl(genreIds, broadcastTypes, episodeCountEnums, years, quarters, statuses); List content = new ArrayList<>(); Slice searchResults = new SliceImpl<>(content); @@ -142,7 +144,7 @@ void getAnimes() { ); given( - animeRepository.findAnimesByCondition( + animeRepository.findSliceByCondition( query, cursor, pageable, @@ -151,13 +153,13 @@ void getAnimes() { ).willReturn(searchResults); //when - animeService.getAnimesByCondition(query, cursor, sort, order, size, searchFilter); + animeService.getSliceByCondition(query, cursor, sort, order, size, searchFilter); //then assertThatNoException(); //verify - verify(animeRepository, times(1)).findAnimesByCondition(query, cursor, pageable, searchFilter); + verify(animeRepository, times(1)).findSliceByCondition(query, cursor, pageable, searchFilter); } @Test From 139136f2eed826a0760b607207b56df90b165cc1 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:43:24 +0900 Subject: [PATCH 12/14] =?UTF-8?q?test:=20=EC=95=A0=EB=8B=88=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=EC=A1=B0=ED=9A=8C=20e2e=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/admin/AdminControllerTest.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java index 8c94f889..206398f4 100644 --- a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java @@ -37,6 +37,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -161,6 +162,142 @@ void postAnime() throws Exception { } //TODO : 애니 등록 실패 시 + @Test + @DisplayName("조회 성공 시 Http Status 200 반환") + void getAnimes() throws Exception { + //when + ResultActions actions = mockMvc.perform( + RestDocumentationRequestBuilders.get(ADMIN_URL+"/animes") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + //then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.items").exists()) + .andExpect(jsonPath("$.items[0].id").exists()) + .andExpect(jsonPath("$.items[0].title").exists()) + .andExpect(jsonPath("$.items[0].thumbnail").exists()) + .andExpect(jsonPath("$.items[0].year").exists()) + .andExpect(jsonPath("$.items[0].quarter").exists()) + .andExpect(jsonPath("$.items[0].status").exists()) + .andExpect(jsonPath("$.items[0].createdAt").exists()) + .andExpect(jsonPath("$.items[0].seriesId").exists()) + .andExpect(jsonPath("$.items[0].seriesTitle").exists()) + .andExpect(jsonPath("$.items[0].bookmarkCount").exists()) + .andExpect(jsonPath("$.items[0].starRatingScoreTotal").exists()) + .andExpect(jsonPath("$.items[0].starRatingCount").exists()) + .andExpect(jsonPath("$.items[0].starRatingAvg").exists()) + .andExpect(jsonPath("$.items[0].reviewCount").exists()) + .andExpect(jsonPath("$.items[0].viewCount").exists()) + .andExpect(jsonPath("$.items[0].isReleased").exists()) + .andExpect(jsonPath("$.page").exists()) + .andExpect(jsonPath("$.size").exists()) + .andExpect(jsonPath("$.totalElements").exists()) + .andExpect(jsonPath("$.totalPages").exists()) + .andDo(document("getAdminAnimes/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + queryParameters( + parameterWithName("query") + .attributes(field("constraints", "1-50자만 허용합니다")) + .optional() + .description("검색 내용. 1-50자만 허용합니다"), + parameterWithName("queryType") + .attributes(field("constraints", "TITLE(애니의 제목), SERIES(시리즈의 제목), ID(애니의 아이디)만 허용")) + .optional() + .description("검색 타입. TITLE(애니의 제목), SERIES(시리즈의 제목), ID(애니의 아이디)만 허용."), + parameterWithName("size") + .attributes(field("constraints", "1-100, 기본 20")) + .optional() + .description("한 페이지에 보여줄 아이템의 개수. 1-100, 기본 20"), + parameterWithName("sort") + .attributes(field("constraints", "LATEST, SERIES, TITLE, BOOKMARK_COUNT, SCORE_TOTAL, SCORE_COUNT, REVIEW_COUNT, VIEW_COUNT")) + .optional() + .description("정렬 기준이다. LATEST, SERIES, TITLE, BOOKMARK_COUNT, SCORE_TOTAL, SCORE_COUNT, REVIEW_COUNT, VIEW_COUNT만 허용한다."), + parameterWithName("direction") + .attributes(field("constraints", "ASC, DESC")) + .optional() + .description("정렬 방향이다. ASC, DESC만 허용한다."), + parameterWithName("statuses") + .attributes(field("constraints", "Status의 리스트 ONGOING, FINISHED, UPCOMING, UNKNOWN")) + .optional() + .description("status의 리스트. ONGOING, FINISHED, UPCOMING, UNKNOWN"), + parameterWithName("years") + .attributes(field("constraints", "년도의 리스트. 현재 년도는 내부 로직으로 걸러집니다.")) + .optional() + .description("년도의 리스트. 현재 년도는 내부 로직으로 걸러집니다.") + ), + responseFields( + fieldWithPath("items") + .type(JsonFieldType.ARRAY) + .description("애니 검색 결과 리스트"), + fieldWithPath("items[].id") + .type(JsonFieldType.NUMBER) + .description("애니의 고유 식별자"), + fieldWithPath("items[].title") + .type(JsonFieldType.STRING) + .description("애니의 제목"), + fieldWithPath("items[].thumbnail") + .type(JsonFieldType.STRING) + .description("애니의 이미지 경로"), + fieldWithPath("items[].year") + .type(JsonFieldType.NUMBER) + .description("애니의 출시년도"), + fieldWithPath("items[].quarter") + .type(JsonFieldType.STRING) + .description("애니의 출시분기"), + fieldWithPath("items[].isReleased") + .type(JsonFieldType.BOOLEAN) + .description("클라이언트가 애니를 열람할 수 있는지 여부"), + fieldWithPath("items[].status") + .type(JsonFieldType.STRING) + .description("애니의 상태"), + fieldWithPath("items[].createdAt") + .type(JsonFieldType.STRING) + .description("애니 생성 날짜"), + fieldWithPath("items[].seriesId") + .type(JsonFieldType.NUMBER) + .description("시리즈의 식별자"), + fieldWithPath("items[].seriesTitle") + .type(JsonFieldType.STRING) + .description("시리즈의 제목"), + fieldWithPath("items[].bookmarkCount") + .type(JsonFieldType.NUMBER) + .description("애니를 북마크한 숫자"), + fieldWithPath("items[].starRatingScoreTotal") + .type(JsonFieldType.NUMBER) + .description("애니 평가 점수 합산"), + fieldWithPath("items[].starRatingCount") + .type(JsonFieldType.NUMBER) + .description("애니 평가한 횟수"), + fieldWithPath("items[].starRatingAvg") + .type(JsonFieldType.NUMBER) + .description("애니 평가 평균(평점 평균)"), + fieldWithPath("items[].reviewCount") + .type(JsonFieldType.NUMBER) + .description("애니 리뷰한 횟수"), + fieldWithPath("items[].viewCount") + .type(JsonFieldType.NUMBER) + .description("애니 조회수"), + fieldWithPath("page") + .type(JsonFieldType.NUMBER) + .description("현재 페이지의 번호. 클라이언트는 1부터 보내야 함. 음수나 0이 와도 1로 계산"), + fieldWithPath("size") + .type(JsonFieldType.NUMBER) + .description("한 페이지에 보여줄 아이템의 개수. 1-100, 기본 20"), + fieldWithPath("totalElements") + .type(JsonFieldType.NUMBER) + .description("총 검색된 애니의 카운트"), + fieldWithPath("totalPages") + .type(JsonFieldType.NUMBER) + .description("페이지의 총 카운트") + ) + )); + + } + + @Test @DisplayName("애니 수정 성공 시 Http Status 204 반환") void patchAnime() throws Exception { From 9d3c968ced45c96e586a7e33063cc5d4c06466f3 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:43:42 +0900 Subject: [PATCH 13/14] =?UTF-8?q?docs:=20=EC=95=A0=EB=8B=88=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=EC=A1=B0=ED=9A=8C=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=ED=99=94=20=EC=B6=94=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 8c7d3bde..fd27cbd5 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -781,6 +781,26 @@ include::{snippets}/postAnime/success/http-response.adoc[] ==== 실패시 +=== GET api/v1/oduckdmin/animes +.curl-request +include::{snippets}/getAdminAnimes/success/curl-request.adoc[] + +.http-request +include::{snippets}/getAdminAnimes/success/http-request.adoc[] + +.query-parameters +include::{snippets}/getAdminAnimes/success/query-parameters.adoc[] + +.http-response +include::{snippets}/getAdminAnimes/success/http-response.adoc[] + +.response-body +include::{snippets}/getAdminAnimes/success/response-body.adoc[] + +.response-fields +include::{snippets}/getAdminAnimes/success/response-fields.adoc[] + + === PATCH api/v1/oduckdmin/animes/:id .curl-request include::{snippets}/patchAnime/success/curl-request.adoc[] From acd6fecc225d21ee63d32791e30b68ce4f5db57d Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 10 Nov 2023 21:51:39 +0900 Subject: [PATCH 14/14] =?UTF-8?q?test:=20queryType=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java index 206398f4..998df77c 100644 --- a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java @@ -204,9 +204,9 @@ void getAnimes() throws Exception { .optional() .description("검색 내용. 1-50자만 허용합니다"), parameterWithName("queryType") - .attributes(field("constraints", "TITLE(애니의 제목), SERIES(시리즈의 제목), ID(애니의 아이디)만 허용")) + .attributes(field("constraints", "TITLE(애니의 제목), SERIES(시리즈의 제목), ID(애니의 아이디)만 허용.")) .optional() - .description("검색 타입. TITLE(애니의 제목), SERIES(시리즈의 제목), ID(애니의 아이디)만 허용."), + .description("검색 타입. TITLE(애니의 제목), SERIES(시리즈의 제목), ID(애니의 아이디)만 허용. queryType이 id로 보내면 query는 Number형으로 보내야 함. 그런데 Number가 아닌 String으로 보내면 전체 검색으로 처리"), parameterWithName("size") .attributes(field("constraints", "1-100, 기본 20")) .optional()