diff --git a/src/main/java/com/challenge/api/service/challenge/ChallengeService.java b/src/main/java/com/challenge/api/service/challenge/ChallengeService.java index a6cff5a..8a3c351 100644 --- a/src/main/java/com/challenge/api/service/challenge/ChallengeService.java +++ b/src/main/java/com/challenge/api/service/challenge/ChallengeService.java @@ -99,10 +99,13 @@ public ChallengeResponse cancelChallenge(Member member, Long challengeId, Challe Challenge challenge = challengeRepository.getReferenceById(challengeId); // validation - Record record = recordValidator.hasRecordFor(challenge, DateUtils.toLocalDate(request.getCancelDate())); + // TODO 마지막 record 상태를 가져와 is_succeed 상태가 true인지 확인 + recordValidator.hasRecordFor(challenge, DateUtils.toLocalDate(request.getCancelDate())); // 기록 삭제 - challenge.getRecords().remove(record); + Record record = Record.cancel(challenge, request.getCancelDate()); + recordRepository.save(record); + return ChallengeResponse.of(challenge); } diff --git a/src/main/java/com/challenge/api/service/record/response/RecordResponse.java b/src/main/java/com/challenge/api/service/record/response/RecordResponse.java index ef06859..40a3d2b 100644 --- a/src/main/java/com/challenge/api/service/record/response/RecordResponse.java +++ b/src/main/java/com/challenge/api/service/record/response/RecordResponse.java @@ -27,7 +27,7 @@ public static RecordResponse of(Challenge challenge) { return RecordResponse.builder() .successDates( records.stream() - .map(record -> DateUtils.toDayString(record.getSuccessDate())) + .map(record -> DateUtils.toDayString(record.getDate())) .toList() ) .build(); diff --git a/src/main/java/com/challenge/api/validator/RecordValidator.java b/src/main/java/com/challenge/api/validator/RecordValidator.java index 938c2a9..661f73d 100644 --- a/src/main/java/com/challenge/api/validator/RecordValidator.java +++ b/src/main/java/com/challenge/api/validator/RecordValidator.java @@ -28,7 +28,7 @@ public class RecordValidator { */ public Record hasRecordFor(Challenge challenge, LocalDate cancelDate) { return challenge.getRecords().stream() - .filter(r -> r.getSuccessDate().equals(cancelDate)) + .filter(r -> r.getDate().equals(cancelDate)) .findFirst() .orElseThrow(() -> new GlobalException(ErrorCode.RECORD_NOT_FOUND)); } diff --git a/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java b/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java index 740c3e9..e46ce22 100644 --- a/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java +++ b/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java @@ -1,6 +1,8 @@ package com.challenge.domain.challenge; import com.challenge.domain.member.Member; +import com.challenge.utils.date.DateUtils; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -18,9 +20,7 @@ public class ChallengeQueryRepository { private final JPAQueryFactory queryFactory; - /** - * 입력 받은 날짜 기준 이전 2달간의 챌린지 목록 조회 (2달전 00:00:00 ~ 입력받은 날짜 23:59:59) - */ + // 입력 받은 날짜 기준 이전 2달간의 챌린지 목록 조회 (2달전 00:00:00 ~ 입력받은 날짜 23:59:59) public List findChallengesBy(Member member, LocalDate targetDate) { LocalDateTime startDateTime = targetDate.minusMonths(2).atStartOfDay(); LocalDateTime endDateTime = targetDate.atTime(23, 59, 59); @@ -33,6 +33,7 @@ public List findChallengesBy(Member member, LocalDate targetDate) { .fetch(); } + // 중복된 기록이 존재하는지 조회 public boolean existsDuplicateRecordBy(Challenge challenge, LocalDate successDate) { Long count = queryFactory.select(record.count()) .from(record) @@ -43,4 +44,52 @@ public boolean existsDuplicateRecordBy(Challenge challenge, LocalDate successDat return count != null && count > 0; } + // 진행중인 챌린지 수 조회 테스트용 + public Long countOngoingChallengesBy(Member member, String targetDateTime) { + LocalDateTime now = DateUtils.toLocalDateTime(targetDateTime); + return queryFactory.select(challenge.count()) + .from(challenge) + .where(challenge.member.eq(member) + .and(challenge.isDeleted.isFalse()) + .and(challenge.endDateTime.goe(now)) + ) + .fetchOne(); + } + + // 진행중인 챌린지 수 조회 + public Long countOngoingChallengesBy(Member member) { + LocalDateTime now = LocalDateTime.now(); + return queryFactory.select(challenge.count()) + .from(challenge) + .where(challenge.member.eq(member) + .and(challenge.isDeleted.isFalse()) + .and(challenge.endDateTime.goe(now)) + ) + .fetchOne(); + } + + // 완료된 챌린지 수 조회 + public Long countCompletedChallengesBy(Member member) { + return queryFactory + .select(challenge.count()) + .from(challenge) + .leftJoin(record) + .on(record.challenge.eq(challenge) + .and(record.isSucceed.isTrue()) + ) + .where(challenge.member.eq(member)) + .groupBy(challenge) + .having(record.count().eq(Expressions.constant(challenge.totalGoalCount))) + .fetchOne(); + } + + // 전체 챌린지 수 조회 + public Long countAllChallengesBy(Member member) { + return queryFactory.select(challenge.count()) + .from(challenge) + .where(challenge.member.eq(member) + .and(challenge.isDeleted.isFalse())) + .fetchOne(); + } + } diff --git a/src/main/java/com/challenge/domain/challenge/ChallengeRepository.java b/src/main/java/com/challenge/domain/challenge/ChallengeRepository.java index 60c1213..57266b4 100644 --- a/src/main/java/com/challenge/domain/challenge/ChallengeRepository.java +++ b/src/main/java/com/challenge/domain/challenge/ChallengeRepository.java @@ -5,6 +5,7 @@ public interface ChallengeRepository extends JpaRepository { + // 해당 멤버에 대한 챌린지 존재 여부 조회 boolean existsByMemberAndId(Member member, Long challengeId); } diff --git a/src/main/java/com/challenge/domain/record/Record.java b/src/main/java/com/challenge/domain/record/Record.java index 967c16f..4129407 100644 --- a/src/main/java/com/challenge/domain/record/Record.java +++ b/src/main/java/com/challenge/domain/record/Record.java @@ -11,8 +11,6 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -23,9 +21,6 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table( - uniqueConstraints = @UniqueConstraint(columnNames = {"challenge_id", "success_date"}) -) public class Record extends BaseDateTimeEntity { @Id @@ -34,24 +29,40 @@ public class Record extends BaseDateTimeEntity { private Long id; @Column(nullable = false) - private LocalDate successDate; + private LocalDate date; + + @Column(nullable = false) + private boolean isSucceed = false; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "challenge_id", nullable = false) private Challenge challenge; @Builder - private Record(LocalDate successDate, Challenge challenge) { - this.successDate = successDate; + private Record(LocalDate date, boolean isSucceed, Challenge challenge) { + this.date = date; this.challenge = challenge; - challenge.addRecord(this); + this.isSucceed = isSucceed; } public static Record achieve(Challenge challenge, String achieveDate) { - return Record.builder() + Record record = Record.builder() + .date(DateUtils.toLocalDate(achieveDate)) + .isSucceed(true) + .challenge(challenge) + .build(); + challenge.addRecord(record); + return record; + } + + public static Record cancel(Challenge challenge, String cancelDate) { + Record record = Record.builder() + .date(DateUtils.toLocalDate(cancelDate)) + .isSucceed(false) .challenge(challenge) - .successDate(DateUtils.toLocalDate(achieveDate)) .build(); + challenge.getRecords().remove(record); + return record; } } diff --git a/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java b/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java index f069a9b..c7c90a3 100644 --- a/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java +++ b/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java @@ -127,7 +127,7 @@ private Category createCategory() { private Record createRecord(Challenge challenge, LocalDate currentDate) { return Record.builder() .challenge(challenge) - .successDate(currentDate) + .date(currentDate) .build(); } diff --git a/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java b/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java index b2530f3..298a0d5 100644 --- a/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java +++ b/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java @@ -131,6 +131,64 @@ void existsDuplicateRecordBy() { assertThat(result).isTrue(); } + @DisplayName("진행중인 챌린지 수를 조회한다.") + @Test + void countOngoingChallengesBy() { + // given + String targetDateTime = "2025-01-08 12:30:59"; + + Member member = createMember(); + memberRepository.save(member); + + Category category = createCategory(); + categoryRepository.save(category); + + Challenge challenge1 = createChallenge(member, category, 1, "제목1", + LocalDateTime.of(2024, 10, 1, 12, 30, 59)); + Challenge challenge2 = createChallenge(member, category, 2, "제목2", + LocalDateTime.of(2024, 11, 11, 14, 0, 0)); + Challenge challenge3 = createChallenge(member, category, 3, "제목3", + LocalDateTime.of(2024, 12, 23, 0, 0, 0)); + Challenge challenge4 = createChallenge(member, category, 1, "제목4", + LocalDateTime.of(2025, 1, 1, 0, 0, 0)); + challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3, challenge4)); + + // when + Long count = challengeQueryRepository.countOngoingChallengesBy(member, targetDateTime); + + // then + assertThat(count).isEqualTo(2); + } + + @DisplayName("완료된 챌린지 수를 조회한다.") + @Test + void countCompletedChallengesBy() { + // given + Member member = createMember(); + memberRepository.save(member); + + Category category = createCategory(); + categoryRepository.save(category); + + Challenge challenge1 = createChallenge(member, category, 1, "제목1", + LocalDateTime.of(2025, 1, 1, 12, 30, 59)); + + Record record1 = createRecord(challenge1, LocalDate.of(2025, 1, 1)); + Record record2 = createRecord(challenge1, LocalDate.of(2025, 1, 2)); + Record record3 = createRecord(challenge1, LocalDate.of(2025, 1, 3)); + Record record4 = createRecord(challenge1, LocalDate.of(2025, 1, 4)); + Record record5 = createRecord(challenge1, LocalDate.of(2025, 1, 5)); + Record record6 = createRecord(challenge1, LocalDate.of(2025, 1, 6)); + Record record7 = createRecord(challenge1, LocalDate.of(2025, 1, 7)); + recordRepository.saveAll(List.of(record1, record2, record3, record4, record5, record6, record7)); + + // when + Long count = challengeQueryRepository.countCompletedChallengesBy(member); + + // then + assertThat(count).isEqualTo(1); + } + private Member createMember() { Job job = Job.builder() .code("1")