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
@@ -1,17 +1,19 @@
package com.back.domain.member.member.entity;

import com.back.global.jpa.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.UUID;

@Entity
@Getter
@NoArgsConstructor
public class Member extends BaseEntity {
@Column(unique = true, nullable = false, length = 36)
private String publicId;

@Column(unique = true, nullable = false)
private String email;

Expand Down Expand Up @@ -51,4 +53,11 @@ public Member(Long id, String email, String name, String nickname, Role role) {
this.nickname = nickname;
this.role = role;
}

@PrePersist
public void generatePublicId() {
if (this.publicId == null) {
this.publicId = UUID.randomUUID().toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/mentoring")
@RequestMapping("/mentorings")
@RequiredArgsConstructor
@Tag(name = "MentoringController", description = "멘토링 API")
public class MentoringController {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.back.domain.mentoring.slot.controller;

import com.back.domain.member.member.entity.Member;
import com.back.domain.mentoring.slot.dto.request.MentorSlotRepetitionRequest;
import com.back.domain.mentoring.slot.dto.request.MentorSlotRequest;
import com.back.domain.mentoring.slot.dto.response.MentorSlotDto;
import com.back.domain.mentoring.slot.dto.response.MentorSlotResponse;
import com.back.domain.mentoring.slot.service.MentorSlotService;
import com.back.global.rq.Rq;
Expand All @@ -10,18 +12,63 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequestMapping("/mentor-slot")
@RequestMapping("/mentor-slots")
@RequiredArgsConstructor
@Tag(name = "MentorSlotController", description = "멘토 슬롯(멘토의 예약 가능 일정) API")
public class MentorSlotController {

private final MentorSlotService mentorSlotService;
private final Rq rq;

@GetMapping
@PreAuthorize("hasRole('MENTOR')")
@Operation(summary = "멘토의 모든 슬롯 목록 조회", description = "멘토가 본인의 모든 슬롯(예약된 슬롯 포함) 목록을 조회합니다.")
public RsData<List<MentorSlotDto>> getMyMentorSlots(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate
) {
Member member = rq.getActor();

LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atStartOfDay();

List<MentorSlotDto> resDtoList = mentorSlotService.getMyMentorSlots(member, startDateTime, endDateTime);

return new RsData<>(
"200",
"나의 모든 일정 목록을 조회하였습니다.",
resDtoList
);
}

@GetMapping("/available/{mentorId}")
@Operation(summary = "멘토의 예약 가능한 슬롯 목록 조회", description = "멘티가 특정 멘토의 예약 가능한 슬롯 목록을 조회합니다.")
public RsData<List<MentorSlotDto>> getAvailableMentorSlots(
@PathVariable Long mentorId,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate
) {
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atStartOfDay();

List<MentorSlotDto> resDtoList = mentorSlotService.getAvailableMentorSlots(mentorId, startDateTime, endDateTime);

return new RsData<>(
"200",
"멘토의 예약 가능 일정 목록을 조회하였습니다.",
resDtoList
);
}

@GetMapping("/{slotId}")
@Operation(summary = "멘토 슬롯 조회", description = "특정 멘토 슬롯을 조회합니다.")
public RsData<MentorSlotResponse> getMentorSlot(
Expand Down Expand Up @@ -52,6 +99,21 @@ public RsData<MentorSlotResponse> createMentorSlot(
);
}

@PostMapping("/repetition")
@PreAuthorize("hasRole('MENTOR')")
@Operation(summary = "반복 슬롯 생성", description = "멘토 슬롯을 반복 생성합니다. 로그인한 멘토만 생성할 수 있습니다.")
public RsData<Void> createMentorSlotRepetition(
@RequestBody @Valid MentorSlotRepetitionRequest reqDto
) {
Member member = rq.getActor();
mentorSlotService.createMentorSlotRepetition(reqDto, member);

return new RsData<>(
"201",
"반복 일정을 등록했습니다."
);
}

@PutMapping("/{slotId}")
@Operation(summary = "멘토 슬롯 수정", description = "멘토 슬롯을 수정합니다. 멘토 슬롯 작성자만 접근할 수 있습니다.")
public RsData<MentorSlotResponse> updateMentorSlot(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.back.domain.mentoring.slot.dto.request;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;

public record MentorSlotRepetitionRequest(
@Schema(description = "반복 시작일")
@NotNull
LocalDate repeatStartDate,

@Schema(description = "반복 종료일")
@NotNull
LocalDate repeatEndDate,

@Schema(description = "반복 요일")
@NotEmpty
List<DayOfWeek> daysOfWeek,

@Schema(description = "시작 시간")
@NotNull
@JsonFormat(pattern = "HH:mm:ss")
LocalTime startTime,

@Schema(description = "종료 시간")
@NotNull
@JsonFormat(pattern = "HH:mm:ss")
LocalTime endTime
){
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.back.domain.mentoring.slot.dto.response;

import com.back.domain.mentoring.slot.constant.MentorSlotStatus;
import com.back.domain.mentoring.slot.entity.MentorSlot;
import io.swagger.v3.oas.annotations.media.Schema;

import java.time.LocalDateTime;

public record MentorSlotDto(
@Schema(description = "멘토 슬롯 ID")
Long mentorSlotId,
@Schema(description = "멘토 ID")
Long mentorId,
@Schema(description = "시작 일시")
LocalDateTime startDateTime,
@Schema(description = "종료 일시")
LocalDateTime endDateTime,
@Schema(description = "멘토 슬롯 상태")
MentorSlotStatus mentorSlotStatus
) {
public static MentorSlotDto from(MentorSlot mentorSlot) {
return new MentorSlotDto(
mentorSlot.getId(),
mentorSlot.getMentor().getId(),
mentorSlot.getStartDateTime(),
mentorSlot.getEndDateTime(),
mentorSlot.getStatus()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.time.LocalDateTime;

public record MentorSlotResponse(
@Schema(description = "멘토링 슬롯 ID")
@Schema(description = "멘토 슬롯 ID")
Long mentorSlotId,

@Schema(description = "멘토 ID")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,45 @@
import org.springframework.data.repository.query.Param;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface MentorSlotRepository extends JpaRepository<MentorSlot, Long> {
Optional<MentorSlot> findTopByOrderByIdDesc();

boolean existsByMentorId(Long mentorId);
long countByMentorId(Long mentorId);
void deleteAllByMentorId(Long mentorId);

@Query("""
SELECT ms
FROM MentorSlot ms
WHERE ms.mentor.id = :mentorId
AND ms.startDateTime < :end
AND ms.endDateTime >= :start
ORDER BY ms.startDateTime ASC
""")
List<MentorSlot> findMySlots(
@Param("mentorId") Long mentorId,
@Param("start") LocalDateTime start,
@Param("end") LocalDateTime end
);

@Query("""
SELECT ms
FROM MentorSlot ms
WHERE ms.mentor.id = :mentorId
AND ms.status = 'AVAILABLE'
AND ms.startDateTime < :end
AND ms.endDateTime >= :start
ORDER BY ms.startDateTime ASC
""")
List<MentorSlot> findAvailableSlots(
@Param("mentorId") Long mentorId,
@Param("start") LocalDateTime start,
@Param("end") LocalDateTime end
);

// TODO: 현재는 시간 겹침만 체크, 추후 1:N 구조 시 활성 예약 기준으로 변경
@Query("""
SELECT CASE WHEN COUNT(ms) > 0
Expand Down Expand Up @@ -43,6 +75,4 @@ boolean existsOverlappingExcept(
@Param("start") LocalDateTime start,
@Param("end") LocalDateTime end
);

long countByMentorId(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.time.Duration;
import java.time.LocalDateTime;

public class MentorSlotValidator {
public class DateTimeValidator {

private static final int MIN_SLOT_DURATION = 20;

Expand All @@ -19,10 +19,13 @@ public static void validateNotNull(LocalDateTime start, LocalDateTime end) {
}
}

public static void validateTimeRange(LocalDateTime start, LocalDateTime end) {
public static void validateEndTimeAfterStart(LocalDateTime start, LocalDateTime end) {
if (!end.isAfter(start)) {
throw new ServiceException(MentorSlotErrorCode.END_TIME_BEFORE_START);
}
}

public static void validateStartTimeNotInPast(LocalDateTime start) {
if (start.isBefore(LocalDateTime.now())) {
throw new ServiceException(MentorSlotErrorCode.START_TIME_IN_PAST);
}
Expand All @@ -35,9 +38,16 @@ public static void validateMinimumDuration(LocalDateTime start, LocalDateTime en
}
}

public static void validateTime(LocalDateTime start, LocalDateTime end) {
validateNotNull(start, end);
validateEndTimeAfterStart(start, end);
}

public static void validateTimeSlot(LocalDateTime start, LocalDateTime end) {
validateNotNull(start, end);
validateTimeRange(start, end);
validateEndTimeAfterStart(start, end);

validateStartTimeNotInPast(start);
validateMinimumDuration(start, end);
}
}
Loading