Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] issue188: 스터디 상태 자동 변경 #191

Merged
merged 21 commits into from Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
43d759f
feat: 스케쥴러를 이용한 스터디 상태 자동 변경
tco0427 Aug 3, 2022
13935dd
refactor: 스케줄러 로직 개선
tco0427 Aug 3, 2022
edbdd6d
fix: 테스트 코드 수정
tco0427 Aug 3, 2022
1d37bc2
refactor: 응답 값이 없는 경우 null 반환
tco0427 Aug 3, 2022
b39181a
chore: 충돌 해결
tco0427 Aug 3, 2022
3bdaddf
Merge branch 'develop' of https://github.com/woowacourse-teams/2022-m…
tco0427 Aug 3, 2022
e224dd1
refactor: 메소드명 개선 및 Getter 추가
tco0427 Aug 3, 2022
604e9b4
refactor: 불필요한 메소드 제거
tco0427 Aug 3, 2022
f10eb90
refactor: log 개선
tco0427 Aug 4, 2022
a196411
refactor: 기존 메소드 활용
tco0427 Aug 4, 2022
5568c77
refactor: `@Enumerated` 통일
tco0427 Aug 4, 2022
7392e40
feat: 테스트 코드 수정
tco0427 Aug 4, 2022
ae0c1b0
refactor: StudyPlanner 수정
tco0427 Aug 4, 2022
d551ac7
Merge branch 'develop' of https://github.com/woowacourse-teams/2022-m…
tco0427 Aug 4, 2022
e2ebea9
Merge branch 'develop' of https://github.com/woowacourse-teams/2022-m…
tco0427 Aug 4, 2022
e1f1ce0
Merge branch 'develop' of https://github.com/woowacourse-teams/2022-m…
tco0427 Aug 4, 2022
dc4d4ea
feat: Study 불변식 검증 추가
tco0427 Aug 4, 2022
e87dc38
Merge branch 'develop' of https://github.com/woowacourse-teams/2022-m…
tco0427 Aug 4, 2022
3cc64c2
refactor: Auto에 Transactional 제거
tco0427 Aug 4, 2022
88aa874
fix: 불필요한 Bean 제거
tco0427 Aug 4, 2022
a89b0ba
test: 테스트 코드 개선
tco0427 Aug 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,11 +1,13 @@
package com.woowacourse.moamoa;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@Slf4j
public class MoamoaApplication {

public static void main(final String[] args) {
Expand Down
Expand Up @@ -9,6 +9,7 @@
import com.woowacourse.moamoa.common.exception.UnauthorizedException;
import com.woowacourse.moamoa.study.domain.exception.InvalidPeriodException;
import com.woowacourse.moamoa.study.service.exception.FailureParticipationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
Expand All @@ -17,6 +18,7 @@
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@RestControllerAdvice
@Slf4j
public class CommonControllerAdvice {

@ExceptionHandler({
Expand All @@ -34,22 +36,25 @@ public ResponseEntity<ErrorResponse> handleBadRequest() {
FailureParticipationException.class
})
public ResponseEntity<ErrorResponse> handleBadRequest(final Exception e) {
log.error("HandleBadRequest : {}", e.getMessage());
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
}

@ExceptionHandler({UnauthorizedException.class})
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<Void> handleUnauthorized(final Exception e) {
log.error("UnauthorizedException : {}", e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

@ExceptionHandler({NotFoundException.class})
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(final Exception e) {
log.error("NotFoundException : {}", e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse(e.getMessage()));
}

@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleInternalServerError(RuntimeException e) {
e.printStackTrace();
log.error("RuntimeException : {}", e.getMessage());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(new ErrorResponse("요청을 처리할 수 없습니다."));
}
}
@@ -1,26 +1,29 @@
package com.woowacourse.moamoa.study.domain;

import static com.woowacourse.moamoa.study.domain.RecruitStatus.RECRUITMENT_END;
import static com.woowacourse.moamoa.study.domain.RecruitStatus.RECRUITMENT_START;
import static javax.persistence.EnumType.STRING;
import static lombok.AccessLevel.PROTECTED;

import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import lombok.NoArgsConstructor;

@Embeddable
@NoArgsConstructor(access = PROTECTED)
public class RecruitPlanner {

@Column(name = "max_member_count")
private Integer max;

@Enumerated(EnumType.STRING)
@Enumerated(STRING)
@Column(name = "recruitment_status")
private RecruitStatus recruitStatus;

private LocalDate enrollmentEndDate;

protected RecruitPlanner() {
}

public RecruitPlanner(final Integer max, final RecruitStatus recruitStatus, final LocalDate enrollmentEndDate) {
this.max = max;
this.recruitStatus = recruitStatus;
Expand All @@ -38,20 +41,26 @@ boolean isRecruitedBeforeThan(LocalDate date) {
return enrollmentEndDate.isBefore(date);
}

boolean isNeedToCloseRecruiting(final LocalDate now) {
return recruitStatus.equals(RecruitStatus.RECRUITMENT_START) && isRecruitedBeforeThan(now);
void updateRecruiting(final LocalDate now) {
if (isNeedToCloseRecruiting(now)) {
closeRecruiting();
}
}

private boolean isNeedToCloseRecruiting(final LocalDate now) {
return recruitStatus.equals(RECRUITMENT_START) && isRecruitedBeforeThan(now);
}

void closeRecruiting() {
recruitStatus = RecruitStatus.RECRUITMENT_END;
recruitStatus = RECRUITMENT_END;
}

LocalDate getEnrollmentEndDate() {
return enrollmentEndDate;
}

boolean isCloseEnrollment() {
return recruitStatus.equals(RecruitStatus.RECRUITMENT_END);
return recruitStatus.equals(RECRUITMENT_END);
}

int getCapacity() {
Expand Down
Expand Up @@ -57,6 +57,10 @@ private Study(final Long id, final Content content, final Participants participa
throw new InvalidPeriodException();
}

if (studyPlanner.isInappropriateCondition(createdAt.toLocalDate())) {
throw new InvalidPeriodException();
}

this.id = id;
this.content = content;
this.participants = participants;
Expand Down Expand Up @@ -95,19 +99,20 @@ public void participate(final Long memberId) {
}
}

private boolean isFullOfCapacity() {
return recruitPlanner.hasCapacity() && recruitPlanner.getCapacity() == participants.getSize();
public void changeStatus(final LocalDate now) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Study 객체에서 상태 관리 👍

recruitPlanner.updateRecruiting(now);
studyPlanner.updateStatus(now);
}

public boolean isNeedToCloseRecruiting(LocalDate now) {
return recruitPlanner.isNeedToCloseRecruiting(now);
public boolean isProgressStatus() {
return studyPlanner.isProgress();
}

public void closeEnrollment() {
recruitPlanner.closeRecruiting();
public boolean isCloseStudy() {
return studyPlanner.isCloseStudy();
}

public boolean isCloseEnrollment() {
return recruitPlanner.isCloseEnrollment();
private boolean isFullOfCapacity() {
return recruitPlanner.hasCapacity() && recruitPlanner.getCapacity() == participants.getSize();
}
}
@@ -1,21 +1,25 @@
package com.woowacourse.moamoa.study.domain;

import static com.woowacourse.moamoa.study.domain.StudyStatus.DONE;
import static com.woowacourse.moamoa.study.domain.StudyStatus.IN_PROGRESS;
import static com.woowacourse.moamoa.study.domain.StudyStatus.PREPARE;
import static javax.persistence.EnumType.STRING;
import static lombok.AccessLevel.PROTECTED;

import com.woowacourse.moamoa.study.domain.exception.InvalidPeriodException;
import java.time.LocalDate;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Enumerated;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Embeddable
@EqualsAndHashCode
@NoArgsConstructor(access = PROTECTED)
public class StudyPlanner {

@Enumerated(value = STRING)
@Enumerated(STRING)
@Column(nullable = false)
private StudyStatus studyStatus;

Expand Down Expand Up @@ -44,28 +48,43 @@ boolean isEndBeforeThan(LocalDate date) {
return endDate.isBefore(date);
}

void updateStatus(final LocalDate now) {
if (isNeedToChangeStatus(now)) {
studyStatus = studyStatus.nextStatus();
}
}

private boolean isNeedToChangeStatus(final LocalDate now) {
return isNeedToCloseStudy(now) || isNeedToChangeProgress(now);
}

private boolean isNeedToCloseStudy(final LocalDate now) {
return (endDate != null) && (isProgress()) && (endDate.isBefore(now) || endDate.isEqual(now));
}

private boolean isNeedToChangeProgress(final LocalDate now) {
return (isPreparing()) && (startDate.isBefore(now) || startDate.isEqual(now));
}

boolean isProgress() {
return studyStatus.equals(IN_PROGRESS);
}

boolean isPreparing() {
return studyStatus.equals(StudyStatus.PREPARE);
return studyStatus.equals(PREPARE);
}

public StudyStatus getStudyStatus() {
return studyStatus;
boolean isCloseStudy() {
return studyStatus.equals(DONE);
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
public boolean isInappropriateCondition(final LocalDate createdDate) {
if (startDate.isEqual(createdDate) && studyStatus == IN_PROGRESS) {
return false;
}
if (o == null || getClass() != o.getClass()) {
if ((startDate.isAfter(createdDate)) && studyStatus == PREPARE) {
return false;
}
final StudyPlanner studyPlanner = (StudyPlanner) o;
return Objects.equals(startDate, studyPlanner.startDate) && Objects.equals(endDate, studyPlanner.endDate);
}

@Override
public int hashCode() {
return Objects.hash(startDate, endDate);
return !studyStatus.equals(DONE);
}
}
@@ -1,15 +1,16 @@
package com.woowacourse.moamoa.study.domain;

import java.util.Arrays;

public enum StudyStatus {

PREPARE, IN_PROGRESS, DONE;

public static StudyStatus find(String status) {
return Arrays.stream(StudyStatus.values())
.filter(studyStatus -> studyStatus.name().equals(status))
.findAny()
.get();
public StudyStatus nextStatus() {
if (this.equals(PREPARE)) {
return IN_PROGRESS;
}
if (this.equals(IN_PROGRESS)) {
return DONE;
}
return PREPARE;
}
}
Expand Up @@ -36,7 +36,7 @@ public class MyStudyDao {
final String startDate = rs.getString("start_date");
final String endDate = rs.getString("end_date");

return new MyStudySummaryData(id, title, StudyStatus.find(studyStatus),
return new MyStudySummaryData(id, title, StudyStatus.valueOf(studyStatus),
currentMemberCount, maxMemberCount, startDate, endDate);
};

Expand Down
@@ -1,34 +1,25 @@
package com.woowacourse.moamoa.study.schedule;

import com.woowacourse.moamoa.MoamoaApplication;
import com.woowacourse.moamoa.study.service.StudyService;
import java.time.LocalDateTime;
import java.time.ZoneId;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.config.TriggerTask;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Slf4j
public class AutoCloseEnrollmentTask extends TriggerTask {

private static final Logger LOGGER = LoggerFactory.getLogger(MoamoaApplication.class);

public AutoCloseEnrollmentTask(final StudyService studyService) {
super(runnable(studyService), new CronTrigger("@daily", ZoneId.of("Asia/Seoul")));
}

private static Runnable runnable(final StudyService studyService) {
return new Runnable() {

@Transactional
public void run() {
LOGGER.debug(LocalDateTime.now() + " : " + "start moamoa scheduled task!");
studyService.autoCloseStudies();
}
return () -> {
log.debug("{} : start moamoa scheduled task!", LocalDateTime.now());
studyService.autoUpdateStatus();
};
}
}
@@ -1,18 +1,15 @@
package com.woowacourse.moamoa.study.service;

import static com.woowacourse.moamoa.study.domain.StudyStatus.IN_PROGRESS;
import static com.woowacourse.moamoa.study.domain.StudyStatus.PREPARE;

import com.woowacourse.moamoa.common.exception.UnauthorizedException;
import com.woowacourse.moamoa.common.utils.DateTimeSystem;
import com.woowacourse.moamoa.member.domain.Member;
import com.woowacourse.moamoa.member.domain.repository.MemberRepository;
import com.woowacourse.moamoa.study.domain.AttachedTags;
import com.woowacourse.moamoa.study.domain.Content;
import com.woowacourse.moamoa.study.domain.Participants;
import com.woowacourse.moamoa.study.domain.StudyPlanner;
import com.woowacourse.moamoa.study.domain.RecruitPlanner;
import com.woowacourse.moamoa.study.domain.Study;
import com.woowacourse.moamoa.study.domain.StudyPlanner;
import com.woowacourse.moamoa.study.domain.repository.StudyRepository;
import com.woowacourse.moamoa.study.service.exception.StudyNotFoundException;
import com.woowacourse.moamoa.study.service.request.CreatingStudyRequest;
Expand Down Expand Up @@ -68,14 +65,12 @@ private Member findMemberBy(final Long githubId) {
.orElseThrow(() -> new UnauthorizedException(String.format("%d의 githubId를 가진 사용자는 없습니다.", githubId)));
}

public void autoCloseStudies() {
public void autoUpdateStatus() {
final List<Study> studies = studyRepository.findAll();
final LocalDate now = dateTimeSystem.now().toLocalDate();

for (Study study : studies) {
if (study.isNeedToCloseRecruiting(now)) {
study.closeEnrollment();
}
study.changeStatus(now);
}
}
}