diff --git a/back/src/main/java/com/back/domain/mentoring/mentoring/service/MentoringStorage.java b/back/src/main/java/com/back/domain/mentoring/mentoring/service/MentoringStorage.java index 29d08298..0ebfcc9c 100644 --- a/back/src/main/java/com/back/domain/mentoring/mentoring/service/MentoringStorage.java +++ b/back/src/main/java/com/back/domain/mentoring/mentoring/service/MentoringStorage.java @@ -63,21 +63,10 @@ public boolean hasReservationsForMentoring(Long mentoringId) { return reservationRepository.existsByMentoringId(mentoringId); } - public boolean hasMentorSlotsForMentor(Long mentorId) { - return mentorSlotRepository.existsByMentorId(mentorId); - } - public boolean hasReservationForMentorSlot(Long slotId) { return reservationRepository.existsByMentorSlotId(slotId); } - - // ===== 데이터 조작 메서드 ===== - - public void deleteMentorSlotsData(Long mentorId) { - mentorSlotRepository.deleteAllByMentorId(mentorId); - } - public MentoringSession getMentoringSessionBySessionUuid(String mentoringSessionId) { return mentoringSessionRepository.findBySessionUrl(mentoringSessionId) .orElseThrow(() -> new ServiceException("404", "세션을 찾을 수 없습니다.")); diff --git a/back/src/main/java/com/back/domain/mentoring/reservation/dto/ReservationDto.java b/back/src/main/java/com/back/domain/mentoring/reservation/dto/ReservationDto.java index c6a89412..8da541c5 100644 --- a/back/src/main/java/com/back/domain/mentoring/reservation/dto/ReservationDto.java +++ b/back/src/main/java/com/back/domain/mentoring/reservation/dto/ReservationDto.java @@ -2,7 +2,6 @@ import com.back.domain.mentoring.reservation.constant.ReservationStatus; import com.back.domain.mentoring.reservation.entity.Reservation; -import com.back.domain.mentoring.session.entity.MentoringSession; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; @@ -23,11 +22,9 @@ public record ReservationDto( @Schema(description = "시작 일시") LocalDateTime startDateTime, @Schema(description = "종료 일시") - LocalDateTime endDateTime, - @Schema(description = "멘토링 세션 ID") - Long mentoringSessionId + LocalDateTime endDateTime ) { - public static ReservationDto from(Reservation reservation, MentoringSession mentoringSession) { + public static ReservationDto from(Reservation reservation) { return new ReservationDto( reservation.getId(), reservation.getStatus(), @@ -35,8 +32,7 @@ public static ReservationDto from(Reservation reservation, MentoringSession ment reservation.getMentoring().getTitle(), reservation.getMentorSlot().getId(), reservation.getMentorSlot().getStartDateTime(), - reservation.getMentorSlot().getEndDateTime(), - mentoringSession != null ? mentoringSession.getId() : null + reservation.getMentorSlot().getEndDateTime() ); } } diff --git a/back/src/main/java/com/back/domain/mentoring/reservation/service/ReservationService.java b/back/src/main/java/com/back/domain/mentoring/reservation/service/ReservationService.java index 2d88fd54..659db6e0 100644 --- a/back/src/main/java/com/back/domain/mentoring/reservation/service/ReservationService.java +++ b/back/src/main/java/com/back/domain/mentoring/reservation/service/ReservationService.java @@ -48,10 +48,7 @@ public Page getReservations(Member member, int page, int size) { } else { reservations = reservationRepository.findAllByMenteeMember(member, pageable); } - return reservations.map(r -> { - MentoringSession mentoringSession = mentoringSessionService.getMentoringSessionByReservation(r); - return ReservationDto.from(r, mentoringSession); - }); + return reservations.map(ReservationDto::from); } @Transactional(readOnly = true) @@ -66,28 +63,24 @@ public ReservationResponse getReservation(Member member, Long reservationId) { @Transactional public ReservationResponse createReservation(Mentee mentee, ReservationRequest reqDto) { - try { - Mentoring mentoring = mentoringStorage.findMentoring(reqDto.mentoringId()); - MentorSlot mentorSlot = mentoringStorage.findMentorSlot(reqDto.mentorSlotId()); + Mentoring mentoring = mentoringStorage.findMentoring(reqDto.mentoringId()); + MentorSlot mentorSlot = mentoringStorage.findMentorSlot(reqDto.mentorSlotId()); - DateTimeValidator.validateStartTimeNotInPast(mentorSlot.getStartDateTime()); - validateMentorSlotStatus(mentorSlot, mentee); - validateOverlappingTimeForMentee(mentee, mentorSlot); + DateTimeValidator.validateStartTimeNotInPast(mentorSlot.getStartDateTime()); + validateMentorSlotStatus(mentorSlot, mentee); + validateOverlappingTimeForMentee(mentee, mentorSlot); - Reservation reservation = Reservation.builder() - .mentoring(mentoring) - .mentee(mentee) - .mentorSlot(mentorSlot) - .preQuestion(reqDto.preQuestion()) - .build(); - reservationRepository.save(reservation); + Reservation reservation = Reservation.builder() + .mentoring(mentoring) + .mentee(mentee) + .mentorSlot(mentorSlot) + .preQuestion(reqDto.preQuestion()) + .build(); + reservationRepository.save(reservation); - mentorSlot.updateStatus(MentorSlotStatus.PENDING); + mentorSlot.updateStatus(MentorSlotStatus.PENDING); - return ReservationResponse.from(reservation); - } catch (OptimisticLockException e) { - throw new ServiceException(ReservationErrorCode.CONCURRENT_RESERVATION_CONFLICT); - } + return ReservationResponse.from(reservation); } @Transactional @@ -98,10 +91,9 @@ public ReservationResponse approveReservation(Mentor mentor, Long reservationId) reservation.approve(mentor); reservation.getMentorSlot().updateStatus(MentorSlotStatus.APPROVED); - // 예약이 승인되면 세션을 생성한다. MentoringSession mentoringSession = mentoringSessionService.create(reservation); - return ReservationResponse.from(reservation); + return ReservationResponse.from(reservation, mentoringSession); } catch (OptimisticLockException e) { throw new ServiceException(ReservationErrorCode.CONCURRENT_APPROVAL_CONFLICT); } diff --git a/back/src/main/java/com/back/domain/mentoring/session/controller/MentoringSessionController.java b/back/src/main/java/com/back/domain/mentoring/session/controller/MentoringSessionController.java index 5fe4a42f..157d7dea 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/controller/MentoringSessionController.java +++ b/back/src/main/java/com/back/domain/mentoring/session/controller/MentoringSessionController.java @@ -5,35 +5,37 @@ import com.back.domain.mentoring.session.service.MentoringSessionManager; import com.back.global.rq.Rq; import com.back.global.rsData.RsData; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/sessions") +@Tag(name = "MentoringSessionController", description = "멘토링 세션 API - 화상 채팅으로 멘토링 진행") public class MentoringSessionController { private final MentoringSessionManager mentoringSessionManager; private final Rq rq; - //세션참여 URL발급 @GetMapping("/{sessionId}/url") + @Operation(summary = "세션참여 URL 발급", description = "세션 참여를 위한 URL을 발급합니다.") public RsData getSessionUrl(@PathVariable Long sessionId) { GetSessionUrlResponse response = mentoringSessionManager.getSessionUrl(sessionId); return new RsData<>("200", "요청완료", response); } - //세션 상세 정보(참여 현황?, 제목 등등?) @GetMapping("/{sessionId}") + @Operation(summary = "세션 상세 정보", description = "세션 제목, 멘토, 멘티, 메시지 목록 등 세션의 상세 정보를 조회합니다.") public RsData getSessionDetail(@PathVariable Long sessionId) { GetSessionInfoResponse response = mentoringSessionManager.getSessionDetail(sessionId); return new RsData<>("200", "요청완료", response); } - //세션 열기 @PutMapping("/{sessionId}") @PreAuthorize("hasRole('MENTOR')") + @Operation(summary = "세션 열기", description = "세션을 열어 멘토링을 진행합니다.") public RsData openSession(@PathVariable Long sessionId) { Member member = rq.getActor(); OpenSessionRequest openSessionRequest = new OpenSessionRequest(sessionId); @@ -41,9 +43,9 @@ public RsData openSession(@PathVariable Long sessionId) { return new RsData<>("200", "세션 오픈 완료", response); } - //세션종료 @DeleteMapping("/{sessionId}") @PreAuthorize("hasRole('MENTOR')") + @Operation(summary = "세션 종료", description = "세션을 닫아 멘토링을 종료합니다.") public RsData closeSession(@PathVariable Long sessionId) { Member member = rq.getActor(); DeleteSessionRequest deleteSessionRequest = new DeleteSessionRequest(sessionId); diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageRequest.java b/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageRequest.java index 437e7ec4..2a84c712 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageRequest.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageRequest.java @@ -1,10 +1,12 @@ package com.back.domain.mentoring.session.dto; import com.back.domain.mentoring.session.entity.MessageType; -import com.back.domain.mentoring.session.entity.SenderRole; +import io.swagger.v3.oas.annotations.media.Schema; public record ChatMessageRequest( + @Schema(description = "메시지 타입", example = "TEXT") MessageType type, + @Schema(description = "메시지 내용", example = "msg") String content ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageResponse.java b/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageResponse.java index 99bb3f35..b0305dc3 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageResponse.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/ChatMessageResponse.java @@ -1,10 +1,15 @@ package com.back.domain.mentoring.session.dto; +import io.swagger.v3.oas.annotations.media.Schema; + import java.time.LocalDateTime; public record ChatMessageResponse( + @Schema(description = "작성자명") String senderName, + @Schema(description = "메시지 내용") String content, + @Schema(description = "작성일시") LocalDateTime createdAt ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/CloseSessionResponse.java b/back/src/main/java/com/back/domain/mentoring/session/dto/CloseSessionResponse.java index 313ddfef..ced47efc 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/CloseSessionResponse.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/CloseSessionResponse.java @@ -1,8 +1,13 @@ package com.back.domain.mentoring.session.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record CloseSessionResponse( + @Schema(description = "세션 URL") String sessionUrl, + @Schema(description = "멘토링 제목") String mentoringTitle, + @Schema(description = "세션 상태") String status ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/DeleteSessionRequest.java b/back/src/main/java/com/back/domain/mentoring/session/dto/DeleteSessionRequest.java index 83287b5f..9391b251 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/DeleteSessionRequest.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/DeleteSessionRequest.java @@ -1,6 +1,9 @@ package com.back.domain.mentoring.session.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record DeleteSessionRequest( + @Schema(description = "세션 ID") Long sessionId ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionInfoResponse.java b/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionInfoResponse.java index 97c6df90..20c5c2bc 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionInfoResponse.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionInfoResponse.java @@ -1,9 +1,15 @@ package com.back.domain.mentoring.session.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record GetSessionInfoResponse( + @Schema(description = "멘토링 제목") String mentoringTitle, + @Schema(description = "멘토 이름") String mentorName, + @Schema(description = "멘티 이름") String menteeName, + @Schema(description = "세션 상태") String sessionStatus ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionUrlResponse.java b/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionUrlResponse.java index 21bf3838..c970f80d 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionUrlResponse.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/GetSessionUrlResponse.java @@ -1,6 +1,9 @@ package com.back.domain.mentoring.session.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record GetSessionUrlResponse( + @Schema(description = "세션 URL") String sessionUrl ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionRequest.java b/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionRequest.java index bf27ab63..74815fb3 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionRequest.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionRequest.java @@ -1,6 +1,9 @@ package com.back.domain.mentoring.session.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record OpenSessionRequest( + @Schema(description = "세션 ID") Long sessionId ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionResponse.java b/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionResponse.java index 344c2b3b..9b90faed 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionResponse.java +++ b/back/src/main/java/com/back/domain/mentoring/session/dto/OpenSessionResponse.java @@ -1,8 +1,13 @@ package com.back.domain.mentoring.session.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public record OpenSessionResponse( + @Schema(description = "세션 URL") String sessionUrl, + @Schema(description = "멘토링 제목") String mentoringTitle, + @Schema(description = "세션 상태") String status ) { } diff --git a/back/src/main/java/com/back/domain/mentoring/session/service/MentoringSessionManager.java b/back/src/main/java/com/back/domain/mentoring/session/service/MentoringSessionManager.java index e91d115b..8ddd4a01 100644 --- a/back/src/main/java/com/back/domain/mentoring/session/service/MentoringSessionManager.java +++ b/back/src/main/java/com/back/domain/mentoring/session/service/MentoringSessionManager.java @@ -7,6 +7,8 @@ import com.back.domain.mentoring.reservation.entity.Reservation; import com.back.domain.mentoring.session.dto.*; import com.back.domain.mentoring.session.entity.MentoringSession; +import com.back.domain.mentoring.slot.constant.MentorSlotStatus; +import com.back.domain.mentoring.slot.entity.MentorSlot; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -49,6 +51,11 @@ public CloseSessionResponse closeSession(Member requestUser, DeleteSessionReques Mentor mentor = memberStorage.findMentorByMember(requestUser); MentoringSession session = mentoringSessionService.getMentoringSession(deleteRequest.sessionId()); MentoringSession closedSession = mentoringSessionService.save(session.closeSession(mentor)); + + Reservation reservation = closedSession.getReservation(); + reservation.complete(); + MentorSlot slot = reservation.getMentorSlot(); + slot.updateStatus(MentorSlotStatus.COMPLETED); return new CloseSessionResponse(closedSession.getSessionUrl(), closedSession.getMentoring().getTitle(), closedSession.getStatus().toString()); } } diff --git a/back/src/test/java/com/back/domain/mentoring/reservation/service/ReservationServiceTest.java b/back/src/test/java/com/back/domain/mentoring/reservation/service/ReservationServiceTest.java index 353154c2..891ac7d2 100644 --- a/back/src/test/java/com/back/domain/mentoring/reservation/service/ReservationServiceTest.java +++ b/back/src/test/java/com/back/domain/mentoring/reservation/service/ReservationServiceTest.java @@ -281,27 +281,6 @@ void throwExceptionWhenStartTimeInPast() { .isInstanceOf(ServiceException.class) .hasFieldOrPropertyWithValue("resultCode", MentorSlotErrorCode.START_TIME_IN_PAST.getCode()); } - - @Test - @DisplayName("동시성 충돌 발생 시 예외") - void throwExceptionOnConcurrentReservation() { - // given - when(mentoringStorage.findMentoring(request.mentoringId())) - .thenReturn(mentoring); - when(mentoringStorage.findMentorSlot(request.mentorSlotId())) - .thenReturn(mentorSlot); - when(reservationRepository.findByMentorSlotIdAndStatusIn(mentorSlot.getId(), - List.of(ReservationStatus.PENDING, ReservationStatus.APPROVED, ReservationStatus.COMPLETED))) - .thenReturn(Optional.empty()); - - // OptimisticLockException 테스트 위해 save 호출 시 예외 설정 - doThrow(new OptimisticLockException()).when(reservationRepository).save(any(Reservation.class)); - - // when & then - assertThatThrownBy(() -> reservationService.createReservation(mentee, request)) - .isInstanceOf(ServiceException.class) - .hasFieldOrPropertyWithValue("resultCode", ReservationErrorCode.CONCURRENT_RESERVATION_CONFLICT.getCode()); - } } @Nested @@ -314,6 +293,9 @@ void approveReservation() { // given when(mentoringStorage.findReservation(reservation.getId())) .thenReturn(reservation); + ReflectionTestUtils.setField(reservation, "status", ReservationStatus.APPROVED); + when(mentoringSessionService.create(any())).thenReturn(MentoringSessionFixture.create(reservation)); + ReflectionTestUtils.setField(reservation, "status", ReservationStatus.PENDING); // when ReservationResponse result = reservationService.approveReservation(mentor, reservation.getId());