From 5fc0d43dada4e251441f9ac1881a7790896700bb Mon Sep 17 00:00:00 2001 From: Cho Sangwook <82208159+Sangwook02@users.noreply.github.com> Date: Tue, 12 Nov 2024 21:21:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=88=98?= =?UTF-8?q?=EB=A3=8C=20=EB=B0=8F=20=EC=B2=A0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#819)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 스터디 수료 및 철회 api 구현 * fix: api 설명 수정 * fix: 로그 수정 * fix: 수강 이력 조회 시 studyId를 이용하도록 수정 * fix: valid 어노테이션 제거 * feat: studyId를 dto에 포함 * fix: post 요청으로 변경 --- .../api/MentorStudyAchievementController.java | 5 +- .../api/MentorStudyHistoryController.java | 35 +++++++++ .../MentorStudyHistoryService.java | 71 +++++++++++++++++++ .../dao/StudyHistoryCustomRepository.java | 3 + .../dao/StudyHistoryCustomRepositoryImpl.java | 9 +++ .../domain/study/domain/StudyHistory.java | 7 ++ .../dto/request/StudyCompleteRequest.java | 5 ++ .../domain/study/domain/StudyHistoryTest.java | 23 ++++++ 8 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyHistoryController.java create mode 100644 src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyHistoryService.java create mode 100644 src/main/java/com/gdschongik/gdsc/domain/study/dto/request/StudyCompleteRequest.java diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyAchievementController.java b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyAchievementController.java index 6b1f26455..a7739b6ad 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyAchievementController.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyAchievementController.java @@ -4,7 +4,6 @@ import com.gdschongik.gdsc.domain.study.dto.request.OutstandingStudentRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -25,7 +24,7 @@ public class MentorStudyAchievementController { @Operation(summary = "우수 스터디원 지정", description = "우수 스터디원으로 지정합니다.") @PostMapping public ResponseEntity designateOutstandingStudent( - @RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) { + @RequestParam(name = "studyId") Long studyId, @RequestBody OutstandingStudentRequest request) { mentorStudyAchievementService.designateOutstandingStudent(studyId, request); return ResponseEntity.ok().build(); } @@ -33,7 +32,7 @@ public ResponseEntity designateOutstandingStudent( @Operation(summary = "우수 스터디원 철회", description = "우수 스터디원 지정을 철회합니다.") @DeleteMapping public ResponseEntity withdrawOutstandingStudent( - @RequestParam(name = "studyId") Long studyId, @Valid @RequestBody OutstandingStudentRequest request) { + @RequestParam(name = "studyId") Long studyId, @RequestBody OutstandingStudentRequest request) { mentorStudyAchievementService.withdrawOutstandingStudent(studyId, request); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyHistoryController.java b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyHistoryController.java new file mode 100644 index 000000000..9a7a5159c --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/api/MentorStudyHistoryController.java @@ -0,0 +1,35 @@ +package com.gdschongik.gdsc.domain.study.api; + +import com.gdschongik.gdsc.domain.study.application.MentorStudyHistoryService; +import com.gdschongik.gdsc.domain.study.dto.request.StudyCompleteRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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; + +@Tag(name = "Mentor Study History", description = "멘토 스터디 수강 이력 API입니다.") +@RestController +@RequestMapping("/mentor/study-history") +@RequiredArgsConstructor +public class MentorStudyHistoryController { + + private final MentorStudyHistoryService mentorStudyHistoryService; + + @Operation(summary = "스터디 수료 처리", description = "스터디 수료 처리합니다.") + @PostMapping("/complete") + public ResponseEntity completeStudy(@RequestBody StudyCompleteRequest request) { + mentorStudyHistoryService.completeStudy(request); + return ResponseEntity.ok().build(); + } + + @Operation(summary = "스터디 수료 철회", description = "스터디 수료 처리를 철회합니다.") + @PostMapping("/withdraw-completion") + public ResponseEntity withdrawStudyCompletion(@RequestBody StudyCompleteRequest request) { + mentorStudyHistoryService.withdrawStudyCompletion(request); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyHistoryService.java b/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyHistoryService.java new file mode 100644 index 000000000..936094a1a --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/application/MentorStudyHistoryService.java @@ -0,0 +1,71 @@ +package com.gdschongik.gdsc.domain.study.application; + +import static com.gdschongik.gdsc.global.exception.ErrorCode.*; + +import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository; +import com.gdschongik.gdsc.domain.study.dao.StudyRepository; +import com.gdschongik.gdsc.domain.study.domain.Study; +import com.gdschongik.gdsc.domain.study.domain.StudyHistory; +import com.gdschongik.gdsc.domain.study.domain.StudyHistoryValidator; +import com.gdschongik.gdsc.domain.study.domain.StudyValidator; +import com.gdschongik.gdsc.domain.study.dto.request.StudyCompleteRequest; +import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.global.util.MemberUtil; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class MentorStudyHistoryService { + + private final MemberUtil memberUtil; + private final StudyValidator studyValidator; + private final StudyHistoryValidator studyHistoryValidator; + private final StudyRepository studyRepository; + private final StudyHistoryRepository studyHistoryRepository; + + @Transactional + public void completeStudy(StudyCompleteRequest request) { + Member currentMember = memberUtil.getCurrentMember(); + Study study = + studyRepository.findById(request.studyId()).orElseThrow(() -> new CustomException(STUDY_NOT_FOUND)); + List studyHistories = + studyHistoryRepository.findAllByStudyIdAndStudentIds(request.studyId(), request.studentIds()); + + studyValidator.validateStudyMentor(currentMember, study); + studyHistoryValidator.validateAppliedToStudy( + studyHistories.size(), request.studentIds().size()); + + studyHistories.forEach(StudyHistory::complete); + + log.info( + "[MentorStudyHistoryService] 스터디 수료 처리: studyId={}, studentIds={}", + request.studyId(), + request.studentIds()); + } + + @Transactional + public void withdrawStudyCompletion(StudyCompleteRequest request) { + Member currentMember = memberUtil.getCurrentMember(); + Study study = + studyRepository.findById(request.studyId()).orElseThrow(() -> new CustomException(STUDY_NOT_FOUND)); + List studyHistories = + studyHistoryRepository.findAllByStudyIdAndStudentIds(request.studyId(), request.studentIds()); + + studyValidator.validateStudyMentor(currentMember, study); + studyHistoryValidator.validateAppliedToStudy( + studyHistories.size(), request.studentIds().size()); + + studyHistories.forEach(StudyHistory::withdrawCompletion); + + log.info( + "[MentorStudyHistoryService] 스터디 수료 철회: studyId={}, studentIds={}", + request.studyId(), + request.studentIds()); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepository.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepository.java index f64f7be6a..97775d02d 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepository.java @@ -1,8 +1,11 @@ package com.gdschongik.gdsc.domain.study.dao; +import com.gdschongik.gdsc.domain.study.domain.StudyHistory; import java.util.List; public interface StudyHistoryCustomRepository { long countByStudyIdAndStudentIds(Long studyId, List studentIds); + + List findAllByStudyIdAndStudentIds(Long studyId, List studentIds); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepositoryImpl.java index 32232e65c..e9e29f4e3 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepositoryImpl.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dao/StudyHistoryCustomRepositoryImpl.java @@ -2,6 +2,7 @@ import static com.gdschongik.gdsc.domain.study.domain.QStudyHistory.*; +import com.gdschongik.gdsc.domain.study.domain.StudyHistory; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; @@ -21,6 +22,14 @@ public long countByStudyIdAndStudentIds(Long studyId, List studentIds) { .fetchOne(); } + @Override + public List findAllByStudyIdAndStudentIds(Long studyId, List studentIds) { + return queryFactory + .selectFrom(studyHistory) + .where(eqStudyId(studyId), studyHistory.student.id.in(studentIds)) + .fetch(); + } + private BooleanExpression eqStudyId(Long studyId) { return studyHistory.study.id.eq(studyId); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java index 9ac737f9d..c15e97274 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java +++ b/src/main/java/com/gdschongik/gdsc/domain/study/domain/StudyHistory.java @@ -78,6 +78,13 @@ public void complete() { studyHistoryStatus = COMPLETED; } + /** + * 스터디 수료 철회 + */ + public void withdrawCompletion() { + studyHistoryStatus = NONE; + } + // 데이터 전달 로직 public boolean isWithinApplicationAndCourse() { return study.isWithinApplicationAndCourse(); diff --git a/src/main/java/com/gdschongik/gdsc/domain/study/dto/request/StudyCompleteRequest.java b/src/main/java/com/gdschongik/gdsc/domain/study/dto/request/StudyCompleteRequest.java new file mode 100644 index 000000000..0bc8d4054 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/study/dto/request/StudyCompleteRequest.java @@ -0,0 +1,5 @@ +package com.gdschongik.gdsc.domain.study.dto.request; + +import java.util.List; + +public record StudyCompleteRequest(Long studyId, List studentIds) {} diff --git a/src/test/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryTest.java b/src/test/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryTest.java index d1af51efe..72d3b3fc4 100644 --- a/src/test/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryTest.java +++ b/src/test/java/com/gdschongik/gdsc/domain/study/domain/StudyHistoryTest.java @@ -55,4 +55,27 @@ class 스터디_수료시 { assertThat(studyHistory.getStudyHistoryStatus()).isEqualTo(COMPLETED); } } + + @Nested + class 스터디_수료_철회시 { + + @Test + void 수료상태는_NONE이다() { + // given + Member student = fixtureHelper.createRegularMember(1L); + Member mentor = fixtureHelper.createRegularMember(2L); + LocalDateTime now = LocalDateTime.now(); + Study study = fixtureHelper.createStudy( + mentor, Period.of(now.plusDays(5), now.plusDays(10)), Period.of(now.minusDays(5), now)); + + StudyHistory studyHistory = StudyHistory.create(student, study); + studyHistory.complete(); + + // when + studyHistory.withdrawCompletion(); + + // then + assertThat(studyHistory.getStudyHistoryStatus()).isEqualTo(NONE); + } + } }