From 4e099eda44d339afd594cdfc10151a9d21e9a6e7 Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 13:43:51 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[Chore]=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20=EC=9E=AC=EC=84=A4=EA=B3=84=20?= =?UTF-8?q?(=EA=B3=84=EC=B8=B5=20=EA=B5=AC=EC=A1=B0=20->=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EA=B5=AC=EC=A1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment => domain/comment/entity}/Comment.java | 8 ++++---- .../leets/assignment/domain/post/entity/BlockType.java | 5 +++++ .../{entity/post => domain/post/entity}/Post.java | 6 +++--- .../{entity/post => domain/post/entity}/PostBlock.java | 2 +- .../{entity/user => domain/user/entity}/User.java | 4 ++-- .../java/com/leets/assignment/entity/post/BlockType.java | 5 ----- .../{entity => global}/baseEntity/BaseEntity.java | 2 +- .../{ => test}/controller/HealthCheckController.java | 6 +++--- .../{ => test}/controller/StringRepeatController.java | 8 ++++---- .../assignment/{ => test}/dto/HealthCheckResponseDto.java | 2 +- .../leets/assignment/{ => test}/dto/RepeatRequestDto.java | 2 +- .../assignment/{ => test}/dto/RepeatResponseDto.java | 2 +- .../assignment/{ => test}/service/HealthCheckService.java | 4 ++-- .../{ => test}/service/StringRepeatService.java | 5 ++--- 14 files changed, 30 insertions(+), 31 deletions(-) rename LeeKunHee/src/main/java/com/leets/assignment/{entity/comment => domain/comment/entity}/Comment.java (78%) create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/BlockType.java rename LeeKunHee/src/main/java/com/leets/assignment/{entity/post => domain/post/entity}/Post.java (83%) rename LeeKunHee/src/main/java/com/leets/assignment/{entity/post => domain/post/entity}/PostBlock.java (95%) rename LeeKunHee/src/main/java/com/leets/assignment/{entity/user => domain/user/entity}/User.java (89%) delete mode 100644 LeeKunHee/src/main/java/com/leets/assignment/entity/post/BlockType.java rename LeeKunHee/src/main/java/com/leets/assignment/{entity => global}/baseEntity/BaseEntity.java (94%) rename LeeKunHee/src/main/java/com/leets/assignment/{ => test}/controller/HealthCheckController.java (72%) rename LeeKunHee/src/main/java/com/leets/assignment/{ => test}/controller/StringRepeatController.java (76%) rename LeeKunHee/src/main/java/com/leets/assignment/{ => test}/dto/HealthCheckResponseDto.java (87%) rename LeeKunHee/src/main/java/com/leets/assignment/{ => test}/dto/RepeatRequestDto.java (85%) rename LeeKunHee/src/main/java/com/leets/assignment/{ => test}/dto/RepeatResponseDto.java (88%) rename LeeKunHee/src/main/java/com/leets/assignment/{ => test}/service/HealthCheckService.java (65%) rename LeeKunHee/src/main/java/com/leets/assignment/{ => test}/service/StringRepeatService.java (60%) diff --git a/LeeKunHee/src/main/java/com/leets/assignment/entity/comment/Comment.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/comment/entity/Comment.java similarity index 78% rename from LeeKunHee/src/main/java/com/leets/assignment/entity/comment/Comment.java rename to LeeKunHee/src/main/java/com/leets/assignment/domain/comment/entity/Comment.java index 80e31be..834dc25 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/entity/comment/Comment.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/comment/entity/Comment.java @@ -1,8 +1,8 @@ -package com.leets.assignment.entity.comment; +package com.leets.assignment.domain.comment.entity; -import com.leets.assignment.entity.baseEntity.BaseEntity; -import com.leets.assignment.entity.user.User; -import com.leets.assignment.entity.post.Post; +import com.leets.assignment.global.baseEntity.BaseEntity; +import com.leets.assignment.domain.user.entity.User; +import com.leets.assignment.domain.post.entity.Post; import jakarta.persistence.*; import lombok.*; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/BlockType.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/BlockType.java new file mode 100644 index 0000000..071b1af --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/BlockType.java @@ -0,0 +1,5 @@ +package com.leets.assignment.domain.post.entity; + +public enum BlockType { + TEXT, IMAGE +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/entity/post/Post.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java similarity index 83% rename from LeeKunHee/src/main/java/com/leets/assignment/entity/post/Post.java rename to LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java index 7a29cbb..3d0cd3d 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/entity/post/Post.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java @@ -1,7 +1,7 @@ -package com.leets.assignment.entity.post; +package com.leets.assignment.domain.post.entity; -import com.leets.assignment.entity.baseEntity.BaseEntity; -import com.leets.assignment.entity.user.User; +import com.leets.assignment.global.baseEntity.BaseEntity; +import com.leets.assignment.domain.user.entity.User; import jakarta.persistence.*; import lombok.*; import java.util.ArrayList; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/entity/post/PostBlock.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/PostBlock.java similarity index 95% rename from LeeKunHee/src/main/java/com/leets/assignment/entity/post/PostBlock.java rename to LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/PostBlock.java index e791629..3399bd6 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/entity/post/PostBlock.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/PostBlock.java @@ -1,4 +1,4 @@ -package com.leets.assignment.entity.post; +package com.leets.assignment.domain.post.entity; import jakarta.persistence.*; import lombok.*; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/entity/user/User.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/user/entity/User.java similarity index 89% rename from LeeKunHee/src/main/java/com/leets/assignment/entity/user/User.java rename to LeeKunHee/src/main/java/com/leets/assignment/domain/user/entity/User.java index 362ead2..d85c95b 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/entity/user/User.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/user/entity/User.java @@ -1,6 +1,6 @@ -package com.leets.assignment.entity.user; +package com.leets.assignment.domain.user.entity; -import com.leets.assignment.entity.baseEntity.BaseEntity; +import com.leets.assignment.global.baseEntity.BaseEntity; import jakarta.persistence.*; import lombok.*; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/entity/post/BlockType.java b/LeeKunHee/src/main/java/com/leets/assignment/entity/post/BlockType.java deleted file mode 100644 index 3073271..0000000 --- a/LeeKunHee/src/main/java/com/leets/assignment/entity/post/BlockType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.leets.assignment.entity.post; - -public enum BlockType { - TEXT, IMAGE -} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/entity/baseEntity/BaseEntity.java b/LeeKunHee/src/main/java/com/leets/assignment/global/baseEntity/BaseEntity.java similarity index 94% rename from LeeKunHee/src/main/java/com/leets/assignment/entity/baseEntity/BaseEntity.java rename to LeeKunHee/src/main/java/com/leets/assignment/global/baseEntity/BaseEntity.java index c690536..9f40ca0 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/entity/baseEntity/BaseEntity.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/baseEntity/BaseEntity.java @@ -1,4 +1,4 @@ -package com.leets.assignment.entity.baseEntity; +package com.leets.assignment.global.baseEntity; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/controller/HealthCheckController.java b/LeeKunHee/src/main/java/com/leets/assignment/test/controller/HealthCheckController.java similarity index 72% rename from LeeKunHee/src/main/java/com/leets/assignment/controller/HealthCheckController.java rename to LeeKunHee/src/main/java/com/leets/assignment/test/controller/HealthCheckController.java index 1f24f99..2cd77aa 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/controller/HealthCheckController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/test/controller/HealthCheckController.java @@ -1,7 +1,7 @@ -package com.leets.assignment.controller; +package com.leets.assignment.test.controller; -import com.leets.assignment.dto.HealthCheckResponseDto; -import com.leets.assignment.service.HealthCheckService; +import com.leets.assignment.test.dto.HealthCheckResponseDto; +import com.leets.assignment.test.service.HealthCheckService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/controller/StringRepeatController.java b/LeeKunHee/src/main/java/com/leets/assignment/test/controller/StringRepeatController.java similarity index 76% rename from LeeKunHee/src/main/java/com/leets/assignment/controller/StringRepeatController.java rename to LeeKunHee/src/main/java/com/leets/assignment/test/controller/StringRepeatController.java index 1100a53..a84b27d 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/controller/StringRepeatController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/test/controller/StringRepeatController.java @@ -1,8 +1,8 @@ -package com.leets.assignment.controller; +package com.leets.assignment.test.controller; -import com.leets.assignment.dto.RepeatRequestDto; -import com.leets.assignment.dto.RepeatResponseDto; -import com.leets.assignment.service.StringRepeatService; +import com.leets.assignment.test.dto.RepeatRequestDto; +import com.leets.assignment.test.dto.RepeatResponseDto; +import com.leets.assignment.test.service.StringRepeatService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/dto/HealthCheckResponseDto.java b/LeeKunHee/src/main/java/com/leets/assignment/test/dto/HealthCheckResponseDto.java similarity index 87% rename from LeeKunHee/src/main/java/com/leets/assignment/dto/HealthCheckResponseDto.java rename to LeeKunHee/src/main/java/com/leets/assignment/test/dto/HealthCheckResponseDto.java index 1c9fe34..44c7a42 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/dto/HealthCheckResponseDto.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/test/dto/HealthCheckResponseDto.java @@ -1,4 +1,4 @@ -package com.leets.assignment.dto; +package com.leets.assignment.test.dto; public class HealthCheckResponseDto { private final String message; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/dto/RepeatRequestDto.java b/LeeKunHee/src/main/java/com/leets/assignment/test/dto/RepeatRequestDto.java similarity index 85% rename from LeeKunHee/src/main/java/com/leets/assignment/dto/RepeatRequestDto.java rename to LeeKunHee/src/main/java/com/leets/assignment/test/dto/RepeatRequestDto.java index 600a144..2f6e3e4 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/dto/RepeatRequestDto.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/test/dto/RepeatRequestDto.java @@ -1,4 +1,4 @@ -package com.leets.assignment.dto; +package com.leets.assignment.test.dto; public class RepeatRequestDto { private String value; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/dto/RepeatResponseDto.java b/LeeKunHee/src/main/java/com/leets/assignment/test/dto/RepeatResponseDto.java similarity index 88% rename from LeeKunHee/src/main/java/com/leets/assignment/dto/RepeatResponseDto.java rename to LeeKunHee/src/main/java/com/leets/assignment/test/dto/RepeatResponseDto.java index ed58d11..ad76efe 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/dto/RepeatResponseDto.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/test/dto/RepeatResponseDto.java @@ -1,4 +1,4 @@ -package com.leets.assignment.dto; +package com.leets.assignment.test.dto; import lombok.Getter; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/service/HealthCheckService.java b/LeeKunHee/src/main/java/com/leets/assignment/test/service/HealthCheckService.java similarity index 65% rename from LeeKunHee/src/main/java/com/leets/assignment/service/HealthCheckService.java rename to LeeKunHee/src/main/java/com/leets/assignment/test/service/HealthCheckService.java index f30e055..4e1fddb 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/service/HealthCheckService.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/test/service/HealthCheckService.java @@ -1,6 +1,6 @@ -package com.leets.assignment.service; +package com.leets.assignment.test.service; -import com.leets.assignment.dto.HealthCheckResponseDto; +import com.leets.assignment.test.dto.HealthCheckResponseDto; import org.springframework.stereotype.Service; @Service diff --git a/LeeKunHee/src/main/java/com/leets/assignment/service/StringRepeatService.java b/LeeKunHee/src/main/java/com/leets/assignment/test/service/StringRepeatService.java similarity index 60% rename from LeeKunHee/src/main/java/com/leets/assignment/service/StringRepeatService.java rename to LeeKunHee/src/main/java/com/leets/assignment/test/service/StringRepeatService.java index c35bee2..e3c12a1 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/service/StringRepeatService.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/test/service/StringRepeatService.java @@ -1,8 +1,7 @@ -package com.leets.assignment.service; +package com.leets.assignment.test.service; -import com.leets.assignment.dto.RepeatRequestDto; -import com.leets.assignment.dto.RepeatResponseDto; +import com.leets.assignment.test.dto.RepeatResponseDto; import org.springframework.stereotype.Service; From 31752fe541f574dc5f4b190c6a667c7ecb60cadf Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 15:24:21 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LeeKunHee/build.gradle | 2 + .../assignment/AssignmentApplication.java | 6 +- .../post/controller/PostController.java | 24 ++++++++ .../domain/post/dto/req/PostRequestDTO.java | 42 +++++++++++++ .../domain/post/dto/res/PostResponseDTO.java | 37 ++++++++++++ .../post/repository/PostRepository.java | 10 ++++ .../domain/post/service/PostService.java | 59 +++++++++++++++++++ .../src/main/resources/application.properties | 14 +++++ 8 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/repository/PostRepository.java create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java diff --git a/LeeKunHee/build.gradle b/LeeKunHee/build.gradle index 260d10b..c49891f 100644 --- a/LeeKunHee/build.gradle +++ b/LeeKunHee/build.gradle @@ -32,6 +32,8 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + runtimeOnly 'com.h2database:h2' //임시 저장. } tasks.named('test') { diff --git a/LeeKunHee/src/main/java/com/leets/assignment/AssignmentApplication.java b/LeeKunHee/src/main/java/com/leets/assignment/AssignmentApplication.java index 73ea32e..0d59b24 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/AssignmentApplication.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/AssignmentApplication.java @@ -2,10 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; - -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) // 이 부분 추가 +@SpringBootApplication +@EnableJpaAuditing public class AssignmentApplication { public static void main(String[] args) { SpringApplication.run(AssignmentApplication.class, args); diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java new file mode 100644 index 0000000..0d50f23 --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -0,0 +1,24 @@ +package com.leets.assignment.domain.post.controller; + +import com.leets.assignment.domain.post.dto.req.PostRequestDTO; +import com.leets.assignment.domain.post.dto.res.PostResponseDTO; +import com.leets.assignment.domain.post.service.PostService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/posts") +@RequiredArgsConstructor +public class PostController { + + private final PostService postService; + + @PostMapping + public PostResponseDTO.PostDetailResDTO createPost( + @Valid @RequestBody PostRequestDTO.CreatePostDTO request + ) { + // 우선 ApiResponse 없이 DTO만 반환해서 잘 나오는지 확인해봅시다! + return postService.createPost(request); + } +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java new file mode 100644 index 0000000..dc38026 --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java @@ -0,0 +1,42 @@ +package com.leets.assignment.domain.post.dto.req; + +import com.leets.assignment.domain.post.entity.BlockType; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.util.List; + +public class PostRequestDTO { + + @Getter + @NoArgsConstructor + public static class CreatePostDTO { + @NotBlank(message = "제목을 입력해주세요.") + @Size(max = 255, message = "제목은 최대 255자까지 가능합니다.") + private String title; + + @NotNull(message = "작성자 ID는 필수입니다.") + private Long userId; + + @NotEmpty(message = "내용을 입력해주세요.") + @Valid // 내부 블록들의 검증을 수행하기 위해 필수! + private List blocks; + } + + @Getter + @NoArgsConstructor + public static class BlockDTO { + @NotNull(message = "순서는 필수입니다.") + private Integer sequence; + + @NotNull(message = "블록 타입은 필수입니다.") + private BlockType blockType; + + @NotBlank(message = "내용을 입력해주세요.") // 각 블록의 내용이 비었을 때 + private String content; + } +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java new file mode 100644 index 0000000..db14ae4 --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java @@ -0,0 +1,37 @@ +package com.leets.assignment.domain.post.dto.res; + +import com.leets.assignment.domain.post.entity.BlockType; +import lombok.Builder; +import java.time.LocalDateTime; +import java.util.List; + +public class PostResponseDTO { + + // 게시글 목록 조회 + @Builder + public record PostListResDTO ( + Long postId, + String title, + String nickname, + LocalDateTime createdAt + ){} + + // 게시글 상세 조회 + @Builder + public record PostDetailResDTO ( + Long postId, + String title, + String nickname, + List blocks, // 블록 리스트 + LocalDateTime createdAt, + LocalDateTime updatedAt + ){} + + @Builder + public record BlockResDTO ( + Long blockId, + Integer sequence, + BlockType blockType, + String content + ){} +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/repository/PostRepository.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/repository/PostRepository.java new file mode 100644 index 0000000..532519f --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/repository/PostRepository.java @@ -0,0 +1,10 @@ +package com.leets.assignment.domain.post.repository; + +import com.leets.assignment.domain.post.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PostRepository extends JpaRepository { + // 기본적으로 save(), findById(), delete() 등을 제공. +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java new file mode 100644 index 0000000..d6b4b2c --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java @@ -0,0 +1,59 @@ +package com.leets.assignment.domain.post.service; + +import com.leets.assignment.domain.post.dto.req.PostRequestDTO; +import com.leets.assignment.domain.post.dto.res.PostResponseDTO; +import com.leets.assignment.domain.post.entity.Post; +import com.leets.assignment.domain.post.entity.PostBlock; +import com.leets.assignment.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PostService { + + private final PostRepository postRepository; + + @Transactional + public PostResponseDTO.PostDetailResDTO createPost(PostRequestDTO.CreatePostDTO request) { + // 1. Post 엔티티 생성 + Post post = Post.builder() + .title(request.getTitle()) + .build(); + + // 2. DTO의 블록들을 엔티티로 변환하여 Post에 추가 + request.getBlocks().forEach(blockDto -> { + PostBlock block = PostBlock.builder() + .sequence(blockDto.getSequence()) + .blockType(blockDto.getBlockType()) + .content(blockDto.getContent()) + .post(post) + .build(); + post.getBlocks().add(block); + }); + + // 3. DB 저장 (CascadeType.ALL 설정으로 블록도 같이 저장) + Post savedPost = postRepository.save(post); + + // 4. 저장된 엔티티를 응답 DTO로 변환해서 반환 + return PostResponseDTO.PostDetailResDTO.builder() + .postId(savedPost.getPostId()) + .title(savedPost.getTitle()) + .nickname("가천대가나디") // 우선 고정값으로 테스트 + .blocks(savedPost.getBlocks().stream() + .map(b -> PostResponseDTO.BlockResDTO.builder() + .blockId(b.getBlockId()) + .sequence(b.getSequence()) + .blockType(b.getBlockType()) + .content(b.getContent()) + .build()) + .collect(Collectors.toList())) + .createdAt(savedPost.getCreatedAt()) + .updatedAt(savedPost.getUpdatedAt()) + .build(); + } +} \ No newline at end of file diff --git a/LeeKunHee/src/main/resources/application.properties b/LeeKunHee/src/main/resources/application.properties index 0bb54b0..ff8634f 100644 --- a/LeeKunHee/src/main/resources/application.properties +++ b/LeeKunHee/src/main/resources/application.properties @@ -1 +1,15 @@ spring.application.name=assignment + +# H2 ?????? ?? +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +# H2 ?? ??? (??? DB ???) +spring.h2.console.enabled=true + +# JPA/Hibernate ?? +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true \ No newline at end of file From 6f02c39c03cdf9bbe1360487f08f6b1ce843d4ad Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 15:56:49 +0900 Subject: [PATCH 3/9] =?UTF-8?q?Feat:=20Global=20Exception=20handling=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 1 - .../global/exception/ExceptionResponse.java | 23 ++++++++ .../exception/GlobalExceptionHandler.java | 53 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/global/exception/ExceptionResponse.java create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java index 0d50f23..93d1de6 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -18,7 +18,6 @@ public class PostController { public PostResponseDTO.PostDetailResDTO createPost( @Valid @RequestBody PostRequestDTO.CreatePostDTO request ) { - // 우선 ApiResponse 없이 DTO만 반환해서 잘 나오는지 확인해봅시다! return postService.createPost(request); } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/global/exception/ExceptionResponse.java b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/ExceptionResponse.java new file mode 100644 index 0000000..638ca36 --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/ExceptionResponse.java @@ -0,0 +1,23 @@ +package com.leets.assignment.global.exception; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ExceptionResponse { + private final boolean isSuccess; + private final String code; + private final String message; + private final Object result; + + // 에러 발생 시 응답을 생성하는 정적 메서드 + public static ExceptionResponse of(String code, String message) { + return ExceptionResponse.builder() + .isSuccess(false) + .code(code) + .message(message) + .result(null) + .build(); + } +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..6101fc1 --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,53 @@ +package com.leets.assignment.global.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice // 프로젝트 전체의 컨트롤러 예외를 여기서 다 잡습니다. +public class GlobalExceptionHandler { + + // 우리가 따로 처리하지 않은 모든 일반적인 에러(RuntimeException)를 잡는 핸들러입니다. + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntimeException(RuntimeException e) { + + // 1. 우리가 만든 응답 객체에 에러 내용을 담습니다. + // 명세서의 공통 에러 코드(예: COMMON_500)를 사용하면 좋습니다. + ExceptionResponse response = ExceptionResponse.of("COMMON500_1", "예기치 않은 서버 에러가 발생했습니다."); + + // 2. HTTP 상태 코드 500과 함께 응답을 보냅니다. + return ResponseEntity + .internalServerError() + .body(response); + } + + // GlobalExceptionHandler.java 내부 + + // 상황 1: JSON 형식이 아예 잘못되었을 때 (HTTP 400) + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + ExceptionResponse response = ExceptionResponse.builder() + .isSuccess(false) + .code("COMMON400_1") // 명세서 코드! + .message("잘못된 요청입니다. JSON 형식을 확인해주세요.") + .result(null) + .build(); + + return ResponseEntity.badRequest().body(response); + } + + // 상황 2: @Valid 검증에 실패했을 때 (HTTP 400) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationException(MethodArgumentNotValidException e) { + ExceptionResponse response = ExceptionResponse.builder() + .isSuccess(false) + .code("COMMON400_2") + .message("잘못된 요청입니다. 입력값을 확인해주세요.") + .result(null) + .build(); + + return ResponseEntity.badRequest().body(response); + } +} From 0ac55d1b77ed81ce5423e88fb1f32fe21103d6f0 Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 16:38:26 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 6 +++ .../domain/post/dto/res/PostResponseDTO.java | 47 ++++++++++++++++--- .../post/exception/PostNotFoundException.java | 8 ++++ .../domain/post/service/PostService.java | 45 +++++++++--------- .../exception/GlobalExceptionHandler.java | 9 ++++ 5 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostNotFoundException.java diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java index 93d1de6..5459be3 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -5,6 +5,7 @@ import com.leets.assignment.domain.post.service.PostService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @@ -20,4 +21,9 @@ public PostResponseDTO.PostDetailResDTO createPost( ) { return postService.createPost(request); } + + @GetMapping("/{postId}") + public ResponseEntity getPost(@PathVariable Long postId) { + return ResponseEntity.ok(postService.getPost(postId)); + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java index db14ae4..af6cb89 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java @@ -1,37 +1,72 @@ package com.leets.assignment.domain.post.dto.res; import com.leets.assignment.domain.post.entity.BlockType; +import com.leets.assignment.domain.post.entity.Post; +import com.leets.assignment.domain.post.entity.PostBlock; import lombok.Builder; import java.time.LocalDateTime; import java.util.List; public class PostResponseDTO { - // 게시글 목록 조회 + // 1. 게시글 목록 조회용 (나중에 목록 기능 만들 때 사용) @Builder public record PostListResDTO ( Long postId, String title, String nickname, LocalDateTime createdAt - ){} + ){ + public static PostListResDTO from(Post post) { + return PostListResDTO.builder() + .postId(post.getPostId()) + .title(post.getTitle()) + .nickname(post.getUser() != null ? post.getUser().getNickname() : "익명") + .createdAt(post.getCreatedAt()) + .build(); + } + } - // 게시글 상세 조회 + // 2. 게시글 상세 조회용 (현재 구현 중인 기능) @Builder public record PostDetailResDTO ( Long postId, String title, String nickname, - List blocks, // 블록 리스트 + List blocks, LocalDateTime createdAt, LocalDateTime updatedAt - ){} + ){ + // 이 메서드가 있어야 Service에서 .from(post)를 호출할 수 있습니다! + public static PostDetailResDTO from(Post post) { + return PostDetailResDTO.builder() + .postId(post.getPostId()) + .title(post.getTitle()) + .nickname(post.getUser() != null ? post.getUser().getNickname() : "가천대가나디") + .createdAt(post.getCreatedAt()) + .updatedAt(post.getUpdatedAt()) + .blocks(post.getBlocks().stream() + .map(BlockResDTO::from) // 아래 BlockResDTO.from 호출 + .toList()) + .build(); + } + } + // 3. 블록 상세 정보 @Builder public record BlockResDTO ( Long blockId, Integer sequence, BlockType blockType, String content - ){} + ){ + public static BlockResDTO from(PostBlock block) { + return BlockResDTO.builder() + .blockId(block.getBlockId()) + .sequence(block.getSequence()) + .blockType(block.getBlockType()) + .content(block.getContent()) + .build(); + } + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostNotFoundException.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostNotFoundException.java new file mode 100644 index 0000000..cdf973a --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostNotFoundException.java @@ -0,0 +1,8 @@ +package com.leets.assignment.domain.post.exception; + +// RuntimeException을 상속받아야 서비스에서 쉽게 던질 수 있습니다. +public class PostNotFoundException extends RuntimeException { + public PostNotFoundException() { + super("해당 게시글을 찾을 수 없습니다."); + } +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java index d6b4b2c..3283bce 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java @@ -4,28 +4,30 @@ import com.leets.assignment.domain.post.dto.res.PostResponseDTO; import com.leets.assignment.domain.post.entity.Post; import com.leets.assignment.domain.post.entity.PostBlock; +import com.leets.assignment.domain.post.exception.PostNotFoundException; import com.leets.assignment.domain.post.repository.PostRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional(readOnly = true) // 기본적으로 읽기 전용으로 설정 (성능 최적화) public class PostService { private final PostRepository postRepository; - @Transactional + /** + * 게시글 생성 + */ + @Transactional // 쓰기 작업이므로 readOnly = false (기본값) 적용 public PostResponseDTO.PostDetailResDTO createPost(PostRequestDTO.CreatePostDTO request) { // 1. Post 엔티티 생성 Post post = Post.builder() .title(request.getTitle()) .build(); - // 2. DTO의 블록들을 엔티티로 변환하여 Post에 추가 + // 2. 블록 추가 로직 request.getBlocks().forEach(blockDto -> { PostBlock block = PostBlock.builder() .sequence(blockDto.getSequence()) @@ -36,24 +38,23 @@ public PostResponseDTO.PostDetailResDTO createPost(PostRequestDTO.CreatePostDTO post.getBlocks().add(block); }); - // 3. DB 저장 (CascadeType.ALL 설정으로 블록도 같이 저장) + // 3. DB 저장 Post savedPost = postRepository.save(post); - // 4. 저장된 엔티티를 응답 DTO로 변환해서 반환 - return PostResponseDTO.PostDetailResDTO.builder() - .postId(savedPost.getPostId()) - .title(savedPost.getTitle()) - .nickname("가천대가나디") // 우선 고정값으로 테스트 - .blocks(savedPost.getBlocks().stream() - .map(b -> PostResponseDTO.BlockResDTO.builder() - .blockId(b.getBlockId()) - .sequence(b.getSequence()) - .blockType(b.getBlockType()) - .content(b.getContent()) - .build()) - .collect(Collectors.toList())) - .createdAt(savedPost.getCreatedAt()) - .updatedAt(savedPost.getUpdatedAt()) - .build(); + // 4. 저장된 엔티티를 DTO로 변환하여 반환 (미리 만든 from 메서드 활용) + return PostResponseDTO.PostDetailResDTO.from(savedPost); + } + + /** + * 게시글 상세 조회 + */ + public PostResponseDTO.PostDetailResDTO getPost(Long postId) { + // 1. DB에서 ID로 조회 + // 2. 데이터가 없으면 우리가 만든 PostNotFoundException 예외 발생! (-> 404 응답) + Post post = postRepository.findById(postId) + .orElseThrow(PostNotFoundException::new); + + // 3. 찾은 엔티티를 DTO로 변환하여 반환 + return PostResponseDTO.PostDetailResDTO.from(post); } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java index 6101fc1..dd05107 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package com.leets.assignment.global.exception; +import com.leets.assignment.domain.post.exception.PostNotFoundException; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -50,4 +51,12 @@ public ResponseEntity handleValidationException(MethodArgumen return ResponseEntity.badRequest().body(response); } + + // 기존 코드 아래에 추가 + @ExceptionHandler(PostNotFoundException.class) + public ResponseEntity handlePostNotFoundException(PostNotFoundException e) { + return ResponseEntity + .status(org.springframework.http.HttpStatus.NOT_FOUND) // 404 에러 + .body(ExceptionResponse.of("POST404_1", e.getMessage())); // 명세서 코드! + } } From ede2448d6f06eb0b8563da334f4966cf0945cabe Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 16:58:00 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 10 ++++++++++ .../domain/post/service/PostService.java | 20 +++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java index 5459be3..515cf12 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -8,6 +8,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/api/posts") @RequiredArgsConstructor @@ -15,6 +17,7 @@ public class PostController { private final PostService postService; + // 게시글 작성 @PostMapping public PostResponseDTO.PostDetailResDTO createPost( @Valid @RequestBody PostRequestDTO.CreatePostDTO request @@ -22,8 +25,15 @@ public PostResponseDTO.PostDetailResDTO createPost( return postService.createPost(request); } + // 게시글 상세 조회 (ID 필요) @GetMapping("/{postId}") public ResponseEntity getPost(@PathVariable Long postId) { return ResponseEntity.ok(postService.getPost(postId)); } + + // 게시글 전체 목록 조회 (추가됨) + @GetMapping + public ResponseEntity> getPostList() { + return ResponseEntity.ok(postService.getPostList()); + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java index 3283bce..8cb3c18 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java @@ -10,6 +10,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) // 기본적으로 읽기 전용으로 설정 (성능 최적화) @@ -17,9 +20,7 @@ public class PostService { private final PostRepository postRepository; - /** - * 게시글 생성 - */ + // 게시글 생성 @Transactional // 쓰기 작업이므로 readOnly = false (기본값) 적용 public PostResponseDTO.PostDetailResDTO createPost(PostRequestDTO.CreatePostDTO request) { // 1. Post 엔티티 생성 @@ -45,9 +46,7 @@ public PostResponseDTO.PostDetailResDTO createPost(PostRequestDTO.CreatePostDTO return PostResponseDTO.PostDetailResDTO.from(savedPost); } - /** - * 게시글 상세 조회 - */ + // 게시글 상세 조회 public PostResponseDTO.PostDetailResDTO getPost(Long postId) { // 1. DB에서 ID로 조회 // 2. 데이터가 없으면 우리가 만든 PostNotFoundException 예외 발생! (-> 404 응답) @@ -57,4 +56,13 @@ public PostResponseDTO.PostDetailResDTO getPost(Long postId) { // 3. 찾은 엔티티를 DTO로 변환하여 반환 return PostResponseDTO.PostDetailResDTO.from(post); } + + // 게시글 전체 목록 조회 (추가됨) + public List getPostList() { + // DB의 모든 글을 가져와서 ListResDTO로 변환 + return postRepository.findAll().stream() + .map(PostResponseDTO.PostListResDTO::from) + .collect(Collectors.toList()); + } + } \ No newline at end of file From 450eeda2658b7a9285606d9cd0355c70812bd77c Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 17:46:00 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EC=86=8C=ED=94=84=ED=8A=B8=20?= =?UTF-8?q?=EB=94=9C=EB=A6=AC=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=20=EB=AA=85=EC=84=B8=EC=84=9C=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 31 +++++++++++++------ .../domain/post/dto/req/PostRequestDTO.java | 2 ++ .../assignment/domain/post/entity/Post.java | 6 ++++ .../domain/post/service/PostService.java | 16 +++++++++- .../global/baseEntity/BaseEntity.java | 2 +- .../assignment/global/common/ApiResponse.java | 25 +++++++++++++++ 6 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java index 515cf12..e51d531 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -3,6 +3,8 @@ import com.leets.assignment.domain.post.dto.req.PostRequestDTO; import com.leets.assignment.domain.post.dto.res.PostResponseDTO; import com.leets.assignment.domain.post.service.PostService; +import com.leets.assignment.global.common.ApiResponse; + import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -17,23 +19,34 @@ public class PostController { private final PostService postService; - // 게시글 작성 + // 1. 게시글 작성 @PostMapping - public PostResponseDTO.PostDetailResDTO createPost( + public ApiResponse createPost( @Valid @RequestBody PostRequestDTO.CreatePostDTO request ) { - return postService.createPost(request); + PostResponseDTO.PostDetailResDTO result = postService.createPost(request); + return ApiResponse.onSuccess("POST200_1", "게시글 작성에 성공했습니다.", result); } - // 게시글 상세 조회 (ID 필요) + // 2. 게시글 상세 조회 @GetMapping("/{postId}") - public ResponseEntity getPost(@PathVariable Long postId) { - return ResponseEntity.ok(postService.getPost(postId)); + public ApiResponse getPost(@PathVariable Long postId) { + PostResponseDTO.PostDetailResDTO result = postService.getPost(postId); + return ApiResponse.onSuccess("POST200_2", "게시글 상세 조회에 성공했습니다.", result); } - // 게시글 전체 목록 조회 (추가됨) + // 3. 게시글 전체 목록 조회 @GetMapping - public ResponseEntity> getPostList() { - return ResponseEntity.ok(postService.getPostList()); + public ApiResponse> getPostList() { + List result = postService.getPostList(); + return ApiResponse.onSuccess("POST200_3", "게시글 목록 조회에 성공했습니다.", result); + } + + // 4. 게시글 삭제 + @DeleteMapping("/{postId}") + public ApiResponse deletePost(@PathVariable Long postId) { + postService.deletePost(postId); + // 삭제는 반환할 데이터가 없으므로 result에 null을 넣습니다. + return ApiResponse.onSuccess("POST200_4", "게시글 삭제에 성공했습니다.", null); } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java index dc38026..494e9ca 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java @@ -39,4 +39,6 @@ public static class BlockDTO { @NotBlank(message = "내용을 입력해주세요.") // 각 블록의 내용이 비었을 때 private String content; } + + } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java index 3d0cd3d..652baec 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java @@ -4,6 +4,8 @@ import com.leets.assignment.domain.user.entity.User; import jakarta.persistence.*; import lombok.*; + +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -33,4 +35,8 @@ private Post(String title, User user) { this.title = title; this.user = user; } + + public void softDelete() { + this.deletedAt = LocalDateTime.now(); + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java index 8cb3c18..07cf620 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.stream.Collectors; +import java.time.LocalDateTime; @Service @RequiredArgsConstructor @@ -51,18 +52,31 @@ public PostResponseDTO.PostDetailResDTO getPost(Long postId) { // 1. DB에서 ID로 조회 // 2. 데이터가 없으면 우리가 만든 PostNotFoundException 예외 발생! (-> 404 응답) Post post = postRepository.findById(postId) + .filter(p -> p.getDeletedAt() == null) // 삭제 안 된 것만 필터링 .orElseThrow(PostNotFoundException::new); // 3. 찾은 엔티티를 DTO로 변환하여 반환 return PostResponseDTO.PostDetailResDTO.from(post); } - // 게시글 전체 목록 조회 (추가됨) + // 게시글 전체 목록 조회 public List getPostList() { // DB의 모든 글을 가져와서 ListResDTO로 변환 return postRepository.findAll().stream() + .filter(post -> post.getDeletedAt() == null) // 삭제된 글 제외 .map(PostResponseDTO.PostListResDTO::from) .collect(Collectors.toList()); } + // 소프트 딜리트 로직 + @Transactional + public void deletePost(Long postId) { + // 1. 존재하는 글인지 확인 + Post post = postRepository.findById(postId) + .filter(p -> p.getDeletedAt() == null) // 이미 삭제된 건 없는 걸로 침 + .orElseThrow(PostNotFoundException::new); + + // 2. setDeletedAt 대신 softDelete() 호출! + post.softDelete(); + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/global/baseEntity/BaseEntity.java b/LeeKunHee/src/main/java/com/leets/assignment/global/baseEntity/BaseEntity.java index 9f40ca0..980ebda 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/global/baseEntity/BaseEntity.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/baseEntity/BaseEntity.java @@ -26,5 +26,5 @@ public abstract class BaseEntity { private LocalDateTime updatedAt; @Column(name = "deleted_at") - private LocalDateTime deletedAt; + protected LocalDateTime deletedAt; } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java b/LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java new file mode 100644 index 0000000..3c285bc --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java @@ -0,0 +1,25 @@ +package com.leets.assignment.global.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class ApiResponse { + private final Boolean isSuccess; + private final String code; + private final String message; + private final T result; + + // 성공 응답을 생성하는 정적 팩토리 메서드 + public static ApiResponse onSuccess(String code, String message, T result) { + return ApiResponse.builder() + .isSuccess(true) + .code(code) + .message(message) + .result(result) + .build(); + } +} \ No newline at end of file From f239d02565d6911da4d83450ffb7e63253cca7d9 Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 20:12:18 +0900 Subject: [PATCH 7/9] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 10 ++ .../domain/post/dto/req/PostRequestDTO.java | 14 +++ .../assignment/domain/post/entity/Post.java | 4 + .../exception/PostForbiddenException.java | 7 ++ .../domain/post/service/PostService.java | 25 ++++ .../assignment/global/common/ApiResponse.java | 1 + .../exception/GlobalExceptionHandler.java | 117 ++++++++++++------ 7 files changed, 137 insertions(+), 41 deletions(-) create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostForbiddenException.java diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java index e51d531..5c15299 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -49,4 +49,14 @@ public ApiResponse deletePost(@PathVariable Long postId) { // 삭제는 반환할 데이터가 없으므로 result에 null을 넣습니다. return ApiResponse.onSuccess("POST200_4", "게시글 삭제에 성공했습니다.", null); } + + // 5. 게시글 수정 + @PatchMapping("/{postId}") + public ApiResponse updatePost( + @PathVariable Long postId, + @Valid @RequestBody PostRequestDTO.UpdatePostDTO request + ) { + PostResponseDTO.PostDetailResDTO result = postService.updatePost(postId, request); + return ApiResponse.onSuccess("POST200_3", "게시글이 수정되었습니다.", result); + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java index 494e9ca..97fa2c9 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java @@ -12,6 +12,7 @@ public class PostRequestDTO { + // 게시글 생성용 DTO @Getter @NoArgsConstructor public static class CreatePostDTO { @@ -27,6 +28,7 @@ public static class CreatePostDTO { private List blocks; } + // 공통 블록 DTO (생성/수정 모두 사용) @Getter @NoArgsConstructor public static class BlockDTO { @@ -40,5 +42,17 @@ public static class BlockDTO { private String content; } + // 게시글 수정용 DTO + @Getter + @NoArgsConstructor + public static class UpdatePostDTO { + @NotBlank(message = "제목을 입력해주세요.") + @Size(max = 255, message = "제목은 최대 255자까지 가능합니다.") + private String title; + + @NotEmpty(message = "내용을 입력해주세요.") // 리스트가 비어있으면 거부 + @Valid + private List blocks; + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java index 652baec..822c93d 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/entity/Post.java @@ -39,4 +39,8 @@ private Post(String title, User user) { public void softDelete() { this.deletedAt = LocalDateTime.now(); } + + public void update(String title) { + this.title = title; + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostForbiddenException.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostForbiddenException.java new file mode 100644 index 0000000..2094343 --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/exception/PostForbiddenException.java @@ -0,0 +1,7 @@ +package com.leets.assignment.domain.post.exception; + +public class PostForbiddenException extends RuntimeException { + public PostForbiddenException() { + super("수정 권한이 없습니다."); + } +} \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java index 07cf620..b84893a 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java @@ -79,4 +79,29 @@ public void deletePost(Long postId) { // 2. setDeletedAt 대신 softDelete() 호출! post.softDelete(); } + + @Transactional + public PostResponseDTO.PostDetailResDTO updatePost(Long postId, PostRequestDTO.UpdatePostDTO request) { + // 1. 게시글 존재 및 삭제 여부 확인 (없으면 POST404_1 발생) + Post post = postRepository.findById(postId) + .filter(p -> p.getDeletedAt() == null) + .orElseThrow(PostNotFoundException::new); + + // 2. 제목 수정 (Dirty Checking) + post.update(request.getTitle()); + + // 3. 블록 수정 (기존 블록 비우고 새로 추가) + post.getBlocks().clear(); + request.getBlocks().forEach(blockDto -> { + PostBlock block = PostBlock.builder() + .sequence(blockDto.getSequence()) + .blockType(blockDto.getBlockType()) + .content(blockDto.getContent()) + .post(post) + .build(); + post.getBlocks().add(block); + }); + + return PostResponseDTO.PostDetailResDTO.from(post); + } } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java b/LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java index 3c285bc..b18f885 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/common/ApiResponse.java @@ -22,4 +22,5 @@ public static ApiResponse onSuccess(String code, String message, T result .result(result) .build(); } + } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java index dd05107..3a8b045 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/global/exception/GlobalExceptionHandler.java @@ -1,62 +1,97 @@ package com.leets.assignment.global.exception; import com.leets.assignment.domain.post.exception.PostNotFoundException; +import com.leets.assignment.global.common.ApiResponse; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -@RestControllerAdvice // 프로젝트 전체의 컨트롤러 예외를 여기서 다 잡습니다. +@RestControllerAdvice public class GlobalExceptionHandler { - // 우리가 따로 처리하지 않은 모든 일반적인 에러(RuntimeException)를 잡는 핸들러입니다. - @ExceptionHandler(RuntimeException.class) - public ResponseEntity handleRuntimeException(RuntimeException e) { + /** + * [POST400_1, POST400_2] @Valid 필드 검증 실패 처리 + * 명세서의 특정 에러 코드와 메시지 형식에 맞춥니다. + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationException(MethodArgumentNotValidException e) { + // 발생한 에러 중 첫 번째 필드 에러를 가져옴 + FieldError fieldError = e.getBindingResult().getFieldError(); - // 1. 우리가 만든 응답 객체에 에러 내용을 담습니다. - // 명세서의 공통 에러 코드(예: COMMON_500)를 사용하면 좋습니다. - ExceptionResponse response = ExceptionResponse.of("COMMON500_1", "예기치 않은 서버 에러가 발생했습니다."); + String errorCode = "COMMON400"; // 기본 에러 코드 + String errorMessage = "입력값이 유효하지 않습니다."; - // 2. HTTP 상태 코드 500과 함께 응답을 보냅니다. - return ResponseEntity - .internalServerError() - .body(response); - } + if (fieldError != null) { + String constraint = fieldError.getCode(); // NotBlank, Size 등 + String field = fieldError.getField(); // title, blocks 등 - // GlobalExceptionHandler.java 내부 + // 1. 제목이나 내용(블록)이 비었을 때 (POST400_1) + if ("NotBlank".equals(constraint) || "NotEmpty".equals(constraint)) { + errorCode = "POST400_1"; + errorMessage = "제목과 내용을 입력해주세요."; + } + // 2. 제목 글자 수 초과 시 (POST400_2) + else if ("Size".equals(constraint) && "title".equals(field)) { + errorCode = "POST400_2"; + errorMessage = "제목은 최대 255자까지 가능합니다."; + } + } - // 상황 1: JSON 형식이 아예 잘못되었을 때 (HTTP 400) - @ExceptionHandler(HttpMessageNotReadableException.class) - public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { - ExceptionResponse response = ExceptionResponse.builder() - .isSuccess(false) - .code("COMMON400_1") // 명세서 코드! - .message("잘못된 요청입니다. JSON 형식을 확인해주세요.") - .result(null) - .build(); - - return ResponseEntity.badRequest().body(response); + return ResponseEntity.badRequest().body( + ApiResponse.builder() + .isSuccess(false) + .code(errorCode) + .message(errorMessage) + .result(null) + .build() + ); } - // 상황 2: @Valid 검증에 실패했을 때 (HTTP 400) - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleValidationException(MethodArgumentNotValidException e) { - ExceptionResponse response = ExceptionResponse.builder() - .isSuccess(false) - .code("COMMON400_2") - .message("잘못된 요청입니다. 입력값을 확인해주세요.") - .result(null) - .build(); - - return ResponseEntity.badRequest().body(response); + /** + * [COMMON400_1] JSON 형식 자체 오류 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + return ResponseEntity.badRequest().body( + ApiResponse.builder() + .isSuccess(false) + .code("COMMON400_1") + .message("잘못된 요청입니다. JSON 형식을 확인해주세요.") + .result(null) + .build() + ); } - // 기존 코드 아래에 추가 + /** + * [POST404_1] 게시글을 찾을 수 없음 (상세조회, 수정, 삭제 공통) + */ @ExceptionHandler(PostNotFoundException.class) - public ResponseEntity handlePostNotFoundException(PostNotFoundException e) { - return ResponseEntity - .status(org.springframework.http.HttpStatus.NOT_FOUND) // 404 에러 - .body(ExceptionResponse.of("POST404_1", e.getMessage())); // 명세서 코드! + public ResponseEntity> handlePostNotFoundException(PostNotFoundException e) { + return ResponseEntity.status(404).body( + ApiResponse.builder() + .isSuccess(false) + .code("POST404_1") + .message("해당 게시글이 존재하지 않습니다.") + .result(null) + .build() + ); + } + + /** + * [COMMON500_1] 기타 예기치 못한 서버 에러 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity> handleAllException(Exception e) { + return ResponseEntity.internalServerError().body( + ApiResponse.builder() + .isSuccess(false) + .code("COMMON500_1") + .message("예기치 않은 서버 에러가 발생했습니다.") + .result(null) + .build() + ); } -} +} \ No newline at end of file From a70c29cde4ddd646c7d066f873a815102c1da047 Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 22:54:16 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Refactor:=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EB=85=B8?= =?UTF-8?q?=EC=B6=9C=20=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 37 ++++++++++--------- .../domain/post/dto/res/PostResponseDTO.java | 4 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java index 5c15299..e935ac6 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -7,6 +7,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -21,36 +22,29 @@ public class PostController { // 1. 게시글 작성 @PostMapping + @ResponseStatus(HttpStatus.CREATED) public ApiResponse createPost( @Valid @RequestBody PostRequestDTO.CreatePostDTO request ) { PostResponseDTO.PostDetailResDTO result = postService.createPost(request); - return ApiResponse.onSuccess("POST200_1", "게시글 작성에 성공했습니다.", result); + return ApiResponse.onSuccess("POST201_1", "게시글 작성에 성공했습니다.", result); } - // 2. 게시글 상세 조회 - @GetMapping("/{postId}") - public ApiResponse getPost(@PathVariable Long postId) { - PostResponseDTO.PostDetailResDTO result = postService.getPost(postId); - return ApiResponse.onSuccess("POST200_2", "게시글 상세 조회에 성공했습니다.", result); - } - - // 3. 게시글 전체 목록 조회 + // 2. 게시글 전체 목록 조회 @GetMapping public ApiResponse> getPostList() { List result = postService.getPostList(); - return ApiResponse.onSuccess("POST200_3", "게시글 목록 조회에 성공했습니다.", result); + return ApiResponse.onSuccess("POST200_1", "게시글 목록 조회에 성공했습니다.", result); } - // 4. 게시글 삭제 - @DeleteMapping("/{postId}") - public ApiResponse deletePost(@PathVariable Long postId) { - postService.deletePost(postId); - // 삭제는 반환할 데이터가 없으므로 result에 null을 넣습니다. - return ApiResponse.onSuccess("POST200_4", "게시글 삭제에 성공했습니다.", null); + // 3. 게시글 상세 조회 + @GetMapping("/{postId}") + public ApiResponse getPost(@PathVariable Long postId) { + PostResponseDTO.PostDetailResDTO result = postService.getPost(postId); + return ApiResponse.onSuccess("POST200_2", "게시글 상세 조회에 성공했습니다.", result); } - // 5. 게시글 수정 + // 4. 게시글 수정 @PatchMapping("/{postId}") public ApiResponse updatePost( @PathVariable Long postId, @@ -59,4 +53,13 @@ public ApiResponse updatePost( PostResponseDTO.PostDetailResDTO result = postService.updatePost(postId, request); return ApiResponse.onSuccess("POST200_3", "게시글이 수정되었습니다.", result); } + + // 5. 게시글 삭제 + @DeleteMapping("/{postId}") + public ApiResponse deletePost(@PathVariable Long postId) { + postService.deletePost(postId); + // 삭제는 반환할 데이터가 없으므로 result에 null을 넣습니다. + return ApiResponse.onSuccess("POST200_4", "게시글 삭제에 성공했습니다.", null); + } + } \ No newline at end of file diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java index af6cb89..56ed894 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/res/PostResponseDTO.java @@ -21,7 +21,7 @@ public static PostListResDTO from(Post post) { return PostListResDTO.builder() .postId(post.getPostId()) .title(post.getTitle()) - .nickname(post.getUser() != null ? post.getUser().getNickname() : "익명") + .nickname(post.getUser() != null ? post.getUser().getNickname() : "알 수 없음") .createdAt(post.getCreatedAt()) .build(); } @@ -42,7 +42,7 @@ public static PostDetailResDTO from(Post post) { return PostDetailResDTO.builder() .postId(post.getPostId()) .title(post.getTitle()) - .nickname(post.getUser() != null ? post.getUser().getNickname() : "가천대가나디") + .nickname(post.getUser() != null ? post.getUser().getNickname() : "알 수 없음") .createdAt(post.getCreatedAt()) .updatedAt(post.getUpdatedAt()) .blocks(post.getBlocks().stream() From db81a0f554a2e8fa9613df293daba2a0e3a6acb6 Mon Sep 17 00:00:00 2001 From: Kunhee Lee Date: Tue, 7 Apr 2026 23:57:51 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20UserRepository=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20=EB=B0=8F=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 7 +++-- .../domain/post/dto/req/PostRequestDTO.java | 3 ++ .../domain/post/service/PostService.java | 29 ++++++++++++++++--- .../user/repository/UserRepository.java | 10 +++++++ .../src/main/resources/application.properties | 9 +++++- LeeKunHee/src/main/resources/data.sql | 3 ++ 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 LeeKunHee/src/main/java/com/leets/assignment/domain/user/repository/UserRepository.java create mode 100644 LeeKunHee/src/main/resources/data.sql diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java index e935ac6..90f59f9 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/controller/PostController.java @@ -56,8 +56,11 @@ public ApiResponse updatePost( // 5. 게시글 삭제 @DeleteMapping("/{postId}") - public ApiResponse deletePost(@PathVariable Long postId) { - postService.deletePost(postId); + public ApiResponse deletePost( + @PathVariable Long postId, + @RequestParam Long userId // 쿼리 파라미터(?userId=1)로 작성자 ID를 받음 + ) { + postService.deletePost(postId, userId); // 삭제는 반환할 데이터가 없으므로 result에 null을 넣습니다. return ApiResponse.onSuccess("POST200_4", "게시글 삭제에 성공했습니다.", null); } diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java index 97fa2c9..058b9d9 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/dto/req/PostRequestDTO.java @@ -46,6 +46,9 @@ public static class BlockDTO { @Getter @NoArgsConstructor public static class UpdatePostDTO { + @NotNull(message = "수정자 ID는 필수입니다.") // 권한 확인을 위해 + private Long userId; + @NotBlank(message = "제목을 입력해주세요.") @Size(max = 255, message = "제목은 최대 255자까지 가능합니다.") private String title; diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java index b84893a..5b96604 100644 --- a/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/post/service/PostService.java @@ -4,8 +4,11 @@ import com.leets.assignment.domain.post.dto.res.PostResponseDTO; import com.leets.assignment.domain.post.entity.Post; import com.leets.assignment.domain.post.entity.PostBlock; +import com.leets.assignment.domain.post.exception.PostForbiddenException; import com.leets.assignment.domain.post.exception.PostNotFoundException; import com.leets.assignment.domain.post.repository.PostRepository; +import com.leets.assignment.domain.user.entity.User; +import com.leets.assignment.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,13 +23,20 @@ public class PostService { private final PostRepository postRepository; + private final UserRepository userRepository; // 게시글 생성 @Transactional // 쓰기 작업이므로 readOnly = false (기본값) 적용 public PostResponseDTO.PostDetailResDTO createPost(PostRequestDTO.CreatePostDTO request) { + + // 0. 실제 DB에서 유저 조회 + User user = userRepository.findById(request.getUserId()) + .orElseThrow(() -> new RuntimeException("존재하지 않는 유저입니다.")); + // 1. Post 엔티티 생성 Post post = Post.builder() .title(request.getTitle()) + .user(user) .build(); // 2. 블록 추가 로직 @@ -70,16 +80,22 @@ public List getPostList() { // 소프트 딜리트 로직 @Transactional - public void deletePost(Long postId) { + public void deletePost(Long postId, Long userId) { // 1. 존재하는 글인지 확인 Post post = postRepository.findById(postId) .filter(p -> p.getDeletedAt() == null) // 이미 삭제된 건 없는 걸로 침 .orElseThrow(PostNotFoundException::new); - // 2. setDeletedAt 대신 softDelete() 호출! + // 2. 삭제 권한 확인 + if (!post.getUser().getUserId().equals(userId)) { + throw new PostForbiddenException(); + } + + // 3. softDelete() 호출! post.softDelete(); } + // 게시글 수정 @Transactional public PostResponseDTO.PostDetailResDTO updatePost(Long postId, PostRequestDTO.UpdatePostDTO request) { // 1. 게시글 존재 및 삭제 여부 확인 (없으면 POST404_1 발생) @@ -87,10 +103,15 @@ public PostResponseDTO.PostDetailResDTO updatePost(Long postId, PostRequestDTO.U .filter(p -> p.getDeletedAt() == null) .orElseThrow(PostNotFoundException::new); - // 2. 제목 수정 (Dirty Checking) + // 2. 작성자 ID 대조 (정석 로직) + if (!post.getUser().getUserId().equals(request.getUserId())) { + throw new PostForbiddenException(); + } + + // 3. 제목 수정 (Dirty Checking) post.update(request.getTitle()); - // 3. 블록 수정 (기존 블록 비우고 새로 추가) + // 4. 블록 수정 (기존 블록 비우고 새로 추가) post.getBlocks().clear(); request.getBlocks().forEach(blockDto -> { PostBlock block = PostBlock.builder() diff --git a/LeeKunHee/src/main/java/com/leets/assignment/domain/user/repository/UserRepository.java b/LeeKunHee/src/main/java/com/leets/assignment/domain/user/repository/UserRepository.java new file mode 100644 index 0000000..10676d6 --- /dev/null +++ b/LeeKunHee/src/main/java/com/leets/assignment/domain/user/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.leets.assignment.domain.user.repository; + +import com.leets.assignment.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { + // 기본 findById는 JpaRepository가 제공합니다. +} \ No newline at end of file diff --git a/LeeKunHee/src/main/resources/application.properties b/LeeKunHee/src/main/resources/application.properties index ff8634f..76aaffa 100644 --- a/LeeKunHee/src/main/resources/application.properties +++ b/LeeKunHee/src/main/resources/application.properties @@ -12,4 +12,11 @@ spring.h2.console.enabled=true # JPA/Hibernate ?? spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true \ No newline at end of file +spring.jpa.show-sql=true + + +# data.sql ??? ?? ????? ?? +spring.sql.init.mode=always + +# Hibernate? ??? ??(ddl-auto)? ?? ?? data.sql? ????? ?? +spring.jpa.defer-datasource-initialization=true \ No newline at end of file diff --git a/LeeKunHee/src/main/resources/data.sql b/LeeKunHee/src/main/resources/data.sql new file mode 100644 index 0000000..8599206 --- /dev/null +++ b/LeeKunHee/src/main/resources/data.sql @@ -0,0 +1,3 @@ +-- 테스트용 유저 데이터 (서버 실행 시 자동 삽입) +INSERT INTO users (user_id, email, name, nickname, password, created_at, updated_at) +VALUES (1, 'test@test.com', '강아지', '가나디', '1234', NOW(), NOW()); \ No newline at end of file