Skip to content

Commit

Permalink
Merge pull request #105 from oduck-team/feature/95
Browse files Browse the repository at this point in the history
애니 상세 조회 시 평가 평균 조회 api #95
  • Loading branch information
jaycobcoder authored Nov 14, 2023
2 parents 55ec660 + 16224bc commit 80c0555
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 28 deletions.
20 changes: 20 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,26 @@ include::{snippets}/getAnimeById/success/response-body.adoc[]
include::{snippets}/getAnimeById/success/response-fields.adoc[]
==== 실패 시

=== GET api/v1/animes/:id/ratings/average
.curl-request
include::{snippets}/getAnimeScoreAvg/success/curl-request.adoc[]

.http-request
include::{snippets}/getAnimeScoreAvg/success/http-request.adoc[]

include::{snippets}/getAnimeScoreAvg/success/path-parameters.adoc[]

==== 성공시
.http-response
include::{snippets}/getAnimeScoreAvg/success/http-response.adoc[]

.response-body
include::{snippets}/getAnimeScoreAvg/success/response-body.adoc[]

.response-fields
include::{snippets}/getAnimeScoreAvg/success/response-fields.adoc[]
==== 실패 시

= oDuckio Documentation
:sectnums:
:toc: left
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down Expand Up @@ -68,17 +69,14 @@ public ResponseEntity<Object> postAnime(@RequestBody @Valid AnimeReq.PostReq req
// 관리자 애니 조회
@GetMapping("/animes")
public ResponseEntity<Object> getAnimes(
@RequestParam(required = false) String query,
@RequestParam(required = false) @Length(min = 0, max = 50) 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<AdminRes.SearchResult> res = animeService.getPageByCondition(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.oduck.api.domain.anime.controller;

import io.oduck.api.domain.anime.dto.AnimeRes;
import io.oduck.api.domain.anime.dto.AnimeRes.StarRatingAvg;
import io.oduck.api.domain.anime.dto.SearchFilterDsl;
import io.oduck.api.domain.anime.entity.BroadcastType;
import io.oduck.api.domain.anime.entity.Quarter;
Expand All @@ -13,6 +14,7 @@
import jakarta.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
Expand All @@ -38,16 +40,14 @@ public class AnimeController {
// 애니 검색 조회
@GetMapping
public ResponseEntity<Object> getAnimesBySearchCondition(
@RequestParam(required = false) String query,
@RequestParam(required = false) @Length(min = 0, max = 50) String query,
@RequestParam(required = false) String cursor,
@RequestParam(required = false, defaultValue = "latest") Sort sort,
@RequestParam(required = false, defaultValue = "DESC") OrderDirection order,
@RequestParam(required = false, defaultValue = "10") @Min(1) @Max(100) int size,
@ModelAttribute SearchFilter searchFilter
){

validateQueryLength(query, 50);

List<Long> genreIds = searchFilter.getGenreIds();
List<BroadcastType> broadcastTypes = searchFilter.getBroadcastTypes();
List<EpisodeCountEnum> episodeCountEnums = searchFilter.getEpisodeCounts();
Expand Down Expand Up @@ -76,12 +76,13 @@ public ResponseEntity<Object> getAnimeById(@PathVariable Long animeId){
return ResponseEntity.ok(res);
}

private void validateQueryLength(String query, int maxLength) {
if(query != null) {
if(query.length() > maxLength){
throw new BadRequestException("글자수는 50자를 넘을 수 없습니다.");
}
}
// 애니 평가 평균 조회
@GetMapping("/{animeId}/ratings/average")
public ResponseEntity<Object> getStarRatingAvg(@PathVariable Long animeId){

StarRatingAvg res = animeService.getStarRatingAverage(animeId);

return ResponseEntity.ok(res);
}

private List<Integer> extractCurrentYearsNotInCurrentYear(List<Integer> years) {
Expand Down
32 changes: 28 additions & 4 deletions src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package io.oduck.api.domain.anime.dto;

import io.oduck.api.domain.anime.entity.*;
import io.oduck.api.domain.anime.entity.Anime;
import io.oduck.api.domain.anime.entity.AnimeGenre;
import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor;
import io.oduck.api.domain.anime.entity.AnimeStudio;
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.Rating;
import io.oduck.api.domain.anime.entity.Status;
import io.oduck.api.global.common.EntityBased;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class AnimeRes {

Expand Down Expand Up @@ -87,4 +94,21 @@ public String bringCursor(String property) {
}
}

@Getter
@NoArgsConstructor
public static class StarRatingAvg {
private Double starRatingAvg;

public StarRatingAvg(Long starRatingScoreTotal, Long starRatingCount) {
this.starRatingAvg = calculateAvg(starRatingScoreTotal, starRatingCount);
}

private double calculateAvg(Long starRatingScoreTotal, Long starRatingCount) {
if(starRatingCount <= 0) {
return 0;
}
return starRatingScoreTotal / starRatingCount;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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.AnimeRes.StarRatingAvg;
import io.oduck.api.domain.anime.dto.SearchFilterDsl;
import io.oduck.api.global.common.OrderDirection;
import io.oduck.api.global.common.PageResponse;
Expand Down Expand Up @@ -108,4 +109,11 @@ PageResponse<AdminRes.SearchResult> getPageByCondition(String query, QueryType q
* @param animeId 애니의 고유 식별자;
*/
void delete(Long animeId);

/**
* 애니의 평가 평균 가져오는 기준이다.
* @param animeId 애니의 고유 식별자;
* @return StarRatingAvg 애니의 평가 평균 dto;
*/
StarRatingAvg getStarRatingAverage(Long animeId);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
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.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.AnimeRes.StarRatingAvg;
import io.oduck.api.domain.anime.dto.AnimeVoiceActorReq;
import io.oduck.api.domain.anime.dto.SearchFilterDsl;
import io.oduck.api.domain.anime.entity.*;
import io.oduck.api.domain.anime.repository.*;
import io.oduck.api.domain.anime.entity.Anime;
import io.oduck.api.domain.anime.entity.AnimeGenre;
import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor;
import io.oduck.api.domain.anime.entity.AnimeStudio;
import io.oduck.api.domain.anime.entity.AnimeVoiceActor;
import io.oduck.api.domain.anime.repository.AnimeGenreRepository;
import io.oduck.api.domain.anime.repository.AnimeOriginalAuthorRepository;
import io.oduck.api.domain.anime.repository.AnimeRepository;
import io.oduck.api.domain.anime.repository.AnimeStudioRepository;
import io.oduck.api.domain.anime.repository.AnimeVoiceActorRepository;
import io.oduck.api.domain.genre.entity.Genre;
import io.oduck.api.domain.genre.repository.GenreRepository;
import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor;
Expand All @@ -22,20 +42,15 @@
import io.oduck.api.global.common.SliceResponse;
import io.oduck.api.global.exception.NotFoundException;
import io.oduck.api.global.utils.PagingUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

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;

@Service
@RequiredArgsConstructor
@Transactional
Expand Down Expand Up @@ -281,6 +296,14 @@ public void delete(Long animeId) {
anime.delete();
}

@Override
public StarRatingAvg getStarRatingAverage(Long animeId) {

Anime anime = findAnime(animeId);

return new StarRatingAvg(anime.getStarRatingScoreTotal(), anime.getStarRatingCount());
}

@Transactional(readOnly = true)
public Anime findAnime(Long animeId) {
return animeRepository.findById(animeId).orElseThrow(() -> new NotFoundException("Anime"));
Expand Down
39 changes: 38 additions & 1 deletion src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class AnimeControllerTest {

@Nested
@DisplayName("조회")
class GetAnime{
class GetAnime {

@Test
@DisplayName("애니 검색 조회 성공 시 Http Status 200 반환")
Expand Down Expand Up @@ -247,4 +247,41 @@ void getAnimeById() throws Exception {
//TODO : 조회 실패 시
}
}

@Nested
@DisplayName("애니 평점")
class GetAnimeScoreAvg {
@Test
@DisplayName("애니 평가 평균 조회 성공 시 Http Status 200 반환")
void getAnimeScoreAvg() throws Exception {
//given
Long animeId = 1L;

//when
ResultActions actions = mockMvc.perform(
RestDocumentationRequestBuilders.get("/animes/"+"{animeId}"+"/ratings/average", animeId)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
);

//then
actions
.andExpect(status().isOk())
.andExpect(jsonPath("$.starRatingAvg").exists())
.andDo(document("getAnimeScoreAvg/success",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(
parameterWithName("animeId")
.description("애니의 고유 식별자")
),
responseFields(
fieldWithPath("starRatingAvg")
.type(JsonFieldType.NUMBER)
.description("애니의 평가 평균")
)
));

}
}
}

0 comments on commit 80c0555

Please sign in to comment.