Skip to content

Commit

Permalink
Merge branch 'dev' into feat/#541
Browse files Browse the repository at this point in the history
# Conflicts:
#	backend/src/main/java/com/votogether/domain/post/controller/PostGuestController.java
#	backend/src/main/java/com/votogether/domain/post/controller/PostGuestControllerDocs.java
#	backend/src/main/java/com/votogether/domain/post/dto/request/post/PostOptionCreateRequest.java
#	backend/src/main/java/com/votogether/domain/post/dto/response/post/PostOptionVoteResultResponse.java
#	backend/src/main/java/com/votogether/domain/post/entity/Post.java
#	backend/src/main/java/com/votogether/domain/post/service/PostCommandService.java
#	backend/src/main/java/com/votogether/domain/post/service/PostGuestService.java
#	backend/src/main/java/com/votogether/infra/image/ImageName.java
#	backend/src/main/java/com/votogether/infra/image/LocalUploader.java
#	backend/src/test/java/com/votogether/domain/post/controller/PostGuestControllerTest.java
#	backend/src/test/java/com/votogether/domain/post/service/PostGuestServiceTest.java
#	backend/src/test/java/com/votogether/domain/post/service/PostQueryServiceTest.java
#	backend/src/test/java/com/votogether/infra/image/LocalUploaderTest.java
#	frontend/src/components/PostForm/index.tsx
#	frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/style.ts
  • Loading branch information
woo-chang committed Sep 19, 2023
2 parents 105c4a3 + 3226715 commit dd3a9eb
Show file tree
Hide file tree
Showing 23 changed files with 334 additions and 79 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.votogether.domain.post.controller;

import com.votogether.domain.post.dto.response.post.PostRankingResponse;
import com.votogether.domain.post.dto.response.post.PostResponse;
import com.votogether.domain.post.entity.vo.PostClosingType;
import com.votogether.domain.post.entity.vo.PostSortType;
Expand Down Expand Up @@ -54,4 +55,10 @@ public ResponseEntity<List<PostResponse>> searchPosts(
return ResponseEntity.ok(responses);
}

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

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.votogether.domain.post.controller;

