Skip to content

Commit 0808856

Browse files
authored
Merge pull request #130 from SWU-Elixir/feat/22-challenge-CRUD
fix: 챌린지 세부 목표 달성 시 발생한 문제 해결
2 parents c88fc5f + 75089db commit 0808856

6 files changed

Lines changed: 84 additions & 6 deletions

File tree

src/main/java/BE_Elixir/Elixir/domain/auth/service/AuthService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import BE_Elixir.Elixir.domain.auth.dto.AccessTokenDTO;
44
import BE_Elixir.Elixir.domain.auth.dto.response.TokenResponseDTO;
55
import BE_Elixir.Elixir.domain.auth.dto.request.LoginRequestDTO;
6+
import BE_Elixir.Elixir.domain.challenge.service.ChallengeAchievementService;
67
import BE_Elixir.Elixir.domain.member.entity.MemberDetails;
78
import BE_Elixir.Elixir.domain.member.service.MemberDetailsService;
89
import BE_Elixir.Elixir.global.exception.CustomException;
@@ -30,6 +31,7 @@ public class AuthService {
3031
private final JwtProvider jwtProvider;
3132
private final RedisAuthService redisAuthService;
3233
private final MemberDetailsService memberDetailsService;
34+
private final ChallengeAchievementService challengeAchievementService;
3335

3436
// 로그인 (jwt 발급 및 Redis 저장)
3537
public TokenResponseDTO signIn(LoginRequestDTO request) {
@@ -51,6 +53,11 @@ public TokenResponseDTO signIn(LoginRequestDTO request) {
5153
redisAuthService.saveRefreshToken(email, refreshToken);
5254
log.info("Refresh Token Redis에 저장: email={}, token={}", email, refreshToken);
5355

56+
String memberEmail = request.getEmail();
57+
58+
// 자동 참여 메서드 호출
59+
challengeAchievementService.challengeParticipation(memberEmail);
60+
5461
return tokenResponse;
5562
} catch (BadCredentialsException e) {
5663
log.warn("로그인 실패 - 잘못된 비밀번호: {}", request.getEmail());

src/main/java/BE_Elixir/Elixir/domain/challenge/entity/ChallengeAchievementId.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ public class ChallengeAchievementId implements Serializable {
1212
private Long memberId;
1313
private Long challengeId;
1414

15+
public ChallengeAchievementId(Long memberId, Long challengeId) {
16+
this.memberId = memberId;
17+
this.challengeId = challengeId;
18+
}
19+
1520
@Override
1621
public boolean equals(Object o) {
1722
if (this == o) return true;

src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
import java.time.LocalDate;
2323
import java.time.LocalDateTime;
24+
import java.util.HashSet;
2425
import java.util.List;
26+
import java.util.Set;
2527
import java.util.function.Consumer;
2628
import java.util.stream.Collectors;
2729

@@ -119,6 +121,7 @@ else if (event instanceof RecipeEvent) {
119121
handleGoal(goalType, memberId, openedAt, resultSetter);
120122
}
121123

124+
122125
// 목표 달성 결과에 따라 다음 단계 활성화
123126
if (achievement.isStep1Goal1Achieved() && achievement.isStep1Goal2Achieved()) {
124127
achievement.setStep2Goal1Active(true);
@@ -168,10 +171,28 @@ private void handleGoal(ChallengeGoalType goalType, Long memberId, LocalDateTime
168171
case DIET_LUNCH ->
169172
achieved = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.점심, openedAt);
170173
case DIET_THREE_MEALS -> {
171-
boolean hasBreakfast = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.아침, openedAt);
172-
boolean hasLunch = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.점심, openedAt);
173-
boolean hasDinner = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.저녁, openedAt);
174-
achieved = hasBreakfast && hasLunch && hasDinner;
174+
// 특정 날짜 단위로 아침/점심/저녁이 모두 기록된 날이 있는지를 체크
175+
List<LocalDate> datesWithBreakfast = dietLogRepository.findTimesWithDietTypeAfter(memberId, DietLogType.아침, openedAt).stream()
176+
.map(LocalDateTime::toLocalDate)
177+
.distinct()
178+
.toList();
179+
180+
List<LocalDate> datesWithLunch = dietLogRepository.findTimesWithDietTypeAfter(memberId, DietLogType.점심, openedAt).stream()
181+
.map(LocalDateTime::toLocalDate)
182+
.distinct()
183+
.toList();
184+
185+
List<LocalDate> datesWithDinner = dietLogRepository.findTimesWithDietTypeAfter(memberId, DietLogType.저녁, openedAt).stream()
186+
.map(LocalDateTime::toLocalDate)
187+
.distinct()
188+
.toList();
189+
190+
// 교집합으로 하루라도 3끼 다 먹은 날이 있는지 확인
191+
Set<LocalDate> breakfastSet = new HashSet<>(datesWithBreakfast);
192+
breakfastSet.retainAll(datesWithLunch);
193+
breakfastSet.retainAll(datesWithDinner);
194+
195+
achieved = !breakfastSet.isEmpty();
175196
}
176197
case DIET_SEASONAL_ONCE -> {
177198
// 사용자의 식단에 포함된 식재료 목록

src/main/java/BE_Elixir/Elixir/domain/challenge/repository/ChallengeAchievementRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package BE_Elixir.Elixir.domain.challenge.repository;
22

33
import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievement;
4+
import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievementId;
45
import org.springframework.data.jpa.repository.JpaRepository;
56

67
import java.util.List;
78
import java.util.Optional;
89

9-
public interface ChallengeAchievementRepository extends JpaRepository<ChallengeAchievement, Long> {
10+
public interface ChallengeAchievementRepository extends JpaRepository<ChallengeAchievement, ChallengeAchievementId> {
1011
// 챌린지에 대한 사용자의 달성 상태 정보 조회
1112
Optional<ChallengeAchievement> findByChallengeIdAndMemberId(Long challengeId, Long memberId);
1213

src/main/java/BE_Elixir/Elixir/domain/challenge/service/ChallengeAchievementService.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import BE_Elixir.Elixir.domain.challenge.dto.response.ChallengeProgressResponseDTO;
55
import BE_Elixir.Elixir.domain.challenge.entity.Challenge;
66
import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievement;
7+
import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievementId;
78
import BE_Elixir.Elixir.domain.challenge.repository.ChallengeAchievementRepository;
89
import BE_Elixir.Elixir.domain.challenge.repository.ChallengeRepository;
10+
import BE_Elixir.Elixir.domain.member.repository.MemberRepository;
911
import BE_Elixir.Elixir.global.exception.CustomException;
1012
import BE_Elixir.Elixir.global.exception.ErrorCode;
1113
import jakarta.transaction.Transactional;
@@ -22,6 +24,7 @@ public class ChallengeAchievementService {
2224

2325
private final ChallengeRepository challengeRepository;
2426
private final ChallengeAchievementRepository challengeAchievementRepository;
27+
private final MemberRepository memberRepository;
2528

2629
@Transactional
2730
public void save(ChallengeAchievement achievement) {
@@ -120,4 +123,40 @@ public ChallengeProgressResponseDTO getBeforeProgress(Long memberId, Long challe
120123
.orElseGet(() -> ChallengeProgressResponseDTO.empty(challenge));
121124
}
122125

126+
// 챌린지 자동 참여
127+
@Transactional
128+
public void challengeParticipation(String memberEmail) {
129+
LocalDate now = LocalDate.now();
130+
int year = now.getYear();
131+
int month = now.getMonthValue();
132+
133+
// 회원 정보 조회 (email -> memberId)
134+
Long memberId = memberRepository.findByEmail(memberEmail)
135+
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND))
136+
.getId();
137+
138+
Challenge challenge = challengeRepository.findByYearAndMonth(year, month)
139+
.orElseThrow(() -> new CustomException(ErrorCode.CHALLENGE_NOT_FOUND));
140+
141+
ChallengeAchievementId id = new ChallengeAchievementId(memberId, challenge.getId());
142+
143+
boolean exists = challengeAchievementRepository.existsById(id);
144+
145+
if (!exists) {
146+
ChallengeAchievement achievement = new ChallengeAchievement();
147+
achievement.setMemberId(memberId);
148+
achievement.setChallengeId(challenge.getId());
149+
achievement.setStep1Goal1Active(true);
150+
achievement.setStep1Goal2Active(true);
151+
achievement.setStep2Goal1Active(false);
152+
achievement.setStep2Goal2Active(false);
153+
achievement.setStep3Goal1Active(false);
154+
achievement.setStep3Goal2Active(false);
155+
achievement.setStep4Goal1Active(false);
156+
achievement.setStep4Goal2Active(false);
157+
achievement.setOpenedAt(LocalDateTime.now());
158+
159+
challengeAchievementRepository.save(achievement);
160+
}
161+
}
123162
}

src/main/java/BE_Elixir/Elixir/domain/dietLog/repository/DietLogRepository.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.data.repository.query.Param;
88
import org.springframework.stereotype.Repository;
99

10+
import java.time.LocalDate;
1011
import java.time.LocalDateTime;
1112
import java.util.List;
1213

@@ -46,5 +47,9 @@ int countDietLogsInMonth(@Param("memberId") Long memberId,
4647
@Param("startOfMonth") LocalDateTime startOfMonth,
4748
@Param("endOfMonth") LocalDateTime endOfMonth);
4849

49-
50+
// - 특정 시간 이후에 작성된 기록
51+
@Query("SELECT DISTINCT dl.time FROM DietLog dl WHERE dl.member.id = :memberId AND dl.type = :type AND dl.time > :after")
52+
List<LocalDateTime> findTimesWithDietTypeAfter(@Param("memberId") Long memberId,
53+
@Param("type") DietLogType type,
54+
@Param("after") LocalDateTime after);
5055
}

0 commit comments

Comments
 (0)