Skip to content

Commit

Permalink
feat: 스터디 수료 처리 및 철회 V2 API 구현 (#891)
Browse files Browse the repository at this point in the history
* feat: 스터디 수료 처리 v2 api 구현

* feat: 스터디 수료 처리 철회 v2 api 구현

* rename: validator 이름 수정

* fix: 엔드포인트 수정

* refactor: 검증 로직 순서 변경

* docs: description 수정

* feat: 역할 검증 추가
  • Loading branch information
Sangwook02 authored Feb 12, 2025
1 parent 170deac commit 48b03e1
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.gdschongik.gdsc.domain.studyv2.api;

import com.gdschongik.gdsc.domain.study.dto.request.StudyCompleteRequest;
import com.gdschongik.gdsc.domain.studyv2.application.MentorStudyHistoryServiceV2;
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 V2", description = "멘토 스터디 수강 이력 API입니다.")
@RestController
@RequestMapping("/v2/mentor/study-histories")
@RequiredArgsConstructor
public class MentorStudyHistoryControllerV2 {

private final MentorStudyHistoryServiceV2 mentorStudyHistoryServiceV2;

@Operation(summary = "스터디 수료 처리", description = "스터디 수료 처리하고 스터디 수료 발급쿠폰을 발급합니다.")
@PostMapping("/complete")
public ResponseEntity<Void> completeStudy(@RequestBody StudyCompleteRequest request) {
mentorStudyHistoryServiceV2.completeStudy(request);
return ResponseEntity.ok().build();
}

@Operation(summary = "스터디 수료 철회", description = "스터디 수료 처리를 철회하고 스터디 수료 발급쿠폰을 회수합니다.")
@PostMapping("/withdraw-completion")
public ResponseEntity<Void> withdrawStudyCompletion(@RequestBody StudyCompleteRequest request) {
mentorStudyHistoryServiceV2.withdrawStudyCompletion(request);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.gdschongik.gdsc.domain.studyv2.application;

import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.study.domain.StudyHistoriesCompletedEvent;
import com.gdschongik.gdsc.domain.study.dto.request.StudyCompleteRequest;
import com.gdschongik.gdsc.domain.studyv2.dao.StudyHistoryV2Repository;
import com.gdschongik.gdsc.domain.studyv2.dao.StudyV2Repository;
import com.gdschongik.gdsc.domain.studyv2.domain.StudyHistoryV2;
import com.gdschongik.gdsc.domain.studyv2.domain.StudyHistoryValidatorV2;
import com.gdschongik.gdsc.domain.studyv2.domain.StudyV2;
import com.gdschongik.gdsc.domain.studyv2.domain.StudyValidatorV2;
import com.gdschongik.gdsc.global.exception.CustomException;
import com.gdschongik.gdsc.global.exception.ErrorCode;
import com.gdschongik.gdsc.global.util.MemberUtil;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
public class MentorStudyHistoryServiceV2 {

private final ApplicationEventPublisher applicationEventPublisher;
private final MemberUtil memberUtil;
private final StudyValidatorV2 studyValidatorV2;
private final StudyHistoryValidatorV2 studyHistoryValidatorV2;
private final StudyV2Repository studyV2Repository;
private final StudyHistoryV2Repository studyHistoryV2Repository;

@Transactional
public void completeStudy(StudyCompleteRequest request) {
Member currentMember = memberUtil.getCurrentMember();
StudyV2 study = studyV2Repository
.findById(request.studyId())
.orElseThrow(() -> new CustomException(ErrorCode.STUDY_NOT_FOUND));

studyValidatorV2.validateStudyMentor(currentMember, study);

List<StudyHistoryV2> studyHistories =
studyHistoryV2Repository.findAllByStudyIdAndStudentIds(request.studyId(), request.studentIds());

studyHistoryValidatorV2.validateAppliedToStudy(
studyHistories.size(), request.studentIds().size());

studyHistories.forEach(StudyHistoryV2::complete);

applicationEventPublisher.publishEvent(new StudyHistoriesCompletedEvent(
studyHistories.stream().map(StudyHistoryV2::getId).toList()));

log.info(
"[MentorStudyHistoryServiceV2] 스터디 수료 처리: studyId={}, studentIds={}",
request.studyId(),
request.studentIds());
}

@Transactional
public void withdrawStudyCompletion(StudyCompleteRequest request) {
Member currentMember = memberUtil.getCurrentMember();
StudyV2 study = studyV2Repository
.findById(request.studyId())
.orElseThrow(() -> new CustomException(ErrorCode.STUDY_NOT_FOUND));

studyValidatorV2.validateStudyMentor(currentMember, study);

List<StudyHistoryV2> studyHistories =
studyHistoryV2Repository.findAllByStudyIdAndStudentIds(request.studyId(), request.studentIds());

studyHistoryValidatorV2.validateAppliedToStudy(
studyHistories.size(), request.studentIds().size());

studyHistories.forEach(StudyHistoryV2::withdrawCompletion);

studyHistoryV2Repository.saveAll(studyHistories);

log.info(
"[MentorStudyHistoryServiceV2] 스터디 수료 철회: studyId={}, studentIds={}",
request.studyId(),
request.studentIds());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.gdschongik.gdsc.domain.studyv2.dao;

import com.gdschongik.gdsc.domain.studyv2.domain.StudyHistoryV2;
import java.util.List;

public interface StudyHistoryV2CustomRepository {

List<StudyHistoryV2> findAllByStudyIdAndStudentIds(Long studyId, List<Long> studentIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.gdschongik.gdsc.domain.studyv2.dao;

import static com.gdschongik.gdsc.domain.studyv2.domain.QStudyHistoryV2.*;

import com.gdschongik.gdsc.domain.studyv2.domain.StudyHistoryV2;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class StudyHistoryV2CustomRepositoryImpl implements StudyHistoryV2CustomRepository {

private final JPAQueryFactory queryFactory;

@Override
public List<StudyHistoryV2> findAllByStudyIdAndStudentIds(Long studyId, List<Long> studentIds) {
return queryFactory
.selectFrom(studyHistoryV2)
.where(studyHistoryV2.study.id.eq(studyId), studyHistoryV2.student.id.in(studentIds))
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gdschongik.gdsc.domain.studyv2.dao;

import com.gdschongik.gdsc.domain.studyv2.domain.StudyHistoryV2;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudyHistoryV2Repository extends JpaRepository<StudyHistoryV2, Long>, StudyHistoryV2CustomRepository {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.gdschongik.gdsc.domain.studyv2.domain;

import static com.gdschongik.gdsc.global.exception.ErrorCode.*;

import com.gdschongik.gdsc.global.annotation.DomainService;
import com.gdschongik.gdsc.global.exception.CustomException;

@DomainService
public class StudyHistoryValidatorV2 {

public void validateAppliedToStudy(long countStudyHistory, int studentCount) {
if (countStudyHistory != studentCount) {
throw new CustomException(STUDY_HISTORY_NOT_APPLIED_STUDENT_EXISTS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.gdschongik.gdsc.domain.studyv2.domain;

import static com.gdschongik.gdsc.global.exception.ErrorCode.*;

import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.global.annotation.DomainService;
import com.gdschongik.gdsc.global.exception.CustomException;

@DomainService
public class StudyValidatorV2 {

public void validateStudyMentor(Member currentMember, StudyV2 study) {
// 어드민인 경우 검증 통과
if (currentMember.isAdmin()) {
return;
}

// 멘토인지 검증
if (!currentMember.isMentor()) {
throw new CustomException(STUDY_ACCESS_NOT_ALLOWED);
}

// 해당 스터디의 담당 멘토인지 검증
if (!currentMember.getId().equals(study.getMentor().getId())) {
throw new CustomException(STUDY_MENTOR_INVALID);
}
}
}

0 comments on commit 48b03e1

Please sign in to comment.