Skip to content

Commit

Permalink
[feat] : 마이페이지 구매내역, 판매내역 API (#121)
Browse files Browse the repository at this point in the history
* [fix] : Message 빌더 수정

* [fix] : 이미지 파일 리스트 전송 API @nOAuth추가

* [rename] : AuctionStatus 네이밍

* [feat] : NotificationResponse 에 NotificationId 추가

* [feat] : 구매내역, 판매내역 API 추가

* [style] : BiddingRepository 코드 정리

* [fix] : samesite 에러로 cookie 응답값 변경

* [fix] : 구매내역, 판매내역 메서드 네이밍 변경

* [feat] : AuctionFixture, BiddingFixture 에 id 생성자 생성

* [feat] : UserApiController 구매내역, 판매내역 api 개선

* [fix] : BiddingRepository 구매내역, 판매내역 조회 메서드 개선과 레포테스트 추가

* [test] : 구매내역, 판매내역 통합테스트 추가 (각각 전체, 입찰 중 조회)
  • Loading branch information
ParkJuhan94 authored Mar 20, 2024
1 parent bdacbe4 commit b6ff5c3
Show file tree
Hide file tree
Showing 16 changed files with 309 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dev.handsup.auth.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -21,7 +23,6 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
Expand All @@ -47,8 +48,8 @@ public ResponseEntity<LoginSimpleResponse> login(
LoginDetailResponse loginDetailResponse = authService.login(request);
LoginSimpleResponse loginSimpleResponse = LoginSimpleResponse.from(loginDetailResponse.accessToken());

Cookie cookie = AuthMapper.toCookie(loginDetailResponse);
httpServletResponse.addCookie(cookie);
ResponseCookie cookie = AuthMapper.toCookie(loginDetailResponse);
httpServletResponse.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

return ResponseEntity.ok(loginSimpleResponse);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import dev.handsup.auth.annotation.NoAuth;
import dev.handsup.image.dto.UploadImagesRequest;
import dev.handsup.image.dto.UploadImagesResponse;
import dev.handsup.image.service.S3Service;
Expand All @@ -22,8 +23,10 @@
@RequiredArgsConstructor
@RequestMapping("/api/images")
public class ImageApiController {

private final S3Service s3Service;

@NoAuth
@Operation(summary = "이미지 파일 리스트 전송 API", description = "이미지 파일을 전송하고 URL을 반환받는다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import dev.handsup.auction.domain.auction_field.AuctionStatus;
import dev.handsup.auction.dto.response.AuctionSimpleResponse;
import dev.handsup.auth.annotation.NoAuth;
import dev.handsup.auth.jwt.JwtAuthorization;
import dev.handsup.common.dto.PageResponse;
Expand Down Expand Up @@ -102,4 +104,32 @@ public ResponseEntity<UserProfileResponse> getUserProfile(
UserProfileResponse response = userService.getUserProfile(userId);
return ResponseEntity.ok(response);
}

@GetMapping("/api/users/buys")
@Operation(summary = "사용자 구매 내역 조회 API",
description = "사용자 구매 내역을 전체/입찰 중/거래 중/완료 별로 경매의 최신 등록 순으로 조회한다")
public ResponseEntity<PageResponse<AuctionSimpleResponse>> getUserBuyHistory(
@Parameter(hidden = true) @JwtAuthorization User user,
@RequestParam(value = "auctionStatus", required = false) AuctionStatus auctionStatus,
Pageable pageable
) {
PageResponse<AuctionSimpleResponse> response = userService
.getUserBuyHistory(user, auctionStatus, pageable);
return ResponseEntity.ok(response);
}

@NoAuth
@GetMapping("/api/users/{userId}/sales")
@Operation(summary = "사용자 판매 내역 조회 API",
description = "사용자 판매 내역을 전체/입찰 중/거래 중/완료 별로 경매의 최신 등록 순으로 조회한다")
public ResponseEntity<PageResponse<AuctionSimpleResponse>> getUserSaleHistory(
@PathVariable Long userId,
@RequestParam(value = "auctionStatus", required = false) AuctionStatus auctionStatus,
Pageable pageable
) {
PageResponse<AuctionSimpleResponse> response = userService
.getUserSaleHistory(userId, auctionStatus, pageable);
return ResponseEntity.ok(response);
}

}
133 changes: 121 additions & 12 deletions api/src/test/java/dev/handsup/user/controller/UserApiControllerTest.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
package dev.handsup.user.controller;

import static org.assertj.core.api.Assertions.*;
import static org.springframework.http.HttpHeaders.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.time.LocalDateTime;
import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import dev.handsup.auction.domain.Auction;
import dev.handsup.auction.domain.auction_field.AuctionStatus;
import dev.handsup.auction.domain.product.product_category.PreferredProductCategory;
import dev.handsup.auction.domain.product.product_category.ProductCategory;
import dev.handsup.auction.domain.product.product_category.ProductCategoryValue;
import dev.handsup.auction.repository.auction.AuctionRepository;
import dev.handsup.auction.repository.product.PreferredProductCategoryRepository;
import dev.handsup.auction.repository.product.ProductCategoryRepository;
import dev.handsup.bidding.domain.Bidding;
import dev.handsup.bidding.repository.BiddingRepository;
import dev.handsup.common.support.ApiTestSupport;
import dev.handsup.fixture.AuctionFixture;
import dev.handsup.fixture.BiddingFixture;
import dev.handsup.fixture.ReviewFixture;
import dev.handsup.fixture.UserFixture;
import dev.handsup.review.domain.Review;
Expand All @@ -38,16 +46,6 @@
@DisplayName("[User 통합 테스트]")
class UserApiControllerTest extends ApiTestSupport {

private final JoinUserRequest request = JoinUserRequest.of(
"[email protected]",
user.getPassword(),
user.getNickname(),
user.getAddress().getSi(),
user.getAddress().getGu(),
user.getAddress().getDong(),
user.getProfileImageUrl(),
List.of(1L)
);
@Autowired
private UserReviewLabelRepository userReviewLabelRepository;
@Autowired
Expand All @@ -62,15 +60,27 @@ class UserApiControllerTest extends ApiTestSupport {
private PreferredProductCategoryRepository preferredProductCategoryRepository;
@Autowired
private ProductCategoryRepository productCategoryRepository;
@Autowired
private BiddingRepository biddingRepository;

@Test
@DisplayName("[[회원가입 API] 회원이 등록되고 회원 ID를 응답한다]")
void joinUserTest() throws Exception {
JoinUserRequest joinUserRequest = JoinUserRequest.of(
"[email protected]",
user.getPassword(),
user.getNickname(),
user.getAddress().getSi(),
user.getAddress().getGu(),
user.getAddress().getDong(),
user.getProfileImageUrl(),
List.of(1L)
);
// when
ResultActions actions = mockMvc.perform(
post("/api/users")
.contentType(APPLICATION_JSON)
.content(toJson(request))
.content(toJson(joinUserRequest))
);

// then
Expand Down Expand Up @@ -172,7 +182,7 @@ void getUserReviewsTest() throws Exception {
}

@Test
@DisplayName("[사용자의 프로필이 반환된다]")
@DisplayName("[[사용자 프로필 조회 API]사용자의 프로필이 반환된다]")
void getUserProfile() throws Exception {
// given
ProductCategory productCategory1 = ProductCategory.from(ProductCategoryValue.BEAUTY_COSMETICS.toString());
Expand Down Expand Up @@ -204,4 +214,103 @@ void getUserProfile() throws Exception {
.value(ProductCategoryValue.BOOKS.toString()))
.andExpect(jsonPath("$.score").value(user.getScore()));
}

@Test
@DisplayName("[사용자 구매 내역 조회 API] 사용자 구매 내역을 "
+ "전체/입찰 중/거래 중/완료 별로 경매의 최신 등록 순으로 조회한다")
void getUserBuyHistory_All() throws Exception {
// given
LocalDateTime now = LocalDateTime.now();
Auction auction1 = AuctionFixture.auction(UserFixture.user(2L, "[email protected]"));
Auction auction2 = AuctionFixture.auction(UserFixture.user(3L, "[email protected]"));
Auction auction3 = AuctionFixture.auction(UserFixture.user(4L, "[email protected]"));
ReflectionTestUtils.setField(auction1, "createdAt", now.minusMinutes(1));
ReflectionTestUtils.setField(auction2, "createdAt", now);
ReflectionTestUtils.setField(auction3, "createdAt", now.plusMinutes(1));
productCategoryRepository.saveAll(List.of(
auction1.getProduct().getProductCategory(),
auction2.getProduct().getProductCategory(),
auction3.getProduct().getProductCategory()
));
auctionRepository.saveAll(List.of(auction1, auction2, auction3));
Bidding bidding1 = BiddingFixture.bidding(1L, auction1, user);
Bidding bidding2 = BiddingFixture.bidding(2L, auction2, user);
Bidding bidding3 = BiddingFixture.bidding(3L, auction3, user);
biddingRepository.saveAll(List.of(bidding1, bidding2, bidding3));

PageRequest pageRequest = PageRequest.of(0, 5);

// when, then
mockMvc.perform(get("/api/users/buys")
.header(AUTHORIZATION, "Bearer " + accessToken)
.param("auctionStatus", (String)null)
.contentType(APPLICATION_JSON)
.content(toJson(pageRequest)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content.size()").value(3))
.andExpect(jsonPath("$.content[0].auctionId").value(auction3.getId()))
.andExpect(jsonPath("$.content[1].auctionId").value(auction2.getId()))
.andExpect(jsonPath("$.content[2].auctionId").value(auction1.getId()));

mockMvc.perform(get("/api/users/buys")
.header(AUTHORIZATION, "Bearer " + accessToken)
.param("auctionStatus", String.valueOf(AuctionStatus.BIDDING))
.contentType(APPLICATION_JSON)
.content(toJson(pageRequest)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content.size()").value(3))
.andExpect(jsonPath("$.content[0].auctionId").value(auction3.getId()))
.andExpect(jsonPath("$.content[1].auctionId").value(auction2.getId()))
.andExpect(jsonPath("$.content[2].auctionId").value(auction1.getId()));
}

@Test
@DisplayName("[사용자 판매 내역 조회 API] 사용자 판매 내역을 "
+ "전체/입찰 중/거래 중/완료 별로 경매의 최신 등록 순으로 조회한다")
void getUserSaleHistory() throws Exception {
// given
LocalDateTime now = LocalDateTime.now();
Auction auction1 = AuctionFixture.auction(1L, user);
Auction auction2 = AuctionFixture.auction(2L, user);
Auction auction3 = AuctionFixture.auction(3L, user);
ReflectionTestUtils.setField(auction1, "createdAt", now.minusMinutes(1));
ReflectionTestUtils.setField(auction2, "createdAt", now);
ReflectionTestUtils.setField(auction3, "createdAt", now.plusMinutes(1));
productCategoryRepository.saveAll(List.of(
auction1.getProduct().getProductCategory(),
auction2.getProduct().getProductCategory(),
auction3.getProduct().getProductCategory()
));
auctionRepository.saveAll(List.of(auction1, auction2, auction3));
Bidding bidding1 = BiddingFixture.bidding(1L, auction1, user);
Bidding bidding2 = BiddingFixture.bidding(2L, auction2, user);
Bidding bidding3 = BiddingFixture.bidding(3L, auction3, user);
biddingRepository.saveAll(List.of(bidding1, bidding2, bidding3));

PageRequest pageRequest = PageRequest.of(0, 5);

// when, then
mockMvc.perform(get("/api/users/{userId}/sales", user.getId())
.header(AUTHORIZATION, "Bearer " + accessToken)
.param("auctionStatus", (String)null)
.contentType(APPLICATION_JSON)
.content(toJson(pageRequest)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content.size()").value(3))
.andExpect(jsonPath("$.content[0].auctionId").value(auction3.getId()))
.andExpect(jsonPath("$.content[1].auctionId").value(auction2.getId()))
.andExpect(jsonPath("$.content[2].auctionId").value(auction1.getId()));

mockMvc.perform(get("/api/users/{userId}/sales", user.getId())
.header(AUTHORIZATION, "Bearer " + accessToken)
.param("auctionStatus", String.valueOf(AuctionStatus.BIDDING))
.contentType(APPLICATION_JSON)
.content(toJson(pageRequest)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content.size()").value(3))
.andExpect(jsonPath("$.content[0].auctionId").value(auction3.getId()))
.andExpect(jsonPath("$.content[1].auctionId").value(auction2.getId()))
.andExpect(jsonPath("$.content[2].auctionId").value(auction1.getId()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
@RequiredArgsConstructor
public enum AuctionStatus {

BIDDING("입찰중"),
TRADING("거래중"),
COMPLETED("종료"),
BIDDING("입찰 중"),
TRADING("거래 중"),
COMPLETED("거래 완료"),
CANCELED("취소");

private final String label;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.handsup.auction.dto.response;

public record AuctionSimpleResponse(

Long auctionId,
String title,
int currentBiddingPrice,
Expand Down
19 changes: 11 additions & 8 deletions core/src/main/java/dev/handsup/auth/dto/AuthMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import static lombok.AccessLevel.*;

import java.time.Duration;

import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;

import dev.handsup.auth.dto.response.LoginDetailResponse;
import dev.handsup.auth.exception.AuthErrorCode;
Expand Down Expand Up @@ -32,14 +35,14 @@ public static String toAccessToken(HttpServletRequest request) {
throw new NotFoundException(AuthErrorCode.NOT_FOUND_ACCESS_TOKEN_IN_REQUEST);
}

public static Cookie toCookie(LoginDetailResponse response) {
Cookie cookie = new Cookie("refreshToken", response.refreshToken());
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(14 * 24 * 60 * 60); // 14일

return cookie;
public static ResponseCookie toCookie(LoginDetailResponse response) {
return ResponseCookie.from("refreshToken", response.refreshToken())
.path("/")
.sameSite("None")
.httpOnly(false)
.secure(true)
.maxAge(Duration.ofDays(15))
.build();
}

public static String extractRefreshTokenFromCookies(HttpServletRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.data.repository.query.Param;

import dev.handsup.auction.domain.Auction;
import dev.handsup.auction.domain.auction_field.AuctionStatus;
import dev.handsup.bidding.domain.Bidding;
import dev.handsup.user.domain.User;

Expand All @@ -31,4 +32,14 @@ public interface BiddingRepository extends JpaRepository<Bidding, Long> {
Slice<Bidding> findByAuctionIdOrderByBiddingPriceDesc(Long auctionId, Pageable pageable);

Optional<Bidding> findByAuctionAndBidder(Auction auction, User bidder);

Slice<Bidding> findByBidderOrderByAuction_CreatedAtDesc(User bidder, Pageable pageable);

Slice<Bidding> findByBidderAndAuction_StatusOrderByAuction_CreatedAtDesc(
User bidder, AuctionStatus auctionStatus, Pageable pageable);

Slice<Bidding> findByAuction_Seller_IdOrderByAuction_CreatedAtDesc(Long sellerId, Pageable pageable);

Slice<Bidding> findByAuction_Seller_IdAndAuction_StatusOrderByAuction_CreatedAtDesc(
Long sellerId, AuctionStatus auctionStatus, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
public class NotificationMapper {

public static NotificationResponse toNotificationResponse(
Long notificationId,
NotificationType notificationType,
String content,
String senderProfileImageUrl,
Long auctionId,
String auctionImageUrl
) {
return NotificationResponse.of(
notificationId,
notificationType,
content,
senderProfileImageUrl,
Expand Down
Loading

0 comments on commit b6ff5c3

Please sign in to comment.