From 0134fcee781f10e3c293cf9f9c27bf4b1e776b97 Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:36:38 +0900 Subject: [PATCH 1/7] =?UTF-8?q?Feat:=20=EC=9E=91=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20DTO=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../back/domain/board/common/dto/AuthorResponse.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java b/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java index 664d8fea..d177b025 100644 --- a/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java +++ b/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java @@ -6,12 +6,14 @@ /** * 작성자 응답 DTO * - * @param id 작성자 ID - * @param nickname 작성자 닉네임 + * @param id 작성자 ID + * @param nickname 작성자 닉네임 + * @param profileImageUrl 작성자 프로필 이미지 */ public record AuthorResponse( Long id, - String nickname + String nickname, + String profileImageUrl ) { @QueryProjection public AuthorResponse {} @@ -19,7 +21,8 @@ public record AuthorResponse( public static AuthorResponse from(User user) { return new AuthorResponse( user.getId(), - user.getUserProfile().getNickname() + user.getUserProfile().getNickname(), + user.getProfileImageUrl() ); } } \ No newline at end of file From 2d737e73ed900c65b96de4a6237723e78f53295f Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Fri, 10 Oct 2025 12:31:33 +0900 Subject: [PATCH 2/7] =?UTF-8?q?Feat:=20=EB=8C=93=EA=B8=80=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=97=AC=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 4 +- .../controller/CommentControllerDocs.java | 15 +++++-- .../comment/dto/CommentListResponse.java | 5 +++ .../repository/CommentLikeRepository.java | 6 ++- .../CommentLikeRepositoryCustom.java | 8 ++++ .../repository/CommentLikeRepositoryImpl.java | 31 ++++++++++++++ .../repository/CommentRepositoryImpl.java | 3 +- .../board/comment/service/CommentService.java | 42 +++++++++++++++++++ .../post/repository/PostRepositoryImpl.java | 2 +- 9 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java create mode 100644 src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentController.java b/src/main/java/com/back/domain/board/comment/controller/CommentController.java index aa873c85..a4091b2e 100644 --- a/src/main/java/com/back/domain/board/comment/controller/CommentController.java +++ b/src/main/java/com/back/domain/board/comment/controller/CommentController.java @@ -44,9 +44,11 @@ public ResponseEntity> createComment( @GetMapping public ResponseEntity>> getComments( @PathVariable Long postId, + @AuthenticationPrincipal CustomUserDetails user, @PageableDefault(sort = "createdAt", direction = Sort.Direction.ASC) Pageable pageable ) { - PageResponse response = commentService.getComments(postId, pageable); + Long userId = (user != null) ? user.getUserId() : null; + PageResponse response = commentService.getComments(postId, userId, pageable); return ResponseEntity .status(HttpStatus.OK) .body(RsData.success( diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java index 4f50bbf9..d62b4728 100644 --- a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java +++ b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java @@ -41,7 +41,8 @@ public interface CommentControllerDocs { "postId": 101, "author": { "id": 5, - "nickname": "홍길동" + "nickname": "홍길동", + "profileImageUrl": null }, "content": "좋은 글 감사합니다!", "createdAt": "2025-09-22T11:30:00", @@ -170,10 +171,12 @@ ResponseEntity> createComment( "parentId": null, "author": { "id": 5, - "nickname": "홍길동" + "nickname": "홍길동", + "profileImageUrl": null }, "content": "부모 댓글", "likeCount": 2, + "likedByMe": true, "createdAt": "2025-09-22T11:30:00", "updatedAt": "2025-09-22T11:30:00", "children": [ @@ -183,10 +186,12 @@ ResponseEntity> createComment( "parentId": 1, "author": { "id": 5, - "nickname": "홍길동" + "nickname": "홍길동", + "profileImageUrl": null }, "content": "자식 댓글", "likeCount": 0, + "likedByMe": false, "createdAt": "2025-09-22T11:35:00", "updatedAt": "2025-09-22T11:35:00", "children": [] @@ -252,6 +257,7 @@ ResponseEntity> createComment( }) ResponseEntity>> getComments( @PathVariable Long postId, + @AuthenticationPrincipal CustomUserDetails user, Pageable pageable ); @@ -275,7 +281,8 @@ ResponseEntity>> getComments( "postId": 101, "author": { "id": 5, - "nickname": "홍길동" + "nickname": "홍길동", + "profileImageUrl": null }, "content": "수정된 댓글 내용입니다.", "createdAt": "2025-09-22T11:30:00", diff --git a/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java b/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java index 90f60554..7c4a2c85 100644 --- a/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java +++ b/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java @@ -22,6 +22,9 @@ public class CommentListResponse { @Setter private long likeCount; + @Setter + private Boolean likedByMe; + private final LocalDateTime createdAt; private final LocalDateTime updatedAt; @@ -35,6 +38,7 @@ public CommentListResponse(Long commentId, AuthorResponse author, String content, long likeCount, + Boolean likedByMe, LocalDateTime createdAt, LocalDateTime updatedAt, List children) { @@ -43,6 +47,7 @@ public CommentListResponse(Long commentId, this.parentId = parentId; this.author = author; this.content = content; + this.likedByMe = likedByMe; this.likeCount = likeCount; this.createdAt = createdAt; this.updatedAt = updatedAt; diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java index 47a3a996..df675a48 100644 --- a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java +++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java @@ -2,12 +2,16 @@ import com.back.domain.board.comment.entity.CommentLike; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Collection; +import java.util.List; import java.util.Optional; @Repository -public interface CommentLikeRepository extends JpaRepository { +public interface CommentLikeRepository extends JpaRepository, CommentLikeRepositoryCustom { boolean existsByUserIdAndCommentId(Long userId, Long commentId); Optional findByUserIdAndCommentId(Long userId, Long commentId); } diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java new file mode 100644 index 00000000..a2ec7cbb --- /dev/null +++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java @@ -0,0 +1,8 @@ +package com.back.domain.board.comment.repository; + +import java.util.Collection; +import java.util.List; + +public interface CommentLikeRepositoryCustom { + List findLikedCommentIdsIn(Long userId, Collection commentIds); +} diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java new file mode 100644 index 00000000..9e520555 --- /dev/null +++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java @@ -0,0 +1,31 @@ +package com.back.domain.board.comment.repository; + +import com.back.domain.board.comment.entity.QCommentLike; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.List; + + +@Repository +@RequiredArgsConstructor +public class CommentLikeRepositoryImpl implements CommentLikeRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List findLikedCommentIdsIn(Long userId, Collection commentIds) { + QCommentLike commentLike = QCommentLike.commentLike; + + return queryFactory + .select(commentLike.comment.id) + .from(commentLike) + .where( + commentLike.user.id.eq(userId), + commentLike.comment.id.in(commentIds) + ) + .fetch(); + } +} \ No newline at end of file diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java b/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java index 11ae4c52..29156bf5 100644 --- a/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java +++ b/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java @@ -112,9 +112,10 @@ private List fetchComments( comment.id, comment.post.id, comment.parent.id, - new QAuthorResponse(user.id, profile.nickname), + new QAuthorResponse(user.id, profile.nickname, profile.profileImageUrl), comment.content, Expressions.constant(0L), // likeCount는 별도 주입 + Expressions.constant(false), comment.createdAt, comment.updatedAt, Expressions.constant(Collections.emptyList()) // children은 별도 주입 diff --git a/src/main/java/com/back/domain/board/comment/service/CommentService.java b/src/main/java/com/back/domain/board/comment/service/CommentService.java index 64b7ab46..98e768f1 100644 --- a/src/main/java/com/back/domain/board/comment/service/CommentService.java +++ b/src/main/java/com/back/domain/board/comment/service/CommentService.java @@ -4,6 +4,8 @@ import com.back.domain.board.comment.dto.CommentRequest; import com.back.domain.board.comment.dto.CommentResponse; import com.back.domain.board.comment.dto.ReplyResponse; +import com.back.domain.board.comment.entity.CommentLike; +import com.back.domain.board.comment.repository.CommentLikeRepository; import com.back.domain.board.common.dto.PageResponse; import com.back.domain.board.comment.entity.Comment; import com.back.domain.board.post.entity.Post; @@ -22,11 +24,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + @Service @RequiredArgsConstructor @Transactional public class CommentService { private final CommentRepository commentRepository; + private final CommentLikeRepository commentLikeRepository; private final UserRepository userRepository; private final PostRepository postRepository; private final ApplicationEventPublisher eventPublisher; @@ -85,6 +92,41 @@ public PageResponse getComments(Long postId, Pageable pagea return PageResponse.from(comments); } + // TODO: 추후 메서드 통합 및 리팩토링 + @Transactional(readOnly = true) + public PageResponse getComments(Long postId, Long userId, Pageable pageable) { + // 기본 댓글 목록 + PageResponse response = getComments(postId, pageable); + + // 로그인 사용자용 로직 + if (userId != null) { + // 댓글 ID 수집 + List commentIds = response.items().stream() + .map(CommentListResponse::getCommentId) + .toList(); + + if (commentIds.isEmpty()) return response; + + // QueryDSL 기반 좋아요 ID 조회 (단일 쿼리) + List likedIds = commentLikeRepository.findLikedCommentIdsIn(userId, commentIds); + Set likedSet = new HashSet<>(likedIds); + + // likedByMe 세팅 + response.items().forEach(c -> c.setLikedByMe(likedSet.contains(c.getCommentId()))); + + // 자식 댓글에도 동일 적용 + response.items().forEach(parent -> { + if (parent.getChildren() != null) { + parent.getChildren().forEach(child -> + child.setLikedByMe(likedSet.contains(child.getCommentId())) + ); + } + }); + } + + return response; + } + /** * 댓글 수정 서비스 * 1. Post 조회 diff --git a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java index b43d1991..97c4ca18 100644 --- a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java +++ b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java @@ -160,7 +160,7 @@ private List fetchPosts(BooleanBuilder where, List Date: Fri, 10 Oct 2025 12:45:42 +0900 Subject: [PATCH 3/7] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=8B=A8=EA=B1=B4=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94/=EB=B6=81=EB=A7=88=ED=81=AC=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommentControllerDocs.java | 3 ++- .../board/post/controller/PostController.java | 7 +++++-- .../post/controller/PostControllerDocs.java | 15 ++++++++++----- .../board/post/dto/PostDetailResponse.java | 8 ++++++++ .../domain/board/post/service/PostService.java | 16 ++++++++++++++++ 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java index d62b4728..7ff99a66 100644 --- a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java +++ b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java @@ -539,7 +539,8 @@ ResponseEntity> deleteComment( "parentId": 25, "author": { "id": 7, - "nickname": "이몽룡" + "nickname": "이몽룡", + "profileImageUrl": null }, "content": "저도 동의합니다!", "createdAt": "2025-09-22T13:30:00", diff --git a/src/main/java/com/back/domain/board/post/controller/PostController.java b/src/main/java/com/back/domain/board/post/controller/PostController.java index bf0ffa5e..f0a1c67a 100644 --- a/src/main/java/com/back/domain/board/post/controller/PostController.java +++ b/src/main/java/com/back/domain/board/post/controller/PostController.java @@ -59,9 +59,12 @@ public ResponseEntity>> getPosts( // 게시글 단건 조회 @GetMapping("/{postId}") public ResponseEntity> getPost( - @PathVariable Long postId + @PathVariable Long postId, + @AuthenticationPrincipal CustomUserDetails user ) { - PostDetailResponse response = postService.getPost(postId); + PostDetailResponse response = (user != null) + ? postService.getPostWithUser(postId, user.getUserId()) + : postService.getPost(postId); return ResponseEntity .status(HttpStatus.OK) .body(RsData.success( diff --git a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java index c3bc9a43..7d04c572 100644 --- a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java +++ b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java @@ -43,7 +43,8 @@ public interface PostControllerDocs { "postId": 101, "author": { "id": 5, - "nickname": "홍길동" + "nickname": "홍길동", + "profileImageUrl": null }, "title": "첫 번째 게시글", "content": "안녕하세요, 첫 글입니다!", @@ -171,7 +172,7 @@ ResponseEntity> createPost( "items": [ { "postId": 1, - "author": { "id": 10, "nickname": "홍길동" }, + "author": { "id": 10, "nickname": "홍길동", "profileImageUrl": null }, "title": "첫 글", "categories": [{ "id": 1, "name": "공지사항" }], "likeCount": 5, @@ -247,7 +248,7 @@ ResponseEntity>> getPosts( "message": "게시글이 조회되었습니다.", "data": { "postId": 101, - "author": { "id": 5, "nickname": "홍길동" }, + "author": { "id": 5, "nickname": "홍길동", "profileImageUrl": null }, "title": "첫 번째 게시글", "content": "안녕하세요, 첫 글입니다!", "categories": [ @@ -257,6 +258,8 @@ ResponseEntity>> getPosts( "likeCount": 10, "bookmarkCount": 2, "commentCount": 3, + "likedByMe": false, + "bookmarkedByMe": false, "createdAt": "2025-09-22T10:30:00", "updatedAt": "2025-09-22T10:30:00" } @@ -296,7 +299,8 @@ ResponseEntity>> getPosts( ) }) ResponseEntity> getPost( - @PathVariable Long postId + @PathVariable Long postId, + @AuthenticationPrincipal CustomUserDetails user ); @Operation( @@ -318,7 +322,8 @@ ResponseEntity> getPost( "postId": 101, "author": { "id": 5, - "nickname": "홍길동" + "nickname": "홍길동", + "profileImageUrl": null }, "title": "수정된 게시글", "content": "안녕하세요, 수정했습니다!", diff --git a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java index e14b124c..d61fe9a8 100644 --- a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java +++ b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java @@ -29,10 +29,16 @@ public record PostDetailResponse( long likeCount, long bookmarkCount, long commentCount, + Boolean likedByMe, + Boolean bookmarkedByMe, LocalDateTime createdAt, LocalDateTime updatedAt ) { public static PostDetailResponse from(Post post) { + return from(post, false, false); + } + + public static PostDetailResponse from(Post post, boolean likedByMe, boolean bookmarkedByMe) { return new PostDetailResponse( post.getId(), AuthorResponse.from(post.getUser()), @@ -44,6 +50,8 @@ public static PostDetailResponse from(Post post) { post.getPostLikes().size(), post.getPostBookmarks().size(), post.getComments().size(), + likedByMe, + bookmarkedByMe, post.getCreatedAt(), post.getUpdatedAt() ); diff --git a/src/main/java/com/back/domain/board/post/service/PostService.java b/src/main/java/com/back/domain/board/post/service/PostService.java index 995e37fa..17086a0e 100644 --- a/src/main/java/com/back/domain/board/post/service/PostService.java +++ b/src/main/java/com/back/domain/board/post/service/PostService.java @@ -7,7 +7,9 @@ import com.back.domain.board.post.dto.PostListResponse; import com.back.domain.board.post.dto.PostRequest; import com.back.domain.board.post.dto.PostResponse; +import com.back.domain.board.post.repository.PostBookmarkRepository; import com.back.domain.board.post.repository.PostCategoryRepository; +import com.back.domain.board.post.repository.PostLikeRepository; import com.back.domain.board.post.repository.PostRepository; import com.back.domain.user.entity.User; import com.back.domain.user.repository.UserRepository; @@ -26,6 +28,8 @@ @Transactional public class PostService { private final PostRepository postRepository; + private final PostLikeRepository postLikeRepository; + private final PostBookmarkRepository postBookmarkRepository; private final UserRepository userRepository; private final PostCategoryRepository postCategoryRepository; @@ -85,6 +89,18 @@ public PostDetailResponse getPost(Long postId) { return PostDetailResponse.from(post); } + // TODO: 로그인 회원용 게시글 단건 조회 서비스, 추후 리팩토링 필요 + @Transactional(readOnly = true) + public PostDetailResponse getPostWithUser(Long postId, Long userId) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); + + boolean likedByMe = postLikeRepository.existsByUserIdAndPostId(userId, postId); + boolean bookmarkedByMe = postBookmarkRepository.existsByUserIdAndPostId(userId, postId); + + return PostDetailResponse.from(post, likedByMe, bookmarkedByMe); + } + /** * 게시글 수정 서비스 * 1. Post 조회 From c36104a9d048d4ee0dcea3b6b54055a4cf16caf2 Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:54:23 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Feat:=20Post=20thumbnailUrl=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/post/dto/PostListResponse.java | 10 +++++++--- .../com/back/domain/board/post/dto/PostRequest.java | 2 ++ .../com/back/domain/board/post/entity/Post.java | 13 +++++++++++++ .../board/post/repository/PostRepositoryImpl.java | 1 + .../back/domain/board/post/service/PostService.java | 2 +- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/back/domain/board/post/dto/PostListResponse.java b/src/main/java/com/back/domain/board/post/dto/PostListResponse.java index 9545bccf..20a5779a 100644 --- a/src/main/java/com/back/domain/board/post/dto/PostListResponse.java +++ b/src/main/java/com/back/domain/board/post/dto/PostListResponse.java @@ -16,19 +16,22 @@ public class PostListResponse { private final Long postId; private final AuthorResponse author; private final String title; + private final String thumbnailUrl; + + @Setter + private List categories; + private final long likeCount; private final long bookmarkCount; private final long commentCount; private final LocalDateTime createdAt; private final LocalDateTime updatedAt; - @Setter - private List categories; - @QueryProjection public PostListResponse(Long postId, AuthorResponse author, String title, + String thumbnailUrl, List categories, long likeCount, long bookmarkCount, @@ -38,6 +41,7 @@ public PostListResponse(Long postId, this.postId = postId; this.author = author; this.title = title; + this.thumbnailUrl = thumbnailUrl; this.categories = categories; this.likeCount = likeCount; this.bookmarkCount = bookmarkCount; diff --git a/src/main/java/com/back/domain/board/post/dto/PostRequest.java b/src/main/java/com/back/domain/board/post/dto/PostRequest.java index 5deef459..12e24a9c 100644 --- a/src/main/java/com/back/domain/board/post/dto/PostRequest.java +++ b/src/main/java/com/back/domain/board/post/dto/PostRequest.java @@ -9,10 +9,12 @@ * * @param title 게시글 제목 * @param content 게시글 내용 + * @param thumbnailUrl 썸네일 URL * @param categoryIds 카테고리 ID 리스트 */ public record PostRequest( @NotBlank String title, @NotBlank String content, + String thumbnailUrl, List categoryIds ) {} \ No newline at end of file diff --git a/src/main/java/com/back/domain/board/post/entity/Post.java b/src/main/java/com/back/domain/board/post/entity/Post.java index e34945e6..743101a5 100644 --- a/src/main/java/com/back/domain/board/post/entity/Post.java +++ b/src/main/java/com/back/domain/board/post/entity/Post.java @@ -9,6 +9,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Entity @Getter @@ -22,6 +24,9 @@ public class Post extends BaseEntity { private String content; + @Column(length = 500) + private String thumbnailUrl; + // TODO: 추후 PostRepositoryImpl#searchPosts 로직 개선 필요, ERD에도 반영할 것 @Column(nullable = false) private Long likeCount = 0L; @@ -49,6 +54,14 @@ public Post(User user, String title, String content) { this.user = user; this.title = title; this.content = content; + this.thumbnailUrl = null; + } + + public Post(User user, String title, String content, String thumbnailUrl) { + this.user = user; + this.title = title; + this.content = content; + this.thumbnailUrl = thumbnailUrl; } // -------------------- 비즈니스 메서드 -------------------- diff --git a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java index 97c4ca18..53806ffd 100644 --- a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java +++ b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java @@ -162,6 +162,7 @@ private List fetchPosts(BooleanBuilder where, List new CustomException(ErrorCode.USER_NOT_FOUND)); // Post 생성 - Post post = new Post(user, request.title(), request.content()); + Post post = new Post(user, request.title(), request.content(), request.thumbnailUrl()); // Category 매핑 if (request.categoryIds() != null) { From 25581d4f787da164a81f6203224a3d59cc74b17d Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:08:21 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Ref:=20content=20=ED=95=84=EB=93=9C=20TEXT?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commits.txt | 624 ++++++++++++++++++ .../back/domain/board/post/entity/Post.java | 1 + 2 files changed, 625 insertions(+) create mode 100644 commits.txt diff --git a/commits.txt b/commits.txt new file mode 100644 index 00000000..6fbd2aa0 --- /dev/null +++ b/commits.txt @@ -0,0 +1,624 @@ +commit 4d6c59aa1381261e4ee0e9bd634f6d1c5a1b5901 +Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> +Date: Fri Oct 10 11:36:38 2025 +0900 + + Feat: 작성자 응답 DTO 프로필 이미지 추가 + +diff --git a/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java b/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java +index 664d8fe..d177b02 100644 +--- a/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java ++++ b/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java +@@ -6,12 +6,14 @@ import com.querydsl.core.annotations.QueryProjection; + /** + * 작성자 응답 DTO + * +- * @param id 작성자 ID +- * @param nickname 작성자 닉네임 ++ * @param id 작성자 ID ++ * @param nickname 작성자 닉네임 ++ * @param profileImageUrl 작성자 프로필 이미지 + */ + public record AuthorResponse( + Long id, +- String nickname ++ String nickname, ++ String profileImageUrl + ) { + @QueryProjection + public AuthorResponse {} +@@ -19,7 +21,8 @@ public record AuthorResponse( + public static AuthorResponse from(User user) { + return new AuthorResponse( + user.getId(), +- user.getUserProfile().getNickname() ++ user.getUserProfile().getNickname(), ++ user.getProfileImageUrl() + ); + } + } +\ No newline at end of file +commit c9b6f84daaf1b583e98d68e0f806456b8196c3f3 +Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> +Date: Fri Oct 10 12:31:33 2025 +0900 + + Feat: 댓글 목록 조회 시 좋아요 여부 추가 + +diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentController.java b/src/main/java/com/back/domain/board/comment/controller/CommentController.java +index aa873c8..a4091b2 100644 +--- a/src/main/java/com/back/domain/board/comment/controller/CommentController.java ++++ b/src/main/java/com/back/domain/board/comment/controller/CommentController.java +@@ -44,9 +44,11 @@ public class CommentController implements CommentControllerDocs { + @GetMapping + public ResponseEntity>> getComments( + @PathVariable Long postId, ++ @AuthenticationPrincipal CustomUserDetails user, + @PageableDefault(sort = "createdAt", direction = Sort.Direction.ASC) Pageable pageable + ) { +- PageResponse response = commentService.getComments(postId, pageable); ++ Long userId = (user != null) ? user.getUserId() : null; ++ PageResponse response = commentService.getComments(postId, userId, pageable); + return ResponseEntity + .status(HttpStatus.OK) + .body(RsData.success( +diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java +index 4f50bbf..d62b472 100644 +--- a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java ++++ b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java +@@ -41,7 +41,8 @@ public interface CommentControllerDocs { + "postId": 101, + "author": { + "id": 5, +- "nickname": "홍길동" ++ "nickname": "홍길동", ++ "profileImageUrl": null + }, + "content": "좋은 글 감사합니다!", + "createdAt": "2025-09-22T11:30:00", +@@ -170,10 +171,12 @@ public interface CommentControllerDocs { + "parentId": null, + "author": { + "id": 5, +- "nickname": "홍길동" ++ "nickname": "홍길동", ++ "profileImageUrl": null + }, + "content": "부모 댓글", + "likeCount": 2, ++ "likedByMe": true, + "createdAt": "2025-09-22T11:30:00", + "updatedAt": "2025-09-22T11:30:00", + "children": [ +@@ -183,10 +186,12 @@ public interface CommentControllerDocs { + "parentId": 1, + "author": { + "id": 5, +- "nickname": "홍길동" ++ "nickname": "홍길동", ++ "profileImageUrl": null + }, + "content": "자식 댓글", + "likeCount": 0, ++ "likedByMe": false, + "createdAt": "2025-09-22T11:35:00", + "updatedAt": "2025-09-22T11:35:00", + "children": [] +@@ -252,6 +257,7 @@ public interface CommentControllerDocs { + }) + ResponseEntity>> getComments( + @PathVariable Long postId, ++ @AuthenticationPrincipal CustomUserDetails user, + Pageable pageable + ); + +@@ -275,7 +281,8 @@ public interface CommentControllerDocs { + "postId": 101, + "author": { + "id": 5, +- "nickname": "홍길동" ++ "nickname": "홍길동", ++ "profileImageUrl": null + }, + "content": "수정된 댓글 내용입니다.", + "createdAt": "2025-09-22T11:30:00", +diff --git a/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java b/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java +index 90f6055..7c4a2c8 100644 +--- a/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java ++++ b/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java +@@ -22,6 +22,9 @@ public class CommentListResponse { + @Setter + private long likeCount; + ++ @Setter ++ private Boolean likedByMe; ++ + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; + +@@ -35,6 +38,7 @@ public class CommentListResponse { + AuthorResponse author, + String content, + long likeCount, ++ Boolean likedByMe, + LocalDateTime createdAt, + LocalDateTime updatedAt, + List children) { +@@ -43,6 +47,7 @@ public class CommentListResponse { + this.parentId = parentId; + this.author = author; + this.content = content; ++ this.likedByMe = likedByMe; + this.likeCount = likeCount; + this.createdAt = createdAt; + this.updatedAt = updatedAt; +diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java +index 47a3a99..df675a4 100644 +--- a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java ++++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java +@@ -2,12 +2,16 @@ package com.back.domain.board.comment.repository; + + import com.back.domain.board.comment.entity.CommentLike; + import org.springframework.data.jpa.repository.JpaRepository; ++import org.springframework.data.jpa.repository.Query; ++import org.springframework.data.repository.query.Param; + import org.springframework.stereotype.Repository; + ++import java.util.Collection; ++import java.util.List; + import java.util.Optional; + + @Repository +-public interface CommentLikeRepository extends JpaRepository { ++public interface CommentLikeRepository extends JpaRepository, CommentLikeRepositoryCustom { + boolean existsByUserIdAndCommentId(Long userId, Long commentId); + Optional findByUserIdAndCommentId(Long userId, Long commentId); + } +diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java +new file mode 100644 +index 0000000..a2ec7cb +--- /dev/null ++++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java +@@ -0,0 +1,8 @@ ++package com.back.domain.board.comment.repository; ++ ++import java.util.Collection; ++import java.util.List; ++ ++public interface CommentLikeRepositoryCustom { ++ List findLikedCommentIdsIn(Long userId, Collection commentIds); ++} +diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java +new file mode 100644 +index 0000000..9e52055 +--- /dev/null ++++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java +@@ -0,0 +1,31 @@ ++package com.back.domain.board.comment.repository; ++ ++import com.back.domain.board.comment.entity.QCommentLike; ++import com.querydsl.jpa.impl.JPAQueryFactory; ++import lombok.RequiredArgsConstructor; ++import org.springframework.stereotype.Repository; ++ ++import java.util.Collection; ++import java.util.List; ++ ++ ++@Repository ++@RequiredArgsConstructor ++public class CommentLikeRepositoryImpl implements CommentLikeRepositoryCustom { ++ ++ private final JPAQueryFactory queryFactory; ++ ++ @Override ++ public List findLikedCommentIdsIn(Long userId, Collection commentIds) { ++ QCommentLike commentLike = QCommentLike.commentLike; ++ ++ return queryFactory ++ .select(commentLike.comment.id) ++ .from(commentLike) ++ .where( ++ commentLike.user.id.eq(userId), ++ commentLike.comment.id.in(commentIds) ++ ) ++ .fetch(); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java b/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java +index 11ae4c5..29156bf 100644 +--- a/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java ++++ b/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java +@@ -112,9 +112,10 @@ public class CommentRepositoryImpl implements CommentRepositoryCustom { + comment.id, + comment.post.id, + comment.parent.id, +- new QAuthorResponse(user.id, profile.nickname), ++ new QAuthorResponse(user.id, profile.nickname, profile.profileImageUrl), + comment.content, + Expressions.constant(0L), // likeCount는 별도 주입 ++ Expressions.constant(false), + comment.createdAt, + comment.updatedAt, + Expressions.constant(Collections.emptyList()) // children은 별도 주입 +diff --git a/src/main/java/com/back/domain/board/comment/service/CommentService.java b/src/main/java/com/back/domain/board/comment/service/CommentService.java +index 64b7ab4..98e768f 100644 +--- a/src/main/java/com/back/domain/board/comment/service/CommentService.java ++++ b/src/main/java/com/back/domain/board/comment/service/CommentService.java +@@ -4,6 +4,8 @@ import com.back.domain.board.comment.dto.CommentListResponse; + import com.back.domain.board.comment.dto.CommentRequest; + import com.back.domain.board.comment.dto.CommentResponse; + import com.back.domain.board.comment.dto.ReplyResponse; ++import com.back.domain.board.comment.entity.CommentLike; ++import com.back.domain.board.comment.repository.CommentLikeRepository; + import com.back.domain.board.common.dto.PageResponse; + import com.back.domain.board.comment.entity.Comment; + import com.back.domain.board.post.entity.Post; +@@ -22,11 +24,16 @@ import org.springframework.data.domain.Pageable; + import org.springframework.stereotype.Service; + import org.springframework.transaction.annotation.Transactional; + ++import java.util.HashSet; ++import java.util.List; ++import java.util.Set; ++ + @Service + @RequiredArgsConstructor + @Transactional + public class CommentService { + private final CommentRepository commentRepository; ++ private final CommentLikeRepository commentLikeRepository; + private final UserRepository userRepository; + private final PostRepository postRepository; + private final ApplicationEventPublisher eventPublisher; +@@ -85,6 +92,41 @@ public class CommentService { + return PageResponse.from(comments); + } + ++ // TODO: 추후 메서드 통합 및 리팩토링 ++ @Transactional(readOnly = true) ++ public PageResponse getComments(Long postId, Long userId, Pageable pageable) { ++ // 기본 댓글 목록 ++ PageResponse response = getComments(postId, pageable); ++ ++ // 로그인 사용자용 로직 ++ if (userId != null) { ++ // 댓글 ID 수집 ++ List commentIds = response.items().stream() ++ .map(CommentListResponse::getCommentId) ++ .toList(); ++ ++ if (commentIds.isEmpty()) return response; ++ ++ // QueryDSL 기반 좋아요 ID 조회 (단일 쿼리) ++ List likedIds = commentLikeRepository.findLikedCommentIdsIn(userId, commentIds); ++ Set likedSet = new HashSet<>(likedIds); ++ ++ // likedByMe 세팅 ++ response.items().forEach(c -> c.setLikedByMe(likedSet.contains(c.getCommentId()))); ++ ++ // 자식 댓글에도 동일 적용 ++ response.items().forEach(parent -> { ++ if (parent.getChildren() != null) { ++ parent.getChildren().forEach(child -> ++ child.setLikedByMe(likedSet.contains(child.getCommentId())) ++ ); ++ } ++ }); ++ } ++ ++ return response; ++ } ++ + /** + * 댓글 수정 서비스 + * 1. Post 조회 +diff --git a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java +index b43d199..97c4ca1 100644 +--- a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java ++++ b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java +@@ -160,7 +160,7 @@ public class PostRepositoryImpl implements PostRepositoryCustom { + return queryFactory + .select(new QPostListResponse( + post.id, +- new QAuthorResponse(user.id, profile.nickname), // 작성자 정보 (N+1 방지 join) ++ new QAuthorResponse(user.id, profile.nickname, profile.profileImageUrl), // 작성자 정보 (N+1 방지 join) + post.title, + Expressions.constant(Collections.emptyList()), // categories는 별도 주입 + likeCount, +commit 231d9348c41b7a3fbe5dbc68493bb4011a620810 +Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> +Date: Fri Oct 10 12:45:42 2025 +0900 + + Feat: 게시글 단건 조회 시 좋아요/북마크 여부 추가 + +diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java +index d62b472..7ff99a6 100644 +--- a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java ++++ b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java +@@ -539,7 +539,8 @@ public interface CommentControllerDocs { + "parentId": 25, + "author": { + "id": 7, +- "nickname": "이몽룡" ++ "nickname": "이몽룡", ++ "profileImageUrl": null + }, + "content": "저도 동의합니다!", + "createdAt": "2025-09-22T13:30:00", +diff --git a/src/main/java/com/back/domain/board/post/controller/PostController.java b/src/main/java/com/back/domain/board/post/controller/PostController.java +index bf0ffa5..f0a1c67 100644 +--- a/src/main/java/com/back/domain/board/post/controller/PostController.java ++++ b/src/main/java/com/back/domain/board/post/controller/PostController.java +@@ -59,9 +59,12 @@ public class PostController implements PostControllerDocs { + // 게시글 단건 조회 + @GetMapping("/{postId}") + public ResponseEntity> getPost( +- @PathVariable Long postId ++ @PathVariable Long postId, ++ @AuthenticationPrincipal CustomUserDetails user + ) { +- PostDetailResponse response = postService.getPost(postId); ++ PostDetailResponse response = (user != null) ++ ? postService.getPostWithUser(postId, user.getUserId()) ++ : postService.getPost(postId); + return ResponseEntity + .status(HttpStatus.OK) + .body(RsData.success( +diff --git a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java +index c3bc9a4..7d04c57 100644 +--- a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java ++++ b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java +@@ -43,7 +43,8 @@ public interface PostControllerDocs { + "postId": 101, + "author": { + "id": 5, +- "nickname": "홍길동" ++ "nickname": "홍길동", ++ "profileImageUrl": null + }, + "title": "첫 번째 게시글", + "content": "안녕하세요, 첫 글입니다!", +@@ -171,7 +172,7 @@ public interface PostControllerDocs { + "items": [ + { + "postId": 1, +- "author": { "id": 10, "nickname": "홍길동" }, ++ "author": { "id": 10, "nickname": "홍길동", "profileImageUrl": null }, + "title": "첫 글", + "categories": [{ "id": 1, "name": "공지사항" }], + "likeCount": 5, +@@ -247,7 +248,7 @@ public interface PostControllerDocs { + "message": "게시글이 조회되었습니다.", + "data": { + "postId": 101, +- "author": { "id": 5, "nickname": "홍길동" }, ++ "author": { "id": 5, "nickname": "홍길동", "profileImageUrl": null }, + "title": "첫 번째 게시글", + "content": "안녕하세요, 첫 글입니다!", + "categories": [ +@@ -257,6 +258,8 @@ public interface PostControllerDocs { + "likeCount": 10, + "bookmarkCount": 2, + "commentCount": 3, ++ "likedByMe": false, ++ "bookmarkedByMe": false, + "createdAt": "2025-09-22T10:30:00", + "updatedAt": "2025-09-22T10:30:00" + } +@@ -296,7 +299,8 @@ public interface PostControllerDocs { + ) + }) + ResponseEntity> getPost( +- @PathVariable Long postId ++ @PathVariable Long postId, ++ @AuthenticationPrincipal CustomUserDetails user + ); + + @Operation( +@@ -318,7 +322,8 @@ public interface PostControllerDocs { + "postId": 101, + "author": { + "id": 5, +- "nickname": "홍길동" ++ "nickname": "홍길동", ++ "profileImageUrl": null + }, + "title": "수정된 게시글", + "content": "안녕하세요, 수정했습니다!", +diff --git a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java +index e14b124..d61fe9a 100644 +--- a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java ++++ b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java +@@ -29,10 +29,16 @@ public record PostDetailResponse( + long likeCount, + long bookmarkCount, + long commentCount, ++ Boolean likedByMe, ++ Boolean bookmarkedByMe, + LocalDateTime createdAt, + LocalDateTime updatedAt + ) { + public static PostDetailResponse from(Post post) { ++ return from(post, false, false); ++ } ++ ++ public static PostDetailResponse from(Post post, boolean likedByMe, boolean bookmarkedByMe) { + return new PostDetailResponse( + post.getId(), + AuthorResponse.from(post.getUser()), +@@ -44,6 +50,8 @@ public record PostDetailResponse( + post.getPostLikes().size(), + post.getPostBookmarks().size(), + post.getComments().size(), ++ likedByMe, ++ bookmarkedByMe, + post.getCreatedAt(), + post.getUpdatedAt() + ); +diff --git a/src/main/java/com/back/domain/board/post/service/PostService.java b/src/main/java/com/back/domain/board/post/service/PostService.java +index 995e37f..17086a0 100644 +--- a/src/main/java/com/back/domain/board/post/service/PostService.java ++++ b/src/main/java/com/back/domain/board/post/service/PostService.java +@@ -7,7 +7,9 @@ import com.back.domain.board.post.dto.PostDetailResponse; + import com.back.domain.board.post.dto.PostListResponse; + import com.back.domain.board.post.dto.PostRequest; + import com.back.domain.board.post.dto.PostResponse; ++import com.back.domain.board.post.repository.PostBookmarkRepository; + import com.back.domain.board.post.repository.PostCategoryRepository; ++import com.back.domain.board.post.repository.PostLikeRepository; + import com.back.domain.board.post.repository.PostRepository; + import com.back.domain.user.entity.User; + import com.back.domain.user.repository.UserRepository; +@@ -26,6 +28,8 @@ import java.util.List; + @Transactional + public class PostService { + private final PostRepository postRepository; ++ private final PostLikeRepository postLikeRepository; ++ private final PostBookmarkRepository postBookmarkRepository; + private final UserRepository userRepository; + private final PostCategoryRepository postCategoryRepository; + +@@ -85,6 +89,18 @@ public class PostService { + return PostDetailResponse.from(post); + } + ++ // TODO: 로그인 회원용 게시글 단건 조회 서비스, 추후 리팩토링 필요 ++ @Transactional(readOnly = true) ++ public PostDetailResponse getPostWithUser(Long postId, Long userId) { ++ Post post = postRepository.findById(postId) ++ .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); ++ ++ boolean likedByMe = postLikeRepository.existsByUserIdAndPostId(userId, postId); ++ boolean bookmarkedByMe = postBookmarkRepository.existsByUserIdAndPostId(userId, postId); ++ ++ return PostDetailResponse.from(post, likedByMe, bookmarkedByMe); ++ } ++ + /** + * 게시글 수정 서비스 + * 1. Post 조회 +commit e2659bb21019551e17490a6873ed7b184721f4a8 +Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> +Date: Fri Oct 10 14:54:23 2025 +0900 + + Feat: Post thumbnailUrl 필드 추가 + +diff --git a/src/main/java/com/back/domain/board/post/dto/PostListResponse.java b/src/main/java/com/back/domain/board/post/dto/PostListResponse.java +index 9545bcc..20a5779 100644 +--- a/src/main/java/com/back/domain/board/post/dto/PostListResponse.java ++++ b/src/main/java/com/back/domain/board/post/dto/PostListResponse.java +@@ -16,19 +16,22 @@ public class PostListResponse { + private final Long postId; + private final AuthorResponse author; + private final String title; ++ private final String thumbnailUrl; ++ ++ @Setter ++ private List categories; ++ + private final long likeCount; + private final long bookmarkCount; + private final long commentCount; + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; + +- @Setter +- private List categories; +- + @QueryProjection + public PostListResponse(Long postId, + AuthorResponse author, + String title, ++ String thumbnailUrl, + List categories, + long likeCount, + long bookmarkCount, +@@ -38,6 +41,7 @@ public class PostListResponse { + this.postId = postId; + this.author = author; + this.title = title; ++ this.thumbnailUrl = thumbnailUrl; + this.categories = categories; + this.likeCount = likeCount; + this.bookmarkCount = bookmarkCount; +diff --git a/src/main/java/com/back/domain/board/post/dto/PostRequest.java b/src/main/java/com/back/domain/board/post/dto/PostRequest.java +index 5deef45..12e24a9 100644 +--- a/src/main/java/com/back/domain/board/post/dto/PostRequest.java ++++ b/src/main/java/com/back/domain/board/post/dto/PostRequest.java +@@ -9,10 +9,12 @@ import java.util.List; + * + * @param title 게시글 제목 + * @param content 게시글 내용 ++ * @param thumbnailUrl 썸네일 URL + * @param categoryIds 카테고리 ID 리스트 + */ + public record PostRequest( + @NotBlank String title, + @NotBlank String content, ++ String thumbnailUrl, + List categoryIds + ) {} +\ No newline at end of file +diff --git a/src/main/java/com/back/domain/board/post/entity/Post.java b/src/main/java/com/back/domain/board/post/entity/Post.java +index e34945e..743101a 100644 +--- a/src/main/java/com/back/domain/board/post/entity/Post.java ++++ b/src/main/java/com/back/domain/board/post/entity/Post.java +@@ -9,6 +9,8 @@ import lombok.NoArgsConstructor; + + import java.util.ArrayList; + import java.util.List; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; + + @Entity + @Getter +@@ -22,6 +24,9 @@ public class Post extends BaseEntity { + + private String content; + ++ @Column(length = 500) ++ private String thumbnailUrl; ++ + // TODO: 추후 PostRepositoryImpl#searchPosts 로직 개선 필요, ERD에도 반영할 것 + @Column(nullable = false) + private Long likeCount = 0L; +@@ -49,6 +54,14 @@ public class Post extends BaseEntity { + this.user = user; + this.title = title; + this.content = content; ++ this.thumbnailUrl = null; ++ } ++ ++ public Post(User user, String title, String content, String thumbnailUrl) { ++ this.user = user; ++ this.title = title; ++ this.content = content; ++ this.thumbnailUrl = thumbnailUrl; + } + + // -------------------- 비즈니스 메서드 -------------------- +diff --git a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java +index 97c4ca1..53806ff 100644 +--- a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java ++++ b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java +@@ -162,6 +162,7 @@ public class PostRepositoryImpl implements PostRepositoryCustom { + post.id, + new QAuthorResponse(user.id, profile.nickname, profile.profileImageUrl), // 작성자 정보 (N+1 방지 join) + post.title, ++ post.thumbnailUrl, + Expressions.constant(Collections.emptyList()), // categories는 별도 주입 + likeCount, + bookmarkCount, +diff --git a/src/main/java/com/back/domain/board/post/service/PostService.java b/src/main/java/com/back/domain/board/post/service/PostService.java +index 17086a0..b53a88d 100644 +--- a/src/main/java/com/back/domain/board/post/service/PostService.java ++++ b/src/main/java/com/back/domain/board/post/service/PostService.java +@@ -47,7 +47,7 @@ public class PostService { + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + // Post 생성 +- Post post = new Post(user, request.title(), request.content()); ++ Post post = new Post(user, request.title(), request.content(), request.thumbnailUrl()); + + // Category 매핑 + if (request.categoryIds() != null) { diff --git a/src/main/java/com/back/domain/board/post/entity/Post.java b/src/main/java/com/back/domain/board/post/entity/Post.java index 743101a5..4696d65b 100644 --- a/src/main/java/com/back/domain/board/post/entity/Post.java +++ b/src/main/java/com/back/domain/board/post/entity/Post.java @@ -22,6 +22,7 @@ public class Post extends BaseEntity { private String title; + @Column(nullable = false, columnDefinition = "TEXT") private String content; @Column(length = 500) From 000fa8aedd17236bd99cbbd0500dfd3b581cca4d Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:12:44 +0900 Subject: [PATCH 6/7] =?UTF-8?q?Test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=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 --- commits.txt | 624 ------------------ .../board/controller/PostControllerTest.java | 18 +- .../domain/board/service/PostServiceTest.java | 14 +- 3 files changed, 16 insertions(+), 640 deletions(-) delete mode 100644 commits.txt diff --git a/commits.txt b/commits.txt deleted file mode 100644 index 6fbd2aa0..00000000 --- a/commits.txt +++ /dev/null @@ -1,624 +0,0 @@ -commit 4d6c59aa1381261e4ee0e9bd634f6d1c5a1b5901 -Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> -Date: Fri Oct 10 11:36:38 2025 +0900 - - Feat: 작성자 응답 DTO 프로필 이미지 추가 - -diff --git a/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java b/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java -index 664d8fe..d177b02 100644 ---- a/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java -+++ b/src/main/java/com/back/domain/board/common/dto/AuthorResponse.java -@@ -6,12 +6,14 @@ import com.querydsl.core.annotations.QueryProjection; - /** - * 작성자 응답 DTO - * -- * @param id 작성자 ID -- * @param nickname 작성자 닉네임 -+ * @param id 작성자 ID -+ * @param nickname 작성자 닉네임 -+ * @param profileImageUrl 작성자 프로필 이미지 - */ - public record AuthorResponse( - Long id, -- String nickname -+ String nickname, -+ String profileImageUrl - ) { - @QueryProjection - public AuthorResponse {} -@@ -19,7 +21,8 @@ public record AuthorResponse( - public static AuthorResponse from(User user) { - return new AuthorResponse( - user.getId(), -- user.getUserProfile().getNickname() -+ user.getUserProfile().getNickname(), -+ user.getProfileImageUrl() - ); - } - } -\ No newline at end of file -commit c9b6f84daaf1b583e98d68e0f806456b8196c3f3 -Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> -Date: Fri Oct 10 12:31:33 2025 +0900 - - Feat: 댓글 목록 조회 시 좋아요 여부 추가 - -diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentController.java b/src/main/java/com/back/domain/board/comment/controller/CommentController.java -index aa873c8..a4091b2 100644 ---- a/src/main/java/com/back/domain/board/comment/controller/CommentController.java -+++ b/src/main/java/com/back/domain/board/comment/controller/CommentController.java -@@ -44,9 +44,11 @@ public class CommentController implements CommentControllerDocs { - @GetMapping - public ResponseEntity>> getComments( - @PathVariable Long postId, -+ @AuthenticationPrincipal CustomUserDetails user, - @PageableDefault(sort = "createdAt", direction = Sort.Direction.ASC) Pageable pageable - ) { -- PageResponse response = commentService.getComments(postId, pageable); -+ Long userId = (user != null) ? user.getUserId() : null; -+ PageResponse response = commentService.getComments(postId, userId, pageable); - return ResponseEntity - .status(HttpStatus.OK) - .body(RsData.success( -diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java -index 4f50bbf..d62b472 100644 ---- a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java -+++ b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java -@@ -41,7 +41,8 @@ public interface CommentControllerDocs { - "postId": 101, - "author": { - "id": 5, -- "nickname": "홍길동" -+ "nickname": "홍길동", -+ "profileImageUrl": null - }, - "content": "좋은 글 감사합니다!", - "createdAt": "2025-09-22T11:30:00", -@@ -170,10 +171,12 @@ public interface CommentControllerDocs { - "parentId": null, - "author": { - "id": 5, -- "nickname": "홍길동" -+ "nickname": "홍길동", -+ "profileImageUrl": null - }, - "content": "부모 댓글", - "likeCount": 2, -+ "likedByMe": true, - "createdAt": "2025-09-22T11:30:00", - "updatedAt": "2025-09-22T11:30:00", - "children": [ -@@ -183,10 +186,12 @@ public interface CommentControllerDocs { - "parentId": 1, - "author": { - "id": 5, -- "nickname": "홍길동" -+ "nickname": "홍길동", -+ "profileImageUrl": null - }, - "content": "자식 댓글", - "likeCount": 0, -+ "likedByMe": false, - "createdAt": "2025-09-22T11:35:00", - "updatedAt": "2025-09-22T11:35:00", - "children": [] -@@ -252,6 +257,7 @@ public interface CommentControllerDocs { - }) - ResponseEntity>> getComments( - @PathVariable Long postId, -+ @AuthenticationPrincipal CustomUserDetails user, - Pageable pageable - ); - -@@ -275,7 +281,8 @@ public interface CommentControllerDocs { - "postId": 101, - "author": { - "id": 5, -- "nickname": "홍길동" -+ "nickname": "홍길동", -+ "profileImageUrl": null - }, - "content": "수정된 댓글 내용입니다.", - "createdAt": "2025-09-22T11:30:00", -diff --git a/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java b/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java -index 90f6055..7c4a2c8 100644 ---- a/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java -+++ b/src/main/java/com/back/domain/board/comment/dto/CommentListResponse.java -@@ -22,6 +22,9 @@ public class CommentListResponse { - @Setter - private long likeCount; - -+ @Setter -+ private Boolean likedByMe; -+ - private final LocalDateTime createdAt; - private final LocalDateTime updatedAt; - -@@ -35,6 +38,7 @@ public class CommentListResponse { - AuthorResponse author, - String content, - long likeCount, -+ Boolean likedByMe, - LocalDateTime createdAt, - LocalDateTime updatedAt, - List children) { -@@ -43,6 +47,7 @@ public class CommentListResponse { - this.parentId = parentId; - this.author = author; - this.content = content; -+ this.likedByMe = likedByMe; - this.likeCount = likeCount; - this.createdAt = createdAt; - this.updatedAt = updatedAt; -diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java -index 47a3a99..df675a4 100644 ---- a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java -+++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepository.java -@@ -2,12 +2,16 @@ package com.back.domain.board.comment.repository; - - import com.back.domain.board.comment.entity.CommentLike; - import org.springframework.data.jpa.repository.JpaRepository; -+import org.springframework.data.jpa.repository.Query; -+import org.springframework.data.repository.query.Param; - import org.springframework.stereotype.Repository; - -+import java.util.Collection; -+import java.util.List; - import java.util.Optional; - - @Repository --public interface CommentLikeRepository extends JpaRepository { -+public interface CommentLikeRepository extends JpaRepository, CommentLikeRepositoryCustom { - boolean existsByUserIdAndCommentId(Long userId, Long commentId); - Optional findByUserIdAndCommentId(Long userId, Long commentId); - } -diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java -new file mode 100644 -index 0000000..a2ec7cb ---- /dev/null -+++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryCustom.java -@@ -0,0 +1,8 @@ -+package com.back.domain.board.comment.repository; -+ -+import java.util.Collection; -+import java.util.List; -+ -+public interface CommentLikeRepositoryCustom { -+ List findLikedCommentIdsIn(Long userId, Collection commentIds); -+} -diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java -new file mode 100644 -index 0000000..9e52055 ---- /dev/null -+++ b/src/main/java/com/back/domain/board/comment/repository/CommentLikeRepositoryImpl.java -@@ -0,0 +1,31 @@ -+package com.back.domain.board.comment.repository; -+ -+import com.back.domain.board.comment.entity.QCommentLike; -+import com.querydsl.jpa.impl.JPAQueryFactory; -+import lombok.RequiredArgsConstructor; -+import org.springframework.stereotype.Repository; -+ -+import java.util.Collection; -+import java.util.List; -+ -+ -+@Repository -+@RequiredArgsConstructor -+public class CommentLikeRepositoryImpl implements CommentLikeRepositoryCustom { -+ -+ private final JPAQueryFactory queryFactory; -+ -+ @Override -+ public List findLikedCommentIdsIn(Long userId, Collection commentIds) { -+ QCommentLike commentLike = QCommentLike.commentLike; -+ -+ return queryFactory -+ .select(commentLike.comment.id) -+ .from(commentLike) -+ .where( -+ commentLike.user.id.eq(userId), -+ commentLike.comment.id.in(commentIds) -+ ) -+ .fetch(); -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java b/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java -index 11ae4c5..29156bf 100644 ---- a/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java -+++ b/src/main/java/com/back/domain/board/comment/repository/CommentRepositoryImpl.java -@@ -112,9 +112,10 @@ public class CommentRepositoryImpl implements CommentRepositoryCustom { - comment.id, - comment.post.id, - comment.parent.id, -- new QAuthorResponse(user.id, profile.nickname), -+ new QAuthorResponse(user.id, profile.nickname, profile.profileImageUrl), - comment.content, - Expressions.constant(0L), // likeCount는 별도 주입 -+ Expressions.constant(false), - comment.createdAt, - comment.updatedAt, - Expressions.constant(Collections.emptyList()) // children은 별도 주입 -diff --git a/src/main/java/com/back/domain/board/comment/service/CommentService.java b/src/main/java/com/back/domain/board/comment/service/CommentService.java -index 64b7ab4..98e768f 100644 ---- a/src/main/java/com/back/domain/board/comment/service/CommentService.java -+++ b/src/main/java/com/back/domain/board/comment/service/CommentService.java -@@ -4,6 +4,8 @@ import com.back.domain.board.comment.dto.CommentListResponse; - import com.back.domain.board.comment.dto.CommentRequest; - import com.back.domain.board.comment.dto.CommentResponse; - import com.back.domain.board.comment.dto.ReplyResponse; -+import com.back.domain.board.comment.entity.CommentLike; -+import com.back.domain.board.comment.repository.CommentLikeRepository; - import com.back.domain.board.common.dto.PageResponse; - import com.back.domain.board.comment.entity.Comment; - import com.back.domain.board.post.entity.Post; -@@ -22,11 +24,16 @@ import org.springframework.data.domain.Pageable; - import org.springframework.stereotype.Service; - import org.springframework.transaction.annotation.Transactional; - -+import java.util.HashSet; -+import java.util.List; -+import java.util.Set; -+ - @Service - @RequiredArgsConstructor - @Transactional - public class CommentService { - private final CommentRepository commentRepository; -+ private final CommentLikeRepository commentLikeRepository; - private final UserRepository userRepository; - private final PostRepository postRepository; - private final ApplicationEventPublisher eventPublisher; -@@ -85,6 +92,41 @@ public class CommentService { - return PageResponse.from(comments); - } - -+ // TODO: 추후 메서드 통합 및 리팩토링 -+ @Transactional(readOnly = true) -+ public PageResponse getComments(Long postId, Long userId, Pageable pageable) { -+ // 기본 댓글 목록 -+ PageResponse response = getComments(postId, pageable); -+ -+ // 로그인 사용자용 로직 -+ if (userId != null) { -+ // 댓글 ID 수집 -+ List commentIds = response.items().stream() -+ .map(CommentListResponse::getCommentId) -+ .toList(); -+ -+ if (commentIds.isEmpty()) return response; -+ -+ // QueryDSL 기반 좋아요 ID 조회 (단일 쿼리) -+ List likedIds = commentLikeRepository.findLikedCommentIdsIn(userId, commentIds); -+ Set likedSet = new HashSet<>(likedIds); -+ -+ // likedByMe 세팅 -+ response.items().forEach(c -> c.setLikedByMe(likedSet.contains(c.getCommentId()))); -+ -+ // 자식 댓글에도 동일 적용 -+ response.items().forEach(parent -> { -+ if (parent.getChildren() != null) { -+ parent.getChildren().forEach(child -> -+ child.setLikedByMe(likedSet.contains(child.getCommentId())) -+ ); -+ } -+ }); -+ } -+ -+ return response; -+ } -+ - /** - * 댓글 수정 서비스 - * 1. Post 조회 -diff --git a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java -index b43d199..97c4ca1 100644 ---- a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java -+++ b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java -@@ -160,7 +160,7 @@ public class PostRepositoryImpl implements PostRepositoryCustom { - return queryFactory - .select(new QPostListResponse( - post.id, -- new QAuthorResponse(user.id, profile.nickname), // 작성자 정보 (N+1 방지 join) -+ new QAuthorResponse(user.id, profile.nickname, profile.profileImageUrl), // 작성자 정보 (N+1 방지 join) - post.title, - Expressions.constant(Collections.emptyList()), // categories는 별도 주입 - likeCount, -commit 231d9348c41b7a3fbe5dbc68493bb4011a620810 -Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> -Date: Fri Oct 10 12:45:42 2025 +0900 - - Feat: 게시글 단건 조회 시 좋아요/북마크 여부 추가 - -diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java -index d62b472..7ff99a6 100644 ---- a/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java -+++ b/src/main/java/com/back/domain/board/comment/controller/CommentControllerDocs.java -@@ -539,7 +539,8 @@ public interface CommentControllerDocs { - "parentId": 25, - "author": { - "id": 7, -- "nickname": "이몽룡" -+ "nickname": "이몽룡", -+ "profileImageUrl": null - }, - "content": "저도 동의합니다!", - "createdAt": "2025-09-22T13:30:00", -diff --git a/src/main/java/com/back/domain/board/post/controller/PostController.java b/src/main/java/com/back/domain/board/post/controller/PostController.java -index bf0ffa5..f0a1c67 100644 ---- a/src/main/java/com/back/domain/board/post/controller/PostController.java -+++ b/src/main/java/com/back/domain/board/post/controller/PostController.java -@@ -59,9 +59,12 @@ public class PostController implements PostControllerDocs { - // 게시글 단건 조회 - @GetMapping("/{postId}") - public ResponseEntity> getPost( -- @PathVariable Long postId -+ @PathVariable Long postId, -+ @AuthenticationPrincipal CustomUserDetails user - ) { -- PostDetailResponse response = postService.getPost(postId); -+ PostDetailResponse response = (user != null) -+ ? postService.getPostWithUser(postId, user.getUserId()) -+ : postService.getPost(postId); - return ResponseEntity - .status(HttpStatus.OK) - .body(RsData.success( -diff --git a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java -index c3bc9a4..7d04c57 100644 ---- a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java -+++ b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java -@@ -43,7 +43,8 @@ public interface PostControllerDocs { - "postId": 101, - "author": { - "id": 5, -- "nickname": "홍길동" -+ "nickname": "홍길동", -+ "profileImageUrl": null - }, - "title": "첫 번째 게시글", - "content": "안녕하세요, 첫 글입니다!", -@@ -171,7 +172,7 @@ public interface PostControllerDocs { - "items": [ - { - "postId": 1, -- "author": { "id": 10, "nickname": "홍길동" }, -+ "author": { "id": 10, "nickname": "홍길동", "profileImageUrl": null }, - "title": "첫 글", - "categories": [{ "id": 1, "name": "공지사항" }], - "likeCount": 5, -@@ -247,7 +248,7 @@ public interface PostControllerDocs { - "message": "게시글이 조회되었습니다.", - "data": { - "postId": 101, -- "author": { "id": 5, "nickname": "홍길동" }, -+ "author": { "id": 5, "nickname": "홍길동", "profileImageUrl": null }, - "title": "첫 번째 게시글", - "content": "안녕하세요, 첫 글입니다!", - "categories": [ -@@ -257,6 +258,8 @@ public interface PostControllerDocs { - "likeCount": 10, - "bookmarkCount": 2, - "commentCount": 3, -+ "likedByMe": false, -+ "bookmarkedByMe": false, - "createdAt": "2025-09-22T10:30:00", - "updatedAt": "2025-09-22T10:30:00" - } -@@ -296,7 +299,8 @@ public interface PostControllerDocs { - ) - }) - ResponseEntity> getPost( -- @PathVariable Long postId -+ @PathVariable Long postId, -+ @AuthenticationPrincipal CustomUserDetails user - ); - - @Operation( -@@ -318,7 +322,8 @@ public interface PostControllerDocs { - "postId": 101, - "author": { - "id": 5, -- "nickname": "홍길동" -+ "nickname": "홍길동", -+ "profileImageUrl": null - }, - "title": "수정된 게시글", - "content": "안녕하세요, 수정했습니다!", -diff --git a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java -index e14b124..d61fe9a 100644 ---- a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java -+++ b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java -@@ -29,10 +29,16 @@ public record PostDetailResponse( - long likeCount, - long bookmarkCount, - long commentCount, -+ Boolean likedByMe, -+ Boolean bookmarkedByMe, - LocalDateTime createdAt, - LocalDateTime updatedAt - ) { - public static PostDetailResponse from(Post post) { -+ return from(post, false, false); -+ } -+ -+ public static PostDetailResponse from(Post post, boolean likedByMe, boolean bookmarkedByMe) { - return new PostDetailResponse( - post.getId(), - AuthorResponse.from(post.getUser()), -@@ -44,6 +50,8 @@ public record PostDetailResponse( - post.getPostLikes().size(), - post.getPostBookmarks().size(), - post.getComments().size(), -+ likedByMe, -+ bookmarkedByMe, - post.getCreatedAt(), - post.getUpdatedAt() - ); -diff --git a/src/main/java/com/back/domain/board/post/service/PostService.java b/src/main/java/com/back/domain/board/post/service/PostService.java -index 995e37f..17086a0 100644 ---- a/src/main/java/com/back/domain/board/post/service/PostService.java -+++ b/src/main/java/com/back/domain/board/post/service/PostService.java -@@ -7,7 +7,9 @@ import com.back.domain.board.post.dto.PostDetailResponse; - import com.back.domain.board.post.dto.PostListResponse; - import com.back.domain.board.post.dto.PostRequest; - import com.back.domain.board.post.dto.PostResponse; -+import com.back.domain.board.post.repository.PostBookmarkRepository; - import com.back.domain.board.post.repository.PostCategoryRepository; -+import com.back.domain.board.post.repository.PostLikeRepository; - import com.back.domain.board.post.repository.PostRepository; - import com.back.domain.user.entity.User; - import com.back.domain.user.repository.UserRepository; -@@ -26,6 +28,8 @@ import java.util.List; - @Transactional - public class PostService { - private final PostRepository postRepository; -+ private final PostLikeRepository postLikeRepository; -+ private final PostBookmarkRepository postBookmarkRepository; - private final UserRepository userRepository; - private final PostCategoryRepository postCategoryRepository; - -@@ -85,6 +89,18 @@ public class PostService { - return PostDetailResponse.from(post); - } - -+ // TODO: 로그인 회원용 게시글 단건 조회 서비스, 추후 리팩토링 필요 -+ @Transactional(readOnly = true) -+ public PostDetailResponse getPostWithUser(Long postId, Long userId) { -+ Post post = postRepository.findById(postId) -+ .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); -+ -+ boolean likedByMe = postLikeRepository.existsByUserIdAndPostId(userId, postId); -+ boolean bookmarkedByMe = postBookmarkRepository.existsByUserIdAndPostId(userId, postId); -+ -+ return PostDetailResponse.from(post, likedByMe, bookmarkedByMe); -+ } -+ - /** - * 게시글 수정 서비스 - * 1. Post 조회 -commit e2659bb21019551e17490a6873ed7b184721f4a8 -Author: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> -Date: Fri Oct 10 14:54:23 2025 +0900 - - Feat: Post thumbnailUrl 필드 추가 - -diff --git a/src/main/java/com/back/domain/board/post/dto/PostListResponse.java b/src/main/java/com/back/domain/board/post/dto/PostListResponse.java -index 9545bcc..20a5779 100644 ---- a/src/main/java/com/back/domain/board/post/dto/PostListResponse.java -+++ b/src/main/java/com/back/domain/board/post/dto/PostListResponse.java -@@ -16,19 +16,22 @@ public class PostListResponse { - private final Long postId; - private final AuthorResponse author; - private final String title; -+ private final String thumbnailUrl; -+ -+ @Setter -+ private List categories; -+ - private final long likeCount; - private final long bookmarkCount; - private final long commentCount; - private final LocalDateTime createdAt; - private final LocalDateTime updatedAt; - -- @Setter -- private List categories; -- - @QueryProjection - public PostListResponse(Long postId, - AuthorResponse author, - String title, -+ String thumbnailUrl, - List categories, - long likeCount, - long bookmarkCount, -@@ -38,6 +41,7 @@ public class PostListResponse { - this.postId = postId; - this.author = author; - this.title = title; -+ this.thumbnailUrl = thumbnailUrl; - this.categories = categories; - this.likeCount = likeCount; - this.bookmarkCount = bookmarkCount; -diff --git a/src/main/java/com/back/domain/board/post/dto/PostRequest.java b/src/main/java/com/back/domain/board/post/dto/PostRequest.java -index 5deef45..12e24a9 100644 ---- a/src/main/java/com/back/domain/board/post/dto/PostRequest.java -+++ b/src/main/java/com/back/domain/board/post/dto/PostRequest.java -@@ -9,10 +9,12 @@ import java.util.List; - * - * @param title 게시글 제목 - * @param content 게시글 내용 -+ * @param thumbnailUrl 썸네일 URL - * @param categoryIds 카테고리 ID 리스트 - */ - public record PostRequest( - @NotBlank String title, - @NotBlank String content, -+ String thumbnailUrl, - List categoryIds - ) {} -\ No newline at end of file -diff --git a/src/main/java/com/back/domain/board/post/entity/Post.java b/src/main/java/com/back/domain/board/post/entity/Post.java -index e34945e..743101a 100644 ---- a/src/main/java/com/back/domain/board/post/entity/Post.java -+++ b/src/main/java/com/back/domain/board/post/entity/Post.java -@@ -9,6 +9,8 @@ import lombok.NoArgsConstructor; - - import java.util.ArrayList; - import java.util.List; -+import java.util.regex.Matcher; -+import java.util.regex.Pattern; - - @Entity - @Getter -@@ -22,6 +24,9 @@ public class Post extends BaseEntity { - - private String content; - -+ @Column(length = 500) -+ private String thumbnailUrl; -+ - // TODO: 추후 PostRepositoryImpl#searchPosts 로직 개선 필요, ERD에도 반영할 것 - @Column(nullable = false) - private Long likeCount = 0L; -@@ -49,6 +54,14 @@ public class Post extends BaseEntity { - this.user = user; - this.title = title; - this.content = content; -+ this.thumbnailUrl = null; -+ } -+ -+ public Post(User user, String title, String content, String thumbnailUrl) { -+ this.user = user; -+ this.title = title; -+ this.content = content; -+ this.thumbnailUrl = thumbnailUrl; - } - - // -------------------- 비즈니스 메서드 -------------------- -diff --git a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java -index 97c4ca1..53806ff 100644 ---- a/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java -+++ b/src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java -@@ -162,6 +162,7 @@ public class PostRepositoryImpl implements PostRepositoryCustom { - post.id, - new QAuthorResponse(user.id, profile.nickname, profile.profileImageUrl), // 작성자 정보 (N+1 방지 join) - post.title, -+ post.thumbnailUrl, - Expressions.constant(Collections.emptyList()), // categories는 별도 주입 - likeCount, - bookmarkCount, -diff --git a/src/main/java/com/back/domain/board/post/service/PostService.java b/src/main/java/com/back/domain/board/post/service/PostService.java -index 17086a0..b53a88d 100644 ---- a/src/main/java/com/back/domain/board/post/service/PostService.java -+++ b/src/main/java/com/back/domain/board/post/service/PostService.java -@@ -47,7 +47,7 @@ public class PostService { - .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); - - // Post 생성 -- Post post = new Post(user, request.title(), request.content()); -+ Post post = new Post(user, request.title(), request.content(), request.thumbnailUrl()); - - // Category 매핑 - if (request.categoryIds() != null) { diff --git a/src/test/java/com/back/domain/board/controller/PostControllerTest.java b/src/test/java/com/back/domain/board/controller/PostControllerTest.java index 66b622e5..a68ab208 100644 --- a/src/test/java/com/back/domain/board/controller/PostControllerTest.java +++ b/src/test/java/com/back/domain/board/controller/PostControllerTest.java @@ -81,7 +81,7 @@ void createPost_success() throws Exception { PostCategory c2 = new PostCategory("자유게시판"); postCategoryRepository.save(c2); - PostRequest request = new PostRequest("첫 번째 게시글", "안녕하세요, 첫 글입니다!", List.of(c1.getId(), c2.getId())); + PostRequest request = new PostRequest("첫 번째 게시글", "안녕하세요, 첫 글입니다!", null, List.of(c1.getId(), c2.getId())); // when ResultActions resultActions = mvc.perform( @@ -107,7 +107,7 @@ void createPost_userNotFound() throws Exception { // given: 토큰만 발급(실제 DB엔 없음) String fakeToken = testJwtTokenProvider.createAccessToken(999L, "ghost", "USER"); - PostRequest request = new PostRequest("제목", "내용", null); + PostRequest request = new PostRequest("제목", "내용", null, null); // when & then mvc.perform(post("/api/posts") @@ -132,7 +132,7 @@ void createPost_categoryNotFound() throws Exception { String accessToken = generateAccessToken(user); // 존재하지 않는 카테고리 ID - PostRequest request = new PostRequest("제목", "내용", List.of(999L)); + PostRequest request = new PostRequest("제목", "내용", null, List.of(999L)); // when & then mvc.perform(post("/api/posts") @@ -178,7 +178,7 @@ void createPost_badRequest() throws Exception { @DisplayName("게시글 생성 실패 - 토큰 없음 → 401 Unauthorized") void createPost_noToken() throws Exception { // given - PostRequest request = new PostRequest("제목", "내용", null); + PostRequest request = new PostRequest("제목", "내용", null, null); // when & then mvc.perform(post("/api/posts") @@ -291,7 +291,7 @@ void updatePost_success() throws Exception { PostCategory c2 = new PostCategory("자유게시판"); postCategoryRepository.save(c2); - PostRequest request = new PostRequest("수정된 게시글", "안녕하세요, 수정했습니다!", List.of(c1.getId(), c2.getId())); + PostRequest request = new PostRequest("수정된 게시글", "안녕하세요, 수정했습니다!", null, List.of(c1.getId(), c2.getId())); // when & then mvc.perform(put("/api/posts/{postId}", post.getId()) @@ -317,7 +317,7 @@ void updatePost_fail_notFound() throws Exception { String accessToken = generateAccessToken(user); - PostRequest request = new PostRequest("수정된 제목", "내용", List.of()); + PostRequest request = new PostRequest("수정된 제목", "내용", null, List.of()); // when & then mvc.perform(put("/api/posts/{postId}", 999L) @@ -353,7 +353,7 @@ void updatePost_fail_noPermission() throws Exception { String accessToken = generateAccessToken(another); - PostRequest request = new PostRequest("수정된 제목", "수정된 내용", List.of(c1.getId())); + PostRequest request = new PostRequest("수정된 제목", "수정된 내용", null, List.of(c1.getId())); // when & then mvc.perform(put("/api/posts/{postId}", post.getId()) @@ -385,7 +385,7 @@ void updatePost_fail_categoryNotFound() throws Exception { String accessToken = generateAccessToken(user); // 존재하지 않는 카테고리 ID - PostRequest request = new PostRequest("수정된 제목", "수정된 내용", List.of(999L)); + PostRequest request = new PostRequest("수정된 제목", "수정된 내용", null, List.of(999L)); // when & then mvc.perform(put("/api/posts/{postId}", post.getId()) @@ -430,7 +430,7 @@ void updatePost_fail_badRequest() throws Exception { @DisplayName("게시글 수정 실패 - 인증 없음 → 401 Unauthorized") void updatePost_fail_unauthorized() throws Exception { // given - PostRequest request = new PostRequest("제목", "내용", List.of()); + PostRequest request = new PostRequest("제목", "내용", null, List.of()); // when & then mvc.perform(put("/api/posts/{postId}", 1L) diff --git a/src/test/java/com/back/domain/board/service/PostServiceTest.java b/src/test/java/com/back/domain/board/service/PostServiceTest.java index 512ddb30..db8ef620 100644 --- a/src/test/java/com/back/domain/board/service/PostServiceTest.java +++ b/src/test/java/com/back/domain/board/service/PostServiceTest.java @@ -61,7 +61,7 @@ void createPost_success_withCategories() { PostCategory category = new PostCategory("공지"); postCategoryRepository.save(category); - PostRequest request = new PostRequest("제목", "내용", List.of(category.getId())); + PostRequest request = new PostRequest("제목", "내용", null, List.of(category.getId())); // when PostResponse response = postService.createPost(request, user.getId()); @@ -78,7 +78,7 @@ void createPost_success_withCategories() { @DisplayName("게시글 생성 실패 - 존재하지 않는 유저") void createPost_fail_userNotFound() { // given - PostRequest request = new PostRequest("제목", "내용", null); + PostRequest request = new PostRequest("제목", "내용", null, null); // when & then assertThatThrownBy(() -> postService.createPost(request, 999L)) @@ -96,7 +96,7 @@ void createPost_fail_categoryNotFound() { userRepository.save(user); // 실제 저장 안 된 카테고리 ID 요청 - PostRequest request = new PostRequest("제목", "내용", List.of(100L, 200L)); + PostRequest request = new PostRequest("제목", "내용", null, List.of(100L, 200L)); // when & then assertThatThrownBy(() -> postService.createPost(request, user.getId())) @@ -197,7 +197,7 @@ void updatePost_success() { post.updateCategories(List.of(oldCategory)); postRepository.save(post); - PostRequest request = new PostRequest("수정된 제목", "수정된 내용", List.of(newCategory.getId())); + PostRequest request = new PostRequest("수정된 제목", "수정된 내용", null, List.of(newCategory.getId())); // when PostResponse response = postService.updatePost(post.getId(), request, user.getId()); @@ -218,7 +218,7 @@ void updatePost_fail_postNotFound() { user.setUserStatus(UserStatus.ACTIVE); userRepository.save(user); - PostRequest request = new PostRequest("제목", "내용", List.of()); + PostRequest request = new PostRequest("제목", "내용", null, List.of()); // when & then assertThatThrownBy(() -> postService.updatePost(999L, request, user.getId())) @@ -248,7 +248,7 @@ void updatePost_fail_noPermission() { post.updateCategories(List.of(category)); postRepository.save(post); - PostRequest request = new PostRequest("수정된 제목", "수정된 내용", List.of(category.getId())); + PostRequest request = new PostRequest("수정된 제목", "수정된 내용", null, List.of(category.getId())); // when & then assertThatThrownBy(() -> postService.updatePost(post.getId(), request, another.getId())) @@ -273,7 +273,7 @@ void updatePost_fail_categoryNotFound() { postRepository.save(post); // 실제 DB에는 없는 카테고리 ID 전달 - PostRequest request = new PostRequest("수정된 제목", "수정된 내용", List.of(999L)); + PostRequest request = new PostRequest("수정된 제목", "수정된 내용", null, List.of(999L)); // when & then assertThatThrownBy(() -> postService.updatePost(post.getId(), request, user.getId())) From 5804091bc55e3b4105b02dde2e290282c2367945 Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:28:05 +0900 Subject: [PATCH 7/7] =?UTF-8?q?Feat:=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20DTO?= =?UTF-8?q?=20=EB=B0=8F=20Swagger=20=EB=AC=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../back/domain/board/post/controller/PostControllerDocs.java | 4 ++++ .../com/back/domain/board/post/dto/PostDetailResponse.java | 3 +++ .../java/com/back/domain/board/post/dto/PostResponse.java | 3 +++ 3 files changed, 10 insertions(+) diff --git a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java index 7d04c572..42e99858 100644 --- a/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java +++ b/src/main/java/com/back/domain/board/post/controller/PostControllerDocs.java @@ -48,6 +48,7 @@ public interface PostControllerDocs { }, "title": "첫 번째 게시글", "content": "안녕하세요, 첫 글입니다!", + "thumbnailUrl": null, "categories": [ { "id": 1, "name": "공지사항" }, { "id": 2, "name": "자유게시판" } @@ -174,6 +175,7 @@ ResponseEntity> createPost( "postId": 1, "author": { "id": 10, "nickname": "홍길동", "profileImageUrl": null }, "title": "첫 글", + "thumbnailUrl": null, "categories": [{ "id": 1, "name": "공지사항" }], "likeCount": 5, "bookmarkCount": 2, @@ -251,6 +253,7 @@ ResponseEntity>> getPosts( "author": { "id": 5, "nickname": "홍길동", "profileImageUrl": null }, "title": "첫 번째 게시글", "content": "안녕하세요, 첫 글입니다!", + "thumbnailUrl": null, "categories": [ { "id": 1, "name": "공지사항" }, { "id": 2, "name": "자유게시판" } @@ -327,6 +330,7 @@ ResponseEntity> getPost( }, "title": "수정된 게시글", "content": "안녕하세요, 수정했습니다!", + "thumbnailUrl": null, "categories": [ { "id": 1, "name": "공지사항" }, { "id": 2, "name": "자유게시판" } diff --git a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java index d61fe9a8..ac8701fe 100644 --- a/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java +++ b/src/main/java/com/back/domain/board/post/dto/PostDetailResponse.java @@ -13,6 +13,7 @@ * @param author 작성자 정보 * @param title 게시글 제목 * @param content 게시글 내용 + * @param thumbnailUrl 썸네일 URL * @param categories 게시글 카테고리 목록 * @param likeCount 좋아요 수 * @param bookmarkCount 북마크 수 @@ -25,6 +26,7 @@ public record PostDetailResponse( AuthorResponse author, String title, String content, + String thumbnailUrl, List categories, long likeCount, long bookmarkCount, @@ -44,6 +46,7 @@ public static PostDetailResponse from(Post post, boolean likedByMe, boolean book AuthorResponse.from(post.getUser()), post.getTitle(), post.getContent(), + post.getThumbnailUrl(), post.getCategories().stream() .map(CategoryResponse::from) .toList(), diff --git a/src/main/java/com/back/domain/board/post/dto/PostResponse.java b/src/main/java/com/back/domain/board/post/dto/PostResponse.java index 70d405c6..484aafcb 100644 --- a/src/main/java/com/back/domain/board/post/dto/PostResponse.java +++ b/src/main/java/com/back/domain/board/post/dto/PostResponse.java @@ -12,6 +12,7 @@ * @param author 작성자 정보 * @param title 게시글 제목 * @param content 게시글 내용 + * @param thumbnailUrl 썸네일 URL * @param categories 게시글 카테고리 목록 * @param createdAt 게시글 생성 일시 * @param updatedAt 게시글 수정 일시 @@ -21,6 +22,7 @@ public record PostResponse( AuthorResponse author, String title, String content, + String thumbnailUrl, List categories, LocalDateTime createdAt, LocalDateTime updatedAt @@ -31,6 +33,7 @@ public static PostResponse from(Post post) { AuthorResponse.from(post.getUser()), post.getTitle(), post.getContent(), + post.getThumbnailUrl(), post.getCategories().stream() .map(CategoryResponse::from) .toList(),