Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
24ca061
chore: 컨벤션에 위배된 메서드 네임 수정
7zrv Jan 14, 2025
3d3bb57
depend: queryDsl의 SQLExpression 사용을 위한 의존성 추가
7zrv Jan 16, 2025
627720f
chore: 테스트 클래스명 수정
7zrv Jan 16, 2025
9a0702f
feat: 전체, 월별, 주간 봉사 시간 랭킹 쿼리 구현
7zrv Jan 16, 2025
902d44c
feat: 랭킹 계산 유스케이스 구현
7zrv Jan 16, 2025
a754527
feat: 랭킹 캐싱을 위한 redis 레포지토리와 유스케이스 구현
7zrv Jan 16, 2025
92a6ce4
feat: 랭킹 계산, 캐싱을 위한 스케줄러 구현
7zrv Jan 16, 2025
0be813a
feat: 봉사 랭킹 조회 유스케이스 구현
7zrv Jan 16, 2025
e65c87d
feat: 테스트용 DB 설정 추가
7zrv Jan 16, 2025
c2c8e0d
feat: 누락된 테스트 클래스 추가
7zrv Jan 16, 2025
33c7151
feat: test container 추가
7zrv Jan 17, 2025
756d27e
chore: application-test.yml 수정
7zrv Jan 17, 2025
26da3b0
refactor: 랭킹 산출 쿼리 리팩토링
7zrv Jan 17, 2025
366523b
feat: 봉사 랭킹 엔드 포인트 구현
7zrv Jan 17, 2025
4abb947
feat: 봉사 시간 랭킹 스케줄러 로깅 추가
7zrv Jan 17, 2025
482ddfa
refactor: 봉사 시간 랭킹 반환 예외 메세지 수정
7zrv Jan 17, 2025
b753096
feat: 봉사자 닉네임 리스트 반환 쿼리 메서드 생성
7zrv Jan 18, 2025
b627ba1
feat: 봉사자 닉네임 리스트 반환 유스케이스 구현
7zrv Jan 18, 2025
d32c9e0
chore: 봉사자 닉네임 반환 관련 클래스의 명시적인 이름 수정
7zrv Jan 18, 2025
81a1e1c
feat: 봉사 시간 랭킹 Dto nickname 필드 추가
7zrv Jan 18, 2025
98fa3af
refactor: 봉사자 닉네임 반환 유스케이스 리팩토링
7zrv Jan 18, 2025
e2ec50d
refactor: 봉사자 닉네임 반환 유스케이스 리팩토링
7zrv Jan 18, 2025
529ec4f
refactor: 봉사 랭킹의 닉네임 필드 추가에 따른 테스트 수정
7zrv Jan 18, 2025
eeaf438
refactor: 봉사 랭킹 객체 맵퍼 리팩토링
7zrv Jan 18, 2025
5587c96
refactor: 봉사 시간 랭킹 유스케이스 리팩토링
7zrv Jan 18, 2025
c27cfbc
chore: 불필요한 클래스 삭제및 build.gradle 개행 수정
7zrv Jan 19, 2025
9f4953d
feat: test container 설정 추가
7zrv Jan 19, 2025
b6d7ac5
fix: 에러 발생 test 수정
7zrv Jan 19, 2025
78e3cf1
chore: cicd workflow 수정
7zrv Jan 19, 2025
c91552e
fix: ci 파이프 라인 수정
7zrv Jan 19, 2025
f2ff999
fix: ci 파이프 라인 수정
7zrv Jan 19, 2025
cb62123
fix: ci 파이프 라인 수정
7zrv Jan 19, 2025
450d909
fix: 봉사시간 계산 테스트 날짜 수정
7zrv Jan 19, 2025
0914343
fix: ci 테스트 레포트 분석을 위한 설정 추가
7zrv Jan 19, 2025
03126bd
fix: 동시성 테스트 스레드 획득 에러 수정
7zrv Jan 19, 2025
e625296
fix: cicd 빌드 로그 옵션 제거
7zrv Jan 19, 2025
e6e3077
fix: 에러 발생 테스트 주석처리
7zrv Jan 19, 2025
9a9331c
chore: 불필요한 구문 제거및 개행 추가
7zrv Jan 19, 2025
4557b6d
chore: 코드 리뷰사항 반영
7zrv Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.somemore.domains.volunteerrecord.dto.response;

import java.util.UUID;

