Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat/CK-114] 로드맵 리뷰를 조회하는 기능을 구현한다 #74

Merged
merged 9 commits into from
Aug 10, 2023
11 changes: 11 additions & 0 deletions backend/kirikiri/src/docs/asciidoc/roadmap.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,14 @@ operation::roadmap-read-api-test/로드맵의_골룸_목록을_조건에_따라_
=== *8-2* 실패 - 존재하지 않는 로드맵인 경우

operation::roadmap-read-api-test/로드맵의_골룸_목록을_조건에_따라_조회할_때_로드맵이_존재하지_않으면_예외가_발생한다[snippets='http-request,query-parameters,http-response,response-fields']

[[로드맵리뷰조회-API]]
== *9. 로드맵의 리뷰 목록 조회 API*

=== *9-1* 성공

operation::roadmap-read-api-test/로드맵의_리뷰들을_조회한다[snippets='http-request,http-response,path-parameters,query-parameters,response-fields']

=== *9-1* 실패 - 존재하지 않는 로드맵인 경우

operation::roadmap-read-api-test/로드맵_리뷰_조회_시_유효하지_않은_로드맵_아이디일_경우_예외를_반환한다[snippets='http-request,http-response,path-parameters,query-parameters,response-fields']
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import co.kirikiri.service.dto.roadmap.response.RoadmapForListResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapGoalRoomResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapReviewResponse;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
Expand Down Expand Up @@ -108,4 +109,12 @@ public ResponseEntity<List<RoadmapGoalRoomResponse>> findGoalRoomsByFilterType(
roadmapId, roadmapGoalRoomsFilterTypeDto, scrollRequest);
return ResponseEntity.ok(responses);
}

