Skip to content

Commit

Permalink
[feat] 관심상품 카테고리 목록 조회 API 구현 (#126)
Browse files Browse the repository at this point in the history
* #125 feat: 관심상품의 카테고리 목록 조회 서비스 구현

- /api/wish/categories

* #125 test: 관심상품의 카테고리 조회 목록 테스트 코드 추가

* #125 test: 관심상품의 카테고리 조회 목록 테스트 코드 추가
  • Loading branch information
yonghwankim-dev authored Oct 1, 2023
1 parent 99ccc02 commit b4eaa04
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import codesquard.app.api.item.response.ItemResponses;
import codesquard.app.api.response.ApiResponse;
import codesquard.app.api.wishitem.response.WishCategoryListResponse;
import codesquard.app.domain.oauth.support.AuthPrincipal;
import codesquard.app.domain.oauth.support.Principal;
import codesquard.app.domain.wish.WishStatus;
Expand All @@ -33,4 +34,9 @@ public ApiResponse<ItemResponses> findAll(@RequestParam(required = false) Long c
@RequestParam(required = false, defaultValue = "10") int size, @RequestParam(required = false) Long cursor) {
return ApiResponse.ok("관심상품 조회에 성공하였습니다.", wishItemService.findAll(categoryId, size, cursor));
}

@GetMapping("/categories")
public ApiResponse<WishCategoryListResponse> readWishCategories(@AuthPrincipal Principal principal) {
return ApiResponse.ok("관심상품의 카테고리 목록 조회를 완료하였습니다.", wishItemService.readWishCategories(principal));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package codesquard.app.api.wishitem;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
Expand All @@ -12,10 +14,13 @@
import codesquard.app.api.errors.exception.RestApiException;
import codesquard.app.api.item.response.ItemResponse;
import codesquard.app.api.item.response.ItemResponses;
import codesquard.app.api.wishitem.response.WishCategoryListResponse;
import codesquard.app.domain.category.Category;
import codesquard.app.domain.item.Item;
import codesquard.app.domain.item.ItemRepository;
import codesquard.app.domain.member.Member;
import codesquard.app.domain.member.MemberRepository;
import codesquard.app.domain.oauth.support.Principal;
import codesquard.app.domain.pagination.PaginationUtils;
import codesquard.app.domain.wish.Wish;
import codesquard.app.domain.wish.WishPaginationRepository;
Expand All @@ -24,6 +29,7 @@
import lombok.RequiredArgsConstructor;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class WishItemService {

Expand All @@ -50,7 +56,7 @@ private void register(Long itemId, Long memberId) {
item.wishRegister();
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new RestApiException(MemberErrorCode.NOT_FOUND_MEMBER));
wishRepository.save(new Wish(member, item, LocalDateTime.now()));
wishRepository.save(new Wish(member, item));
}

private void cancel(Long itemId) {
Expand All @@ -60,9 +66,19 @@ private void cancel(Long itemId) {
wishRepository.deleteByItemId(itemId);
}

@Transactional(readOnly = true)
public ItemResponses findAll(Long categoryId, int size, Long cursor) {
Slice<ItemResponse> itemResponses = wishPaginationRepository.findAll(categoryId, size, cursor);
return PaginationUtils.getItemResponses(itemResponses);
}

public WishCategoryListResponse readWishCategories(Principal principal) {
List<Wish> wishes = wishRepository.findAllByMemberId(principal.getMemberId());
List<Category> categories = wishes.stream()
.map(Wish::getItem)
.map(Item::getCategory)
.sorted(Comparator.comparing(Category::getId))
.distinct()
.collect(Collectors.toUnmodifiableList());
return WishCategoryListResponse.of(categories);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package codesquard.app.api.wishitem.response;

import codesquard.app.domain.category.Category;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class WishCategoryItemResponse {
private Long categoryId;
private String categoryName;

public static WishCategoryItemResponse from(Category category) {
return new WishCategoryItemResponse(category.getId(), category.getName());
}

@Override
public String toString() {
return String.format("%s, %s(categoryId=%d, categoryName=%s)", "관심상품 카테고리 항목", this.getClass().getSimpleName(),
categoryId, categoryName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package codesquard.app.api.wishitem.response;

import java.util.List;
import java.util.stream.Collectors;

import codesquard.app.domain.category.Category;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class WishCategoryListResponse {
private List<WishCategoryItemResponse> categories;

public static WishCategoryListResponse of(List<Category> categories) {
return new WishCategoryListResponse(categories.stream()
.map(WishCategoryItemResponse::from)
.collect(Collectors.toUnmodifiableList())
);
}

@Override
public String toString() {
return String.format("%s, %s(categories=%s)", "관심상품 카테고리 리스트 응답", this.getClass().getSimpleName(), categories);
}
}
4 changes: 2 additions & 2 deletions backend/src/main/java/codesquard/app/domain/wish/Wish.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public class Wish {
private Item item;
private LocalDateTime createdAt;

public Wish(Member member, Item item, LocalDateTime createdAt) {
public Wish(Member member, Item item) {
this.member = member;
this.item = item;
this.createdAt = createdAt;
this.createdAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package codesquard.app.domain.wish;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface WishRepository extends JpaRepository<Wish, Long> {
Expand All @@ -9,4 +11,6 @@ public interface WishRepository extends JpaRepository<Wish, Long> {
int countWishByItemId(Long itemId);

boolean existsByMemberIdAndItemId(Long memberId, Long itemId);

List<Wish> findAllByMemberId(Long memberId);
}
38 changes: 37 additions & 1 deletion backend/src/main/resources/db/mysql/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ VALUES (0, '롤러블레이드 팝니다', now(), 169000, '청운동', 'ON_SALE'
'https://second-hand-team03-a.s3.ap-northeast-2.amazonaws.com/public/sample/roller_blade.jpeg',
'롤러 블레이드', 0, 0, 1, 1);

INSERT INTO image (image_url, thumbnail, item_id)
VALUES ('https://second-hand-team03-a.s3.ap-northeast-2.amazonaws.com/public/sample/roller_blade.jpeg',
true,
1);



INSERT INTO item(chat_count,
content,
created_at,
Expand All @@ -79,7 +86,36 @@ VALUES (0, '롤러블레이드 팝니다', now(), 169000, '역삼1동', 'ON_SALE
INSERT INTO image (image_url, thumbnail, item_id)
VALUES ('https://second-hand-team03-a.s3.ap-northeast-2.amazonaws.com/public/sample/roller_blade.jpeg',
true,
1);
2);


INSERT INTO item(chat_count,
content,
created_at,
price,
region,
status,
thumbnail_url,
title,
view_count,
wish_count,
category_id,
member_id)
VALUES (0, '의자 팝니다.', now(), 130000, '역삼1동', 'ON_SALE',
'https://second-hand-team03-a.s3.ap-northeast-2.amazonaws.com/public/sample/char.jpeg',
'옛날 의자', 0, 0, 6, 1);

INSERT INTO image (image_url, thumbnail, item_id)
VALUES ('https://second-hand-team03-a.s3.ap-northeast-2.amazonaws.com/public/sample/char.jpeg',
true,
3);

INSERT INTO wish(created_at, item_id, member_id)
VALUES (now(), 1, 2);
INSERT INTO wish(created_at, item_id, member_id)
VALUES (now(), 2, 2);
INSERT INTO wish(created_at, item_id, member_id)
VALUES (now(), 3, 2);

INSERT INTO chat_room(created_at, item_id, member_id)
VALUES (now(), 1, 2);
Expand Down
28 changes: 28 additions & 0 deletions backend/src/test/java/codesquard/app/ItemTestSupport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package codesquard.app;

import static java.time.LocalDateTime.*;

import codesquard.app.domain.category.Category;
import codesquard.app.domain.item.Item;
import codesquard.app.domain.item.ItemStatus;
import codesquard.app.domain.member.Member;

public class ItemTestSupport {

public static Item createItem(String title, String content, Long price, ItemStatus status, String region,
Member member, Category category) {
return Item.builder()
.title(title)
.content(content)
.price(price)
.status(status)
.region(region)
.createdAt(now())
.wishCount(0L)
.viewCount(0L)
.chatCount(0L)
.member(member)
.category(category)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ public void findDetailItemBySeller() {
new Image("imageUrlValue2", saveItem, false));
imageRepository.saveAll(images);

Wish wish = new Wish(member, item, now());
Wish wish = new Wish(member, item);
wishRepository.save(wish);

// when
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package codesquard.app.api.wishitem;

import static codesquard.app.CategoryTestSupport.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import codesquard.app.ControllerTestSupport;
import codesquard.app.api.wishitem.response.WishCategoryListResponse;
import codesquard.app.domain.oauth.support.Principal;

@ActiveProfiles("test")
@WebMvcTest(controllers = WishItemController.class)
class WishItemControllerTest extends ControllerTestSupport {
private MockMvc mockMvc;

@Autowired
private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter;

@Autowired
private WishItemController wishItemController;

@MockBean
private WishItemService wishItemService;

@BeforeEach
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(wishItemController)
.setControllerAdvice(globalExceptionHandler)
.setCustomArgumentResolvers(authPrincipalArgumentResolver)
.setMessageConverters(jackson2HttpMessageConverter)
.alwaysDo(print())
.build();

given(authPrincipalArgumentResolver.supportsParameter(any())).willReturn(true);

Principal principal = new Principal(1L, "[email protected]", "23Yong", null, null);
given(authPrincipalArgumentResolver.resolveArgument(any(), any(), any(), any())).willReturn(principal);
}

@DisplayName("관심 상품들의 카테고리 목록을 요청한다")
@Test
public void readWishCategories() throws Exception {
// given
WishCategoryListResponse response = WishCategoryListResponse.of(
List.of(findByName("스포츠/레저"), findByName("가구/인테리어")));
given(wishItemService.readWishCategories(ArgumentMatchers.any(Principal.class)))
.willReturn(response);

// when & then
mockMvc.perform(get("/api/wishes/categories"))
.andExpect(status().isOk())
.andExpect(jsonPath("statusCode").value(equalTo(200)))
.andExpect(jsonPath("message").value(equalTo("관심상품의 카테고리 목록 조회를 완료하였습니다.")))
.andExpect(jsonPath("data.categories[*].categoryName").value(
containsInAnyOrder("스포츠/레저", "가구/인테리어")));
}

}
Loading

0 comments on commit b4eaa04

Please sign in to comment.