From d1edf5892702b979c9c8edebcaba446e41f8b58a Mon Sep 17 00:00:00 2001 From: 5jisoo <56earls@gmail.com> Date: Sat, 17 Feb 2024 04:52:37 +0900 Subject: [PATCH 1/4] #103 Feat: get home tab API --- .../domain/repository/FollowRepository.java | 1 + .../post/controller/PostController.java | 30 +++++++++++-------- .../com/apps/pochak/post/domain/Post.java | 4 +++ .../domain/repository/PostRepository.java | 11 +++++++ .../apps/pochak/post/service/PostService.java | 10 +++++++ 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/apps/pochak/follow/domain/repository/FollowRepository.java b/src/main/java/com/apps/pochak/follow/domain/repository/FollowRepository.java index 40eaf7c0..b543c25c 100644 --- a/src/main/java/com/apps/pochak/follow/domain/repository/FollowRepository.java +++ b/src/main/java/com/apps/pochak/follow/domain/repository/FollowRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; import static com.apps.pochak.global.apiPayload.code.status.ErrorStatus.NOT_FOLLOW; diff --git a/src/main/java/com/apps/pochak/post/controller/PostController.java b/src/main/java/com/apps/pochak/post/controller/PostController.java index 4cdbe974..cedabc53 100644 --- a/src/main/java/com/apps/pochak/post/controller/PostController.java +++ b/src/main/java/com/apps/pochak/post/controller/PostController.java @@ -6,6 +6,7 @@ import com.apps.pochak.global.apiPayload.code.status.SuccessStatus; import com.apps.pochak.global.s3.ValidFile; import com.apps.pochak.like.service.LikeService; +import com.apps.pochak.post.dto.PostElements; import com.apps.pochak.post.dto.request.PostUploadRequest; import com.apps.pochak.post.dto.response.PostDetailResponse; import com.apps.pochak.post.service.PostService; @@ -27,12 +28,19 @@ public class PostController { private final PostService postService; - @DeleteMapping("/{postId}") - public ApiResponse deletePost( - @PathVariable("postId") final Long postId + @GetMapping("") + public ApiResponse getHomeTab(@PageableDefault(30) Pageable pageable) { + return ApiResponse.onSuccess(postService.getHomeTab(pageable)); + } + + @PostMapping(value = "", consumes = {APPLICATION_JSON_VALUE, MULTIPART_FORM_DATA_VALUE}) + public ApiResponse uploadPost( + @RequestPart(value = "postImage") + @ValidFile(message = "게시물 이미지는 필수로 전달해야 합니다.") final MultipartFile postImage, + @RequestPart("request") @Valid final PostUploadRequest request ) { - postService.deletePost(postId); - return ApiResponse.of(SUCCESS_DELETE_POST); + postService.savePost(postImage, request); + return ApiResponse.of(SUCCESS_UPLOAD_POST); } @GetMapping("/{postId}") @@ -42,13 +50,11 @@ public ApiResponse getPostDetail( return ApiResponse.onSuccess(postService.getPostDetail(postId)); } - @PostMapping(value = "", consumes = {APPLICATION_JSON_VALUE, MULTIPART_FORM_DATA_VALUE}) - public ApiResponse uploadPost( - @RequestPart(value = "postImage") - @ValidFile(message = "게시물 이미지는 필수로 전달해야 합니다.") final MultipartFile postImage, - @RequestPart("request") @Valid final PostUploadRequest request + @DeleteMapping("/{postId}") + public ApiResponse deletePost( + @PathVariable("postId") final Long postId ) { - postService.savePost(postImage, request); - return ApiResponse.of(SUCCESS_UPLOAD_POST); + postService.deletePost(postId); + return ApiResponse.of(SUCCESS_DELETE_POST); } } diff --git a/src/main/java/com/apps/pochak/post/domain/Post.java b/src/main/java/com/apps/pochak/post/domain/Post.java index 5d570f3c..8f246349 100644 --- a/src/main/java/com/apps/pochak/post/domain/Post.java +++ b/src/main/java/com/apps/pochak/post/domain/Post.java @@ -10,6 +10,8 @@ import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLRestriction; +import java.time.LocalDateTime; + import static com.apps.pochak.post.domain.PostStatus.PRIVATE; import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.FetchType.LAZY; @@ -31,6 +33,8 @@ public class Post extends BaseEntity { @Column(columnDefinition = "VARCHAR(255) DEFAULT 'PRIVATE'") private PostStatus postStatus; + private LocalDateTime allowedDate; + @ManyToOne(fetch = LAZY) @JoinColumn(name = "owner_id") private Member owner; diff --git a/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java b/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java index 88583537..3a199112 100644 --- a/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java +++ b/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java @@ -37,4 +37,15 @@ Page findPostByOwnerAndPostStatusOrderByCreatedDateDesc(final Member owner default Post findPostById(final Long postId) { return findById(postId).orElseThrow(() -> new GeneralException(INVALID_POST_ID)); } + + @Query("select distinct p from Post p " + + "join Tag t on p = t.post and p.postStatus = 'PUBLIC' and t.member.id in ( " + + "select f.receiver.id from Follow f where f.sender = :loginMember and f.status = 'ACTIVE' " + + ") " + + "order by p.allowedDate desc " + ) + Page findTaggedPostsOfFollowing( + @Param("loginMember") final Member loginMember, + final Pageable pageable + ); } diff --git a/src/main/java/com/apps/pochak/post/service/PostService.java b/src/main/java/com/apps/pochak/post/service/PostService.java index 4500dc0e..9fc630e6 100644 --- a/src/main/java/com/apps/pochak/post/service/PostService.java +++ b/src/main/java/com/apps/pochak/post/service/PostService.java @@ -2,6 +2,7 @@ import com.apps.pochak.comment.domain.repository.CommentRepository; import com.apps.pochak.comment.dto.response.CommentElements; +import com.apps.pochak.follow.domain.Follow; import com.apps.pochak.global.apiPayload.exception.GeneralException; import com.apps.pochak.login.jwt.JwtService; import com.apps.pochak.member.domain.Member; @@ -20,12 +21,15 @@ import com.apps.pochak.member.domain.repository.MemberRepository; import com.apps.pochak.post.domain.Post; import com.apps.pochak.post.domain.repository.PostRepository; +import com.apps.pochak.post.dto.PostElements; import com.apps.pochak.post.dto.request.PostUploadRequest; import com.apps.pochak.post.dto.response.PostDetailResponse; import com.apps.pochak.tag.domain.Tag; import com.apps.pochak.tag.domain.repository.TagRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -133,4 +137,10 @@ public void savePost( ).collect(Collectors.toList()); alarmRepository.saveAll(tagApprovalAlarmList); } + + public PostElements getHomeTab(Pageable pageable) { + final Member loginMember = jwtService.getLoginMember(); + final Page taggedPost = postRepository.findTaggedPostsOfFollowing(loginMember, pageable); + return PostElements.from(taggedPost); + } } From 896c627092b070154ec1a38cf9fcfd9d166f374f Mon Sep 17 00:00:00 2001 From: 5jisoo <56earls@gmail.com> Date: Sat, 17 Feb 2024 05:01:09 +0900 Subject: [PATCH 2/4] =?UTF-8?q?#86=20Fix:=20N+1=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/MemberRepository.java | 7 +++- .../apps/pochak/post/service/PostService.java | 37 ++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/apps/pochak/member/domain/repository/MemberRepository.java b/src/main/java/com/apps/pochak/member/domain/repository/MemberRepository.java index 1df411de..b0e74a9e 100644 --- a/src/main/java/com/apps/pochak/member/domain/repository/MemberRepository.java +++ b/src/main/java/com/apps/pochak/member/domain/repository/MemberRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.security.core.parameters.P; import java.util.List; import java.util.Optional; @@ -12,7 +13,11 @@ import static com.apps.pochak.global.apiPayload.code.status.ErrorStatus.INVALID_MEMBER_HANDLE; public interface MemberRepository extends JpaRepository { - Optional findMemberByHandle(String handle); + Optional findMemberByHandle(final String handle); + + @Query("select m from Member m " + + "where m.handle in :handleList") + List findMemberByHandleList(@Param("handleList") final List handle); Optional findMemberBySocialId(String socialId); diff --git a/src/main/java/com/apps/pochak/post/service/PostService.java b/src/main/java/com/apps/pochak/post/service/PostService.java index 9fc630e6..ac3d1481 100644 --- a/src/main/java/com/apps/pochak/post/service/PostService.java +++ b/src/main/java/com/apps/pochak/post/service/PostService.java @@ -56,15 +56,10 @@ public class PostService { private final S3Service s3Service; private final JwtService jwtService; - @Transactional - public void deletePost(final Long postId) { + public PostElements getHomeTab(Pageable pageable) { final Member loginMember = jwtService.getLoginMember(); - final Post post = postRepository.findById(postId).orElseThrow(() -> new GeneralException(INVALID_POST_ID)); - if (!post.getOwner().getId().equals(loginMember.getId())) { - throw new GeneralException(NOT_YOUR_POST); - } - postRepository.delete(post); - commentRepository.bulkDeleteByPost(post); + final Page taggedPost = postRepository.findTaggedPostsOfFollowing(loginMember, pageable); + return PostElements.from(taggedPost); } public PostDetailResponse getPostDetail(final Long postId) { @@ -114,21 +109,24 @@ public void savePost( final String image = s3Service.upload(postImage, POST); final Post post = request.toEntity(image, loginMember); postRepository.save(post); - final List taggedMemberHandles = request.getTaggedMemberHandleList(); - // TODO: N+1 고치기 - final List taggedMemberList = taggedMemberHandles.stream().map( - memberRepository::findByHandle - ).collect(Collectors.toList()); + final List taggedMemberHandles = request.getTaggedMemberHandleList(); + final List taggedMemberList = memberRepository.findMemberByHandleList(taggedMemberHandles); + final List tagList = saveTags(taggedMemberList, post); + saveTagApprovalAlarms(tagList); + } + private List saveTags(List taggedMemberList, Post post) { final List tagList = taggedMemberList.stream().map( member -> Tag.builder() .member(member) .post(post) .build() ).collect(Collectors.toList()); - tagRepository.saveAll(tagList); + return tagRepository.saveAll(tagList); + } + private void saveTagApprovalAlarms(List tagList) { final List tagApprovalAlarmList = tagList.stream().map( tag -> TagApprovalAlarm.builder() .tag(tag) @@ -138,9 +136,14 @@ public void savePost( alarmRepository.saveAll(tagApprovalAlarmList); } - public PostElements getHomeTab(Pageable pageable) { + @Transactional + public void deletePost(final Long postId) { final Member loginMember = jwtService.getLoginMember(); - final Page taggedPost = postRepository.findTaggedPostsOfFollowing(loginMember, pageable); - return PostElements.from(taggedPost); + final Post post = postRepository.findById(postId).orElseThrow(() -> new GeneralException(INVALID_POST_ID)); + if (!post.getOwner().getId().equals(loginMember.getId())) { + throw new GeneralException(NOT_YOUR_POST); + } + postRepository.delete(post); + commentRepository.bulkDeleteByPost(post); } } From 3c4b6150b233f36c19a37089b5c3ea6a3db483d0 Mon Sep 17 00:00:00 2001 From: 5jisoo <56earls@gmail.com> Date: Sat, 17 Feb 2024 05:59:29 +0900 Subject: [PATCH 3/4] =?UTF-8?q?#86=20Fix:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20@RequestParam=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/post.adoc | 15 ++-- .../post/controller/PostController.java | 21 ++--- .../domain/repository/PostRepository.java | 4 +- .../post/dto/request/PostUploadRequest.java | 5 ++ .../apps/pochak/post/service/PostService.java | 23 +----- src/main/resources/static/docs/post.html | 82 +++++++++---------- src/main/resources/static/docs/profile.html | 6 +- .../post/controller/PostControllerTest.java | 34 ++------ 8 files changed, 72 insertions(+), 118 deletions(-) diff --git a/src/docs/asciidoc/post.adoc b/src/docs/asciidoc/post.adoc index eadff7f4..ed73945b 100644 --- a/src/docs/asciidoc/post.adoc +++ b/src/docs/asciidoc/post.adoc @@ -18,19 +18,16 @@ endif::[] === Request include::{snippets}/upload-post/curl-request.adoc[] + +==== request headers +include::{snippets}/upload-post/request-headers.adoc[] + ==== request parts include::{snippets}/upload-post/request-parts.adoc[] -- request는 `application/json` 타입으로 다음과 같이 전달합니다. - -```json -{ - "caption" : "게시물 내용", - "taggedMemberHandleList" : ["habongee"] -} -``` +==== query params +include::{snippets}/upload-post/query-parameters.adoc[] -include::{snippets}/upload-post/request-part-request-fields.adoc[] === Response ==== response body diff --git a/src/main/java/com/apps/pochak/post/controller/PostController.java b/src/main/java/com/apps/pochak/post/controller/PostController.java index cedabc53..c4205de1 100644 --- a/src/main/java/com/apps/pochak/post/controller/PostController.java +++ b/src/main/java/com/apps/pochak/post/controller/PostController.java @@ -1,26 +1,17 @@ package com.apps.pochak.post.controller; -import com.apps.pochak.comment.dto.response.CommentElements; -import com.apps.pochak.comment.service.CommentService; import com.apps.pochak.global.apiPayload.ApiResponse; -import com.apps.pochak.global.apiPayload.code.status.SuccessStatus; -import com.apps.pochak.global.s3.ValidFile; -import com.apps.pochak.like.service.LikeService; import com.apps.pochak.post.dto.PostElements; import com.apps.pochak.post.dto.request.PostUploadRequest; import com.apps.pochak.post.dto.response.PostDetailResponse; import com.apps.pochak.post.service.PostService; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import static com.apps.pochak.comment.service.CommentService.DEFAULT_PAGING_SIZE; -import static com.apps.pochak.global.apiPayload.code.status.SuccessStatus.*; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import static com.apps.pochak.global.apiPayload.code.status.SuccessStatus.SUCCESS_DELETE_POST; +import static com.apps.pochak.global.apiPayload.code.status.SuccessStatus.SUCCESS_UPLOAD_POST; @RestController @RequiredArgsConstructor @@ -33,13 +24,11 @@ public ApiResponse getHomeTab(@PageableDefault(30) Pageable pageab return ApiResponse.onSuccess(postService.getHomeTab(pageable)); } - @PostMapping(value = "", consumes = {APPLICATION_JSON_VALUE, MULTIPART_FORM_DATA_VALUE}) + @PostMapping(value = "") public ApiResponse uploadPost( - @RequestPart(value = "postImage") - @ValidFile(message = "게시물 이미지는 필수로 전달해야 합니다.") final MultipartFile postImage, - @RequestPart("request") @Valid final PostUploadRequest request + @ModelAttribute final PostUploadRequest request ) { - postService.savePost(postImage, request); + postService.savePost(request); return ApiResponse.of(SUCCESS_UPLOAD_POST); } diff --git a/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java b/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java index 3a199112..1cb3efac 100644 --- a/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java +++ b/src/main/java/com/apps/pochak/post/domain/repository/PostRepository.java @@ -39,9 +39,9 @@ default Post findPostById(final Long postId) { } @Query("select distinct p from Post p " + - "join Tag t on p = t.post and p.postStatus = 'PUBLIC' and t.member.id in ( " + + "join Tag t on p = t.post and p.postStatus = 'PUBLIC' and t.status = 'ACTIVE' and ( t.member.id in ( " + "select f.receiver.id from Follow f where f.sender = :loginMember and f.status = 'ACTIVE' " + - ") " + + ") or t.member = :loginMember ) " + "order by p.allowedDate desc " ) Page findTaggedPostsOfFollowing( diff --git a/src/main/java/com/apps/pochak/post/dto/request/PostUploadRequest.java b/src/main/java/com/apps/pochak/post/dto/request/PostUploadRequest.java index e725c787..3603dc3c 100644 --- a/src/main/java/com/apps/pochak/post/dto/request/PostUploadRequest.java +++ b/src/main/java/com/apps/pochak/post/dto/request/PostUploadRequest.java @@ -1,5 +1,6 @@ package com.apps.pochak.post.dto.request; +import com.apps.pochak.global.s3.ValidFile; import com.apps.pochak.member.domain.Member; import com.apps.pochak.post.domain.Post; import jakarta.validation.Valid; @@ -8,6 +9,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -15,6 +17,9 @@ @NoArgsConstructor @AllArgsConstructor public class PostUploadRequest { + @ValidFile(message = "게시물 이미지는 필수로 전달해야 합니다.") + private MultipartFile postImage; + private String caption; @Valid diff --git a/src/main/java/com/apps/pochak/post/service/PostService.java b/src/main/java/com/apps/pochak/post/service/PostService.java index ac3d1481..d7451933 100644 --- a/src/main/java/com/apps/pochak/post/service/PostService.java +++ b/src/main/java/com/apps/pochak/post/service/PostService.java @@ -1,13 +1,5 @@ package com.apps.pochak.post.service; -import com.apps.pochak.comment.domain.repository.CommentRepository; -import com.apps.pochak.comment.dto.response.CommentElements; -import com.apps.pochak.follow.domain.Follow; -import com.apps.pochak.global.apiPayload.exception.GeneralException; -import com.apps.pochak.login.jwt.JwtService; -import com.apps.pochak.member.domain.Member; -import com.apps.pochak.post.domain.Post; -import com.apps.pochak.post.domain.repository.PostRepository; import com.apps.pochak.alarm.domain.TagApprovalAlarm; import com.apps.pochak.alarm.domain.repository.AlarmRepository; import com.apps.pochak.comment.domain.Comment; @@ -31,17 +23,13 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.stream.Collectors; -import static com.apps.pochak.global.apiPayload.code.status.ErrorStatus.PRIVATE_POST; +import static com.apps.pochak.global.apiPayload.code.status.ErrorStatus.*; import static com.apps.pochak.global.s3.DirName.POST; -import static com.apps.pochak.global.apiPayload.code.status.ErrorStatus.INVALID_POST_ID; -import static com.apps.pochak.global.apiPayload.code.status.ErrorStatus.NOT_YOUR_POST; - @Service @RequiredArgsConstructor public class PostService { @@ -61,7 +49,7 @@ public PostElements getHomeTab(Pageable pageable) { final Page taggedPost = postRepository.findTaggedPostsOfFollowing(loginMember, pageable); return PostElements.from(taggedPost); } - + public PostDetailResponse getPostDetail(final Long postId) { final Member loginMember = jwtService.getLoginMember(); final Post post = postRepository.findPostById(postId); @@ -101,12 +89,9 @@ private Boolean isMyPost(final Post post, } @Transactional - public void savePost( - final MultipartFile postImage, - final PostUploadRequest request - ) { + public void savePost(final PostUploadRequest request) { final Member loginMember = jwtService.getLoginMember(); - final String image = s3Service.upload(postImage, POST); + final String image = s3Service.upload(request.getPostImage(), POST); final Post post = request.toEntity(image, loginMember); postRepository.save(post); diff --git a/src/main/resources/static/docs/post.html b/src/main/resources/static/docs/post.html index 18dfaca2..3c345b8f 100644 --- a/src/main/resources/static/docs/post.html +++ b/src/main/resources/static/docs/post.html @@ -452,7 +452,9 @@

POST API