Skip to content

Commit

Permalink
내 랭킹정보 조회 기능 (#522)
Browse files Browse the repository at this point in the history
* feat: (#515) 모든 멤버의 게시글 작성 개수 불러오기 메서드 추가

* feat: (#515) 모든 멤버의 투표 수 불러오기 메서드 추가

* feat: (#515) 랭킹 순위 구하는 클래스 구현

* feat: (#515) 내 랭킹 정보 조회 API 구현

* style: (#515) 주석 수정

* feat: (#515) swagger 추가

* style: (#515) final 키워드 추가

* refactor: (#515) 응답 변수 추출

* refactor: (#515) 메서드 이름 수정 및 순서 변경

* refactor: (#515) 클래스와 메서드 이름 수정 및 상수 추출

* refactor: (#515) 테스트 응답 검증 추가 및 변수 이름 수정

* refactor: (#515) 트랜잭션 어노테이션 추가

* refactor: (#515) score 반환 long으로 변환

* refactor: (#515) getter 메서드 명 수정

* test: (#515) assertAll 로 수정

* refactor: (#515) ranking 도메인 패키지로 변경

* refactor: (#515) 클래스 이름 및 메서드 이름 수정

* refactor: (#515) 클래스,메서드,변수명 수정

* refactor: (#515) 패키지 수정

* refactor: (#515) swagger 어노테이션 명세 수정

* refactor: (#515) uri 수정

* fix: (#515) uri 테스트 수정
  • Loading branch information
aiaiaiai1 authored and tjdtls690 committed Sep 12, 2023
1 parent 252972b commit 6e8744a
Show file tree
Hide file tree
Showing 16 changed files with 490 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ ResponseEntity<Void> updateDetails(
final Member member
);

@Operation(summary = "회원 탈퇴", description = "회원 ㄷ탈퇴한다.")
@Operation(summary = "회원 탈퇴", description = "회원 탈퇴한다.")
@ApiResponse(responseCode = "200", description = "회원 탈퇴 성공")
ResponseEntity<Void> deleteMember(final Member member);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,4 @@ private void deleteReportByMember(final Member member) {
.toList();
reportRepository.deleteAllById(reportIdsByMember);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ public interface PostRepository extends JpaRepository<Post, Long>, PostCustomRep

List<Post> findAllByWriter(final Member member);

@Query("SELECT COUNT(p)" +
"FROM Member m " +
"LEFT JOIN Post p ON m.id = p.writer.id AND p.writer IN :members " +
"WHERE m IN :members " +
"GROUP BY m.id")
List<Integer> findCountsByMembers(@Param("members") final List<Member> members);

@Query("SELECT v.postOption.post FROM Vote v WHERE v.member = :member "
+ "AND v.postOption.post.deadline < CURRENT_TIMESTAMP")
Slice<Post> findClosedPostsVotedByMember(@Param("member") final Member member, final Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.votogether.domain.ranking.controller;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.ranking.dto.response.RankingResponse;
import com.votogether.domain.ranking.service.RankingService;
import com.votogether.global.jwt.Auth;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class RankingController implements RankingControllerDocs {

private final RankingService rankingService;

@GetMapping("/members/me/ranking/passion")
public ResponseEntity<RankingResponse> getRanking(@Auth final Member member) {
final RankingResponse response = rankingService.getPassionRanking(member);
return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.votogether.domain.ranking.controller;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.ranking.dto.response.RankingResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;

@Tag(name = "랭킹", description = "랭킹 API")
public interface RankingControllerDocs {

@Operation(summary = "나의 열정 랭킹 조회", description = "나의 열정 랭킹 정보를 조회한다.")
@ApiResponse(responseCode = "200", description = "나의 열정 랭킹 정보 조회 성공")
ResponseEntity<RankingResponse> getRanking(final Member member);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.votogether.domain.ranking.dto.response;

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

@Schema(description = "랭킹 정보 응답")
public record RankingResponse(
@Schema(description = "랭킹", example = "2")
int ranking,

@Schema(description = "닉네임", example = "유저")
String nickname,

@Schema(description = "게시글 수", example = "5")
int postCount,

@Schema(description = "투표 수", example = "6")
int voteCount,

@Schema(description = "점수", example = "31")
long score
) {
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.votogether.domain.ranking.entity;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.ranking.entity.vo.PassionRecord;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PassionRankings {

private final Map<Member, Integer> rankings = new HashMap<>();
private final Map<Member, PassionRecord> passionBoard;

public PassionRankings(final Map<Member, PassionRecord> passionBoard) {
this.passionBoard = passionBoard;
calculateRanking();
}

private void calculateRanking() {
final List<Member> members = passionBoard.entrySet().stream()
.sorted(Comparator.comparingLong(entry -> -entry.getValue().calculateScore()))
.map(s -> s.getKey())
.toList();

int currentRanking = 1;
int previousRanking = -1;
long previousScore = -1;
for (Member member : members) {
long currentScore = passionBoard.get(member).calculateScore();
int ranking = (currentScore == previousScore) ? previousRanking : currentRanking;

this.rankings.put(member, ranking);

previousRanking = ranking;
previousScore = currentScore;
currentRanking++;
}
}

public long getScore(Member member) {
return passionBoard.get(member).calculateScore();
}

public int getRanking(Member member) {
return rankings.get(member);
}

public PassionRecord getActivityRecord(Member member) {
return passionBoard.get(member);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.votogether.domain.ranking.entity.vo;

import lombok.Getter;

@Getter
public class PassionRecord {

private static final int POST_WEIGHT = 5;
private static final int VOTE_WEIGHT = 1;

private final int postCount;
private final int voteCount;

public PassionRecord(int postCount, int voteCount) {
this.postCount = postCount;
this.voteCount = voteCount;
}

public long calculateScore() {
return postCount * POST_WEIGHT + voteCount * VOTE_WEIGHT;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.votogether.domain.ranking.service;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.repository.MemberRepository;
import com.votogether.domain.post.repository.PostRepository;
import com.votogether.domain.ranking.dto.response.RankingResponse;
import com.votogether.domain.ranking.entity.PassionRankings;
import com.votogether.domain.ranking.entity.vo.PassionRecord;
import com.votogether.domain.vote.repository.VoteRepository;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class RankingService {

private final MemberRepository memberRepository;
private final PostRepository postRepository;
private final VoteRepository voteRepository;

@Transactional(readOnly = true)
public RankingResponse getPassionRanking(final Member member) {
final PassionRankings passionRankings = getPassionRankings();
return new RankingResponse(
passionRankings.getRanking(member),
member.getNickname(),
passionRankings.getActivityRecord(member).getPostCount(),
passionRankings.getActivityRecord(member).getVoteCount(),
passionRankings.getScore(member)
);
}

private PassionRankings getPassionRankings() {
final List<Member> members = memberRepository.findAll();
final List<Integer> postCounts = postRepository.findCountsByMembers(members);
final List<Integer> voteCounts = voteRepository.findCountsByMembers(members);

final Map<Member, PassionRecord> passionBoard = new HashMap<>();

for (int i = 0; i < members.size(); i++) {
passionBoard.put(members.get(i), new PassionRecord(postCounts.get(i), voteCounts.get(i)));
}

return new PassionRankings(passionBoard);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ List<VoteStatus> findVoteCountByPostOptionIdGroupByAgeRangeAndGender(

List<Vote> findAllByMember(final Member member);

@Query("SELECT COUNT(v)" +
"FROM Member m " +
"LEFT JOIN Vote v ON m.id = v.member.id AND v.member IN :members " +
"WHERE m IN :members " +
"GROUP BY m.id")
List<Integer> findCountsByMembers(@Param("members") final List<Member> members);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.votogether.domain.member.entity;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

import com.votogether.domain.ranking.entity.PassionRankings;
import com.votogether.domain.ranking.entity.vo.PassionRecord;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;

class PassionRankingsTest {

@Nested
@DisplayName("랭킹")
class Ranking {

@Test
@DisplayName("랭킹 순위와 점수를 계산한다.")
void calculateRank() {
//given
Member memberA = Member.builder().nickname("00").build();
Member memberB = Member.builder().nickname("11").build();
Member memberC = Member.builder().nickname("22").build();
ReflectionTestUtils.setField(memberA, "id", 1L);
ReflectionTestUtils.setField(memberB, "id", 2L);
ReflectionTestUtils.setField(memberC, "id", 3L);

Map<Member, PassionRecord> board = new HashMap<>();
board.put(memberA, new PassionRecord(1, 1));
board.put(memberB, new PassionRecord(1, 2));
board.put(memberC, new PassionRecord(1, 3));

PassionRankings passionRankings = new PassionRankings(board);

//when, then
assertAll(
() -> assertThat(passionRankings.getScore(memberA)).isEqualTo(6),
() -> assertThat(passionRankings.getScore(memberB)).isEqualTo(7),
() -> assertThat(passionRankings.getScore(memberC)).isEqualTo(8),
() -> assertThat(passionRankings.getRanking(memberA)).isEqualTo(3),
() -> assertThat(passionRankings.getRanking(memberB)).isEqualTo(2),
() -> assertThat(passionRankings.getRanking(memberC)).isEqualTo(1)
);
}

@Test
@DisplayName("동순위가 존재하는 랭킹 순위와 점수를 계산한다.")
void calculateRank1() {
//given
Member memberA = Member.builder().nickname("00").build();
Member memberB = Member.builder().nickname("11").build();
Member memberC = Member.builder().nickname("22").build();
Member memberD = Member.builder().nickname("33").build();
ReflectionTestUtils.setField(memberA, "id", 1L);
ReflectionTestUtils.setField(memberB, "id", 2L);
ReflectionTestUtils.setField(memberC, "id", 3L);
ReflectionTestUtils.setField(memberD, "id", 4L);

Map<Member, PassionRecord> board = new HashMap<>();
board.put(memberA, new PassionRecord(1, 1));
board.put(memberB, new PassionRecord(1, 3));
board.put(memberC, new PassionRecord(1, 3));
board.put(memberD, new PassionRecord(1, 3));

PassionRankings passionRankings = new PassionRankings(board);

//when, then
assertAll(
() -> assertThat(passionRankings.getScore(memberA)).isEqualTo(6),
() -> assertThat(passionRankings.getScore(memberB)).isEqualTo(8),
() -> assertThat(passionRankings.getScore(memberC)).isEqualTo(8),
() -> assertThat(passionRankings.getRanking(memberA)).isEqualTo(4),
() -> assertThat(passionRankings.getRanking(memberB)).isEqualTo(1),
() -> assertThat(passionRankings.getRanking(memberC)).isEqualTo(1),
() -> assertThat(passionRankings.getRanking(memberD)).isEqualTo(1)
);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import com.votogether.test.annotation.ServiceTest;
import com.votogether.test.fixtures.MemberFixtures;
import com.votogether.test.persister.MemberTestPersister;
import com.votogether.test.persister.PostTestPersister;
import com.votogether.test.persister.VoteTestPersister;
import jakarta.persistence.EntityManager;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
Expand Down Expand Up @@ -63,6 +65,12 @@ class MemberServiceTest {
@Autowired
MemberTestPersister memberTestPersister;

@Autowired
PostTestPersister postTestPersister;

@Autowired
VoteTestPersister voteTestPersister;

@Autowired
EntityManager em;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -839,4 +839,28 @@ void findPostsByHot() {

}

@Test
@DisplayName("모든 유저의 작성한 게시글 수를 가져온다.")
void findCountsByMembers() {
// given
Member member = memberTestPersister.builder().save();
Member member1 = memberTestPersister.builder().save();
Member member2 = memberTestPersister.builder().save();

postTestPersister.builder().writer(member).save();
postTestPersister.builder().writer(member1).save();
postTestPersister.builder().writer(member1).save();

// when
List<Integer> postCounts = postRepository.findCountsByMembers(List.of(member, member1, member2));

// then
assertAll(
() -> assertThat(postCounts).hasSize(3),
() -> assertThat(postCounts.get(0)).isEqualTo(1),
() -> assertThat(postCounts.get(1)).isEqualTo(2),
() -> assertThat(postCounts.get(2)).isEqualTo(0)
);
}

}
Loading

0 comments on commit 6e8744a

Please sign in to comment.