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

인기게시글 랭킹 목록 조회 기능 #546

Merged
merged 13 commits into from
Sep 12, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.votogether.domain.post.dto.request.post.PostCreateRequest;
import com.votogether.domain.post.dto.request.post.PostUpdateRequest;
import com.votogether.domain.post.dto.response.post.PostDetailResponse;
import com.votogether.domain.post.dto.response.post.PostRankingResponse;
import com.votogether.domain.post.dto.response.post.PostResponse;
import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse;
import com.votogether.domain.post.entity.vo.PostClosingType;
Expand Down Expand Up @@ -175,6 +176,12 @@ public ResponseEntity<Void> save(
return ResponseEntity.created(URI.create("/posts/" + postId)).build();
}

@GetMapping("ranking/popular/guest")
public ResponseEntity<List<PostRankingResponse>> getRanking() {
final List<PostRankingResponse> responses = postService.getRanking();
return ResponseEntity.ok(responses);
}

@PutMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> update(
@PathVariable final Long postId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.votogether.domain.post.dto.request.post.PostCreateRequest;
import com.votogether.domain.post.dto.request.post.PostUpdateRequest;
import com.votogether.domain.post.dto.response.post.PostDetailResponse;
import com.votogether.domain.post.dto.response.post.PostRankingResponse;
import com.votogether.domain.post.dto.response.post.PostResponse;
import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse;
import com.votogether.domain.post.entity.vo.PostClosingType;
Expand Down Expand Up @@ -186,6 +187,10 @@ ResponseEntity<List<PostResponse>> getPostsByMe(
final Member member
);

@Operation(summary = "인기 게시글 랭킹 조회", description = "인기 게시글 랭킹을 조회한다.")
@ApiResponse(responseCode = "200", description = "인기 게시글 랭킹 조회 성공")
ResponseEntity<List<PostRankingResponse>> getRanking();

@Operation(summary = "게시글 작성", description = "게시글을 작성한다.")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "게시글 작성 성공"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.votogether.domain.post.dto.response.post;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "게시글 랭킹 정보 응답")
public record PostRankingResponse(
@Schema(description = "게시글 랭킹", example = "1")
int ranking,

@Schema(description = "게시글 정보")
PostSummaryResponse postSummaryResponse
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.votogether.domain.post.dto.response.post;

import com.votogether.domain.post.entity.Post;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "게시글 간략 정보 응답")
public record PostSummaryResponse(
@Schema(description = "게시글 ID", example = "1")
Long id,

@Schema(description = "작성자", example = "익명의손님1")
String writer,

@Schema(description = "게시글 제목", example = "제목")
String title,

@Schema(description = "투표 수", example = "123")
long voteCount
) {

public static PostSummaryResponse from(final Post post) {
return new PostSummaryResponse(
post.getId(),
post.getWriter().getNickname(),
post.getPostBody().getTitle(),
post.getTotalVoteCount()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import com.votogether.domain.post.dto.request.post.PostOptionUpdateRequest;
import com.votogether.domain.post.dto.request.post.PostUpdateRequest;
import com.votogether.domain.post.dto.response.post.PostDetailResponse;
import com.votogether.domain.post.dto.response.post.PostRankingResponse;
import com.votogether.domain.post.dto.response.post.PostResponse;
import com.votogether.domain.post.dto.response.post.PostSummaryResponse;
import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse;
import com.votogether.domain.post.entity.Post;
import com.votogether.domain.post.entity.PostBody;
Expand Down Expand Up @@ -365,5 +367,44 @@ private <T, R> List<R> transformElements(final List<T> elements, final Function<
.toList();
}

@Transactional(readOnly = true)
public List<PostRankingResponse> getRanking() {
final List<Post> posts = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId(
PostClosingType.ALL,
PostSortType.HOT,
null,
PageRequest.of(0, BASIC_PAGING_SIZE)
);

final Map<Post, Integer> rankings = calculateRanking(posts);

return rankings.entrySet().stream()
.map(entry ->
new PostRankingResponse(
entry.getValue(),
PostSummaryResponse.from(entry.getKey())
)
)
.toList();
}

private Map<Post, Integer> calculateRanking(final List<Post> posts) {
final Map<Post, Integer> rankings = new LinkedHashMap<>();

int currentRanking = 1;
int previousRanking = -1;
long previousVoteCount = -1;
for (Post post : posts) {
final long currentVoteCount = post.getTotalVoteCount();
final int ranking = (currentVoteCount == previousVoteCount) ? previousRanking : currentRanking;
rankings.put(post, ranking);

previousRanking = ranking;
previousVoteCount = currentVoteCount;
currentRanking++;
}

return rankings;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import com.votogether.domain.post.dto.request.post.PostOptionUpdateRequest;
import com.votogether.domain.post.dto.request.post.PostUpdateRequest;
import com.votogether.domain.post.dto.response.post.PostDetailResponse;
import com.votogether.domain.post.dto.response.post.PostRankingResponse;
import com.votogether.domain.post.dto.response.post.PostResponse;
import com.votogether.domain.post.dto.response.post.PostSummaryResponse;
import com.votogether.domain.post.dto.response.post.WriterResponse;
import com.votogether.domain.post.dto.response.vote.VoteCountForAgeGroupResponse;
import com.votogether.domain.post.dto.response.vote.VoteDetailResponse;
Expand Down Expand Up @@ -57,6 +59,7 @@
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

Expand Down Expand Up @@ -658,6 +661,7 @@ void postClosedEarly() throws Exception {
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
}

@Test
@DisplayName("회원본인이 작성한 게시글 목록을 조회한다.")
void getPostsByWriter() throws JsonProcessingException {
// given
Expand Down Expand Up @@ -802,7 +806,7 @@ void searchPostsWithKeywordForGuest() {
assertThat(result.get(0)).usingRecursiveComparison().isEqualTo(postResponse);
}


@Test
@DisplayName("게시글을 삭제한다.")
void delete() {
// given
Expand Down Expand Up @@ -871,4 +875,29 @@ void update() throws IOException {
.status(HttpStatus.OK);
}

@Test
@DisplayName("인기 게시물 랭킹을 불러온다.")
void getRanking() {
// given
Post post = Post.builder()
.postBody(PostBody.builder().title("제목").build())
.writer(MemberFixtures.MALE_10.get())
.build();
ReflectionTestUtils.setField(post, "id", 1L);

PostRankingResponse postRankingResponse = new PostRankingResponse(1, PostSummaryResponse.from(post));
given(postService.getRanking()).willReturn(List.of(postRankingResponse));

// when, then
List<PostRankingResponse> result = RestAssuredMockMvc.given().log().all()
.when().get("/posts/ranking/popular/guest")
.then().log().all()
.status(HttpStatus.OK)
.extract()
.as(new ParameterizedTypeReference<List<PostRankingResponse>>() {
}.getType());

assertThat(result).isEqualTo(List.of(postRankingResponse));
}

}
Loading