@GetMapping("/{roadmapId}/reviews")
public ResponseEntity<List<RoadmapReviewResponse>> findRoadmapReview(
@PathVariable final Long roadmapId,
@ModelAttribute final CustomScrollRequest scrollRequest) {
final List<RoadmapReviewResponse> responses = roadmapReadService.findRoadmapReviews(roadmapId, scrollRequest);
return ResponseEntity.ok(responses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import java.util.regex.Pattern;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -73,4 +74,20 @@ public void updateRoadmap(final Roadmap roadmap) {
public boolean isNotSameRoadmap(final Roadmap roadmap) {
return this.roadmap == null || !this.roadmap.equals(roadmap);
}

public String getContent() {
return content;
}

public Double getRate() {
return rate;
}

public String getMemberNickname() {
return member.getNickname().getValue();
}

public LocalDateTime getCreatedAt() {
return createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package co.kirikiri.persistence.roadmap;

import co.kirikiri.domain.roadmap.Roadmap;
import co.kirikiri.domain.roadmap.RoadmapReview;
import co.kirikiri.persistence.dto.RoadmapLastValueDto;
import java.util.List;

public interface RoadmapReviewQueryRepository {

List<RoadmapReview> findRoadmapReviewWithMemberByRoadmapOrderByLatest(final Roadmap roadmap,
final RoadmapLastValueDto lastValue,
final int pageSize);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package co.kirikiri.persistence.roadmap;

import static co.kirikiri.domain.member.QMember.member;
import static co.kirikiri.domain.roadmap.QRoadmap.roadmap;
import static co.kirikiri.domain.roadmap.QRoadmapReview.roadmapReview;

import co.kirikiri.domain.roadmap.Roadmap;
import co.kirikiri.domain.roadmap.RoadmapReview;
import co.kirikiri.persistence.QuerydslRepositorySupporter;
import co.kirikiri.persistence.dto.RoadmapLastValueDto;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import java.time.LocalDateTime;
import java.util.List;

public class RoadmapReviewQueryRepositoryImpl extends QuerydslRepositorySupporter implements
RoadmapReviewQueryRepository {

public RoadmapReviewQueryRepositoryImpl() {
super(RoadmapReview.class);
}

@Override
public List<RoadmapReview> findRoadmapReviewWithMemberByRoadmapOrderByLatest(final Roadmap roadmap,
final RoadmapLastValueDto lastValue,
final int pageSize) {
return selectFrom(roadmapReview)
.innerJoin(roadmapReview.member, member)
.fetchJoin()
.where(roadmapCond(roadmap), lessThanLastValue(lastValue))
.limit(pageSize)
.orderBy(orderByCreatedAtDesc())
.fetch();
}

private BooleanExpression roadmapCond(final Roadmap roadmap) {
return roadmapReview.roadmap.eq(roadmap);
}

private BooleanExpression lessThanLastValue(final RoadmapLastValueDto lastValue) {
if (lastValue == null) {
return null;
}
return roadmap.createdAt.lt(lastValue.getLastCreatedAt());
}

private OrderSpecifier<LocalDateTime> orderByCreatedAtDesc() {
return roadmapReview.createdAt.desc();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoadmapReviewRepository extends JpaRepository<RoadmapReview, Long> {
public interface RoadmapReviewRepository extends JpaRepository<RoadmapReview, Long>, RoadmapReviewQueryRepository {

Optional<RoadmapReview> findByRoadmapAndMember(final Roadmap roadmap, final Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import co.kirikiri.domain.roadmap.Roadmap;
import co.kirikiri.domain.roadmap.RoadmapCategory;
import co.kirikiri.domain.roadmap.RoadmapContent;
import co.kirikiri.domain.roadmap.RoadmapReview;
import co.kirikiri.exception.NotFoundException;
import co.kirikiri.persistence.dto.GoalRoomLastValueDto;
import co.kirikiri.persistence.dto.RoadmapFilterType;
Expand All @@ -17,6 +18,7 @@
import co.kirikiri.persistence.roadmap.RoadmapCategoryRepository;
import co.kirikiri.persistence.roadmap.RoadmapContentRepository;
import co.kirikiri.persistence.roadmap.RoadmapRepository;
import co.kirikiri.persistence.roadmap.RoadmapReviewRepository;
import co.kirikiri.service.dto.CustomScrollRequest;
import co.kirikiri.service.dto.roadmap.RoadmapGoalRoomsFilterTypeDto;
import co.kirikiri.service.dto.roadmap.request.RoadmapFilterTypeRequest;
Expand All @@ -26,6 +28,7 @@
import co.kirikiri.service.dto.roadmap.response.RoadmapForListResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapGoalRoomResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapReviewResponse;
import co.kirikiri.service.mapper.GoalRoomMapper;
import co.kirikiri.service.mapper.RoadmapMapper;
import java.util.List;
Expand All @@ -41,6 +44,7 @@ public class RoadmapReadService {
private final RoadmapRepository roadmapRepository;
private final RoadmapCategoryRepository roadmapCategoryRepository;
private final RoadmapContentRepository roadmapContentRepository;
private final RoadmapReviewRepository roadmapReviewRepository;
private final GoalRoomRepository goalRoomRepository;
private final MemberRepository memberRepository;

Expand Down Expand Up @@ -121,4 +125,13 @@ public List<RoadmapGoalRoomResponse> findRoadmapGoalRoomsByFilterType(final Long
roadmap, filterType, goalRoomLastValueDto, scrollRequest.size());
return GoalRoomMapper.convertToRoadmapGoalRoomResponses(goalRoomsWithPendingMembers);
}

public List<RoadmapReviewResponse> findRoadmapReviews(final Long roadmapId,
final CustomScrollRequest scrollRequest) {
final Roadmap roadmap = findRoadmapById(roadmapId);
final RoadmapLastValueDto roadmapLastValueDto = RoadmapLastValueDto.create(scrollRequest);
final List<RoadmapReview> roadmapReviews = roadmapReviewRepository.findRoadmapReviewWithMemberByRoadmapOrderByLatest(
roadmap, roadmapLastValueDto, scrollRequest.size());
return RoadmapMapper.convertToRoadmapReviewResponses(roadmapReviews);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package co.kirikiri.service.dto.roadmap.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;

public record RoadmapReviewResponse(
Long id,
String name,
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime createdAt,
String content,
Double rate
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public static List<GoalRoomTodoResponse> convertGoalRoomTodoResponses(final Goal
}

private static GoalRoomTodoResponse convertGoalRoomTodoResponse(final List<Long> checkedTodoIds,
final GoalRoomToDo goalRoomToDo) {
final GoalRoomToDo goalRoomToDo) {
final GoalRoomToDoCheckResponse checkResponse = new GoalRoomToDoCheckResponse(
isCheckedTodo(goalRoomToDo.getId(), checkedTodoIds));
return new GoalRoomTodoResponse(goalRoomToDo.getId(),
Expand All @@ -165,7 +165,8 @@ public static MemberGoalRoomResponse convertToMemberGoalRoomResponse(final GoalR
final List<Long> checkedTodoIds) {
final GoalRoomRoadmapNodesResponse nodeResponses = convertToGoalRoomRoadmapNodesResponse(
goalRoom.getGoalRoomRoadmapNodes());
final List<GoalRoomTodoResponse> todoResponses = convertGoalRoomTodoResponsesLimit(goalRoom.getGoalRoomToDos(), checkedTodoIds);
final List<GoalRoomTodoResponse> todoResponses = convertGoalRoomTodoResponsesLimit(goalRoom.getGoalRoomToDos(),
checkedTodoIds);
final List<CheckFeedResponse> checkFeedResponses = convertToCheckFeedResponses(checkFeeds);

return new MemberGoalRoomResponse(goalRoom.getName().getValue(), goalRoom.getStatus().name(),
Expand Down Expand Up @@ -194,7 +195,7 @@ private static GoalRoomRoadmapNodesResponse convertToGoalRoomRoadmapNodesRespons
}

private static List<GoalRoomTodoResponse> convertGoalRoomTodoResponsesLimit(final GoalRoomToDos goalRoomToDos,
final List<Long> checkedTodoIds) {
final List<Long> checkedTodoIds) {
return goalRoomToDos.getValues()
.stream()
.map(goalRoomToDo -> convertGoalRoomTodoResponse(checkedTodoIds, goalRoomToDo))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import co.kirikiri.domain.roadmap.RoadmapNode;
import co.kirikiri.domain.roadmap.RoadmapNodeImage;
import co.kirikiri.domain.roadmap.RoadmapNodes;
import co.kirikiri.domain.roadmap.RoadmapReview;
import co.kirikiri.domain.roadmap.RoadmapTags;
import co.kirikiri.persistence.dto.RoadmapFilterType;
import co.kirikiri.service.dto.member.response.MemberResponse;
Expand All @@ -25,6 +26,7 @@
import co.kirikiri.service.dto.roadmap.response.RoadmapForListResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapNodeResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapReviewResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapTagResponse;
import java.util.List;
import lombok.AccessLevel;
Expand Down Expand Up @@ -155,4 +157,16 @@ public static List<MemberRoadmapResponse> convertMemberRoadmapResponses(final Li
})
.toList();
}

public static List<RoadmapReviewResponse> convertToRoadmapReviewResponses(
final List<RoadmapReview> roadmapReviews) {
return roadmapReviews.stream()
.map(RoadmapMapper::convertToRoadmapReviewResponse)
.toList();
}

private static RoadmapReviewResponse convertToRoadmapReviewResponse(final RoadmapReview roadmapReview) {
return new RoadmapReviewResponse(roadmapReview.getId(), roadmapReview.getMemberNickname(),
roadmapReview.getCreatedAt(), roadmapReview.getContent(), roadmapReview.getRate());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import co.kirikiri.service.dto.roadmap.response.RoadmapGoalRoomResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapNodeResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapReviewResponse;
import co.kirikiri.service.dto.roadmap.response.RoadmapTagResponse;
import com.fasterxml.jackson.core.type.TypeReference;
import java.time.LocalDate;
Expand Down Expand Up @@ -542,6 +543,90 @@ class RoadmapReadApiTest extends ControllerTestHelper {
.isEqualTo(expected);
}

@Test
void 로드맵의_리뷰들을_조회한다() throws Exception {
// given
final List<RoadmapReviewResponse> expected = List.of(
new RoadmapReviewResponse(1L, "작성자1", LocalDateTime.now(), "리뷰 내용", 4.5),
new RoadmapReviewResponse(2L, "작성자2", LocalDateTime.now(), "리뷰 내용", 5.0)
);

when(roadmapReadService.findRoadmapReviews(anyLong(), any()))
.thenReturn(expected);

// when
final String response = mockMvc.perform(
get(API_PREFIX + "/roadmaps/{roadmapId}/reviews", 1L)
.param("lastCreatedAt", "")
.param("size", "10")
.contextPath(API_PREFIX))
.andExpect(status().isOk())
.andDo(documentationResultHandler.document(
pathParameters(
parameterWithName("roadmapId").description("로드맵 아이디")
),
queryParameters(
parameterWithName("lastCreatedAt").optional()
.description("이전에 가장 마지막으로 조회한 리뷰의 생성일자(첫 요청에는 없어도 상관없음)"),
parameterWithName("size").description("한 번에 조회할 리뷰갯수")
),
responseFields(
fieldWithPath("[0].id").description("리뷰 아이디"),
fieldWithPath("[0].name").description("작성자 닉네임"),
fieldWithPath("[0].createdAt").description("리뷰 최종 작성날짜"),
fieldWithPath("[0].content").description("리뷰 내용"),
fieldWithPath("[0].rate").description("별점")
)))
.andReturn().getResponse()
.getContentAsString();

// then
final List<RoadmapReviewResponse> reviewResponse = objectMapper.readValue(response,
new TypeReference<>() {
});

assertThat(reviewResponse)
.usingRecursiveComparison()
.ignoringFields("createdAt")
.isEqualTo(expected);
}

@Test
void 로드맵_리뷰_조회_시_유효하지_않은_로드맵_아이디일_경우_예외를_반환한다() throws Exception {
// given
when(roadmapReadService.findRoadmapReviews(anyLong(), any()))
.thenThrow(new NotFoundException("존재하지 않는 로드맵입니다. roadmapId = 1"));

// given
final String response = mockMvc.perform(
get(API_PREFIX + "/roadmaps/{roadmapId}/reviews", 1L)
.param("lastCreatedAt", "")
.param("size", "10")
.contextPath(API_PREFIX))
.andExpect(status().isNotFound())
.andDo(documentationResultHandler.document(
pathParameters(
parameterWithName("roadmapId").description("로드맵 아이디")
),
queryParameters(
parameterWithName("lastCreatedAt").optional()
.description("이전에 가장 마지막으로 조회한 리뷰의 생성일자(첫 요청에는 없어도 상관없음)"),
parameterWithName("size").description("한 번에 조회할 리뷰갯수")
),
responseFields(
fieldWithPath("message").description("예외 메시지")
)))
.andReturn().getResponse()
.getContentAsString();

// then
final ErrorResponse errorResponse = objectMapper.readValue(response, new TypeReference<>() {
});

assertThat(errorResponse.message())
.isEqualTo("존재하지 않는 로드맵입니다. roadmapId = 1");
}

private RoadmapResponse 단일_로드맵_조회에_대한_응답() {
final RoadmapCategoryResponse category = new RoadmapCategoryResponse(1, "운동");
final MemberResponse creator = new MemberResponse(1, "닉네임");
Expand Down
Loading