public record VolunteerMonthlyRankingResponseDto(
UUID volunteerId,
int totalHours,
long ranking
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.somemore.domains.volunteerrecord.dto.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.somemore.domains.center.dto.response.PreferItemResponseDto;
import lombok.Builder;

import java.util.List;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Builder
public record VolunteerRankingResponseDto(
List<VolunteerTotalRankingResponseDto> volunteerTotalRankingResponse,
List<VolunteerMonthlyRankingResponseDto> volunteerMonthlyResponse,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얘만 Ranking이 빠진 거 같은데 혹시 다른 의도가 있으신건가용?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아닙니다! 그냥... 바빴나봐요 찾아주셔서 감사합니다 ㅎㅎ

List<VolunteerWeeklyRankingResponseDto> volunteerWeeklyRankingResponse
) {
public static VolunteerRankingResponseDto of(
List<VolunteerTotalRankingResponseDto> totalRanking,
List<VolunteerMonthlyRankingResponseDto> monthlyRanking,
List<VolunteerWeeklyRankingResponseDto> weeklyRanking){

return VolunteerRankingResponseDto.builder()
.volunteerTotalRankingResponse(totalRanking)
.volunteerMonthlyResponse(monthlyRanking)
.volunteerWeeklyRankingResponse(weeklyRanking)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.somemore.domains.volunteerrecord.dto.response;

import java.util.UUID;

public record VolunteerTotalRankingResponseDto(
UUID volunteerId,
int totalHours,
long ranking
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.somemore.domains.volunteerrecord.dto.response;

import java.util.UUID;

public record VolunteerWeeklyRankingResponseDto(
UUID volunteerId,
int totalHours,
long ranking
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.somemore.domains.volunteerrecord.repository.mapper;

import com.somemore.domains.volunteerrecord.dto.response.VolunteerMonthlyRankingResponseDto;
import com.somemore.domains.volunteerrecord.dto.response.VolunteerTotalRankingResponseDto;
import com.somemore.domains.volunteerrecord.dto.response.VolunteerWeeklyRankingResponseDto;

import java.util.UUID;

public class VolunteerRankingMapper {

private VolunteerRankingMapper() {
throw new UnsupportedOperationException("유틸리티 클래스는 인스턴스화할 수 없습니다.");
}

public static VolunteerTotalRankingResponseDto toTotalRankingResponse(Object[] result) {
return new VolunteerTotalRankingResponseDto(
(UUID) result[0],
((Number) result[1]).intValue(),
((Number) result[2]).longValue()
);
}

public static VolunteerWeeklyRankingResponseDto toWeeklyRankingResponse(Object[] result) {
return new VolunteerWeeklyRankingResponseDto(
(UUID) result[0],
((Number) result[1]).intValue(),
((Number) result[2]).longValue()
);
}

public static VolunteerMonthlyRankingResponseDto toMonthlyRankingResponse(Object[] result) {
return new VolunteerMonthlyRankingResponseDto(
(UUID) result[0],
((Number) result[1]).intValue(),
((Number) result[2]).longValue()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.somemore.domains.volunteerrecord.service;

import com.somemore.domains.volunteerrecord.dto.response.VolunteerMonthlyRankingResponseDto;
import com.somemore.domains.volunteerrecord.dto.response.VolunteerRankingResponseDto;
import com.somemore.domains.volunteerrecord.dto.response.VolunteerTotalRankingResponseDto;
import com.somemore.domains.volunteerrecord.dto.response.VolunteerWeeklyRankingResponseDto;
import com.somemore.domains.volunteerrecord.repository.VolunteerRecordRepository;
import com.somemore.domains.volunteerrecord.repository.mapper.VolunteerRankingMapper;
import com.somemore.domains.volunteerrecord.usecase.CalculateRankingUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class CalculateRankingService implements CalculateRankingUseCase {

private final VolunteerRecordRepository volunteerRecordRepository;

@Override
public VolunteerRankingResponseDto calculateRanking() {

List<Object[]> volunteerTotalRanking = volunteerRecordRepository.findTotalTopRankingWithTies();
List<Object[]> volunteerMonthlyRanking = volunteerRecordRepository.findMonthlyTopRankingWithTies();
List<Object[]> volunteerWeeklyRanking = volunteerRecordRepository.findWeeklyTopRankingWithTies();

List<VolunteerTotalRankingResponseDto> totalRankingDtos = volunteerTotalRanking.stream()
.map(VolunteerRankingMapper::toTotalRankingResponse)
.toList();

List<VolunteerMonthlyRankingResponseDto> monthlyRankingDtos = volunteerMonthlyRanking.stream()
.map(VolunteerRankingMapper::toMonthlyRankingResponse)
.toList();

List<VolunteerWeeklyRankingResponseDto> weeklyRankingDtos = volunteerWeeklyRanking.stream()
.map(VolunteerRankingMapper::toWeeklyRankingResponse)
.toList();

return VolunteerRankingResponseDto.of(
totalRankingDtos,
monthlyRankingDtos,
weeklyRankingDtos
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.somemore.domains.volunteerrecord.usecase;

import com.somemore.domains.volunteerrecord.dto.response.VolunteerRankingResponseDto;

public interface CalculateRankingUseCase {
VolunteerRankingResponseDto calculateRanking();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.somemore.domains.volunteerrecord.service;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
import com.somemore.domains.volunteerrecord.dto.response.VolunteerRankingResponseDto;
import com.somemore.domains.volunteerrecord.repository.VolunteerRecordRepository;
import com.somemore.support.IntegrationTestSupport;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

@Transactional
class CalculateRankingServiceTest extends IntegrationTestSupport {

@Autowired
private CalculateRankingService calculateRankingService;

@Autowired
private VolunteerRecordRepository volunteerRecordRepository;

@DisplayName("전체 기간 봉사 시간 합계로 4위까지의 랭킹을 반환할 수 있다. 동점자를 같이 반환한다.")
@Test
void calculateTotalVolunteerRanking() {

// given
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "봉사1", LocalDate.of(2025, 1, 10), 100));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "봉사2", LocalDate.of(2025, 1, 11), 100));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "봉사3", LocalDate.of(2025, 1, 12), 90));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "봉사4", LocalDate.of(2025, 1, 13), 60));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "봉사5", LocalDate.of(2025, 1, 14), 50));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "봉사6", LocalDate.of(2025, 1, 15), 30));

// when
VolunteerRankingResponseDto ranking = calculateRankingService.calculateRanking();

// then
assertThat(ranking.volunteerTotalRankingResponse()).hasSize(5);

assertThat(ranking.volunteerTotalRankingResponse().get(0).totalHours()).isEqualTo(100L);
assertThat(ranking.volunteerTotalRankingResponse().get(0).ranking()).isEqualTo(1);

assertThat(ranking.volunteerTotalRankingResponse().get(1).totalHours()).isEqualTo(100L);
assertThat(ranking.volunteerTotalRankingResponse().get(1).ranking()).isEqualTo(1);

assertThat(ranking.volunteerTotalRankingResponse().get(3).totalHours()).isEqualTo(60L);
assertThat(ranking.volunteerTotalRankingResponse().get(3).ranking()).isEqualTo(3);
}

@DisplayName("주간 봉사 시간으로 4위까지의 랭킹을 반환할 수 있다. 동점자를 같이 반환한다.")
@Test
void calculateWeeklyVolunteerRanking() {

// given
LocalDate currentDate = LocalDate.now();

// 이번 주 데이터
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번주봉사1", currentDate, 100));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번주봉사2", currentDate, 100));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번주봉사3", currentDate, 90));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번주봉사4", currentDate, 60));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번주봉사5", currentDate, 50));

