diff --git a/src/main/java/com/leets/blog/controller/ApiControllerAdvice.java b/src/main/java/com/leets/blog/controller/ApiControllerAdvice.java new file mode 100644 index 0000000..a02d986 --- /dev/null +++ b/src/main/java/com/leets/blog/controller/ApiControllerAdvice.java @@ -0,0 +1,24 @@ +package com.leets.blog.controller; + +import com.leets.blog.support.error.PostException; +import com.leets.blog.support.response.ApiResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ApiControllerAdvice { + private final Logger log = LoggerFactory.getLogger(getClass()); + + @ExceptionHandler(PostException.class) + public ResponseEntity handlePostException(PostException e) { + switch (e.getErrorType().getLogLevel()) { + case ERROR -> log.error("PostException : {}", e.getMessage(), e); + case WARN -> log.warn("PostException : {}", e.getMessage(), e); + default -> log.info("PostException : {}", e.getMessage(), e); + } + return new ResponseEntity<>(ApiResponse.error(e.getErrorType(), e.getData()), e.getErrorType().getStatus()); + } +} diff --git a/src/main/java/com/leets/blog/controller/v1/PostController.java b/src/main/java/com/leets/blog/controller/v1/PostController.java new file mode 100644 index 0000000..b2b3735 --- /dev/null +++ b/src/main/java/com/leets/blog/controller/v1/PostController.java @@ -0,0 +1,50 @@ +package com.leets.blog.controller.v1; + +import com.leets.blog.dto.request.AddPostRequest; +import com.leets.blog.dto.request.UpdatePostRequest; +import com.leets.blog.dto.response.PostResponse; +import com.leets.blog.service.PostService; +import com.leets.blog.support.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/posts") +@RequiredArgsConstructor +public class PostController { + + private final PostService postService; + + @GetMapping + public ApiResponse> getPosts() { + List result = postService.getPosts(); + return ApiResponse.success(result); + } + + @GetMapping("/{postId}") + public ApiResponse getPostByPostId(@PathVariable Long postId) { + PostResponse result = postService.getPostByPostId(postId); + return ApiResponse.success(result); + } + + @PostMapping + public ApiResponse writePost(@RequestBody AddPostRequest request) { + PostResponse result = postService.addPost(request); + return ApiResponse.success(result); + } + + @PutMapping("/{postId}") + public ApiResponse reWritePost(@PathVariable Long postId, @RequestBody UpdatePostRequest request) { + PostResponse result = postService.updatePost(postId, request); + return ApiResponse.success(result); + } + + @DeleteMapping("/{postId}") + public ApiResponse deletePost(@PathVariable Long postId) { + postService.deletePost(postId); + return ApiResponse.success(); + } + +} diff --git a/src/main/java/com/leets/blog/controller/v1/TestController.java b/src/main/java/com/leets/blog/controller/v1/TestController.java index 51778ef..c1889ac 100644 --- a/src/main/java/com/leets/blog/controller/v1/TestController.java +++ b/src/main/java/com/leets/blog/controller/v1/TestController.java @@ -1,7 +1,7 @@ package com.leets.blog.controller.v1; -import com.leets.blog.dto.StringRequest; -import com.leets.blog.dto.StringResponse; +import com.leets.blog.dto.request.StringRequest; +import com.leets.blog.dto.response.StringResponse; import com.leets.blog.service.DtoConverter; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; diff --git a/src/main/java/com/leets/blog/dto/request/AddPostRequest.java b/src/main/java/com/leets/blog/dto/request/AddPostRequest.java new file mode 100644 index 0000000..ffb6498 --- /dev/null +++ b/src/main/java/com/leets/blog/dto/request/AddPostRequest.java @@ -0,0 +1,21 @@ +package com.leets.blog.dto.request; + +import com.leets.blog.support.error.ErrorType; +import com.leets.blog.support.error.PostException; + +public record AddPostRequest( + String title, + String content +) { + public AddPostRequest { + // 제목이 10자를 초과할 경우 예외 발생 + if (title == null || title.length() < 10) { + throw new PostException(ErrorType.INVALID_EXCEPTION); + } + + // 내용이 비어있는지 확인하는 추가 검증 + if (content == null || content.isBlank()) { + throw new PostException(ErrorType.INVALID_EXCEPTION); + } + } +} diff --git a/src/main/java/com/leets/blog/dto/StringRequest.java b/src/main/java/com/leets/blog/dto/request/StringRequest.java similarity index 61% rename from src/main/java/com/leets/blog/dto/StringRequest.java rename to src/main/java/com/leets/blog/dto/request/StringRequest.java index ba8fec6..4deb476 100644 --- a/src/main/java/com/leets/blog/dto/StringRequest.java +++ b/src/main/java/com/leets/blog/dto/request/StringRequest.java @@ -1,4 +1,4 @@ -package com.leets.blog.dto; +package com.leets.blog.dto.request; public record StringRequest( String string diff --git a/src/main/java/com/leets/blog/dto/request/UpdatePostRequest.java b/src/main/java/com/leets/blog/dto/request/UpdatePostRequest.java new file mode 100644 index 0000000..1e1bfb9 --- /dev/null +++ b/src/main/java/com/leets/blog/dto/request/UpdatePostRequest.java @@ -0,0 +1,21 @@ +package com.leets.blog.dto.request; + +import com.leets.blog.support.error.ErrorType; +import com.leets.blog.support.error.PostException; + +public record UpdatePostRequest( + String title, + String content +) { + public UpdatePostRequest { + // 제목이 10자를 초과할 경우 예외 발생 + if (title != null && title.length() > 10) { + throw new PostException(ErrorType.INVALID_EXCEPTION); + } + + // 내용이 비어있는지 확인하는 추가 검증 + if (content == null || content.isBlank()) { + throw new PostException(ErrorType.INVALID_EXCEPTION); + } + } +} diff --git a/src/main/java/com/leets/blog/dto/response/PostResponse.java b/src/main/java/com/leets/blog/dto/response/PostResponse.java new file mode 100644 index 0000000..0fa6b75 --- /dev/null +++ b/src/main/java/com/leets/blog/dto/response/PostResponse.java @@ -0,0 +1,8 @@ +package com.leets.blog.dto.response; + +public record PostResponse( + Long postId, + String title, + String content +) { +} diff --git a/src/main/java/com/leets/blog/dto/StringResponse.java b/src/main/java/com/leets/blog/dto/response/StringResponse.java similarity index 70% rename from src/main/java/com/leets/blog/dto/StringResponse.java rename to src/main/java/com/leets/blog/dto/response/StringResponse.java index 1930bee..6324052 100644 --- a/src/main/java/com/leets/blog/dto/StringResponse.java +++ b/src/main/java/com/leets/blog/dto/response/StringResponse.java @@ -1,4 +1,4 @@ -package com.leets.blog.dto; +package com.leets.blog.dto.response; public record StringResponse( String string_one, diff --git a/src/main/java/com/leets/blog/entity/Post.java b/src/main/java/com/leets/blog/entity/Post.java index e2f62a5..9a6d4f6 100644 --- a/src/main/java/com/leets/blog/entity/Post.java +++ b/src/main/java/com/leets/blog/entity/Post.java @@ -1,6 +1,7 @@ package com.leets.blog.entity; import jakarta.persistence.*; +import lombok.Getter; import java.time.LocalDateTime; import java.util.ArrayList; @@ -8,6 +9,7 @@ @Entity @Table(name = "post") +@Getter public class Post { public Post() {} @@ -39,4 +41,12 @@ public Post(String title, String content) { // Post는 여러 개의 Comment와 연결됨 (1:N) @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) private List comments = new ArrayList<>(); + + public void setTitle(String title) { + this.title = title; + } + + public void setContent(String content) { + this.content = content; + } } diff --git a/src/main/java/com/leets/blog/repository/CommentRepository.java b/src/main/java/com/leets/blog/repository/CommentRepository.java new file mode 100644 index 0000000..235c707 --- /dev/null +++ b/src/main/java/com/leets/blog/repository/CommentRepository.java @@ -0,0 +1,9 @@ +package com.leets.blog.repository; + +import com.leets.blog.entity.Comment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CommentRepository extends JpaRepository { +} diff --git a/src/main/java/com/leets/blog/repository/PostRepository.java b/src/main/java/com/leets/blog/repository/PostRepository.java new file mode 100644 index 0000000..91ec723 --- /dev/null +++ b/src/main/java/com/leets/blog/repository/PostRepository.java @@ -0,0 +1,9 @@ +package com.leets.blog.repository; + +import com.leets.blog.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PostRepository extends JpaRepository { +} diff --git a/src/main/java/com/leets/blog/repository/UserRepository.java b/src/main/java/com/leets/blog/repository/UserRepository.java new file mode 100644 index 0000000..70e6bb6 --- /dev/null +++ b/src/main/java/com/leets/blog/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.leets.blog.repository; + +import com.leets.blog.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { +} diff --git a/src/main/java/com/leets/blog/service/DtoConverter.java b/src/main/java/com/leets/blog/service/DtoConverter.java index 11beaa7..c707e08 100644 --- a/src/main/java/com/leets/blog/service/DtoConverter.java +++ b/src/main/java/com/leets/blog/service/DtoConverter.java @@ -1,10 +1,13 @@ package com.leets.blog.service; -import com.leets.blog.dto.StringRequest; -import com.leets.blog.dto.StringResponse; -import org.springframework.stereotype.Service; +import com.leets.blog.dto.response.StringResponse; +import com.leets.blog.dto.request.AddPostRequest; +import com.leets.blog.dto.request.UpdatePostRequest; +import com.leets.blog.dto.response.PostResponse; +import com.leets.blog.entity.Post; +import org.springframework.stereotype.Component; -@Service +@Component public class DtoConverter { // NOTE : StringRequest -> StringResponse @@ -12,4 +15,27 @@ public StringResponse convert(String string) { return new StringResponse(string, string); } + public Post toPost(AddPostRequest request) { + return new Post( + request.title(), + request.content() + ); + } + + public Post toPost(UpdatePostRequest request) { + return new Post( + request.title(), + request.content() + ); + } + + public PostResponse toDTO(Post post) { + return new PostResponse( + post.getId(), + post.getTitle(), + post.getContent() + ); + } + + } diff --git a/src/main/java/com/leets/blog/service/PostFinder.java b/src/main/java/com/leets/blog/service/PostFinder.java new file mode 100644 index 0000000..cebca24 --- /dev/null +++ b/src/main/java/com/leets/blog/service/PostFinder.java @@ -0,0 +1,34 @@ +package com.leets.blog.service; + +import com.leets.blog.entity.Post; +import com.leets.blog.repository.PostRepository; +import com.leets.blog.support.error.ErrorType; +import com.leets.blog.support.error.PostException; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class PostFinder { + + private final PostRepository postRepository; + + public PostFinder(PostRepository postRepository) { + this.postRepository = postRepository; + } + + public List findPosts() { + List posts = postRepository.findAll(); + + if (posts.isEmpty()) { + throw new PostException(ErrorType.NOT_EXIST_POST); + } + + return posts; + } + + public Post findPostByPostId(Long postId) { + return postRepository.findById(postId) + .orElseThrow(() -> new PostException(ErrorType.NOT_FOUND_POST)); + } +} diff --git a/src/main/java/com/leets/blog/service/PostManager.java b/src/main/java/com/leets/blog/service/PostManager.java new file mode 100644 index 0000000..bbe79a5 --- /dev/null +++ b/src/main/java/com/leets/blog/service/PostManager.java @@ -0,0 +1,39 @@ +package com.leets.blog.service; + +import com.leets.blog.entity.Post; +import com.leets.blog.repository.PostRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class PostManager { + + private final PostRepository postRepository; + + public PostManager(PostRepository postRepository) { + this.postRepository = postRepository; + } + + @Transactional + public Post add (Post post) { + return postRepository.save(post); + } + + // NOTE : userId 제외 + @Transactional + public Post update (Long postId, Post post) { + + Post savedPost = postRepository.findById(postId).orElseThrow(RuntimeException::new); + + savedPost.setTitle(post.getTitle()); + savedPost.setContent(post.getContent()); + + return savedPost; + } + + // NOTE : userId 제외 + @Transactional + public void delete (Long postId) { + postRepository.deleteById(postId); + } +} diff --git a/src/main/java/com/leets/blog/service/PostService.java b/src/main/java/com/leets/blog/service/PostService.java new file mode 100644 index 0000000..f6167aa --- /dev/null +++ b/src/main/java/com/leets/blog/service/PostService.java @@ -0,0 +1,69 @@ +package com.leets.blog.service; + +import com.leets.blog.dto.request.AddPostRequest; +import com.leets.blog.dto.request.UpdatePostRequest; +import com.leets.blog.dto.response.PostResponse; +import com.leets.blog.entity.Post; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class PostService { + + private final PostFinder postFinder; + private final PostManager postManager; + private final PostValidator postValidator; + private final DtoConverter dtoConverter; + + public PostService(PostFinder postFinder, PostManager postManager, PostValidator postValidator, DtoConverter dtoConverter) { + this.postFinder = postFinder; + this.postManager = postManager; + this.postValidator = postValidator; + this.dtoConverter = dtoConverter; + } + + + public List getPosts() { + List posts = postFinder.findPosts(); + + return posts.stream() + .map(dtoConverter::toDTO) + .collect(Collectors.toList()); + } + + public PostResponse getPostByPostId(Long postId) { + Post post = postFinder.findPostByPostId(postId); + + PostResponse postResponse = dtoConverter.toDTO(post); + + return postResponse; + } + + public PostResponse addPost(AddPostRequest addPostRequest) { + Post post = dtoConverter.toPost(addPostRequest); + + Post newPost = postManager.add(post); + + PostResponse postResponse = dtoConverter.toDTO(newPost); + + return postResponse; + } + + // TODO : 사용자 정보 받기 + 사용자 정보 밸리데이터 넣기 + public PostResponse updatePost(Long postId, UpdatePostRequest updatePostRequest) { + Post post = dtoConverter.toPost(updatePostRequest); + + Post newPost = postManager.update(postId, post); + + PostResponse postResponse = dtoConverter.toDTO(newPost); + + return postResponse; + } + + // TODO : 사용자 정보 받기 + 사용자 정보 밸리데이터 넣기 + public void deletePost(Long postId) { + postManager.delete(postId); + } +} diff --git a/src/main/java/com/leets/blog/service/PostValidator.java b/src/main/java/com/leets/blog/service/PostValidator.java new file mode 100644 index 0000000..5c504a1 --- /dev/null +++ b/src/main/java/com/leets/blog/service/PostValidator.java @@ -0,0 +1,19 @@ +package com.leets.blog.service; + +import org.springframework.stereotype.Component; + +@Component +public class PostValidator { + + // NOTE : 존재하지 않는 게시글 접근 시 + public void validateGet() { + throw new IllegalArgumentException("작성 중"); + } + + + // NOTE : 잘못된 입력값 요청 시 + public void validateNew() { + throw new IllegalArgumentException("작성 중"); + } + +} diff --git a/src/main/java/com/leets/blog/support/error/ErrorCode.java b/src/main/java/com/leets/blog/support/error/ErrorCode.java new file mode 100644 index 0000000..90c5307 --- /dev/null +++ b/src/main/java/com/leets/blog/support/error/ErrorCode.java @@ -0,0 +1,11 @@ +package com.leets.blog.support.error; + +public enum ErrorCode { + + E500, + + E404, + + E400, E403 + +} diff --git a/src/main/java/com/leets/blog/support/error/ErrorMessage.java b/src/main/java/com/leets/blog/support/error/ErrorMessage.java new file mode 100644 index 0000000..bd0783d --- /dev/null +++ b/src/main/java/com/leets/blog/support/error/ErrorMessage.java @@ -0,0 +1,34 @@ +package com.leets.blog.support.error; + +public class ErrorMessage { + + private final String code; + + private final String message; + + private final Object data; + + public ErrorMessage(ErrorType errorType) { + this.code = errorType.getCode().name(); + this.message = errorType.getMessage(); + this.data = null; + } + + public ErrorMessage(ErrorType errorType, Object data) { + this.code = errorType.getCode().name(); + this.message = errorType.getMessage(); + this.data = data; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public Object getData() { + return data; + } +} diff --git a/src/main/java/com/leets/blog/support/error/ErrorType.java b/src/main/java/com/leets/blog/support/error/ErrorType.java new file mode 100644 index 0000000..1050907 --- /dev/null +++ b/src/main/java/com/leets/blog/support/error/ErrorType.java @@ -0,0 +1,50 @@ +package com.leets.blog.support.error; + +import org.springframework.boot.logging.LogLevel; +import org.springframework.http.HttpStatus; + +public enum ErrorType { + + INVALID_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.E400, "잘못된 요청입니다.", + LogLevel.ERROR), + + NOT_FOUND_POST(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.E400, "게시글을 찾을 수 없습니다.", + LogLevel.ERROR), + + NOT_EXIST_POST(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.E400, "게시글들이 존재하지 않습니다.", + LogLevel.ERROR) + ; + + + private final HttpStatus status; + + private final ErrorCode code; + + private final String message; + + private final LogLevel logLevel; + + ErrorType(HttpStatus status, ErrorCode code, String message, LogLevel logLevel) { + + this.status = status; + this.code = code; + this.message = message; + this.logLevel = logLevel; + } + + public HttpStatus getStatus() { + return status; + } + + public ErrorCode getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public LogLevel getLogLevel() { + return logLevel; + } +} diff --git a/src/main/java/com/leets/blog/support/error/PostException.java b/src/main/java/com/leets/blog/support/error/PostException.java new file mode 100644 index 0000000..6f9b9bb --- /dev/null +++ b/src/main/java/com/leets/blog/support/error/PostException.java @@ -0,0 +1,28 @@ +package com.leets.blog.support.error; + +public class PostException extends RuntimeException { + + private final ErrorType errorType; + + private final Object data; + + public PostException(ErrorType errorType) { + super(errorType.getMessage()); + this.errorType = errorType; + this.data = null; + } + + public PostException(ErrorType errorType, Object data) { + super(errorType.getMessage()); + this.errorType = errorType; + this.data = data; + } + + public ErrorType getErrorType() { + return errorType; + } + + public Object getData() { + return data; + } +} diff --git a/src/main/java/com/leets/blog/support/response/ApiResponse.java b/src/main/java/com/leets/blog/support/response/ApiResponse.java new file mode 100644 index 0000000..16c2e41 --- /dev/null +++ b/src/main/java/com/leets/blog/support/response/ApiResponse.java @@ -0,0 +1,48 @@ +package com.leets.blog.support.response; + +import com.leets.blog.support.error.ErrorMessage; +import com.leets.blog.support.error.ErrorType; + +public class ApiResponse { + + private final ResultType result; + + private final S data; + + private final ErrorMessage error; + + private ApiResponse(ResultType result, S data, ErrorMessage error) { + this.result = result; + this.data = data; + this.error = error; + } + + public static ApiResponse success() { + return new ApiResponse<>(ResultType.SUCCESS, null, null); + } + + public static ApiResponse success(S data) { + return new ApiResponse<>(ResultType.SUCCESS, data, null); + } + + public static ApiResponse error(ErrorType error) { + return new ApiResponse<>(ResultType.ERROR, null, new ErrorMessage(error, null)); + } + + public static ApiResponse error(ErrorType error, Object errorData) { + return new ApiResponse<>(ResultType.ERROR, null, new ErrorMessage(error, errorData)); + } + + public ResultType getResult() { + return result; + } + + public Object getData() { + return data; + } + + public ErrorMessage getError() { + return error; + } + +} \ No newline at end of file diff --git a/src/main/java/com/leets/blog/support/response/ResultType.java b/src/main/java/com/leets/blog/support/response/ResultType.java new file mode 100644 index 0000000..f2ebb70 --- /dev/null +++ b/src/main/java/com/leets/blog/support/response/ResultType.java @@ -0,0 +1,7 @@ +package com.leets.blog.support.response; + +public enum ResultType { + + SUCCESS, ERROR + +}