Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/docs/asciidoc/post-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,23 @@ include::{snippetsDir}/loadUserPickAllPopular/1/http-response.adoc[]
include::{snippetsDir}/loadUserPickAllPopular/1/response-fields.adoc[]


---

=== **14. 해시태그 추천 - "해시태그 기반 상품 조회**

게시글 조회 api 이지만, Request Body가 필요하여 "POST" 메서드로 구현하였습니다.

==== Request
include::{snippetsDir}/loadPostProducts/1/http-request.adoc[]

==== Request Body Parameters
include::{snippetsDir}/loadPostProducts/1/request-fields.adoc[]

==== 성공 Response
include::{snippetsDir}/loadPostProducts/1/http-response.adoc[]

==== Response Body Fields
include::{snippetsDir}/loadPostProducts/1/response-fields.adoc[]


---
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.ftm.server.adapter.in.web.post.controller;

import com.ftm.server.adapter.in.web.post.dto.request.LoadProductsByHashTagRequest;
import com.ftm.server.adapter.in.web.post.dto.response.LoadProductsByHashTagResponse;
import com.ftm.server.application.port.in.post.LoadProductsByHashTagUseCase;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import com.ftm.server.infrastructure.security.UserPrincipal;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class LoadProductsByHashTagController {

private final LoadProductsByHashTagUseCase useCase;

@PostMapping("/api/posts/products")
public ResponseEntity loadProductsByHashTag(
@AuthenticationPrincipal UserPrincipal userPrincipal,
@RequestBody(required = false) LoadProductsByHashTagRequest request) {

List<LoadProductsByHashTagResponse> result =
useCase
.execute(
userPrincipal == null ? null : userPrincipal.getId(),
request == null ? null : request.getHashTagList())
.stream()
.map(LoadProductsByHashTagResponse::from)
.toList();

return ResponseEntity.ok().body(ApiResponse.success(SuccessResponseCode.OK, result));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ftm.server.adapter.in.web.post.dto.request;

import com.ftm.server.domain.enums.ProductHashtag;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class LoadProductsByHashTagRequest {
private final List<ProductHashtag> hashTagList;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ftm.server.adapter.in.web.post.dto.response;

import com.ftm.server.application.vo.post.LoadProductsByHashTagVo;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class LoadProductsByHashTagResponse {
private final Long productId;
private final String productName;
private final String productImage;
private final Boolean likeYn;
private final String brand;
private final Long recommendedCount;
private final Long postId;

public static LoadProductsByHashTagResponse from(LoadProductsByHashTagVo vo) {
return new LoadProductsByHashTagResponse(
vo.getProductId(),
vo.getProductName(),
vo.getProductImage(),
vo.getLikeYn(),
vo.getBrand(),
vo.getRecommendedCount(),
vo.getPostId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.ftm.server.common.exception.CustomException;
import com.ftm.server.common.response.enums.ErrorResponseCode;
import com.ftm.server.domain.entity.*;
import com.ftm.server.domain.enums.ProductHashtag;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand All @@ -36,14 +37,16 @@ public class PostDomainPersistenceAdapter
DeletePostProductPort,
DeletePostProductImagePort,
LoadPostWithBookmarkCountPort,
LoadUserForPostDomainPort {
LoadUserForPostDomainPort,
LoadProductLikePort {

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 ProductLikeRepository productLikeRepository;

private final PostMapper postMapper;
private final PostImageMapper postImageMapper;
Expand Down Expand Up @@ -237,6 +240,26 @@ public List<PostProduct> loadPostProductsByPostIds(FindByIdsQuery query) {
.toList();
}

@Override
public List<PostProduct> loadPostProductsByHashTags(FindByProductHashTagsQuery query) {
return postProductRepository
.findByHashtags(
query.getProductHashtagList().stream()
.map(ProductHashtag::name)
.toList()
.toArray(new String[0]))
.stream()
.map(p -> postProductMapper.toDomainEntity(p))
.toList();
}

@Override
public List<PostProduct> loadAllPostProduct() {
return postProductRepository.findAllByLatest().stream()
.map(p -> postProductMapper.toDomainEntity(p))
.toList();
}

@Override
public List<PostProductImage> loadPostProductImagesByPostProductIds(FindByIdsQuery query) {
List<PostProductImageJpaEntity> postProductImageJpaEntities =
Expand Down Expand Up @@ -369,4 +392,10 @@ public List<Post> loadPostListByUsers(FindByUserIdsQuery query) {
public List<UserIdAndNameVo> loadPostAndAuthorName(FindByIdsQuery query) {
return userRepository.findUserNameByUserIds(query.getIds());
}

@Override
public List<LoadProductAndUserLikeVo> findProductLikeByUser(
Long userId, List<Long> productIds) {
return productLikeRepository.findProductLikeByUser(userId, productIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.ftm.server.adapter.out.persistence.model;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "product_like")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ProductLikeJpaEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_product_id")
private PostProductJpaEntity postProduct;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private UserJpaEntity user;

@Builder(access = AccessLevel.PRIVATE)
private ProductLikeJpaEntity(PostProductJpaEntity postProduct, UserJpaEntity user) {
this.postProduct = postProduct;
this.user = user;
}

public static ProductLikeJpaEntity from(
PostProductJpaEntity postProductJpaEntity, UserJpaEntity userJpaEntity) {
return ProductLikeJpaEntity.builder()
.postProduct(postProductJpaEntity)
.user(userJpaEntity)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.ftm.server.adapter.out.persistence.repository;

public interface PostProductCustomRepository {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.ftm.server.adapter.out.persistence.repository;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class PostProductCustomRepositoryImpl implements PostProductCustomRepository {

// private final JPAQueryFactory queryFactory;
//
// @Override
// public List<PostProductJpaEntity> findByHashtags(List<ProductHashtag> postHashtagList) {
//
// List<String> targetHashtags =
// List.of(ProductHashtag.values()).stream().map(a->a.name()).toList();
//
// List<PostProductJpaEntity> result = queryFactory
// .selectFrom(postProductJpaEntity)
// .where(
// Expressions.booleanTemplate(
// "{0} @> cast({1} as product_hashtag[])",
// postProductJpaEntity.hashtags,
// targetHashtags.toArray(new String[0])
// )
// )
// .orderBy(postProductJpaEntity.createdAt.desc())
// .fetch();
//
// return result;
//
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface PostProductRepository extends JpaRepository<PostProductJpaEntity, Long> {
public interface PostProductRepository
extends JpaRepository<PostProductJpaEntity, Long>, PostProductCustomRepository {

List<PostProductJpaEntity> findAllByPost(PostJpaEntity post);

Expand All @@ -17,4 +18,18 @@ public interface PostProductRepository extends JpaRepository<PostProductJpaEntit
@Modifying
@Query("DELETE FROM PostProductJpaEntity pp WHERE pp.id IN (:postProductIds)")
void deleteAllByIdInBatch(@Param("postProductIds") List<Long> postProductIds);

@Query("SELECT p FROM PostProductJpaEntity p order by p.createdAt desc ")
List<PostProductJpaEntity> findAllByLatest();

@Query(
value =
"""
SELECT *
FROM post_product
WHERE hashtags @> CAST(:hashtags AS product_hashtag[])
ORDER BY created_at DESC
""",
nativeQuery = true)
List<PostProductJpaEntity> findByHashtags(@Param("hashtags") String[] hashtags);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ftm.server.adapter.out.persistence.repository;

import com.ftm.server.adapter.out.persistence.model.ProductLikeJpaEntity;
import com.ftm.server.application.vo.post.LoadProductAndUserLikeVo;
import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductLikeRepository extends JpaRepository<ProductLikeJpaEntity, Long> {

@Query(
"""
SELECT new com.ftm.server.application.vo.post.LoadProductAndUserLikeVo(
p.id,
:userId,
CASE WHEN pl.id IS NOT NULL THEN true ELSE false END
)
FROM PostProductJpaEntity p
LEFT JOIN ProductLikeJpaEntity pl
ON pl.postProduct.id = p.id
AND pl.user.id = :userId
WHERE p.id in (:postProductIds)
""")
List<LoadProductAndUserLikeVo> findProductLikeByUser(
@Param("userId") Long userId, @Param("postProductIds") List<Long> postProductIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ftm.server.application.port.in.post;

import com.ftm.server.application.vo.post.LoadProductsByHashTagVo;
import com.ftm.server.common.annotation.UseCase;
import com.ftm.server.domain.enums.ProductHashtag;
import java.util.List;

@UseCase
public interface LoadProductsByHashTagUseCase {

List<LoadProductsByHashTagVo> execute(Long userId, List<ProductHashtag> hashtagList);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.ftm.server.application.query.FindByIdsQuery;
import com.ftm.server.application.query.FindByPostIdQuery;
import com.ftm.server.application.query.FindByProductHashTagsQuery;
import com.ftm.server.common.annotation.Port;
import com.ftm.server.domain.entity.PostProduct;
import java.util.List;
Expand All @@ -14,4 +15,8 @@ public interface LoadPostProductPort {
List<PostProduct> loadPostProductsByIds(FindByIdsQuery query);

List<PostProduct> loadPostProductsByPostIds(FindByIdsQuery query);

List<PostProduct> loadPostProductsByHashTags(FindByProductHashTagsQuery query);

List<PostProduct> loadAllPostProduct();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ftm.server.application.port.out.persistence.post;

import com.ftm.server.application.vo.post.LoadProductAndUserLikeVo;
import com.ftm.server.common.annotation.Port;
import java.util.List;

@Port
public interface LoadProductLikePort {
List<LoadProductAndUserLikeVo> findProductLikeByUser(Long userId, List<Long> productIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ftm.server.application.query;

import com.ftm.server.domain.enums.ProductHashtag;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class FindByProductHashTagsQuery {
List<ProductHashtag> productHashtagList;
}
Loading
Loading