import com.votogether.domain.post.dto.response.post.PostRankingResponse;
import com.votogether.domain.post.dto.response.post.PostResponse;
import com.votogether.domain.post.entity.vo.PostClosingType;
import com.votogether.domain.post.entity.vo.PostSortType;
Expand Down Expand Up @@ -85,4 +86,8 @@ ResponseEntity<List<PostResponse>> searchPosts(
@Parameter(description = "게시글 정렬 기준", example = "HOT") final PostSortType postSortType
);

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ public class PostOptionCreateRequest {
private String content;

@Schema(description = "게시글 옵션 이미지 파일", example = "votogether.png")
private MultipartFile optionImage;
private MultipartFile imageFile;

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ private static double calculateVotePercent(final long totalCount, final long vot
if (voteCount == HIDDEN_COUNT || totalCount == 0) {
return 0.0;
}
return ((double) voteCount / totalCount);
final double votePercent = ((double) voteCount / totalCount) * 100;
return Math.round(votePercent * 10) / 10.0;
}

public static PostOptionVoteResultResponse ofGuest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,10 @@ public PostContentImage getFirstContentImage() {
return postContentImages.get(0);
}

public long getTotalVoteCount() {
return postOptions.stream()
.mapToLong(PostOption::getVoteCount)
.sum();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private void addPostOptions(final Post post, final List<PostOptionCreateRequest>
}

private PostOption createPostOptionFromRequest(int sequence, final PostOptionCreateRequest postOptionCreate) {
final String imageUrl = imageUploader.upload(postOptionCreate.getOptionImage());
final String imageUrl = imageUploader.upload(postOptionCreate.getImageFile());
return PostOption.builder()
.sequence(sequence)
.content(postOptionCreate.getContent())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.votogether.domain.post.service;

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.entity.Post;
import com.votogether.domain.post.entity.vo.PostClosingType;
import com.votogether.domain.post.entity.vo.PostSortType;
Expand All @@ -9,7 +11,9 @@
import com.votogether.domain.post.repository.PostRepository;
import com.votogether.global.exception.BadRequestException;
import com.votogether.global.exception.NotFoundException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -84,4 +88,45 @@ private List<PostResponse> convertToResponses(final List<Post> posts) {
.toList();
}

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

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 @@ -12,7 +12,7 @@ public class ImageName {

private static final String DOT = ".";
private static final String UNDER_BAR = "_";
private static final Set<String> IMAGE_EXTENSIONS = Set.of("jpeg", "jpg", "png", "webp");
private static final Set<String> IMAGE_EXTENSIONS = Set.of("jpeg", "jpg", "png", "webp", "heic", "heif");

public static String from(final String originalFilename) {
final String extension = StringUtils.getFilenameExtension(originalFilename);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.votogether.infra.image;

import com.votogether.global.exception.ImageException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -29,8 +26,7 @@ public LocalUploader(

public String upload(final MultipartFile image) {
final File directory = loadDirectory(getImageStorePath());

if (isEmptyFileOrNotImage(image)) {
if (isEmptyImage(image)) {
return null;
}
final String saveFileName = ImageName.from(image.getOriginalFilename());
Expand All @@ -51,16 +47,8 @@ private File loadDirectory(final String directoryLocation) {
return directory;
}

private boolean isEmptyFileOrNotImage(final MultipartFile multipartFile) {
return multipartFile == null || multipartFile.isEmpty() || isNotImageFile(multipartFile);
}

private boolean isNotImageFile(final MultipartFile file) {
try (InputStream originalInputStream = new BufferedInputStream(file.getInputStream())) {
return ImageIO.read(originalInputStream) == null;
} catch (IOException e) {
throw new ImageException(ImageExceptionType.INVALID_IMAGE_READ);
}
private boolean isEmptyImage(final MultipartFile multipartFile) {
return multipartFile == null || multipartFile.isEmpty();
}

private void transferFile(final MultipartFile file, final File uploadPath) {
Expand All @@ -86,7 +74,6 @@ public void delete(final String path) {

private String getImageLocalPath(final String fullPath) {
final int urlIndex = fullPath.lastIndexOf(url);

if (urlIndex == -1) {
throw new ImageException(ImageExceptionType.IMAGE_URL);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@

import com.votogether.domain.post.dto.response.post.CategoryResponse;
import com.votogether.domain.post.dto.response.post.PostOptionVoteResultResponse;
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.PostVoteResultResponse;
import com.votogether.domain.post.dto.response.post.PostWriterResponse;
import com.votogether.domain.post.entity.Post;
import com.votogether.domain.post.entity.vo.PostClosingType;
import com.votogether.domain.post.entity.vo.PostSortType;
import com.votogether.domain.post.service.PostGuestService;
import com.votogether.global.exception.GlobalExceptionHandler;
import com.votogether.test.ControllerTest;
import com.votogether.test.fixtures.MemberFixtures;
import io.restassured.common.mapper.TypeRef;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import java.time.LocalDateTime;
Expand All @@ -33,6 +37,7 @@
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
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 @@ -187,6 +192,32 @@ void return400pageIsNegative() {

}

@Test
@DisplayName("인기 게시물 랭킹을 불러온다.")
void getRanking() {
// given
Post post = Post.builder()
.title("제목")
.content("내용")
.writer(MemberFixtures.MALE_10.get())
.deadline(LocalDateTime.now().plusDays(3))
.build();
ReflectionTestUtils.setField(post, "id", 1L);
PostRankingResponse postRankingResponse = new PostRankingResponse(1, PostSummaryResponse.from(post));
given(postGuestService.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 TypeRef<List<PostRankingResponse>>() {
}.getType());

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

private PostResponse mockingPostResponse() {
return new PostResponse(
1L,
Expand Down
Loading

0 comments on commit dd3a9eb

Please sign in to comment.