From 9ec6c1268c3baf4b9d5ea23676d81adbecc6d120 Mon Sep 17 00:00:00 2001 From: jaehyuk Date: Thu, 8 Aug 2024 01:59:20 +0900 Subject: [PATCH 1/7] =?UTF-8?q?:sparkles:=20[Feat]=20=EB=AA=B0=EC=9E=85?= =?UTF-8?q?=EB=8F=84=20=EA=B8=B0=EB=A1=9D=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/handler/StudyTimeHandler.java | 10 ++++ .../java/timify/com/member/domain/Member.java | 4 +- .../com/studytime/StudyTimeConverter.java | 33 ++++++++++ .../com/studytime/StudyTimeService.java | 60 +++++++++++++++++++ .../controller/StudyTimeController.java | 33 ++++++++++ .../controller/StudyTimeControllerImpl.java | 43 +++++++++++++ .../com/studytime/domain/StudyTime.java | 51 +++++++++++++++- .../com/studytime/domain/StudyTimeGrade.java | 19 ++++++ .../com/studytime/dto/StudyTimeRequest.java | 23 +++++++ .../com/studytime/dto/StudyTimeResponse.java | 32 ++++++++++ .../timify/com/subject/domain/Subject.java | 4 +- .../java/timify/com/utils/DateTimeUtil.java | 14 +++++ 12 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 src/main/java/timify/com/common/apiPayload/exception/handler/StudyTimeHandler.java create mode 100644 src/main/java/timify/com/studytime/StudyTimeConverter.java create mode 100644 src/main/java/timify/com/studytime/StudyTimeService.java create mode 100644 src/main/java/timify/com/studytime/controller/StudyTimeController.java create mode 100644 src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java create mode 100644 src/main/java/timify/com/studytime/domain/StudyTimeGrade.java create mode 100644 src/main/java/timify/com/studytime/dto/StudyTimeRequest.java create mode 100644 src/main/java/timify/com/studytime/dto/StudyTimeResponse.java diff --git a/src/main/java/timify/com/common/apiPayload/exception/handler/StudyTimeHandler.java b/src/main/java/timify/com/common/apiPayload/exception/handler/StudyTimeHandler.java new file mode 100644 index 0000000..c346124 --- /dev/null +++ b/src/main/java/timify/com/common/apiPayload/exception/handler/StudyTimeHandler.java @@ -0,0 +1,10 @@ +package timify.com.common.apiPayload.exception.handler; + +import timify.com.common.apiPayload.code.BaseErrorCode; +import timify.com.common.apiPayload.exception.GeneralException; + +public class StudyTimeHandler extends GeneralException { + public StudyTimeHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/timify/com/member/domain/Member.java b/src/main/java/timify/com/member/domain/Member.java index e14dab3..581d0ec 100644 --- a/src/main/java/timify/com/member/domain/Member.java +++ b/src/main/java/timify/com/member/domain/Member.java @@ -19,13 +19,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import timify.com.domain.MemberMission; +import timify.com.studytime.domain.StudyTime; +import timify.com.todo.domain.Todo; import timify.com.domain.common.BaseDateTimeEntity; import timify.com.study.domain.StudyMethod; import timify.com.study.domain.StudyPlace; import timify.com.study.domain.StudyType; -import timify.com.studytime.domain.StudyTime; import timify.com.subject.domain.Subject; -import timify.com.todo.domain.Todo; @Entity @Getter diff --git a/src/main/java/timify/com/studytime/StudyTimeConverter.java b/src/main/java/timify/com/studytime/StudyTimeConverter.java new file mode 100644 index 0000000..91f7518 --- /dev/null +++ b/src/main/java/timify/com/studytime/StudyTimeConverter.java @@ -0,0 +1,33 @@ +package timify.com.studytime; + +import static timify.com.utils.DateTimeUtil.stringToLocalTime; + +import java.time.Duration; +import timify.com.studytime.domain.StudyTime; +import timify.com.studytime.dto.StudyTimeRequest.studyTimeRequest; +import timify.com.studytime.dto.StudyTimeResponse; + +public class StudyTimeConverter { + + public static StudyTime toStudyTime(studyTimeRequest request, double temp) { + + return StudyTime.builder() + .startTime(stringToLocalTime(request.getStartTime())) + .endTime(stringToLocalTime(request.getEndTime())) + .grade(request.getGrade()) + .temp(temp) + .build(); + + } + + public static StudyTimeResponse.studyTimeDto toStudyTimeDto(StudyTime studyTime) { + return StudyTimeResponse.studyTimeDto.builder() + .studyTimeId(studyTime.getId()) + .startTime(studyTime.getStartTime()) + .endTime(studyTime.getEndTime()) + .totalTime((int) Duration.between(studyTime.getStartTime(), studyTime.getEndTime()).toMinutes()) + .temp(studyTime.getTemp()) + .build(); + } + +} diff --git a/src/main/java/timify/com/studytime/StudyTimeService.java b/src/main/java/timify/com/studytime/StudyTimeService.java new file mode 100644 index 0000000..be81735 --- /dev/null +++ b/src/main/java/timify/com/studytime/StudyTimeService.java @@ -0,0 +1,60 @@ +package timify.com.studytime; + +import static timify.com.common.apiPayload.code.status.ErrorStatus.STUDY_TYPE_NOT_FOUND; +import static timify.com.utils.DateTimeUtil.stringToLocalTime; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import timify.com.common.apiPayload.exception.handler.StudyTimeHandler; +import timify.com.member.domain.Member; +import timify.com.studytime.domain.StudyTime; +import timify.com.studytime.dto.StudyTimeRequest.studyTimeRequest; +import timify.com.studytime.repository.StudyTimeRepository; +import timify.com.todo.domain.Todo; +import timify.com.todo.repository.TodoRepository; + +@Service +@RequiredArgsConstructor +public class StudyTimeService { + + private final static double DEFAULT_TEMP = 50d; + private final StudyTimeRepository studyTimeRepository; + private final TodoRepository todoRepository; + + @Transactional + public StudyTime startTodo(Member member, Long todoId, studyTimeRequest request) { + + Todo todo = todoRepository.findById(todoId) + .orElseThrow(() -> new StudyTimeHandler(STUDY_TYPE_NOT_FOUND)); + + double temp = calculateTemp(request); + + StudyTime studyTime = StudyTimeConverter.toStudyTime(request, temp); + + studyTime.associateMember(member); + studyTime.associateSubject(todo.getSubject()); + studyTime.associateTodo(todo); + + return studyTimeRepository.save(studyTime); + } + + private double calculateTemp(studyTimeRequest request) { + LocalDateTime startTime = stringToLocalTime(request.getStartTime()); + LocalDateTime endTime = stringToLocalTime(request.getEndTime()); + + long countStudyTime = studyTimeRepository.count(); + + if (countStudyTime == 0) { + int minutes = (int) Duration.between(startTime, endTime).toMinutes(); + return DEFAULT_TEMP + minutes * request.getGrade().getScore(); + } + + int minutes = (int) Duration.between(startTime, endTime).toMinutes(); + return minutes * request.getGrade().getScore(); + } +} diff --git a/src/main/java/timify/com/studytime/controller/StudyTimeController.java b/src/main/java/timify/com/studytime/controller/StudyTimeController.java new file mode 100644 index 0000000..e4b228c --- /dev/null +++ b/src/main/java/timify/com/studytime/controller/StudyTimeController.java @@ -0,0 +1,33 @@ +package timify.com.studytime.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import timify.com.auth.annotation.AuthMember; +import timify.com.common.apiPayload.ApiResponse; +import timify.com.member.domain.Member; +import timify.com.studytime.dto.StudyTimeRequest.studyTimeRequest; +import timify.com.studytime.dto.StudyTimeResponse.studyTimeDto; + +@Tag(name = "StudyTime", description = "StudyTime 관련 API") +public interface StudyTimeController { + + @Operation(summary = "할 일 스톱워치 API", description = "할 일 스톱워치 API 입니다.") + @Parameters(value = { + @Parameter(name = "todoId", description = "몰입도를 기록할 할 일에 해당하는 todoId 을 입력해 주세요.") + }) + ApiResponse startTodo(@AuthMember Member member, + @PathVariable Long todoId, + @RequestBody studyTimeRequest request); + + @Operation(summary = "시간 기록 삭제 API", description = "시간 기록 삭제 API 입니다.") + @Parameters(value = { + @Parameter(name = "studyTimeId", description = "기록을 삭제할 StudyTimeId 을 입력해 주세요.") + }) + ApiResponse deleteStudyTime(@AuthMember Member member, + @PathVariable Long studyTimeId); + +} diff --git a/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java b/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java new file mode 100644 index 0000000..be10f2c --- /dev/null +++ b/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java @@ -0,0 +1,43 @@ +package timify.com.studytime.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import timify.com.auth.annotation.AuthMember; +import timify.com.common.apiPayload.ApiResponse; +import timify.com.common.apiPayload.code.status.SuccessStatus; +import timify.com.member.domain.Member; +import timify.com.studytime.StudyTimeConverter; +import timify.com.studytime.StudyTimeService; +import timify.com.studytime.domain.StudyTime; +import timify.com.studytime.dto.StudyTimeRequest.studyTimeRequest; +import timify.com.studytime.dto.StudyTimeResponse.studyTimeDto; + +@RestController +@RequiredArgsConstructor +@Slf4j +@RequestMapping("/v1/subject/todo/{todoId}") +public class StudyTimeControllerImpl implements StudyTimeController{ + + private final StudyTimeService studyTimeService; + + @PostMapping("/study_time/start") + public ApiResponse startTodo(@AuthMember Member member, + @PathVariable Long todoId, + @RequestBody studyTimeRequest request) { + + StudyTime studyTime = studyTimeService.startTodo(member, todoId, request); + + return ApiResponse.onSuccess(StudyTimeConverter.toStudyTimeDto(studyTime)); + } + + @DeleteMapping("/study_time/delete") + public ApiResponse deleteStudyTime(Member member, Long studyTimeId) { + return null; + } +} diff --git a/src/main/java/timify/com/studytime/domain/StudyTime.java b/src/main/java/timify/com/studytime/domain/StudyTime.java index ad85301..8ec35d7 100644 --- a/src/main/java/timify/com/studytime/domain/StudyTime.java +++ b/src/main/java/timify/com/studytime/domain/StudyTime.java @@ -38,7 +38,7 @@ public class StudyTime extends BaseDateTimeEntity { private LocalDateTime endTime; @Column(nullable = false) - private double score; + private StudyTimeGrade grade; // 최상 @Column(nullable = false) private double temp; @@ -54,4 +54,53 @@ public class StudyTime extends BaseDateTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "todo_id", nullable = false) private Todo todo; + + public void associateMember(Member member) { + if (this.member != null) { + this.member.getStudyTimeList().remove(this); + } + this.member = member; + this.member.getStudyTimeList().add(this); + } + + public void disassociateMember(Member member) { + if (member != null) { + member.getStudyTimeList().remove(this); + this.member = null; + } + } + + public void associateSubject(Subject subject) { + if (this.subject != null) { + this.subject.getStudyTimeList().remove(this); + } + this.subject = subject; + this.subject.getStudyTimeList().add(this); + } + + // 연관관계 해제 메소드 + public void disassociateSubject(Subject subject) { + if (subject != null) { + subject.getStudyTimeList().remove(this); + this.subject = null; + } + } + + public void associateTodo(Todo todo) { + if (this.todo != null) { + this.todo.getStudyTimeList().remove(this); + } + this.todo = todo; + this.todo.getStudyTimeList().add(this); + } + + // 연관관계 해제 메소드 + public void disassociateTodo(Todo todo) { + if (todo != null) { + todo.getStudyTimeList().remove(this); + this.todo = null; + } + } + + } diff --git a/src/main/java/timify/com/studytime/domain/StudyTimeGrade.java b/src/main/java/timify/com/studytime/domain/StudyTimeGrade.java new file mode 100644 index 0000000..e49cdb8 --- /dev/null +++ b/src/main/java/timify/com/studytime/domain/StudyTimeGrade.java @@ -0,0 +1,19 @@ +package timify.com.studytime.domain; + +import lombok.Getter; + +@Getter +public enum StudyTimeGrade { + EXCELLENT(2.0), // 최상 + GOOD(1.5), // 상 + AVERAGE(1.2), // 중 + BELOW_AVERAGE(1.0), // 하 + POOR(1.0); // 최하 + + private double score; + + StudyTimeGrade(double score) { + this.score = score; + } + +} diff --git a/src/main/java/timify/com/studytime/dto/StudyTimeRequest.java b/src/main/java/timify/com/studytime/dto/StudyTimeRequest.java new file mode 100644 index 0000000..a1c7bc5 --- /dev/null +++ b/src/main/java/timify/com/studytime/dto/StudyTimeRequest.java @@ -0,0 +1,23 @@ +package timify.com.studytime.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; +import timify.com.studytime.domain.StudyTimeGrade; + +public class StudyTimeRequest { + + @Getter + @NoArgsConstructor + public static class studyTimeRequest { + + @NotBlank + String startTime; + + @NotBlank + String endTime; + + @NotBlank + StudyTimeGrade grade; //최상 + } +} diff --git a/src/main/java/timify/com/studytime/dto/StudyTimeResponse.java b/src/main/java/timify/com/studytime/dto/StudyTimeResponse.java new file mode 100644 index 0000000..5c0c19e --- /dev/null +++ b/src/main/java/timify/com/studytime/dto/StudyTimeResponse.java @@ -0,0 +1,32 @@ +package timify.com.studytime.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import timify.com.studytime.domain.StudyTimeGrade; + +public class StudyTimeResponse { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class studyTimeDto { + + Long studyTimeId; + + LocalDateTime startTime; + + LocalDateTime endTime; + + int totalTime; + + double temp; + + StudyTimeGrade score; + + Double status; + } +} diff --git a/src/main/java/timify/com/subject/domain/Subject.java b/src/main/java/timify/com/subject/domain/Subject.java index 0f8a692..127243e 100644 --- a/src/main/java/timify/com/subject/domain/Subject.java +++ b/src/main/java/timify/com/subject/domain/Subject.java @@ -19,10 +19,10 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import timify.com.domain.common.BaseDateTimeEntity; -import timify.com.member.domain.Member; import timify.com.studytime.domain.StudyTime; import timify.com.todo.domain.Todo; +import timify.com.domain.common.BaseDateTimeEntity; +import timify.com.member.domain.Member; @Entity @Getter diff --git a/src/main/java/timify/com/utils/DateTimeUtil.java b/src/main/java/timify/com/utils/DateTimeUtil.java index c09bc44..ef0f572 100644 --- a/src/main/java/timify/com/utils/DateTimeUtil.java +++ b/src/main/java/timify/com/utils/DateTimeUtil.java @@ -1,6 +1,8 @@ package timify.com.utils; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class DateTimeUtil { @@ -16,4 +18,16 @@ public static LocalDate stringToLocalDate(String dateString) { return LocalDate.parse(dateString, formatter); } + /** + * HHMMSS 형식의 string을 LocalTime type으로 변환 + * + * @param timeString + * @return + */ + public static LocalDateTime stringToLocalTime(String timeString) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss"); + LocalTime localTime = LocalTime.parse(timeString, formatter); + return LocalDateTime.of(LocalDate.now(), localTime); + } + } From f9762f1b8fab9cbc1ee5a2ca48ce7dec3cb346d8 Mon Sep 17 00:00:00 2001 From: jaehyuk Date: Thu, 8 Aug 2024 02:09:21 +0900 Subject: [PATCH 2/7] =?UTF-8?q?:recycle:=20[Refactor]=20Todo=20Entity,=20n?= =?UTF-8?q?ullable=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/timify/com/todo/domain/Todo.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/timify/com/todo/domain/Todo.java b/src/main/java/timify/com/todo/domain/Todo.java index 6eb0f9a..3af8f80 100644 --- a/src/main/java/timify/com/todo/domain/Todo.java +++ b/src/main/java/timify/com/todo/domain/Todo.java @@ -59,15 +59,15 @@ public class Todo extends BaseDateTimeEntity { private Subject subject; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "study_type_id", nullable = false) + @JoinColumn(name = "study_type_id") private StudyType studyType; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "study_method_id", nullable = false) + @JoinColumn(name = "study_method_id") private StudyMethod studyMethod; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "study_place_id", nullable = false) + @JoinColumn(name = "study_place_id") private StudyPlace studyPlace; // studyTime 양방향 매핑 From 0443e0867427f810eca24482846f01874968f99c Mon Sep 17 00:00:00 2001 From: jaehyuk Date: Mon, 12 Aug 2024 20:38:17 +0900 Subject: [PATCH 3/7] =?UTF-8?q?:sparkles:=20[Feat]=20=EB=AA=B0=EC=9E=85?= =?UTF-8?q?=EB=8F=84=20=EA=B8=B0=EB=A1=9D=20=EC=82=AD=EC=A0=9C=20+=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20+=20=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/code/status/ErrorStatus.java | 10 +- .../com/studytime/StudyTimeConverter.java | 1 + .../com/studytime/StudyTimeService.java | 200 ++++++++++++++++-- .../controller/StudyTimeController.java | 23 +- .../controller/StudyTimeControllerImpl.java | 54 ++++- .../com/studytime/domain/StudyTime.java | 21 ++ .../com/studytime/dto/StudyTimeRequest.java | 2 + .../com/studytime/dto/StudyTimeResponse.java | 4 +- .../repository/StudyTimeRepository.java | 11 + .../java/timify/com/todo/TodoConverter.java | 14 +- .../java/timify/com/todo/TodoService.java | 1 - .../java/timify/com/todo/dto/TodoRequest.java | 2 + .../java/timify/com/utils/DateTimeUtil.java | 12 +- 13 files changed, 312 insertions(+), 43 deletions(-) diff --git a/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java b/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java index da12c01..498adcd 100644 --- a/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java @@ -66,7 +66,15 @@ public enum ErrorStatus implements BaseErrorCode { // 할 일 관련 NO_TODO_FOUND(HttpStatus.NOT_FOUND, "TODO4040", "해당 할 일을 찾지 못하였습니다."), MOVED_SUBJECT_RESTRICTION(HttpStatus.BAD_REQUEST, "TODO4040", "보관함에 이동된 항목은 삭제 및 조회만 가능합니다."), - NOT_TODO_OWNER(HttpStatus.BAD_REQUEST, "TODO4009", "해당 회원의 할 일이 아닙니다."); + NOT_TODO_OWNER(HttpStatus.BAD_REQUEST, "TODO4009", "해당 회원의 할 일이 아닙니다."), + + // 몰입도 관련 + + + NOT_STUDY_TIME_OWNER(HttpStatus.BAD_REQUEST, "STUDY_TIME4001", "해당 회원의 몰입도 기록이 아닙니다."), + OVERLAP_STUDY_TIME(HttpStatus.BAD_REQUEST, "STUDY_TIME4002", "겹치는 기록 시간이 존재합니다."), + NOT_POSSIBLE_STUDY_TIME(HttpStatus.BAD_REQUEST, "STUDY_TIME4003", "시간 설정이 올바르지 않습니다."), + NO_STUDY_TIME_FOUND(HttpStatus.NOT_FOUND, "STUDY_TIME4040", "해당 몰입도 기록을 찾지 못하였습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/timify/com/studytime/StudyTimeConverter.java b/src/main/java/timify/com/studytime/StudyTimeConverter.java index 91f7518..289ed7a 100644 --- a/src/main/java/timify/com/studytime/StudyTimeConverter.java +++ b/src/main/java/timify/com/studytime/StudyTimeConverter.java @@ -27,6 +27,7 @@ public static StudyTimeResponse.studyTimeDto toStudyTimeDto(StudyTime studyTime) .endTime(studyTime.getEndTime()) .totalTime((int) Duration.between(studyTime.getStartTime(), studyTime.getEndTime()).toMinutes()) .temp(studyTime.getTemp()) + .grade(studyTime.getGrade()) .build(); } diff --git a/src/main/java/timify/com/studytime/StudyTimeService.java b/src/main/java/timify/com/studytime/StudyTimeService.java index be81735..301b332 100644 --- a/src/main/java/timify/com/studytime/StudyTimeService.java +++ b/src/main/java/timify/com/studytime/StudyTimeService.java @@ -1,21 +1,33 @@ package timify.com.studytime; -import static timify.com.common.apiPayload.code.status.ErrorStatus.STUDY_TYPE_NOT_FOUND; +import static timify.com.common.apiPayload.code.status.ErrorStatus.NOT_POSSIBLE_STUDY_TIME; +import static timify.com.common.apiPayload.code.status.ErrorStatus.NOT_STUDY_TIME_OWNER; +import static timify.com.common.apiPayload.code.status.ErrorStatus.NOT_TODO_OWNER; +import static timify.com.common.apiPayload.code.status.ErrorStatus.NO_TODO_FOUND; +import static timify.com.common.apiPayload.code.status.ErrorStatus.OVERLAP_STUDY_TIME; import static timify.com.utils.DateTimeUtil.stringToLocalTime; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import timify.com.common.apiPayload.exception.handler.StudyHandler; import timify.com.common.apiPayload.exception.handler.StudyTimeHandler; +import timify.com.common.apiPayload.exception.handler.TodoHandler; import timify.com.member.domain.Member; import timify.com.studytime.domain.StudyTime; +import timify.com.studytime.domain.StudyTimeGrade; import timify.com.studytime.dto.StudyTimeRequest.studyTimeRequest; import timify.com.studytime.repository.StudyTimeRepository; +import timify.com.todo.TodoConverter; import timify.com.todo.domain.Todo; +import timify.com.todo.dto.TodoRequest.todoRequest; import timify.com.todo.repository.TodoRepository; @Service @@ -27,34 +39,194 @@ public class StudyTimeService { private final TodoRepository todoRepository; @Transactional - public StudyTime startTodo(Member member, Long todoId, studyTimeRequest request) { + public List recordStudyTime(Member member, Long todoId, studyTimeRequest request) { + Todo todo = validateTodoOwner(member, todoId); - Todo todo = todoRepository.findById(todoId) - .orElseThrow(() -> new StudyTimeHandler(STUDY_TYPE_NOT_FOUND)); + LocalDateTime startTime = stringToLocalTime(request.getStartTime()); + LocalDateTime endTime = stringToLocalTime(request.getEndTime()); + + // 중복되는 시간 확인 + List overlappingTimes = studyTimeRepository.findByTodoAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual( + todo, endTime, startTime); + if (!overlappingTimes.isEmpty()) { + throw new StudyTimeHandler(OVERLAP_STUDY_TIME); + } + + List studyTimes = new ArrayList<>(); + + // 4 AM 기준 시간 설정 + LocalTime boundaryTime = LocalTime.of(4, 0); + LocalDateTime boundaryDateTime = LocalDateTime.of(startTime.toLocalDate(), boundaryTime); + + if (endTime.isBefore(startTime)) { + throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); + } + + // 4 AM 넘는 경우 + if (isCrossingBoundary(startTime, endTime, boundaryDateTime)) { + studyTimes.add(saveStudyTimePart(member, todo, startTime, boundaryDateTime, request)); + studyTimes.add( + saveStudyTimePartForNextDay(member, todo, boundaryDateTime, endTime, request)); + return studyTimes; + } + + studyTimes.add(saveStudyTime(member, todo, startTime, endTime, request)); + return studyTimes; + } + + @Transactional + public void deleteStudyTime(Member member, Long studyTimeId) { + + StudyTime studyTime = validateStudyTimeOwner(member, studyTimeId); + + studyTime.disassociateMember(member); + studyTime.disassociateSubject(studyTime.getTodo().getSubject()); + studyTime.disassociateTodo(studyTime.getTodo()); + studyTimeRepository.delete(studyTime); + + if (studyTimeRepository.countByTodo(studyTime.getTodo()) == 1) { + StudyTime firstStudyTime = studyTimeRepository.findByTodoOrderByStartTimeAsc( + studyTime.getTodo()).get(0); + double newTemp = firstStudyTime.getTemp() + DEFAULT_TEMP; + firstStudyTime.updateTemp(newTemp); + } + } + + @Transactional(readOnly = true) + public List getStudyTimes(Member member, Long todoId) { + return studyTimeRepository.findByMemberAndTodoId(member, todoId); + } + + @Transactional + public StudyTime updateStudyTime(Member member, Long studyTimeId, studyTimeRequest request) { + + StudyTime studyTime = validateStudyTimeOwner(member, studyTimeId); + + LocalDateTime startTime = stringToLocalTime(request.getStartTime()); + LocalDateTime endTime = stringToLocalTime(request.getEndTime()); + + if (endTime.isBefore(startTime)) { + throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); + } + + studyTime.updateStartTime(startTime); + studyTime.updateEndTime(endTime); + studyTime.updateGrade(request.getGrade()); + studyTime.updateTemp(updateTemp(studyTime, studyTime.getTodo(), startTime, endTime, + request.getGrade())); + + return studyTime; + } + + private double updateTemp(StudyTime studyTime, Todo todo, LocalDateTime startTime, + LocalDateTime endTime, StudyTimeGrade grade) { + + int minutes = (int) Duration.between(startTime, endTime).toMinutes(); - double temp = calculateTemp(request); + List studyTimes = studyTimeRepository.findByTodoOrderByStartTimeAsc(todo); - StudyTime studyTime = StudyTimeConverter.toStudyTime(request, temp); + boolean isFirstStudyTime = studyTimes.get(0).getId().equals(studyTime.getId()); + if (isFirstStudyTime) { + return DEFAULT_TEMP + (minutes * grade.getScore()); + } + + return minutes * grade.getScore(); + } + + private boolean isCrossingBoundary(LocalDateTime startTime, LocalDateTime endTime, + LocalDateTime boundaryDateTime) { + return endTime.isAfter(boundaryDateTime) && startTime.isBefore(boundaryDateTime); + } + + private StudyTime saveStudyTimePart(Member member, Todo todo, LocalDateTime startTime, + LocalDateTime boundaryDateTime, studyTimeRequest request) { + StudyTime studyTime = StudyTimeConverter.toStudyTime( + new studyTimeRequest(request.getStartTime(), + boundaryDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), + request.getGrade()), + calculateTemp(todo, startTime, boundaryDateTime, request.getGrade())); studyTime.associateMember(member); studyTime.associateSubject(todo.getSubject()); studyTime.associateTodo(todo); + return studyTimeRepository.save(studyTime); + } + + private StudyTime saveStudyTimePartForNextDay(Member member, Todo todo, + LocalDateTime boundaryDateTime, LocalDateTime endTime, studyTimeRequest request) { + Todo nextDayTodo = findOrCreateNextDayTodo(todo, + boundaryDateTime.toLocalDate().plusDays(1)); + StudyTime studyTime = StudyTimeConverter.toStudyTime(new studyTimeRequest( + boundaryDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), + request.getEndTime(), request.getGrade()), + calculateTemp(nextDayTodo, boundaryDateTime, endTime, request.getGrade())); + + studyTime.associateMember(member); + studyTime.associateSubject(nextDayTodo.getSubject()); + studyTime.associateTodo(nextDayTodo); return studyTimeRepository.save(studyTime); } - private double calculateTemp(studyTimeRequest request) { - LocalDateTime startTime = stringToLocalTime(request.getStartTime()); - LocalDateTime endTime = stringToLocalTime(request.getEndTime()); + private StudyTime saveStudyTime(Member member, Todo todo, LocalDateTime startTime, + LocalDateTime endTime, studyTimeRequest request) { + StudyTime studyTime = StudyTimeConverter.toStudyTime(request, + calculateTemp(todo, startTime, endTime, request.getGrade())); + + studyTime.associateMember(member); + studyTime.associateSubject(todo.getSubject()); + studyTime.associateTodo(todo); + return studyTimeRepository.save(studyTime); + } + + private Todo findOrCreateNextDayTodo(Todo todo, LocalDate nextDayDate) { + return todoRepository.findByMemberAndSubjectId(todo.getMember(), todo.getSubject().getId()) + .stream().filter(existingTodo -> existingTodo.getDate().equals(nextDayDate)).findFirst() + .orElseGet(() -> { + Todo newTodo = TodoConverter.toTodo(new todoRequest(todo.getContent(), nextDayDate, + todo.getStudyType() != null ? todo.getStudyType().getId() : null, + todo.getStudyMethod() != null ? todo.getStudyMethod().getId() : null, + todo.getStudyPlace() != null ? todo.getStudyPlace().getId() : null), + todo.getStudyType(), todo.getStudyMethod(), todo.getStudyPlace()); - long countStudyTime = studyTimeRepository.count(); + newTodo.associateMember(todo.getMember()); + newTodo.associateSubject(todo.getSubject()); + return todoRepository.save(newTodo); + }); + } + + private double calculateTemp(Todo todo, LocalDateTime startTime, LocalDateTime endTime, + StudyTimeGrade grade) { + + int minutes = (int) Duration.between(startTime, endTime).toMinutes(); + long countStudyTime = studyTimeRepository.countByTodo(todo); if (countStudyTime == 0) { - int minutes = (int) Duration.between(startTime, endTime).toMinutes(); - return DEFAULT_TEMP + minutes * request.getGrade().getScore(); + return DEFAULT_TEMP + (minutes * grade.getScore()); } - int minutes = (int) Duration.between(startTime, endTime).toMinutes(); - return minutes * request.getGrade().getScore(); + return minutes * grade.getScore(); + } + + private StudyTime validateStudyTimeOwner(Member member, Long studyTimeId) { + StudyTime studyTime = studyTimeRepository.findById(studyTimeId) + .orElseThrow(() -> new StudyTimeHandler(NOT_STUDY_TIME_OWNER)); + + if (!studyTime.getMember().equals(member)) { + throw new StudyHandler(NOT_TODO_OWNER); + } + + return studyTime; + } + + private Todo validateTodoOwner(Member member, Long todoId) { + Todo todo = todoRepository.findById(todoId) + .orElseThrow(() -> new TodoHandler(NO_TODO_FOUND)); + + if (!todo.getMember().equals(member)) { + throw new TodoHandler(NOT_TODO_OWNER); + } + + return todo; } } diff --git a/src/main/java/timify/com/studytime/controller/StudyTimeController.java b/src/main/java/timify/com/studytime/controller/StudyTimeController.java index e4b228c..ccc57b7 100644 --- a/src/main/java/timify/com/studytime/controller/StudyTimeController.java +++ b/src/main/java/timify/com/studytime/controller/StudyTimeController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import timify.com.auth.annotation.AuthMember; @@ -15,11 +16,12 @@ @Tag(name = "StudyTime", description = "StudyTime 관련 API") public interface StudyTimeController { - @Operation(summary = "할 일 스톱워치 API", description = "할 일 스톱워치 API 입니다.") + @Operation(summary = "몰입시간 기록 API", description = "몰입시간 기록하는 API 입니다. " + + "등록할 시간은 yyyyMMddHHmmss 형식, Grade에는 EXCELLENT, GOOD, AVERAGE, BELOW_AVERAGE, POOR 중 하나를 입력해 주세요. )") @Parameters(value = { @Parameter(name = "todoId", description = "몰입도를 기록할 할 일에 해당하는 todoId 을 입력해 주세요.") }) - ApiResponse startTodo(@AuthMember Member member, + ApiResponse> recordStudyTime(@AuthMember Member member, @PathVariable Long todoId, @RequestBody studyTimeRequest request); @@ -27,7 +29,22 @@ ApiResponse startTodo(@AuthMember Member member, @Parameters(value = { @Parameter(name = "studyTimeId", description = "기록을 삭제할 StudyTimeId 을 입력해 주세요.") }) - ApiResponse deleteStudyTime(@AuthMember Member member, + ApiResponse deleteStudyTime(@AuthMember Member member, @PathVariable Long studyTimeId); + @Operation(summary = "몰입 시간 조회 API", description = "몰입 시간을 조회하는 API 입니다.") + @Parameters(value = { + @Parameter(name = "todoId", description = "몰입도를 기록할 할 일에 해당하는 todoId 을 입력해 주세요.") + }) + ApiResponse> getStudyTimes(@AuthMember Member member, + @PathVariable Long todoId); + + @Operation(summary = "몰입 시간 수정 API", description = "몰입 시간을 수정하는 API 입니다.") + @Parameters(value = { + @Parameter(name = "studyTimeId", description = "몰입 시간을 수정할 studyTimeId 을 입력해 주세요.") + }) + ApiResponse updateStudyTime(@AuthMember Member member, + @PathVariable Long studyTimeId, + @RequestBody studyTimeRequest request); + } diff --git a/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java b/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java index be10f2c..3347336 100644 --- a/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java +++ b/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java @@ -1,8 +1,15 @@ package timify.com.studytime.controller; +import static timify.com.studytime.dto.StudyTimeResponse.*; + +import com.sun.net.httpserver.Authenticator.Success; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -16,28 +23,59 @@ import timify.com.studytime.StudyTimeService; import timify.com.studytime.domain.StudyTime; import timify.com.studytime.dto.StudyTimeRequest.studyTimeRequest; +import timify.com.studytime.dto.StudyTimeResponse; import timify.com.studytime.dto.StudyTimeResponse.studyTimeDto; @RestController @RequiredArgsConstructor @Slf4j -@RequestMapping("/v1/subject/todo/{todoId}") +@RequestMapping("/v1/subject/todo") public class StudyTimeControllerImpl implements StudyTimeController{ private final StudyTimeService studyTimeService; - @PostMapping("/study_time/start") - public ApiResponse startTodo(@AuthMember Member member, + @PostMapping("/{todoId}/study/record") + public ApiResponse> recordStudyTime(@AuthMember Member member, @PathVariable Long todoId, @RequestBody studyTimeRequest request) { - StudyTime studyTime = studyTimeService.startTodo(member, todoId, request); + List dtoList = studyTimeService.recordStudyTime(member, todoId, request); - return ApiResponse.onSuccess(StudyTimeConverter.toStudyTimeDto(studyTime)); + List studyTimes = dtoList.stream() + .map(StudyTimeConverter::toStudyTimeDto) + .collect(Collectors.toList()); + + return ApiResponse.onSuccess(studyTimes); + } + + @DeleteMapping("/study/{studyTimeId}/delete") + public ApiResponse deleteStudyTime(@AuthMember Member member, + @PathVariable Long studyTimeId) { + + studyTimeService.deleteStudyTime(member, studyTimeId); + return ApiResponse.onSuccess("몰입 기록 삭제 성공"); } - @DeleteMapping("/study_time/delete") - public ApiResponse deleteStudyTime(Member member, Long studyTimeId) { - return null; + @GetMapping("/{todoId}") + public ApiResponse> getStudyTimes(@AuthMember Member member, + @PathVariable Long todoId) { + + List studyTimes = studyTimeService.getStudyTimes(member, todoId); + List dtoList = studyTimes.stream() + .map(StudyTimeConverter::toStudyTimeDto) + .collect(Collectors.toList()); + + return ApiResponse.onSuccess(dtoList); } + + @PatchMapping("/study/{studyTimeId}/update") + public ApiResponse updateStudyTime(@AuthMember Member member, + @PathVariable Long studyTimeId, + @RequestBody studyTimeRequest request) { + + StudyTime studyTime = studyTimeService.updateStudyTime(member, studyTimeId, request); + return ApiResponse.onSuccess(StudyTimeConverter.toStudyTimeDto(studyTime)); + } + + } diff --git a/src/main/java/timify/com/studytime/domain/StudyTime.java b/src/main/java/timify/com/studytime/domain/StudyTime.java index 8ec35d7..11ccdc9 100644 --- a/src/main/java/timify/com/studytime/domain/StudyTime.java +++ b/src/main/java/timify/com/studytime/domain/StudyTime.java @@ -2,12 +2,15 @@ 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; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotBlank; import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -38,6 +41,7 @@ public class StudyTime extends BaseDateTimeEntity { private LocalDateTime endTime; @Column(nullable = false) + @Enumerated(EnumType.STRING) private StudyTimeGrade grade; // 최상 @Column(nullable = false) @@ -55,6 +59,22 @@ public class StudyTime extends BaseDateTimeEntity { @JoinColumn(name = "todo_id", nullable = false) private Todo todo; + public void updateStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public void updateEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public void updateGrade(StudyTimeGrade grade) { + this.grade = grade; + } + + public void updateTemp(double temp) { + this.temp = temp; + } + public void associateMember(Member member) { if (this.member != null) { this.member.getStudyTimeList().remove(this); @@ -91,6 +111,7 @@ public void associateTodo(Todo todo) { this.todo.getStudyTimeList().remove(this); } this.todo = todo; + System.out.println(this.todo.getStudyTimeList()); this.todo.getStudyTimeList().add(this); } diff --git a/src/main/java/timify/com/studytime/dto/StudyTimeRequest.java b/src/main/java/timify/com/studytime/dto/StudyTimeRequest.java index a1c7bc5..d56288c 100644 --- a/src/main/java/timify/com/studytime/dto/StudyTimeRequest.java +++ b/src/main/java/timify/com/studytime/dto/StudyTimeRequest.java @@ -1,6 +1,7 @@ package timify.com.studytime.dto; import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import timify.com.studytime.domain.StudyTimeGrade; @@ -9,6 +10,7 @@ public class StudyTimeRequest { @Getter @NoArgsConstructor + @AllArgsConstructor public static class studyTimeRequest { @NotBlank diff --git a/src/main/java/timify/com/studytime/dto/StudyTimeResponse.java b/src/main/java/timify/com/studytime/dto/StudyTimeResponse.java index 5c0c19e..40adcae 100644 --- a/src/main/java/timify/com/studytime/dto/StudyTimeResponse.java +++ b/src/main/java/timify/com/studytime/dto/StudyTimeResponse.java @@ -25,8 +25,6 @@ public static class studyTimeDto { double temp; - StudyTimeGrade score; - - Double status; + StudyTimeGrade grade; } } diff --git a/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java b/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java index 7d49e3e..025bea9 100644 --- a/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java +++ b/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java @@ -1,11 +1,15 @@ package timify.com.studytime.repository; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import timify.com.member.domain.Member; import timify.com.studytime.domain.StudyTime; +import timify.com.todo.domain.Todo; public interface StudyTimeRepository extends JpaRepository { @@ -17,4 +21,11 @@ public interface StudyTimeRepository extends JpaRepository { List findAllByTodoDateAndMember(@Param("date") LocalDate date, @Param("memberId") Long memberId); + List findByMemberAndTodoId(Member member, Long todoId); + + long countByTodo(Todo todo); + + List findByTodoOrderByStartTimeAsc(Todo todo); + + List findByTodoAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(Todo todo, LocalDateTime endTime, LocalDateTime startTime); } diff --git a/src/main/java/timify/com/todo/TodoConverter.java b/src/main/java/timify/com/todo/TodoConverter.java index b90c313..0b5e666 100644 --- a/src/main/java/timify/com/todo/TodoConverter.java +++ b/src/main/java/timify/com/todo/TodoConverter.java @@ -1,5 +1,6 @@ package timify.com.todo; +import java.util.ArrayList; import timify.com.study.domain.StudyMethod; import timify.com.study.domain.StudyPlace; import timify.com.study.domain.StudyType; @@ -19,6 +20,7 @@ public static Todo toTodo(todoRequest request, StudyType studyType, StudyMethod .studyType(studyType) .studyMethod(studyMethod) .studyPlace(studyPlace) + .studyTimeList(new ArrayList<>()) // 리스트 초기화 .build(); } @@ -30,12 +32,12 @@ public static TodoResponse.todoDto toTodoDto(Todo todo) { .date(todo.getDate()) .status(todo.getStatus()) .subjectId(todo.getSubject().getId()) - .studyTypeId(todo.getStudyType().getId()) - .studyTypeTitle(todo.getStudyType().getTitle()) - .studyMethodId(todo.getStudyMethod().getId()) - .studyMethodTitle(todo.getStudyMethod().getTitle()) - .studyPlaceId(todo.getStudyPlace().getId()) - .studyPlaceTitle(todo.getStudyPlace().getTitle()) + .studyTypeId(todo.getStudyType() != null ? todo.getStudyType().getId() : null) + .studyTypeTitle(todo.getStudyType() != null ? todo.getStudyType().getTitle() : null) + .studyMethodId(todo.getStudyMethod() != null ? todo.getStudyMethod().getId() : null) + .studyMethodTitle(todo.getStudyMethod() != null ? todo.getStudyMethod().getTitle() : null) + .studyPlaceId(todo.getStudyPlace() != null ? todo.getStudyPlace().getId() : null) + .studyPlaceTitle(todo.getStudyPlace() != null ? todo.getStudyPlace().getTitle() : null) .time(0) .temp(0) .build(); diff --git a/src/main/java/timify/com/todo/TodoService.java b/src/main/java/timify/com/todo/TodoService.java index 1331d37..5368c6f 100644 --- a/src/main/java/timify/com/todo/TodoService.java +++ b/src/main/java/timify/com/todo/TodoService.java @@ -8,7 +8,6 @@ import static timify.com.common.apiPayload.code.status.ErrorStatus.STUDY_TYPE_NOT_FOUND; import static timify.com.todo.dto.TodoRequest.todoRequest; -import jakarta.validation.Valid; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/timify/com/todo/dto/TodoRequest.java b/src/main/java/timify/com/todo/dto/TodoRequest.java index a0fe35a..5f52bfe 100644 --- a/src/main/java/timify/com/todo/dto/TodoRequest.java +++ b/src/main/java/timify/com/todo/dto/TodoRequest.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.util.List; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.cglib.core.Local; @@ -12,6 +13,7 @@ public class TodoRequest { @Getter + @AllArgsConstructor @NoArgsConstructor public static class todoRequest { diff --git a/src/main/java/timify/com/utils/DateTimeUtil.java b/src/main/java/timify/com/utils/DateTimeUtil.java index ef0f572..9e08953 100644 --- a/src/main/java/timify/com/utils/DateTimeUtil.java +++ b/src/main/java/timify/com/utils/DateTimeUtil.java @@ -19,15 +19,13 @@ public static LocalDate stringToLocalDate(String dateString) { } /** - * HHMMSS 형식의 string을 LocalTime type으로 변환 + * yyyyMMddHHmmss 형식의 string을 LocalTime type으로 변환 * - * @param timeString + * @param dateTimeString * @return */ - public static LocalDateTime stringToLocalTime(String timeString) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss"); - LocalTime localTime = LocalTime.parse(timeString, formatter); - return LocalDateTime.of(LocalDate.now(), localTime); + public static LocalDateTime stringToLocalTime(String dateTimeString) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + return LocalDateTime.parse(dateTimeString, formatter); } - } From 6c29465edcdec6efc7d62f01e95160a57a534f98 Mon Sep 17 00:00:00 2001 From: jaehyuk Date: Tue, 13 Aug 2024 00:47:08 +0900 Subject: [PATCH 4/7] =?UTF-8?q?:sparkles:=20[Feat]=20=ED=95=AD=EB=AA=A9=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/timify/com/todo/TodoService.java | 22 +++++++++++++++++++ .../com/todo/controller/TodoController.java | 9 ++++++++ .../todo/controller/TodoControllerImpl.java | 9 ++++++++ .../java/timify/com/todo/domain/Todo.java | 4 ++++ 4 files changed, 44 insertions(+) diff --git a/src/main/java/timify/com/todo/TodoService.java b/src/main/java/timify/com/todo/TodoService.java index 5368c6f..445e725 100644 --- a/src/main/java/timify/com/todo/TodoService.java +++ b/src/main/java/timify/com/todo/TodoService.java @@ -10,6 +10,7 @@ import java.time.LocalDate; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -29,6 +30,7 @@ import timify.com.subject.domain.SubjectStatus; import timify.com.subject.repository.SubjectRepository; import timify.com.todo.domain.Todo; +import timify.com.todo.domain.TodoStatus; import timify.com.todo.dto.TodoRequest.copyTodoRequest; import timify.com.todo.repository.TodoRepository; @@ -120,6 +122,24 @@ public List copyTodo(Member member, Long todoId, copyTodoRequest request) return copiedTodos; } + @Transactional + public Todo updateStatus(Member member, Long todoId, String status) { + Todo todo = validateTodoOwner(member, todoId); + + TodoStatus newStatus = Arrays.stream(TodoStatus.values()) + .filter(s -> s.name().equalsIgnoreCase(status)) + .findFirst() + .orElseThrow(() -> new TodoHandler(ErrorStatus.NOT_CHANGE_STATUS)); + + if(todo.getStatus() == newStatus) { + throw new TodoHandler(ErrorStatus.NOT_CHANGE_STATUS); + } + + todo.updateStatus(newStatus); + + return todo; + } + private void validateSubjectIsActive(Subject subject) { if (subject.getStatus() != SubjectStatus.ACTIVE) { throw new TodoHandler(ErrorStatus.MOVED_SUBJECT_RESTRICTION); @@ -193,4 +213,6 @@ private Todo validateTodoOwner(Member member, Long todoId) { } return todo; } + + } diff --git a/src/main/java/timify/com/todo/controller/TodoController.java b/src/main/java/timify/com/todo/controller/TodoController.java index 27f66d5..fd036a5 100644 --- a/src/main/java/timify/com/todo/controller/TodoController.java +++ b/src/main/java/timify/com/todo/controller/TodoController.java @@ -67,4 +67,13 @@ ApiResponse> copyTodo( @AuthMember Member member, @PathVariable(name = "todoId") Long todoId, @RequestBody @Valid copyTodoRequest request); + + @Operation(summary = "할 일 상태 변경 API", description = "할 일 상태를 변경하는 API 입니다.") + @Parameters(value = { + @Parameter(name = "todoId", description = "복사할 할 일의 todoId를 입력해 주세요.") + }) + ApiResponse updateTodoStatus( + @AuthMember Member member, + @PathVariable(name = "todoId") Long todoId, + @RequestParam(name = "status") String status); } diff --git a/src/main/java/timify/com/todo/controller/TodoControllerImpl.java b/src/main/java/timify/com/todo/controller/TodoControllerImpl.java index 6c9a24e..d4c3a21 100644 --- a/src/main/java/timify/com/todo/controller/TodoControllerImpl.java +++ b/src/main/java/timify/com/todo/controller/TodoControllerImpl.java @@ -87,4 +87,13 @@ public ApiResponse> copyTodo(@AuthMember Member member, return ApiResponse.of(SuccessStatus._OK, copyTodoList); } + + @PatchMapping("/todo/{todoId}/update") + public ApiResponse updateTodoStatus(@AuthMember Member member, + @PathVariable Long todoId, + @RequestParam(name = "status") String status) { + + Todo todo = todoService.updateStatus(member, todoId, status); + return ApiResponse.onSuccess(TodoConverter.toTodoDto(todo)); + } } diff --git a/src/main/java/timify/com/todo/domain/Todo.java b/src/main/java/timify/com/todo/domain/Todo.java index 3af8f80..20d1f08 100644 --- a/src/main/java/timify/com/todo/domain/Todo.java +++ b/src/main/java/timify/com/todo/domain/Todo.java @@ -127,4 +127,8 @@ public void updateStudyMethod(StudyMethod studyMethod) { public void updateStudyPlace(StudyPlace studyPlace) { this.studyPlace = studyPlace; } + + public void updateStatus(TodoStatus newStatus) { + this.status = newStatus; + } } From 913723066668f78c07818dde176668e08cb096a8 Mon Sep 17 00:00:00 2001 From: jaehyuk Date: Wed, 14 Aug 2024 01:47:50 +0900 Subject: [PATCH 5/7] =?UTF-8?q?:sparkles:=20[Feat]=20=EB=AA=B0=EC=9E=85?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=EC=A1=B0=EA=B1=B4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/studytime/StudyTimeService.java | 127 ++++++++++-------- .../controller/StudyTimeController.java | 6 +- .../controller/StudyTimeControllerImpl.java | 12 +- .../repository/StudyTimeRepository.java | 4 +- .../java/timify/com/todo/TodoConverter.java | 12 +- .../com/todo/controller/TodoController.java | 2 +- .../todo/controller/TodoControllerImpl.java | 2 +- .../java/timify/com/todo/dto/TodoRequest.java | 1 - .../java/timify/com/utils/DateTimeUtil.java | 4 +- 9 files changed, 97 insertions(+), 73 deletions(-) diff --git a/src/main/java/timify/com/studytime/StudyTimeService.java b/src/main/java/timify/com/studytime/StudyTimeService.java index 301b332..c064e8b 100644 --- a/src/main/java/timify/com/studytime/StudyTimeService.java +++ b/src/main/java/timify/com/studytime/StudyTimeService.java @@ -25,9 +25,7 @@ import timify.com.studytime.domain.StudyTimeGrade; import timify.com.studytime.dto.StudyTimeRequest.studyTimeRequest; import timify.com.studytime.repository.StudyTimeRepository; -import timify.com.todo.TodoConverter; import timify.com.todo.domain.Todo; -import timify.com.todo.dto.TodoRequest.todoRequest; import timify.com.todo.repository.TodoRepository; @Service @@ -45,9 +43,12 @@ public List recordStudyTime(Member member, Long todoId, studyTimeRequ LocalDateTime startTime = stringToLocalTime(request.getStartTime()); LocalDateTime endTime = stringToLocalTime(request.getEndTime()); - // 중복되는 시간 확인 - List overlappingTimes = studyTimeRepository.findByTodoAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual( - todo, endTime, startTime); + validateStudyTimeRange(todo, startTime, endTime); + + List overlappingTimes = studyTimeRepository + .findByMemberAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(member, endTime, + startTime); + if (!overlappingTimes.isEmpty()) { throw new StudyTimeHandler(OVERLAP_STUDY_TIME); } @@ -58,7 +59,7 @@ public List recordStudyTime(Member member, Long todoId, studyTimeRequ LocalTime boundaryTime = LocalTime.of(4, 0); LocalDateTime boundaryDateTime = LocalDateTime.of(startTime.toLocalDate(), boundaryTime); - if (endTime.isBefore(startTime)) { + if (isInvalidStudyTime(startTime, endTime)) { throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); } @@ -79,17 +80,17 @@ public void deleteStudyTime(Member member, Long studyTimeId) { StudyTime studyTime = validateStudyTimeOwner(member, studyTimeId); - studyTime.disassociateMember(member); - studyTime.disassociateSubject(studyTime.getTodo().getSubject()); - studyTime.disassociateTodo(studyTime.getTodo()); - studyTimeRepository.delete(studyTime); - - if (studyTimeRepository.countByTodo(studyTime.getTodo()) == 1) { + if (studyTime.getTodo().getStudyTimeList().size() == 1) { StudyTime firstStudyTime = studyTimeRepository.findByTodoOrderByStartTimeAsc( studyTime.getTodo()).get(0); double newTemp = firstStudyTime.getTemp() + DEFAULT_TEMP; firstStudyTime.updateTemp(newTemp); } + + studyTime.disassociateMember(member); + studyTime.disassociateSubject(studyTime.getTodo().getSubject()); + studyTime.disassociateTodo(studyTime.getTodo()); + studyTimeRepository.delete(studyTime); } @Transactional(readOnly = true) @@ -98,40 +99,64 @@ public List getStudyTimes(Member member, Long todoId) { } @Transactional - public StudyTime updateStudyTime(Member member, Long studyTimeId, studyTimeRequest request) { + public List updateStudyTime(Member member, Long studyTimeId, + studyTimeRequest request) { StudyTime studyTime = validateStudyTimeOwner(member, studyTimeId); LocalDateTime startTime = stringToLocalTime(request.getStartTime()); LocalDateTime endTime = stringToLocalTime(request.getEndTime()); - if (endTime.isBefore(startTime)) { - throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); + validateStudyTimeRange(studyTime.getTodo(), startTime, endTime); + + List overlappingTimes = studyTimeRepository + .findByMemberAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(member, endTime, + startTime); + + if (!overlappingTimes.isEmpty()) { + throw new StudyTimeHandler(OVERLAP_STUDY_TIME); } - studyTime.updateStartTime(startTime); - studyTime.updateEndTime(endTime); - studyTime.updateGrade(request.getGrade()); - studyTime.updateTemp(updateTemp(studyTime, studyTime.getTodo(), startTime, endTime, - request.getGrade())); + List studyTimes = new ArrayList<>(); - return studyTime; - } + // 4 AM 기준 + LocalTime boundaryTime = LocalTime.of(4, 0); + LocalDateTime boundaryDateTime = LocalDateTime.of(startTime.toLocalDate(), boundaryTime); - private double updateTemp(StudyTime studyTime, Todo todo, LocalDateTime startTime, - LocalDateTime endTime, StudyTimeGrade grade) { + if (isInvalidStudyTime(startTime, endTime)) { + throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); + } - int minutes = (int) Duration.between(startTime, endTime).toMinutes(); + // 4 AM 넘는 경우 + if (isCrossingBoundary(startTime, endTime, boundaryDateTime)) { + studyTimes.add( + saveStudyTimePart(member, studyTime.getTodo(), startTime, boundaryDateTime, + request)); + studyTimes.add( + saveStudyTimePartForNextDay(member, studyTime.getTodo(), boundaryDateTime, endTime, + request)); + return studyTimes; + } + + studyTimes.add(saveStudyTime(member, studyTime.getTodo(), startTime, endTime, request)); + return studyTimes; + } - List studyTimes = studyTimeRepository.findByTodoOrderByStartTimeAsc(todo); + private void validateStudyTimeRange(Todo todo, LocalDateTime startTime, LocalDateTime endTime) { + LocalDate todoDate = todo.getDate(); - boolean isFirstStudyTime = studyTimes.get(0).getId().equals(studyTime.getId()); + LocalDateTime startOfDay = LocalDateTime.of(todoDate.minusDays(1), LocalTime.of(4, 0)); + LocalDateTime endOfDay = LocalDateTime.of(todoDate.plusDays(1), LocalTime.of(4, 0)); - if (isFirstStudyTime) { - return DEFAULT_TEMP + (minutes * grade.getScore()); + if (startTime.isBefore(startOfDay) || endTime.isAfter(endOfDay)) { + throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); } + } - return minutes * grade.getScore(); + private boolean isInvalidStudyTime(LocalDateTime startTime, LocalDateTime endTime) { + LocalDateTime now = LocalDateTime.now(); + + return endTime.isBefore(startTime) && (startTime.isAfter(now) || endTime.isAfter(now)); } private boolean isCrossingBoundary(LocalDateTime startTime, LocalDateTime endTime, @@ -143,7 +168,7 @@ private StudyTime saveStudyTimePart(Member member, Todo todo, LocalDateTime star LocalDateTime boundaryDateTime, studyTimeRequest request) { StudyTime studyTime = StudyTimeConverter.toStudyTime( new studyTimeRequest(request.getStartTime(), - boundaryDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), + boundaryDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")), request.getGrade()), calculateTemp(todo, startTime, boundaryDateTime, request.getGrade())); studyTime.associateMember(member); @@ -155,12 +180,24 @@ private StudyTime saveStudyTimePart(Member member, Todo todo, LocalDateTime star private StudyTime saveStudyTimePartForNextDay(Member member, Todo todo, LocalDateTime boundaryDateTime, LocalDateTime endTime, studyTimeRequest request) { - Todo nextDayTodo = findOrCreateNextDayTodo(todo, - boundaryDateTime.toLocalDate().plusDays(1)); - StudyTime studyTime = StudyTimeConverter.toStudyTime(new studyTimeRequest( - boundaryDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), + Todo nextDayTodo = Todo.builder() + .content(todo.getContent()) + .date(boundaryDateTime.toLocalDate().plusDays(1)) + .studyType(todo.getStudyType()) + .studyMethod(todo.getStudyMethod()) + .studyPlace(todo.getStudyPlace()) + .build(); + + nextDayTodo.associateMember(todo.getMember()); + nextDayTodo.associateSubject(todo.getSubject()); + nextDayTodo = todoRepository.save(nextDayTodo); + + StudyTime studyTime = StudyTimeConverter.toStudyTime( + new studyTimeRequest( + boundaryDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")), request.getEndTime(), request.getGrade()), - calculateTemp(nextDayTodo, boundaryDateTime, endTime, request.getGrade())); + calculateTemp(nextDayTodo, boundaryDateTime, endTime, request.getGrade()) + ); studyTime.associateMember(member); studyTime.associateSubject(nextDayTodo.getSubject()); @@ -179,27 +216,11 @@ private StudyTime saveStudyTime(Member member, Todo todo, LocalDateTime startTim return studyTimeRepository.save(studyTime); } - private Todo findOrCreateNextDayTodo(Todo todo, LocalDate nextDayDate) { - return todoRepository.findByMemberAndSubjectId(todo.getMember(), todo.getSubject().getId()) - .stream().filter(existingTodo -> existingTodo.getDate().equals(nextDayDate)).findFirst() - .orElseGet(() -> { - Todo newTodo = TodoConverter.toTodo(new todoRequest(todo.getContent(), nextDayDate, - todo.getStudyType() != null ? todo.getStudyType().getId() : null, - todo.getStudyMethod() != null ? todo.getStudyMethod().getId() : null, - todo.getStudyPlace() != null ? todo.getStudyPlace().getId() : null), - todo.getStudyType(), todo.getStudyMethod(), todo.getStudyPlace()); - - newTodo.associateMember(todo.getMember()); - newTodo.associateSubject(todo.getSubject()); - return todoRepository.save(newTodo); - }); - } - private double calculateTemp(Todo todo, LocalDateTime startTime, LocalDateTime endTime, StudyTimeGrade grade) { int minutes = (int) Duration.between(startTime, endTime).toMinutes(); - long countStudyTime = studyTimeRepository.countByTodo(todo); + long countStudyTime = todo.getStudyTimeList().size(); if (countStudyTime == 0) { return DEFAULT_TEMP + (minutes * grade.getScore()); diff --git a/src/main/java/timify/com/studytime/controller/StudyTimeController.java b/src/main/java/timify/com/studytime/controller/StudyTimeController.java index ccc57b7..013f977 100644 --- a/src/main/java/timify/com/studytime/controller/StudyTimeController.java +++ b/src/main/java/timify/com/studytime/controller/StudyTimeController.java @@ -17,7 +17,7 @@ public interface StudyTimeController { @Operation(summary = "몰입시간 기록 API", description = "몰입시간 기록하는 API 입니다. " - + "등록할 시간은 yyyyMMddHHmmss 형식, Grade에는 EXCELLENT, GOOD, AVERAGE, BELOW_AVERAGE, POOR 중 하나를 입력해 주세요. )") + + "등록할 시간은 yyyyMMddHHmm 형식, Grade에는 EXCELLENT, GOOD, AVERAGE, BELOW_AVERAGE, POOR 중 하나를 입력해 주세요. )") @Parameters(value = { @Parameter(name = "todoId", description = "몰입도를 기록할 할 일에 해당하는 todoId 을 입력해 주세요.") }) @@ -34,7 +34,7 @@ ApiResponse deleteStudyTime(@AuthMember Member member, @Operation(summary = "몰입 시간 조회 API", description = "몰입 시간을 조회하는 API 입니다.") @Parameters(value = { - @Parameter(name = "todoId", description = "몰입도를 기록할 할 일에 해당하는 todoId 을 입력해 주세요.") + @Parameter(name = "todoId", description = "시간 기록을 조회할 할 일에 해당하는 todoId 을 입력해 주세요.") }) ApiResponse> getStudyTimes(@AuthMember Member member, @PathVariable Long todoId); @@ -43,7 +43,7 @@ ApiResponse> getStudyTimes(@AuthMember Member member, @Parameters(value = { @Parameter(name = "studyTimeId", description = "몰입 시간을 수정할 studyTimeId 을 입력해 주세요.") }) - ApiResponse updateStudyTime(@AuthMember Member member, + ApiResponse> updateStudyTime(@AuthMember Member member, @PathVariable Long studyTimeId, @RequestBody studyTimeRequest request); diff --git a/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java b/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java index 3347336..bf0fee1 100644 --- a/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java +++ b/src/main/java/timify/com/studytime/controller/StudyTimeControllerImpl.java @@ -68,13 +68,17 @@ public ApiResponse> getStudyTimes(@AuthMember Member member, return ApiResponse.onSuccess(dtoList); } - @PatchMapping("/study/{studyTimeId}/update") - public ApiResponse updateStudyTime(@AuthMember Member member, + @PatchMapping("/study/{studyTimeId}/update-status") + public ApiResponse> updateStudyTime(@AuthMember Member member, @PathVariable Long studyTimeId, @RequestBody studyTimeRequest request) { - StudyTime studyTime = studyTimeService.updateStudyTime(member, studyTimeId, request); - return ApiResponse.onSuccess(StudyTimeConverter.toStudyTimeDto(studyTime)); + List dtoList = studyTimeService.updateStudyTime(member, studyTimeId, request); + List studyTimes = dtoList.stream() + .map(StudyTimeConverter::toStudyTimeDto) + .collect(Collectors.toList()); + + return ApiResponse.onSuccess(studyTimes); } diff --git a/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java b/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java index 025bea9..99c0ac4 100644 --- a/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java +++ b/src/main/java/timify/com/studytime/repository/StudyTimeRepository.java @@ -23,9 +23,9 @@ List findAllByTodoDateAndMember(@Param("date") LocalDate date, List findByMemberAndTodoId(Member member, Long todoId); - long countByTodo(Todo todo); List findByTodoOrderByStartTimeAsc(Todo todo); - List findByTodoAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(Todo todo, LocalDateTime endTime, LocalDateTime startTime); + List findByMemberAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(Member member, LocalDateTime endTime, + LocalDateTime startTime); } diff --git a/src/main/java/timify/com/todo/TodoConverter.java b/src/main/java/timify/com/todo/TodoConverter.java index 024cfe2..8c32c9e 100644 --- a/src/main/java/timify/com/todo/TodoConverter.java +++ b/src/main/java/timify/com/todo/TodoConverter.java @@ -52,12 +52,12 @@ public static TodoResponse.todoDto toTodoDto(Todo todo, int totalTime, double to .date(todo.getDate()) .status(todo.getStatus()) .subjectId(todo.getSubject().getId()) - .studyTypeId(todo.getStudyType().getId()) - .studyTypeTitle(todo.getStudyType().getTitle()) - .studyMethodId(todo.getStudyMethod().getId()) - .studyMethodTitle(todo.getStudyMethod().getTitle()) - .studyPlaceId(todo.getStudyPlace().getId()) - .studyPlaceTitle(todo.getStudyPlace().getTitle()) + .studyTypeId(todo.getStudyType() != null ? todo.getStudyType().getId() : null) + .studyTypeTitle(todo.getStudyType() != null ? todo.getStudyType().getTitle() : null) + .studyMethodId(todo.getStudyMethod() != null ? todo.getStudyMethod().getId() : null) + .studyMethodTitle(todo.getStudyMethod() != null ? todo.getStudyMethod().getTitle() : null) + .studyPlaceId(todo.getStudyPlace() != null ? todo.getStudyPlace().getId() : null) + .studyPlaceTitle(todo.getStudyPlace() != null ? todo.getStudyPlace().getTitle() : null) .time(totalTime) .temp(totalTemp) .build(); diff --git a/src/main/java/timify/com/todo/controller/TodoController.java b/src/main/java/timify/com/todo/controller/TodoController.java index 97e621f..a11abf2 100644 --- a/src/main/java/timify/com/todo/controller/TodoController.java +++ b/src/main/java/timify/com/todo/controller/TodoController.java @@ -71,7 +71,7 @@ ApiResponse> copyTodo( @PathVariable(name = "todoId") Long todoId, @RequestBody @Valid copyTodoRequest request); - @Operation(summary = "할 일 상태 변경 API", description = "할 일 상태를 변경하는 API 입니다.") + @Operation(summary = "할 일 상태 변경 API", description = "할 일 상태를 변경하는 API 입니다. status -> NOT_STARTED(시작 전), STOP(멈춤), NOT_COMPLETED(미완료), COMPLETED(완료)") @Parameters(value = { @Parameter(name = "todoId", description = "복사할 할 일의 todoId를 입력해 주세요.") }) diff --git a/src/main/java/timify/com/todo/controller/TodoControllerImpl.java b/src/main/java/timify/com/todo/controller/TodoControllerImpl.java index 8ecf2b5..1e297bb 100644 --- a/src/main/java/timify/com/todo/controller/TodoControllerImpl.java +++ b/src/main/java/timify/com/todo/controller/TodoControllerImpl.java @@ -85,7 +85,7 @@ public ApiResponse> copyTodo(@AuthMember Member member, return ApiResponse.of(SuccessStatus._OK, copyTodoList); } - @PatchMapping("/todo/{todoId}/update") + @PatchMapping("/todo/{todoId}/update-status") public ApiResponse updateTodoStatus(@AuthMember Member member, @PathVariable Long todoId, @RequestParam(name = "status") String status) { diff --git a/src/main/java/timify/com/todo/dto/TodoRequest.java b/src/main/java/timify/com/todo/dto/TodoRequest.java index 5f52bfe..2571227 100644 --- a/src/main/java/timify/com/todo/dto/TodoRequest.java +++ b/src/main/java/timify/com/todo/dto/TodoRequest.java @@ -13,7 +13,6 @@ public class TodoRequest { @Getter - @AllArgsConstructor @NoArgsConstructor public static class todoRequest { diff --git a/src/main/java/timify/com/utils/DateTimeUtil.java b/src/main/java/timify/com/utils/DateTimeUtil.java index 9e08953..844738b 100644 --- a/src/main/java/timify/com/utils/DateTimeUtil.java +++ b/src/main/java/timify/com/utils/DateTimeUtil.java @@ -19,13 +19,13 @@ public static LocalDate stringToLocalDate(String dateString) { } /** - * yyyyMMddHHmmss 형식의 string을 LocalTime type으로 변환 + * yyyyMMddHHmm 형식의 string을 LocalTime type으로 변환 * * @param dateTimeString * @return */ public static LocalDateTime stringToLocalTime(String dateTimeString) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm"); return LocalDateTime.parse(dateTimeString, formatter); } } From 5bedb26307e49f746b97becdf82aad8033c7dc9d Mon Sep 17 00:00:00 2001 From: jaehyuk Date: Wed, 14 Aug 2024 19:58:56 +0900 Subject: [PATCH 6/7] =?UTF-8?q?:sparkles:=20[Feat]=20=EB=AA=B0=EC=9E=85?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EA=B8=B0=EB=A1=9D=20=EC=8B=9C,=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/studytime/StudyTimeService.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/timify/com/studytime/StudyTimeService.java b/src/main/java/timify/com/studytime/StudyTimeService.java index c064e8b..c488968 100644 --- a/src/main/java/timify/com/studytime/StudyTimeService.java +++ b/src/main/java/timify/com/studytime/StudyTimeService.java @@ -45,6 +45,10 @@ public List recordStudyTime(Member member, Long todoId, studyTimeRequ validateStudyTimeRange(todo, startTime, endTime); +// if (isInvalidStudyTime(startTime, endTime)) { +// throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); +// } + List overlappingTimes = studyTimeRepository .findByMemberAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(member, endTime, startTime); @@ -59,10 +63,6 @@ public List recordStudyTime(Member member, Long todoId, studyTimeRequ LocalTime boundaryTime = LocalTime.of(4, 0); LocalDateTime boundaryDateTime = LocalDateTime.of(startTime.toLocalDate(), boundaryTime); - if (isInvalidStudyTime(startTime, endTime)) { - throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); - } - // 4 AM 넘는 경우 if (isCrossingBoundary(startTime, endTime, boundaryDateTime)) { studyTimes.add(saveStudyTimePart(member, todo, startTime, boundaryDateTime, request)); @@ -109,6 +109,12 @@ public List updateStudyTime(Member member, Long studyTimeId, validateStudyTimeRange(studyTime.getTodo(), startTime, endTime); + if (isInvalidStudyTime(startTime, endTime)) { + throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); + } + + studyTimeRepository.delete(studyTime); + List overlappingTimes = studyTimeRepository .findByMemberAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(member, endTime, startTime); @@ -123,10 +129,6 @@ public List updateStudyTime(Member member, Long studyTimeId, LocalTime boundaryTime = LocalTime.of(4, 0); LocalDateTime boundaryDateTime = LocalDateTime.of(startTime.toLocalDate(), boundaryTime); - if (isInvalidStudyTime(startTime, endTime)) { - throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); - } - // 4 AM 넘는 경우 if (isCrossingBoundary(startTime, endTime, boundaryDateTime)) { studyTimes.add( @@ -145,8 +147,8 @@ public List updateStudyTime(Member member, Long studyTimeId, private void validateStudyTimeRange(Todo todo, LocalDateTime startTime, LocalDateTime endTime) { LocalDate todoDate = todo.getDate(); - LocalDateTime startOfDay = LocalDateTime.of(todoDate.minusDays(1), LocalTime.of(4, 0)); - LocalDateTime endOfDay = LocalDateTime.of(todoDate.plusDays(1), LocalTime.of(4, 0)); + LocalDateTime startOfDay = LocalDateTime.of(todoDate, LocalTime.of(4, 0)); + LocalDateTime endOfDay = LocalDateTime.of(todoDate.plusDays(2), LocalTime.of(4, 0)); if (startTime.isBefore(startOfDay) || endTime.isAfter(endOfDay)) { throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); @@ -156,7 +158,7 @@ private void validateStudyTimeRange(Todo todo, LocalDateTime startTime, LocalDat private boolean isInvalidStudyTime(LocalDateTime startTime, LocalDateTime endTime) { LocalDateTime now = LocalDateTime.now(); - return endTime.isBefore(startTime) && (startTime.isAfter(now) || endTime.isAfter(now)); + return endTime.isBefore(startTime) || (startTime.isAfter(now) || endTime.isAfter(now)); } private boolean isCrossingBoundary(LocalDateTime startTime, LocalDateTime endTime, @@ -182,10 +184,12 @@ private StudyTime saveStudyTimePartForNextDay(Member member, Todo todo, Todo nextDayTodo = Todo.builder() .content(todo.getContent()) - .date(boundaryDateTime.toLocalDate().plusDays(1)) + .date(boundaryDateTime.toLocalDate()) .studyType(todo.getStudyType()) .studyMethod(todo.getStudyMethod()) .studyPlace(todo.getStudyPlace()) + .status(todo.getStatus()) + .studyTimeList(new ArrayList<>()) .build(); nextDayTodo.associateMember(todo.getMember()); From cd41679f981de86dfbb3a6bb5a79ba54c298dea0 Mon Sep 17 00:00:00 2001 From: jaehyuk Date: Wed, 14 Aug 2024 21:20:12 +0900 Subject: [PATCH 7/7] =?UTF-8?q?:sparkles:=20[Feat]=20=EC=B5=9C=EB=8C=80=20?= =?UTF-8?q?=EB=AA=B0=EC=9E=85=EC=8B=9C=EA=B0=84=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/code/status/ErrorStatus.java | 3 +-- .../timify/com/studytime/StudyTimeService.java | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java b/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java index 498adcd..5c51651 100644 --- a/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/timify/com/common/apiPayload/code/status/ErrorStatus.java @@ -69,11 +69,10 @@ public enum ErrorStatus implements BaseErrorCode { NOT_TODO_OWNER(HttpStatus.BAD_REQUEST, "TODO4009", "해당 회원의 할 일이 아닙니다."), // 몰입도 관련 - - NOT_STUDY_TIME_OWNER(HttpStatus.BAD_REQUEST, "STUDY_TIME4001", "해당 회원의 몰입도 기록이 아닙니다."), OVERLAP_STUDY_TIME(HttpStatus.BAD_REQUEST, "STUDY_TIME4002", "겹치는 기록 시간이 존재합니다."), NOT_POSSIBLE_STUDY_TIME(HttpStatus.BAD_REQUEST, "STUDY_TIME4003", "시간 설정이 올바르지 않습니다."), + OVER_24H_STUDY_TIME(HttpStatus.BAD_REQUEST, "STUDY_TIME4004", "최대 24시간까지 기록할 수 있습니다."), NO_STUDY_TIME_FOUND(HttpStatus.NOT_FOUND, "STUDY_TIME4040", "해당 몰입도 기록을 찾지 못하였습니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/timify/com/studytime/StudyTimeService.java b/src/main/java/timify/com/studytime/StudyTimeService.java index c488968..cd9bf16 100644 --- a/src/main/java/timify/com/studytime/StudyTimeService.java +++ b/src/main/java/timify/com/studytime/StudyTimeService.java @@ -5,6 +5,7 @@ import static timify.com.common.apiPayload.code.status.ErrorStatus.NOT_TODO_OWNER; import static timify.com.common.apiPayload.code.status.ErrorStatus.NO_TODO_FOUND; import static timify.com.common.apiPayload.code.status.ErrorStatus.OVERLAP_STUDY_TIME; +import static timify.com.common.apiPayload.code.status.ErrorStatus.OVER_24H_STUDY_TIME; import static timify.com.utils.DateTimeUtil.stringToLocalTime; import java.time.Duration; @@ -45,9 +46,13 @@ public List recordStudyTime(Member member, Long todoId, studyTimeRequ validateStudyTimeRange(todo, startTime, endTime); -// if (isInvalidStudyTime(startTime, endTime)) { -// throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); -// } + if (isInvalidStudyTime(startTime, endTime)) { + throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); + } + + if (Duration.between(startTime, endTime).toHours() > 24) { + throw new StudyTimeHandler(OVER_24H_STUDY_TIME); + } List overlappingTimes = studyTimeRepository .findByMemberAndStartTimeLessThanEqualAndEndTimeGreaterThanEqual(member, endTime, @@ -113,6 +118,10 @@ public List updateStudyTime(Member member, Long studyTimeId, throw new StudyTimeHandler(NOT_POSSIBLE_STUDY_TIME); } + if (Duration.between(startTime, endTime).toHours() > 24) { + throw new StudyTimeHandler(OVER_24H_STUDY_TIME); + } + studyTimeRepository.delete(studyTime); List overlappingTimes = studyTimeRepository