diff --git a/backend/build.gradle b/backend/build.gradle index 93c4b7ad..d03d17ea 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -31,6 +31,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' implementation 'ch.qos.logback:logback-classic' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' //Swagger + implementation 'org.springframework.boot:spring-boot-starter-validation' // security implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/backend/src/main/java/org/example/backend/board/controller/BoardController.java b/backend/src/main/java/org/example/backend/board/controller/BoardController.java index 039081c4..f8689ef7 100644 --- a/backend/src/main/java/org/example/backend/board/controller/BoardController.java +++ b/backend/src/main/java/org/example/backend/board/controller/BoardController.java @@ -36,7 +36,7 @@ public class BoardController { @Operation(summary = "게시판 생성 API 입니다.", description = "게시판 생성입니다.") @PostMapping(consumes = "multipart/form-data") - public ResponseEntity createBoard(@RequestPart(value = "boardReqDto") BoardReqDto boardReqDto, + public ResponseEntity createBoard(@RequestPart(value = "boardReqDto") @Valid BoardReqDto boardReqDto, @RequestPart(value = "boardFiles", required = false) List multipartFileList) { Long boardId = boardService.saveBoard(boardReqDto, multipartFileList); return new ResponseEntity<>(boardId, HttpStatus.OK); @@ -44,7 +44,7 @@ public ResponseEntity createBoard(@RequestPart(value = "boardReqDto") Boar @Operation(summary = "모든 게시판 조회 API", description = "모든 게시판의 리스트 반환") @GetMapping - public ResponseDto> getAllBoards(@Valid @ModelAttribute PageRequestDto pageRequest) { + public ResponseDto> getAllBoards(@ModelAttribute @Valid PageRequestDto pageRequest) { Page boardList = boardService.getAllBoards(pageRequest.toPageable()); return ResponseDto.ok(boardList.getNumber(), boardList.getTotalPages(), boardList.getContent()); @@ -52,7 +52,7 @@ public ResponseDto> getAllBoards(@Valid @ModelAttribute PageRe @Operation(summary = "카테고리별 게시판 조회 API", description = "카테고리별 게시판 리스트 반환") @GetMapping("/category/{category}") public ResponseDto> getBoardsByCategory(@PathVariable("category") Category category, - @Valid @ModelAttribute PageRequestDto pageRequest) { + @ModelAttribute @Valid PageRequestDto pageRequest) { Page boardList = boardService.getBoardsByCategory(category, pageRequest.toPageable()); return ResponseDto.ok(boardList.getNumber(), boardList.getTotalPages(), boardList.getContent()); diff --git a/backend/src/main/java/org/example/backend/board/domain/dto/BoardReqDto.java b/backend/src/main/java/org/example/backend/board/domain/dto/BoardReqDto.java index 3a144f6f..c5fc177d 100644 --- a/backend/src/main/java/org/example/backend/board/domain/dto/BoardReqDto.java +++ b/backend/src/main/java/org/example/backend/board/domain/dto/BoardReqDto.java @@ -1,6 +1,7 @@ package org.example.backend.board.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import java.util.List; import lombok.AccessLevel; import lombok.Builder; @@ -10,11 +11,20 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class BoardReqDto { + + @NotBlank(message = "제목은 필수 입력값입니다.") private String title; + + @NotBlank(message = "내용은 필수 입력값입니다.") private String content; + + @NotBlank(message = "작성자는 필수 입력값입니다.") private String writer; + @Schema(hidden = true) private List fileList; + + @NotBlank(message = "카테고리는 필수 입력값입니다.") private String category; @Builder @@ -28,4 +38,4 @@ private BoardReqDto(String title, String content, String writer, String category public void setFileList(List fileList) { this.fileList = fileList; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/org/example/backend/board/exception/BoardExceptionType.java b/backend/src/main/java/org/example/backend/board/exception/BoardExceptionType.java index 914ac42b..a0c32c62 100644 --- a/backend/src/main/java/org/example/backend/board/exception/BoardExceptionType.java +++ b/backend/src/main/java/org/example/backend/board/exception/BoardExceptionType.java @@ -10,10 +10,6 @@ @RequiredArgsConstructor public enum BoardExceptionType implements BaseExceptionType { NOT_FOUND_BOARD(NOT_FOUND, "게시글을 찾을 수 없습니다"), - REQUIRED_TITLE(BAD_REQUEST, "제목은 필수 입력값입니다."), - REQUIRED_CONTENT(BAD_REQUEST, "내용은 필수 입력값입니다."), - REQUIRED_CATEGORY(BAD_REQUEST, "카티고리는 필수 입력값입니다"), - REQUIRED_DEPARTMENT_ID(BAD_REQUEST, "부서 ID는 필수 입력값입니다."), REQUIRED_FILE(BAD_REQUEST, "파일이 비어 있습니다.") ; diff --git a/backend/src/main/java/org/example/backend/board/service/BoardService.java b/backend/src/main/java/org/example/backend/board/service/BoardService.java index 5d16e68a..663b1600 100644 --- a/backend/src/main/java/org/example/backend/board/service/BoardService.java +++ b/backend/src/main/java/org/example/backend/board/service/BoardService.java @@ -29,27 +29,12 @@ public class BoardService { @Transactional public Long saveBoard(BoardReqDto boardReqDto, List multipartFileList) { - validateUserRequiredFields(boardReqDto); - fileUpload(boardReqDto, multipartFileList); Board board = Board.of(boardReqDto); Board savedBoard = boardRepository.save(board); return savedBoard.getId(); } - - private void validateUserRequiredFields(BoardReqDto dto) { - if (dto.getTitle() == null || dto.getTitle().isEmpty()) { - throw new BoardException(BoardExceptionType.REQUIRED_TITLE); - } - if (dto.getContent() == null || dto.getContent().isEmpty()) { - throw new BoardException(BoardExceptionType.REQUIRED_CONTENT); - } - if (dto.getCategory() == null || dto.getCategory().isEmpty() || !Category.contains(dto.getCategory())) { - throw new BoardException(BoardExceptionType.REQUIRED_CATEGORY); - } - } - public BoardResDto getBoard(Long boardId) { Board board = findBoardById(boardId); return BoardResDto.of(board); diff --git a/backend/src/main/java/org/example/backend/common/dto/PageRequestDto.java b/backend/src/main/java/org/example/backend/common/dto/PageRequestDto.java index 5f1c06dd..5af99c45 100644 --- a/backend/src/main/java/org/example/backend/common/dto/PageRequestDto.java +++ b/backend/src/main/java/org/example/backend/common/dto/PageRequestDto.java @@ -2,6 +2,7 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Pattern; import lombok.Getter; import lombok.Setter; import org.springframework.data.domain.PageRequest; @@ -11,17 +12,22 @@ @Getter @Setter public class PageRequestDto { - @Min(0) + + @Min(value = 0, message = "페이지 번호는 0 이상이어야 합니다.") private int page = 0; - @Min(1) @Max(100) + @Min(value = 1, message = "페이지 크기는 최소 1이어야 합니다.") + @Max(value = 100, message = "페이지 크기는 최대 100이어야 합니다.") private int size = 10; + @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "정렬 기준은 알파벳, 숫자, 또는 언더스코어(_)만 포함할 수 있습니다.") private String sort = "id"; + + @Pattern(regexp = "^(ASC|DESC)$", message = "정렬 방향은 'ASC' 또는 'DESC'만 가능합니다.") private String sortDirection = "ASC"; public Pageable toPageable() { Sort.Direction direction = Sort.Direction.valueOf(sortDirection.toUpperCase()); return PageRequest.of(page, size, direction, sort); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/org/example/backend/common/exception/ExceptionControllerAdvice.java b/backend/src/main/java/org/example/backend/common/exception/ExceptionControllerAdvice.java index d010905d..5e3fc9a7 100644 --- a/backend/src/main/java/org/example/backend/common/exception/ExceptionControllerAdvice.java +++ b/backend/src/main/java/org/example/backend/common/exception/ExceptionControllerAdvice.java @@ -5,12 +5,9 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.backend.common.exception.paging.InvalidPaginationParameterException; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -27,14 +24,6 @@ ResponseEntity handleException(HttpServletRequest request, Ba .body(new ExceptionResponse(type.errorMessage())); } - @ExceptionHandler(MissingServletRequestParameterException.class) - ResponseEntity handleMissingParams(MissingServletRequestParameterException e) { - String errorMessage = e.getParameterName() + " 값이 누락 되었습니다."; - log.info("잘못된 요청이 들어왔습니다. 내용: {}", errorMessage); - return ResponseEntity.status(BAD_REQUEST) - .body(new ExceptionResponse(errorMessage)); - } - @ExceptionHandler(Exception.class) ResponseEntity handleException(HttpServletRequest request, Exception e) { log.error("예상하지 못한 예외가 발생했습니다. URI: {}, ", request.getRequestURI(), e); @@ -49,26 +38,9 @@ ResponseEntity handleHttpMessageNotReadableException(HttpServ .body(new ExceptionResponse("요청 본문이 비어 있습니다.")); } - @ExceptionHandler(DataIntegrityViolationException.class) - public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException e) { - log.error("DataIntegrityViolationException occurred: ", e); - - //참조 제약 조건 위반 확인 - if (e.getMessage().contains("FOREIGN KEY") || e.getMessage().contains("foreign key")) { - return ResponseEntity.badRequest() - .body(new ExceptionResponse("해당 데이터를 삭제하기 전에 연관된 데이터를 먼저 삭제해주세요.")); - } - - return ResponseEntity.badRequest() - .body(new ExceptionResponse("데이터 처리 중 오류가 발생했습니다.")); - } - - - @ExceptionHandler({MethodArgumentNotValidException.class}) - public ResponseEntity validException( - MethodArgumentNotValidException e) { - + @ExceptionHandler(BindException.class) + public ResponseEntity bindException(BindException e) { return ResponseEntity.status(BAD_REQUEST) - .body(new ExceptionResponse(e.getMessage())); + .body(new ExceptionResponse(e.getBindingResult().getAllErrors().get(0).getDefaultMessage())); } } \ No newline at end of file diff --git a/backend/src/main/java/org/example/backend/common/exception/paging/InvalidPaginationParameterException.java b/backend/src/main/java/org/example/backend/common/exception/paging/InvalidPaginationParameterException.java deleted file mode 100644 index 5b0a3dc8..00000000 --- a/backend/src/main/java/org/example/backend/common/exception/paging/InvalidPaginationParameterException.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.example.backend.common.exception.paging; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.example.backend.common.exception.BaseException; - -@Getter -@RequiredArgsConstructor -public class InvalidPaginationParameterException extends BaseException { - private final InvalidPaginationParameterExceptionType exceptionType; - - @Override - public InvalidPaginationParameterExceptionType exceptionType(){ - return exceptionType; - } -} diff --git a/backend/src/main/java/org/example/backend/common/exception/paging/InvalidPaginationParameterExceptionType.java b/backend/src/main/java/org/example/backend/common/exception/paging/InvalidPaginationParameterExceptionType.java deleted file mode 100644 index 8107fe49..00000000 --- a/backend/src/main/java/org/example/backend/common/exception/paging/InvalidPaginationParameterExceptionType.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.example.backend.common.exception.paging; - -import lombok.RequiredArgsConstructor; -import org.example.backend.common.exception.BaseExceptionType; -import org.springframework.http.HttpStatus; - -@RequiredArgsConstructor -public enum InvalidPaginationParameterExceptionType implements BaseExceptionType { - INVALID_PAGE("페이지 번호는 0 이상이어야 합니다.", HttpStatus.BAD_REQUEST), - INVALID_SIZE("페이지 크기는 양의 정수여야 합니다.", HttpStatus.BAD_REQUEST), - UNKNOWN_PARAMETER("잘못된 paging 파라미터입니다.", HttpStatus.BAD_REQUEST); - - private final String message; - private final HttpStatus httpStatus; - - - @Override - public String errorMessage() { - return message; - } - - @Override - public HttpStatus httpStatus() { - return httpStatus; - } -} \ No newline at end of file diff --git a/backend/src/main/java/org/example/backend/department/domain/dto/Department/DepartmentReqDto.java b/backend/src/main/java/org/example/backend/department/domain/dto/Department/DepartmentReqDto.java index 3d749d08..db82560c 100644 --- a/backend/src/main/java/org/example/backend/department/domain/dto/Department/DepartmentReqDto.java +++ b/backend/src/main/java/org/example/backend/department/domain/dto/Department/DepartmentReqDto.java @@ -1,5 +1,7 @@ package org.example.backend.department.domain.dto.Department; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -8,13 +10,28 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class DepartmentReqDto { + + @NotBlank(message = "한글 이름은 필수 입력값입니다.") + @Size(max = 255, message = "한글 이름은 최대 255자까지 입력 가능합니다.") private String koreanName; + + @Size(max = 255, message = "영문 이름은 최대 255자까지 입력 가능합니다.") private String englishName; + private String intro; + + @Size(max = 15, message = "전화번호는 최대 15자까지 입력 가능합니다.") private String phoneN; + + @Size(max = 255, message = "위치는 최대 255자까지 입력 가능합니다.") private String location; + private String educationalObjective; + + @Size(max = 255, message = "근무 시간은 최대 255자까지 입력 가능합니다.") private String workHour; + + @Size(max = 2083, message = "지도 링크는 최대 2083자까지 입력 가능합니다.") private String map; @Builder diff --git a/backend/src/main/java/org/example/backend/department/exception/DepartmentExceptionType.java b/backend/src/main/java/org/example/backend/department/exception/DepartmentExceptionType.java index 285b32a9..be5d9e39 100644 --- a/backend/src/main/java/org/example/backend/department/exception/DepartmentExceptionType.java +++ b/backend/src/main/java/org/example/backend/department/exception/DepartmentExceptionType.java @@ -9,7 +9,6 @@ @RequiredArgsConstructor public enum DepartmentExceptionType implements BaseExceptionType { NOT_FOUND_DEPARTMENT(NOT_FOUND, "부서을 찾을 수 없습니다"), - REQUIRED_KOREAN_NAME(NOT_FOUND, "한글 이름은 필수 입력값입니다."), ; private final HttpStatus httpStatus; diff --git a/backend/src/main/java/org/example/backend/department/service/DepartmentService.java b/backend/src/main/java/org/example/backend/department/service/DepartmentService.java index 97985ab6..d04253b1 100644 --- a/backend/src/main/java/org/example/backend/department/service/DepartmentService.java +++ b/backend/src/main/java/org/example/backend/department/service/DepartmentService.java @@ -10,7 +10,6 @@ import org.springframework.transaction.annotation.Transactional; import static org.example.backend.department.exception.DepartmentExceptionType.NOT_FOUND_DEPARTMENT; -import static org.example.backend.department.exception.DepartmentExceptionType.REQUIRED_KOREAN_NAME; @Service @RequiredArgsConstructor @@ -20,18 +19,11 @@ public class DepartmentService { @Transactional public Long saveDepartment(DepartmentReqDto departmentReqDto) { - validateDepartmentRequiredFields(departmentReqDto); Department department = Department.of(departmentReqDto); departmentRepository.save(department); return department.getId(); } - private void validateDepartmentRequiredFields(DepartmentReqDto dto) { - if (dto.getKoreanName() == null || dto.getKoreanName().isEmpty()) { - throw new DepartmentException(REQUIRED_KOREAN_NAME); - } - } - public DepartmentResDto getDepartment(Long departmentId) { Department department = findDepartmentById(departmentId); return DepartmentResDto.of(department); diff --git a/backend/src/main/java/org/example/backend/global/config/web/CorsMvcConfig.java b/backend/src/main/java/org/example/backend/global/config/web/CorsMvcConfig.java deleted file mode 100644 index 993b5a44..00000000 --- a/backend/src/main/java/org/example/backend/global/config/web/CorsMvcConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.example.backend.global.config.web; - -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -public class CorsMvcConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry corsRegistry) { - - corsRegistry.addMapping("/**") - .allowedOrigins("http://localhost:3000"); - } -} \ No newline at end of file diff --git a/backend/src/main/java/org/example/backend/global/config/web/WebConfig.java b/backend/src/main/java/org/example/backend/global/config/web/WebConfig.java index febbe31f..cb3c3a55 100644 --- a/backend/src/main/java/org/example/backend/global/config/web/WebConfig.java +++ b/backend/src/main/java/org/example/backend/global/config/web/WebConfig.java @@ -3,6 +3,7 @@ import org.example.backend.global.aop.AuthUserResolver; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @@ -20,4 +21,11 @@ public WebConfig(AuthUserResolver authUserResolver) { public void addArgumentResolvers(List resolvers) { resolvers.add(authUserResolver); } + + @Override + public void addCorsMappings(CorsRegistry corsRegistry) { + + corsRegistry.addMapping("/**") + .allowedOrigins("http://localhost:3000"); + } } \ No newline at end of file diff --git a/backend/src/main/java/org/example/backend/professor/controller/ProfessorController.java b/backend/src/main/java/org/example/backend/professor/controller/ProfessorController.java index deed2376..031ced5a 100644 --- a/backend/src/main/java/org/example/backend/professor/controller/ProfessorController.java +++ b/backend/src/main/java/org/example/backend/professor/controller/ProfessorController.java @@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -36,7 +35,7 @@ public class ProfessorController { @Operation(summary = "교수 생성 API", description = "교수 생성") @PostMapping(consumes = "multipart/form-data") public ResponseEntity createProfessor( - @RequestPart(value = "professorReqDto") ProfessorReqDto professorReqDto, + @RequestPart(value = "professorReqDto") @Valid ProfessorReqDto professorReqDto, @RequestPart(value = "profileImage", required = false) MultipartFile multipartFile ) { Long professorId = professorService.saveProfessor(professorReqDto, multipartFile); diff --git a/backend/src/main/java/org/example/backend/professor/domain/dto/professor/ProfessorReqDto.java b/backend/src/main/java/org/example/backend/professor/domain/dto/professor/ProfessorReqDto.java index c434c4cd..d80b8f2b 100644 --- a/backend/src/main/java/org/example/backend/professor/domain/dto/professor/ProfessorReqDto.java +++ b/backend/src/main/java/org/example/backend/professor/domain/dto/professor/ProfessorReqDto.java @@ -1,34 +1,49 @@ package org.example.backend.professor.domain.dto.professor; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ProfessorReqDto { @Schema(description = "교수 이름", example = "김세종") + @NotBlank(message = "이름은 필수 입력값입니다.") + @Size(max = 50, message = "이름은 최대 50자까지 입력 가능합니다.") private String name; @Schema(description = "교수 전공", example = "신경생물학") + @NotBlank(message = "전공은 필수 입력값입니다.") + @Size(max = 100, message = "전공은 최대 100자까지 입력 가능합니다.") private String major; @Schema(description = "교수 전화번호", example = "010-1234-5678") + @NotBlank(message = "전화번호는 필수 입력값입니다.") + @Pattern(regexp = "010-\\d{4}-\\d{4}", message = "전화번호는 '010-1234-5678' 형식이어야 합니다.") private String phoneN; @Schema(description = "교수 이메일", example = "example@sju.ac.kr") + @NotBlank(message = "이메일은 필수 입력값입니다.") + @Email(message = "올바른 이메일 형식이어야 합니다.") private String email; @Schema(description = "교수 직위", example = "정교수") + @NotBlank(message = "직위는 필수 입력값입니다.") + @Size(max = 30, message = "직위는 최대 30자까지 입력 가능합니다.") private String position; @Schema(description = "교수 홈페이지", example = "https://www.sju.ac.kr/professor/example") + @Size(max = 200, message = "홈페이지 URL은 최대 200자까지 입력 가능합니다.") private String homepage; @Schema(description = "교수 연구실 위치", example = "충무관 1128호") + @Size(max = 100, message = "연구실 위치는 최대 100자까지 입력 가능합니다.") private String lab; @Schema(hidden = true) @@ -62,4 +77,4 @@ public static ProfessorReqDto of(String name, String major, String phoneN, Strin public void setProfileImage(String profileImage) { this.profileImage = profileImage; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/org/example/backend/professor/exception/ProfessorExceptionType.java b/backend/src/main/java/org/example/backend/professor/exception/ProfessorExceptionType.java index 5d739bb9..893af2e4 100644 --- a/backend/src/main/java/org/example/backend/professor/exception/ProfessorExceptionType.java +++ b/backend/src/main/java/org/example/backend/professor/exception/ProfessorExceptionType.java @@ -12,9 +12,6 @@ public enum ProfessorExceptionType implements BaseExceptionType { NOT_FOUND_PROFESSOR(NOT_FOUND, "교수를 찾을 수 없습니다"), NOT_FOUND_FILE(NOT_FOUND, "파일을 찾을 수 없습니다"), - - REQUIRED_NAME(BAD_REQUEST, "이름은 필수 입력값입니다."), - DUPLICATE_PHONE(BAD_REQUEST, "전화번호가 이미 존재합니다."), DUPLICATE_EMAIL(BAD_REQUEST, "이메일이 이미 존재합니다.") ; diff --git a/backend/src/main/java/org/example/backend/professor/service/ProfessorService.java b/backend/src/main/java/org/example/backend/professor/service/ProfessorService.java index bf2bdb50..7774f8fd 100644 --- a/backend/src/main/java/org/example/backend/professor/service/ProfessorService.java +++ b/backend/src/main/java/org/example/backend/professor/service/ProfessorService.java @@ -29,7 +29,6 @@ public class ProfessorService { @Transactional public Long saveProfessor(ProfessorReqDto professorReqDto, MultipartFile multipartFile) { - validateUserRequiredFields(professorReqDto); validateUserUniqueFields(professorReqDto); if (multipartFile != null && !multipartFile.isEmpty()) { @@ -43,12 +42,6 @@ public Long saveProfessor(ProfessorReqDto professorReqDto, MultipartFile multipa return savedProfessor.getId(); } - private void validateUserRequiredFields(ProfessorReqDto dto) { - if (dto.getName() == null || dto.getName().isEmpty()) { - throw new ProfessorException(ProfessorExceptionType.REQUIRED_NAME); - } - } - private void validateUserUniqueFields(ProfessorReqDto dto) { if (professorRepository.existsByPhoneN(dto.getPhoneN())) { throw new ProfessorException(ProfessorExceptionType.DUPLICATE_PHONE); diff --git a/backend/src/main/java/org/example/backend/reservation/controller/ReservationController.java b/backend/src/main/java/org/example/backend/reservation/controller/ReservationController.java index 8e4b75f9..dd60be43 100644 --- a/backend/src/main/java/org/example/backend/reservation/controller/ReservationController.java +++ b/backend/src/main/java/org/example/backend/reservation/controller/ReservationController.java @@ -1,5 +1,6 @@ package org.example.backend.reservation.controller; +import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -31,7 +32,7 @@ public class ReservationController { @PostMapping("/{roomId}/reservation") public ResponseEntity> createReservation( @PathVariable(value = "roomId") Long roomId, - @RequestBody ReservationReqDto reqDto) { + @RequestBody @Valid ReservationReqDto reqDto) { User user = userRepository.findById(1L) .orElseThrow(() -> new UserException(UserExceptionType.NOT_FOUND_USER)); List resDtos = reservationService.createReservation(roomId, reqDto, user); diff --git a/backend/src/main/java/org/example/backend/reservation/domain/RepetitionType.java b/backend/src/main/java/org/example/backend/reservation/domain/RepetitionType.java deleted file mode 100644 index a77c6e00..00000000 --- a/backend/src/main/java/org/example/backend/reservation/domain/RepetitionType.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.example.backend.reservation.domain; - -public enum RepetitionType { - DAILY("단일 일정"), - WEEKLY("주간 반복") - ; - - private final String description; - - RepetitionType(String description) { - this.description = description; - } - public String getDescription() { - return description; - } -} diff --git a/backend/src/main/java/org/example/backend/reservation/domain/Reservation.java b/backend/src/main/java/org/example/backend/reservation/domain/Reservation.java index 9406d0a1..d7a4e32b 100644 --- a/backend/src/main/java/org/example/backend/reservation/domain/Reservation.java +++ b/backend/src/main/java/org/example/backend/reservation/domain/Reservation.java @@ -45,10 +45,6 @@ public class Reservation extends BaseEntity { @Column(name = "etc") private String etc; - @Enumerated(EnumType.STRING) - @Column(name = "repetition_type") - private RepetitionType repetitionType; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "room_id") private Room room; @@ -59,23 +55,21 @@ public class Reservation extends BaseEntity { @Builder private Reservation(LocalDateTime startTime, LocalDateTime endTime, ReservationPurpose purpose, - String etc, RepetitionType repetitionType, Room room, User user) { + String etc, Room room, User user) { this.startTime = startTime; this.endTime = endTime; this.purpose = purpose; this.etc = etc; - this.repetitionType = repetitionType; this.room = room; this.user = user; } public static Reservation of(ReservationReqDto dto, Room room, User user) { return Reservation.builder() - .startTime(TimeParsingUtils.formatterLocalDateTime(dto.getStartTime())) - .endTime(TimeParsingUtils.formatterLocalDateTime(dto.getEndTime())) - .purpose(ReservationPurpose.valueOf(dto.getDefaultPurpose())) + .startTime(TimeParsingUtils.formatterLocalDateTime(String.valueOf(dto.getStartTime()))) + .endTime(TimeParsingUtils.formatterLocalDateTime(String.valueOf(dto.getEndTime()))) + .purpose(ReservationPurpose.valueOf(dto.getPurpose())) .etc(dto.getEtc()) - .repetitionType(RepetitionType.valueOf(dto.getRepetitionType())) .room(room) .user(user) .build(); diff --git a/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationReqDto.java b/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationReqDto.java index 10b6da5a..eb665aac 100644 --- a/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationReqDto.java +++ b/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationReqDto.java @@ -1,50 +1,48 @@ package org.example.backend.reservation.domain.dto; - import com.fasterxml.jackson.annotation.JsonFormat; -import java.time.LocalDateTime; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.example.backend.common.utils.TimeParsingUtils; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ReservationReqDto { + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + @NotNull(message = "시작 시간은 필수 입력값입니다.") private String startTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + @NotNull(message = "종료 시간은 필수 입력값입니다.") private String endTime; + + @NotBlank(message = "목적은 필수 입력값입니다.") + @Size(max = 100, message = "목적은 최대 100자까지 입력 가능합니다.") private String purpose; + + @Size(max = 200, message = "기타 내용은 최대 200자까지 입력 가능합니다.") private String etc; - private String repetitionType; @Builder private ReservationReqDto(String startTime, String endTime, - String purpose, String etc, String repetitionType) { + String purpose, String etc) { this.startTime = startTime; this.endTime = endTime; this.purpose = purpose; this.etc = etc; - this.repetitionType = repetitionType; } - public static ReservationReqDto of(LocalDateTime startTime, LocalDateTime endTime, - String purpose, String repetitionType, String etc) { + public static ReservationReqDto of(String startTime, String endTime, + String purpose, String etc) { return ReservationReqDto.builder() - .startTime(TimeParsingUtils.formatterString(startTime)) - .endTime(TimeParsingUtils.formatterString(endTime)) + .startTime(startTime) + .endTime(endTime) .purpose(purpose) .etc(etc) - .repetitionType(repetitionType) .build(); } - - public boolean isWeeklyReservation() { - return !startTime.equals(endTime); - } - - public String getDefaultPurpose() { - return (purpose == null || purpose.isBlank()) ? "CLASS" : purpose; - } -} \ No newline at end of file +} diff --git a/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationResDto.java b/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationResDto.java index 5000ca12..0e01313e 100644 --- a/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationResDto.java +++ b/backend/src/main/java/org/example/backend/reservation/domain/dto/ReservationResDto.java @@ -19,20 +19,18 @@ public class ReservationResDto { private String endTime; private ReservationPurpose purpose; private String etc; - private String repetitionType; private Long roomId; private Long userId; @Builder private ReservationResDto(Long id, String startTime, String endTime, - ReservationPurpose purpose, String etc, String repetitionType, + ReservationPurpose purpose, String etc, Long roomId, Long userId) { this.id = id; this.startTime = startTime; this.endTime = endTime; this.purpose = purpose; this.etc = etc; - this.repetitionType = repetitionType; this.roomId = roomId; this.userId = userId; } @@ -44,7 +42,6 @@ public static ReservationResDto of(Reservation reservation) { .endTime(TimeParsingUtils.formatterString(reservation.getEndTime())) .purpose(reservation.getPurpose()) .etc(reservation.getEtc()) - .repetitionType(reservation.getRepetitionType().name()) .roomId(reservation.getRoom().getId()) .userId(reservation.getUser().getId()) .build(); diff --git a/backend/src/main/java/org/example/backend/reservation/exception/ReservationExceptionType.java b/backend/src/main/java/org/example/backend/reservation/exception/ReservationExceptionType.java index 603d9937..5305e913 100644 --- a/backend/src/main/java/org/example/backend/reservation/exception/ReservationExceptionType.java +++ b/backend/src/main/java/org/example/backend/reservation/exception/ReservationExceptionType.java @@ -13,7 +13,7 @@ public enum ReservationExceptionType implements BaseExceptionType { NOT_FOUND_RESERVATION(NOT_FOUND, "예약을 찾을 수 없습니다"), EXIST_ALREADY_RESERVATION(BAD_REQUEST, "해당 시간에 이미 승인된 예약이 존재합니다."), CONFLICT_TIMETABLE(BAD_REQUEST, "해당 시간에 정기 수업이 있어 예약이 불가능합니다."), - INVALID_TIME_ORDER(BAD_REQUEST, "시작 시간은 종료 시간보다 이후일 수 없습니다."), // 새 예외 + INVALID_TIME_ORDER(BAD_REQUEST, "시작 시간은 종료 시간보다 이후일 수 없습니다."), EXCEEDS_MAX_DURATION(BAD_REQUEST, "예약은 최대 2시간까지만 가능합니다."), PAST_TIME_RESERVATION(BAD_REQUEST, "과거의 시간으로 예약할 수 없습니다."), ; diff --git a/backend/src/main/java/org/example/backend/reservation/service/ReservationService.java b/backend/src/main/java/org/example/backend/reservation/service/ReservationService.java index 5c18c85d..21f4aefe 100644 --- a/backend/src/main/java/org/example/backend/reservation/service/ReservationService.java +++ b/backend/src/main/java/org/example/backend/reservation/service/ReservationService.java @@ -9,7 +9,6 @@ import java.util.ArrayList; import lombok.RequiredArgsConstructor; import org.example.backend.common.utils.TimeParsingUtils; -import org.example.backend.reservation.domain.RepetitionType; import org.example.backend.reservation.domain.ReservationPurpose; import org.example.backend.reservation.exception.ReservationException; import org.example.backend.room.domain.Room; @@ -38,42 +37,10 @@ public List createReservation(Long seminarRoomId, Reservation validateReservationOverlap(reqDto, seminarRoomId); - if (reqDto.isWeeklyReservation()) { - List weeklyReservations = createWeeklyReservations(reqDto, room, user); - reservationRepository.saveAll(weeklyReservations); - return weeklyReservations.stream() - .map(ReservationResDto::of) - .collect(Collectors.toList()); - } else { - Reservation reservation = Reservation.of(reqDto, room, user); - reservationRepository.save(reservation); - return List.of(ReservationResDto.of(reservation)); - } - } + Reservation reservation = Reservation.of(reqDto, room, user); + reservationRepository.save(reservation); + return List.of(ReservationResDto.of(reservation)); - private List createWeeklyReservations(ReservationReqDto reqDto, Room room, User user) { - List reservations = new ArrayList<>(); - LocalDateTime startDateTime = TimeParsingUtils.formatterLocalDateTime(reqDto.getStartTime()); - LocalDateTime endDateTime = TimeParsingUtils.formatterLocalDateTime(reqDto.getEndTime()); - - LocalDate startDate = startDateTime.toLocalDate(); - LocalDate endDate = endDateTime.toLocalDate(); - - while (!startDate.isAfter(endDate)) { - LocalDateTime weeklyStart = LocalDateTime.of(startDate, startDateTime.toLocalTime()); - LocalDateTime weeklyEnd = LocalDateTime.of(startDate, endDateTime.toLocalTime()); - reservations.add(Reservation.builder() - .startTime(weeklyStart) - .endTime(weeklyEnd) - .purpose(ReservationPurpose.valueOf(reqDto.getDefaultPurpose())) - .etc(reqDto.getEtc()) - .repetitionType(RepetitionType.WEEKLY) - .room(room) - .user(user) - .build()); - startDate = startDate.plusWeeks(1); - } - return reservations; } public List getReservationsByRoom(Long seminarRoomId) { diff --git a/backend/src/main/java/org/example/backend/seminar/controller/SeminarController.java b/backend/src/main/java/org/example/backend/seminar/controller/SeminarController.java index 1be4fa2e..69c1e7df 100644 --- a/backend/src/main/java/org/example/backend/seminar/controller/SeminarController.java +++ b/backend/src/main/java/org/example/backend/seminar/controller/SeminarController.java @@ -31,7 +31,7 @@ public class SeminarController { @Operation(summary = "세미나 생성 API", description = "세미나 생성") @PostMapping - public ResponseEntity createSeminar(@RequestBody SeminarReqDto seminarReqDto) { + public ResponseEntity createSeminar(@RequestBody @Valid SeminarReqDto seminarReqDto) { Long seminarId = seminarService.saveSeminar(seminarReqDto); return new ResponseEntity<>(seminarId, HttpStatus.OK); } diff --git a/backend/src/main/java/org/example/backend/seminar/domain/dto/SeminarReqDto.java b/backend/src/main/java/org/example/backend/seminar/domain/dto/SeminarReqDto.java index 78ac051c..90dedec8 100644 --- a/backend/src/main/java/org/example/backend/seminar/domain/dto/SeminarReqDto.java +++ b/backend/src/main/java/org/example/backend/seminar/domain/dto/SeminarReqDto.java @@ -1,14 +1,15 @@ package org.example.backend.seminar.domain.dto; +import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Builder; -import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class SeminarReqDto { + @NotBlank(message = "세미나 이름은 필수 입력값입니다.") private String name; private String writer; private String place; diff --git a/backend/src/main/java/org/example/backend/seminar/exception/SeminarExceptionType.java b/backend/src/main/java/org/example/backend/seminar/exception/SeminarExceptionType.java index 41bab747..1bcd191d 100644 --- a/backend/src/main/java/org/example/backend/seminar/exception/SeminarExceptionType.java +++ b/backend/src/main/java/org/example/backend/seminar/exception/SeminarExceptionType.java @@ -11,8 +11,6 @@ public enum SeminarExceptionType implements BaseExceptionType { NOT_FOUND_SEMINAR(NOT_FOUND, "세미나를 찾을 수 없습니다"), - - REQUIRED_NAME(BAD_REQUEST, "이름은 필수 입력값입니다."), ; private final HttpStatus httpStatus; diff --git a/backend/src/main/java/org/example/backend/seminar/service/SeminarService.java b/backend/src/main/java/org/example/backend/seminar/service/SeminarService.java index 4d92a291..3a78a3cb 100644 --- a/backend/src/main/java/org/example/backend/seminar/service/SeminarService.java +++ b/backend/src/main/java/org/example/backend/seminar/service/SeminarService.java @@ -22,18 +22,11 @@ public class SeminarService { @Transactional public Long saveSeminar(SeminarReqDto seminarReqDto) { - validateUserRequiredFields(seminarReqDto); Seminar seminar = Seminar.of(seminarReqDto); Seminar savedSeminar = seminarRepository.save(seminar); return savedSeminar.getId(); } - private void validateUserRequiredFields(SeminarReqDto dto) { - if (dto.getName() == null || dto.getName().isEmpty()) { - throw new SeminarException(SeminarExceptionType.REQUIRED_NAME); - } - } - public SeminarResDto getSeminar(Long seminarId) { Seminar seminar = findSeminarById(seminarId); return SeminarResDto.of(seminar); diff --git a/backend/src/main/java/org/example/backend/thesis/controller/ThesisController.java b/backend/src/main/java/org/example/backend/thesis/controller/ThesisController.java index 2e42c12f..ef169d11 100644 --- a/backend/src/main/java/org/example/backend/thesis/controller/ThesisController.java +++ b/backend/src/main/java/org/example/backend/thesis/controller/ThesisController.java @@ -19,7 +19,6 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -35,7 +34,7 @@ public class ThesisController { @Operation(summary = "논문 생성 API", description = "논문 생성") @PostMapping(consumes = "multipart/form-data") public ResponseEntity createThesis( - @RequestPart(value = "thesisReqDto") ThesisReqDto thesisReqDto, + @RequestPart(value = "thesisReqDto") @Valid ThesisReqDto thesisReqDto, @RequestPart(value = "thesis_image", required = false) MultipartFile multipartFile) { Long thesisId = thesisService.saveThesis(thesisReqDto, multipartFile); return new ResponseEntity<>(thesisId, HttpStatus.OK); diff --git a/backend/src/main/java/org/example/backend/thesis/domain/dto/ThesisReqDto.java b/backend/src/main/java/org/example/backend/thesis/domain/dto/ThesisReqDto.java index cd710807..ede18c56 100644 --- a/backend/src/main/java/org/example/backend/thesis/domain/dto/ThesisReqDto.java +++ b/backend/src/main/java/org/example/backend/thesis/domain/dto/ThesisReqDto.java @@ -1,6 +1,8 @@ package org.example.backend.thesis.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -9,18 +11,35 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ThesisReqDto { + + @NotBlank(message = "논문 제목은 필수 입력값입니다.") + @Size(max = 255, message = "논문 제목은 최대 255자까지 입력 가능합니다.") private String title; + + @NotBlank(message = "저자는 필수 입력값입니다.") + @Size(max = 255, message = "저자 이름은 최대 255자까지 입력 가능합니다.") private String author; + + @Size(max = 255, message = "저널 이름은 최대 255자까지 입력 가능합니다.") private String journal; + private String content; + + @Size(max = 2083, message = "링크는 최대 2083자까지 입력 가능합니다.") private String link; + private String publicationDate; + @Schema(hidden = true) private String thesisImage; + private String publicationCollection; private String publicationIssue; private String publicationPage; + + @Size(max = 255, message = "ISSN은 최대 255자까지 입력 가능합니다.") private String issn; + private Long professorId; @Builder diff --git a/backend/src/main/java/org/example/backend/thesis/exception/ThesisException.java b/backend/src/main/java/org/example/backend/thesis/exception/ThesisException.java index c12c02e9..baa39ac6 100644 --- a/backend/src/main/java/org/example/backend/thesis/exception/ThesisException.java +++ b/backend/src/main/java/org/example/backend/thesis/exception/ThesisException.java @@ -3,7 +3,6 @@ import lombok.RequiredArgsConstructor; import org.example.backend.common.exception.BaseException; import org.example.backend.common.exception.BaseExceptionType; -import org.example.backend.thesis.exception.ThesisExceptionType; @RequiredArgsConstructor public class ThesisException extends BaseException { diff --git a/backend/src/main/java/org/example/backend/thesis/service/ThesisService.java b/backend/src/main/java/org/example/backend/thesis/service/ThesisService.java index 66fc2be7..04403bff 100644 --- a/backend/src/main/java/org/example/backend/thesis/service/ThesisService.java +++ b/backend/src/main/java/org/example/backend/thesis/service/ThesisService.java @@ -10,7 +10,6 @@ import org.example.backend.thesis.domain.dto.ThesisResDto; import org.example.backend.thesis.domain.entity.Thesis; import org.example.backend.thesis.exception.ThesisException; -import org.example.backend.thesis.exception.ThesisExceptionType; import org.example.backend.thesis.repository.ThesisRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -29,7 +28,6 @@ public class ThesisService { @Transactional public Long saveThesis(ThesisReqDto thesisReqDto, MultipartFile multipartFile) { - validateUserRequiredFields(thesisReqDto); Professor professor = findProfessorById(thesisReqDto.getProfessorId()); if (multipartFile != null && !multipartFile.isEmpty()) { @@ -43,14 +41,6 @@ public Long saveThesis(ThesisReqDto thesisReqDto, MultipartFile multipartFile) { return savedThesis.getId(); } - private void validateUserRequiredFields(ThesisReqDto dto) { - if (dto.getTitle() == null || dto.getTitle().isEmpty()) { - throw new ThesisException(ThesisExceptionType.REQUIRED_TITLE); - } - if (dto.getAuthor() == null || dto.getAuthor().isEmpty()) { - throw new ThesisException(ThesisExceptionType.REQUIRED_AUTHOR); - } - } private Professor findProfessorById(Long professorId) { return professorRepository.findById(professorId) diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index 0e3c21c5..e44c84bb 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -302,15 +302,15 @@ VALUES ('22010321', '정석민', '바이오융합공학과', '010-4523-7819', 'U ('22010330', '문준호', '바이오융합공학과', '010-4512-6708', 'USER'); -INSERT INTO reservation (start_time, end_time, purpose, etc, repetition_type, room_id, user_id, created_at, +INSERT INTO reservation (start_time, end_time, purpose, etc, room_id, user_id, created_at, updated_at) -VALUES ('2024-12-01 09:00:00', '2024-12-01 11:00:00', 'MEETING', '홍성무교수님 랩미팅', 'DAILY', 1, 1, NOW(), NOW()), - ('2024-12-01 13:00:00', '2024-12-01 15:00:00', 'MEETING', '김은희교수님 랩미팅', 'WEEKLY', 1, 2, NOW(), NOW()), - ('2024-12-02 10:00:00', '2024-12-02 12:00:00', 'MEETING', '김민수교수님 랩미팅', 'DAILY', 1, 1, NOW(), NOW()), - ('2024-12-03 14:00:00', '2024-12-03 16:00:00', 'MEETING', '전종훈교수님 랩미팅', 'WEEKLY', 1, 3, NOW(), NOW()), - ('2024-12-04 09:30:00', '2024-12-04 11:30:00', 'MEETING', '서민석교수님 랩미팅', 'DAILY', 1, 2, NOW(), NOW()), - ('2024-12-05 15:00:00', '2024-12-05 17:00:00', 'MEETING', '홍성무교수님 랩미팅', 'DAILY', 1, 3, NOW(), NOW()), - ('2024-12-06 10:30:00', '2024-12-06 12:30:00', 'MEETING', '김은희교수님 랩미팅', 'WEEKLY', 1, 1, NOW(), NOW()), - ('2024-12-07 13:30:00', '2024-12-07 15:30:00', 'MEETING', '김민수교수님 랩미팅', 'WEEKLY', 1, 2, NOW(), NOW()), - ('2024-12-08 11:00:00', '2024-12-08 13:00:00', 'MEETING', '전종훈교수님 랩미팅', 'DAILY', 1, 1, NOW(), NOW()), - ('2024-12-09 16:00:00', '2024-12-09 18:00:00', 'MEETING', '서민석교수님 랩미팅', 'DAILY', 1, 4, NOW(), NOW()); \ No newline at end of file +VALUES ('2024-12-01 09:00:00', '2024-12-01 11:00:00', 'MEETING', '홍성무교수님 랩미팅', 1, 1, NOW(), NOW()), + ('2024-12-01 13:00:00', '2024-12-01 15:00:00', 'MEETING', '김은희교수님 랩미팅', 1, 2, NOW(), NOW()), + ('2024-12-02 10:00:00', '2024-12-02 12:00:00', 'MEETING', '김민수교수님 랩미팅', 1, 1, NOW(), NOW()), + ('2024-12-03 14:00:00', '2024-12-03 16:00:00', 'MEETING', '전종훈교수님 랩미팅', 1, 3, NOW(), NOW()), + ('2024-12-04 09:30:00', '2024-12-04 11:30:00', 'MEETING', '서민석교수님 랩미팅', 1, 2, NOW(), NOW()), + ('2024-12-05 15:00:00', '2024-12-05 17:00:00', 'MEETING', '홍성무교수님 랩미팅', 1, 3, NOW(), NOW()), + ('2024-12-06 10:30:00', '2024-12-06 12:30:00', 'MEETING', '김은희교수님 랩미팅', 1, 1, NOW(), NOW()), + ('2024-12-07 13:30:00', '2024-12-07 15:30:00', 'MEETING', '김민수교수님 랩미팅', 1, 2, NOW(), NOW()), + ('2024-12-08 11:00:00', '2024-12-08 13:00:00', 'MEETING', '전종훈교수님 랩미팅', 1, 1, NOW(), NOW()), + ('2024-12-09 16:00:00', '2024-12-09 18:00:00', 'MEETING', '서민석교수님 랩미팅', 1, 4, NOW(), NOW()); \ No newline at end of file