Skip to content

Commit da1dbd1

Browse files
committed
2 parents edd976e + 9dfa0e8 commit da1dbd1

38 files changed

+1291
-92
lines changed

back/src/main/java/com/back/domain/mentoring/mentoring/controller/MentoringController.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.back.domain.mentoring.mentoring.service.MentoringService;
99
import com.back.global.rq.Rq;
1010
import com.back.global.rsData.RsData;
11+
import io.swagger.v3.oas.annotations.Operation;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
1113
import jakarta.validation.Valid;
1214
import lombok.RequiredArgsConstructor;
1315
import org.springframework.data.domain.Page;
@@ -17,11 +19,13 @@
1719
@RestController
1820
@RequestMapping("/mentoring")
1921
@RequiredArgsConstructor
22+
@Tag(name = "MentoringController", description = "멘토링 API")
2023
public class MentoringController {
2124
private final MentoringService mentoringService;
2225
private final Rq rq;
2326

2427
@GetMapping
28+
@Operation(summary = "멘토링 목록 조회")
2529
public RsData<MentoringPagingResponse> getMentorings(
2630
@RequestParam(defaultValue = "0") int page,
2731
@RequestParam(defaultValue = "10") int size,
@@ -38,20 +42,22 @@ public RsData<MentoringPagingResponse> getMentorings(
3842
}
3943

4044
@GetMapping("/{mentoringId}")
45+
@Operation(summary = "멘토링 상세 조회")
4146
public RsData<MentoringResponse> getMentoring(
4247
@PathVariable Long mentoringId
4348
) {
44-
MentoringResponse mentoring = mentoringService.getMentoring(mentoringId);
49+
MentoringResponse resDto = mentoringService.getMentoring(mentoringId);
4550

4651
return new RsData<>(
4752
"200",
4853
"멘토링을 조회하였습니다.",
49-
mentoring
54+
resDto
5055
);
5156
}
5257

5358
@PostMapping
5459
@PreAuthorize("hasRole('MENTOR')")
60+
@Operation(summary = "멘토링 생성")
5561
public RsData<MentoringResponse> createMentoring(
5662
@RequestBody @Valid MentoringRequest reqDto
5763
) {
@@ -66,6 +72,7 @@ public RsData<MentoringResponse> createMentoring(
6672
}
6773

6874
@PutMapping("/{mentoringId}")
75+
@Operation(summary = "멘토링 수정")
6976
public RsData<MentoringResponse> updateMentoring(
7077
@PathVariable Long mentoringId,
7178
@RequestBody @Valid MentoringRequest reqDto
@@ -81,6 +88,7 @@ public RsData<MentoringResponse> updateMentoring(
8188
}
8289

8390
@DeleteMapping("/{mentoringId}")
91+
@Operation(summary = "멘토링 삭제")
8492
public RsData<Void> deleteMentoring(
8593
@PathVariable Long mentoringId
8694
) {

back/src/main/java/com/back/domain/mentoring/mentoring/dto/response/MentoringPagingResponse.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package com.back.domain.mentoring.mentoring.dto.response;
22

33
import com.back.domain.mentoring.mentoring.dto.MentoringDto;
4+
import io.swagger.v3.oas.annotations.media.Schema;
45
import org.springframework.data.domain.Page;
56

67
import java.util.List;
78

89
public record MentoringPagingResponse(
10+
@Schema(description = "멘토링 목록")
911
List<MentoringDto> mentorings,
12+
@Schema(description = "현재 페이지 (0부터 시작)")
1013
int currentPage,
14+
@Schema(description = "총 페이지")
1115
int totalPage,
16+
@Schema(description = "총 개수")
1217
long totalElements,
18+
@Schema(description = "다음 페이지 존재 여부")
1319
boolean hasNext
1420
) {
1521
public static MentoringPagingResponse from(Page<MentoringDto> page) {

back/src/main/java/com/back/domain/mentoring/mentoring/dto/response/MentoringResponse.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import com.back.domain.member.mentor.dto.MentorDto;
44
import com.back.domain.mentoring.mentoring.dto.MentoringDetailDto;
5+
import io.swagger.v3.oas.annotations.media.Schema;
56

67
public record MentoringResponse(
8+
@Schema(description = "멘토링")
79
MentoringDetailDto mentoring,
10+
@Schema(description = "멘토")
811
MentorDto mentor
912
) {
1013
}

back/src/main/java/com/back/domain/mentoring/mentoring/repository/MentoringRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import com.back.domain.mentoring.mentoring.entity.Mentoring;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6+
import java.util.List;
67
import java.util.Optional;
78

89
public interface MentoringRepository extends JpaRepository<Mentoring, Long>, MentoringRepositoryCustom {
10+
List<Mentoring> findByMentorId(Long mentorId);
911
Optional<Mentoring> findTopByOrderByIdDesc();
10-
1112
boolean existsByMentorId(Long mentorId);
1213
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.back.domain.mentoring.slot.controller;
2+
3+
import com.back.domain.member.member.entity.Member;
4+
import com.back.domain.mentoring.slot.dto.request.MentorSlotRequest;
5+
import com.back.domain.mentoring.slot.dto.response.MentorSlotResponse;
6+
import com.back.domain.mentoring.slot.service.MentorSlotService;
7+
import com.back.global.rq.Rq;
8+
import com.back.global.rsData.RsData;
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import jakarta.validation.Valid;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.security.access.prepost.PreAuthorize;
14+
import org.springframework.web.bind.annotation.*;
15+
16+
@RestController
17+
@RequestMapping("/mentor-slot")
18+
@RequiredArgsConstructor
19+
@Tag(name = "MentorSlotController", description = "멘토 슬롯(멘토의 예약 가능 일정) API")
20+
public class MentorSlotController {
21+
22+
private final MentorSlotService mentorSlotService;
23+
private final Rq rq;
24+
25+
@PostMapping
26+
@PreAuthorize("hasRole('MENTOR')")
27+
@Operation(summary = "멘토 슬롯 생성")
28+
public RsData<MentorSlotResponse> createMentorSlot(
29+
@RequestBody @Valid MentorSlotRequest reqDto
30+
) {
31+
Member member = rq.getActor();
32+
MentorSlotResponse resDto = mentorSlotService.createMentorSlot(reqDto, member);
33+
34+
return new RsData<>(
35+
"201",
36+
"멘토링 예약 일정을 등록했습니다.",
37+
resDto
38+
);
39+
}
40+
41+
@PutMapping("/{slotId}")
42+
@Operation(summary = "멘토 슬롯 수정")
43+
public RsData<MentorSlotResponse> updateMentorSlot(
44+
@PathVariable Long slotId,
45+
@RequestBody @Valid MentorSlotRequest reqDto
46+
) {
47+
Member member = rq.getActor();
48+
MentorSlotResponse resDto = mentorSlotService.updateMentorSlot(slotId, reqDto, member);
49+
50+
return new RsData<>(
51+
"200",
52+
"멘토링 예약 일정이 수정되었습니다.",
53+
resDto
54+
);
55+
}
56+
57+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.back.domain.mentoring.slot.dto;
2+
3+
import com.back.domain.mentoring.slot.constant.MentorSlotStatus;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
import java.time.LocalDateTime;
7+
8+
public record MentorSlotDto(
9+
@Schema(description = "멘토링 슬롯 ID")
10+
Long mentorSlotId,
11+
@Schema(description = "시작 일시")
12+
LocalDateTime startDateTime,
13+
@Schema(description = "종료 일시")
14+
LocalDateTime endDateTime,
15+
@Schema(description = "멘토 슬롯 상태")
16+
MentorSlotStatus mentorSlotStatus
17+
) {
18+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.back.domain.mentoring.slot.dto.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.NotNull;
5+
6+
import java.time.LocalDateTime;
7+
8+
public record MentorSlotRequest(
9+
@Schema(description = "멘토 ID")
10+
@NotNull
11+
Long mentorId,
12+
13+
@Schema(description = "시작 일시")
14+
@NotNull
15+
LocalDateTime startDateTime,
16+
17+
@Schema(description = "종료 일시")
18+
@NotNull
19+
LocalDateTime endDateTime
20+
) {
21+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.back.domain.mentoring.slot.dto.response;
2+
3+
import com.back.domain.mentoring.mentoring.entity.Mentoring;
4+
import com.back.domain.mentoring.slot.constant.MentorSlotStatus;
5+
import com.back.domain.mentoring.slot.entity.MentorSlot;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
8+
import java.time.LocalDateTime;
9+
10+
public record MentorSlotResponse(
11+
@Schema(description = "멘토링 슬롯 ID")
12+
Long mentorSlotId,
13+
14+
@Schema(description = "멘토 ID")
15+
Long mentorId,
16+
17+
@Schema(description = "멘토링 ID")
18+
Long mentoringId,
19+
20+
@Schema(description = "멘토링 제목")
21+
String mentoringTitle,
22+
23+
@Schema(description = "시작 일시")
24+
LocalDateTime startDateTime,
25+
26+
@Schema(description = "종료 일시")
27+
LocalDateTime endDateTime,
28+
29+
@Schema(description = "멘토 슬롯 상태")
30+
MentorSlotStatus mentorSlotStatus,
31+
32+
@Schema(description = "생성일")
33+
LocalDateTime createDate,
34+
35+
@Schema(description = "수정일")
36+
LocalDateTime modifyDate
37+
) {
38+
public static MentorSlotResponse from(MentorSlot mentorSlot, Mentoring mentoring) {
39+
return new MentorSlotResponse(
40+
mentorSlot.getId(),
41+
mentorSlot.getMentor().getId(),
42+
mentoring.getId(),
43+
mentoring.getTitle(),
44+
mentorSlot.getStartDateTime(),
45+
mentorSlot.getEndDateTime(),
46+
mentorSlot.getStatus(),
47+
mentorSlot.getCreateDate(),
48+
mentorSlot.getModifyDate()
49+
);
50+
}
51+
}

back/src/main/java/com/back/domain/mentoring/slot/entity/MentorSlot.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,53 @@ public class MentorSlot extends BaseEntity {
2929
@OneToOne(mappedBy = "mentorSlot")
3030
private Reservation reservation;
3131

32+
@Enumerated(EnumType.STRING)
33+
@Column(nullable = false)
34+
private MentorSlotStatus status;
35+
3236
@Builder
3337
public MentorSlot(Mentor mentor, LocalDateTime startDateTime, LocalDateTime endDateTime) {
3438
this.mentor = mentor;
3539
this.startDateTime = startDateTime;
3640
this.endDateTime = endDateTime;
41+
this.status = MentorSlotStatus.AVAILABLE;
3742
}
3843

39-
public MentorSlotStatus getStatus() {
44+
// =========================
45+
// TODO - 현재 상태
46+
// 1. reservation 필드에는 활성 예약(PENDING, APPROVED)만 세팅
47+
// 2. 취소/거절 예약은 DB에 남기고 reservation 필드에는 연결하지 않음
48+
// 3. 슬롯 재생성 불필요, 상태 기반 isAvailable() 로 새 예약 가능 판단
49+
//
50+
// TODO - 추후 변경
51+
// 1. 1:N 구조로 리팩토링
52+
// - MentorSlot에 여러 Reservation 연결 가능
53+
// - 모든 예약 기록(히스토리) 보존
54+
// 2. 상태 기반 필터링 유지: 활성 예약만 계산 시 사용
55+
// 3. 이벤트 소싱/분석 등 확장 가능하도록 구조 개선
56+
// =========================
57+
58+
public void updateStatus() {
4059
if (reservation == null) {
41-
return MentorSlotStatus.AVAILABLE;
60+
this.status = MentorSlotStatus.AVAILABLE;
61+
} else {
62+
this.status = switch (reservation.getStatus()) {
63+
case PENDING -> MentorSlotStatus.PENDING;
64+
case APPROVED -> MentorSlotStatus.APPROVED;
65+
case COMPLETED -> MentorSlotStatus.COMPLETED;
66+
case REJECTED, CANCELED -> MentorSlotStatus.AVAILABLE;
67+
};
4268
}
43-
44-
return switch (reservation.getStatus()) {
45-
case PENDING -> MentorSlotStatus.PENDING;
46-
case APPROVED -> MentorSlotStatus.APPROVED;
47-
case COMPLETED -> MentorSlotStatus.COMPLETED;
48-
default -> MentorSlotStatus.AVAILABLE;
49-
};
5069
}
5170

5271
public boolean isAvailable() {
5372
return reservation == null ||
5473
reservation.getStatus().equals(ReservationStatus.REJECTED) ||
5574
reservation.getStatus().equals(ReservationStatus.CANCELED);
5675
}
76+
77+
public void update(LocalDateTime startDateTime, LocalDateTime endDateTime) {
78+
this.startDateTime = startDateTime;
79+
this.endDateTime = endDateTime;
80+
}
5781
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.back.domain.mentoring.slot.error;
2+
3+
import com.back.global.exception.ErrorCode;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@AllArgsConstructor
9+
public enum MentorSlotErrorCode implements ErrorCode {
10+
11+
// 400 DateTime 체크
12+
START_TIME_REQUIRED("400-1", "시작 일시와 종료 일시는 필수입니다."),
13+
END_TIME_REQUIRED("400-2", "시작 일시와 종료 일시는 필수입니다."),
14+
START_TIME_IN_PAST("400-3", "시작 일시는 현재 이후여야 합니다."),
15+
END_TIME_BEFORE_START("400-4", "종료 일시는 시작 일시보다 이후여야 합니다."),
16+
INSUFFICIENT_SLOT_DURATION("400-5", "슬롯은 최소 30분 이상이어야 합니다."),
17+
18+
// 400 Slot 체크
19+
CANNOT_UPDATE_RESERVED_SLOT("400-6", "예약된 슬롯은 수정할 수 없습니다."),
20+
21+
// 403
22+
NOT_OWNER("403-1", "일정의 소유주가 아닙니다."),
23+
24+
// 404
25+
NOT_FOUND_MENTOR_SLOT("404-1", "일정 정보가 없습니다."),
26+
27+
// 409
28+
OVERLAPPING_SLOT("409-1", "선택한 시간은 이미 예약된 시간대입니다.");
29+
30+
private final String code;
31+
private final String message;
32+
}

0 commit comments

Comments
 (0)