diff --git a/src/main/java/com/challenge/api/service/challenge/response/ChallengeResponse.java b/src/main/java/com/challenge/api/service/challenge/response/ChallengeResponse.java index e9b6c28..b098cff 100644 --- a/src/main/java/com/challenge/api/service/challenge/response/ChallengeResponse.java +++ b/src/main/java/com/challenge/api/service/challenge/response/ChallengeResponse.java @@ -3,6 +3,7 @@ import com.challenge.api.service.category.response.CategoryResponse; import com.challenge.api.service.record.response.RecordResponse; import com.challenge.domain.challenge.Challenge; +import com.challenge.domain.challenge.ChallengeStatus; import com.challenge.utils.date.DateUtils; import lombok.Builder; import lombok.Getter; @@ -15,6 +16,7 @@ public class ChallengeResponse { private final RecordResponse record; private final String title; private final String content; + private final ChallengeStatus status; private final int durationInWeeks; private final int weekGoalCount; private final int totalGoalCount; @@ -24,13 +26,14 @@ public class ChallengeResponse { @Builder private ChallengeResponse(Long id, CategoryResponse category, RecordResponse record, String title, - String content, int durationInWeeks, int weekGoalCount, int totalGoalCount, String color, - String startDateTime, String endDateTime) { + String content, ChallengeStatus status, int durationInWeeks, int weekGoalCount, + int totalGoalCount, String color, String startDateTime, String endDateTime) { this.id = id; this.category = category; this.record = record; this.title = title; this.content = content; + this.status = status; this.durationInWeeks = durationInWeeks; this.weekGoalCount = weekGoalCount; this.totalGoalCount = totalGoalCount; @@ -46,6 +49,7 @@ public static ChallengeResponse of(Challenge challenge) { .record(RecordResponse.of(challenge)) .title(challenge.getTitle()) .content(challenge.getContent()) + .status(challenge.getStatus()) .durationInWeeks(challenge.getDurationInWeeks()) .weekGoalCount(challenge.getWeeklyGoalCount()) .totalGoalCount(challenge.getTotalGoalCount()) diff --git a/src/main/java/com/challenge/domain/challenge/Challenge.java b/src/main/java/com/challenge/domain/challenge/Challenge.java index 401fb10..acbbb65 100644 --- a/src/main/java/com/challenge/domain/challenge/Challenge.java +++ b/src/main/java/com/challenge/domain/challenge/Challenge.java @@ -9,6 +9,8 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -52,6 +54,10 @@ public class Challenge extends BaseDateTimeEntity { @Column(length = 500) private String content; + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)", nullable = false) + private ChallengeStatus status; + @Column(nullable = false) private int durationInWeeks; @@ -64,9 +70,6 @@ public class Challenge extends BaseDateTimeEntity { @Column(nullable = false, length = 10) private String color; - @Column(nullable = false) - private boolean isDeleted = false; - @Column(nullable = false) private LocalDateTime startDateTime; @@ -74,18 +77,18 @@ public class Challenge extends BaseDateTimeEntity { private LocalDateTime endDateTime; @Builder - private Challenge(Member member, Category category, String title, String content, int durationInWeeks, - int weeklyGoalCount, String color, boolean isDeleted, LocalDateTime startDateTime) { + private Challenge(Member member, Category category, String title, String content, ChallengeStatus status, + int durationInWeeks, int weeklyGoalCount, String color, LocalDateTime startDateTime) { this.challengeRecords = new ArrayList<>(); this.member = member; this.category = category; this.title = title; this.content = content; + this.status = status; this.durationInWeeks = durationInWeeks; this.weeklyGoalCount = weeklyGoalCount; this.totalGoalCount = durationInWeeks * weeklyGoalCount; this.color = color; - this.isDeleted = isDeleted; this.startDateTime = startDateTime; this.endDateTime = startDateTime.plusWeeks(durationInWeeks) .toLocalDate() @@ -95,12 +98,13 @@ private Challenge(Member member, Category category, String title, String content } public static Challenge create(Member member, Category category, ChallengeCreateServiceRequest request, - LocalDateTime startDateTime) { + LocalDateTime startDateTime) { return Challenge.builder() .member(member) .category(category) .title(request.getTitle()) .content(request.getContent()) + .status(ChallengeStatus.ONGOING) .durationInWeeks(request.getDurationInWeeks()) .weeklyGoalCount(request.getWeeklyGoalCount()) .color(request.getColor()) @@ -120,7 +124,7 @@ public void addRecord(ChallengeRecord challengeRecord) { } public void delete() { - this.isDeleted = true; + this.status = ChallengeStatus.REMOVED; } } diff --git a/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java b/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java index 618a5d8..b4e92ad 100644 --- a/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java +++ b/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java @@ -1,7 +1,6 @@ package com.challenge.domain.challenge; import com.challenge.domain.member.Member; -import com.challenge.utils.date.DateUtils; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -26,7 +25,7 @@ public List findChallengesBy(Member member, LocalDate targetDate) { return queryFactory.selectFrom(challenge) .where(challenge.member.eq(member) - .and(challenge.isDeleted.isFalse()) + .and(challenge.status.ne(ChallengeStatus.REMOVED)) .and(challenge.endDateTime.goe(startDateTime)) .and(challenge.startDateTime.loe(endDateTime))) .fetch(); @@ -44,41 +43,31 @@ public boolean existsDuplicateRecordBy(Challenge challenge, LocalDate successDat return count != null && count > 0; } - // 진행중인 챌린지 수 조회 테스트용 - public Long countOngoingChallengesBy(Member member, String targetDateTime) { - LocalDateTime now = DateUtils.toLocalDateTime(targetDateTime); + // 진행중인 챌린지 수 조회 + public Long countOngoingChallengesBy(Member member) { return queryFactory.select(challenge.count()) .from(challenge) .where(challenge.member.eq(member) - .and(challenge.isDeleted.isFalse()) - .and(challenge.endDateTime.goe(now)) + .and(challenge.status.eq(ChallengeStatus.ONGOING)) ) .fetchOne(); } - // 진행중인 챌린지 수 조회 - public Long countOngoingChallengesBy(Member member) { - LocalDateTime now = LocalDateTime.now(); + // 완료된 챌린지 수 조회 + public Long countSucceedChallengesBy(Member member) { return queryFactory.select(challenge.count()) .from(challenge) .where(challenge.member.eq(member) - .and(challenge.isDeleted.isFalse()) - .and(challenge.endDateTime.goe(now)) - ) + .and(challenge.status.eq(ChallengeStatus.SUCCEED))) .fetchOne(); } - // 완료된 챌린지 수 조회 - public Long countCompletedChallengesBy(Member member) { - return null; - } - // 전체 챌린지 수 조회 public Long countAllChallengesBy(Member member) { return queryFactory.select(challenge.count()) .from(challenge) .where(challenge.member.eq(member) - .and(challenge.isDeleted.isFalse())) + .and(challenge.status.ne(ChallengeStatus.REMOVED))) .fetchOne(); } diff --git a/src/main/java/com/challenge/domain/challenge/ChallengeStatus.java b/src/main/java/com/challenge/domain/challenge/ChallengeStatus.java index e6a9242..3552ecc 100644 --- a/src/main/java/com/challenge/domain/challenge/ChallengeStatus.java +++ b/src/main/java/com/challenge/domain/challenge/ChallengeStatus.java @@ -7,15 +7,9 @@ @Getter public enum ChallengeStatus { - // - Q. 챌린지 기간이 만료되는 경우, 어떻게 자동으로 챌린지의 상태를 변경할 수 있을까? - // A. 스케줄러 라이브러리를 사용하여 매 00시 00분 00초에 챌린지의 상태를 확인하여 챌린지 기간이 만료된 경우 상태를 변경한다. - - // - Q. 챌린지의 상태를 변경하는 로직은 어디에 위치하는 것이 좋을까? - // A. 챌린지의 상태를 변경하는 로직은 챌린지 도메인에 위치하는 것이 좋다. - ONGOING("진행 중"), - SUCCEED("성공"), // 모든 목표를 달성한 경우 - FAILED("실패"), // 하나라도 목표를 달성하지 못한 경우 + SUCCEED("성공"), + EXPIRED("기간 만료"), REMOVED("삭제"); private final String description; diff --git a/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java b/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java index 1267f65..48de3d5 100644 --- a/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java +++ b/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java @@ -11,6 +11,7 @@ import com.challenge.domain.category.CategoryRepository; import com.challenge.domain.challenge.Challenge; import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.challenge.ChallengeStatus; import com.challenge.domain.challengeRecord.ChallengeRecord; import com.challenge.domain.challengeRecord.ChallengeRecordRepository; import com.challenge.domain.job.Job; @@ -368,10 +369,10 @@ void deleteChallenge() { // then - 삭제된 챌린지 검증 assertThat(deletedChallenge).isEqualTo(challenge2.getId()); assertThat(challengeRepository.findAll()).hasSize(2) - .extracting("title", "content", "isDeleted") + .extracting("title", "content", "status") .containsExactly( - tuple("제목1", "내용1", false), - tuple("제목2", "내용2", true) + tuple("제목1", "내용1", ChallengeStatus.ONGOING), + tuple("제목2", "내용2", ChallengeStatus.REMOVED) ); // given - 모든 챌린지 조회를 위한 요청 request @@ -413,12 +414,13 @@ private Member createMember() { } private Challenge createChallenge(Member member, Category category, int durationInWeeks, String title, - LocalDateTime startDateTime) { + LocalDateTime startDateTime) { return Challenge.builder() .member(member) .category(category) .durationInWeeks(durationInWeeks) .title(title) + .status(ChallengeStatus.ONGOING) .color("#30B0C7") .weeklyGoalCount(1) .startDateTime(startDateTime) diff --git a/src/test/java/com/challenge/api/validator/RecordValidatorTest.java b/src/test/java/com/challenge/api/validator/RecordValidatorTest.java index 27e3540..77ba51c 100644 --- a/src/test/java/com/challenge/api/validator/RecordValidatorTest.java +++ b/src/test/java/com/challenge/api/validator/RecordValidatorTest.java @@ -4,6 +4,7 @@ import com.challenge.domain.category.CategoryRepository; import com.challenge.domain.challenge.Challenge; import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.challenge.ChallengeStatus; import com.challenge.domain.challengeRecord.ChallengeRecord; import com.challenge.domain.challengeRecord.ChallengeRecordRepository; import com.challenge.domain.job.Job; @@ -125,6 +126,7 @@ private Challenge createChallenge(Member member, Category category, LocalDateTim .member(member) .category(category) .title("제목") + .status(ChallengeStatus.ONGOING) .color("#30B0C7") .durationInWeeks(1) .weeklyGoalCount(1) diff --git a/src/test/java/com/challenge/docs/ChallengeControllerDocsTest.java b/src/test/java/com/challenge/docs/ChallengeControllerDocsTest.java index 70722c5..3e30f12 100644 --- a/src/test/java/com/challenge/docs/ChallengeControllerDocsTest.java +++ b/src/test/java/com/challenge/docs/ChallengeControllerDocsTest.java @@ -11,6 +11,7 @@ import com.challenge.api.service.challenge.request.ChallengeUpdateServiceRequest; import com.challenge.api.service.challenge.response.ChallengeResponse; import com.challenge.api.service.record.response.RecordResponse; +import com.challenge.domain.challenge.ChallengeStatus; import com.challenge.domain.member.Member; import com.challenge.utils.date.DateUtils; import org.junit.jupiter.api.DisplayName; @@ -82,6 +83,7 @@ void getChallenges() throws Exception { .build()) .title("챌린지 제목1") .content("챌린지 내용1") + .status(ChallengeStatus.ONGOING) .durationInWeeks(1) .weekGoalCount(2) .totalGoalCount(4) @@ -104,6 +106,7 @@ void getChallenges() throws Exception { .build()) .title("챌린지 제목2") .content("챌린지 내용2") + .status(ChallengeStatus.ONGOING) .durationInWeeks(2) .weekGoalCount(2) .totalGoalCount(4) @@ -148,6 +151,8 @@ void getChallenges() throws Exception { fieldWithPath("data[].content").type(STRING) .optional() .description("챌린지 내용"), + fieldWithPath("data[].status").type(STRING) + .description("챌린지 상태"), fieldWithPath("data[].durationInWeeks").type(NUMBER) .description("챌린지 기간 (주 단위)"), fieldWithPath("data[].weekGoalCount").type(NUMBER) @@ -196,6 +201,7 @@ void createChallengeSuccess() throws Exception { .record(null) .title("챌린지 제목") .content("챌린지 내용") + .status(ChallengeStatus.ONGOING) .durationInWeeks(2) .weekGoalCount(2) .totalGoalCount(4) @@ -247,6 +253,8 @@ void createChallengeSuccess() throws Exception { fieldWithPath("data.content").type(STRING) .optional() .description("챌린지 내용"), + fieldWithPath("data.status").type(STRING) + .description("챌린지 상태"), fieldWithPath("data.durationInWeeks").type(NUMBER) .description("챌린지 기간 (주 단위)"), fieldWithPath("data.weekGoalCount").type(NUMBER) @@ -330,6 +338,7 @@ void achieveChallenge() throws Exception { .build()) .title("챌린지 제목") .content("챌린지 내용") + .status(ChallengeStatus.ONGOING) .durationInWeeks(2) .weekGoalCount(2) .totalGoalCount(4) @@ -377,6 +386,9 @@ void achieveChallenge() throws Exception { fieldWithPath("data.content").type(STRING) .optional() .description("챌린지 내용"), + fieldWithPath("data.status").type(STRING) + .optional() + .description("챌린지 상태"), fieldWithPath("data.durationInWeeks").type(NUMBER) .description("챌린지 기간 (주 단위)"), fieldWithPath("data.weekGoalCount").type(NUMBER) @@ -425,6 +437,7 @@ void cancelChallenge() throws Exception { .build()) .title("챌린지 제목") .content("챌린지 내용") + .status(ChallengeStatus.ONGOING) .durationInWeeks(2) .weekGoalCount(2) .totalGoalCount(4) @@ -474,6 +487,8 @@ void cancelChallenge() throws Exception { fieldWithPath("data.content").type(STRING) .optional() .description("챌린지 내용"), + fieldWithPath("data.status").type(STRING) + .description("챌린지 상태"), fieldWithPath("data.durationInWeeks").type(NUMBER) .description("챌린지 기간 (주 단위)"), fieldWithPath("data.weekGoalCount").type(NUMBER) @@ -523,6 +538,7 @@ void updateChallenge() throws Exception { .build()) .title("수정된 챌린지 제목") .content("수정된 챌린지 내용") + .status(ChallengeStatus.ONGOING) .durationInWeeks(2) .weekGoalCount(2) .totalGoalCount(4) @@ -576,6 +592,8 @@ void updateChallenge() throws Exception { fieldWithPath("data.content").type(STRING) .optional() .description("챌린지 내용"), + fieldWithPath("data.status").type(STRING) + .description("챌린지 상태"), fieldWithPath("data.durationInWeeks").type(NUMBER) .description("챌린지 기간 (주 단위)"), fieldWithPath("data.weekGoalCount").type(NUMBER) diff --git a/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java b/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java index 9b3eeeb..2476b6e 100644 --- a/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java +++ b/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java @@ -23,6 +23,9 @@ import java.time.LocalDateTime; import java.util.List; +import static com.challenge.domain.challenge.ChallengeStatus.ONGOING; +import static com.challenge.domain.challenge.ChallengeStatus.REMOVED; +import static com.challenge.domain.challenge.ChallengeStatus.SUCCEED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; @@ -69,11 +72,11 @@ void findChallengesBy() { Member member = createMember(); memberRepository.save(member); - Challenge challenge1 = createChallenge(member, category, 1, "제목1", false, + Challenge challenge1 = createChallenge(member, category, 1, "제목1", ONGOING, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); - Challenge challenge2 = createChallenge(member, category, 2, "제목2", false, + Challenge challenge2 = createChallenge(member, category, 2, "제목2", ONGOING, LocalDateTime.of(2024, 11, 16, 14, 0, 0)); - Challenge challenge3 = createChallenge(member, category, 3, "제목3", false, + Challenge challenge3 = createChallenge(member, category, 3, "제목3", ONGOING, LocalDateTime.of(2024, 12, 1, 0, 0, 0)); challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3)); @@ -135,34 +138,54 @@ void existsDuplicateRecordBy() { @Test void countOngoingChallenges() { // 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", false, + Challenge challenge1 = createChallenge(member, category, 1, "제목1", ONGOING, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); - Challenge challenge2 = createChallenge(member, category, 2, "제목2", false, + Challenge challenge2 = createChallenge(member, category, 2, "제목2", SUCCEED, LocalDateTime.of(2024, 11, 11, 14, 0, 0)); - Challenge challenge3 = createChallenge(member, category, 3, "제목3", false, + Challenge challenge3 = createChallenge(member, category, 3, "제목3", SUCCEED, LocalDateTime.of(2024, 12, 23, 0, 0, 0)); - Challenge challenge4 = createChallenge(member, category, 1, "제목4", false, + Challenge challenge4 = createChallenge(member, category, 1, "제목4", REMOVED, LocalDateTime.of(2025, 1, 1, 0, 0, 0)); challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3, challenge4)); // when - Long count = challengeQueryRepository.countOngoingChallengesBy(member, targetDateTime); + Long count = challengeQueryRepository.countOngoingChallengesBy(member); // then - assertThat(count).isEqualTo(2); + assertThat(count).isEqualTo(1); } @DisplayName("완료된 챌린지 수를 조회한다.") @Test - void countCompletedChallenges() { + void countSucceedChallenges() { + // given + Member member = createMember(); + memberRepository.save(member); + + Category category = createCategory(); + categoryRepository.save(category); + + Challenge challenge1 = createChallenge(member, category, 1, "제목1", ONGOING, + LocalDateTime.of(2024, 10, 1, 12, 30, 59)); + Challenge challenge2 = createChallenge(member, category, 2, "제목2", ONGOING, + LocalDateTime.of(2024, 11, 11, 14, 0, 0)); + Challenge challenge3 = createChallenge(member, category, 3, "제목3", ONGOING, + LocalDateTime.of(2024, 12, 23, 0, 0, 0)); + Challenge challenge4 = createChallenge(member, category, 1, "제목4", SUCCEED, + LocalDateTime.of(2025, 1, 1, 0, 0, 0)); + challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3, challenge4)); + + // when + Long count = challengeQueryRepository.countSucceedChallengesBy(member); + + // then + assertThat(count).isEqualTo(1); } @DisplayName("전체 챌린지 수를 조회한다.") @@ -175,13 +198,13 @@ void countAllChallenges() { Category category = createCategory(); categoryRepository.save(category); - Challenge challenge1 = createChallenge(member, category, 1, "제목1", false, + Challenge challenge1 = createChallenge(member, category, 1, "제목1", ONGOING, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); - Challenge challenge2 = createChallenge(member, category, 2, "제목2", false, + Challenge challenge2 = createChallenge(member, category, 2, "제목2", ONGOING, LocalDateTime.of(2024, 11, 11, 14, 0, 0)); - Challenge challenge3 = createChallenge(member, category, 3, "제목3", true, + Challenge challenge3 = createChallenge(member, category, 3, "제목3", REMOVED, LocalDateTime.of(2024, 12, 23, 0, 0, 0)); - Challenge challenge4 = createChallenge(member, category, 1, "제목4", true, + Challenge challenge4 = createChallenge(member, category, 1, "제목4", REMOVED, LocalDateTime.of(2025, 1, 1, 0, 0, 0)); challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3, challenge4)); @@ -212,14 +235,14 @@ private Member createMember() { } private Challenge createChallenge(Member member, Category category, int durationInWeeks, String title, - boolean isDeleted, LocalDateTime startDateTime) { + ChallengeStatus status, LocalDateTime startDateTime) { return Challenge.builder() .member(member) .category(category) .durationInWeeks(durationInWeeks) .title(title) .color("#30B0C7") - .isDeleted(isDeleted) + .status(status) .weeklyGoalCount(1) .startDateTime(startDateTime) .build(); diff --git a/src/test/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepositoryTest.java b/src/test/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepositoryTest.java index d2accde..8a9e414 100644 --- a/src/test/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepositoryTest.java +++ b/src/test/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepositoryTest.java @@ -4,6 +4,7 @@ import com.challenge.domain.category.CategoryRepository; import com.challenge.domain.challenge.Challenge; import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.challenge.ChallengeStatus; import com.challenge.domain.job.Job; import com.challenge.domain.job.JobRepository; import com.challenge.domain.member.Gender; @@ -160,6 +161,7 @@ private Challenge createChallenge(Member member, Category category, LocalDateTim .member(member) .category(category) .title("제목") + .status(ChallengeStatus.ONGOING) .color("#30B0C7") .durationInWeeks(1) .weeklyGoalCount(1)