diff --git a/build.gradle b/build.gradle index e753fc4..1497be9 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'me.paulschwarz:spring-dotenv:4.0.0' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' diff --git a/src/main/java/com/web/SearchWeb/config/ApiResponse.java b/src/main/java/com/web/SearchWeb/config/ApiResponse.java new file mode 100644 index 0000000..c1c5027 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/config/ApiResponse.java @@ -0,0 +1,38 @@ +package com.web.SearchWeb.config; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class ApiResponse { + private final boolean success; + private final T data; + private final ErrorResponse error; // 여기서 ErrorResponse는 JSON 포장지 역할 + + public static ApiResponse success(T data) { + return new ApiResponse<>(true, data, null); + } + + // Enum을 인자로 받음으로써 타입 안정성 확보 + public static ApiResponse fail(ErrorCode errorCode) { + return new ApiResponse<>( + false, + null, + new ErrorResponse(errorCode) // Enum을 생성자로 넘겨서 변환 + ); + } + + @Getter + public static class ErrorResponse { + private final String code; + private final String message; + + // Enum에서 필요한 정보만 추출하여 세팅 + private ErrorResponse(ErrorCode errorCode) { + this.code = errorCode.getCode(); + this.message = errorCode.getMessage(); + } + } +} diff --git a/src/main/java/com/web/SearchWeb/config/BusinessException.java b/src/main/java/com/web/SearchWeb/config/BusinessException.java new file mode 100644 index 0000000..7aa60d6 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/config/BusinessException.java @@ -0,0 +1,13 @@ +package com.web.SearchWeb.config; + +import lombok.Getter; + +@Getter +public class BusinessException extends RuntimeException { + private final ErrorCode errorCode; + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/web/SearchWeb/config/CommonErrorCode.java b/src/main/java/com/web/SearchWeb/config/CommonErrorCode.java new file mode 100644 index 0000000..30a2e56 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/config/CommonErrorCode.java @@ -0,0 +1,17 @@ +package com.web.SearchWeb.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum CommonErrorCode implements ErrorCode{ + // 500 Internal Server Error: 서버 내부 오류 + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "C006", "서버에 오류가 발생했습니다."); + + private final HttpStatus status; + private final String code; + private final String message; + +} diff --git a/src/main/java/com/web/SearchWeb/config/ErrorCode.java b/src/main/java/com/web/SearchWeb/config/ErrorCode.java new file mode 100644 index 0000000..c352033 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/config/ErrorCode.java @@ -0,0 +1,9 @@ +package com.web.SearchWeb.config; + +import org.springframework.http.HttpStatus; + +public interface ErrorCode { + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} diff --git a/src/main/java/com/web/SearchWeb/config/GlobalExceptionHandler.java b/src/main/java/com/web/SearchWeb/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..4897c1b --- /dev/null +++ b/src/main/java/com/web/SearchWeb/config/GlobalExceptionHandler.java @@ -0,0 +1,34 @@ +package com.web.SearchWeb.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + // FolderException, MemberException 등 모든 비즈니스 예외가 이 메서드 하나로 들어옵니다. + @ExceptionHandler(BusinessException.class) + protected ResponseEntity> handleBusinessException(BusinessException e) { + ErrorCode errorCode = e.getErrorCode(); + + // 로그에는 어떤 도메인에서 예외가 났는지 클래스명과 메시지를 찍어줍니다. + log.warn("BusinessException [{}]: {}", e.getClass().getSimpleName(), errorCode.getMessage()); + + return ResponseEntity + .status(errorCode.getStatus()) + .body(ApiResponse.fail(errorCode)); + } + + // 그 외 예상치 못한 서버 에러(500) 처리 + @ExceptionHandler(Exception.class) + protected ResponseEntity> handleException(Exception e) { + log.error("Internal Server Error", e); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.fail(CommonErrorCode.INTERNAL_SERVER_ERROR)); + } +} diff --git a/src/main/java/com/web/SearchWeb/folder/controller/FolderController.java b/src/main/java/com/web/SearchWeb/folder/controller/FolderController.java deleted file mode 100644 index d0220cb..0000000 --- a/src/main/java/com/web/SearchWeb/folder/controller/FolderController.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.web.SearchWeb.folder.controller; - -import com.web.SearchWeb.aop.OwnerCheck; -import com.web.SearchWeb.folder.dto.request.FolderCreateRequestDto; -import com.web.SearchWeb.folder.dto.request.FolderUpdateRequestDto; -import com.web.SearchWeb.folder.dto.response.FolderResponseDto; -import com.web.SearchWeb.folder.service.FolderService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - - -/** - * 코드 작성자: - * - 서진영(jin2304) - * - * 코드 설명: - * - 사용자의 폴더 관리 API (CRUD + 태그 조회). - * - 모든 요청은 OwnerCheck AOP를 통해 권한을 검증. - * - * 코드 주요 기능: - * - 폴더 생성 - * - 폴더 단일 조회 - * - 폴더 목록 조회 - * - 폴더 수정 - * - 폴더 삭제 - * - 폴더 태그 목록 조회 - * - * 코드 작성일: - * - 2025.12.31 ~ 2026.01.02 - */ -@Controller -public class FolderController { - - private final FolderService folderService; - - @Autowired - public FolderController(FolderService folderService) { - this.folderService = folderService; - } - - - /** - * 폴더 생성 - */ - @PostMapping("/myPage/{memberId}/folder") - @OwnerCheck(idParam = "memberId", service = "memberService") - public ResponseEntity insertFolder(@PathVariable final int memberId, - @RequestBody FolderCreateRequestDto folderCreateRequestDto) { - FolderResponseDto folder = folderService.insertFolder(memberId, folderCreateRequestDto); - return ResponseEntity.ok(folder); - } - - - /** - * 폴더 단일 조회 - */ - @GetMapping("/myPage/{memberId}/folder/{folderId}") - @OwnerCheck(idParam = "memberId", service = "memberService") - public ResponseEntity getFolder(@PathVariable final int memberId, - @PathVariable final int folderId) { - FolderResponseDto folder = folderService.selectFolder(memberId, folderId); - return ResponseEntity.ok(folder); - } - - - /** - * 폴더 목록 조회 - */ - @GetMapping("/myPage/{memberId}/folders") - @OwnerCheck(idParam = "memberId", service = "memberService") - public ResponseEntity> getFolders(@PathVariable final int memberId, - @RequestParam(required = false, defaultValue = "All") String tag, - @RequestParam(required = false, defaultValue = "Newest") String sort) { - List folders = folderService.selectFolderList(memberId, tag, sort); - return ResponseEntity.ok(folders); - } - - - /** - * 폴더 수정 - */ - @PutMapping("/myPage/{memberId}/folder/{folderId}") - @OwnerCheck(idParam = "memberId", service = "memberService") - public ResponseEntity updateFolder(@PathVariable final int memberId, - @PathVariable final int folderId, - @RequestBody FolderUpdateRequestDto folderUpdateRequestDto) { - int result = folderService.updateFolder(memberId, folderId, folderUpdateRequestDto); - return ResponseEntity.ok(result); - } - - - /** - * 폴더 삭제 - */ - @DeleteMapping("/myPage/{memberId}/folder/{folderId}") - @OwnerCheck(idParam = "memberId", service = "memberService") - public ResponseEntity deleteFolder(@PathVariable final int memberId, - @PathVariable final int folderId) { - int result = folderService.deleteFolder(memberId, folderId); - return ResponseEntity.ok(result); - } - - - /** - * 폴더 태그 목록 조회 - */ - @GetMapping("/myPage/{memberId}/folderTags") - @OwnerCheck(idParam = "memberId", service = "memberService") - public ResponseEntity> getFolderTags(@PathVariable final int memberId) { - List tags = folderService.selectFolderTags(memberId); - return ResponseEntity.ok(tags); - } -} diff --git a/src/main/java/com/web/SearchWeb/folder/controller/MemberFolderController.java b/src/main/java/com/web/SearchWeb/folder/controller/MemberFolderController.java new file mode 100644 index 0000000..2fa140e --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/controller/MemberFolderController.java @@ -0,0 +1,95 @@ +package com.web.SearchWeb.folder.controller; + +import com.web.SearchWeb.config.ApiResponse; +import com.web.SearchWeb.folder.controller.dto.MemberFolderRequests; +import com.web.SearchWeb.folder.controller.dto.MemberFolderResponses; +import com.web.SearchWeb.folder.domain.MemberFolder; +import com.web.SearchWeb.folder.service.MemberFolderService; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/folders") +public class MemberFolderController { + + private final MemberFolderService memberFolderService; + + // 생성 (201 Created 응답) + @PostMapping + public ResponseEntity> create(@RequestBody MemberFolderRequests.Create req) { + Long folderId = memberFolderService.create( + req.ownerMemberId, + req.parentFolderId, + req.folderName, + req.description + ); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(ApiResponse.success(folderId)); + } + + // 단건 조회 + @GetMapping("/{folderId}") + public ResponseEntity> get(@PathVariable Long folderId) { + MemberFolder folder = memberFolderService.get(folderId); + return ResponseEntity.ok(ApiResponse.success(MemberFolderResponses.from(folder))); + } + + // 루트 폴더 조회 + @GetMapping("/owners/{ownerMemberId}/root") + public ResponseEntity>> listRoot(@PathVariable Long ownerMemberId) { + List responses = memberFolderService.listRootFolders(ownerMemberId) + .stream() + .map(MemberFolderResponses::from) + .collect(Collectors.toList()); + + return ResponseEntity.ok(ApiResponse.success(responses)); + } + + // 하위 폴더 조회 + @GetMapping("/owners/{ownerMemberId}/children/{parentFolderId}") + public ResponseEntity>> listChildren( + @PathVariable Long ownerMemberId, + @PathVariable Long parentFolderId + ) { + List responses = memberFolderService.listChildren(ownerMemberId, parentFolderId) + .stream() + .map(MemberFolderResponses::from) + .collect(Collectors.toList()); + + return ResponseEntity.ok(ApiResponse.success(responses)); + } + + // 수정 (200 OK) + @PutMapping("/{folderId}") + public ResponseEntity> update(@PathVariable Long folderId, @RequestBody MemberFolderRequests.Update req) { + memberFolderService.update(folderId, req.folderName, req.description); + return ResponseEntity.ok(ApiResponse.success(null)); + } + + // 이동(부모 변경) + @PutMapping("/{folderId}/move") + public ResponseEntity> move(@PathVariable Long folderId, @RequestBody MemberFolderRequests.Move req) { + memberFolderService.move(folderId, req.newParentFolderId); + return ResponseEntity.ok(ApiResponse.success(null)); + } + + // 삭제 + @DeleteMapping("/{folderId}") + public ResponseEntity> delete(@PathVariable Long folderId) { + memberFolderService.delete(folderId); + return ResponseEntity.ok(ApiResponse.success(null)); + } +} \ No newline at end of file diff --git a/src/main/java/com/web/SearchWeb/folder/controller/dto/MemberFolderRequests.java b/src/main/java/com/web/SearchWeb/folder/controller/dto/MemberFolderRequests.java new file mode 100644 index 0000000..3e19b7e --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/controller/dto/MemberFolderRequests.java @@ -0,0 +1,29 @@ +package com.web.SearchWeb.folder.controller.dto; + +public class MemberFolderRequests { + + /** + * 폴더 생성 요청 + */ + public static class Create { + public Long ownerMemberId; + public Long parentFolderId; // null이면 루트 + public String folderName; + public String description; + } + + /** + * 폴더 수정 요청 + */ + public static class Update { + public String folderName; + public String description; + } + + /** + * 폴더 이동 요청 + */ + public static class Move { + public Long newParentFolderId; // null이면 루트로 이동 + } +} diff --git a/src/main/java/com/web/SearchWeb/folder/controller/dto/MemberFolderResponses.java b/src/main/java/com/web/SearchWeb/folder/controller/dto/MemberFolderResponses.java new file mode 100644 index 0000000..fb6655d --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/controller/dto/MemberFolderResponses.java @@ -0,0 +1,26 @@ +package com.web.SearchWeb.folder.controller.dto; + +import com.web.SearchWeb.folder.domain.MemberFolder; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class MemberFolderResponses { + private final Long memberFolderId; + private final Long ownerMemberId; + private final Long parentFolderId; + private final String folderName; + private final String description; + + // Entity -> DTO 변환을 위한 정적 팩토리 메서드 + public static MemberFolderResponses from(MemberFolder folder) { + return MemberFolderResponses.builder() + .memberFolderId(folder.getMemberFolderId()) + .ownerMemberId(folder.getOwnerMemberId()) + .parentFolderId(folder.getParentFolderId()) + .folderName(folder.getFolderName()) + .description(folder.getDescription()) + .build(); + } +} diff --git a/src/main/java/com/web/SearchWeb/folder/dao/FolderDao.java b/src/main/java/com/web/SearchWeb/folder/dao/FolderDao.java deleted file mode 100644 index f0856dd..0000000 --- a/src/main/java/com/web/SearchWeb/folder/dao/FolderDao.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.web.SearchWeb.folder.dao; - -import com.web.SearchWeb.folder.domain.Folder; -import com.web.SearchWeb.folder.dto.request.FolderSearchRequestDto; - -import java.util.List; - -public interface FolderDao { - // 폴더 생성 - int insertFolder(Folder folder); - // 폴더 단일 조회 - Folder selectFolder(int memberId, int folderId); - // 폴더 목록 조회 - List selectFolderList(FolderSearchRequestDto searchRequest); - // 폴더 수정 - int updateFolder(Folder folder); - // 폴더 삭제 - int deleteFolder(int memberId, int folderId); - // 폴더 태그 목록 조회 - List selectFolderTags(int memberId); -} \ No newline at end of file diff --git a/src/main/java/com/web/SearchWeb/folder/dao/MemberFolderJpaDao.java b/src/main/java/com/web/SearchWeb/folder/dao/MemberFolderJpaDao.java new file mode 100644 index 0000000..314f7e7 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/dao/MemberFolderJpaDao.java @@ -0,0 +1,17 @@ +package com.web.SearchWeb.folder.dao; + +import com.web.SearchWeb.folder.domain.MemberFolder; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberFolderJpaDao extends JpaRepository { + + // 루트 폴더 + List findAllByOwnerMemberIdAndParentFolderIdIsNull(Long ownerMemberId); + + // 직계 하위 폴더 + List findAllByOwnerMemberIdAndParentFolderId(Long ownerMemberId, Long parentFolderId); + + // 사용자 전체 폴더 + List findAllByOwnerMemberId(Long ownerMemberId); +} diff --git a/src/main/java/com/web/SearchWeb/folder/dao/MybatisFolderDao.java b/src/main/java/com/web/SearchWeb/folder/dao/MybatisFolderDao.java deleted file mode 100644 index 63b109f..0000000 --- a/src/main/java/com/web/SearchWeb/folder/dao/MybatisFolderDao.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.web.SearchWeb.folder.dao; - -import com.web.SearchWeb.folder.domain.Folder; -import com.web.SearchWeb.folder.dto.request.FolderSearchRequestDto; -import org.apache.ibatis.session.SqlSession; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public class MybatisFolderDao implements FolderDao { - - private final FolderDao mapper; - - @Autowired - public MybatisFolderDao(SqlSession sqlSession) { - mapper = sqlSession.getMapper(FolderDao.class); - } - - - @Override - public int insertFolder(Folder folder) { - return mapper.insertFolder(folder); - } - - - @Override - public Folder selectFolder(int memberId, int folderId) { - return mapper.selectFolder(memberId, folderId); - } - - - @Override - public List selectFolderList(FolderSearchRequestDto searchRequest) { - return mapper.selectFolderList(searchRequest); - } - - - @Override - public int updateFolder(Folder folder) { - return mapper.updateFolder(folder); - } - - - @Override - public int deleteFolder(int memberId, int folderId) { - return mapper.deleteFolder(memberId, folderId); - } - - - @Override - public List selectFolderTags(int memberId) { - return mapper.selectFolderTags(memberId); - } -} - diff --git a/src/main/java/com/web/SearchWeb/folder/domain/Folder.java b/src/main/java/com/web/SearchWeb/folder/domain/Folder.java deleted file mode 100644 index 15ec109..0000000 --- a/src/main/java/com/web/SearchWeb/folder/domain/Folder.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.web.SearchWeb.folder.domain; - -import java.time.LocalDateTime; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -@ToString -public class Folder { - private int folderId; - private int member_memberId; - private String name; - private String tag; - private LocalDateTime created_date; - private LocalDateTime modified_date; -} \ No newline at end of file diff --git a/src/main/java/com/web/SearchWeb/folder/domain/MemberFolder.java b/src/main/java/com/web/SearchWeb/folder/domain/MemberFolder.java new file mode 100644 index 0000000..7ae81b8 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/domain/MemberFolder.java @@ -0,0 +1,47 @@ +package com.web.SearchWeb.folder.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@Entity +@Table(name = "member_folder") +public class MemberFolder { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_folder_id") + private Long memberFolderId; + @Column(name = "owner_member_id", nullable = false) + private Long ownerMemberId; + @Column(name = "parent_folder_id") + private Long parentFolderId; + @Column(name = "folder_name", nullable = false, length = 100) + private String folderName; + @Column(name = "description", length = 500) + private String description; + + public void changeInfo(String folderName, String description) { + if (folderName == null || folderName.isBlank()) { + throw new IllegalArgumentException("folderName must not be blank"); + } + this.folderName = folderName; + this.description = description; + } + + public void changeParent(Long newParentFolderId) { + this.parentFolderId = newParentFolderId; + } +} diff --git a/src/main/java/com/web/SearchWeb/folder/dto/request/FolderCreateRequestDto.java b/src/main/java/com/web/SearchWeb/folder/dto/request/FolderCreateRequestDto.java deleted file mode 100644 index 69d594b..0000000 --- a/src/main/java/com/web/SearchWeb/folder/dto/request/FolderCreateRequestDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.web.SearchWeb.folder.dto.request; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@Setter -@Getter -@ToString -@NoArgsConstructor -@AllArgsConstructor -public class FolderCreateRequestDto { - private String name; - private String tag; -} diff --git a/src/main/java/com/web/SearchWeb/folder/dto/request/FolderSearchRequestDto.java b/src/main/java/com/web/SearchWeb/folder/dto/request/FolderSearchRequestDto.java deleted file mode 100644 index 30b6575..0000000 --- a/src/main/java/com/web/SearchWeb/folder/dto/request/FolderSearchRequestDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.web.SearchWeb.folder.dto.request; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class FolderSearchRequestDto { - private int memberId; - private String tag; - private String sort; -} \ No newline at end of file diff --git a/src/main/java/com/web/SearchWeb/folder/dto/request/FolderUpdateRequestDto.java b/src/main/java/com/web/SearchWeb/folder/dto/request/FolderUpdateRequestDto.java deleted file mode 100644 index 8e67567..0000000 --- a/src/main/java/com/web/SearchWeb/folder/dto/request/FolderUpdateRequestDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.web.SearchWeb.folder.dto.request; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@Setter -@Getter -@ToString -@NoArgsConstructor -@AllArgsConstructor -public class FolderUpdateRequestDto { - private String name; - private String tag; -} \ No newline at end of file diff --git a/src/main/java/com/web/SearchWeb/folder/dto/response/FolderResponseDto.java b/src/main/java/com/web/SearchWeb/folder/dto/response/FolderResponseDto.java deleted file mode 100644 index ae3e9b5..0000000 --- a/src/main/java/com/web/SearchWeb/folder/dto/response/FolderResponseDto.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.web.SearchWeb.folder.dto.response; - -import com.web.SearchWeb.folder.domain.Folder; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -@Getter -@ToString -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class FolderResponseDto { - private int folderId; - private String name; - private String tag; - private LocalDateTime created_date; - private LocalDateTime modified_date; - - /** - * Folder Entity -> FolderResponseDto 변환 메서드 - */ - public static FolderResponseDto from(Folder folder) { - if (folder == null) { - return null; - } - return FolderResponseDto.builder() - .folderId(folder.getFolderId()) - .name(folder.getName()) - .tag(folder.getTag()) - .created_date(folder.getCreated_date()) - .modified_date(folder.getModified_date()) - .build(); - } - - /** - * Folder Entity List -> FolderResponseDto List 변환 메서드 - */ - public static List fromList(List folders) { - if (folders == null) { - return null; - } - return folders.stream() - .map(FolderResponseDto::from) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/com/web/SearchWeb/folder/error/FolderErrorCode.java b/src/main/java/com/web/SearchWeb/folder/error/FolderErrorCode.java new file mode 100644 index 0000000..ca76170 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/error/FolderErrorCode.java @@ -0,0 +1,17 @@ +package com.web.SearchWeb.folder.error; + +import com.web.SearchWeb.config.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum FolderErrorCode implements ErrorCode { + FOLDER_NOT_FOUND(HttpStatus.NOT_FOUND, "F001", "폴더를 찾을 수 없습니다."), + DUPLICATE_FOLDER_NAME(HttpStatus.BAD_REQUEST, "F002", "이미 존재하는 폴더명입니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/web/SearchWeb/folder/error/FolderException.java b/src/main/java/com/web/SearchWeb/folder/error/FolderException.java new file mode 100644 index 0000000..d97d24a --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/error/FolderException.java @@ -0,0 +1,19 @@ +package com.web.SearchWeb.folder.error; + +import com.web.SearchWeb.config.BusinessException; +import com.web.SearchWeb.config.ErrorCode; +import lombok.Getter; + +@Getter +public class FolderException extends BusinessException { + + private FolderException(ErrorCode errorCode) { + super(errorCode); + } + + public static class NotFound extends FolderException { + public NotFound() { + super(FolderErrorCode.FOLDER_NOT_FOUND); + } + } +} diff --git a/src/main/java/com/web/SearchWeb/folder/service/FolderService.java b/src/main/java/com/web/SearchWeb/folder/service/FolderService.java deleted file mode 100644 index 6731961..0000000 --- a/src/main/java/com/web/SearchWeb/folder/service/FolderService.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.web.SearchWeb.folder.service; - -import com.web.SearchWeb.folder.dto.request.FolderCreateRequestDto; -import com.web.SearchWeb.folder.dto.response.FolderResponseDto; -import com.web.SearchWeb.folder.dto.request.FolderUpdateRequestDto; - -import java.util.List; - -public interface FolderService { - // 폴더 생성 - FolderResponseDto insertFolder(int memberId, FolderCreateRequestDto folderCreateRequestDto); - // 폴더 단일 조회 - FolderResponseDto selectFolder(int memberId, int folderId); - // 폴더 목록 조회 - List selectFolderList(int memberId, String tag, String sort); - // 폴더 수정 - int updateFolder(int memberId, int folderId, FolderUpdateRequestDto folderUpdateRequestDto); - // 폴더 삭제 - int deleteFolder(int memberId, int folderId); - // 폴더 태그 목록 조회 - List selectFolderTags(int memberId); -} \ No newline at end of file diff --git a/src/main/java/com/web/SearchWeb/folder/service/FolderServiceImpl.java b/src/main/java/com/web/SearchWeb/folder/service/FolderServiceImpl.java deleted file mode 100644 index 232ce4e..0000000 --- a/src/main/java/com/web/SearchWeb/folder/service/FolderServiceImpl.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.web.SearchWeb.folder.service; - -import com.web.SearchWeb.folder.dao.FolderDao; -import com.web.SearchWeb.folder.domain.Folder; -import com.web.SearchWeb.folder.dto.request.FolderCreateRequestDto; -import com.web.SearchWeb.folder.dto.request.FolderSearchRequestDto; -import com.web.SearchWeb.folder.dto.response.FolderResponseDto; -import com.web.SearchWeb.folder.dto.request.FolderUpdateRequestDto; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.time.LocalDateTime; -import java.util.List; - -@Service -public class FolderServiceImpl implements FolderService { - - private final FolderDao folderDao; - - @Autowired - public FolderServiceImpl(FolderDao folderDao) { - this.folderDao = folderDao; - } - - @Override - public FolderResponseDto insertFolder(int memberId, FolderCreateRequestDto folderCreateRequestDto) { - LocalDateTime now = LocalDateTime.now(); - Folder folder = Folder.builder() - .member_memberId(memberId) - .name(folderCreateRequestDto.getName()) - .tag(folderCreateRequestDto.getTag()) - .created_date(now) - .modified_date(now) - .build(); - folderDao.insertFolder(folder); - // useGeneratedKeys로 생성된 folderId가 folder 객체에 자동 설정됨 - return FolderResponseDto.from(folder); - } - - @Override - public FolderResponseDto selectFolder(int memberId, int folderId) { - Folder folder = folderDao.selectFolder(memberId, folderId); - return FolderResponseDto.from(folder); - } - - @Override - public List selectFolderList(int memberId, String tag, String sort) { - FolderSearchRequestDto searchRequest = FolderSearchRequestDto.builder() - .memberId(memberId) - .tag(tag) - .sort(sort) - .build(); - List folders = folderDao.selectFolderList(searchRequest); - return FolderResponseDto.fromList(folders); - } - - @Override - public int updateFolder(int memberId, int folderId, FolderUpdateRequestDto folderUpdateRequestDto) { - Folder folder = Folder.builder() - .folderId(folderId) - .member_memberId(memberId) - .name(folderUpdateRequestDto.getName()) - .tag(folderUpdateRequestDto.getTag()) - .build(); - return folderDao.updateFolder(folder); - } - - @Override - public int deleteFolder(int memberId, int folderId) { - return folderDao.deleteFolder(memberId, folderId); - } - - @Override - public List selectFolderTags(int memberId) { - return folderDao.selectFolderTags(memberId); - } -} \ No newline at end of file diff --git a/src/main/java/com/web/SearchWeb/folder/service/MemberFolderService.java b/src/main/java/com/web/SearchWeb/folder/service/MemberFolderService.java new file mode 100644 index 0000000..ca8b448 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/service/MemberFolderService.java @@ -0,0 +1,22 @@ +package com.web.SearchWeb.folder.service; + +import com.web.SearchWeb.folder.domain.MemberFolder; +import java.util.List; +import java.util.Optional; + +public interface MemberFolderService { + + Long create(Long ownerMemberId, Long parentFolderId, String folderName, String description); + + MemberFolder get(Long memberFolderId); + + List listRootFolders(Long ownerMemberId); + + List listChildren(Long ownerMemberId, Long parentFolderId); + + void update(Long memberFolderId, String folderName, String description); + + void move(Long memberFolderId, Long newParentFolderId); + + void delete(Long memberFolderId); +} diff --git a/src/main/java/com/web/SearchWeb/folder/service/MemberFolderServiceImpl.java b/src/main/java/com/web/SearchWeb/folder/service/MemberFolderServiceImpl.java new file mode 100644 index 0000000..2023694 --- /dev/null +++ b/src/main/java/com/web/SearchWeb/folder/service/MemberFolderServiceImpl.java @@ -0,0 +1,86 @@ +package com.web.SearchWeb.folder.service; + +import com.web.SearchWeb.folder.dao.MemberFolderJpaDao; +import com.web.SearchWeb.folder.domain.MemberFolder; +import com.web.SearchWeb.folder.error.FolderException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MemberFolderServiceImpl implements MemberFolderService { + + private final MemberFolderJpaDao memberFolderJpaRepository; + + @Override + @Transactional + public Long create(Long ownerMemberId, Long parentFolderId, String folderName, String description) { + validateFolderName(folderName); + + MemberFolder folder = MemberFolder.builder() + .ownerMemberId(ownerMemberId) + .parentFolderId(parentFolderId) + .folderName(folderName) + .description(description) + .build(); + + return memberFolderJpaRepository.save(folder).getMemberFolderId(); + } + + @Override + @Transactional(readOnly = true) + public MemberFolder get(Long memberFolderId) { + return memberFolderJpaRepository.findById(memberFolderId) + .orElseThrow(FolderException.NotFound::new); + } + + @Override + @Transactional(readOnly = true) + public List listRootFolders(Long ownerMemberId) { + return memberFolderJpaRepository.findAllByOwnerMemberIdAndParentFolderIdIsNull(ownerMemberId); + } + + @Override + @Transactional(readOnly = true) + public List listChildren(Long ownerMemberId, Long parentFolderId) { + return memberFolderJpaRepository.findAllByOwnerMemberIdAndParentFolderId(ownerMemberId, parentFolderId); + } + + @Override + @Transactional + public void update(Long memberFolderId, String folderName, String description) { + validateFolderName(folderName); + + MemberFolder folder = memberFolderJpaRepository.findById(memberFolderId) + .orElseThrow(FolderException.NotFound::new); + + folder.changeInfo(folderName,description); + // TODO : 수정 로직 좀 더 생각해보기 + } + + @Override + @Transactional + public void move(Long memberFolderId, Long newParentFolderId) { + MemberFolder folder = memberFolderJpaRepository.findById(memberFolderId) + .orElseThrow(FolderException.NotFound::new); + folder.changeParent(newParentFolderId); + } + + @Override + @Transactional + public void delete(Long memberFolderId) { + if (!memberFolderJpaRepository.existsById(memberFolderId)) { + return; // 멱등 삭제 + } + memberFolderJpaRepository.deleteById(memberFolderId); + } + + private void validateFolderName(String folderName) { + if (folderName == null || folderName.isBlank()) { + throw new IllegalArgumentException("folderName must not be blank"); + } + + } +} \ No newline at end of file diff --git a/src/main/resources/mapper/folder-mapper.xml b/src/main/resources/mapper/folder-mapper.xml deleted file mode 100644 index 3f5ab4a..0000000 --- a/src/main/resources/mapper/folder-mapper.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - INSERT INTO folder(member_memberId, name, tag) - VALUES(#{member_memberId}, #{name}, #{tag}) - - - - - - - - - - - - - - UPDATE folder - SET name = #{name}, - tag = #{tag} - WHERE folderId = #{folderId} - AND member_memberId = #{member_memberId} - - - - - - DELETE FROM folder - WHERE folderId = #{folderId} - AND member_memberId = #{memberId} - - - - - - - -