diff --git a/src/main/java/corecord/dev/common/exception/GeneralExceptionAdvice.java b/src/main/java/corecord/dev/common/exception/GeneralExceptionAdvice.java index 47579de..634d171 100644 --- a/src/main/java/corecord/dev/common/exception/GeneralExceptionAdvice.java +++ b/src/main/java/corecord/dev/common/exception/GeneralExceptionAdvice.java @@ -2,6 +2,7 @@ import corecord.dev.common.response.ApiResponse; import corecord.dev.common.status.ErrorStatus; +import corecord.dev.domain.folder.exception.model.FolderException; import corecord.dev.domain.token.exception.model.TokenException; import corecord.dev.domain.user.exception.model.UserException; import lombok.extern.slf4j.Slf4j; @@ -29,6 +30,13 @@ public ResponseEntity> handleTokenException(TokenException e) return ApiResponse.error(e.getTokenErrorStatus()); } + // FolderException 처리 + @ExceptionHandler(FolderException.class) + public ResponseEntity> handleFolderException(FolderException e) { + log.warn(">>>>>>>>FolderException: {}", e.getFolderErrorStatus().getMessage()); + return ApiResponse.error(e.getFolderErrorStatus()); + } + // GeneralException 처리 @ExceptionHandler(GeneralException.class) public ResponseEntity> handleGeneralException(GeneralException e) { diff --git a/src/main/java/corecord/dev/domain/folder/constant/FolderSuccessStatus.java b/src/main/java/corecord/dev/domain/folder/constant/FolderSuccessStatus.java new file mode 100644 index 0000000..cba0777 --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/constant/FolderSuccessStatus.java @@ -0,0 +1,20 @@ +package corecord.dev.domain.folder.constant; + +import corecord.dev.common.base.BaseSuccessStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum FolderSuccessStatus implements BaseSuccessStatus { + + FOLDER_CREATE_SUCCESS(HttpStatus.CREATED, "S601", "폴더 생성이 성공적으로 완료되었습니다."), + FOLDER_DELETE_SUCCESS(HttpStatus.OK, "S601", "폴더 삭제 성공적으로 완료되었습니다."), + FOLDER_GET_SUCCESS(HttpStatus.OK, "S601", "폴더 리스트 조회가 성공적으로 완료되었습니다."), + FOLDER_UPDATE_SUCCESS(HttpStatus.OK, "S601", "폴더 수정이 성공적으로 완료되었습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/main/java/corecord/dev/domain/folder/controller/FolderController.java b/src/main/java/corecord/dev/domain/folder/controller/FolderController.java new file mode 100644 index 0000000..2d86e6b --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/controller/FolderController.java @@ -0,0 +1,54 @@ +package corecord.dev.domain.folder.controller; + +import corecord.dev.common.response.ApiResponse; +import corecord.dev.domain.folder.constant.FolderSuccessStatus; +import corecord.dev.domain.folder.dto.request.FolderRequest; +import corecord.dev.domain.folder.dto.response.FolderResponse; +import corecord.dev.domain.folder.service.FolderService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/folders") +public class FolderController { + private final FolderService folderService; + + @PostMapping("") + public ResponseEntity> createFolder( + @RequestBody FolderRequest.FolderDto folderDto + ) { + FolderResponse.FolderDtoList folderResponse = folderService.createFolder(folderDto); + + return ApiResponse.success(FolderSuccessStatus.FOLDER_CREATE_SUCCESS, folderResponse); + } + + @DeleteMapping("/{folderId}") + public ResponseEntity> deleteFolder( + @PathVariable(name = "folderId") Long folderId + ) { + FolderResponse.FolderDtoList folderResponse = folderService.deleteFolder(folderId); + + return ApiResponse.success(FolderSuccessStatus.FOLDER_DELETE_SUCCESS, folderResponse); + } + + @GetMapping("") + public ResponseEntity> getFolders( + ) { + FolderResponse.FolderDtoList folderResponse = folderService.getFolderList(); + + return ApiResponse.success(FolderSuccessStatus.FOLDER_GET_SUCCESS, folderResponse); + } + + @PatchMapping("") + public ResponseEntity> updateFolder( + @RequestBody FolderRequest.FolderUpdateDto folderDto + ) { + FolderResponse.FolderDtoList folderResponse = folderService.updateFolder(folderDto); + + return ApiResponse.success(FolderSuccessStatus.FOLDER_UPDATE_SUCCESS, folderResponse); + } + + +} diff --git a/src/main/java/corecord/dev/domain/folder/converter/FolderConverter.java b/src/main/java/corecord/dev/domain/folder/converter/FolderConverter.java new file mode 100644 index 0000000..9c06160 --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/converter/FolderConverter.java @@ -0,0 +1,27 @@ +package corecord.dev.domain.folder.converter; + +import corecord.dev.domain.folder.dto.response.FolderResponse; +import corecord.dev.domain.folder.entity.Folder; + +import java.util.List; + +public class FolderConverter { + + public static Folder toFolderEntity(String title) { + return Folder.builder() + .title(title) + .build(); + } + + public static FolderResponse.FolderDto toFolderDto(Folder folder) { + return FolderResponse.FolderDto.builder() + .title(folder.getTitle()) + .build(); + } + + public static FolderResponse.FolderDtoList toFolderDtoList(List folderDtoList) { + return FolderResponse.FolderDtoList.builder() + .folderDtoList(folderDtoList) + .build(); + } +} diff --git a/src/main/java/corecord/dev/domain/folder/dto/request/FolderRequest.java b/src/main/java/corecord/dev/domain/folder/dto/request/FolderRequest.java new file mode 100644 index 0000000..e2990ac --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/dto/request/FolderRequest.java @@ -0,0 +1,17 @@ +package corecord.dev.domain.folder.dto.request; + +import lombok.Data; + +public class FolderRequest { + + @Data + public static class FolderDto { + private String title; + } + + @Data + public static class FolderUpdateDto { + private Long folderId; + private String title; + } +} diff --git a/src/main/java/corecord/dev/domain/folder/dto/response/FolderResponse.java b/src/main/java/corecord/dev/domain/folder/dto/response/FolderResponse.java new file mode 100644 index 0000000..3bc1799 --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/dto/response/FolderResponse.java @@ -0,0 +1,24 @@ +package corecord.dev.domain.folder.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +import java.util.List; + +public class FolderResponse { + + @Builder @Getter + @AllArgsConstructor @Data + public static class FolderDto { + private Long folderId; + private String title; + } + + @Builder @Getter + @AllArgsConstructor @Data + public static class FolderDtoList { + private List folderDtoList; + } +} diff --git a/src/main/java/corecord/dev/domain/folder/entity/Folder.java b/src/main/java/corecord/dev/domain/folder/entity/Folder.java index b6f2ea8..ce0e3d1 100644 --- a/src/main/java/corecord/dev/domain/folder/entity/Folder.java +++ b/src/main/java/corecord/dev/domain/folder/entity/Folder.java @@ -25,4 +25,8 @@ public class Folder extends BaseEntity { @OneToOne(mappedBy = "folder") private Record record; + + public void updateTitle(String title) { + this.title = title; + } } diff --git a/src/main/java/corecord/dev/domain/folder/exception/enums/FolderErrorStatus.java b/src/main/java/corecord/dev/domain/folder/exception/enums/FolderErrorStatus.java new file mode 100644 index 0000000..b3a8ef9 --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/exception/enums/FolderErrorStatus.java @@ -0,0 +1,18 @@ +package corecord.dev.domain.folder.exception.enums; + +import corecord.dev.common.base.BaseErrorStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum FolderErrorStatus implements BaseErrorStatus { + DUPLICATED_FOLDER_TITLE(HttpStatus.BAD_REQUEST, "E0400_DUPLICATED_TITLE", "이미 존재하는 폴더 명입니다."), + OVERFLOW_FOLDER_TITLE(HttpStatus.BAD_REQUEST, "E0400_OVERFLOW_TITLE", "폴더 명은 15자 이내여야 합니다."), + FOLDER_NOT_FOUND(HttpStatus.NOT_FOUND, "E0404_FOLDER", "존재하지 않는 폴더입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/main/java/corecord/dev/domain/folder/exception/model/FolderException.java b/src/main/java/corecord/dev/domain/folder/exception/model/FolderException.java new file mode 100644 index 0000000..9f41081 --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/exception/model/FolderException.java @@ -0,0 +1,16 @@ +package corecord.dev.domain.folder.exception.model; + +import corecord.dev.domain.folder.exception.enums.FolderErrorStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class FolderException extends RuntimeException { + private final FolderErrorStatus folderErrorStatus; + + @Override + public String getMessage() { + return folderErrorStatus.getMessage(); + } +} diff --git a/src/main/java/corecord/dev/domain/folder/repository/FolderRepository.java b/src/main/java/corecord/dev/domain/folder/repository/FolderRepository.java new file mode 100644 index 0000000..306a465 --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/repository/FolderRepository.java @@ -0,0 +1,20 @@ +package corecord.dev.domain.folder.repository; + +import corecord.dev.domain.folder.dto.response.FolderResponse; +import corecord.dev.domain.folder.entity.Folder; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface FolderRepository extends JpaRepository { + + @Query("SELECT new corecord.dev.domain.folder.dto.response.FolderResponse$FolderDto(f.folderId, f.title) " + + "FROM Folder f " + + "ORDER BY f.createdAt") + List findFolderDtoList(); + + boolean existsByTitle(String title); +} diff --git a/src/main/java/corecord/dev/domain/folder/service/FolderService.java b/src/main/java/corecord/dev/domain/folder/service/FolderService.java new file mode 100644 index 0000000..22fd939 --- /dev/null +++ b/src/main/java/corecord/dev/domain/folder/service/FolderService.java @@ -0,0 +1,96 @@ +package corecord.dev.domain.folder.service; + +import corecord.dev.domain.folder.converter.FolderConverter; +import corecord.dev.domain.folder.dto.request.FolderRequest; +import corecord.dev.domain.folder.dto.response.FolderResponse; +import corecord.dev.domain.folder.entity.Folder; +import corecord.dev.domain.folder.exception.enums.FolderErrorStatus; +import corecord.dev.domain.folder.exception.model.FolderException; +import corecord.dev.domain.folder.repository.FolderRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +public class FolderService { + private final FolderRepository folderRepository; + + /* + * 폴더명(title)을 request로 받아, 새로운 폴더를 생성 + * @param folderDto + * @return + */ + @Transactional + public FolderResponse.FolderDtoList createFolder(FolderRequest.FolderDto folderDto) { + validateDuplicatedFolderTitle(folderDto.getTitle()); + + Folder folder = FolderConverter.toFolderEntity(folderDto.getTitle()); + folderRepository.save(folder); + + return getFolderList(); + } + + /* + * folderId를 통해 folder을 삭제 + * @param folderId + * @return + */ + @Transactional + public FolderResponse.FolderDtoList deleteFolder(Long folderId) { + Folder folder = findFolderById(folderId); + folderRepository.delete(folder); + + return getFolderList(); + } + + /* + * folderId를 받아, 해당 folder의 title을 수정 + * @param folderDto + * @return + */ + @Transactional + public FolderResponse.FolderDtoList updateFolder(FolderRequest.FolderUpdateDto folderDto) { + Folder folder = findFolderById(folderDto.getFolderId()); + + validateDuplicatedFolderTitle(folderDto.getTitle()); + validateTitleLength(folderDto.getTitle()); + + folder.updateTitle(folderDto.getTitle()); + + return getFolderList(); + } + + /* + * 생성일 오름차순으로 폴더 리스트를 조회 + * @return + */ + @Transactional(readOnly = true) + public FolderResponse.FolderDtoList getFolderList() { + List folderList = folderRepository.findFolderDtoList(); + return FolderConverter.toFolderDtoList(folderList); + } + + // 폴더명 중복 검사 + private void validateDuplicatedFolderTitle(String title) { + if (folderRepository.existsByTitle(title)) { + throw new FolderException(FolderErrorStatus.DUPLICATED_FOLDER_TITLE); + } + } + + // title 글자 수 검사 + private void validateTitleLength(String title) { + if (title.length() > 15) { + throw new FolderException(FolderErrorStatus.OVERFLOW_FOLDER_TITLE); + } + } + + private Folder findFolderById(Long folderId) { + return folderRepository.findById(folderId) + .orElseThrow(() -> new FolderException(FolderErrorStatus.FOLDER_NOT_FOUND)); + } +}