// 지난 주 데이터
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "지난주봉사", currentDate.minusWeeks(1), 200));

// when
VolunteerRankingResponseDto ranking = calculateRankingService.calculateRanking();

// then
assertThat(ranking.volunteerWeeklyRankingResponse()).hasSize(5);

assertThat(ranking.volunteerWeeklyRankingResponse().get(0).totalHours()).isEqualTo(100L);
assertThat(ranking.volunteerWeeklyRankingResponse().get(0).ranking()).isEqualTo(1);

assertThat(ranking.volunteerWeeklyRankingResponse().get(1).totalHours()).isEqualTo(100L);
assertThat(ranking.volunteerWeeklyRankingResponse().get(1).ranking()).isEqualTo(1);

assertThat(ranking.volunteerWeeklyRankingResponse().get(3).totalHours()).isEqualTo(60L);
assertThat(ranking.volunteerWeeklyRankingResponse().get(3).ranking()).isEqualTo(3);
}

@DisplayName("월간 봉사 시간으로 4위까지의 랭킹을 반환할 수 있다. 동점자를 같이 반환한다.")
@Test
void calculateMonthlyVolunteerRanking() {

// given
LocalDate currentDate = LocalDate.now();

// 이번 달 데이터
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번달봉사1", currentDate, 100));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번달봉사2", currentDate, 100));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번달봉사3", currentDate, 90));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번달봉사4", currentDate, 60));
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "이번달봉사5", currentDate, 50));

// 지난 달 데이터
volunteerRecordRepository.save(VolunteerRecord.create(UUID.randomUUID(), "지난달봉사", currentDate.minusMonths(1), 200));

// when
VolunteerRankingResponseDto ranking = calculateRankingService.calculateRanking();

// then
assertThat(ranking.volunteerMonthlyResponse()).hasSize(5);

assertThat(ranking.volunteerMonthlyResponse().get(0).totalHours()).isEqualTo(100L);
assertThat(ranking.volunteerMonthlyResponse().get(0).ranking()).isEqualTo(1);

assertThat(ranking.volunteerMonthlyResponse().get(1).totalHours()).isEqualTo(100L);
assertThat(ranking.volunteerMonthlyResponse().get(1).ranking()).isEqualTo(1);

assertThat(ranking.volunteerMonthlyResponse().get(3).totalHours()).isEqualTo(60L);
assertThat(ranking.volunteerMonthlyResponse().get(3).ranking()).isEqualTo(3);
}
}