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: 나의 뱃지 목록 조회 api 구현 #250

Merged
merged 9 commits into from
Oct 2, 2024
49 changes: 49 additions & 0 deletions src/main/java/com/dnd/runus/application/badge/BadgeService.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
package com.dnd.runus.application.badge;

import com.dnd.runus.domain.badge.BadgeAchievementRepository;
import com.dnd.runus.domain.badge.BadgeRepository;
import com.dnd.runus.domain.badge.BadgeWithAchieveStatusAndAchievedAt;
import com.dnd.runus.domain.badge.BadgeWithAchievedStatusAndRecentlyStatus;
import com.dnd.runus.global.constant.BadgeType;
import com.dnd.runus.presentation.v1.badge.dto.response.AchievedBadgesResponse;
import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

import static com.dnd.runus.global.constant.TimeConstant.SERVER_TIMEZONE_ID;

@Service
@RequiredArgsConstructor
public class BadgeService {
private final BadgeAchievementRepository badgeAchievementRepository;
private final BadgeRepository badgeRepository;

public AchievedBadgesResponse getAchievedBadges(long memberId) {
return new AchievedBadgesResponse(badgeAchievementRepository.findByMemberIdWithBadge(memberId).stream()
Expand All @@ -19,4 +35,37 @@ public AchievedBadgesResponse getAchievedBadges(long memberId) {
badgeAchievement.createdAt().toLocalDateTime()))
.toList());
}

