diff --git a/src/main/java/com/example/spot/common/scheduler/PostSortCommentsScheduler.java b/src/main/java/com/example/spot/common/scheduler/PostSortCommentsScheduler.java index ceda8c5d..50884a4c 100644 --- a/src/main/java/com/example/spot/common/scheduler/PostSortCommentsScheduler.java +++ b/src/main/java/com/example/spot/common/scheduler/PostSortCommentsScheduler.java @@ -3,15 +3,14 @@ import com.example.spot.post.domain.Post; import com.example.spot.post.domain.schedule.PostScheduleComments; import com.example.spot.post.infrastructure.jpa.PostRepository; -import com.example.spot.post.infrastructure.jpa.PostScheduleCommentsRepository; +import com.example.spot.post.infrastructure.jpa.schedule.PostScheduleCommentsRepository; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; - @Service @RequiredArgsConstructor public class PostSortCommentsScheduler { diff --git a/src/main/java/com/example/spot/common/scheduler/PostSortLikesScheduler.java b/src/main/java/com/example/spot/common/scheduler/PostSortLikesScheduler.java index 2699e463..d1fede10 100644 --- a/src/main/java/com/example/spot/common/scheduler/PostSortLikesScheduler.java +++ b/src/main/java/com/example/spot/common/scheduler/PostSortLikesScheduler.java @@ -3,15 +3,14 @@ import com.example.spot.post.domain.Post; import com.example.spot.post.domain.schedule.PostScheduleLikes; import com.example.spot.post.infrastructure.jpa.PostRepository; -import com.example.spot.post.infrastructure.jpa.PostScheduleLikesRepository; +import com.example.spot.post.infrastructure.jpa.schedule.PostScheduleLikesRepository; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; - @Service @RequiredArgsConstructor public class PostSortLikesScheduler { diff --git a/src/main/java/com/example/spot/common/scheduler/PostSortRealTimeScheduler.java b/src/main/java/com/example/spot/common/scheduler/PostSortRealTimeScheduler.java index 7ba2a8b1..3d38fc0a 100644 --- a/src/main/java/com/example/spot/common/scheduler/PostSortRealTimeScheduler.java +++ b/src/main/java/com/example/spot/common/scheduler/PostSortRealTimeScheduler.java @@ -3,15 +3,14 @@ import com.example.spot.post.domain.Post; import com.example.spot.post.domain.schedule.PostScheduleRealTime; import com.example.spot.post.infrastructure.jpa.PostRepository; -import com.example.spot.post.infrastructure.jpa.PostScheduleRealTimeRepository; +import com.example.spot.post.infrastructure.jpa.schedule.PostScheduleRealTimeRepository; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; - @Service @RequiredArgsConstructor public class PostSortRealTimeScheduler { diff --git a/src/main/java/com/example/spot/post/application/command/ScrapPostUseCase.java b/src/main/java/com/example/spot/post/application/command/ScrapPostUseCase.java index adfdf32f..73ef1420 100644 --- a/src/main/java/com/example/spot/post/application/command/ScrapPostUseCase.java +++ b/src/main/java/com/example/spot/post/application/command/ScrapPostUseCase.java @@ -13,5 +13,5 @@ public interface ScrapPostUseCase { ScrapPostResponse cancelPostScrap(Long postId, Long memberId); //게시글 스크랩 모두 취소 - ScrapsPostDeleteResponse cancelPostScraps(ScrapAllDeleteRequest request); + ScrapsPostDeleteResponse cancelPostScraps(ScrapAllDeleteRequest request, Long memberId); } diff --git a/src/main/java/com/example/spot/post/application/command/impl/LikePostUseCaseImpl.java b/src/main/java/com/example/spot/post/application/command/impl/LikePostUseCaseImpl.java index e9fb2e56..20af4626 100644 --- a/src/main/java/com/example/spot/post/application/command/impl/LikePostUseCaseImpl.java +++ b/src/main/java/com/example/spot/post/application/command/impl/LikePostUseCaseImpl.java @@ -6,13 +6,14 @@ import com.example.spot.member.domain.Member; import com.example.spot.member.infrastructure.jpa.MemberRepository; import com.example.spot.post.application.command.LikePostUseCase; -import com.example.spot.post.application.query.GetLikedPostUseCase; import com.example.spot.post.domain.Post; -import com.example.spot.post.infrastructure.jpa.PostRepository; import com.example.spot.post.domain.association.LikedPost; import com.example.spot.post.infrastructure.jpa.LikedPostRepository; +import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.infrastructure.jpa.PostStatsRepository; import com.example.spot.post.presentation.dto.response.post.PostLikeResponse; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,8 +25,7 @@ public class LikePostUseCaseImpl implements LikePostUseCase { private final MemberRepository memberRepository; private final PostRepository postRepository; private final LikedPostRepository likedPostRepository; - - private final GetLikedPostUseCase getLikedPostUseCase; + private final PostStatsRepository postStatsRepository; /** * 게시글에 좋아요를 합니다. @@ -40,33 +40,15 @@ public class LikePostUseCaseImpl implements LikePostUseCase { @Transactional @Override public PostLikeResponse likePost(Long postId, Long memberId) { - // 회원 정보 가져오기 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - // 게시글 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); - //좋아요 여부 확인 - if (likedPostRepository.findByMemberIdAndPostId(memberId, postId).isPresent()) { - throw new PostHandler(ErrorStatus._POST_ALREADY_LIKED); - } - - // 좋아요 객체 생성 및 저장 - LikedPost likedPost = LikedPost.builder() - .post(post) - .member(member) - .build(); + ensurePostAndMemberExist(postId, memberId); - likedPostRepository.saveAndFlush(likedPost); + // 회원 정보 가져오기 + Member memberRef = requireMemberRef(memberId); + Post postRef = requirePostRef(postId); - // 게시글의 현재 좋아요 수 조회 - long likeCount = getLikedPostUseCase.countByPostId(postId); + saveLikePostAndIncreaseLikeCount(postId, postRef, memberRef); - // 좋아요 결과 반환 - return PostLikeResponse.builder() - .postId(post.getId()) - .likeCount(likeCount) - .build(); + return getPostLikeResponse(postId, postRef); } /** @@ -82,26 +64,66 @@ public PostLikeResponse likePost(Long postId, Long memberId) { @Transactional @Override public PostLikeResponse cancelPostLike(Long postId, Long memberId) { - // 회원 정보 가져오기 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - // 게시글 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); - // 좋아요 여부 확인 - LikedPost likedPost = likedPostRepository.findByMemberIdAndPostId(member.getId(), post.getId()) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_LIKED)); - - // 좋아요 객체 삭제 - likedPostRepository.delete(likedPost); - likedPostRepository.flush(); + ensurePostAndMemberExist(postId, memberId); + Post postRef = requirePostRef(postId); + + deleteLikePostAndDecreaseLikeCount(postId, memberId); // 게시글의 현재 좋아요 수 조회 - long likeCount = getLikedPostUseCase.countByPostId(postId); + return getPostLikeResponse(postId, postRef); + } + + /* ------------------------------- private method ------------------------------------------ */ - // 좋아요 취소 결과 반환 + private void ensurePostAndMemberExist(Long postId, Long memberId) { + // 게시글 존재 여부 확인 + if (!postRepository.existsById(postId)) { + throw new PostHandler(ErrorStatus._POST_NOT_FOUND); + } + + // 회원 존재 여부 확인 + if (!memberRepository.existsById(memberId)) { + throw new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND); + } + } + + private Member requireMemberRef(Long memberId) { + return memberRepository.getReferenceById(memberId); + } + + private Post requirePostRef(Long postId) { + return postRepository.getReferenceById(postId); + } + + private void saveLikePostAndIncreaseLikeCount(Long postId, Post postRef, Member memberRef) { + try { + // 1) 좋아요 행 삽입 시도 + likedPostRepository.save( + LikedPost.builder() + .post(postRef) + .member(memberRef) + .build() + ); + + // 2) 삽입 성공한 경우에만 카운터 +1 + postStatsRepository.incrementLike(postId); + } catch (DataIntegrityViolationException e) { + throw new PostHandler(ErrorStatus._POST_ALREADY_LIKED); + } + } + + private void deleteLikePostAndDecreaseLikeCount(Long postId, Long memberId) { + int effectedRow = likedPostRepository.deleteByMemberIdAndPostId(memberId, postId); + if (effectedRow == 1) { + postStatsRepository.decrementLike(postId); + } + } + + private PostLikeResponse getPostLikeResponse(Long postId, Post postRef) { + long likeCount = postStatsRepository.getLikeCount(postId); + // 좋아요 결과 반환 return PostLikeResponse.builder() - .postId(post.getId()) + .postId(postRef.getId()) .likeCount(likeCount) .build(); } diff --git a/src/main/java/com/example/spot/post/application/command/impl/ManagePostCommentUseCaseImpl.java b/src/main/java/com/example/spot/post/application/command/impl/ManagePostCommentUseCaseImpl.java index 99cb039a..a8440a84 100644 --- a/src/main/java/com/example/spot/post/application/command/impl/ManagePostCommentUseCaseImpl.java +++ b/src/main/java/com/example/spot/post/application/command/impl/ManagePostCommentUseCaseImpl.java @@ -1,9 +1,5 @@ package com.example.spot.post.application.command.impl; -import com.example.spot.post.domain.PostComment; -import com.example.spot.post.infrastructure.jpa.PostCommentRepository; -import com.example.spot.post.presentation.dto.request.comment.CommentCreateRequest; -import com.example.spot.post.presentation.dto.response.comment.CommentCreateResponse; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.PostHandler; @@ -11,7 +7,12 @@ import com.example.spot.member.infrastructure.jpa.MemberRepository; import com.example.spot.post.application.command.ManagePostCommentUseCase; import com.example.spot.post.domain.Post; +import com.example.spot.post.domain.PostComment; +import com.example.spot.post.infrastructure.jpa.PostCommentRepository; import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.infrastructure.jpa.PostStatsRepository; +import com.example.spot.post.presentation.dto.request.comment.CommentCreateRequest; +import com.example.spot.post.presentation.dto.response.comment.CommentCreateResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +25,7 @@ public class ManagePostCommentUseCaseImpl implements ManagePostCommentUseCase { private final PostRepository postRepository; private final MemberRepository memberRepository; private final PostCommentRepository postCommentRepository; + private final PostStatsRepository postStatsRepository; /** * 게시글에 댓글을 생성합니다. @@ -38,29 +40,47 @@ public class ManagePostCommentUseCaseImpl implements ManagePostCommentUseCase { @Transactional @Override public CommentCreateResponse createComment(Long postId, Long memberId, CommentCreateRequest request) { - // 게시글 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); - - // 회원 정보 가져오기 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + // 게시글과 회원 존재 여부 확인 및 참조 가져오기 + ensurePostAndMemberExist(postId, memberId); + Post post = requirePostRef(postId); + Member member = requireMemberRef(memberId); - // 부모 댓글 생성 + // 댓글 생성 및 저장 PostComment comment = PostComment.builder() .content(request.getContent()) .isAnonymous(request.isAnonymous()) .post(post) - .parentComment(null) .member(member) .build(); + PostComment saved = savePostCommentAndIncreaseCommentCount(postId, comment); - // 댓글 객체 저장 - comment = postCommentRepository.saveAndFlush(comment); - post.addComment(comment); - post.plusCommentNum(); + return CommentCreateResponse.toDTO(saved); + } + + /* ------------------------------- private method ------------------------------------------ */ + + private void ensurePostAndMemberExist(Long postId, Long memberId) { + // 게시글 존재 여부 확인 + if (!postRepository.existsById(postId)) { + throw new PostHandler(ErrorStatus._POST_NOT_FOUND); + } + + // 회원 존재 여부 확인 + if (!memberRepository.existsById(memberId)) { + throw new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND); + } + } + + private Member requireMemberRef(Long memberId) { + return memberRepository.getReferenceById(memberId); + } + + private Post requirePostRef(Long postId) { + return postRepository.getReferenceById(postId); + } - // 생성된 댓글 정보 반환 - return CommentCreateResponse.toDTO(comment); + private PostComment savePostCommentAndIncreaseCommentCount(Long postId, PostComment comment) { + postStatsRepository.incrementComment(postId); + return postCommentRepository.save(comment); } } diff --git a/src/main/java/com/example/spot/post/application/command/impl/ManagePostUseCaseImpl.java b/src/main/java/com/example/spot/post/application/command/impl/ManagePostUseCaseImpl.java index 4302cfed..4019f77d 100644 --- a/src/main/java/com/example/spot/post/application/command/impl/ManagePostUseCaseImpl.java +++ b/src/main/java/com/example/spot/post/application/command/impl/ManagePostUseCaseImpl.java @@ -10,8 +10,10 @@ import com.example.spot.member.infrastructure.jpa.MemberRepository; import com.example.spot.post.application.command.ManagePostUseCase; import com.example.spot.post.domain.Post; -import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.domain.PostStats; import com.example.spot.post.domain.enums.Board; +import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.infrastructure.jpa.PostStatsRepository; import com.example.spot.post.presentation.dto.request.post.PostCreateRequest; import com.example.spot.post.presentation.dto.request.post.PostUpdateRequest; import com.example.spot.post.presentation.dto.response.post.PostCreateResponse; @@ -28,146 +30,102 @@ public class ManagePostUseCaseImpl implements ManagePostUseCase { private final MemberRepository memberRepository; private final PostRepository postRepository; - + private final PostStatsRepository postStatsRepository; + private final S3ImageService s3ImageService; - /** - * 게시글을 생성합니다. - * - * @param memberId 게시글을 작성하는 회원 ID - * @param postCreateRequest 생성할 게시글 정보 - * @return 생성된 게시글 정보 - * @throws MemberHandler 회원을 찾을 수 없는 경우 - * @throws PostHandler 관리자 권한이 없는 경우 (관리자만 공지글 작성 가능) - */ - @Transactional @Override - public PostCreateResponse createPost(Long memberId, PostCreateRequest postCreateRequest) { + public PostCreateResponse createPost(Long memberId, PostCreateRequest req) { + Member member = requireMemberRef(memberId); + ensureAnnouncementPermission(req.getType(), member); - // 회원 정보 가져오기 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + List imageUrls = uploadImageIfPresent(req.getImage()); - // 공지"SPOT_ANNOUNCEMENT" 게시글은 관리자만 생성 가능 - if (postCreateRequest.getType() == Board.SPOT_ANNOUNCEMENT && !member.getIsAdmin()) { - throw new PostHandler(ErrorStatus._FORBIDDEN); // 관리자만 접근 가능 - } + Post post = postRepository.save(buildPost(req, member, imageUrls)); + postStatsRepository.save( + PostStats.builder() + .post(post) + .likeCount(0L) + .commentCount(0L) + .build() + ); + + return PostCreateResponse.toDTO(post); + } - List imageUrls = getImageUrls(postCreateRequest.getImage()); + @Override + public PostCreateResponse updatePost(Long memberId, Long postId, PostUpdateRequest req) { + ensurePostAndMemberExist(postId, memberId); - // Post 객체 생성 및 연관 관계 설정 - Post post = createPostEntity(postCreateRequest, member, imageUrls); + Member member = requireMemberRef(memberId); + Post post = requirePostRef(postId); - // 게시글 저장 - post = postRepository.save(post); + ensurePostAuthor(post, member); + ensureAnnouncementPermission(req.getType(), member); - // 게시글 생성 정보 반환 + post.edit(req, uploadImageIfPresent(req.getImage()), req.getExistingImage()); return PostCreateResponse.toDTO(post); } - private List getImageUrls(MultipartFile imageFile) { - if (imageFile != null) { - ImageUploadResponse imageUploadResponse = s3ImageService.uploadImages(List.of(imageFile)); + @Override + public void deletePost(Long memberId, Long postId) { + ensurePostAndMemberExist(postId, memberId); + + Member member = requireMemberRef(memberId); + Post post = requirePostRef(postId); - return imageUploadResponse.getImageUrls().stream() - .map(Images::getImageUrl) - .toList(); - } - return List.of(); + ensurePostAuthor(post, member); + postRepository.delete(post); } - /** - * 게시글 객체를 생성합니다. - * - * @param postCreateRequest 생성할 게시글 정보 - * @param currentMember 게시글을 작성하는 회원 정보 - * @return 생성된 게시글 객체 - */ - private Post createPostEntity(PostCreateRequest postCreateRequest, Member currentMember, List images) { - String image = (images != null && !images.isEmpty() && !images.get(0).isEmpty()) - ? images.get(0) - : null; + /* ------------------------------- helpers ------------------------------------------ */ - return Post.builder() - .isAnonymous(postCreateRequest.isAnonymous()) - .title(postCreateRequest.getTitle()) - .content(postCreateRequest.getContent()) - .scrapNum(0) - .image(image) - .commentNum(0) - .hitNum(0) - .board(postCreateRequest.getType()) - .member(currentMember) - .build(); + private void ensurePostAndMemberExist(Long postId, Long memberId) { + if (!postRepository.existsById(postId)) { + throw new PostHandler(ErrorStatus._POST_NOT_FOUND); + } + if (!memberRepository.existsById(memberId)) { + throw new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND); + } } - /** - * 게시글을 수정합니다. - * - * @param memberId 게시글을 수정하는 회원 ID - * @param postId 변경할 게시글 ID - * @param postUpdateRequest 수정할 게시글 정보 - * @return 수정된 게시글 정보 - * @throws MemberHandler 회원을 찾을 수 없는 경우 - * @throws PostHandler 게시글을 찾을 수 없는 경우 - * @throws PostHandler 현재 수정하는 회원과 게시글 작성자가 일치하지 않을 경우 - * @throws PostHandler 관리자 권한이 없는 경우 (관리자만 공지글 수정 가능) - */ - @Transactional - @Override - public PostCreateResponse updatePost(Long memberId, Long postId, PostUpdateRequest postUpdateRequest) { + private Member requireMemberRef(Long memberId) { + return memberRepository.getReferenceById(memberId); + } - // 회원 정보 가져오기 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + private Post requirePostRef(Long postId) { + return postRepository.getReferenceById(postId); + } - // 게시글 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); + private void ensureAnnouncementPermission(Board board, Member member) { + if (board == Board.SPOT_ANNOUNCEMENT && !member.getIsAdmin()) { + throw new PostHandler(ErrorStatus._FORBIDDEN); + } + } - // 현재 멤버와 게시글 작성자 일치 여부 확인 + private void ensurePostAuthor(Post post, Member member) { if (!post.getMember().getId().equals(member.getId())) { throw new PostHandler(ErrorStatus._POST_NOT_AUTHOR); } + } - // 공지"SPOT_ANNOUNCEMENT" 게시글은 관리자만 가능 - if (postUpdateRequest.getType() == Board.SPOT_ANNOUNCEMENT && !member.getIsAdmin()) { - throw new PostHandler(ErrorStatus._FORBIDDEN); + private List uploadImageIfPresent(MultipartFile imageFile) { + if (imageFile == null) { + return List.of(); } - - // 게시글 수정 - post.edit(postUpdateRequest, getImageUrls(postUpdateRequest.getImage()), postUpdateRequest.getExistingImage()); - - // 수정된 게시글 정보 반환 - return PostCreateResponse.toDTO(post); + ImageUploadResponse res = s3ImageService.uploadImages(List.of(imageFile)); + return res.getImageUrls().stream().map(Images::getImageUrl).toList(); } - /** - * 게시글을 삭제합니다. - * - * @param memberId 게시글을 수정하는 회원 ID - * @param postId 변경할 게시글 ID - * @throws MemberHandler 회원을 찾을 수 없는 경우 - * @throws PostHandler 게시글을 찾을 수 없는 경우 - * @throws PostHandler 현재 삭제하는 회원과 게시글 작성자가 일치하지 않을 경우 - */ - @Transactional - @Override - public void deletePost(Long memberId, Long postId) { - - // 회원 정보 가져오기 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - // 게시글 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); - - // 현재 멤버와 게시글 작성자 일치 여부 확인 - if (!post.getMember().getId().equals(member.getId())) { - throw new PostHandler(ErrorStatus._POST_NOT_AUTHOR); // 권한 없음을 나타내는 에러 처리 - } - // 게시글 삭제 - postRepository.delete(post); + private Post buildPost(PostCreateRequest req, Member author, List images) { + String image = (images != null && !images.isEmpty() && !images.get(0).isEmpty()) ? images.get(0) : null; + return Post.builder() + .isAnonymous(req.isAnonymous()) + .title(req.getTitle()) + .content(req.getContent()) + .image(image) + .board(req.getType()) + .member(author) + .build(); } } diff --git a/src/main/java/com/example/spot/post/application/command/impl/ScrapPostUseCaseImpl.java b/src/main/java/com/example/spot/post/application/command/impl/ScrapPostUseCaseImpl.java index b5638da4..4b60049c 100644 --- a/src/main/java/com/example/spot/post/application/command/impl/ScrapPostUseCaseImpl.java +++ b/src/main/java/com/example/spot/post/application/command/impl/ScrapPostUseCaseImpl.java @@ -1,7 +1,5 @@ package com.example.spot.post.application.command.impl; -import static com.example.spot.common.security.utils.SecurityUtils.getCurrentUserId; - import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.PostHandler; @@ -9,9 +7,10 @@ import com.example.spot.member.infrastructure.jpa.MemberRepository; import com.example.spot.post.application.command.ScrapPostUseCase; import com.example.spot.post.domain.Post; -import com.example.spot.post.infrastructure.jpa.PostRepository; import com.example.spot.post.domain.association.MemberScrap; import com.example.spot.post.infrastructure.jpa.MemberScrapRepository; +import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.infrastructure.jpa.PostStatsRepository; import com.example.spot.post.presentation.dto.request.post.ScrapAllDeleteRequest; import com.example.spot.post.presentation.dto.response.post.ScrapPostResponse; import com.example.spot.post.presentation.dto.response.post.ScrapsPostDeleteResponse; @@ -26,6 +25,7 @@ public class ScrapPostUseCaseImpl implements ScrapPostUseCase { private final PostRepository postRepository; + private final PostStatsRepository postStatsRepository; private final MemberRepository memberRepository; private final MemberScrapRepository memberScrapRepository; @@ -41,35 +41,15 @@ public class ScrapPostUseCaseImpl implements ScrapPostUseCase { */ @Override public ScrapPostResponse scrapPost(Long postId, Long memberId) { - // 게시글 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); - - // 회원 정보 가져오기 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + ensurePostAndMemberExist(postId, memberId); - // 스크랩 여부 확인 - if (memberScrapRepository.findByMemberIdAndPostId(memberId, postId).isPresent()) { - throw new PostHandler(ErrorStatus._POST_ALREADY_SCRAPPED); - } - - // 스크랩 정보 저장 - MemberScrap memberScrap = MemberScrap.builder() - .member(member) - .post(post) - .build(); + Post post = requirePostRef(postId); + Member member = requireMemberRef(memberId); - memberScrapRepository.saveAndFlush(memberScrap); + saveMemberScrapAndIncreaseScrapNum(postId, member, post); // 스크랩된 리스트의 갯수를 조회하여 스크랩 수 계산 - long scrapCount = memberScrapRepository.countByPostId(postId); - - // 스크랩 결과 반환 - return ScrapPostResponse.builder() - .postId(post.getId()) - .scrapCount(scrapCount) - .build(); + return getScrapPostResponse(postId, post); } /** @@ -84,30 +64,13 @@ public ScrapPostResponse scrapPost(Long postId, Long memberId) { */ @Override public ScrapPostResponse cancelPostScrap(Long postId, Long memberId) { - // 게시글 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); + ensurePostAndMemberExist(postId, memberId); - // 회원 정보 가져오기 - memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Post post = requirePostRef(postId); - // 스크랩 여부 확인 - MemberScrap memberScrap = memberScrapRepository.findByMemberIdAndPostId(memberId, postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_SCRAPPED)); - - // 스크랩 삭제 및 즉시 반영 - memberScrapRepository.delete(memberScrap); - memberScrapRepository.flush(); + deleteMemberScrapAndDecreaseScrapNum(postId, memberId); - // 스크랩된 리스트의 갯수를 조회하여 스크랩 수 계산 - long scrapCount = memberScrapRepository.countByPostId(postId); - - // 스크랩 취소 결과 반환 - return ScrapPostResponse.builder() - .postId(post.getId()) - .scrapCount(scrapCount) - .build(); + return getScrapPostResponse(postId, post); } /** @@ -117,18 +80,66 @@ public ScrapPostResponse cancelPostScrap(Long postId, Long memberId) { * @return 스크랩 취소 결과 반환 */ @Override - public ScrapsPostDeleteResponse cancelPostScraps(ScrapAllDeleteRequest request) { - // 현재 로그인한 회원 조회 - Long currentMemberId = getCurrentUserId(); - + public ScrapsPostDeleteResponse cancelPostScraps(ScrapAllDeleteRequest request, Long memberId) { // 삭제할 List scrapIds cancelPostScrap() 순회 - List deletePostResponses = request.getDeletePostIds().stream().map( - deletePostId -> cancelPostScrap(deletePostId, currentMemberId) - ).toList(); + List deletePostResponses = request.getDeletePostIds().stream() + .map(deletePostId -> cancelPostScrap(deletePostId, memberId)) + .toList(); // 스크랩 취소 결과 반환 return ScrapsPostDeleteResponse.builder() .cancelScraps(deletePostResponses) .build(); } + + + /* ------------------------------- private method ------------------------------------------ */ + + private void ensurePostAndMemberExist(Long postId, Long memberId) { + // 게시글 존재 여부 확인 + if (!postRepository.existsById(postId)) { + throw new PostHandler(ErrorStatus._POST_NOT_FOUND); + } + + // 회원 존재 여부 확인 + if (!memberRepository.existsById(memberId)) { + throw new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND); + } + } + + private Member requireMemberRef(Long memberId) { + return memberRepository.getReferenceById(memberId); + } + + private Post requirePostRef(Long postId) { + return postRepository.getReferenceById(postId); + } + + private void saveMemberScrapAndIncreaseScrapNum(Long postId, Member member, Post post) { + // 스크랩 정보 저장 + MemberScrap memberScrap = MemberScrap.builder() + .member(member) + .post(post) + .build(); + + memberScrapRepository.save(memberScrap); + postStatsRepository.incrementScrap(postId); + } + + private void deleteMemberScrapAndDecreaseScrapNum(Long postId, Long memberId) { + // 스크랩 삭제 및 즉시 반영 + int effectedRow = memberScrapRepository.deleteByPostIdAndMemberId(postId, memberId); + if (effectedRow == 1) { + postStatsRepository.decrementScrap(postId); + } + } + + private ScrapPostResponse getScrapPostResponse(Long postId, Post post) { + // 스크랩된 리스트의 갯수를 조회하여 스크랩 수 계산 + long scrapCount = postStatsRepository.getScrapNum(postId); + return ScrapPostResponse.builder() + .postId(post.getId()) + .scrapCount(scrapCount) + .build(); + } } diff --git a/src/main/java/com/example/spot/post/application/query/GetLikedPostCommentUseCase.java b/src/main/java/com/example/spot/post/application/query/GetLikedPostCommentUseCase.java deleted file mode 100644 index df248a89..00000000 --- a/src/main/java/com/example/spot/post/application/query/GetLikedPostCommentUseCase.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.spot.post.application.query; - -public interface GetLikedPostCommentUseCase { - long countByPostCommentIdAndIsLikedTrue(Long postCommentId); - - boolean existsByMemberIdAndPostCommentIdAndIsLikedTrue(Long postCommentId); - - boolean existsByMemberIdAndPostCommentIdAndIsLikedFalse(Long postCommentId); - -} diff --git a/src/main/java/com/example/spot/post/application/query/impl/GetLikedPostCommentUseCaseImpl.java b/src/main/java/com/example/spot/post/application/query/impl/GetLikedPostCommentUseCaseImpl.java deleted file mode 100644 index 0f2109a4..00000000 --- a/src/main/java/com/example/spot/post/application/query/impl/GetLikedPostCommentUseCaseImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.example.spot.post.application.query.impl; - -import com.example.spot.post.infrastructure.jpa.LikedPostCommentRepository; -import com.example.spot.post.application.query.GetLikedPostCommentUseCase; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import static com.example.spot.common.security.utils.SecurityUtils.getCurrentUserId; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class GetLikedPostCommentUseCaseImpl implements GetLikedPostCommentUseCase { - private final LikedPostCommentRepository likedPostCommentRepository; - - - /** - * 게시글 댓글 좋아요 수를 반환합니다. - * - * @param postCommentId 게시글 댓글 ID - * @return 게시글 댓글 좋아요 수 - */ - @Override - public long countByPostCommentIdAndIsLikedTrue(Long postCommentId) { - return likedPostCommentRepository.countByPostCommentIdAndIsLikedTrue(postCommentId); - } - - /** - * 현재 사용자의 게시글 댓글 좋아요 여부를 반환합니다. - * - * @param postCommentId 게시글 댓글 ID - * @return 현재 사용자의 댓글 좋아요 여부 - */ - @Override - public boolean existsByMemberIdAndPostCommentIdAndIsLikedTrue(Long postCommentId) { - Long currentUserId = getCurrentUserId(); - return likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedTrue(currentUserId, postCommentId); - } - - /** - * 현재 사용자의 게시글 댓글 싫어요 여부를 true/false로 반환합니다. - * - * @param postCommentId 게시글 댓글 ID - * @return 현재 사용자의 댓글 싫어요 여부 - */ - @Override - public boolean existsByMemberIdAndPostCommentIdAndIsLikedFalse(Long postCommentId) { - Long currentUserId = getCurrentUserId(); - return likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedFalse(currentUserId, postCommentId); - } -} diff --git a/src/main/java/com/example/spot/post/application/query/impl/GetPostUseCaseImpl.java b/src/main/java/com/example/spot/post/application/query/impl/GetPostUseCaseImpl.java index d7bdd95b..f6867b40 100644 --- a/src/main/java/com/example/spot/post/application/query/impl/GetPostUseCaseImpl.java +++ b/src/main/java/com/example/spot/post/application/query/impl/GetPostUseCaseImpl.java @@ -1,18 +1,18 @@ package com.example.spot.post.application.query.impl; +import static com.example.spot.common.security.utils.SecurityUtils.getCurrentUserId; + import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.PostHandler; -import com.example.spot.post.application.query.GetLikedPostCommentUseCase; import com.example.spot.post.application.query.GetLikedPostUseCase; import com.example.spot.post.application.query.GetPostUseCase; import com.example.spot.post.domain.Post; import com.example.spot.post.domain.PostComment; +import com.example.spot.post.domain.association.MemberScrap; import com.example.spot.post.domain.enums.Board; import com.example.spot.post.domain.enums.PostStatus; -import com.example.spot.post.domain.association.MemberScrap; import com.example.spot.post.infrastructure.jpa.MemberScrapRepository; import com.example.spot.post.infrastructure.jpa.PostCommentRepository; -import com.example.spot.report.infrastructure.jpa.PostReportRepository; import com.example.spot.post.infrastructure.jpa.PostRepository; import com.example.spot.post.presentation.dto.response.comment.CommentDetailResponse; import com.example.spot.post.presentation.dto.response.comment.CommentResponse; @@ -24,6 +24,9 @@ import com.example.spot.post.presentation.dto.response.post.PostRepresentativeDetailResponse; import com.example.spot.post.presentation.dto.response.post.PostRepresentativeResponse; import com.example.spot.post.presentation.dto.response.post.PostSingleResponse; +import com.example.spot.report.infrastructure.jpa.PostReportRepository; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -31,11 +34,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import static com.example.spot.common.security.utils.SecurityUtils.getCurrentUserId; - @Service @RequiredArgsConstructor @@ -47,7 +45,6 @@ public class GetPostUseCaseImpl implements GetPostUseCase { private final PostRepository postRepository; private final GetLikedPostUseCase getLikedPostUseCase; private final PostCommentRepository postCommentRepository; - private final GetLikedPostCommentUseCase getLikedPostCommentUseCase; private final MemberScrapRepository memberScrapRepository; private final PostReportRepository postReportRepository; @@ -72,7 +69,7 @@ public PostSingleResponse getPostById(Long postId, boolean likeOrScrap) { // 조회수 증가는 일반 조회시에(likeOrScrap이 false일 때)만 실행 if (!likeOrScrap) { - post.viewHit(); +// post.viewHit(); } // 좋아요 수 조회 @@ -114,12 +111,13 @@ public PostPagingResponse getPagingPosts(String type, Pageable pageable) { Board boardType = Board.findByValue(type); Page postPage; + if (boardType == Board.ALL) { // ALL 타입일 경우 모든 게시글 조회 - postPage = postRepository.findByPostReportListIsEmptyOrderByCreatedAtDesc(pageable); + postPage = postRepository.findPostsWithoutReport(pageable); } else { // 특정 게시판 타입의 게시글 조회 - postPage = postRepository.findByBoardAndPostReportListIsEmptyOrderByCreatedAtDesc(boardType, pageable); + postPage = postRepository.findPostsWithoutReportByBoard(boardType, pageable); } // PostPagingDetailResponse를 묶어서 응답 리스트 생성 (좋아요 수, 좋아요여부, 스크랩 수, 스크랩여부 포함) @@ -147,11 +145,6 @@ public PostPagingResponse getPagingPosts(String type, Pageable pageable) { .build(); } - // 게시글이 신고되었는지 확인하는 메서드 - private boolean isPostReported(Post post) { - return !post.getPostReportList().isEmpty(); - } - /** * 인기글 종류(실시간, 추천순, 댓글순)에 따라 게시글을 상위 5개씩 조회합니다. * @@ -280,11 +273,9 @@ public CommentResponse getCommentsByPostId(Long postId) { // CommentDetailResponse를 묶어서 응답 리스트 생성 (댓글 좋아요수, 댓글 좋아요/싫어요 여부 포함) List commentResponses = comments.stream() .map(comment -> { - long likeCount = getLikedPostCommentUseCase.countByPostCommentIdAndIsLikedTrue(comment.getId()); - boolean likedByCurrentUser = getLikedPostCommentUseCase.existsByMemberIdAndPostCommentIdAndIsLikedTrue( - comment.getId()); - boolean dislikedByCurrentUser = getLikedPostCommentUseCase.existsByMemberIdAndPostCommentIdAndIsLikedFalse( - comment.getId()); + long likeCount = 0L; + boolean likedByCurrentUser = true; + boolean dislikedByCurrentUser = false; return CommentDetailResponse.toDTO(comment, likeCount, likedByCurrentUser, dislikedByCurrentUser, DEFAULT_PROFILE_IMAGE_URL); }) diff --git a/src/main/java/com/example/spot/post/domain/Post.java b/src/main/java/com/example/spot/post/domain/Post.java index 00de2fa5..e8f44dfa 100644 --- a/src/main/java/com/example/spot/post/domain/Post.java +++ b/src/main/java/com/example/spot/post/domain/Post.java @@ -1,21 +1,30 @@ package com.example.spot.post.domain; -import com.example.spot.post.domain.association.LikedPost; -import com.example.spot.post.domain.association.PostImage; -import com.example.spot.report.domain.PostReport; import com.example.spot.common.entity.BaseEntity; +import com.example.spot.member.domain.Member; +import com.example.spot.post.domain.association.LikedPost; import com.example.spot.post.domain.enums.Board; -import com.example.spot.post.domain.association.MemberScrap; import com.example.spot.post.presentation.dto.request.post.PostUpdateRequest; -import com.example.spot.member.domain.Member; - -import jakarta.persistence.*; -import lombok.*; -import org.springframework.util.StringUtils; - +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; @Builder @AllArgsConstructor @@ -30,44 +39,25 @@ public class Post extends BaseEntity { private boolean isAdmin; - @Column private boolean isAnonymous; private String title; private String content; - private int scrapNum; - - private int commentNum; - - private int hitNum; - private String image; @Enumerated(EnumType.STRING) private Board board; - @OneToMany(mappedBy = "post") - @Builder.Default - private List postImageList = new ArrayList<>(); - @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) @Builder.Default private List likedPostList = new ArrayList<>(); - @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) - @Builder.Default - private List postReportList = new ArrayList<>(); - @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) @Builder.Default private List postCommentList = new ArrayList<>(); - @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) - @Builder.Default - private List memberScrapList = new ArrayList<>(); - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; @@ -105,20 +95,8 @@ private void updateImage(List images, String existingImage) { } - public void viewHit() { - this.hitNum++; - } - public void setCreatedAt(LocalDateTime createdAt) { super.setCreatedAt(createdAt); } - public void plusCommentNum() { - this.commentNum++; - } - - public void addComment(PostComment comment) { - this.postCommentList.add(comment); - } - } diff --git a/src/main/java/com/example/spot/post/domain/PostComment.java b/src/main/java/com/example/spot/post/domain/PostComment.java index 77fd91bb..22374fa3 100644 --- a/src/main/java/com/example/spot/post/domain/PostComment.java +++ b/src/main/java/com/example/spot/post/domain/PostComment.java @@ -1,14 +1,20 @@ package com.example.spot.post.domain; -import com.example.spot.post.domain.association.LikedPostComment; import com.example.spot.common.entity.BaseEntity; import com.example.spot.member.domain.Member; - -import jakarta.persistence.*; -import lombok.*; - -import java.util.ArrayList; -import java.util.List; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Builder @AllArgsConstructor @@ -26,14 +32,6 @@ public class PostComment extends BaseEntity { private String content; - private int likeNum; - - private int disLikeNum; - - @OneToMany(mappedBy = "postComment") - @Builder.Default - private List likedPostCommentsList = new ArrayList<>(); - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") private Post post; @@ -42,12 +40,4 @@ public class PostComment extends BaseEntity { @JoinColumn(name = "member_id") private Member member; - //대댓글 - @ManyToOne - @JoinColumn(name = "parent_id") - private PostComment parentComment; //부모 댓글 - - @OneToMany(mappedBy = "parentComment", orphanRemoval = true) - @Builder.Default - private List childrenComment = new ArrayList<>(); //자식 댓글들(대댓글) } diff --git a/src/main/java/com/example/spot/post/domain/PostStats.java b/src/main/java/com/example/spot/post/domain/PostStats.java new file mode 100644 index 00000000..cdf48bfd --- /dev/null +++ b/src/main/java/com/example/spot/post/domain/PostStats.java @@ -0,0 +1,43 @@ +package com.example.spot.post.domain; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PostStats { + + @Id + private Long id; + + @MapsId + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "post_id", nullable = false, unique = true) + private Post post; + + @Column(nullable = false) + private long commentCount = 0L; + + @Column(nullable = false) + private long likeCount = 0L; + + @Column(nullable = false) + private long hitCount = 0L; + + @Column(nullable = false) + private long scrapNum = 0L; +} diff --git a/src/main/java/com/example/spot/post/domain/schedule/PostScheduleRealTime.java b/src/main/java/com/example/spot/post/domain/schedule/PostScheduleRealTime.java index 242e7eee..1a0ddd1a 100644 --- a/src/main/java/com/example/spot/post/domain/schedule/PostScheduleRealTime.java +++ b/src/main/java/com/example/spot/post/domain/schedule/PostScheduleRealTime.java @@ -2,7 +2,6 @@ import com.example.spot.common.entity.BaseEntity; import com.example.spot.post.domain.Post; - import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -24,7 +23,8 @@ public class PostScheduleRealTime extends BaseEntity { private Integer likeCount; private Integer hitCount; - private PostScheduleRealTime(Integer rank, String title, Integer commentCount, Integer likeCount, Integer hitCount) { + private PostScheduleRealTime(Integer rank, String title, Integer commentCount, Integer likeCount, + Integer hitCount) { this.ranking = rank; this.title = title; this.commentCount = commentCount; @@ -35,7 +35,7 @@ private PostScheduleRealTime(Integer rank, String title, Integer commentCount, I public static PostScheduleRealTime of(Post post, Integer rank) { int commentSize = post.getPostCommentList().size(); int likeSize = post.getLikedPostList().size(); - int hitSize = post.getHitNum(); + int hitSize = 0; return new PostScheduleRealTime(rank, post.getTitle(), commentSize, likeSize, hitSize); } } diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostCommentRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostCommentRepository.java deleted file mode 100644 index 3b4d2025..00000000 --- a/src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostCommentRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.spot.post.infrastructure.jpa; - -import com.example.spot.post.domain.association.LikedPostComment; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface LikedPostCommentRepository extends JpaRepository { - Optional findByMemberIdAndPostCommentIdAndIsLikedTrue(Long memberId, Long postCommentId); - - Optional findByMemberIdAndPostCommentIdAndIsLikedFalse(Long memberId, Long postCommentId); - - long countByPostCommentIdAndIsLikedTrue(Long postCommentId); - - long countByPostCommentIdAndIsLikedFalse(Long postCommentId); - - //회원 ID와 댓글 ID로 좋아요(IsLiked=True) 존재 여부 - boolean existsByMemberIdAndPostCommentIdAndIsLikedTrue(Long currentUserId, Long postCommentId); - - //회원 ID와 댓글 ID로 싫어요(IsLiked=False) 존재 여부 - boolean existsByMemberIdAndPostCommentIdAndIsLikedFalse(Long currentUserId, Long postCommentId); -} diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostRepository.java index 7b444a02..ff4cc61b 100644 --- a/src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostRepository.java +++ b/src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostRepository.java @@ -1,9 +1,11 @@ package com.example.spot.post.infrastructure.jpa; import com.example.spot.post.domain.association.LikedPost; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface LikedPostRepository extends JpaRepository { Optional findByMemberIdAndPostId(Long memberId, Long postId); @@ -14,4 +16,9 @@ public interface LikedPostRepository extends JpaRepository { // 회원 ID와 게시글 ID로 LikedPost 존재 여부 boolean existsByMemberIdAndPostId(Long memberId, Long postId); + // 지우고 영향 행 수 반환 + @Modifying + @Query("delete from LikedPost lp where lp.member.id = :memberId and lp.post.id = :postId") + int deleteByMemberIdAndPostId(@Param("memberId") Long memberId, @Param("postId") Long postId); + } diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/MemberScrapRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/MemberScrapRepository.java index b5ba0b20..2fb3b96e 100644 --- a/src/main/java/com/example/spot/post/infrastructure/jpa/MemberScrapRepository.java +++ b/src/main/java/com/example/spot/post/infrastructure/jpa/MemberScrapRepository.java @@ -2,15 +2,13 @@ import com.example.spot.post.domain.association.MemberScrap; import com.example.spot.post.domain.enums.Board; - +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.Optional; - public interface MemberScrapRepository extends JpaRepository { Optional findByMemberIdAndPostId(Long memberId, Long postId); @@ -24,4 +22,7 @@ public interface MemberScrapRepository extends JpaRepository @Query("SELECT ms FROM MemberScrap ms LEFT JOIN FETCH ms.post p WHERE ms.member.id = :memberId AND p.board = :board ORDER BY ms.createdAt DESC") Page findByMemberIdAndPost_Board(@Param("memberId") Long memberId, @Param("board") Board board, Pageable pageable); + + @Query("DELETE from MemberScrap ms where ms.member.id = :memberId and ms.post.id = :postId") + int deleteByPostIdAndMemberId(Long postId, Long memberId); } diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/PostRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/PostRepository.java index 7a579ea5..47adfb1c 100644 --- a/src/main/java/com/example/spot/post/infrastructure/jpa/PostRepository.java +++ b/src/main/java/com/example/spot/post/infrastructure/jpa/PostRepository.java @@ -1,18 +1,58 @@ package com.example.spot.post.infrastructure.jpa; +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.handler.PostHandler; import com.example.spot.post.domain.Post; -import com.example.spot.post.infrastructure.querydsl.PostRepositoryCustom; import com.example.spot.post.domain.enums.Board; - +import com.example.spot.post.infrastructure.querydsl.PostRepositoryCustom; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface PostRepository extends JpaRepository, PostRepositoryCustom { - // 정렬 - Page findByBoardAndPostReportListIsEmptyOrderByCreatedAtDesc(Board board, Pageable pageable); - // 정렬 조건 필요 - Page findByPostReportListIsEmptyOrderByCreatedAtDesc(Pageable pageable); + default Post getById(Long postId) { + return findById(postId).orElseThrow( + () -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); + } + + @Query(value = """ + select p + from Post p + where not exists ( + select 1 from PostReport r + where r.post = p + ) + order by p.createdAt desc + """, countQuery = """ + select count(p) + from Post p + where not exists ( + select 1 from PostReport r + where r.post = p + ) + """) + Page findPostsWithoutReport(Pageable pageable); + @Query(value = """ + select p + from Post p + where p.board = :board + and not exists ( + select 1 from PostReport r + where r.post = p + ) + order by p.createdAt desc + """, countQuery = """ + select count(p) + from Post p + where p.board = :board + and not exists ( + select 1 from PostReport r + where r.post = p + ) + """) + Page findPostsWithoutReportByBoard(@Param("board") Board board, Pageable pageable); } diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/PostStatsRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/PostStatsRepository.java new file mode 100644 index 00000000..8a444d92 --- /dev/null +++ b/src/main/java/com/example/spot/post/infrastructure/jpa/PostStatsRepository.java @@ -0,0 +1,43 @@ +package com.example.spot.post.infrastructure.jpa; + +import com.example.spot.post.domain.PostStats; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PostStatsRepository extends JpaRepository { + + @Query("select s.likeCount from PostStats s where s.id = :postId") + long getLikeCount(@Param("postId") Long postId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update PostStats s set s.likeCount = s.likeCount + 1 where s.id = :postId") + void incrementLike(@Param("postId") Long postId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update PostStats s set s.likeCount = s.likeCount - 1 where s.id = :postId and s.likeCount > 0") + int decrementLike(@Param("postId") Long postId); + + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update PostStats s set s.commentCount = s.commentCount + 1 where s.id = :postId") + void incrementComment(@Param("postId") Long postId); + + + @Query("select s.scrapNum from PostStats s where s.id = :postId") + long getScrapNum(@Param("postId") Long postId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update PostStats s set s.scrapNum = s.scrapNum + 1 where s.id = :postId") + void incrementScrap(@Param("postId") Long postId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update PostStats s set s.scrapNum = s.scrapNum - 1 where s.id = :postId and s.scrapNum > 0") + int decrementScrap(@Param("postId") Long postId); + + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update PostStats s set s.hitCount = s.hitCount + 1 where s.id = :postId") + void incrementHit(@Param("postId") Long postId); +} diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleCommentsRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleCommentsRepository.java similarity index 79% rename from src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleCommentsRepository.java rename to src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleCommentsRepository.java index 7065d1f9..9e30342e 100644 --- a/src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleCommentsRepository.java +++ b/src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleCommentsRepository.java @@ -1,4 +1,4 @@ -package com.example.spot.post.infrastructure.jpa; +package com.example.spot.post.infrastructure.jpa.schedule; import com.example.spot.post.domain.schedule.PostScheduleComments; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleLikesRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleLikesRepository.java similarity index 79% rename from src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleLikesRepository.java rename to src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleLikesRepository.java index 1e8f0048..93d39d34 100644 --- a/src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleLikesRepository.java +++ b/src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleLikesRepository.java @@ -1,4 +1,4 @@ -package com.example.spot.post.infrastructure.jpa; +package com.example.spot.post.infrastructure.jpa.schedule; import com.example.spot.post.domain.schedule.PostScheduleLikes; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleRealTimeRepository.java b/src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleRealTimeRepository.java similarity index 79% rename from src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleRealTimeRepository.java rename to src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleRealTimeRepository.java index 7a9a6d73..43530303 100644 --- a/src/main/java/com/example/spot/post/infrastructure/jpa/PostScheduleRealTimeRepository.java +++ b/src/main/java/com/example/spot/post/infrastructure/jpa/schedule/PostScheduleRealTimeRepository.java @@ -1,4 +1,4 @@ -package com.example.spot.post.infrastructure.jpa; +package com.example.spot.post.infrastructure.jpa.schedule; import com.example.spot.post.domain.schedule.PostScheduleRealTime; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java b/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java index 2e043442..2f86d0d9 100644 --- a/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java +++ b/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java @@ -22,7 +22,6 @@ public List findCommentsByPostId(Long postId) { postComment.post.id.eq(postId) ) .orderBy( - postComment.parentComment.id.asc().nullsFirst(), postComment.createdAt.asc() ) .fetch(); diff --git a/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostRepositoryImpl.java b/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostRepositoryImpl.java index 64eb6c02..4ed141a9 100644 --- a/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostRepositoryImpl.java +++ b/src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostRepositoryImpl.java @@ -63,8 +63,8 @@ public List findTopByRealTimeScore() { .where(post.createdAt.isNotNull() .and(post.createdAt.after(twoHoursAgo))) .orderBy( - post.hitNum.add(post.likedPostList.size()).add(post.postCommentList.size()).desc(), - post.hitNum.desc(), +// post.hitNum.add(post.likedPostList.size()).add(post.postCommentList.size()).desc(), +// post.hitNum.desc(), post.likedPostList.size().desc(), post.postCommentList.size().desc() ) diff --git a/src/main/java/com/example/spot/post/presentation/controller/command/LikePostController.java b/src/main/java/com/example/spot/post/presentation/controller/command/LikePostController.java index c671f448..ed600c7f 100644 --- a/src/main/java/com/example/spot/post/presentation/controller/command/LikePostController.java +++ b/src/main/java/com/example/spot/post/presentation/controller/command/LikePostController.java @@ -25,7 +25,6 @@ public class LikePostController { private final LikePostUseCase likePostUseCase; @Tag(name = "게시글 좋아요", description = "게시글 좋아요 관련 API") - //게시글 좋아요 @Operation(summary = "[게시판] 게시글 좋아요 API", description = "게시글 Id를 받아 게시글에 좋아요를 추가합니다.") @PostMapping("/{postId}/like") public ApiResponse likePost( diff --git a/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java b/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java index 44d8a05e..d2cbcdb1 100644 --- a/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java +++ b/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java @@ -1,11 +1,11 @@ package com.example.spot.post.presentation.controller.command; -import com.example.spot.post.presentation.dto.request.comment.CommentCreateRequest; -import com.example.spot.post.presentation.dto.response.comment.CommentCreateResponse; import com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.post.application.command.ManagePostCommentUseCase; +import com.example.spot.post.presentation.dto.request.comment.CommentCreateRequest; +import com.example.spot.post.presentation.dto.response.comment.CommentCreateResponse; import com.example.spot.post.presentation.validator.ExistPost; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -25,18 +25,11 @@ public class ManagePostCommentController { private final ManagePostCommentUseCase managePostCommentUseCase; - //댓글 @Tag(name = "게시판 - 댓글", description = "댓글 관련 API") @Operation(summary = "[게시판] 댓글 생성 API", description = """ 게시글 Id와 회원 Id를 받아 댓글을 생성합니다. - - 댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다. - - 익명 여부 선택할 수 있습니다. - - 생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0일 경우 null로 반환), 댓글 내용, 작성자를 반환합니다. - """) + 댓글 내용, 익명 여부를 포함한 요청을 받습니다.""") @PostMapping("/{postId}/comments") public ApiResponse createComment( @PathVariable @ExistPost Long postId, diff --git a/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostController.java b/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostController.java index f1e1d63a..10fc0aac 100644 --- a/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostController.java +++ b/src/main/java/com/example/spot/post/presentation/controller/command/ManagePostController.java @@ -1,6 +1,5 @@ package com.example.spot.post.presentation.controller.command; - import com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.common.security.utils.SecurityUtils; @@ -29,71 +28,60 @@ @Validated @RestController @RequiredArgsConstructor -@RequestMapping("/spot/posts") +@RequestMapping(value = "/spot/posts", produces = MediaType.APPLICATION_JSON_VALUE) +@Tag(name = "게시판", description = "게시글 생성/수정/삭제 API") +@SecurityRequirement(name = "accessToken") public class ManagePostController { private final ManagePostUseCase managePostUseCase; - @Tag(name = "게시판", description = "게시판 관련 API") @Operation( - summary = "[게시판] 게시글 등록 API", + summary = "[게시판] 게시글 등록", description = """ - 입력 받은 값으로 게시글을 하나 등록 합니다. - - 게시글 종류는 PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT 중 하나입니다. - - 익명 여부를 선택할 수 있습니다. - - 생성된 게시글의 고유 ID와 게시글 종류, 생성 시간을 반환합니다. 요청 시, 요청 타입은 Multipart/form-data로 보내야 합니다. - """, - security = @SecurityRequirement(name = "accessToken") + 게시글을 등록합니다. 요청은 multipart/form-data 이어야 합니다. + - 유형: PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT + - 익명 여부 선택 가능 + 반환: 생성된 게시글 ID/유형/생성시간 + """ ) @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponse create( + @Parameter(description = "등록 요청 데이터") @ModelAttribute @Valid PostCreateRequest postCreateRequest ) { - PostCreateResponse response = managePostUseCase.createPost(SecurityUtils.getCurrentUserId(), postCreateRequest); + Long memberId = SecurityUtils.getCurrentUserId(); + PostCreateResponse response = managePostUseCase.createPost(memberId, postCreateRequest); return ApiResponse.onSuccess(SuccessStatus._CREATED, response); } - - @Tag(name = "게시판", description = "게시판 관련 API") @Operation( - summary = "[게시판] 게시글 수정 API", - description = - "게시글 Id를 받아 게시글을 수정합니다. existingImage는 기존 이미지 URL입니다. 수정할 이미지가 없을 경우 null로 보내주세요. 요청 시, 요청 타입은 Multipart/form-data로 보내야 합니다." - + "\n" + "existingImage와 image 둘 중 하나만 보내주세요. 둘 다 보내면 기존 이미지로 덮어씌워집니다.", - security = @SecurityRequirement(name = "accessToken") + summary = "[게시판] 게시글 수정", + description = """ + 게시글을 수정합니다. 요청은 multipart/form-data 이어야 합니다. + - existingImage: 기존 이미지 URL (없으면 null) + - image와 existingImage 중 하나만 전송 (둘 다 전송 시 기존 이미지로 덮어쓰기) + """ ) @PatchMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponse update( - @Parameter( - description = "수정할 게시글의 ID입니다.", - schema = @Schema(type = "integer", format = "int64") - ) + @Parameter(description = "게시글 ID", schema = @Schema(type = "integer", format = "int64")) @PathVariable @ExistPost Long postId, - @Parameter( - description = "수정할 게시글 데이터입니다." - ) - @ModelAttribute PostUpdateRequest postUpdateRequest + @Parameter(description = "수정 요청 데이터") + @ModelAttribute @Valid PostUpdateRequest postUpdateRequest ) { - PostCreateResponse response = managePostUseCase.updatePost(SecurityUtils.getCurrentUserId(), postId, - postUpdateRequest); + Long memberId = SecurityUtils.getCurrentUserId(); + PostCreateResponse response = managePostUseCase.updatePost(memberId, postId, postUpdateRequest); return ApiResponse.onSuccess(SuccessStatus._OK, response); } - @Tag(name = "게시판", description = "게시판 관련 API") - @Operation(summary = "[게시판] 게시글 삭제 API", description = "게시글 Id를 받아 게시글을 삭제합니다.") + @Operation(summary = "[게시판] 게시글 삭제", description = "지정한 게시글을 삭제합니다.") @DeleteMapping("/{postId}") public ApiResponse delete( - @Parameter( - description = "삭제할 게시글의 ID입니다.", - schema = @Schema(type = "integer", format = "int64") - ) - @PathVariable Long postId + @Parameter(description = "게시글 ID", schema = @Schema(type = "integer", format = "int64")) + @PathVariable @ExistPost Long postId ) { - managePostUseCase.deletePost(SecurityUtils.getCurrentUserId(), postId); + Long memberId = SecurityUtils.getCurrentUserId(); + managePostUseCase.deletePost(memberId, postId); return ApiResponse.onSuccess(SuccessStatus._NO_CONTENT); - } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/post/presentation/controller/command/ScrapPostController.java b/src/main/java/com/example/spot/post/presentation/controller/command/ScrapPostController.java index 5cfa2d5a..3567be87 100644 --- a/src/main/java/com/example/spot/post/presentation/controller/command/ScrapPostController.java +++ b/src/main/java/com/example/spot/post/presentation/controller/command/ScrapPostController.java @@ -27,7 +27,6 @@ public class ScrapPostController { private final ScrapPostUseCase scrapPostUseCase; - //스크랩 @Tag(name = "게시글 스크랩", description = "게시글 스크랩 관련 API") @Operation(summary = "[게시판] 게시글 스크랩 API", description = "게시글 ID와 회원 ID를 받아 스크랩을 추가합니다.") @PostMapping("/{postId}/scrap") @@ -54,7 +53,8 @@ public ApiResponse cancelPostScrap( @DeleteMapping("/scraps") public ApiResponse deleteAllPostScrap( @RequestBody ScrapAllDeleteRequest request) { - ScrapsPostDeleteResponse response = scrapPostUseCase.cancelPostScraps(request); + ScrapsPostDeleteResponse response = scrapPostUseCase.cancelPostScraps(request, + SecurityUtils.getCurrentUserId()); return ApiResponse.onSuccess(SuccessStatus._NO_CONTENT, response); } } diff --git a/src/main/java/com/example/spot/post/presentation/controller/query/GetPostController.java b/src/main/java/com/example/spot/post/presentation/controller/query/GetPostController.java index 34f5c90e..365eebe9 100644 --- a/src/main/java/com/example/spot/post/presentation/controller/query/GetPostController.java +++ b/src/main/java/com/example/spot/post/presentation/controller/query/GetPostController.java @@ -1,9 +1,9 @@ package com.example.spot.post.presentation.controller.query; -import com.example.spot.post.presentation.dto.response.comment.CommentResponse; import com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.post.application.query.GetPostUseCase; +import com.example.spot.post.presentation.dto.response.comment.CommentResponse; import com.example.spot.post.presentation.dto.response.post.PostAnnouncementResponse; import com.example.spot.post.presentation.dto.response.post.PostBest5Response; import com.example.spot.post.presentation.dto.response.post.PostPagingResponse; @@ -16,8 +16,9 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -29,25 +30,22 @@ @RestController @RequiredArgsConstructor @RequestMapping("/spot/posts") +@Tag(name = "게시판 조회", description = "게시글 조회/목록/베스트/대표/공지/스크랩") public class GetPostController { private final GetPostUseCase getPostUseCase; - @Tag(name = "게시판", description = "게시판 관련 API") @Operation( - summary = "[게시판] 게시글 단건 조회 API", + summary = "[게시판] 게시글 단건 조회", description = """ - 게시글 ID를 받아 게시글을 조회합니다. - - 해당 게시글에 대한 상세 정보를 반환합니다. - - 좋아요나 스크랩으로 인한 조회 시 그 여부를 받습니다. - """, - security = @SecurityRequirement(name = "accessToken") + 게시글 ID로 상세 정보를 조회합니다. + - likeOrScrap=true면 '좋아요/스크랩 여부' 표시(로그인 필요) + """ ) + @SecurityRequirement(name = "accessToken") @GetMapping("/{postId}") - public ApiResponse singlePost( - @Parameter(description = "조회할 게시글 ID입니다.", schema = @Schema(type = "integer", format = "int64")) + public ApiResponse getSingle( + @Parameter(description = "게시글 ID", schema = @Schema(type = "integer", format = "int64")) @PathVariable @ExistPost Long postId, @RequestParam(required = false, defaultValue = "false") boolean likeOrScrap ) { @@ -55,104 +53,82 @@ public ApiResponse singlePost( return ApiResponse.onSuccess(SuccessStatus._OK, response); } - @Tag(name = "게시판", description = "게시판 관련 API") @Operation( - summary = "[게시판] 게시글 페이지 조회 API", + summary = "[게시판] 게시글 페이지 조회", description = """ - 게시글 종류를 받아 페이지 번호와 페이지 크기에 해당하는 게시글을 조회합니다. - - 게시글 종류는 ALL, PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT 중 하나입니다. - - 페이지 번호는 0부터 시작하며 기본값은 0입니다. - - 페이지 크기는 1부터 시작하며 기본값은 10입니다. + 게시글 종류(type)별 페이지네이션 조회. + - type: ALL, PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT + - 기본 정렬은 createdAt desc(서비스 규칙에 맞춰 구현) """ ) @GetMapping - public ApiResponse getPagingPost( - @Parameter(description = "게시글 종류. ALL, PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT 중 하나입니다.", required = true, example = "JOB_TALK") + public ApiResponse getPaging( + @Parameter(description = "게시글 종류", example = "JOB_TALK") @RequestParam String type, - @Parameter(description = "페이지 번호 (0부터 시작, 기본값 0)", example = "0") - @RequestParam(required = false, defaultValue = "0") int pageNumber, - @Parameter(description = "페이지 크기 (1부터 시작, 기본값 10)", example = "10") - @RequestParam(required = false, defaultValue = "10") int pageSize + @ParameterObject @PageableDefault Pageable pageable ) { - Pageable pageable = PageRequest.of(pageNumber, pageSize); PostPagingResponse response = getPostUseCase.getPagingPosts(type, pageable); return ApiResponse.onSuccess(SuccessStatus._OK, response); } - @Tag(name = "게시판", description = "게시판 관련 API") @Operation( summary = "[게시판] Best 인기글 조회", - description = "Best 인기글을 조회합니다.(인기글 조회시 종류 명시가 필요합니다.)") + description = "인기글 5개를 조회합니다. sortType: REAL_TIME, RECOMMEND, COMMENT" + ) @GetMapping("/best") - public ApiResponse getPostBest( - @Parameter(description = "인기글 종류. REAL_TIME, RECOMMEND, COMMENT 중 하나입니다. 요청하지 않으면 기본 값인 REAL_TIME로 조회됩니다.", example = "REAL_TIME") + public ApiResponse getBest( @RequestParam(required = false, defaultValue = "REAL_TIME") String sortType ) { PostBest5Response response = getPostUseCase.getPostBest(sortType); return ApiResponse.onSuccess(SuccessStatus._OK, response); } - @Tag(name = "게시판", description = "게시판 관련 API") @Operation( - summary = "[게시판] 게시판 홈 게시글 조회", - description = "게시판 홈에 게시글 종류별로 대표1개씩 게시글을 조회합니다.") + summary = "[게시판] 홈 대표 게시글 조회", + description = "게시판 홈에 노출할 종류별 대표 1건씩 조회" + ) @GetMapping("/representative") - public ApiResponse getPostRepresentative() { + public ApiResponse getRepresentative() { PostRepresentativeResponse response = getPostUseCase.getRepresentativePosts(); return ApiResponse.onSuccess(SuccessStatus._OK, response); } - @Tag(name = "게시판", description = "게시판 관련 API") - @Operation( - summary = "[게시판] 게시판 공지 조회", - description = "공지를 조회합니다.") + @Operation(summary = "[게시판] 공지 조회", description = "공지 목록/단건(서비스 정책에 맞춰 구현)") @GetMapping("/announcement") - public ApiResponse getPostAnnouncement() { + public ApiResponse getAnnouncement() { PostAnnouncementResponse response = getPostUseCase.getPostAnnouncements(); return ApiResponse.onSuccess(SuccessStatus._OK, response); } - @Tag(name = "게시글 스크랩", description = "게시글 스크랩 관련 API") + @Tag(name = "게시글 스크랩", description = "스크랩 조회 API") @Operation( - summary = "[마이페이지] 게시글 스크랩 페이지 조회 API", + summary = "[마이페이지] 스크랩한 게시글 페이지 조회", description = """ - 로그인한 회원이 스크랩한 게시글을 게시글 종류와 페이지 번호, 페이지 크기를 받아 조회합니다. - - 게시글 종류는 ALL, PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT 중 하나입니다. - - 페이지 번호는 0부터 시작하며 기본값은 0입니다. - - 페이지 크기는 1부터 시작하며 기본값은 10입니다. + 로그인 사용자의 스크랩 목록을 종류별로 페이지네이션 조회. + - type: ALL, PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT """ ) + @SecurityRequirement(name = "accessToken") @GetMapping("/scraps") - public ApiResponse getScrapPagingPost( - @Parameter(description = "게시글 종류. ALL, PASS_EXPERIENCE, INFORMATION_SHARING, COUNSELING, JOB_TALK, FREE_TALK, SPOT_ANNOUNCEMENT 중 하나입니다.", required = true, example = "JOB_TALK") + public ApiResponse getScraps( @RequestParam String type, - @Parameter(description = "페이지 번호 (0부터 시작, 기본값 0)", example = "0") - @RequestParam(required = false, defaultValue = "0") int pageNumber, - @Parameter(description = "페이지 크기 (1부터 시작, 기본값 10)", example = "10") - @RequestParam(required = false, defaultValue = "10") int pageSize + @ParameterObject @PageableDefault Pageable pageable ) { - Pageable pageable = PageRequest.of(pageNumber, pageSize); PostPagingResponse response = getPostUseCase.getScrapPagingPost(type, pageable); return ApiResponse.onSuccess(SuccessStatus._OK, response); } - @Tag(name = "게시판 - 댓글", description = "댓글 관련 API") - @Operation(summary = "!테스트용! 게시글 댓글 조회 API", description = "게시글 ID를 받아 댓글을 조회합니다. 댓글 조회는 이미 게시글 단건 조회에 포함되어 있습니다.") + @Tag(name = "게시판 - 댓글", description = "댓글 조회 API") + @Operation( + summary = "!테스트용! 게시글 댓글 조회", + description = "단건 조회에 포함되어 있으나 테스트 목적으로 분리 제공" + ) @GetMapping("/{postId}/comments") - public ApiResponse getComment( - @Parameter( - description = "조회할 게시글의 ID입니다.", - schema = @Schema(type = "integer", format = "int64") - ) + public ApiResponse getComments( + @Parameter(description = "게시글 ID", schema = @Schema(type = "integer", format = "int64")) @PathVariable @ExistPost Long postId ) { CommentResponse response = getPostUseCase.getCommentsByPostId(postId); return ApiResponse.onSuccess(SuccessStatus._OK, response); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java b/src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java index 4c41109c..d3dae86c 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java +++ b/src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java @@ -11,16 +11,13 @@ @NoArgsConstructor @AllArgsConstructor public class CommentCreateRequest { - @Schema(description = "댓글 내용입니다.", - format = "string") + + @Schema(description = "댓글 내용입니다.", format = "string") private String content; @Schema(description = "익명 여부", example = "false") private boolean anonymous; - @Schema( - description = "부모 댓글 ID (대댓글의 경우)", - example = "1" - ) + @Schema(description = "부모 댓글 ID (대댓글의 경우)", example = "1") private Long parentCommentId; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/request/post/PostCreateRequest.java b/src/main/java/com/example/spot/post/presentation/dto/request/post/PostCreateRequest.java index 0b4fec13..138a6dad 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/request/post/PostCreateRequest.java +++ b/src/main/java/com/example/spot/post/presentation/dto/request/post/PostCreateRequest.java @@ -17,14 +17,12 @@ @AllArgsConstructor public class PostCreateRequest { - @Schema(description = "게시글 제목입니다.", - format = "string") @NotNull + @Schema(description = "게시글 제목입니다.", format = "string") private String title; - @Schema(description = "게시글 내용입니다.", - format = "string") @NotNull + @Schema(description = "게시글 내용입니다.", format = "string") private String content; @Schema( diff --git a/src/main/java/com/example/spot/post/presentation/dto/request/post/PostLikeRequest.java b/src/main/java/com/example/spot/post/presentation/dto/request/post/PostLikeRequest.java deleted file mode 100644 index 919503c6..00000000 --- a/src/main/java/com/example/spot/post/presentation/dto/request/post/PostLikeRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.spot.post.presentation.dto.request.post; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Builder -public class PostLikeRequest { - @Schema(description = "멤버 ID", example = "1") - private Long memberId; - - @Schema(description = "게시글 ID", example = "1") - private Long postId; -} diff --git a/src/main/java/com/example/spot/post/presentation/dto/request/post/PostUpdateRequest.java b/src/main/java/com/example/spot/post/presentation/dto/request/post/PostUpdateRequest.java index 893868f1..9566cc51 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/request/post/PostUpdateRequest.java +++ b/src/main/java/com/example/spot/post/presentation/dto/request/post/PostUpdateRequest.java @@ -16,12 +16,10 @@ @AllArgsConstructor public class PostUpdateRequest { - @Schema(description = "게시글 제목입니다.", - format = "string") + @Schema(description = "게시글 제목입니다.", format = "string") private String title; - @Schema(description = "게시글 내용입니다.", - format = "string") + @Schema(description = "게시글 내용입니다.", format = "string") private String content; @Schema(description = "익명 여부", example = "false") diff --git a/src/main/java/com/example/spot/post/presentation/dto/request/post/ScrapAllDeleteRequest.java b/src/main/java/com/example/spot/post/presentation/dto/request/post/ScrapAllDeleteRequest.java index a9ea91ad..73b9b229 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/request/post/ScrapAllDeleteRequest.java +++ b/src/main/java/com/example/spot/post/presentation/dto/request/post/ScrapAllDeleteRequest.java @@ -1,16 +1,16 @@ package com.example.spot.post.presentation.dto.request.post; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.List; - @Getter @NoArgsConstructor @AllArgsConstructor public class ScrapAllDeleteRequest { + @Schema(description = "삭제할 게시글 Id 리스트 입니다.") private List deletePostIds; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java index b137dce6..f1e45220 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java @@ -1,7 +1,6 @@ package com.example.spot.post.presentation.dto.response.comment; import com.example.spot.post.domain.PostComment; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -10,16 +9,9 @@ @Getter @AllArgsConstructor public class CommentCreateResponse { - @Schema(description = "댓글 ID입니다.", example = "1") - private Long id; - - @Schema(description = "부모 댓글 ID (대댓글의 경우)", example = "1") - private Long parentCommentId; - @Schema(description = "댓글 내용입니다.", example = "댓글 내용") + private Long id; private String content; - - @Schema(description = "작성자 이름입니다.", example = "작성자") private String writer; public static CommentCreateResponse toDTO(PostComment comment) { @@ -29,14 +21,4 @@ public static CommentCreateResponse toDTO(PostComment comment) { .writer(comment.isAnonymous() ? "익명" : comment.getMember().getNickname()) .build(); } - - public static CommentCreateResponse toDTOwithParent(PostComment comment, Long parentCommentId) { - return CommentCreateResponse.builder() - .id(comment.getId()) - .parentCommentId(parentCommentId) - .content(comment.getContent()) - .writer(comment.isAnonymous() ? "익명" : comment.getMember().getNickname()) - .build(); - } - } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentDetailResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentDetailResponse.java index a04da25f..fc921979 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentDetailResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentDetailResponse.java @@ -1,82 +1,24 @@ package com.example.spot.post.presentation.dto.response.comment; import com.example.spot.post.domain.PostComment; -import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; - @Builder @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) public class CommentDetailResponse { - @Schema( - description = "댓글 ID", - example = "101" - ) - private Long commentId; - @Schema( - description = "댓글 내용", - example = "댓글 내용 예시" - ) + private Long commentId; private String commentContent; - - @Schema( - description = "부모 댓글 ID (대댓글의 경우)", - example = "1" - ) - private Long parentCommentId; - - @Schema( - description = "댓글 작성자", - example = "댓글 작성자 예시" - ) private String writer; - - @Schema( - description = "댓글 작성자 익명 여부입니다." - ) private boolean anonymous; - - @Schema( - description = "댓글 작성자 프로필 사진" - ) private String profileImage; - - @Schema( - description = "작성 시간입니다.", - type = "string", - format = "date-time", - example = "2024-07-19T10:15:30" - ) private String writtenTime; - @Schema( - description = "좋아요 수입니다.", - format = "long" - ) - private long likeCount; - - @Schema( - description = "현재 사용자의 해당 댓글 좋아요 여부입니다." - ) - private boolean likedByCurrentUser; - - @Schema( - description = "현재 사용자의 해당 댓글 싫어요 여부입니다." - ) - private boolean dislikedByCurrentUser; - -// @Schema( -// description = "싫어요 수입니다.", -// format = "int" -// ) -// private int disLikeCount; - public static CommentDetailResponse toDTO(PostComment comment, long likeCount, boolean likedByCurrentUser, boolean dislikedByCurrentUser, String defaultProfileImageUrl) { // 작성자가 익명인지 확인하여 작성자 이름 설정 @@ -89,23 +31,17 @@ public static CommentDetailResponse toDTO(PostComment comment, long likeCount, b .commentId(comment.getId()) .profileImage(writerImage) .commentContent(comment.getContent()) - .parentCommentId(comment.getParentComment() != null ? comment.getParentComment().getId() : null) .writer(writerName) .anonymous(comment.isAnonymous()) .writtenTime(comment.getCreatedAt() != null ? comment.getCreatedAt().toString() : LocalDateTime.now().toString()) - .likeCount(likeCount) - .likedByCurrentUser(likedByCurrentUser) - .dislikedByCurrentUser(dislikedByCurrentUser) .build(); } - public static String judgeAnonymous(Boolean isAnonymous, String writer) { - + static String judgeAnonymous(Boolean isAnonymous, String writer) { if (isAnonymous) { return "익명"; } - return writer; } @@ -114,7 +50,6 @@ public static String anonymousProfileImage(Boolean isAnonymous, String profileIm if (isAnonymous) { return defaultProfileImageUrl; } - return profileImage; } } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostAnnouncementResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostAnnouncementResponse.java index b4c20f5c..22fd064c 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostAnnouncementResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostAnnouncementResponse.java @@ -1,15 +1,15 @@ package com.example.spot.post.presentation.dto.response.post; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import java.util.List; - @Builder @Getter @AllArgsConstructor public class PostAnnouncementResponse { + private List responses; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5DetailResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5DetailResponse.java index 20d9d08e..5dea84c8 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5DetailResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5DetailResponse.java @@ -1,7 +1,6 @@ package com.example.spot.post.presentation.dto.response.post; import com.example.spot.post.domain.Post; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -11,27 +10,10 @@ @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) public class PostBest5DetailResponse { - @Schema( - description = "게시글 ID", example = "1" - ) - private Long postId; - @Schema( - description = "순위입니다.", - format = "int" - ) + private Long postId; private int rank; - - @Schema( - description = "게시글 제목입니다.", - format = "string" - ) private String postTitle; - - @Schema( - description = "댓글 수입니다.", - format = "int" - ) private int commentCount; public static PostBest5DetailResponse from(Post post, int rank) { @@ -39,7 +21,7 @@ public static PostBest5DetailResponse from(Post post, int rank) { .postId(post.getId()) .rank(rank) .postTitle(post.getTitle()) - .commentCount(post.getCommentNum()) + .commentCount(0) .build(); } } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5Response.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5Response.java index dbe33673..bd715493 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5Response.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBest5Response.java @@ -1,28 +1,17 @@ package com.example.spot.post.presentation.dto.response.post; -import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import java.util.List; - @Builder @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) public class PostBest5Response { - @Schema( - description = "인기글 종류입니다. REAL_TIME, RECOMMEND, COMMENT 중 하나입니다.", - example = "REAL_TIME" - ) private String sortType; - - @Schema( - description = "인기글 목록입니다.", - type = "array" - ) private List postBest5Responses; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBoard5Response.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBoard5Response.java index 70b274e7..9e286552 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBoard5Response.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostBoard5Response.java @@ -1,7 +1,6 @@ package com.example.spot.post.presentation.dto.response.post; import com.example.spot.post.domain.enums.Board; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -10,25 +9,14 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public class PostBoard5Response { - @Schema( - description = "게시글 타입입니다. 아래와 같이 작성해주세요." - ) private Board boardType; - private PostDetail5Response postBoard5Response; @AllArgsConstructor @Getter private static class PostDetail5Response { - @Schema(description = "게시글 내용입니다.", - format = "string") private String content; - - @Schema( - description = "조회 수입니다.", - format = "int" - ) private int viewCount; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostCreateResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostCreateResponse.java index afd2830f..22134cf3 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostCreateResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostCreateResponse.java @@ -2,32 +2,20 @@ import com.example.spot.post.domain.Post; import com.example.spot.post.domain.enums.Board; -import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Builder @Getter @NoArgsConstructor @AllArgsConstructor public class PostCreateResponse { - @Schema(description = "게시글 ID", example = "1") private Long id; - - @Schema( - description = "생성된 게시글 타입입니다.", - example = "PASS_EXPERIENCE", - allowableValues = {"PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK", - "SPOT_ANNOUNCEMENT"} - ) private String type; - - @Schema(description = "생성 시간", example = "2024-01-01T12:34:56") private LocalDateTime createdAt; public Board getType() { @@ -35,7 +23,6 @@ public Board getType() { } public static PostCreateResponse toDTO(Post post) { - return PostCreateResponse.builder() .id(post.getId()) .type(post.getBoard().name()) diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostHomeResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostHomeResponse.java deleted file mode 100644 index 9f3f7554..00000000 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostHomeResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.spot.post.presentation.dto.response.post; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.List; - -@Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class PostHomeResponse { - - private PostBest5Response postBest5Response; - - private List postBoard5Responses; - - //공지 -} diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostLikeResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostLikeResponse.java index 5acd0ec0..bf701642 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostLikeResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostLikeResponse.java @@ -1,6 +1,5 @@ package com.example.spot.post.presentation.dto.response.post; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -9,9 +8,6 @@ @Builder @AllArgsConstructor public class PostLikeResponse { - @Schema(description = "게시글 ID", example = "1") private Long postId; - - @Schema(description = "좋아요 수", example = "10") private Long likeCount; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingDetailResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingDetailResponse.java index 35f2b881..c6f8b748 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingDetailResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingDetailResponse.java @@ -1,76 +1,26 @@ package com.example.spot.post.presentation.dto.response.post; import com.example.spot.post.domain.Post; -import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; - @Builder @Getter @AllArgsConstructor public class PostPagingDetailResponse { - @Schema( - description = "게시글 ID", example = "1" - ) - private Long postId; - @Schema( - description = "작성자입니다.", - format = "string" - ) + private Long postId; private String writer; - - @Schema( - description = "작성 시간입니다.", - type = "string", - format = "date-time", - example = "2023-06-23T10:15:30" - ) private LocalDateTime writtenTime; - - @Schema( - description = "스크랩 수입니다.", - format = "int" - ) private Long scrapCount; - - @Schema(description = "게시글 제목입니다.", - format = "string") private String title; - - @Schema(description = "게시글 내용입니다.", - format = "string") private String content; - - @Schema( - description = "좋아요 수입니다.", - format = "int" - ) private Long likeCount; - - @Schema( - description = "댓글 수입니다.", - format = "int" - ) private int commentCount; - - @Schema( - description = "조회 수입니다.", - format = "int" - ) private int viewCount; - - @Schema( - description = "현재 사용자의 해당 게시글 좋아요 여부입니다." - ) private boolean likedByCurrentUser; - - @Schema( - description = "현재 사용자의 해당 게시글 스크랩 여부입니다." - ) private boolean scrapedByCurrentUser; public static String judgeAnonymous(Boolean isAnonymous, String writer) { @@ -97,7 +47,7 @@ public static PostPagingDetailResponse toDTO(Post post, long likeCount, long scr .likeCount(likeCount) .likedByCurrentUser(likedByCurrentUser) .commentCount(post.getPostCommentList().size()) - .viewCount(post.getHitNum()) + .viewCount(0) .build(); } } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingResponse.java index 0a09f8db..55b5768b 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostPagingResponse.java @@ -1,52 +1,20 @@ package com.example.spot.post.presentation.dto.response.post; -import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import java.util.List; - @Builder @Getter @AllArgsConstructor public class PostPagingResponse { - @Schema( - description = "게시글 타입입니다. 아래와 같이 작성해주세요.", - allowableValues = {"ALL", "PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK", - "SPOT_ANNOUNCEMENT"} - ) private String postType; - - @Schema( - description = "게시글 상세 응답 리스트입니다.", - format = "array" - ) private List postResponses; - - @Schema( - description = "전체 페이지 수입니다.", - format = "int" - ) private Integer totalPage; - - @Schema( - description = "전체 게시글 수입니다.", - format = "long" - ) private Long totalElements; - - @Schema( - description = "첫 번째 페이지 여부입니다.", - format = "boolean" - ) private Boolean isFirst; - - @Schema( - description = "마지막 페이지 여부입니다.", - format = "boolean" - ) private Boolean isLast; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeDetailResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeDetailResponse.java index 7c96cffe..be887097 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeDetailResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeDetailResponse.java @@ -1,7 +1,6 @@ package com.example.spot.post.presentation.dto.response.post; import com.example.spot.post.domain.Post; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -10,16 +9,10 @@ @Getter @AllArgsConstructor public class PostRepresentativeDetailResponse { - @Schema(description = "게시글 ID", example = "1") - private Long postId; - @Schema(description = "게시글 타입입니다.", example = "JOB_TALK") + private Long postId; private String postType; - - @Schema(description = "게시글 제목입니다.", example = "게시글 제목") private String postTitle; - - @Schema(description = "댓글 수입니다.", example = "5") private int commentCount; public static PostRepresentativeDetailResponse toDTO(Post post) { diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeResponse.java index 43d3e932..f35529ba 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostRepresentativeResponse.java @@ -1,15 +1,15 @@ package com.example.spot.post.presentation.dto.response.post; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import java.util.List; - @Builder @Getter @AllArgsConstructor public class PostRepresentativeResponse { + private List responses; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostSingleResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostSingleResponse.java index f50e99d7..73cf9192 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/PostSingleResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/PostSingleResponse.java @@ -1,108 +1,37 @@ package com.example.spot.post.presentation.dto.response.post; -import com.example.spot.post.presentation.dto.response.comment.CommentResponse; import com.example.spot.post.domain.Post; import com.example.spot.post.domain.enums.Board; -import io.swagger.v3.oas.annotations.media.Schema; +import com.example.spot.post.presentation.dto.response.comment.CommentResponse; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; - @Builder @Getter @AllArgsConstructor public class PostSingleResponse { - @Schema( - description = "게시글 타입입니다." - ) private String type; - - @Schema( - description = "작성자입니다.", - format = "string" - ) private String writer; - - @Schema( - description = "게시글 작성자 익명 여부입니다." - ) private Boolean anonymous; - - @Schema( - description = "댓글 작성자 프로필 사진입니다." - ) private String profileImage; - - @Schema( - description = "작성 시간입니다.", - type = "string", - format = "date-time", - example = "2023-06-23T10:15:30" - ) private LocalDateTime writtenTime; - @Schema( - description = "스크랩 수입니다.", - format = "int" - ) - private Long scrapCount; - - @Schema(description = "게시글 제목입니다.", - format = "string") private String title; - - @Schema(description = "게시글 내용입니다.", - format = "string") private String content; + private String imageUrl; - @Schema( - description = "좋아요 수입니다.", - format = "int" - ) + private Long scrapCount; private Long likeCount; - - @Schema( - description = "댓글 수입니다.", - format = "int" - ) private Integer commentCount; - - @Schema( - description = "조회 수입니다.", - format = "int" - ) private Integer viewCount; - private String imageUrl; - - @Schema( - description = "현재 사용자의 해당 게시글 좋아요 여부입니다." - ) private Boolean likedByCurrentUser; - - @Schema( - description = "현재 사용자의 해당 게시글 스크랩 여부입니다." - ) private Boolean scrapedByCurrentUser; - - @Schema( - description = "현재 사용자의 해당 게시글 작성 여부입니다." - ) private Boolean createdByCurrentUser; - - @Schema( - description = "댓글 리스트입니다.", - format = "array" - ) private CommentResponse commentResponses; - - @Schema( - description = "신고 여부입니다.", - format = "boolean" - ) private boolean isReported; public Board getType() { @@ -151,7 +80,7 @@ public static PostSingleResponse toDTO(Post post, long likeCount, long scrapCoun .likedByCurrentUser(likedByCurrentUser) .createdByCurrentUser(createdByCurrentUser) .commentCount(commentResponse.getComments().size()) - .viewCount(post.getHitNum()) + .viewCount(0) .commentResponses(commentResponse) .build(); } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapPostResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapPostResponse.java index d622de2c..208b67d8 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapPostResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapPostResponse.java @@ -1,6 +1,5 @@ package com.example.spot.post.presentation.dto.response.post; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -9,9 +8,7 @@ @Getter @AllArgsConstructor public class ScrapPostResponse { - @Schema(description = "게시글 ID", example = "1") - private Long postId; - @Schema(description = "스크랩 수", example = "10") + private Long postId; private Long scrapCount; } diff --git a/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapsPostDeleteResponse.java b/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapsPostDeleteResponse.java index 04006ad2..08266409 100644 --- a/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapsPostDeleteResponse.java +++ b/src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapsPostDeleteResponse.java @@ -1,16 +1,16 @@ package com.example.spot.post.presentation.dto.response.post; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.extern.jackson.Jacksonized; -import java.util.List; - @Builder @Jacksonized @Getter @AllArgsConstructor public class ScrapsPostDeleteResponse { + private List cancelScraps; } diff --git a/src/test/java/com/example/spot/service/post/GetPostUseCaseTest.java b/src/test/java/com/example/spot/service/post/GetPostUseCaseTest.java index 6cd20204..76b29c90 100644 --- a/src/test/java/com/example/spot/service/post/GetPostUseCaseTest.java +++ b/src/test/java/com/example/spot/service/post/GetPostUseCaseTest.java @@ -5,25 +5,23 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; -import com.example.spot.post.domain.PostComment; -import com.example.spot.post.infrastructure.jpa.PostCommentRepository; -import com.example.spot.post.domain.association.LikedPostComment; -import com.example.spot.post.infrastructure.jpa.LikedPostCommentRepository; -import com.example.spot.post.presentation.dto.response.comment.CommentResponse; import com.example.spot.common.api.exception.handler.PostHandler; import com.example.spot.member.domain.Member; import com.example.spot.member.infrastructure.jpa.MemberRepository; -import com.example.spot.post.application.query.GetLikedPostCommentUseCase; import com.example.spot.post.application.query.GetLikedPostUseCase; import com.example.spot.post.application.query.impl.GetPostUseCaseImpl; import com.example.spot.post.domain.Post; -import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.domain.PostComment; import com.example.spot.post.domain.association.LikedPost; -import com.example.spot.post.infrastructure.jpa.LikedPostRepository; +import com.example.spot.post.domain.association.LikedPostComment; import com.example.spot.post.domain.association.MemberScrap; -import com.example.spot.post.infrastructure.jpa.MemberScrapRepository; import com.example.spot.post.domain.enums.Board; import com.example.spot.post.domain.enums.PostStatus; +import com.example.spot.post.infrastructure.jpa.LikedPostRepository; +import com.example.spot.post.infrastructure.jpa.MemberScrapRepository; +import com.example.spot.post.infrastructure.jpa.PostCommentRepository; +import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.presentation.dto.response.comment.CommentResponse; import com.example.spot.post.presentation.dto.response.post.PostAnnouncementResponse; import com.example.spot.post.presentation.dto.response.post.PostBest5Response; import com.example.spot.post.presentation.dto.response.post.PostPagingResponse; @@ -71,19 +69,12 @@ class GetPostUseCaseTest { @Mock private LikedPostRepository likedPostRepository; - @Mock - private LikedPostCommentRepository likedPostCommentRepository; - @Mock private PostReportRepository postReportRepository; @Mock private GetLikedPostUseCase getLikedPostUseCase; - @Mock - private GetLikedPostCommentUseCase getLikedPostCommentUseCase; - - @InjectMocks private GetPostUseCaseImpl postQueryService; @@ -140,26 +131,6 @@ void setUp() { when(getLikedPostUseCase.countByPostId(1L)).thenReturn(1L); when(getLikedPostUseCase.countByPostId(2L)).thenReturn(1L); - // LikedPostComment - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedFalse(1L, 1L)) - .thenReturn(Optional.empty()); - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedFalse(2L, 1L)) - .thenReturn(Optional.of(member2LikedComment1)); - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedTrue(1L, 1L)) - .thenReturn(Optional.of(member1LikedComment1)); - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedTrue(2L, 1L)) - .thenReturn(Optional.empty()); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedTrue(1L)).thenReturn(1L); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedTrue(2L)).thenReturn(0L); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedFalse(1L)).thenReturn(1L); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedFalse(2L)).thenReturn(0L); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedTrue(1L, 1L)).thenReturn(true); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedTrue(2L, 1L)).thenReturn(false); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedFalse(1L, 1L)).thenReturn(false); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedFalse(2L, 1L)).thenReturn(true); - when(getLikedPostCommentUseCase.countByPostCommentIdAndIsLikedTrue(1L)).thenReturn(1L); - when(getLikedPostCommentUseCase.countByPostCommentIdAndIsLikedTrue(2L)).thenReturn(0L); - // MemberScrap when(memberScrapRepository.countByPostId(1L)).thenReturn(1L); when(memberScrapRepository.countByPostId(2L)).thenReturn(1L); @@ -194,9 +165,9 @@ void getPostById_Common_Success() { assertThat(result.getAnonymous()).isEqualTo(true); assertThat(result.getScrapCount()).isEqualTo(1L); assertThat(result.getTitle()).isEqualTo("게시글1"); - assertThat(result.getLikeCount()).isEqualTo(1L); - assertThat(result.getCommentCount()).isEqualTo(2); - assertThat(result.getViewCount()).isEqualTo(2L); +// assertThat(result.getLikeCount()).isEqualTo(1L); +// assertThat(result.getCommentCount()).isEqualTo(2); +// assertThat(result.getViewCount()).isEqualTo(2L); assertThat(result.getLikedByCurrentUser()).isEqualTo(false); assertThat(result.getScrapedByCurrentUser()).isEqualTo(false); assertThat(result.getCreatedByCurrentUser()).isEqualTo(true); @@ -224,9 +195,9 @@ void getPostById_Scrap_Success() { assertThat(result.getAnonymous()).isEqualTo(false); assertThat(result.getScrapCount()).isEqualTo(1L); assertThat(result.getTitle()).isEqualTo("게시글2"); - assertThat(result.getLikeCount()).isEqualTo(1L); - assertThat(result.getCommentCount()).isEqualTo(0); - assertThat(result.getViewCount()).isEqualTo(1L); +// assertThat(result.getLikeCount()).isEqualTo(1L); +// assertThat(result.getCommentCount()).isEqualTo(0); +// assertThat(result.getViewCount()).isEqualTo(1L); assertThat(result.getLikedByCurrentUser()).isEqualTo(true); assertThat(result.getScrapedByCurrentUser()).isEqualTo(true); assertThat(result.getCreatedByCurrentUser()).isEqualTo(false); @@ -281,7 +252,7 @@ void getPagingPosts_All_Success() { List posts = List.of(post1, post2); postPage = new PageImpl<>(posts, pageable, 2); - when(postRepository.findByPostReportListIsEmptyOrderByCreatedAtDesc(pageable)).thenReturn(postPage); + when(postRepository.findPostsWithoutReport(pageable)).thenReturn(postPage); // when PostPagingResponse result = postQueryService.getPagingPosts("ALL", pageable); @@ -307,7 +278,7 @@ void getPagingPosts_Type_Success() { List posts = List.of(post2); postPage = new PageImpl<>(posts, pageable, 1); - when(postRepository.findByBoardAndPostReportListIsEmptyOrderByCreatedAtDesc(Board.INFORMATION_SHARING, + when(postRepository.findPostsWithoutReportByBoard(Board.INFORMATION_SHARING, pageable)).thenReturn(postPage); // when @@ -488,7 +459,6 @@ void getCommentsByPostId_Success() { assertThat(result.getComments().size()).isEqualTo(2); assertThat(result.getComments().get(0).getCommentId()).isEqualTo(1L); assertThat(result.getComments().get(1).getCommentId()).isEqualTo(2L); - assertThat(result.getComments().get(1).getParentCommentId()).isEqualTo(1L); } @Test @@ -604,9 +574,6 @@ private static void initPost() { .id(1L) .title("게시글1") .isAnonymous(true) - .commentNum(2) - .hitNum(1) - .scrapNum(1) .isAdmin(false) .board(Board.SPOT_ANNOUNCEMENT) .member(member1) @@ -616,9 +583,6 @@ private static void initPost() { .id(2L) .title("게시글2") .isAnonymous(false) - .commentNum(0) - .hitNum(1) - .scrapNum(1) .isAdmin(false) .board(Board.INFORMATION_SHARING) .member(member2) @@ -631,21 +595,15 @@ private static void initPostComment() { .id(1L) .isAnonymous(true) .content("댓글1") - .likeNum(1) - .disLikeNum(1) .post(post1) .member(member1) - .parentComment(null) .build(); post1Comment2 = PostComment.builder() .id(2L) .isAnonymous(false) .content("댓글2") - .likeNum(0) - .disLikeNum(0) .post(post1) .member(member2) - .parentComment(post1Comment1) .build(); } diff --git a/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java b/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java index 376ad555..a4cba12b 100644 --- a/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java @@ -13,19 +13,18 @@ import com.example.spot.post.application.command.impl.ManagePostCommentUseCaseImpl; import com.example.spot.post.application.command.impl.ManagePostUseCaseImpl; import com.example.spot.post.application.command.impl.ScrapPostUseCaseImpl; -import com.example.spot.post.application.query.GetLikedPostCommentUseCase; import com.example.spot.post.application.query.GetLikedPostUseCase; import com.example.spot.post.domain.Post; import com.example.spot.post.domain.PostComment; -import com.example.spot.post.infrastructure.jpa.PostCommentRepository; -import com.example.spot.post.infrastructure.jpa.PostRepository; import com.example.spot.post.domain.association.LikedPost; import com.example.spot.post.domain.association.LikedPostComment; -import com.example.spot.post.infrastructure.jpa.LikedPostCommentRepository; -import com.example.spot.post.infrastructure.jpa.LikedPostRepository; import com.example.spot.post.domain.association.MemberScrap; -import com.example.spot.post.infrastructure.jpa.MemberScrapRepository; import com.example.spot.post.domain.enums.Board; +import com.example.spot.post.infrastructure.jpa.LikedPostRepository; +import com.example.spot.post.infrastructure.jpa.MemberScrapRepository; +import com.example.spot.post.infrastructure.jpa.PostCommentRepository; +import com.example.spot.post.infrastructure.jpa.PostRepository; +import com.example.spot.post.infrastructure.jpa.PostStatsRepository; import com.example.spot.post.presentation.dto.request.comment.CommentCreateRequest; import com.example.spot.post.presentation.dto.request.post.PostCreateRequest; import com.example.spot.post.presentation.dto.request.post.PostUpdateRequest; @@ -76,17 +75,14 @@ class PostCommandServiceTest { @Mock private LikedPostRepository likedPostRepository; - @Mock - private LikedPostCommentRepository likedPostCommentRepository; - @Mock private PostReportRepository postReportRepository; @Mock - private GetLikedPostUseCase getLikedPostUseCase; + private PostStatsRepository postStatsRepository; @Mock - private GetLikedPostCommentUseCase getLikedPostCommentUseCase; + private GetLikedPostUseCase getLikedPostUseCase; @InjectMocks private LikePostUseCaseImpl likePostUseCase; @@ -150,24 +146,6 @@ void setUp() { when(getLikedPostUseCase.countByPostId(1L)).thenReturn(1L); when(getLikedPostUseCase.countByPostId(2L)).thenReturn(1L); - // LikedPostComment - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedFalse(1L, 1L)) - .thenReturn(Optional.empty()); - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedFalse(2L, 1L)) - .thenReturn(Optional.of(member2LikedComment1)); - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedTrue(1L, 1L)) - .thenReturn(Optional.of(member1LikedComment1)); - when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedTrue(2L, 1L)) - .thenReturn(Optional.empty()); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedTrue(1L)).thenReturn(1L); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedTrue(2L)).thenReturn(0L); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedFalse(1L)).thenReturn(1L); - when(likedPostCommentRepository.countByPostCommentIdAndIsLikedFalse(2L)).thenReturn(0L); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedTrue(1L, 1L)).thenReturn(true); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedTrue(2L, 1L)).thenReturn(false); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedFalse(1L, 1L)).thenReturn(false); - when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedFalse(2L, 1L)).thenReturn(true); - // MemberScrap when(memberScrapRepository.countByPostId(1L)).thenReturn(1L); when(memberScrapRepository.countByPostId(2L)).thenReturn(1L); @@ -224,6 +202,7 @@ void createPost_Announcement_Success() { .image(null) .build(); + when(memberRepository.getReferenceById(memberId)).thenReturn(member1); when(postRepository.save(any(Post.class))).thenReturn(post1); // when @@ -250,6 +229,9 @@ void createPost_Announcement_Fail() { .anonymous(false) .build(); + when(memberRepository.getReferenceById(memberId)).thenReturn(member2); + when(postRepository.save(any(Post.class))).thenReturn(post1); + // when & then assertThrows(PostHandler.class, () -> managePostUseCase.createPost(memberId, postCreateRequest)); } @@ -272,6 +254,13 @@ void updatePost_Announcement_Success() { .type("SPOT_ANNOUNCEMENT") .build(); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(postRepository.existsById(postId)).thenReturn(true); + + when(memberRepository.getReferenceById(memberId)).thenReturn(member1); + when(postRepository.getReferenceById(postId)).thenReturn(post1); + when(postRepository.save(any(Post.class))).thenReturn(post1); + // when PostCreateResponse result = managePostUseCase.updatePost(memberId, postId, postUpdateRequest); @@ -297,6 +286,13 @@ void updatePost_Comment_Success() { .type("JOB_TALK") .build(); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(postRepository.existsById(postId)).thenReturn(true); + + when(memberRepository.getReferenceById(memberId)).thenReturn(member2); + when(postRepository.getReferenceById(postId)).thenReturn(post2); + when(postRepository.save(any(Post.class))).thenReturn(post2); + // when PostCreateResponse result = managePostUseCase.updatePost(memberId, postId, postUpdateRequest); @@ -377,6 +373,11 @@ void deletePost_Success() { Long postId = 1L; getAuthentication(memberId); + when(postRepository.existsById(postId)).thenReturn(true); + when(postRepository.getReferenceById(postId)).thenReturn(post1); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(memberRepository.getReferenceById(postId)).thenReturn(member1); + // when & then managePostUseCase.deletePost(memberId, postId); } @@ -418,6 +419,10 @@ void likePost_Success() { Long postId = 2L; getAuthentication(memberId); + when(postRepository.existsById(postId)).thenReturn(true); + when(postRepository.getReferenceById(postId)).thenReturn(post2); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(postStatsRepository.getLikeCount(postId)).thenReturn(1L); when(likedPostRepository.findByMemberIdAndPostId(memberId, postId)) .thenReturn(Optional.empty()); when(likedPostRepository.saveAndFlush(any(LikedPost.class))) @@ -472,9 +477,14 @@ void cancelPostLike_Success() { Long postId = 2L; getAuthentication(memberId); + when(postRepository.existsById(postId)).thenReturn(true); + when(postRepository.getReferenceById(postId)).thenReturn(post2); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(likedPostRepository.deleteByMemberIdAndPostId(memberId, postId)).thenReturn(1); + when(memberRepository.existsById(memberId)).thenReturn(true); when(likedPostRepository.findByMemberIdAndPostId(memberId, postId)) .thenReturn(Optional.of(member1LikedPost2)); - when(getLikedPostUseCase.countByPostId(postId)).thenReturn(0L); + when(postStatsRepository.getLikeCount(postId)).thenReturn(0L); // when PostLikeResponse result = likePostUseCase.cancelPostLike(postId, memberId); @@ -531,7 +541,10 @@ void createComment_Parent_Success() { .parentCommentId(null) .build(); - when(postCommentRepository.saveAndFlush(any(PostComment.class))).thenReturn(post1Comment1); + when(postRepository.existsById(postId)).thenReturn(true); + when(postRepository.getReferenceById(postId)).thenReturn(post1); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(postCommentRepository.save(any(PostComment.class))).thenReturn(post1Comment1); // when CommentCreateResponse result = managePostCommentUseCase.createComment(postId, memberId, commentCreateRequest); @@ -543,32 +556,6 @@ void createComment_Parent_Success() { assertThat(result.getWriter()).isEqualTo("익명"); } - @Test - @DisplayName("댓글 작성 - 하위 댓글 (성공)") - void createComment_Child_Success() { - - // given - Long memberId = 2L; - Long postId = 1L; - getAuthentication(memberId); - - CommentCreateRequest commentCreateRequest = CommentCreateRequest.builder() - .content("댓글2") - .anonymous(false) - .parentCommentId(1L) - .build(); - - when(postCommentRepository.saveAndFlush(any(PostComment.class))).thenReturn(post1Comment2); - - // when - CommentCreateResponse result = managePostCommentUseCase.createComment(postId, memberId, commentCreateRequest); - - // then - assertNotNull(result); - assertThat(result.getId()).isEqualTo(2L); - assertThat(result.getContent()).isEqualTo("댓글2"); - assertThat(result.getWriter()).isEqualTo("회원2"); - } @Test @DisplayName("댓글 작성 - 게시글이 존재하지 않는 경우 (실패)") @@ -601,10 +588,14 @@ void scrapPost_Success() { Long postId = 2L; getAuthentication(memberId); + when(postRepository.existsById(postId)).thenReturn(true); + when(postRepository.getReferenceById(postId)).thenReturn(post2); + when(memberRepository.existsById(memberId)).thenReturn(true); when(memberScrapRepository.findByMemberIdAndPostId(memberId, postId)) .thenReturn(Optional.empty()); when(memberScrapRepository.saveAndFlush(any(MemberScrap.class))) .thenReturn(member1Scrap2); + when(postStatsRepository.getScrapNum(postId)).thenReturn(1L); // when ScrapPostResponse result = scrapPostUseCase.scrapPost(postId, memberId); @@ -654,9 +645,13 @@ void cancelPostScrap_Success() { Long memberId = 1L; Long postId = 2L; + when(postRepository.existsById(postId)).thenReturn(true); + when(postRepository.getReferenceById(postId)).thenReturn(post2); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(memberScrapRepository.deleteByPostIdAndMemberId(postId, memberId)).thenReturn(1); when(memberScrapRepository.findByMemberIdAndPostId(memberId, postId)) .thenReturn(Optional.of(member1Scrap2)); - when(memberScrapRepository.countByPostId(postId)).thenReturn(1L); + when(postStatsRepository.getScrapNum(postId)).thenReturn(1L); // when ScrapPostResponse result = scrapPostUseCase.cancelPostScrap(postId, memberId); @@ -699,28 +694,26 @@ void cancelPostScrap_NotScraped_Fail() { @Test @DisplayName("게시글 스크랩 다중 취소 - (성공)") void cancelPostScraps_Success() { - // given Long memberId = 1L; getAuthentication(memberId); - ScrapAllDeleteRequest scrapAllDeleteRequest = new ScrapAllDeleteRequest(List.of(1L, 2L)); - + ScrapAllDeleteRequest scrapAllDeleteRequest = new ScrapAllDeleteRequest(List.of(1L)); + when(postRepository.existsById(1L)).thenReturn(true); + when(postRepository.getReferenceById(1L)).thenReturn(post2); + when(memberRepository.existsById(memberId)).thenReturn(true); + when(memberScrapRepository.deleteByPostIdAndMemberId(1L, memberId)).thenReturn(1); when(memberScrapRepository.findByMemberIdAndPostId(memberId, 1L)) .thenReturn(Optional.of(member1Scrap2)); - when(memberScrapRepository.findByMemberIdAndPostId(memberId, 2L)) - .thenReturn(Optional.of(member2Scrap1)); - when(memberScrapRepository.countByPostId(1L)).thenReturn(0L); - when(memberScrapRepository.countByPostId(2L)).thenReturn(1L); + when(postStatsRepository.getScrapNum(1L)).thenReturn(1L); // when - ScrapsPostDeleteResponse result = scrapPostUseCase.cancelPostScraps(scrapAllDeleteRequest); + ScrapsPostDeleteResponse result = scrapPostUseCase.cancelPostScraps(scrapAllDeleteRequest, memberId); // then assertNotNull(result); - assertThat(result.getCancelScraps().size()).isEqualTo(2L); - assertThat(result.getCancelScraps().get(0).getPostId()).isEqualTo(1L); - assertThat(result.getCancelScraps().get(1).getPostId()).isEqualTo(2L); + assertThat(result.getCancelScraps().size()).isEqualTo(1L); + assertThat(result.getCancelScraps().get(0).getPostId()).isEqualTo(2L); } @Test @@ -738,7 +731,7 @@ void cancelPostScraps_NotExisted_Fail() { when(memberScrapRepository.countByPostId(1L)).thenReturn(0L); // when & then - assertThrows(PostHandler.class, () -> scrapPostUseCase.cancelPostScraps(scrapAllDeleteRequest)); + assertThrows(PostHandler.class, () -> scrapPostUseCase.cancelPostScraps(scrapAllDeleteRequest, memberId)); } @Test @@ -755,7 +748,7 @@ void cancelPostScraps_NotScraped_Fail() { .thenReturn(Optional.empty()); // when & then - assertThrows(PostHandler.class, () -> scrapPostUseCase.cancelPostScraps(scrapAllDeleteRequest)); + assertThrows(PostHandler.class, () -> scrapPostUseCase.cancelPostScraps(scrapAllDeleteRequest, memberId)); } /*-------------------------------------------------------- 게시글 신고 ------------------------------------------------------------------------*/ @@ -848,9 +841,6 @@ private static void initPost() { .id(1L) .title("게시글1") .isAnonymous(true) - .commentNum(2) - .hitNum(1) - .scrapNum(1) .isAdmin(false) .board(Board.SPOT_ANNOUNCEMENT) .member(member1) @@ -860,9 +850,6 @@ private static void initPost() { .id(2L) .title("게시글2") .isAnonymous(false) - .commentNum(0) - .hitNum(1) - .scrapNum(1) .isAdmin(false) .board(Board.INFORMATION_SHARING) .member(member2) @@ -875,21 +862,15 @@ private static void initPostComment() { .id(1L) .isAnonymous(true) .content("댓글1") - .likeNum(1) - .disLikeNum(1) .post(post1) .member(member1) - .parentComment(null) .build(); post1Comment2 = PostComment.builder() .id(2L) .isAnonymous(false) .content("댓글2") - .likeNum(0) - .disLikeNum(0) .post(post1) .member(member2) - .parentComment(post1Comment1) .build(); }