Skip to content

Commit 90bb13b

Browse files
authored
Feat: 멘토 슬롯 반복 일정 생성 및 관리 (#81)
* Feat: 멘토의 예약 가능 일정 목록 조회 * Feat: 멘토의 모든 일정 목록 조회 * Feat: 반복 슬롯 생성
1 parent 2b1cd60 commit 90bb13b

File tree

13 files changed

+435
-75
lines changed

13 files changed

+435
-75
lines changed

back/src/main/java/com/back/domain/member/member/entity/Member.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package com.back.domain.member.member.entity;
22

33
import com.back.global.jpa.BaseEntity;
4-
import jakarta.persistence.Column;
5-
import jakarta.persistence.Entity;
6-
import jakarta.persistence.EnumType;
7-
import jakarta.persistence.Enumerated;
4+
import jakarta.persistence.*;
85
import lombok.Getter;
96
import lombok.NoArgsConstructor;
107

8+
import java.util.UUID;
9+
1110
@Entity
1211
@Getter
1312
@NoArgsConstructor
1413
public class Member extends BaseEntity {
14+
@Column(unique = true, nullable = false, length = 36)
15+
private String publicId;
16+
1517
@Column(unique = true, nullable = false)
1618
private String email;
1719

@@ -51,4 +53,11 @@ public Member(Long id, String email, String name, String nickname, Role role) {
5153
this.nickname = nickname;
5254
this.role = role;
5355
}
56+
57+
@PrePersist
58+
public void generatePublicId() {
59+
if (this.publicId == null) {
60+
this.publicId = UUID.randomUUID().toString();
61+
}
62+
}
5463
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import org.springframework.web.bind.annotation.*;
1818

1919
@RestController
20-
@RequestMapping("/mentoring")
20+
@RequestMapping("/mentorings")
2121
@RequiredArgsConstructor
2222
@Tag(name = "MentoringController", description = "멘토링 API")
2323
public class MentoringController {

back/src/main/java/com/back/domain/mentoring/slot/controller/MentorSlotController.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.back.domain.mentoring.slot.controller;
22

33
import com.back.domain.member.member.entity.Member;
4+
import com.back.domain.mentoring.slot.dto.request.MentorSlotRepetitionRequest;
45
import com.back.domain.mentoring.slot.dto.request.MentorSlotRequest;
6+
import com.back.domain.mentoring.slot.dto.response.MentorSlotDto;
57
import com.back.domain.mentoring.slot.dto.response.MentorSlotResponse;
68
import com.back.domain.mentoring.slot.service.MentorSlotService;
79
import com.back.global.rq.Rq;
@@ -10,18 +12,63 @@
1012
import io.swagger.v3.oas.annotations.tags.Tag;
1113
import jakarta.validation.Valid;
1214
import lombok.RequiredArgsConstructor;
15+
import org.springframework.format.annotation.DateTimeFormat;
1316
import org.springframework.security.access.prepost.PreAuthorize;
1417
import org.springframework.web.bind.annotation.*;
1518

19+
import java.time.LocalDate;
20+
import java.time.LocalDateTime;
21+
import java.util.List;
22+
1623
@RestController
17-
@RequestMapping("/mentor-slot")
24+
@RequestMapping("/mentor-slots")
1825
@RequiredArgsConstructor
1926
@Tag(name = "MentorSlotController", description = "멘토 슬롯(멘토의 예약 가능 일정) API")
2027
public class MentorSlotController {
2128

2229
private final MentorSlotService mentorSlotService;
2330
private final Rq rq;
2431

32+
@GetMapping
33+
@PreAuthorize("hasRole('MENTOR')")
34+
@Operation(summary = "멘토의 모든 슬롯 목록 조회", description = "멘토가 본인의 모든 슬롯(예약된 슬롯 포함) 목록을 조회합니다.")
35+
public RsData<List<MentorSlotDto>> getMyMentorSlots(
36+
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
37+
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate
38+
) {
39+
Member member = rq.getActor();
40+
41+
LocalDateTime startDateTime = startDate.atStartOfDay();
42+
LocalDateTime endDateTime = endDate.atStartOfDay();
43+
44+
List<MentorSlotDto> resDtoList = mentorSlotService.getMyMentorSlots(member, startDateTime, endDateTime);
45+
46+
return new RsData<>(
47+
"200",
48+
"나의 모든 일정 목록을 조회하였습니다.",
49+
resDtoList
50+
);
51+
}
52+
53+
@GetMapping("/available/{mentorId}")
54+
@Operation(summary = "멘토의 예약 가능한 슬롯 목록 조회", description = "멘티가 특정 멘토의 예약 가능한 슬롯 목록을 조회합니다.")
55+
public RsData<List<MentorSlotDto>> getAvailableMentorSlots(
56+
@PathVariable Long mentorId,
57+
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
58+
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate
59+
) {
60+
LocalDateTime startDateTime = startDate.atStartOfDay();
61+
LocalDateTime endDateTime = endDate.atStartOfDay();
62+
63+
List<MentorSlotDto> resDtoList = mentorSlotService.getAvailableMentorSlots(mentorId, startDateTime, endDateTime);
64+
65+
return new RsData<>(
66+
"200",
67+
"멘토의 예약 가능 일정 목록을 조회하였습니다.",
68+
resDtoList
69+
);
70+
}
71+
2572
@GetMapping("/{slotId}")
2673
@Operation(summary = "멘토 슬롯 조회", description = "특정 멘토 슬롯을 조회합니다.")
2774
public RsData<MentorSlotResponse> getMentorSlot(
@@ -52,6 +99,21 @@ public RsData<MentorSlotResponse> createMentorSlot(
5299
);
53100
}
54101

102+
@PostMapping("/repetition")
103+
@PreAuthorize("hasRole('MENTOR')")
104+
@Operation(summary = "반복 슬롯 생성", description = "멘토 슬롯을 반복 생성합니다. 로그인한 멘토만 생성할 수 있습니다.")
105+
public RsData<Void> createMentorSlotRepetition(
106+
@RequestBody @Valid MentorSlotRepetitionRequest reqDto
107+
) {
108+
Member member = rq.getActor();
109+
mentorSlotService.createMentorSlotRepetition(reqDto, member);
110+
111+
return new RsData<>(
112+
"201",
113+
"반복 일정을 등록했습니다."
114+
);
115+
}
116+
55117
@PutMapping("/{slotId}")
56118
@Operation(summary = "멘토 슬롯 수정", description = "멘토 슬롯을 수정합니다. 멘토 슬롯 작성자만 접근할 수 있습니다.")
57119
public RsData<MentorSlotResponse> updateMentorSlot(

back/src/main/java/com/back/domain/mentoring/slot/dto/MentorSlotDto.java

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.back.domain.mentoring.slot.dto.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import jakarta.validation.constraints.NotEmpty;
6+
import jakarta.validation.constraints.NotNull;
7+
8+
import java.time.DayOfWeek;
9+
import java.time.LocalDate;
10+
import java.time.LocalTime;
11+
import java.util.List;
12+
13+
public record MentorSlotRepetitionRequest(
14+
@Schema(description = "반복 시작일")
15+
@NotNull
16+
LocalDate repeatStartDate,
17+
18+
@Schema(description = "반복 종료일")
19+
@NotNull
20+
LocalDate repeatEndDate,
21+
22+
@Schema(description = "반복 요일")
23+
@NotEmpty
24+
List<DayOfWeek> daysOfWeek,
25+
26+
@Schema(description = "시작 시간")
27+
@NotNull
28+
@JsonFormat(pattern = "HH:mm:ss")
29+
LocalTime startTime,
30+
31+
@Schema(description = "종료 시간")
32+
@NotNull
33+
@JsonFormat(pattern = "HH:mm:ss")
34+
LocalTime endTime
35+
){
36+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.back.domain.mentoring.slot.dto.response;
2+
3+
import com.back.domain.mentoring.slot.constant.MentorSlotStatus;
4+
import com.back.domain.mentoring.slot.entity.MentorSlot;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
7+
import java.time.LocalDateTime;
8+
9+
public record MentorSlotDto(
10+
@Schema(description = "멘토 슬롯 ID")
11+
Long mentorSlotId,
12+
@Schema(description = "멘토 ID")
13+
Long mentorId,
14+
@Schema(description = "시작 일시")
15+
LocalDateTime startDateTime,
16+
@Schema(description = "종료 일시")
17+
LocalDateTime endDateTime,
18+
@Schema(description = "멘토 슬롯 상태")
19+
MentorSlotStatus mentorSlotStatus
20+
) {
21+
public static MentorSlotDto from(MentorSlot mentorSlot) {
22+
return new MentorSlotDto(
23+
mentorSlot.getId(),
24+
mentorSlot.getMentor().getId(),
25+
mentorSlot.getStartDateTime(),
26+
mentorSlot.getEndDateTime(),
27+
mentorSlot.getStatus()
28+
);
29+
}
30+
}

back/src/main/java/com/back/domain/mentoring/slot/dto/response/MentorSlotResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import java.time.LocalDateTime;
99

1010
public record MentorSlotResponse(
11-
@Schema(description = "멘토링 슬롯 ID")
11+
@Schema(description = "멘토 슬롯 ID")
1212
Long mentorSlotId,
1313

1414
@Schema(description = "멘토 ID")

back/src/main/java/com/back/domain/mentoring/slot/repository/MentorSlotRepository.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,45 @@
66
import org.springframework.data.repository.query.Param;
77

88
import java.time.LocalDateTime;
9+
import java.util.List;
910
import java.util.Optional;
1011

1112
public interface MentorSlotRepository extends JpaRepository<MentorSlot, Long> {
1213
Optional<MentorSlot> findTopByOrderByIdDesc();
14+
1315
boolean existsByMentorId(Long mentorId);
16+
long countByMentorId(Long mentorId);
1417
void deleteAllByMentorId(Long mentorId);
1518

19+
@Query("""
20+
SELECT ms
21+
FROM MentorSlot ms
22+
WHERE ms.mentor.id = :mentorId
23+
AND ms.startDateTime < :end
24+
AND ms.endDateTime >= :start
25+
ORDER BY ms.startDateTime ASC
26+
""")
27+
List<MentorSlot> findMySlots(
28+
@Param("mentorId") Long mentorId,
29+
@Param("start") LocalDateTime start,
30+
@Param("end") LocalDateTime end
31+
);
32+
33+
@Query("""
34+
SELECT ms
35+
FROM MentorSlot ms
36+
WHERE ms.mentor.id = :mentorId
37+
AND ms.status = 'AVAILABLE'
38+
AND ms.startDateTime < :end
39+
AND ms.endDateTime >= :start
40+
ORDER BY ms.startDateTime ASC
41+
""")
42+
List<MentorSlot> findAvailableSlots(
43+
@Param("mentorId") Long mentorId,
44+
@Param("start") LocalDateTime start,
45+
@Param("end") LocalDateTime end
46+
);
47+
1648
// TODO: 현재는 시간 겹침만 체크, 추후 1:N 구조 시 활성 예약 기준으로 변경
1749
@Query("""
1850
SELECT CASE WHEN COUNT(ms) > 0
@@ -43,6 +75,4 @@ boolean existsOverlappingExcept(
4375
@Param("start") LocalDateTime start,
4476
@Param("end") LocalDateTime end
4577
);
46-
47-
long countByMentorId(Long id);
4878
}

back/src/main/java/com/back/domain/mentoring/slot/service/MentorSlotValidator.java renamed to back/src/main/java/com/back/domain/mentoring/slot/service/DateTimeValidator.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import java.time.Duration;
77
import java.time.LocalDateTime;
88

9-
public class MentorSlotValidator {
9+
public class DateTimeValidator {
1010

1111
private static final int MIN_SLOT_DURATION = 20;
1212

@@ -19,10 +19,13 @@ public static void validateNotNull(LocalDateTime start, LocalDateTime end) {
1919
}
2020
}
2121

22-
public static void validateTimeRange(LocalDateTime start, LocalDateTime end) {
22+
public static void validateEndTimeAfterStart(LocalDateTime start, LocalDateTime end) {
2323
if (!end.isAfter(start)) {
2424
throw new ServiceException(MentorSlotErrorCode.END_TIME_BEFORE_START);
2525
}
26+
}
27+
28+
public static void validateStartTimeNotInPast(LocalDateTime start) {
2629
if (start.isBefore(LocalDateTime.now())) {
2730
throw new ServiceException(MentorSlotErrorCode.START_TIME_IN_PAST);
2831
}
@@ -35,9 +38,16 @@ public static void validateMinimumDuration(LocalDateTime start, LocalDateTime en
3538
}
3639
}
3740

41+
public static void validateTime(LocalDateTime start, LocalDateTime end) {
42+
validateNotNull(start, end);
43+
validateEndTimeAfterStart(start, end);
44+
}
45+
3846
public static void validateTimeSlot(LocalDateTime start, LocalDateTime end) {
3947
validateNotNull(start, end);
40-
validateTimeRange(start, end);
48+
validateEndTimeAfterStart(start, end);
49+
50+
validateStartTimeNotInPast(start);
4151
validateMinimumDuration(start, end);
4252
}
4353
}

0 commit comments

Comments
 (0)