@Transactional(readOnly = true)
public AllBadgesListResponse getListOfAllBadges(long memberId) {

List<BadgeWithAchieveStatusAndAchievedAt> allBadges =
badgeRepository.findAllBadgesWithAchieveStatusByMemberId(memberId);

Map<BadgeType, List<AllBadgesListResponse.BadgeWithAchievedStatus>> badgeMap = new EnumMap<>(BadgeType.class);
List<AllBadgesListResponse.BadgeWithAchievedStatus> recencyBadges = new ArrayList<>();

OffsetDateTime oneWeekAgo = LocalDate.now(SERVER_TIMEZONE_ID)
.atStartOfDay(SERVER_TIMEZONE_ID)
.toOffsetDateTime()
.minusDays(7);

allBadges.forEach(badge -> {
BadgeWithAchievedStatusAndRecentlyStatus badgeMapWithRecentlyStatus =
BadgeWithAchievedStatusAndRecentlyStatus.from(badge, oneWeekAgo);
badgeMap.computeIfAbsent(badgeMapWithRecentlyStatus.badgeType(), k -> new ArrayList<>())
.add(badgeMapWithRecentlyStatus.badgeWithAchievedStatus());
if (badgeMapWithRecentlyStatus.isRecent()) {
recencyBadges.add(badgeMapWithRecentlyStatus.badgeWithAchievedStatus());
}
});
WonSteps marked this conversation as resolved.
Show resolved Hide resolved

return new AllBadgesListResponse(
recencyBadges,
badgeMap.get(BadgeType.PERSONAL_RECORD),
badgeMap.get(BadgeType.DISTANCE_METER),
badgeMap.get(BadgeType.STREAK),
badgeMap.get(BadgeType.DURATION_SECONDS),
badgeMap.get(BadgeType.LEVEL));
WonSteps marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.dnd.runus.domain.badge;

import com.dnd.runus.global.constant.BadgeType;
import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse;

import java.time.OffsetDateTime;

public record BadgeWithAchievedStatusAndRecentlyStatus(
AllBadgesListResponse.BadgeWithAchievedStatus badgeWithAchievedStatus, BadgeType badgeType, boolean isRecent) {
public static BadgeWithAchievedStatusAndRecentlyStatus from(
BadgeWithAchieveStatusAndAchievedAt badgeWithAchievedStatus, OffsetDateTime criterionDate) {
return new BadgeWithAchievedStatusAndRecentlyStatus(
AllBadgesListResponse.BadgeWithAchievedStatus.of(
badgeWithAchievedStatus.badge(), badgeWithAchievedStatus.isAchieved()),
badgeWithAchievedStatus.badge().type(),
badgeWithAchievedStatus.isAchieved() && criterionDate.isBefore(badgeWithAchievedStatus.achievedAt()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.dnd.runus.application.badge.BadgeService;
import com.dnd.runus.presentation.annotation.MemberId;
import com.dnd.runus.presentation.v1.badge.dto.response.AchievedBadgesResponse;
import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -20,4 +21,18 @@ public class BadgeController {
public AchievedBadgesResponse getMyBadges(@MemberId long memberId) {
return badgeService.getAchievedBadges(memberId);
}

@GetMapping("/me/lists")
@Operation(
summary = "나의 뱃지 조회 목록 조회",
description =
"""
서비스의 전체 뱃지를 조회 합니다.<br>
자신이 획득한 뱃지는 isAchieved가 true로 표시됩니다.<br>
뱃지의 카테고리별 리스트로 뱃지가 리턴됩니다.<br>
최근 일주일에 사용자가 획득한 뱃지는 recencyBadges 리스트에 리턴됩니다.<br>
""")
public AllBadgesListResponse getAllBadgesList(@MemberId long memberId) {
return badgeService.getListOfAllBadges(memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.dnd.runus.presentation.v1.badge.dto.response;

import com.dnd.runus.domain.badge.Badge;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;

public record AllBadgesListResponse(
@Schema(description = "신규 뱃지 목록")
List<BadgeWithAchievedStatus> recencyBadges,
@Schema(description = "개인기록 뱃지 목록")
List<BadgeWithAchievedStatus> personalBadges,
@Schema(description = "러닝 거리 뱃지 목록")
List<BadgeWithAchievedStatus> distanceBadges,
@Schema(description = "연속 뱃지 목록")
List<BadgeWithAchievedStatus> streakBadges,
@Schema(description = "사간 뱃지 목록")
List<BadgeWithAchievedStatus> durationBadges,
@Schema(description = "레벨 뱃지 목록")
List<BadgeWithAchievedStatus> levelBadges
) {
public record BadgeWithAchievedStatus(
@Schema(description = "뱃지 id")
long id,
@Schema(description = "뱃지 이름")
String name,
@Schema(description = "뱃지 이미지 url")
String imageUrl,
@Schema(description = "뱃지 달성 여부")
boolean isAchieved
) {
public static BadgeWithAchievedStatus of(Badge badge, boolean isAchieved) {
return new BadgeWithAchievedStatus(badge.badgeId(), badge.name(), badge.imageUrl(), isAchieved);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@
import com.dnd.runus.domain.badge.Badge;
import com.dnd.runus.domain.badge.BadgeAchievement;
import com.dnd.runus.domain.badge.BadgeAchievementRepository;
import com.dnd.runus.domain.badge.BadgeRepository;
import com.dnd.runus.domain.badge.BadgeWithAchieveStatusAndAchievedAt;
import com.dnd.runus.global.constant.BadgeType;
import com.dnd.runus.presentation.v1.badge.dto.response.AchievedBadgesResponse;
import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse;
import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse.BadgeWithAchievedStatus;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.List;

import static com.dnd.runus.global.constant.TimeConstant.SERVER_TIMEZONE_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.BDDMockito.given;

@ExtendWith(MockitoExtension.class)
Expand All @@ -26,6 +36,9 @@ class BadgeServiceTest {
@Mock
private BadgeAchievementRepository badgeAchievementRepository;

@Mock
private BadgeRepository badgeRepository;

@Test
@DisplayName("자신이 획득한 뱃지 전체 조회에 성공")
void getAchievedBadges() {
Expand Down Expand Up @@ -64,4 +77,78 @@ void getAchievedBadges_Empty() {
List<AchievedBadgesResponse.AchievedBadge> achievedBadges = achievedBadgesResponse.badges();
assertEquals(0, achievedBadges.size());
}

@DisplayName("모든 배지 리스트를 조회 한다.")
@Test
void findAllBadges() {
// given
long memberId = 1L;
OffsetDateTime todayMidnight = LocalDate.now(SERVER_TIMEZONE_ID)
.atStartOfDay(SERVER_TIMEZONE_ID)
.toOffsetDateTime();
given(badgeRepository.findAllBadgesWithAchieveStatusByMemberId(memberId))
.willReturn(List.of(
new BadgeWithAchieveStatusAndAchievedAt(
new Badge(1L, "streak_today_achieved", "description", "imageUrl", BadgeType.STREAK, 0),
true,
todayMidnight),
new BadgeWithAchieveStatusAndAchievedAt(
new Badge(
1L, "streak_6daysAgo_achieved", "description", "imageUrl", BadgeType.STREAK, 0),
true,
todayMidnight.minusDays(6)),
new BadgeWithAchieveStatusAndAchievedAt(
new Badge(
1L, "streak_8daysAgo_achieved", "description", "imageUrl", BadgeType.STREAK, 0),
true,
todayMidnight.minusDays(8)),
new BadgeWithAchieveStatusAndAchievedAt(
new Badge(1L, "streak_NotAchieved", "description", "imageUrl", BadgeType.STREAK, 0),
false,
null),
new BadgeWithAchieveStatusAndAchievedAt(
new Badge(1L, "distance", "description", "imageUrl", BadgeType.DISTANCE_METER, 0),
false,
null),
new BadgeWithAchieveStatusAndAchievedAt(
new Badge(1L, "personal", "description", "imageUrl", BadgeType.PERSONAL_RECORD, 0),
false,
null),
new BadgeWithAchieveStatusAndAchievedAt(
new Badge(1L, "duration", "description", "imageUrl", BadgeType.DURATION_SECONDS, 0),
false,
null)));

// when
AllBadgesListResponse result = badgeService.getListOfAllBadges(memberId);

// then
List<BadgeWithAchievedStatus> recencyBadges = result.recencyBadges();
List<BadgeWithAchievedStatus> streakBadges = result.streakBadges();
List<BadgeWithAchievedStatus> distanceBadges = result.distanceBadges();
List<BadgeWithAchievedStatus> personalBadges = result.personalBadges();
List<BadgeWithAchievedStatus> durationBadges = result.durationBadges();

assertFalse(recencyBadges.isEmpty());
assertFalse(streakBadges.isEmpty());
assertFalse(distanceBadges.isEmpty());
assertFalse(personalBadges.isEmpty());
assertFalse(durationBadges.isEmpty());
assertNull(result.levelBadges());
WonSteps marked this conversation as resolved.
Show resolved Hide resolved

assertThat(recencyBadges.size()).isEqualTo(2);

assertThat(streakBadges.size()).isEqualTo(4);
streakBadges.forEach(v -> {
if (v.name().contains("achieved")) {
assertTrue(v.isAchieved());
} else {
assertFalse(v.isAchieved());
}
});

assertThat(distanceBadges.size()).isEqualTo(1);
assertThat(personalBadges.size()).isEqualTo(1);
assertThat(durationBadges.size()).isEqualTo(1);
}
}