From 42fa0d9e6bb620b71f129b5d6bbd329cb82c71a4 Mon Sep 17 00:00:00 2001 From: Choi MinJung Date: Tue, 17 Jun 2025 17:40:15 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=B0=B8=EC=97=AC=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0(=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EC=9E=90=EB=8F=99=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?in=20AuthService)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthService.java | 7 ++++ .../entity/ChallengeAchievementId.java | 5 +++ .../listener/ChallengeEventListener.java | 3 ++ .../ChallengeAchievementRepository.java | 3 +- .../service/ChallengeAchievementService.java | 39 +++++++++++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/main/java/BE_Elixir/Elixir/domain/auth/service/AuthService.java b/src/main/java/BE_Elixir/Elixir/domain/auth/service/AuthService.java index dab1281..296a104 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/auth/service/AuthService.java +++ b/src/main/java/BE_Elixir/Elixir/domain/auth/service/AuthService.java @@ -3,6 +3,7 @@ import BE_Elixir.Elixir.domain.auth.dto.AccessTokenDTO; import BE_Elixir.Elixir.domain.auth.dto.response.TokenResponseDTO; import BE_Elixir.Elixir.domain.auth.dto.request.LoginRequestDTO; +import BE_Elixir.Elixir.domain.challenge.service.ChallengeAchievementService; import BE_Elixir.Elixir.domain.member.entity.MemberDetails; import BE_Elixir.Elixir.domain.member.service.MemberDetailsService; import BE_Elixir.Elixir.global.exception.CustomException; @@ -30,6 +31,7 @@ public class AuthService { private final JwtProvider jwtProvider; private final RedisAuthService redisAuthService; private final MemberDetailsService memberDetailsService; + private final ChallengeAchievementService challengeAchievementService; // 로그인 (jwt 발급 및 Redis 저장) public TokenResponseDTO signIn(LoginRequestDTO request) { @@ -51,6 +53,11 @@ public TokenResponseDTO signIn(LoginRequestDTO request) { redisAuthService.saveRefreshToken(email, refreshToken); log.info("Refresh Token Redis에 저장: email={}, token={}", email, refreshToken); + String memberEmail = request.getEmail(); + + // 자동 참여 메서드 호출 + challengeAchievementService.challengeParticipation(memberEmail); + return tokenResponse; } catch (BadCredentialsException e) { log.warn("로그인 실패 - 잘못된 비밀번호: {}", request.getEmail()); diff --git a/src/main/java/BE_Elixir/Elixir/domain/challenge/entity/ChallengeAchievementId.java b/src/main/java/BE_Elixir/Elixir/domain/challenge/entity/ChallengeAchievementId.java index 83788f0..7754a1d 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/challenge/entity/ChallengeAchievementId.java +++ b/src/main/java/BE_Elixir/Elixir/domain/challenge/entity/ChallengeAchievementId.java @@ -12,6 +12,11 @@ public class ChallengeAchievementId implements Serializable { private Long memberId; private Long challengeId; + public ChallengeAchievementId(Long memberId, Long challengeId) { + this.memberId = memberId; + this.challengeId = challengeId; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java b/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java index 759cbb3..69805ff 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java +++ b/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java @@ -123,14 +123,17 @@ else if (event instanceof RecipeEvent) { if (achievement.isStep1Goal1Achieved() && achievement.isStep1Goal2Achieved()) { achievement.setStep2Goal1Active(true); achievement.setStep2Goal2Active(true); + achievement.setOpenedAt(LocalDateTime.now()); } if (achievement.isStep2Goal1Achieved() && achievement.isStep2Goal2Achieved()) { achievement.setStep3Goal1Active(true); achievement.setStep3Goal2Active(true); + achievement.setOpenedAt(LocalDateTime.now()); } if (achievement.isStep3Goal1Achieved() && achievement.isStep3Goal2Achieved()) { achievement.setStep4Goal1Active(true); achievement.setStep4Goal2Active(true); + achievement.setOpenedAt(LocalDateTime.now()); } // 서비스에서 달성 정보 저장 diff --git a/src/main/java/BE_Elixir/Elixir/domain/challenge/repository/ChallengeAchievementRepository.java b/src/main/java/BE_Elixir/Elixir/domain/challenge/repository/ChallengeAchievementRepository.java index 515d029..80ac90d 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/challenge/repository/ChallengeAchievementRepository.java +++ b/src/main/java/BE_Elixir/Elixir/domain/challenge/repository/ChallengeAchievementRepository.java @@ -1,12 +1,13 @@ package BE_Elixir.Elixir.domain.challenge.repository; import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievement; +import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievementId; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; import java.util.Optional; -public interface ChallengeAchievementRepository extends JpaRepository { +public interface ChallengeAchievementRepository extends JpaRepository { // 챌린지에 대한 사용자의 달성 상태 정보 조회 Optional findByChallengeIdAndMemberId(Long challengeId, Long memberId); diff --git a/src/main/java/BE_Elixir/Elixir/domain/challenge/service/ChallengeAchievementService.java b/src/main/java/BE_Elixir/Elixir/domain/challenge/service/ChallengeAchievementService.java index cc0d983..f585818 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/challenge/service/ChallengeAchievementService.java +++ b/src/main/java/BE_Elixir/Elixir/domain/challenge/service/ChallengeAchievementService.java @@ -4,8 +4,10 @@ import BE_Elixir.Elixir.domain.challenge.dto.response.ChallengeProgressResponseDTO; import BE_Elixir.Elixir.domain.challenge.entity.Challenge; import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievement; +import BE_Elixir.Elixir.domain.challenge.entity.ChallengeAchievementId; import BE_Elixir.Elixir.domain.challenge.repository.ChallengeAchievementRepository; import BE_Elixir.Elixir.domain.challenge.repository.ChallengeRepository; +import BE_Elixir.Elixir.domain.member.repository.MemberRepository; import BE_Elixir.Elixir.global.exception.CustomException; import BE_Elixir.Elixir.global.exception.ErrorCode; import jakarta.transaction.Transactional; @@ -22,6 +24,7 @@ public class ChallengeAchievementService { private final ChallengeRepository challengeRepository; private final ChallengeAchievementRepository challengeAchievementRepository; + private final MemberRepository memberRepository; @Transactional public void save(ChallengeAchievement achievement) { @@ -120,4 +123,40 @@ public ChallengeProgressResponseDTO getBeforeProgress(Long memberId, Long challe .orElseGet(() -> ChallengeProgressResponseDTO.empty(challenge)); } + // 챌린지 자동 참여 + @Transactional + public void challengeParticipation(String memberEmail) { + LocalDate now = LocalDate.now(); + int year = now.getYear(); + int month = now.getMonthValue(); + + // 회원 정보 조회 (email -> memberId) + Long memberId = memberRepository.findByEmail(memberEmail) + .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)) + .getId(); + + Challenge challenge = challengeRepository.findByYearAndMonth(year, month) + .orElseThrow(() -> new CustomException(ErrorCode.CHALLENGE_NOT_FOUND)); + + ChallengeAchievementId id = new ChallengeAchievementId(memberId, challenge.getId()); + + boolean exists = challengeAchievementRepository.existsById(id); + + if (!exists) { + ChallengeAchievement achievement = new ChallengeAchievement(); + achievement.setMemberId(memberId); + achievement.setChallengeId(challenge.getId()); + achievement.setStep1Goal1Active(true); + achievement.setStep1Goal2Active(true); + achievement.setStep2Goal1Active(false); + achievement.setStep2Goal2Active(false); + achievement.setStep3Goal1Active(false); + achievement.setStep3Goal2Active(false); + achievement.setStep4Goal1Active(false); + achievement.setStep4Goal2Active(false); + achievement.setOpenedAt(LocalDateTime.now()); + + challengeAchievementRepository.save(achievement); + } + } } From 11c28d59e69c53a7f26a94ac914a50480033f7aa Mon Sep 17 00:00:00 2001 From: Choi MinJung Date: Tue, 17 Jun 2025 17:59:58 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=20=EB=AA=A9=ED=91=9C=20=EB=8B=AC=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EC=A7=84=ED=96=89=20=ED=98=84=ED=99=A9=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0(openedAt=20=EC=84=A4=EC=A0=95=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/event/listener/ChallengeEventListener.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java b/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java index 69805ff..a160e74 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java +++ b/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java @@ -119,21 +119,19 @@ else if (event instanceof RecipeEvent) { handleGoal(goalType, memberId, openedAt, resultSetter); } + // 목표 달성 결과에 따라 다음 단계 활성화 if (achievement.isStep1Goal1Achieved() && achievement.isStep1Goal2Achieved()) { achievement.setStep2Goal1Active(true); achievement.setStep2Goal2Active(true); - achievement.setOpenedAt(LocalDateTime.now()); } if (achievement.isStep2Goal1Achieved() && achievement.isStep2Goal2Achieved()) { achievement.setStep3Goal1Active(true); achievement.setStep3Goal2Active(true); - achievement.setOpenedAt(LocalDateTime.now()); } if (achievement.isStep3Goal1Achieved() && achievement.isStep3Goal2Achieved()) { achievement.setStep4Goal1Active(true); achievement.setStep4Goal2Active(true); - achievement.setOpenedAt(LocalDateTime.now()); } // 서비스에서 달성 정보 저장 From 75089db0fd9fe0c7e409c4819ebfddcfe32a8b22 Mon Sep 17 00:00:00 2001 From: Choi MinJung Date: Tue, 17 Jun 2025 18:52:02 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=20=EB=AA=A9=ED=91=9C=EC=9D=B8=20'=ED=95=98?= =?UTF-8?q?=EB=A3=A8=203=EB=81=BC=20=EA=B8=B0=EB=A1=9D'=20=EC=8B=9C=20?= =?UTF-8?q?=ED=95=B4=EB=8B=B9=20=EC=84=B8=EB=B6=80=20=EB=AA=A9=ED=91=9C?= =?UTF-8?q?=EA=B0=80=20=EA=B0=9C=EB=B0=A9=EB=90=9C=20=EC=9D=B4=ED=9B=84?= =?UTF-8?q?=EB=A7=8C=20=EB=B9=84=EA=B5=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/ChallengeEventListener.java | 28 ++++++++++++++++--- .../dietLog/repository/DietLogRepository.java | 7 ++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java b/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java index a160e74..d59aee3 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java +++ b/src/main/java/BE_Elixir/Elixir/domain/challenge/event/listener/ChallengeEventListener.java @@ -21,7 +21,9 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -169,10 +171,28 @@ private void handleGoal(ChallengeGoalType goalType, Long memberId, LocalDateTime case DIET_LUNCH -> achieved = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.점심, openedAt); case DIET_THREE_MEALS -> { - boolean hasBreakfast = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.아침, openedAt); - boolean hasLunch = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.점심, openedAt); - boolean hasDinner = dietLogRepository.existsByMemberIdAndTypeAndTimeAfter(memberId, DietLogType.저녁, openedAt); - achieved = hasBreakfast && hasLunch && hasDinner; + // 특정 날짜 단위로 아침/점심/저녁이 모두 기록된 날이 있는지를 체크 + List datesWithBreakfast = dietLogRepository.findTimesWithDietTypeAfter(memberId, DietLogType.아침, openedAt).stream() + .map(LocalDateTime::toLocalDate) + .distinct() + .toList(); + + List datesWithLunch = dietLogRepository.findTimesWithDietTypeAfter(memberId, DietLogType.점심, openedAt).stream() + .map(LocalDateTime::toLocalDate) + .distinct() + .toList(); + + List datesWithDinner = dietLogRepository.findTimesWithDietTypeAfter(memberId, DietLogType.저녁, openedAt).stream() + .map(LocalDateTime::toLocalDate) + .distinct() + .toList(); + + // 교집합으로 하루라도 3끼 다 먹은 날이 있는지 확인 + Set breakfastSet = new HashSet<>(datesWithBreakfast); + breakfastSet.retainAll(datesWithLunch); + breakfastSet.retainAll(datesWithDinner); + + achieved = !breakfastSet.isEmpty(); } case DIET_SEASONAL_ONCE -> { // 사용자의 식단에 포함된 식재료 목록 diff --git a/src/main/java/BE_Elixir/Elixir/domain/dietLog/repository/DietLogRepository.java b/src/main/java/BE_Elixir/Elixir/domain/dietLog/repository/DietLogRepository.java index b2d6367..d8573b1 100644 --- a/src/main/java/BE_Elixir/Elixir/domain/dietLog/repository/DietLogRepository.java +++ b/src/main/java/BE_Elixir/Elixir/domain/dietLog/repository/DietLogRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -46,5 +47,9 @@ int countDietLogsInMonth(@Param("memberId") Long memberId, @Param("startOfMonth") LocalDateTime startOfMonth, @Param("endOfMonth") LocalDateTime endOfMonth); - + // - 특정 시간 이후에 작성된 기록 + @Query("SELECT DISTINCT dl.time FROM DietLog dl WHERE dl.member.id = :memberId AND dl.type = :type AND dl.time > :after") + List findTimesWithDietTypeAfter(@Param("memberId") Long memberId, + @Param("type") DietLogType type, + @Param("after") LocalDateTime after); } \ No newline at end of file