Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
@RequiredArgsConstructor
public class MemberStorage {
Expand All @@ -29,9 +27,6 @@ public Mentor findMentorByMemberId(Long memberId) {
return mentorRepository.findByMemberIdWithMember(memberId)
.orElseThrow(() -> new ServiceException(MemberErrorCode.NOT_FOUND_MENTOR));
}
public Optional<Mentor> findMentorByMemberOptional(Member member) {
return mentorRepository.findByMemberIdWithMember(member.getId());
}

public Mentee findMenteeByMember(Member member) {
return menteeRepository.findByMemberIdWithMember(member.getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ public Mentee(Member member, Long jobId) {
public void delete() {
this.isDeleted = true;
}

public boolean isMember(Member member) {
return this.member.equals(member);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ public void updateCareerYears(Integer careerYears) {
public void delete() {
this.isDeleted = true;
}

public boolean isMember(Member member) {
return this.member.equals(member);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ public record MentoringWithTagsDto(
@Schema(description = "멘토링 제목")
String title,
@Schema(description = "멘토링 태그")
List<String> tags
List<String> tags,
@Schema(description = "멘토 ID")
Long mentorId,
@Schema(description = "멘토 닉네임")
String nickname
) {
public static MentoringWithTagsDto from(Mentoring mentoring) {
return new MentoringWithTagsDto(
mentoring.getId(),
mentoring.getTitle(),
mentoring.getTags()
mentoring.getTags(),
mentoring.getMentor().getId(),
mentoring.getMentor().getMember().getNickname()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import com.back.domain.member.member.service.MemberStorage;
import com.back.domain.member.mentee.entity.Mentee;
import com.back.domain.member.mentor.entity.Mentor;
import com.back.domain.mentoring.reservation.dto.ReservationDto;
import com.back.domain.mentoring.reservation.dto.request.ReservationRequest;
import com.back.domain.mentoring.reservation.dto.response.ReservationPagingResponse;
import com.back.domain.mentoring.reservation.dto.response.ReservationResponse;
import com.back.domain.mentoring.reservation.service.ReservationService;
import com.back.global.rq.Rq;
Expand All @@ -13,11 +15,10 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@RestController
@RequestMapping("/reservations")
@RequiredArgsConstructor
Expand All @@ -28,14 +29,46 @@ public class ReservationController {
private final ReservationService reservationService;
private final MemberStorage memberStorage;

@GetMapping
@Operation(summary = "나의 예약 목록 조회", description = "본인의 예약 목록을 조회합니다. 로그인 후 조회할 수 있습니다.")
public RsData<ReservationPagingResponse> getReservations(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Member member = rq.getActor();
Page<ReservationDto> reservationPage = reservationService.getReservations(member, page, size);
ReservationPagingResponse resDto = ReservationPagingResponse.from(reservationPage);

return new RsData<>(
"200",
"예약 목록을 조회하였습니다.",
resDto
);
}


@GetMapping("/{reservationId}")
@Operation(summary = "예약 조회", description = "특정 예약을 조회합니다. 로그인 후 예약 조회할 수 있습니다.")
public RsData<ReservationResponse> getReservation(
@PathVariable Long reservationId
) {
Member member = rq.getActor();
ReservationResponse resDto = reservationService.getReservation(member, reservationId);

return new RsData<>(
"200",
"예약을 조회하였습니다.",
resDto
);
}

@PostMapping
@PreAuthorize("hasRole('MENTEE')")
@Operation(summary = "예약 신청", description = "멘티가 멘토의 슬롯을 선택해 예약 신청을 합니다. 로그인한 멘티만 예약 신청할 수 있습니다.")
public RsData<ReservationResponse> createReservation(
@RequestBody @Valid ReservationRequest reqDto
) {
Mentee mentee = memberStorage.findMenteeByMember(rq.getActor());

ReservationResponse resDto = reservationService.createReservation(mentee, reqDto);

return new RsData<>(
Expand All @@ -52,7 +85,6 @@ public RsData<ReservationResponse> approveReservation(
@PathVariable Long reservationId
) {
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());

ReservationResponse resDto = reservationService.approveReservation(mentor, reservationId);

return new RsData<>(
Expand All @@ -69,7 +101,6 @@ public RsData<ReservationResponse> rejectReservation(
@PathVariable Long reservationId
) {
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());

ReservationResponse resDto = reservationService.rejectReservation(mentor, reservationId);

return new RsData<>(
Expand All @@ -85,15 +116,7 @@ public RsData<ReservationResponse> cancelReservation(
@PathVariable Long reservationId
) {
Member member = rq.getActor();
ReservationResponse resDto;

Optional<Mentor> mentor = memberStorage.findMentorByMemberOptional(member);
if (mentor.isPresent()) {
resDto = reservationService.cancelReservation(mentor.get(), reservationId);
} else {
Mentee mentee = memberStorage.findMenteeByMember(member);
resDto = reservationService.cancelReservation(mentee, reservationId);
}
ReservationResponse resDto = reservationService.cancelReservation(member, reservationId);

return new RsData<>(
"200",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.back.domain.mentoring.reservation.dto;

import com.back.domain.mentoring.reservation.constant.ReservationStatus;
import com.back.domain.mentoring.reservation.entity.Reservation;
import io.swagger.v3.oas.annotations.media.Schema;

import java.time.LocalDateTime;

public record ReservationDto(
@Schema(description = "예약 ID")
Long reservationId,
@Schema(description = "예약 상태")
ReservationStatus status,

@Schema(description = "멘토링 ID")
Long mentoringId,
@Schema(description = "멘토링 제목")
String title,

@Schema(description = "멘토 슬롯 ID")
Long mentorSlotId,
@Schema(description = "시작 일시")
LocalDateTime startDateTime,
@Schema(description = "종료 일시")
LocalDateTime endDateTime
) {
public static ReservationDto from(Reservation reservation) {
return new ReservationDto(
reservation.getId(),
reservation.getStatus(),
reservation.getMentoring().getId(),
reservation.getMentoring().getTitle(),
reservation.getMentorSlot().getId(),
reservation.getMentorSlot().getStartDateTime(),
reservation.getMentorSlot().getEndDateTime()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.back.domain.mentoring.reservation.dto.response;

import com.back.domain.mentoring.reservation.dto.ReservationDto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.data.domain.Page;

import java.util.List;

public record ReservationPagingResponse(
@Schema(description = "예약 목록")
List<ReservationDto> reservations,
@Schema(description = "현재 페이지 (0부터 시작)")
int currentPage,
@Schema(description = "총 페이지")
int totalPage,
@Schema(description = "총 개수")
long totalElements,
@Schema(description = "다음 페이지 존재 여부")
boolean hasNext
) {
public static ReservationPagingResponse from(Page<ReservationDto> page) {
return new ReservationPagingResponse(
page.getContent(),
page.getNumber(),
page.getTotalPages(),
page.getTotalElements(),
page.hasNext()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.back.domain.mentoring.reservation.entity;

import com.back.domain.member.member.entity.Member;
import com.back.domain.member.mentee.entity.Mentee;
import com.back.domain.member.mentor.entity.Mentor;
import com.back.domain.mentoring.mentoring.entity.Mentoring;
Expand All @@ -14,6 +15,13 @@
import lombok.NoArgsConstructor;

@Entity
@Table(
name = "reservation",
indexes = {
@Index(name = "idx_reservation_mentor", columnList = "mentor_id"),
@Index(name = "idx_reservation_mentee", columnList = "mentee_id")
}
)
@Getter
@NoArgsConstructor
public class Reservation extends BaseEntity {
Expand Down Expand Up @@ -86,15 +94,10 @@ public void reject(Mentor mentor) {
updateStatus(ReservationStatus.REJECTED);
}

public void cancel(Mentor mentor) {
ensureMentor(mentor);
ensureCanCancel();
ensureNotPast();
updateStatus(ReservationStatus.CANCELED);
}

public void cancel(Mentee mentee) {
ensureMentee(mentee);
public void cancel(Member member) {
if (!mentor.isMember(member) && !mentee.isMember(member) ) {
throw new ServiceException(ReservationErrorCode.FORBIDDEN_NOT_PARTICIPANT);
}
ensureCanCancel();
ensureNotPast();
updateStatus(ReservationStatus.CANCELED);
Expand All @@ -114,12 +117,6 @@ private void ensureMentor(Mentor mentor) {
}
}

private void ensureMentee(Mentee mentee) {
if (!isMentee(mentee)) {
throw new ServiceException(ReservationErrorCode.FORBIDDEN_NOT_MENTEE);
}
}

private void ensureCanApprove() {
if(!this.status.canApprove()) {
throw new ServiceException(ReservationErrorCode.CANNOT_APPROVE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public enum ReservationErrorCode implements ErrorCode {
// 403
FORBIDDEN_NOT_MENTOR("403-1", "해당 예약에 대한 멘토 권한이 없습니다."),
FORBIDDEN_NOT_MENTEE("403-2", "해당 예약에 대한 멘티 권한이 없습니다."),
FORBIDDEN_NOT_PARTICIPANT("403-3", "해당 예약에 대한 권한이 없습니다"),
RESERVATION_NOT_ACCESSIBLE("403-4", "예약을 찾을 수 없거나 권한이 없습니다"),

// 404
NOT_FOUND_RESERVATION("404-1", "예약이 존재하지 않습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.back.domain.mentoring.reservation.repository;

import com.back.domain.member.member.entity.Member;
import com.back.domain.mentoring.reservation.constant.ReservationStatus;
import com.back.domain.mentoring.reservation.entity.Reservation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;
Expand All @@ -11,6 +16,40 @@ public interface ReservationRepository extends JpaRepository<Reservation, Long>
Optional<Reservation> findTopByOrderByIdDesc();
Optional<Reservation> findByMentorSlotIdAndStatusIn(Long mentorSlotId, List<ReservationStatus> statuses);

@Query("""
SELECT r
FROM Reservation r
WHERE r.id = :reservationId
AND (r.mentee.member = :member
OR r.mentor.member = :member)
""")
Optional<Reservation> findByIdAndMember(
@Param("reservationId") Long reservationId,
@Param("member") Member member
);

@Query("""
SELECT r
FROM Reservation r
WHERE r.mentor.member = :member
ORDER BY r.mentorSlot.startDateTime DESC
""")
Page<Reservation> findAllByMentorMember(
@Param("member") Member member,
Pageable pageable
);

@Query("""
SELECT r
FROM Reservation r
WHERE r.mentee.member = :member
ORDER BY r.mentorSlot.startDateTime DESC
""")
Page<Reservation> findAllByMenteeMember(
@Param("member") Member member,
Pageable pageable
);

boolean existsByMentoringId(Long mentoringId);

/**
Expand Down
Loading