From 207c16f4b0db0c5a51601dbc3d73f3a86819a594 Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Sat, 19 Apr 2025 20:10:38 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=ED=94=BD=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=83=81=EC=84=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LoadPostDetailController.java | 29 +++++ .../dto/response/LoadPostDetailResponse.java | 53 +++++++++ .../post/dto/response/PostImageResponse.java | 22 ++++ .../response/PostProductImageResponse.java | 22 ++++ .../dto/response/PostProductResponse.java | 31 ++++++ .../post/dto/response/PostWriterResponse.java | 25 +++++ .../post/PostDomainPersistenceAdapter.java | 105 +++++++++++++++--- .../out/persistence/model/PostJpaEntity.java | 11 ++ .../repository/PostImageRepository.java | 7 +- .../PostProductImageCustomRepository.java | 10 ++ .../PostProductImageCustomRepositoryImpl.java | 25 +++++ .../PostProductImageRepository.java | 2 +- .../repository/PostProductRepository.java | 7 +- .../port/in/post/LoadPostDetailUseCase.java | 11 ++ .../persistence/post/LoadPostImagePort.java | 12 ++ .../out/persistence/post/LoadPostPort.java | 12 ++ .../post/LoadPostProductImagePort.java | 13 +++ .../persistence/post/LoadPostProductPort.java | 12 ++ .../persistence/post/LoadUserForPostPort.java | 12 ++ .../post/LoadUserImageForPostPort.java | 12 ++ .../out/persistence/post/UpdatePostPort.java | 10 ++ .../application/query/FindByPostIdQuery.java | 17 +++ .../query/FindByPostProductIdsQuery.java | 18 +++ .../service/post/LoadPostDetailService.java | 79 +++++++++++++ .../application/vo/post/PostDetailVo.java | 63 +++++++++++ .../vo/post/PostProductDetailVo.java | 43 +++++++ .../response/enums/ErrorResponseCode.java | 1 + .../com/ftm/server/domain/entity/Post.java | 4 + .../security/SecurityConfig.java | 3 +- 29 files changed, 654 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/ftm/server/adapter/in/web/post/controller/LoadPostDetailController.java create mode 100644 src/main/java/com/ftm/server/adapter/in/web/post/dto/response/LoadPostDetailResponse.java create mode 100644 src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostImageResponse.java create mode 100644 src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductImageResponse.java create mode 100644 src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductResponse.java create mode 100644 src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostWriterResponse.java create mode 100644 src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepository.java create mode 100644 src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepositoryImpl.java create mode 100644 src/main/java/com/ftm/server/application/port/in/post/LoadPostDetailUseCase.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostImagePort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductImagePort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserForPostPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserImageForPostPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/post/UpdatePostPort.java create mode 100644 src/main/java/com/ftm/server/application/query/FindByPostIdQuery.java create mode 100644 src/main/java/com/ftm/server/application/query/FindByPostProductIdsQuery.java create mode 100644 src/main/java/com/ftm/server/application/service/post/LoadPostDetailService.java create mode 100644 src/main/java/com/ftm/server/application/vo/post/PostDetailVo.java create mode 100644 src/main/java/com/ftm/server/application/vo/post/PostProductDetailVo.java diff --git a/src/main/java/com/ftm/server/adapter/in/web/post/controller/LoadPostDetailController.java b/src/main/java/com/ftm/server/adapter/in/web/post/controller/LoadPostDetailController.java new file mode 100644 index 0000000..49e47e2 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/post/controller/LoadPostDetailController.java @@ -0,0 +1,29 @@ +package com.ftm.server.adapter.in.web.post.controller; + +import com.ftm.server.adapter.in.web.post.dto.response.LoadPostDetailResponse; +import com.ftm.server.application.port.in.post.LoadPostDetailUseCase; +import com.ftm.server.application.query.FindByIdQuery; +import com.ftm.server.application.vo.post.PostDetailVo; +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class LoadPostDetailController { + + private final LoadPostDetailUseCase loadPostDetailUseCase; + + @GetMapping("/api/posts/{postId}") + public ResponseEntity> loadPostDetail(@PathVariable Long postId) { + PostDetailVo vo = loadPostDetailUseCase.execute(FindByIdQuery.of(postId)); + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(SuccessResponseCode.OK, LoadPostDetailResponse.from(vo))); + } +} diff --git a/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/LoadPostDetailResponse.java b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/LoadPostDetailResponse.java new file mode 100644 index 0000000..1008297 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/LoadPostDetailResponse.java @@ -0,0 +1,53 @@ +package com.ftm.server.adapter.in.web.post.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ftm.server.application.vo.post.PostDetailVo; +import com.ftm.server.domain.enums.GroomingCategory; +import com.ftm.server.domain.enums.HashTag; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import lombok.Getter; + +@Getter +public class LoadPostDetailResponse { + + private final Long postId; + private final String title; + private final String content; + private final GroomingCategory groomingCategory; + private final List hashTags; + private final Integer viewCount; + private final Integer likeCount; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm", shape = JsonFormat.Shape.STRING) + private final LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm", shape = JsonFormat.Shape.STRING) + private final LocalDateTime updatedAt; + + private final List postImages; + private final PostWriterResponse writer; + private final List postProducts; + + private LoadPostDetailResponse(PostDetailVo postDetailVo) { + this.postId = postDetailVo.getPostId(); + this.title = postDetailVo.getTitle(); + this.content = postDetailVo.getContent(); + this.groomingCategory = postDetailVo.getGroomingCategory(); + this.hashTags = Arrays.stream(postDetailVo.getHashTags()).map(HashTag::getValue).toList(); + this.viewCount = postDetailVo.getViewCount(); + this.likeCount = postDetailVo.getLikeCount(); + this.createdAt = postDetailVo.getCreatedAt(); + this.updatedAt = postDetailVo.getUpdatedAt(); + this.postImages = + postDetailVo.getPostImages().stream().map(PostImageResponse::from).toList(); + this.writer = PostWriterResponse.from(postDetailVo.getUser(), postDetailVo.getUserImage()); + this.postProducts = + postDetailVo.getProducts().stream().map(PostProductResponse::from).toList(); + } + + public static LoadPostDetailResponse from(PostDetailVo postDetailVo) { + return new LoadPostDetailResponse(postDetailVo); + } +} diff --git a/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostImageResponse.java b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostImageResponse.java new file mode 100644 index 0000000..dc17351 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostImageResponse.java @@ -0,0 +1,22 @@ +package com.ftm.server.adapter.in.web.post.dto.response; + +import static com.ftm.server.common.consts.PropertiesHolder.CDN_PATH; + +import com.ftm.server.domain.entity.PostImage; +import lombok.Getter; + +@Getter +public class PostImageResponse { + + private final Long postImageId; + private final String imageUrl; + + private PostImageResponse(PostImage postImage) { + this.postImageId = postImage.getId(); + this.imageUrl = CDN_PATH + "/" + postImage.getObjectKey(); + } + + public static PostImageResponse from(PostImage postImage) { + return new PostImageResponse(postImage); + } +} diff --git a/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductImageResponse.java b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductImageResponse.java new file mode 100644 index 0000000..bda1de5 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductImageResponse.java @@ -0,0 +1,22 @@ +package com.ftm.server.adapter.in.web.post.dto.response; + +import static com.ftm.server.common.consts.PropertiesHolder.CDN_PATH; + +import com.ftm.server.domain.entity.PostProductImage; +import lombok.Getter; + +@Getter +public class PostProductImageResponse { + + private final Long postProductImageId; + private final String imageUrl; + + private PostProductImageResponse(PostProductImage postProductImage) { + this.postProductImageId = postProductImage.getId(); + this.imageUrl = CDN_PATH + "/" + postProductImage.getObjectKey(); + } + + public static PostProductImageResponse from(PostProductImage postProductImage) { + return new PostProductImageResponse(postProductImage); + } +} diff --git a/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductResponse.java b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductResponse.java new file mode 100644 index 0000000..895d6fe --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostProductResponse.java @@ -0,0 +1,31 @@ +package com.ftm.server.adapter.in.web.post.dto.response; + +import com.ftm.server.application.vo.post.PostProductDetailVo; +import com.ftm.server.domain.enums.HashTag; +import java.util.Arrays; +import java.util.List; +import lombok.Getter; + +@Getter +public class PostProductResponse { + + private final Long postProductId; + private final String name; + private final String brand; + private final List hashTags; + private final PostProductImageResponse postProductImage; + + private PostProductResponse(PostProductDetailVo postProductDetailVo) { + this.postProductId = postProductDetailVo.getPostProductId(); + this.name = postProductDetailVo.getName(); + this.brand = postProductDetailVo.getBrand(); + this.hashTags = + Arrays.stream(postProductDetailVo.getHashTags()).map(HashTag::getValue).toList(); + this.postProductImage = + PostProductImageResponse.from(postProductDetailVo.getPostProductImage()); + } + + public static PostProductResponse from(PostProductDetailVo postProductDetailVo) { + return new PostProductResponse(postProductDetailVo); + } +} diff --git a/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostWriterResponse.java b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostWriterResponse.java new file mode 100644 index 0000000..626ce35 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/post/dto/response/PostWriterResponse.java @@ -0,0 +1,25 @@ +package com.ftm.server.adapter.in.web.post.dto.response; + +import static com.ftm.server.common.consts.PropertiesHolder.CDN_PATH; + +import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.entity.UserImage; +import lombok.Getter; + +@Getter +public class PostWriterResponse { + + private final Long userId; + private final String nickname; + private final String imageUrl; + + private PostWriterResponse(User user, UserImage userImage) { + this.userId = user.getId(); + this.nickname = user.getNickname(); + this.imageUrl = CDN_PATH + "/" + userImage.getObjectKey(); + } + + public static PostWriterResponse from(User user, UserImage userImage) { + return new PostWriterResponse(user, userImage); + } +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/post/PostDomainPersistenceAdapter.java b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/post/PostDomainPersistenceAdapter.java index 9d9b8fa..d6e1302 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/post/PostDomainPersistenceAdapter.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/post/PostDomainPersistenceAdapter.java @@ -1,40 +1,52 @@ package com.ftm.server.adapter.out.persistence.adapter.post; -import com.ftm.server.adapter.out.persistence.mapper.PostImageMapper; -import com.ftm.server.adapter.out.persistence.mapper.PostMapper; -import com.ftm.server.adapter.out.persistence.mapper.PostProductImageMapper; -import com.ftm.server.adapter.out.persistence.mapper.PostProductMapper; +import com.ftm.server.adapter.out.persistence.mapper.*; import com.ftm.server.adapter.out.persistence.model.*; import com.ftm.server.adapter.out.persistence.repository.*; -import com.ftm.server.application.port.out.persistence.post.SavePostImagePort; -import com.ftm.server.application.port.out.persistence.post.SavePostPort; -import com.ftm.server.application.port.out.persistence.post.SavePostProductImagePort; -import com.ftm.server.application.port.out.persistence.post.SavePostProductPort; +import com.ftm.server.application.port.out.persistence.post.*; +import com.ftm.server.application.query.FindByIdQuery; +import com.ftm.server.application.query.FindByPostIdQuery; +import com.ftm.server.application.query.FindByPostProductIdsQuery; +import com.ftm.server.application.query.FindByUserIdQuery; import com.ftm.server.common.annotation.Adapter; import com.ftm.server.common.exception.CustomException; import com.ftm.server.common.response.enums.ErrorResponseCode; -import com.ftm.server.domain.entity.Post; -import com.ftm.server.domain.entity.PostImage; -import com.ftm.server.domain.entity.PostProduct; -import com.ftm.server.domain.entity.PostProductImage; +import com.ftm.server.domain.entity.*; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @Adapter @RequiredArgsConstructor public class PostDomainPersistenceAdapter - implements SavePostPort, SavePostImagePort, SavePostProductPort, SavePostProductImagePort { + implements SavePostPort, + SavePostImagePort, + SavePostProductPort, + SavePostProductImagePort, + LoadPostPort, + LoadPostImagePort, + LoadPostProductPort, + LoadPostProductImagePort, + LoadUserForPostPort, + LoadUserImageForPostPort, + UpdatePostPort { private final PostRepository postRepository; private final PostImageRepository postImageRepository; private final PostProductRepository postProductRepository; private final PostProductImageRepository postProductImageRepository; private final UserRepository userRepository; + private final UserImageRepository userImageRepository; private final PostMapper postMapper; private final PostImageMapper postImageMapper; private final PostProductMapper postProductMapper; private final PostProductImageMapper postProductImageMapper; + private final UserMapper userMapper; + private final UserImageMapper userImageMapper; @Override public Post savePost(Post post) { @@ -119,4 +131,71 @@ public List savePostProductImages(List produ .map(postProductImageMapper::toDomainEntity) .toList(); } + + @Override + public Optional loadPost(FindByIdQuery query) { + return postRepository.findById(query.getId()).map(postMapper::toDomainEntity); + } + + @Override + public List loadPostImagesByPostId(FindByPostIdQuery query) { + PostJpaEntity postJpaEntity = + postRepository + .findById(query.getPostId()) + .orElseThrow(() -> new CustomException(ErrorResponseCode.POST_NOT_FOUND)); + + return postImageRepository.findAllByPost(postJpaEntity).stream() + .map(postImageMapper::toDomainEntity) + .toList(); + } + + @Override + public List loadPostProductsByPostId(FindByPostIdQuery query) { + PostJpaEntity postJpaEntity = + postRepository + .findById(query.getPostId()) + .orElseThrow(() -> new CustomException(ErrorResponseCode.POST_NOT_FOUND)); + + return postProductRepository.findAllByPost(postJpaEntity).stream() + .map(postProductMapper::toDomainEntity) + .toList(); + } + + @Override + public Map loadPostProductImagesByPostProductIds( + FindByPostProductIdsQuery query) { + List postProductImageJpaEntities = + postProductImageRepository.findByPostProductIds(query); + + return postProductImageJpaEntities.stream() + .map(postProductImageMapper::toDomainEntity) + .collect(Collectors.toMap(PostProductImage::getPostProductId, Function.identity())); + } + + @Override + public Optional loadUserById(FindByIdQuery query) { + return userRepository.findById(query.getId()).map(userMapper::toDomainEntity); + } + + @Override + public Optional loadUserImageByUserId(FindByUserIdQuery query) { + UserJpaEntity userJpaEntity = + userRepository + .findById(query.getUserId()) + .orElseThrow(() -> CustomException.USER_NOT_FOUND); + + return userImageRepository + .findByUserId(query.getUserId()) + .map(userImageMapper::toDomainEntity); + } + + @Override + public void updatePost(Post post) { + PostJpaEntity postJpaEntity = + postRepository + .findById(post.getId()) + .orElseThrow(() -> new CustomException(ErrorResponseCode.POST_NOT_FOUND)); + + postJpaEntity.updatePostForDomainEntity(post); + } } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java b/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java index a0399ca..1361380 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java @@ -91,4 +91,15 @@ public static PostJpaEntity from(Post post, UserJpaEntity userJpaEntity) { .deletedAt(post.getDeletedAt()) .build(); } + + public void updatePostForDomainEntity(Post post) { + this.title = post.getTitle(); + this.content = post.getContent(); + this.groomingCategory = post.getGroomingCategory(); + this.hashtags = post.getHashtags(); + this.viewCount = post.getViewCount(); + this.likeCount = post.getLikeCount(); + this.isDeleted = post.getIsDeleted(); + this.deletedAt = post.getDeletedAt(); + } } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostImageRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostImageRepository.java index 70ad414..d6b2292 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostImageRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostImageRepository.java @@ -1,6 +1,11 @@ package com.ftm.server.adapter.out.persistence.repository; import com.ftm.server.adapter.out.persistence.model.PostImageJpaEntity; +import com.ftm.server.adapter.out.persistence.model.PostJpaEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -public interface PostImageRepository extends JpaRepository {} +public interface PostImageRepository extends JpaRepository { + + List findAllByPost(PostJpaEntity post); +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepository.java new file mode 100644 index 0000000..970092c --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepository.java @@ -0,0 +1,10 @@ +package com.ftm.server.adapter.out.persistence.repository; + +import com.ftm.server.adapter.out.persistence.model.PostProductImageJpaEntity; +import com.ftm.server.application.query.FindByPostProductIdsQuery; +import java.util.List; + +public interface PostProductImageCustomRepository { + + List findByPostProductIds(FindByPostProductIdsQuery query); +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepositoryImpl.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepositoryImpl.java new file mode 100644 index 0000000..f8ff760 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageCustomRepositoryImpl.java @@ -0,0 +1,25 @@ +package com.ftm.server.adapter.out.persistence.repository; + +import static com.ftm.server.adapter.out.persistence.model.QPostProductImageJpaEntity.postProductImageJpaEntity; + +import com.ftm.server.adapter.out.persistence.model.PostProductImageJpaEntity; +import com.ftm.server.application.query.FindByPostProductIdsQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class PostProductImageCustomRepositoryImpl implements PostProductImageCustomRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findByPostProductIds(FindByPostProductIdsQuery query) { + return queryFactory + .selectFrom(postProductImageJpaEntity) + .where(postProductImageJpaEntity.postProduct.id.in(query.getPostProductIds())) + .fetch(); + } +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageRepository.java index 159ff7b..86a9a40 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductImageRepository.java @@ -4,4 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface PostProductImageRepository - extends JpaRepository {} + extends JpaRepository, PostProductImageCustomRepository {} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductRepository.java index 6874c5b..4f6f9a8 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostProductRepository.java @@ -1,6 +1,11 @@ package com.ftm.server.adapter.out.persistence.repository; +import com.ftm.server.adapter.out.persistence.model.PostJpaEntity; import com.ftm.server.adapter.out.persistence.model.PostProductJpaEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -public interface PostProductRepository extends JpaRepository {} +public interface PostProductRepository extends JpaRepository { + + List findAllByPost(PostJpaEntity post); +} diff --git a/src/main/java/com/ftm/server/application/port/in/post/LoadPostDetailUseCase.java b/src/main/java/com/ftm/server/application/port/in/post/LoadPostDetailUseCase.java new file mode 100644 index 0000000..9b76b15 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/in/post/LoadPostDetailUseCase.java @@ -0,0 +1,11 @@ +package com.ftm.server.application.port.in.post; + +import com.ftm.server.application.query.FindByIdQuery; +import com.ftm.server.application.vo.post.PostDetailVo; +import com.ftm.server.common.annotation.UseCase; + +@UseCase +public interface LoadPostDetailUseCase { + + PostDetailVo execute(FindByIdQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostImagePort.java b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostImagePort.java new file mode 100644 index 0000000..9f1a713 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostImagePort.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.port.out.persistence.post; + +import com.ftm.server.application.query.FindByPostIdQuery; +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.PostImage; +import java.util.List; + +@Port +public interface LoadPostImagePort { + + List loadPostImagesByPostId(FindByPostIdQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostPort.java new file mode 100644 index 0000000..21539ce --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostPort.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.port.out.persistence.post; + +import com.ftm.server.application.query.FindByIdQuery; +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.Post; +import java.util.Optional; + +@Port +public interface LoadPostPort { + + Optional loadPost(FindByIdQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductImagePort.java b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductImagePort.java new file mode 100644 index 0000000..ffb915d --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductImagePort.java @@ -0,0 +1,13 @@ +package com.ftm.server.application.port.out.persistence.post; + +import com.ftm.server.application.query.FindByPostProductIdsQuery; +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.PostProductImage; +import java.util.Map; + +@Port +public interface LoadPostProductImagePort { + + Map loadPostProductImagesByPostProductIds( + FindByPostProductIdsQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductPort.java new file mode 100644 index 0000000..189ec24 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadPostProductPort.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.port.out.persistence.post; + +import com.ftm.server.application.query.FindByPostIdQuery; +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.PostProduct; +import java.util.List; + +@Port +public interface LoadPostProductPort { + + List loadPostProductsByPostId(FindByPostIdQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserForPostPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserForPostPort.java new file mode 100644 index 0000000..9871262 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserForPostPort.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.port.out.persistence.post; + +import com.ftm.server.application.query.FindByIdQuery; +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.User; +import java.util.Optional; + +@Port +public interface LoadUserForPostPort { + + Optional loadUserById(FindByIdQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserImageForPostPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserImageForPostPort.java new file mode 100644 index 0000000..e844d23 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/post/LoadUserImageForPostPort.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.port.out.persistence.post; + +import com.ftm.server.application.query.FindByUserIdQuery; +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.UserImage; +import java.util.Optional; + +@Port +public interface LoadUserImageForPostPort { + + Optional loadUserImageByUserId(FindByUserIdQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/post/UpdatePostPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/post/UpdatePostPort.java new file mode 100644 index 0000000..c08830b --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/post/UpdatePostPort.java @@ -0,0 +1,10 @@ +package com.ftm.server.application.port.out.persistence.post; + +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.Post; + +@Port +public interface UpdatePostPort { + + void updatePost(Post post); +} diff --git a/src/main/java/com/ftm/server/application/query/FindByPostIdQuery.java b/src/main/java/com/ftm/server/application/query/FindByPostIdQuery.java new file mode 100644 index 0000000..52ab147 --- /dev/null +++ b/src/main/java/com/ftm/server/application/query/FindByPostIdQuery.java @@ -0,0 +1,17 @@ +package com.ftm.server.application.query; + +import lombok.Getter; + +@Getter +public class FindByPostIdQuery { + + private final Long postId; + + private FindByPostIdQuery(Long postId) { + this.postId = postId; + } + + public static FindByPostIdQuery of(Long postId) { + return new FindByPostIdQuery(postId); + } +} diff --git a/src/main/java/com/ftm/server/application/query/FindByPostProductIdsQuery.java b/src/main/java/com/ftm/server/application/query/FindByPostProductIdsQuery.java new file mode 100644 index 0000000..ca89554 --- /dev/null +++ b/src/main/java/com/ftm/server/application/query/FindByPostProductIdsQuery.java @@ -0,0 +1,18 @@ +package com.ftm.server.application.query; + +import java.util.List; +import lombok.Getter; + +@Getter +public class FindByPostProductIdsQuery { + + private final List postProductIds; + + private FindByPostProductIdsQuery(List postProductIds) { + this.postProductIds = postProductIds; + } + + public static FindByPostProductIdsQuery of(List postProductIds) { + return new FindByPostProductIdsQuery(postProductIds); + } +} diff --git a/src/main/java/com/ftm/server/application/service/post/LoadPostDetailService.java b/src/main/java/com/ftm/server/application/service/post/LoadPostDetailService.java new file mode 100644 index 0000000..0d99515 --- /dev/null +++ b/src/main/java/com/ftm/server/application/service/post/LoadPostDetailService.java @@ -0,0 +1,79 @@ +package com.ftm.server.application.service.post; + +import com.ftm.server.application.port.in.post.LoadPostDetailUseCase; +import com.ftm.server.application.port.out.persistence.post.*; +import com.ftm.server.application.query.FindByIdQuery; +import com.ftm.server.application.query.FindByPostIdQuery; +import com.ftm.server.application.query.FindByPostProductIdsQuery; +import com.ftm.server.application.query.FindByUserIdQuery; +import com.ftm.server.application.vo.post.PostDetailVo; +import com.ftm.server.common.exception.CustomException; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.domain.entity.*; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LoadPostDetailService implements LoadPostDetailUseCase { + + private final LoadPostPort loadPostPort; + private final LoadPostImagePort loadPostImagePort; + private final LoadPostProductPort loadPostProductPort; + private final LoadPostProductImagePort loadPostProductImagePort; + private final LoadUserForPostPort loadUserForPostPort; + private final LoadUserImageForPostPort loadUserImageForPostPort; + private final UpdatePostPort updatePostPort; + + @Override + @Transactional + public PostDetailVo execute(FindByIdQuery query) { + // 게시글 조회 + Post post = + loadPostPort + .loadPost(query) + .orElseThrow(() -> new CustomException(ErrorResponseCode.POST_NOT_FOUND)); + if (post.getIsDeleted()) { // 게시글이 삭제된 경우 예외처리 + throw new CustomException(ErrorResponseCode.POST_NOT_FOUND); + } + + // 조회수 업데이트 + post.updateViewCount(post.getViewCount() + 1); + updatePostPort.updatePost(post); + + // 유저 조회 + User user = + loadUserForPostPort + .loadUserById(FindByIdQuery.of(post.getUserId())) + .orElseThrow(() -> CustomException.USER_NOT_FOUND); + + // 유저 이미지 조회 + UserImage userImage = + loadUserImageForPostPort + .loadUserImageByUserId(FindByUserIdQuery.of(user.getId())) + .orElseThrow( + () -> new CustomException(ErrorResponseCode.USER_IMAGE_NOT_FOUND)); + + // 게시글 이미지 목록 조회 + List postImages = + loadPostImagePort.loadPostImagesByPostId(FindByPostIdQuery.of(post.getId())); + + // 게시글 상품 목록 조회 + List postProducts = + loadPostProductPort.loadPostProductsByPostId(FindByPostIdQuery.of(post.getId())); + + // 게시글 상품 별 이미지 Map, 여러 상품의 이미지 정보 한번에 조회 + Map postProductImageMap = + loadPostProductImagePort.loadPostProductImagesByPostProductIds( + FindByPostProductIdsQuery.of( + postProducts.stream().map(PostProduct::getId).toList())); + + return PostDetailVo.from( + post, user, userImage, postImages, postProducts, postProductImageMap); + } +} diff --git a/src/main/java/com/ftm/server/application/vo/post/PostDetailVo.java b/src/main/java/com/ftm/server/application/vo/post/PostDetailVo.java new file mode 100644 index 0000000..38cdac9 --- /dev/null +++ b/src/main/java/com/ftm/server/application/vo/post/PostDetailVo.java @@ -0,0 +1,63 @@ +package com.ftm.server.application.vo.post; + +import com.ftm.server.domain.entity.*; +import com.ftm.server.domain.enums.GroomingCategory; +import com.ftm.server.domain.enums.HashTag; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import lombok.Getter; + +@Getter +public class PostDetailVo { + + private final Long postId; + private final String title; + private final String content; + private final GroomingCategory groomingCategory; + private final HashTag[] hashTags; + private final Integer viewCount; + private final Integer likeCount; + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; + private final User user; + private final UserImage userImage; + private final List postImages; + private final List products; + + private PostDetailVo( + Post post, + User user, + UserImage userImage, + List postImages, + List products) { + this.postId = post.getId(); + this.title = post.getTitle(); + this.content = post.getContent(); + this.groomingCategory = post.getGroomingCategory(); + this.hashTags = post.getHashtags(); + this.viewCount = post.getViewCount(); + this.likeCount = post.getLikeCount(); + this.createdAt = post.getCreatedAt(); + this.updatedAt = post.getUpdatedAt(); + this.user = user; + this.userImage = userImage; + this.postImages = postImages; + this.products = products; + } + + public static PostDetailVo from( + Post post, + User user, + UserImage userImage, + List postImages, + List postProducts, + Map postProductImageMap) { + return new PostDetailVo( + post, + user, + userImage, + postImages, + PostProductDetailVo.listFrom(postProducts, postProductImageMap)); + } +} diff --git a/src/main/java/com/ftm/server/application/vo/post/PostProductDetailVo.java b/src/main/java/com/ftm/server/application/vo/post/PostProductDetailVo.java new file mode 100644 index 0000000..fda65ce --- /dev/null +++ b/src/main/java/com/ftm/server/application/vo/post/PostProductDetailVo.java @@ -0,0 +1,43 @@ +package com.ftm.server.application.vo.post; + +import com.ftm.server.domain.entity.PostProduct; +import com.ftm.server.domain.entity.PostProductImage; +import com.ftm.server.domain.enums.HashTag; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import lombok.Getter; + +@Getter +public class PostProductDetailVo { + + private final Long postProductId; + private final String name; + private final String brand; + private final HashTag[] hashTags; + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; + private final PostProductImage postProductImage; + + private PostProductDetailVo(PostProduct postProduct, PostProductImage postProductImage) { + this.postProductId = postProduct.getId(); + this.name = postProduct.getName(); + this.brand = postProduct.getBrand(); + this.hashTags = postProduct.getHashTags(); + this.createdAt = postProduct.getCreatedAt(); + this.updatedAt = postProduct.getUpdatedAt(); + this.postProductImage = postProductImage; + } + + public static List listFrom( + List postProducts, Map postProductImageMap) { + return postProducts.stream() + .map(postProduct -> from(postProduct, postProductImageMap.get(postProduct.getId()))) + .toList(); + } + + public static PostProductDetailVo from( + PostProduct postProduct, PostProductImage postProductImage) { + return new PostProductDetailVo(postProduct, postProductImage); + } +} diff --git a/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java b/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java index df0c8f4..0c3d35e 100644 --- a/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java +++ b/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java @@ -38,6 +38,7 @@ public enum ErrorResponseCode { GROOMING_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "E404_004", "요청한 그루밍 카테고리 정보를 찾을 수 없습니다."), POST_NOT_FOUND(HttpStatus.NOT_FOUND, "E404_005", "요청한 게시글을 찾을 수 없습니다."), POST_PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "E404_006", "요청한 상품을 찾을 수 없습니다."), + POST_PRODUCT_IMAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "E404_007", "요청한 상품 이미지를 찾을 수 없습니다."), // 409번 USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "E409_001", "이미 존재하는 사용자입니다."), diff --git a/src/main/java/com/ftm/server/domain/entity/Post.java b/src/main/java/com/ftm/server/domain/entity/Post.java index bc00495..c16d987 100644 --- a/src/main/java/com/ftm/server/domain/entity/Post.java +++ b/src/main/java/com/ftm/server/domain/entity/Post.java @@ -93,4 +93,8 @@ public static Post create(SavePostCommand command) { .isDeleted(false) .build(); } + + public void updateViewCount(int viewCount) { + this.viewCount = viewCount; + } } diff --git a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java index 6199fa4..cb43694 100644 --- a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java +++ b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java @@ -54,7 +54,8 @@ public class SecurityConfig { "/api/users/email/duplication", "/api/users/options", "/api/grooming/tests", - "/api/auth/session/validity" + "/api/auth/session/validity", + "/api/posts/*" }; private static final String[] POST_ANONYMOUS_MATCHERS = { From ef952620c959f71770db43b75cddf03a4b8c9741 Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Mon, 21 Apr 2025 21:08:50 +0900 Subject: [PATCH 2/3] =?UTF-8?q?test:=20=EC=9C=A0=EC=A0=80=ED=94=BD=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=83=81=EC=84=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ftm/server/post/LoadPostDetailTest.java | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/test/java/com/ftm/server/post/LoadPostDetailTest.java diff --git a/src/test/java/com/ftm/server/post/LoadPostDetailTest.java b/src/test/java/com/ftm/server/post/LoadPostDetailTest.java new file mode 100644 index 0000000..bdb5e96 --- /dev/null +++ b/src/test/java/com/ftm/server/post/LoadPostDetailTest.java @@ -0,0 +1,183 @@ +package com.ftm.server.post; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import com.ftm.server.adapter.in.web.post.dto.request.SavePostProductRequest; +import com.ftm.server.adapter.in.web.post.dto.request.SavePostRequest; +import com.ftm.server.application.command.post.SavePostCommand; +import com.ftm.server.application.port.in.post.SavePostUseCase; +import com.ftm.server.application.port.out.s3.S3ImageDeletePort; +import com.ftm.server.application.port.out.s3.S3PostImageUploadPort; +import com.ftm.server.application.port.out.s3.S3PostProductImageUploadPort; +import com.ftm.server.application.port.out.transcation.AfterRollbackExecutorPort; +import com.ftm.server.application.vo.post.PostInfoVo; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.enums.GroomingCategory; +import com.ftm.server.domain.enums.HashTag; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.request.ParameterDescriptor; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +public class LoadPostDetailTest extends BaseTest { + + @Autowired private SavePostUseCase savePostUseCase; + @MockitoSpyBean private S3PostImageUploadPort s3PostImageUploadPort; + @MockitoSpyBean private S3PostProductImageUploadPort s3PostProductImageUploadPort; + @MockitoSpyBean private S3ImageDeletePort s3ImageDeletePort; + @MockitoSpyBean private AfterRollbackExecutorPort afterRollbackExecutorPort; + + private final ParameterDescriptor pathParametersForPostId = + parameterWithName("postId").description("게시글 ID"); + + private final List responseFieldLoadPostDetail = + List.of( + fieldWithPath("status").type(NUMBER).description("응답 상태"), + fieldWithPath("code").type(STRING).description("상태 코드"), + fieldWithPath("message").type(STRING).description("메시지"), + fieldWithPath("data").type(OBJECT).optional().description("응답 데이터"), + fieldWithPath("data.postId").type(NUMBER).description("게시글 ID"), + fieldWithPath("data.title").type(STRING).description("게시글 제목"), + fieldWithPath("data.content").type(STRING).description("게시글 내용"), + fieldWithPath("data.groomingCategory").type(STRING).description("게시글 그루밍 카테고리"), + fieldWithPath("data.hashTags[]").type(ARRAY).description("게시글 해시태그 목록"), + fieldWithPath("data.viewCount").type(NUMBER).description("게시글 조회수"), + fieldWithPath("data.likeCount").type(NUMBER).description("게시글 좋아요 수"), + fieldWithPath("data.createdAt").type(STRING).description("게시글 생성 날짜"), + fieldWithPath("data.updatedAt").type(STRING).description("게시글 수정 날짜"), + fieldWithPath("data.postImages[]").type(ARRAY).description("게시글 이미지 목록 정보"), + fieldWithPath("data.postImages[].postImageId") + .type(NUMBER) + .description("게시글 이미지 ID"), + fieldWithPath("data.postImages[].imageUrl") + .type(STRING) + .description("게시글 이미지 URL"), + fieldWithPath("data.writer").type(OBJECT).description("게시글 작성자 정보"), + fieldWithPath("data.writer.userId").type(NUMBER).description("게시글 작성자 ID"), + fieldWithPath("data.writer.nickname").type(STRING).description("게시글 작성자 닉네임"), + fieldWithPath("data.writer.imageUrl") + .type(STRING) + .description("게시글 작성자 프로필 이미지 URL"), + fieldWithPath("data.postProducts[]").type(ARRAY).description("게시글 상품 목록 정보"), + fieldWithPath("data.postProducts[].postProductId") + .type(NUMBER) + .description("게시글 상품 ID"), + fieldWithPath("data.postProducts[].name").type(STRING).description("게시글 상품 이름"), + fieldWithPath("data.postProducts[].brand") + .type(STRING) + .description("게시글 상품 브랜드"), + fieldWithPath("data.postProducts[].hashTags[]") + .type(ARRAY) + .description("게시글 상품 해시태그 목록"), + fieldWithPath("data.postProducts[].postProductImage") + .type(OBJECT) + .description("게시글 상품 이미지 정보"), + fieldWithPath("data.postProducts[].postProductImage.postProductImageId") + .type(NUMBER) + .description("게시글 상품 이미지 ID"), + fieldWithPath("data.postProducts[].postProductImage.imageUrl") + .type(STRING) + .description("게시글 상품 이미지 URL")); + + private Long savedPostId; + + private ResultActions getResultActions(Long postId) throws Exception { + return mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/posts/{postId}", postId) + .accept(MediaType.APPLICATION_JSON)); + } + + private RestDocumentationResultHandler getDocument(Integer identifier) { + return document( + "loadPostDetail/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + pathParameters(pathParametersForPostId), + responseFields(responseFieldLoadPostDetail), + resource( + ResourceSnippetParameters.builder() + .tag("유저픽 게시글") + .summary("유저픽 게시글 상세 조회 api") + .description("유저픽 게시글 상세 조회 api 입니다.") + .responseFields(responseFieldLoadPostDetail) + .build())); + } + + @BeforeEach + void setUp() throws Exception { + User user = createTestUser("test@gmail.com", "test1234!"); + + SavePostRequest postRequest = + new SavePostRequest( + "독도 토너 추천", + GroomingCategory.BEAUTY, + List.of(HashTag.PERFUME), + "
test
", + List.of(new SavePostProductRequest(-1, "독도 토너", "라운드랩", List.of()))); + + // s3 실제 호출 대신 mock 대입 + doReturn(List.of()).when(s3PostImageUploadPort).uploadImages(new ArrayList<>(List.of())); + doReturn(List.of()) + .when(s3PostProductImageUploadPort) + .uploadImages(new ArrayList<>(List.of())); + doNothing().when(s3ImageDeletePort).deleteImages(any()); + doNothing().when(afterRollbackExecutorPort).doAfterRollback(any()); + + PostInfoVo post = + savePostUseCase.execute( + SavePostCommand.from(user.getId(), postRequest, List.of(), List.of())); + savedPostId = post.getId(); + } + + @Test + @Transactional + void 유저픽_게시글_상세_조회_성공() throws Exception { + // when + ResultActions resultActions = getResultActions(savedPostId); + + // then + resultActions.andExpect(status().isOk()).andDo(print()); + + // document + resultActions.andDo(getDocument(1)); + } + + @Test + @Transactional + void 유저픽_게시글_상세_조회_실패() throws Exception { + // when + ResultActions resultActions = getResultActions(1000L); + + // then + resultActions + .andExpect(status().is(ErrorResponseCode.POST_NOT_FOUND.getHttpStatus().value())) + .andDo(print()); + + // document + resultActions.andDo(getDocument(2)); + } +} From 75e87edbeec2afc30357bd057e038ecfb91aeedc Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Mon, 21 Apr 2025 21:09:03 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20=EC=9C=A0=EC=A0=80=ED=94=BD=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=83=81=EC=84=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/post-api.adoc | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/docs/asciidoc/post-api.adoc b/src/docs/asciidoc/post-api.adoc index 9ac0b97..9e1c462 100644 --- a/src/docs/asciidoc/post-api.adoc +++ b/src/docs/asciidoc/post-api.adoc @@ -44,3 +44,27 @@ include::{snippetsDir}/savePost/5/http-response.adoc[] 실패 5. 이미지 파일 업로드에 실패할 경우 include::{snippetsDir}/savePost/6/http-response.adoc[] + +--- + +=== **2. 게시글 상세 조회** + +유저픽 게시글 상세 조회 api 입니다. + +==== Request +include::{snippetsDir}/loadPostDetail/1/http-request.adoc[] + +==== Request Path Parameters +include::{snippetsDir}/loadPostDetail/1/path-parameters.adoc[] + +==== 성공 Response +include::{snippetsDir}/loadPostDetail/1/http-response.adoc[] + +==== Response Body Fields +include::{snippetsDir}/loadPostDetail/1/response-fields.adoc[] + +==== 실패 Response + +실패 1. 존재하지 않는 게시글이거나, 삭제된 게시글인 경우 + +include::{snippetsDir}/loadPostDetail/2/http-response.adoc[] \ No newline at end of file