Skip to content

Commit 99c9093

Browse files
authored
Merge pull request #98 from Daily-Step/feat/91
[Feat] 챌린지 달성 및 취소 수정
2 parents 59c5ba3 + bf0e6bc commit 99c9093

18 files changed

Lines changed: 516 additions & 132 deletions

File tree

src/main/java/com/challenge/api/service/challenge/ChallengeService.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
import com.challenge.domain.challenge.Challenge;
1515
import com.challenge.domain.challenge.ChallengeQueryRepository;
1616
import com.challenge.domain.challenge.ChallengeRepository;
17+
import com.challenge.domain.challengeRecord.ChallengeRecord;
18+
import com.challenge.domain.challengeRecord.ChallengeRecordRepository;
1719
import com.challenge.domain.member.Member;
18-
import com.challenge.domain.record.Record;
19-
import com.challenge.domain.record.RecordRepository;
2020
import com.challenge.utils.date.DateUtils;
2121
import com.challenge.validator.DateValidator;
2222
import lombok.RequiredArgsConstructor;
@@ -36,7 +36,7 @@ public class ChallengeService {
3636

3737
private final ChallengeRepository challengeRepository;
3838
private final CategoryRepository categoryRepository;
39-
private final RecordRepository recordRepository;
39+
private final ChallengeRecordRepository challengeRecordRepository;
4040

4141
private final ChallengeValidator challengeValidator;
4242
private final CategoryValidator categoryValidator;
@@ -75,36 +75,32 @@ public ChallengeResponse createChallenge(Member member, ChallengeCreateServiceRe
7575
public ChallengeResponse achieveChallenge(Member member, Long challengeId,
7676
ChallengeAchieveServiceRequest request) {
7777
// validation
78-
challengeValidator.challengeExistsBy(member, challengeId);
79-
DateValidator.isLocalDateFormatter(request.getAchieveDate());
80-
DateValidator.isBeforeOrEqualToTodayFrom(request.getAchieveDate());
78+
validateChallengeAchieveOrCancel(member, challengeId, request.getAchieveDate());
8179

8280
Challenge challenge = challengeRepository.getReferenceById(challengeId);
8381
challengeValidator.hasDuplicateRecordFor(challenge, DateUtils.toLocalDate(request.getAchieveDate()));
8482

85-
Record record = Record.achieve(challenge, request.getAchieveDate());
86-
recordRepository.save(record);
87-
challenge.addRecord(record);
83+
ChallengeRecord challengeRecord = ChallengeRecord.achieve(challenge, request.getAchieveDate());
84+
challengeRecordRepository.save(challengeRecord);
8885

8986
return ChallengeResponse.of(challenge);
9087
}
9188

9289
@Transactional
9390
public ChallengeResponse cancelChallenge(Member member, Long challengeId, ChallengeCancelServiceRequest request) {
9491
// validation
95-
challengeValidator.challengeExistsBy(member, challengeId);
96-
DateValidator.isLocalDateFormatter(request.getCancelDate());
97-
DateValidator.isBeforeOrEqualToTodayFrom(request.getCancelDate());
92+
validateChallengeAchieveOrCancel(member, challengeId, request.getCancelDate());
9893

9994
Challenge challenge = challengeRepository.getReferenceById(challengeId);
10095

10196
// validation
102-
Record record = recordValidator.hasRecordFor(challenge, DateUtils.toLocalDate(request.getCancelDate()));
97+
ChallengeRecord challengeRecord = recordValidator.isLatestRecordSuccessfulBy(challenge,
98+
DateUtils.toLocalDate(request.getCancelDate()));
10399

104100
// 기록 삭제
105-
challenge.getRecords().remove(record);
106-
// Record removedRecord = record.cancel(challenge, request.getCancelDate());
107-
// recordRepository.save(removedRecord);
101+
challenge.getChallengeRecords().remove(challengeRecord);
102+
ChallengeRecord cancelRecord = ChallengeRecord.cancel(challenge, request.getCancelDate());
103+
challengeRecordRepository.save(cancelRecord);
108104

109105
return ChallengeResponse.of(challenge);
110106
}
@@ -132,4 +128,11 @@ public Long deleteChallenge(Member member, Long challengeId) {
132128
return challengeId;
133129
}
134130

131+
// 챌린지 달성 또는 취소 시 유효성 검사
132+
private void validateChallengeAchieveOrCancel(Member member, Long challengeId, String actionDate) {
133+
challengeValidator.challengeExistsBy(member, challengeId);
134+
DateValidator.isLocalDateFormatter(actionDate);
135+
DateValidator.isBeforeOrEqualToTodayFrom(actionDate);
136+
}
137+
135138
}

src/main/java/com/challenge/api/service/record/response/RecordResponse.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.challenge.api.service.record.response;
22

33
import com.challenge.domain.challenge.Challenge;
4-
import com.challenge.domain.record.Record;
4+
import com.challenge.domain.challengeRecord.ChallengeRecord;
55
import com.challenge.utils.date.DateUtils;
66
import lombok.Builder;
77
import lombok.Getter;
@@ -19,15 +19,16 @@ private RecordResponse(List<String> successDates) {
1919
}
2020

2121
public static RecordResponse of(Challenge challenge) {
22-
List<Record> records = challenge.getRecords();
23-
if (records.isEmpty()) {
22+
List<ChallengeRecord> challengeRecords = challenge.getChallengeRecords();
23+
if (challengeRecords.isEmpty()) {
2424
return null;
2525
}
2626

2727
return RecordResponse.builder()
2828
.successDates(
29-
records.stream()
30-
.map(record -> DateUtils.toDayString(record.getSuccessDate()))
29+
challengeRecords.stream()
30+
.filter(ChallengeRecord::isSucceed)
31+
.map(record -> DateUtils.toDayString(record.getRecordDate()))
3132
.toList()
3233
)
3334
.build();
Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.challenge.api.validator;
22

33
import com.challenge.domain.challenge.Challenge;
4-
import com.challenge.domain.record.Record;
5-
import com.challenge.domain.record.RecordRepository;
4+
import com.challenge.domain.challengeRecord.ChallengeRecord;
5+
import com.challenge.domain.challengeRecord.ChallengeRecordQueryRepository;
66
import com.challenge.exception.ErrorCode;
77
import com.challenge.exception.GlobalException;
88
import lombok.RequiredArgsConstructor;
@@ -16,21 +16,17 @@
1616
@RequiredArgsConstructor
1717
public class RecordValidator {
1818

19-
private final RecordRepository recordRepository;
20-
21-
/**
22-
* 특정 챌린지와 날짜에 해당하는 레코드가 존재하는지 검증하고 반환
23-
*
24-
* @param challenge 검증 대상 챌린지
25-
* @param cancelDate 취소하려는 날짜
26-
* @return 검증된 Record 엔티티
27-
* @throws GlobalException RECORD_NOT_FOUND 예외
28-
*/
29-
public Record hasRecordFor(Challenge challenge, LocalDate cancelDate) {
30-
return challenge.getRecords().stream()
31-
.filter(r -> r.getSuccessDate().equals(cancelDate))
32-
.findFirst()
33-
.orElseThrow(() -> new GlobalException(ErrorCode.RECORD_NOT_FOUND));
19+
private final ChallengeRecordQueryRepository challengeRecordQueryRepository;
20+
21+
public ChallengeRecord isLatestRecordSuccessfulBy(Challenge challenge, LocalDate cancelDate) {
22+
ChallengeRecord challengeRecord = challengeRecordQueryRepository.isLatestRecordSuccessfulBy(challenge,
23+
cancelDate);
24+
25+
if (challengeRecord != null) {
26+
return challengeRecord;
27+
}
28+
29+
throw new GlobalException(ErrorCode.LATEST_ACHIEVE_RECORD_NOT_FOUND);
3430
}
3531

3632
}

src/main/java/com/challenge/domain/challenge/Challenge.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import com.challenge.api.service.challenge.request.ChallengeUpdateServiceRequest;
55
import com.challenge.domain.BaseDateTimeEntity;
66
import com.challenge.domain.category.Category;
7+
import com.challenge.domain.challengeRecord.ChallengeRecord;
78
import com.challenge.domain.member.Member;
8-
import com.challenge.domain.record.Record;
99
import jakarta.persistence.CascadeType;
1010
import jakarta.persistence.Column;
1111
import jakarta.persistence.Entity;
@@ -36,7 +36,7 @@ public class Challenge extends BaseDateTimeEntity {
3636
private Long id;
3737

3838
@OneToMany(mappedBy = "challenge", cascade = CascadeType.REMOVE, orphanRemoval = true)
39-
private List<Record> records = new ArrayList<>();
39+
private List<ChallengeRecord> challengeRecords = new ArrayList<>();
4040

4141
@ManyToOne(fetch = FetchType.LAZY)
4242
@JoinColumn(name = "member_id", nullable = false)
@@ -65,7 +65,7 @@ public class Challenge extends BaseDateTimeEntity {
6565
private String color;
6666

6767
@Column(nullable = false)
68-
private boolean isDeleted;
68+
private boolean isDeleted = false;
6969

7070
@Column(nullable = false)
7171
private LocalDateTime startDateTime;
@@ -75,8 +75,8 @@ public class Challenge extends BaseDateTimeEntity {
7575

7676
@Builder
7777
private Challenge(Member member, Category category, String title, String content, int durationInWeeks,
78-
int weeklyGoalCount, String color, LocalDateTime startDateTime) {
79-
this.records = new ArrayList<>();
78+
int weeklyGoalCount, String color, boolean isDeleted, LocalDateTime startDateTime) {
79+
this.challengeRecords = new ArrayList<>();
8080
this.member = member;
8181
this.category = category;
8282
this.title = title;
@@ -85,7 +85,7 @@ private Challenge(Member member, Category category, String title, String content
8585
this.weeklyGoalCount = weeklyGoalCount;
8686
this.totalGoalCount = durationInWeeks * weeklyGoalCount;
8787
this.color = color;
88-
this.isDeleted = false;
88+
this.isDeleted = isDeleted;
8989
this.startDateTime = startDateTime;
9090
this.endDateTime = startDateTime.plusWeeks(durationInWeeks)
9191
.toLocalDate()
@@ -115,8 +115,8 @@ public void update(Category category, ChallengeUpdateServiceRequest request) {
115115
this.content = request.getContent();
116116
}
117117

118-
public void addRecord(Record record) {
119-
this.records.add(record);
118+
public void addRecord(ChallengeRecord challengeRecord) {
119+
this.challengeRecords.add(challengeRecord);
120120
}
121121

122122
public void delete() {

src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.challenge.domain.member.Member;
44
import com.challenge.utils.date.DateUtils;
5-
import com.querydsl.core.types.dsl.Expressions;
65
import com.querydsl.jpa.impl.JPAQueryFactory;
76
import lombok.RequiredArgsConstructor;
87
import org.springframework.stereotype.Repository;
@@ -12,7 +11,7 @@
1211
import java.util.List;
1312

1413
import static com.challenge.domain.challenge.QChallenge.challenge;
15-
import static com.challenge.domain.record.QRecord.record;
14+
import static com.challenge.domain.challengeRecord.QChallengeRecord.challengeRecord;
1615

1716
@RequiredArgsConstructor
1817
@Repository
@@ -35,10 +34,11 @@ public List<Challenge> findChallengesBy(Member member, LocalDate targetDate) {
3534

3635
// 중복된 기록이 존재하는지 조회
3736
public boolean existsDuplicateRecordBy(Challenge challenge, LocalDate successDate) {
38-
Long count = queryFactory.select(record.count())
39-
.from(record)
40-
.where(record.challenge.eq(challenge)
41-
.and(record.successDate.eq(successDate)))
37+
Long count = queryFactory.select(challengeRecord.count())
38+
.from(challengeRecord)
39+
.where(challengeRecord.challenge.eq(challenge)
40+
.and(challengeRecord.recordDate.eq(successDate))
41+
.and(challengeRecord.isSucceed.eq(true)))
4242
.fetchOne();
4343

4444
return count != null && count > 0;
@@ -70,17 +70,7 @@ public Long countOngoingChallengesBy(Member member) {
7070

7171
// 완료된 챌린지 수 조회
7272
public Long countCompletedChallengesBy(Member member) {
73-
return queryFactory
74-
.select(challenge.count())
75-
.from(challenge)
76-
.leftJoin(record)
77-
.on(record.challenge.eq(challenge)
78-
.and(record.isSucceed.isTrue())
79-
)
80-
.where(challenge.member.eq(member))
81-
.groupBy(challenge)
82-
.having(record.count().eq(Expressions.constant(challenge.totalGoalCount)))
83-
.fetchOne();
73+
return null;
8474
}
8575

8676
// 전체 챌린지 수 조회
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.challenge.domain.challenge;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
6+
@RequiredArgsConstructor
7+
@Getter
8+
public enum ChallengeStatus {
9+
10+
// - Q. 챌린지 기간이 만료되는 경우, 어떻게 자동으로 챌린지의 상태를 변경할 수 있을까?
11+
// A. 스케줄러 라이브러리를 사용하여 매 00시 00분 00초에 챌린지의 상태를 확인하여 챌린지 기간이 만료된 경우 상태를 변경한다.
12+
13+
// - Q. 챌린지의 상태를 변경하는 로직은 어디에 위치하는 것이 좋을까?
14+
// A. 챌린지의 상태를 변경하는 로직은 챌린지 도메인에 위치하는 것이 좋다.
15+
16+
ONGOING("진행 중"),
17+
SUCCEED("성공"), // 모든 목표를 달성한 경우
18+
FAILED("실패"), // 하나라도 목표를 달성하지 못한 경우
19+
REMOVED("삭제");
20+
21+
private final String description;
22+
}

src/main/java/com/challenge/domain/record/Record.java renamed to src/main/java/com/challenge/domain/challengeRecord/ChallengeRecord.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.challenge.domain.record;
1+
package com.challenge.domain.challengeRecord;
22

33
import com.challenge.domain.BaseDateTimeEntity;
44
import com.challenge.domain.challenge.Challenge;
@@ -21,43 +21,43 @@
2121
@Entity
2222
@Getter
2323
@NoArgsConstructor(access = AccessLevel.PROTECTED)
24-
public class Record extends BaseDateTimeEntity {
24+
public class ChallengeRecord extends BaseDateTimeEntity {
2525

2626
@Id
2727
@GeneratedValue(strategy = GenerationType.IDENTITY)
28-
@Column(name = "record_id")
28+
@Column(name = "challenge_record_id")
2929
private Long id;
3030

31-
@Column(nullable = false)
32-
private LocalDate successDate;
33-
34-
@Column(nullable = false)
35-
private boolean isSucceed = false;
36-
3731
@ManyToOne(fetch = FetchType.LAZY)
3832
@JoinColumn(name = "challenge_id", nullable = false)
3933
private Challenge challenge;
4034

35+
@Column(nullable = false)
36+
private LocalDate recordDate;
37+
38+
@Column(nullable = false)
39+
private boolean isSucceed;
40+
4141
@Builder
42-
private Record(LocalDate successDate, boolean isSucceed, Challenge challenge) {
43-
this.successDate = successDate;
42+
private ChallengeRecord(LocalDate recordDate, boolean isSucceed, Challenge challenge) {
43+
this.recordDate = recordDate;
4444
this.challenge = challenge;
4545
this.isSucceed = isSucceed;
4646
}
4747

48-
public static Record achieve(Challenge challenge, String achieveDate) {
49-
Record record = Record.builder()
50-
.successDate(DateUtils.toLocalDate(achieveDate))
48+
public static ChallengeRecord achieve(Challenge challenge, String achieveDate) {
49+
ChallengeRecord achieveRecord = ChallengeRecord.builder()
50+
.recordDate(DateUtils.toLocalDate(achieveDate))
5151
.isSucceed(true)
5252
.challenge(challenge)
5353
.build();
54-
challenge.addRecord(record);
55-
return record;
54+
challenge.addRecord(achieveRecord);
55+
return achieveRecord;
5656
}
5757

58-
public Record cancel(Challenge challenge, String cancelDate) {
59-
return Record.builder()
60-
.successDate(DateUtils.toLocalDate(cancelDate))
58+
public static ChallengeRecord cancel(Challenge challenge, String cancelDate) {
59+
return ChallengeRecord.builder()
60+
.recordDate(DateUtils.toLocalDate(cancelDate))
6161
.isSucceed(false)
6262
.challenge(challenge)
6363
.build();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.challenge.domain.challengeRecord;
2+
3+
import com.challenge.domain.challenge.Challenge;
4+
import com.challenge.exception.ErrorCode;
5+
import com.challenge.exception.GlobalException;
6+
import com.querydsl.jpa.impl.JPAQueryFactory;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.stereotype.Repository;
9+
10+
import java.time.LocalDate;
11+
12+
import static com.challenge.domain.challengeRecord.QChallengeRecord.challengeRecord;
13+
14+
@RequiredArgsConstructor
15+
@Repository
16+
public class ChallengeRecordQueryRepository {
17+
18+
private final JPAQueryFactory queryFactory;
19+
20+
public ChallengeRecord isLatestRecordSuccessfulBy(Challenge challenge, LocalDate cancelDate) {
21+
ChallengeRecord latestRecord = queryFactory.selectFrom(challengeRecord)
22+
.where(challengeRecord.challenge.eq(challenge)
23+
.and(challengeRecord.recordDate.eq(cancelDate))
24+
)
25+
.orderBy(challengeRecord.createdAt.desc())
26+
.fetchFirst();
27+
28+
if (latestRecord != null && latestRecord.isSucceed()) {
29+
return latestRecord;
30+
}
31+
32+
throw new GlobalException(ErrorCode.LATEST_ACHIEVE_RECORD_NOT_FOUND);
33+
}
34+
35+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.challenge.domain.challengeRecord;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
5+
public interface ChallengeRecordRepository extends JpaRepository<ChallengeRecord, Long> {
6+
7+
}

0 commit comments

Comments
 (0)