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 @@ -37,4 +37,4 @@ public record LessonDetailResponseDto(
LocalDateTime updatedAt,
List<String> lessonImages
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ public static MyLessonApplicationListResponseDto toDtoListWithCount(List<LessonA
count
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -20,6 +21,8 @@
import com.threestar.trainus.domain.lesson.teacher.dto.LessonApplicationListWrapperDto;
import com.threestar.trainus.domain.lesson.teacher.dto.LessonCreateRequestDto;
import com.threestar.trainus.domain.lesson.teacher.dto.LessonResponseDto;
import com.threestar.trainus.domain.lesson.teacher.dto.LessonUpdateRequestDto;
import com.threestar.trainus.domain.lesson.teacher.dto.LessonUpdateResponseDto;
import com.threestar.trainus.domain.lesson.teacher.dto.ParticipantListResponseDto;
import com.threestar.trainus.domain.lesson.teacher.dto.ParticipantListWrapperDto;
import com.threestar.trainus.domain.lesson.teacher.entity.ApplicationAction;
Expand Down Expand Up @@ -150,4 +153,15 @@ public ResponseEntity<PagedResponse<CreatedLessonListWrapperDto>> getCreatedLess
return PagedResponse.ok("개설한 레슨 목록 조회 완료.", wrapperDto, responseDto.count(), HttpStatus.OK);
}

@PatchMapping("/lessons/{lessonId}")
@Operation(summary = "레슨 수정 api", description = "레슨 정보를 수정")
public ResponseEntity<BaseResponse<LessonUpdateResponseDto>> updateLesson(
@PathVariable Long lessonId,
@Valid @RequestBody LessonUpdateRequestDto requestDto,
@LoginUser Long loginUserId) {

LessonUpdateResponseDto responseDto = adminLessonService.updateLesson(lessonId, requestDto, loginUserId);
return BaseResponse.ok("레슨이 수정되었습니다.", responseDto, HttpStatus.OK);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,4 @@ public record LessonCreateRequestDto(
List<String> lessonImages

) {

public LessonCreateRequestDto {
// null 체크 등 추가 검증 로직
if (lessonImages == null) {
lessonImages = List.of(); // 빈 리스트로 초기화
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.threestar.trainus.domain.lesson.teacher.dto;

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

import com.threestar.trainus.domain.lesson.teacher.entity.Category;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;

/**
* 레슨 수정 요청 데이터
*/
public record LessonUpdateRequestDto(

@Size(max = 50, message = "레슨명은 50자 이내여야 합니다.")
String lessonName,

@Size(max = 255, message = "레슨 설명은 255자 이내여야 합니다.")
String description,

Category category,

@Min(value = 0, message = "가격은 0원 이상이어야 합니다.")
Integer price,

@Min(value = 1, message = "최대 참가 인원은 1명 이상이어야 합니다.")
@Max(value = 100, message = "최대 참가 인원은 100명 이하여야 합니다.")
Integer maxParticipants,

LocalDateTime startAt,

LocalDateTime endAt,

LocalDateTime openTime,

Boolean openRun,

@Size(max = 10, message = "시/도는 10자 이하여야 합니다.")
String city,

@Size(max = 10, message = "시/군/구는 10자 이하여야 합니다.")
String district,

@Size(max = 10, message = "읍/면/동은 10자 이하여야 합니다.")
String dong,

@Size(max = 25, message = "상세주소는 25자 이하여야 합니다.")
String addressDetail,

@Size(max = 5, message = "이미지는 최대 5장까지 첨부 가능합니다.")
List<String> lessonImages

) {

// 레슨이름, 설명, 이미지는 항상 수정가능
public boolean hasBasicInfoChanges() {
return lessonName != null || description != null || (lessonImages != null && !lessonImages.isEmpty());
}

// 제한된 필드들은 수정(참가자 없을때만 가능)
public boolean hasRestrictedChanges() {
return category != null || price != null || startAt != null || endAt != null || openTime != null
|| openRun != null || city != null || district != null || dong != null || addressDetail != null;
}

//시간 관련 필드 수정 체크
public boolean hasTimeChanges() {
return startAt != null || endAt != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.threestar.trainus.domain.lesson.teacher.dto;

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

import com.threestar.trainus.domain.lesson.teacher.entity.Category;
import com.threestar.trainus.domain.lesson.teacher.entity.LessonStatus;

import lombok.Builder;

/**
* 레슨 수정 응답 데이터
*/
@Builder
public record LessonUpdateResponseDto(
Long id,
String lessonName,
String description,
Long lessonLeader,
Category category,
Integer price,
Integer maxParticipants,
LocalDateTime startAt,
LocalDateTime endAt,
LocalDateTime openTime,
Boolean openRun,
String city,
String district,
String dong,
String addressDetail,
LessonStatus status,
LocalDateTime createdAt,
LocalDateTime updatedAt,
List<String> lessonImages
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.time.LocalDateTime;

import com.threestar.trainus.global.entity.BaseDateEntity;
import com.threestar.trainus.global.exception.domain.ErrorCode;
import com.threestar.trainus.global.exception.handler.BusinessException;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -130,4 +132,83 @@ public void incrementParticipantCount() {
}
}

//레슨 이름 수정
public void updateLessonName(String lessonName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setter 없이 적절하게 설계된 것 같습니다.
requestDto를 받아 update()메서드로 수정 메서드들을 하나로 묶는 방법도 있을 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update()메서드안에서 조건에 대한 검증을 또 해야 되는데 이방법이 더 복잡해 질것 같아서 개별 메소드로 설계하였습니당!

if (lessonName != null && !lessonName.trim().isEmpty()) {
this.lessonName = lessonName;
}
}

//레슨 설명 수정
public void updateDescription(String description) {
if (description != null && !description.trim().isEmpty()) {
this.description = description;
}
}

//카테고리 수정
public void updateCategory(Category category) {
if (category != null) {
this.category = category;
}
}

//가격 수정
public void updatePrice(Integer price) {
if (price != null) {
this.price = price;
}
}

//최대 참가자수 수정(참가자가 이미 있는 경우는 참가자수 증가만 가능)
public void updateMaxParticipants(Integer newMaxParticipants, boolean hasParticipants) {
if (newMaxParticipants != null) {
if (hasParticipants && newMaxParticipants < this.maxParticipants) {
throw new BusinessException(ErrorCode.LESSON_MAX_PARTICIPANTS_CANNOT_DECREASE);
}
this.maxParticipants = newMaxParticipants;
}
}

//레슨 시간 수정
public void updateLessonTime(LocalDateTime startAt, LocalDateTime endAt) {
if (startAt != null) {
this.startAt = startAt;
}
if (endAt != null) {
this.endAt = endAt;
}
}

//오픈시각 수정
public void updateOpenTime(LocalDateTime openTime) {
this.openTime = openTime;
}

//참여방식 수정
public void updateOpenRun(Boolean openRun) {
if (openRun != null) {
this.openRun = openRun;
}
}

//지역정보 수정
public void updateLocation(String city, String district, String dong) {
if (city != null && !city.trim().isEmpty()) {
this.city = city;
}
if (district != null && !district.trim().isEmpty()) {
this.district = district;
}
if (dong != null && !dong.trim().isEmpty()) {
this.dong = dong;
}
}

//상세주소 수정
public void updateAddressDetail(String addressDetail) {
if (addressDetail != null && !addressDetail.trim().isEmpty()) {
this.addressDetail = addressDetail;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.threestar.trainus.domain.lesson.student.dto.LessonDetailResponseDto;
import com.threestar.trainus.domain.lesson.teacher.dto.LessonCreateRequestDto;
import com.threestar.trainus.domain.lesson.teacher.dto.LessonResponseDto;
import com.threestar.trainus.domain.lesson.teacher.dto.LessonUpdateResponseDto;
import com.threestar.trainus.domain.lesson.teacher.entity.Lesson;
import com.threestar.trainus.domain.lesson.teacher.entity.LessonImage;
import com.threestar.trainus.domain.profile.entity.Profile;
Expand Down Expand Up @@ -98,4 +99,34 @@ public static LessonDetailResponseDto toLessonDetailDto(
.lessonImages(lessonImages)
.build();
}

//레슨 수정
public static LessonUpdateResponseDto toUpdateResponseDto(Lesson lesson, List<LessonImage> lessonImages) {
// 이미지 엔티티 목록에서 URL만 추출
List<String> imageUrls = lessonImages.stream()
.map(LessonImage::getImageUrl)
.toList();

return LessonUpdateResponseDto.builder()
.id(lesson.getId())
.lessonName(lesson.getLessonName())
.description(lesson.getDescription())
.lessonLeader(lesson.getLessonLeader())
.category(lesson.getCategory())
.price(lesson.getPrice())
.maxParticipants(lesson.getMaxParticipants())
.startAt(lesson.getStartAt())
.endAt(lesson.getEndAt())
.openTime(lesson.getOpenTime())
.openRun(lesson.getOpenRun())
.city(lesson.getCity())
.district(lesson.getDistrict())
.dong(lesson.getDong())
.addressDetail(lesson.getAddressDetail())
.status(lesson.getStatus())
.createdAt(lesson.getCreatedAt())
.updatedAt(lesson.getUpdatedAt())
.lessonImages(imageUrls)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,43 @@ public interface LessonRepository extends JpaRepository<Lesson, Long> {
Optional<Lesson> findByIdAndDeletedAtIsNull(Long lessonId);

// 중복 레슨 검증(같은 강사가 같은 이름과 시작시간으로 레슨 생성했는지 체크)
@Query("SELECT COUNT(l) > 0 FROM Lesson l WHERE " + "l.lessonLeader = :lessonLeader AND "
+ "l.lessonName = :lessonName AND " + "l.startAt = :startAt AND " + "l.deletedAt IS NULL")
@Query("""
SELECT COUNT(l) > 0 FROM Lesson l
WHERE l.lessonLeader = :lessonLeader
AND l.lessonName = :lessonName
AND l.startAt = :startAt
AND l.deletedAt IS NULL
""")
boolean existsDuplicateLesson(@Param("lessonLeader") Long lessonLeader,
@Param("lessonName") String lessonName,
@Param("startAt") LocalDateTime startAt);

// 시간 겹침 검증(같은 강사가 동일 시간대에 다른 레슨이 있는지 체크)
@Query(
"SELECT COUNT(l) > 0 FROM Lesson l WHERE " + "l.lessonLeader = :lessonLeader AND " + "l.deletedAt IS NULL AND "
+ "(l.startAt < :endAt AND l.endAt > :startAt)")
// 시간 겹침 검증(같은 강사가 동일 시간대에 다른 레슨이 있는지 체크) ->레슨생성시에 사용
@Query("""
SELECT COUNT(l) > 0 FROM Lesson l
WHERE l.lessonLeader = :lessonLeader
AND l.deletedAt IS NULL
AND (l.startAt < :endAt AND l.endAt > :startAt)
""")
boolean hasTimeConflictLesson(@Param("lessonLeader") Long lessonLeader,
@Param("startAt") LocalDateTime startAt,
@Param("endAt") LocalDateTime endAt);

//시간 겹침 검증 ->레슨 수정시 사용 (현재 수정중인 레슨 제외)
@Query("""
SELECT COUNT(l) > 0 FROM Lesson l
WHERE l.lessonLeader = :lessonLeader
AND l.deletedAt IS NULL
AND l.id != :excludeLessonId
AND (l.startAt < :endAt AND l.endAt > :startAt)
""")
boolean hasTimeConflictForUpdate(
@Param("lessonLeader") Long lessonLeader,
@Param("startAt") LocalDateTime startAt,
@Param("endAt") LocalDateTime endAt,
@Param("excludeLessonId") Long excludeLessonId
);

// 강사가 개설한 레슨 목록 조회 (페이징)
Page<Lesson> findByLessonLeaderAndDeletedAtIsNull(Long lessonLeader, Pageable pageable);

Expand All @@ -51,8 +74,7 @@ Page<Lesson> findByLessonLeaderAndStatusAndDeletedAtIsNull(Long lessonLeader, Le
:search IS NULL OR
LOWER(l.lessonName) LIKE LOWER(CONCAT('%', :search, '%'))
)
"""
)
""")
Page<Lesson> findBySearchConditions(
@Param("category") Category category,
@Param("city") String city,
Expand Down
Loading