Skip to content

Commit

Permalink
#122 feature: 전체, 상세 조회시 즐겨찾기 여부
Browse files Browse the repository at this point in the history
  • Loading branch information
wocjf0513 committed Dec 13, 2023
1 parent 4e16e10 commit 9a4e3bc
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public FavoritesResponseDto getFavorites(long memberId, Pageable pageable) {
Member member = memberService.getMemberById(memberId);
Page<Favorite> favorites = favoriteRepository.findAllByMemberId(member.getId(), pageable);
for (Favorite favorite : favorites) {
productResponses.add(ProductMapper.toProductResponse(favorite.getProduct()));
productResponses.add(ProductMapper.toProductResponse(favorite.getProduct(),true));
}
return FavoritesResponseDto.builder()
.pageCount(favorites.getTotalPages())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fc.shimpyo_be.domain.product.entity;

import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
import com.fc.shimpyo_be.domain.product.util.CategoryConverter;
import com.fc.shimpyo_be.domain.room.entity.Room;
import jakarta.persistence.CascadeType;
Expand Down Expand Up @@ -59,12 +60,14 @@ public class Product {
private List<ProductImage> photoUrls = new ArrayList<>();
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Room> rooms = new ArrayList<>();
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Favorite> favorites = new ArrayList<>();


@Builder
public Product(Long id, String name, Address address, Category category, String description,
float starAvg, String thumbnail, ProductOption productOption, Amenity amenity,
List<ProductImage> photoUrls, List<Room> rooms) {
List<ProductImage> photoUrls, List<Room> rooms, List<Favorite> favorites) {
this.id = id;
this.name = name;
this.address = address;
Expand All @@ -76,6 +79,7 @@ public Product(Long id, String name, Address address, Category category, String
this.amenity = amenity;
this.photoUrls = photoUrls;
this.rooms = rooms;
this.favorites = favorites;
}

public void updateStarAvg(float starAvg) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.fc.shimpyo_be.domain.product.service;

import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
import com.fc.shimpyo_be.domain.product.dto.response.PaginatedProductResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductDetailsResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.exception.ProductNotFoundException;
import com.fc.shimpyo_be.domain.product.repository.ProductCustomRepositoryImpl;
Expand All @@ -11,7 +13,9 @@
import com.fc.shimpyo_be.domain.room.entity.Room;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import com.fc.shimpyo_be.global.util.DateTimeUtil;
import com.fc.shimpyo_be.global.util.SecurityUtil;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
Expand All @@ -34,6 +38,8 @@ public class ProductService {

private final RedisTemplate<String, Object> restTemplate;

private final SecurityUtil securityUtil;

public PaginatedProductResponse getProducts(final SearchKeywordRequest searchKeywordRequest,
final Pageable pageable) {

Expand All @@ -43,7 +49,7 @@ public PaginatedProductResponse getProducts(final SearchKeywordRequest searchKey

return PaginatedProductResponse.builder()
.productResponses(
products.getContent().stream().map(ProductMapper::toProductResponse).toList())
getProductResponseSettingFavorites(products.getContent()))
.pageCount(products.getTotalPages())
.build();
}
Expand All @@ -52,16 +58,19 @@ public ProductDetailsResponse getProductDetails(final Long productId, final Stri
final String endDate) {
Product product = productRepository.findById(productId)
.orElseThrow(ProductNotFoundException::new);
HashSet<Long> favoriteProductIds = getFavoriteProductIds(List.of(product));
ProductDetailsResponse productDetailsResponse = ProductMapper.toProductDetailsResponse(
product);
product, favoriteProductIds.contains(product.getId()));

productDetailsResponse.rooms().forEach(
roomResponse -> roomResponse.setRemaining(
countAvailableForReservationUsingRoomCode(roomResponse.getRoomCode(), startDate,
endDate)));
return productDetailsResponse;
}

public long countAvailableForReservationUsingRoomCode(final Long roomCode, final String startDate,
public long countAvailableForReservationUsingRoomCode(final Long roomCode,
final String startDate,
final String endDate) {
AtomicLong remaining = new AtomicLong();
List<Room> rooms = Optional.of(roomRepository.findByCode(roomCode)).orElseThrow();
Expand Down Expand Up @@ -93,5 +102,31 @@ public boolean isAvailableForReservation(final Long roomId, final String startDa
return true;
}

private List<ProductResponse> getProductResponseSettingFavorites(List<Product> products) {

HashSet<Long> favoriteProductIds = getFavoriteProductIds(products);

return products.stream().map(product -> ProductMapper.toProductResponse(product,
favoriteProductIds.contains(product.getId()))).toList();

}

private HashSet<Long> getFavoriteProductIds(List<Product> products) {
Long userId = securityUtil.getNullableCurrentMemberId();
HashSet<Long> favoriteProductId = new HashSet<>();
if (userId != null) {
for (Product product : products) {
for (Favorite favorite : product.getFavorites()) {
if (favorite.getMember().getId().equals(userId)) {
favoriteProductId.add(product.getId());
break;
}
}
}
}

return favoriteProductId;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,24 @@
public class ProductMapper {


public static ProductResponse toProductResponse(Product product) {

List<Room> rooms = product.getRooms();
long price = rooms.isEmpty() ? 0 : rooms.stream().map(PricePickerByDateUtil::getPrice)
.min((o1, o2) -> Math.toIntExact(
o1 - o2)).orElseThrow();
price = price == 0 ? 100000 : price;
public static ProductResponse toProductResponse(Product product, boolean isFavorite) {

return ProductResponse.builder().productId(product.getId()).productName(product.getName())
.address(
product.getAddress().getAddress() + " " + product.getAddress().getDetailAddress())
.category(product.getCategory().getName())
.image(product.getThumbnail())
.starAvg(product.getStarAvg())
.price(price)
.price(getPrice(product))
.capacity(product.getRooms().isEmpty()
? 0 : Long.valueOf(
product.getRooms().stream().map(Room::getCapacity).min((o1, o2) -> o2 - o1)
.orElseThrow()))
.favorites(false)
.favorites(isFavorite)
.build();
}

public static ProductDetailsResponse toProductDetailsResponse(Product product) {

List<String> images = new ArrayList<>();
images.add(product.getThumbnail());

if (product.getPhotoUrls() != null) {
images.addAll(product.getPhotoUrls().stream().map(ProductImage::getPhotoUrl).toList());
}
public static ProductDetailsResponse toProductDetailsResponse(Product product, boolean isFavorite) {

return ProductDetailsResponse.builder()
.productId(product.getId())
Expand All @@ -60,8 +47,8 @@ public static ProductDetailsResponse toProductDetailsResponse(Product product) {
.productAmenityResponse(toProductAmenityResponse(product.getAmenity()))
.starAvg(product.getStarAvg())
.productOptionResponse(toProductOptionResponse(product.getProductOption()))
.favorites(false)
.images(images)
.favorites(isFavorite)
.images(getImage(product))
.rooms(product.getRooms().stream().map(RoomMapper::toRoomResponse).distinct().toList())
.build();
}
Expand Down Expand Up @@ -102,4 +89,24 @@ private static ProductOptionResponse toProductOptionResponse(ProductOption produ
.build();
}

private static long getPrice(Product product) {
List<Room> rooms = product.getRooms();
long price = rooms.isEmpty() ? 0 : rooms.stream().map(PricePickerByDateUtil::getPrice)
.min((o1, o2) -> Math.toIntExact(
o1 - o2)).orElseThrow();

return price == 0 ? 100000 : price;
}

private static List<String> getImage(Product product) {
List<String> images = new ArrayList<>();
images.add(product.getThumbnail());

if (product.getPhotoUrls() != null) {
images.addAll(product.getPhotoUrls().stream().map(ProductImage::getPhotoUrl).toList());
}

return images;
}

}
9 changes: 9 additions & 0 deletions src/main/java/com/fc/shimpyo_be/global/util/SecurityUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ public Long getCurrentMemberId() {
}
return Long.parseLong(authentication.getName());
}

public Long getNullableCurrentMemberId() {
final Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null || authentication.getName() == null) {
return null;
}
return Long.parseLong(authentication.getName());
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
package com.fc.shimpyo_be.domain.product.docs;

import static org.junit.matchers.JUnitMatchers.everyItem;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fc.shimpyo_be.config.RestDocsSupport;
import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
import com.fc.shimpyo_be.domain.favorite.repository.FavoriteRepository;
import com.fc.shimpyo_be.domain.member.entity.Authority;
import com.fc.shimpyo_be.domain.member.entity.Member;
import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.entity.ProductImage;
import com.fc.shimpyo_be.domain.product.factory.ProductFactory;
import com.fc.shimpyo_be.domain.product.repository.ProductImageRepository;
import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.room.entity.Room;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import com.fc.shimpyo_be.global.util.SecurityUtil;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.restdocs.payload.JsonFieldType;
Expand All @@ -45,6 +55,15 @@ class ProductRestIntegrationDocsTest extends RestDocsSupport {
@Autowired
private RedisTemplate<String, Object> restTemplate;

@Autowired
private FavoriteRepository favoriteRepository;

@Autowired
private MemberRepository memberRepository;

@MockBean
private SecurityUtil securityUtil;

@DisplayName("숙소 저장 후, 검색 조회 및 페이징할 수 있다.")
@Test
void getProducts() throws Exception {
Expand Down Expand Up @@ -301,5 +320,34 @@ void isAvailableForReservation() throws Exception {
));
}

@Test
@DisplayName("전체 조회에서 즐겨찾기 여부를 볼 수 있다.")
void isAvailableGetFavoriteInGetProducts() throws Exception {
//given
given(securityUtil.getNullableCurrentMemberId()).willReturn(1L);
Product product = productRepository.save(ProductFactory.createTestProduct());
Room room = roomRepository.save(ProductFactory.createTestRoom(product, 0L));
Member member = Member.builder()
.email("[email protected]")
.name("test")
.password("$10$ygrAExVYmFTkZn2d0.Pk3Ot5CNZwIBjZH5f.WW0AnUq4w4PtBi9Nm")
.photoUrl(
"https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
.authority(Authority.ROLE_USER)
.build();
memberRepository.save(member);
Favorite favorite = Favorite.builder().product(product).member(member).build();
favoriteRepository.save(favorite);

// when
ResultActions getProductAction = mockMvc.perform(
get("/api/products"));

//then
getProductAction
.andDo(MockMvcResultHandlers.print()).andExpect(status().isOk()).andExpect(jsonPath("$.data.productResponses[0].favorites").value(true));

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ProductRestControllerTest {
void getAllProducts() {
//given
List<ProductResponse> productResponses = new ArrayList<>();
productResponses.add(ProductMapper.toProductResponse(ProductFactory.createTestProduct()));
productResponses.add(ProductMapper.toProductResponse(ProductFactory.createTestProduct(),false));
PaginatedProductResponse paginatedProductResponse = PaginatedProductResponse.builder()
.productResponses(productResponses)
.pageCount(1)
Expand All @@ -67,7 +67,7 @@ void getAllProducts() {
void getProductDetails() {
//given
Product product = ProductFactory.createTestProduct();
ProductDetailsResponse expectedResult = ProductMapper.toProductDetailsResponse(product);
ProductDetailsResponse expectedResult = ProductMapper.toProductDetailsResponse(product,false);
doReturn(expectedResult).when(productService)
.getProductDetails(1L, "2024-12-27", "2024-12-28");
//when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ void getProducts() {

//then
assertThat(result.productResponses()).usingRecursiveAssertion().isEqualTo(
productPage.getContent().stream().map(ProductMapper::toProductResponse).toList());
productPage.getContent().stream()
.map(product -> ProductMapper.toProductResponse(product, false)).toList());
}

@Test
void getProductDetails() {
//given
Product product = ProductFactory.createTestProduct();
Room room = ProductFactory.createTestRoom(product,0L);
Room room = ProductFactory.createTestRoom(product, 0L);
product.getRooms().add(room);
given(productRepository.findById(product.getId())).willReturn(Optional.ofNullable(product));
doReturn(1L).when(
Expand All @@ -83,7 +84,8 @@ void getProductDetails() {
"2023-11-27", "2023-11-28");
//then
for (int i = 0; i < result.rooms().size(); i++) {
RoomResponse roomResponse = ProductMapper.toProductDetailsResponse(product).rooms()
RoomResponse roomResponse = ProductMapper.toProductDetailsResponse(product, false)
.rooms()
.get(i);
roomResponse.setRemaining(1L);
assertThat(result.rooms().get(i)).usingRecursiveComparison()
Expand Down

0 comments on commit 9a4e3bc

Please sign in to comment.