From 82329d842969c8590a478fb4ed0ad327d2be35a2 Mon Sep 17 00:00:00 2001 From: yeon015 Date: Sat, 19 Aug 2023 18:33:32 +0900 Subject: [PATCH] =?UTF-8?q?feat(#125)=20:=20=EB=A9=94=EC=9D=B8=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=ED=8E=98=EC=9D=B4=EC=A7=95=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?&=20=EA=B1=B0=EB=9E=98=ED=95=98=EA=B8=B0=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20&=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/web/HistoryController.java | 21 +++++++ .../controller/web/ProductController.java | 18 ++++-- .../trothcam/domain/history/History.java | 6 ++ .../trothcam/domain/product/Product.java | 3 + .../domain/product/ProductRepository.java | 13 ++++ .../trothcam/dto/web/HistoryResDto.java | 21 +++++++ .../dto/web/ProductsPagingListResDto.java | 16 +++++ .../trothcam/dto/web/TransactionReqDto.java | 14 +++++ .../trothcam/exception/base/ErrorCode.java | 3 + .../trothcam/service/web/HistoryService.java | 28 ++++++++- .../service/web/LikeProductService.java | 5 +- .../trothcam/service/web/ProductService.java | 62 ++++++++++++++++--- 12 files changed, 194 insertions(+), 16 deletions(-) create mode 100644 src/main/java/trothly/trothcam/dto/web/HistoryResDto.java create mode 100644 src/main/java/trothly/trothcam/dto/web/ProductsPagingListResDto.java create mode 100644 src/main/java/trothly/trothcam/dto/web/TransactionReqDto.java diff --git a/src/main/java/trothly/trothcam/controller/web/HistoryController.java b/src/main/java/trothly/trothcam/controller/web/HistoryController.java index 192a8c5..fc5096a 100644 --- a/src/main/java/trothly/trothcam/controller/web/HistoryController.java +++ b/src/main/java/trothly/trothcam/controller/web/HistoryController.java @@ -1,11 +1,32 @@ package trothly.trothcam.controller.web; import lombok.RequiredArgsConstructor; +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.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import trothly.trothcam.domain.member.Member; +import trothly.trothcam.dto.web.HistoryResDto; +import trothly.trothcam.dto.web.TransactionReqDto; +import trothly.trothcam.exception.base.BaseResponse; +import trothly.trothcam.exception.custom.BadRequestException; +import trothly.trothcam.service.web.HistoryService; @RequiredArgsConstructor @RestController @RequestMapping("/api/history") public class HistoryController { + + private final HistoryService historyService; + + @PostMapping("/transaction") + public BaseResponse saveHistory(@RequestBody TransactionReqDto req, @AuthenticationPrincipal Member member) { + if(req.getProductId() == null) { + throw new BadRequestException("존재하지 않는 상품 아이디 입니다."); + } + + HistoryResDto res = historyService.saveTransaction(req, member); + return BaseResponse.onSuccess(res); + } } diff --git a/src/main/java/trothly/trothcam/controller/web/ProductController.java b/src/main/java/trothly/trothcam/controller/web/ProductController.java index 4bea8d5..679e545 100644 --- a/src/main/java/trothly/trothcam/controller/web/ProductController.java +++ b/src/main/java/trothly/trothcam/controller/web/ProductController.java @@ -7,10 +7,7 @@ import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.*; import trothly.trothcam.domain.member.Member; -import trothly.trothcam.dto.web.ProductDetailResDto; -import trothly.trothcam.dto.web.ProductRankResDto; -import trothly.trothcam.dto.web.ProductReqDto; -import trothly.trothcam.dto.web.ProductsResDto; +import trothly.trothcam.dto.web.*; import trothly.trothcam.exception.base.BaseException; import trothly.trothcam.exception.base.BaseResponse; import trothly.trothcam.exception.base.ErrorCode; @@ -86,6 +83,19 @@ public BaseResponse> findRanking(@PathVariable String ty return BaseResponse.onSuccess(result); } + /* 메인 화면 페이징 처리 */ + @GetMapping("/product-ranking/{type}/{page}") + public BaseResponse getProducts(@PathVariable String type, @PathVariable int page) { + if(type.equals("top") && page >= 0) { + return BaseResponse.onSuccess(productService.getProductsTop(page)); + } else if (type.equals("latest") && page >= 0) { + return BaseResponse.onSuccess(productService.getProductsLatest(page)); + } else { + throw new BaseException(REQUEST_ERROR); + } + + } + /* view all */ @GetMapping("/view-all/{type}") public BaseResponse> findViewAll(@PathVariable String type) { diff --git a/src/main/java/trothly/trothcam/domain/history/History.java b/src/main/java/trothly/trothcam/domain/history/History.java index ebe7fb2..2ef9e08 100644 --- a/src/main/java/trothly/trothcam/domain/history/History.java +++ b/src/main/java/trothly/trothcam/domain/history/History.java @@ -45,4 +45,10 @@ public class History { @Column(name = "sold_at", updatable = false, nullable = false) private LocalDateTime soldAt; + public History(Product product, Member seller, Member buyer, Long price) { + this.product = product; + this.seller = seller; + this.buyer = buyer; + this.price = price; + } } diff --git a/src/main/java/trothly/trothcam/domain/product/Product.java b/src/main/java/trothly/trothcam/domain/product/Product.java index 54c5fff..811cde3 100644 --- a/src/main/java/trothly/trothcam/domain/product/Product.java +++ b/src/main/java/trothly/trothcam/domain/product/Product.java @@ -63,6 +63,9 @@ public void updatePublicYn(PublicYn publicYn) { this.publicYn = publicYn; } + public void updateOwner(Member owner) { + this.member = owner; + } public void updateInfo(PublicResDto publicResDto) { this.price = publicResDto.getPrice(); this.description = publicResDto.getDescription(); diff --git a/src/main/java/trothly/trothcam/domain/product/ProductRepository.java b/src/main/java/trothly/trothcam/domain/product/ProductRepository.java index 45b0df0..552b450 100644 --- a/src/main/java/trothly/trothcam/domain/product/ProductRepository.java +++ b/src/main/java/trothly/trothcam/domain/product/ProductRepository.java @@ -1,5 +1,7 @@ package trothly.trothcam.domain.product; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @@ -39,6 +41,17 @@ public interface ProductRepository extends JpaRepository { "from history h join product p on h.product_id = p.product_id order by sold_at desc LIMIT 30", nativeQuery = true) List findRankLatestViewAllDto(); + //페이징 처리 + @Query(value = "select ap.history_id as historyId, ap.product_id as productId, ap.seller_id as sellerId, ap.buyer_id as buyerId, ap.price as price, ap.sold_at as soldAt, p.image_id as imageId, p.title as title, p.tags as tags\n" + + "from (select *, rank() over (partition by h.product_id order by price desc, sold_at asc) as rk from history h) as ap join product p on ap.product_id = p.product_id\n" + + "where ap.rk <= 1\n" + + "order by price desc, sold_at asc", nativeQuery = true, countQuery = "select count(*) from (select *, rank() over (partition by h.product_id order by h.price desc, sold_at asc) as rk from history h) as ap " + + "join product p on ap.product_id = p.product_id where ap.rk <= 1 group by ap.price, sold_at order by ap.price desc, sold_at asc") + Page findRankPagingDto(Pageable pageable); + + @Query(value = "select h.history_id as historyId, h.product_id as productId, h.seller_id as sellerId, h.buyer_id as buyerId, h.price as price, h.sold_at as soldAt, p.image_id as imageId, p.title as title, p.tags as tags \n" + + "from history h join product p on h.product_id = p.product_id order by sold_at desc", nativeQuery = true) + Page findLatestPagingDto(Pageable pageable); // 비공개 인증서 리스트 조회 List findAllByMemberAndPublicYn(Member member, PublicYn publicYn); diff --git a/src/main/java/trothly/trothcam/dto/web/HistoryResDto.java b/src/main/java/trothly/trothcam/dto/web/HistoryResDto.java new file mode 100644 index 0000000..3cb7f41 --- /dev/null +++ b/src/main/java/trothly/trothcam/dto/web/HistoryResDto.java @@ -0,0 +1,21 @@ +package trothly.trothcam.dto.web; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class HistoryResDto { + private Long historyId; + private Long productId; + private Long buyerId; + private Long sellerId; + private Long price; + private LocalDateTime soldAt; + private Long newOwnerId; +} diff --git a/src/main/java/trothly/trothcam/dto/web/ProductsPagingListResDto.java b/src/main/java/trothly/trothcam/dto/web/ProductsPagingListResDto.java new file mode 100644 index 0000000..ccf39d0 --- /dev/null +++ b/src/main/java/trothly/trothcam/dto/web/ProductsPagingListResDto.java @@ -0,0 +1,16 @@ +package trothly.trothcam.dto.web; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class ProductsPagingListResDto { + private List getProductRankResDto; + private int totalPages; +} diff --git a/src/main/java/trothly/trothcam/dto/web/TransactionReqDto.java b/src/main/java/trothly/trothcam/dto/web/TransactionReqDto.java new file mode 100644 index 0000000..8b59e30 --- /dev/null +++ b/src/main/java/trothly/trothcam/dto/web/TransactionReqDto.java @@ -0,0 +1,14 @@ +package trothly.trothcam.dto.web; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class TransactionReqDto { + private Long productId; + private Long price; +} diff --git a/src/main/java/trothly/trothcam/exception/base/ErrorCode.java b/src/main/java/trothly/trothcam/exception/base/ErrorCode.java index bce4be4..a25c96c 100644 --- a/src/main/java/trothly/trothcam/exception/base/ErrorCode.java +++ b/src/main/java/trothly/trothcam/exception/base/ErrorCode.java @@ -32,6 +32,9 @@ public enum ErrorCode { // BaseResponseStatus와 같은 역할 NOT_LIKED(2014, "좋아요를 누르지 않은 상품입니다.", BAD_REQUEST), HISTORIES_NOT_FOUND(2015, "거래 내역이 존재하지 않습니다.", BAD_REQUEST), PRODUCT_NOT_FOUND(2016, "인증서가 존재하지 않습니다.", BAD_REQUEST), + SAME_MEMBER(2017, "상품의 소유자와 현재 구매하려고 하는 사용자가 같습니다.", BAD_REQUEST), + PRODUCT_IS_NOT_FOUND(2018, "존재하지 않는 상품입니다.", BAD_REQUEST), + IMAGE_NOT_FOUND(2019, "존재하지 않는 이미지입니다.", BAD_REQUEST), /** * 3000 : Response 오류 diff --git a/src/main/java/trothly/trothcam/service/web/HistoryService.java b/src/main/java/trothly/trothcam/service/web/HistoryService.java index 6ad3048..ed60516 100644 --- a/src/main/java/trothly/trothcam/service/web/HistoryService.java +++ b/src/main/java/trothly/trothcam/service/web/HistoryService.java @@ -5,15 +5,21 @@ import org.springframework.transaction.annotation.Transactional; import trothly.trothcam.domain.history.History; import trothly.trothcam.domain.history.HistoryRepository; +import trothly.trothcam.domain.member.Member; +import trothly.trothcam.domain.member.MemberRepository; +import trothly.trothcam.domain.product.Product; import trothly.trothcam.domain.product.ProductRepository; import trothly.trothcam.dto.web.HistoryDto; +import trothly.trothcam.dto.web.HistoryResDto; import trothly.trothcam.dto.web.ProductReqDto; +import trothly.trothcam.dto.web.TransactionReqDto; import trothly.trothcam.exception.base.BaseException; +import trothly.trothcam.exception.custom.BadRequestException; import java.util.ArrayList; import java.util.List; -import static trothly.trothcam.exception.base.ErrorCode.HISTORIES_NOT_FOUND; +import static trothly.trothcam.exception.base.ErrorCode.*; @Service @Transactional @@ -22,6 +28,7 @@ public class HistoryService { private final HistoryRepository historyRepository; private final ProductRepository productRepository; + private final MemberRepository memberRepository; // 거래 내역 전체 조회 public List findAllHistory(ProductReqDto req) { @@ -41,4 +48,23 @@ public List findAllHistory(ProductReqDto req) { } // 거래 내역 저장 + public HistoryResDto saveTransaction(TransactionReqDto req, Member member) { + Product product = productRepository.findById(req.getProductId()).orElseThrow( + () -> new BaseException(PRODUCT_IS_NOT_FOUND) + ); + + Member seller = memberRepository.findById(product.getMember().getId()).orElseThrow( + () -> new BaseException(MEMBER_NOT_FOUND) + ); + + if(member.getId() == product.getMember().getId()) { + throw new BaseException(SAME_MEMBER); + } + + History newHistory = historyRepository.save(new History(product, seller, member, req.getPrice())); + + product.updateOwner(member); + + return new HistoryResDto(newHistory.getId(), product.getId(), member.getId(), seller.getId(), req.getPrice(), newHistory.getSoldAt(), member.getId()); + } } diff --git a/src/main/java/trothly/trothcam/service/web/LikeProductService.java b/src/main/java/trothly/trothcam/service/web/LikeProductService.java index 82eda35..2664c42 100644 --- a/src/main/java/trothly/trothcam/service/web/LikeProductService.java +++ b/src/main/java/trothly/trothcam/service/web/LikeProductService.java @@ -15,8 +15,7 @@ import java.util.Optional; -import static trothly.trothcam.exception.base.ErrorCode.ALREADY_LIKED; -import static trothly.trothcam.exception.base.ErrorCode.NOT_LIKED; +import static trothly.trothcam.exception.base.ErrorCode.*; @Service @Transactional @@ -35,7 +34,7 @@ public LikeResDto saveLike(ProductReqDto req, Member member) { } Product product = productRepository.findById(req.getProductId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 상품입니다.") + () -> new BaseException(PRODUCT_IS_NOT_FOUND) ); LikeProduct newLike = likeProductRepository.save(new LikeProduct(product, member)); diff --git a/src/main/java/trothly/trothcam/service/web/ProductService.java b/src/main/java/trothly/trothcam/service/web/ProductService.java index 7240498..5c584a4 100644 --- a/src/main/java/trothly/trothcam/service/web/ProductService.java +++ b/src/main/java/trothly/trothcam/service/web/ProductService.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import trothly.trothcam.domain.history.History; @@ -29,6 +31,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import static trothly.trothcam.exception.base.ErrorCode.*; + @Slf4j @Service @Transactional(readOnly = true) @@ -99,19 +103,19 @@ public ProductDetailResDto findProductDetailOn(ProductReqDto req, Member member) Boolean liked = false; Product product = productRepository.findById(req.getProductId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 상품입니다.") + () -> new BaseException(PRODUCT_IS_NOT_FOUND) ); Image image = imageRepository.findById(product.getImage().getId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 이미지입니다.") + () -> new BaseException(IMAGE_NOT_FOUND) ); Member findOwner = memberRepository.findById(product.getMember().getId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 회원입니다.") + () -> new BaseException(MEMBER_NOT_FOUND) ); Member findAuthorship = memberRepository.findById(image.getMember().getId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 회원입니다.") + () -> new BaseException(MEMBER_NOT_FOUND) ); // 조회수 갱신 @@ -141,19 +145,19 @@ public ProductDetailResDto findProductDetail(ProductReqDto req) { Boolean liked = false; Product product = productRepository.findById(req.getProductId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 상품입니다.") + () -> new BaseException(PRODUCT_IS_NOT_FOUND) ); Image image = imageRepository.findById(product.getImage().getId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 이미지입니다.") + () -> new BaseException(IMAGE_NOT_FOUND) ); Member findOwner = memberRepository.findById(product.getMember().getId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 회원입니다.") + () -> new BaseException(MEMBER_NOT_FOUND) ); Member findAuthorship = memberRepository.findById(image.getMember().getId()).orElseThrow( - () -> new BadRequestException("존재하지 않는 회원입니다.") + () -> new BaseException(MEMBER_NOT_FOUND) ); // 조회수 갱신 @@ -214,6 +218,48 @@ public List findProductRankLatest() { return latestResDtos; } + /* 메인 페이징 처리 - top */ + @Transactional + public ProductsPagingListResDto getProductsTop(int page) { + try { + PageRequest pageRequest = PageRequest.of(page, 8); + Page productTops = productRepository.findRankPagingDto(pageRequest); + List topPagingDto = productTops.stream() + .map(t -> { + Optional owner = memberRepository.findById(t.getBuyerId()); + Optional image = imageRepository.findById(t.getImageId()); + + return new ProductRankResDto(t.getHistoryId(), t.getProductId(), owner.get().getWebToken(), owner.get().getName(), + image.get().getMember().getWebToken(), t.getTitle(), t.getTags(), image.get().getImageUrl(), + t.getPrice(), t.getSoldAt()); + }).collect(Collectors.toList()); + return new ProductsPagingListResDto(topPagingDto, productTops.getTotalPages()); + } catch (Exception e) { + throw new BaseException(ErrorCode.DATABASE_ERROR); + } + } + + /* 메인 페이징 처리 - top */ + @Transactional + public ProductsPagingListResDto getProductsLatest(int page) { + try { + PageRequest pageRequest = PageRequest.of(page, 8); + Page productLatests = productRepository.findLatestPagingDto(pageRequest); + List latestPagingDto = productLatests.stream() + .map(t -> { + Optional owner = memberRepository.findById(t.getBuyerId()); + Optional image = imageRepository.findById(t.getImageId()); + + return new ProductRankResDto(t.getHistoryId(), t.getProductId(), owner.get().getWebToken(), owner.get().getName(), + image.get().getMember().getWebToken(), t.getTitle(), t.getTags(), image.get().getImageUrl(), + t.getPrice(), t.getSoldAt()); + }).collect(Collectors.toList()); + return new ProductsPagingListResDto(latestPagingDto, productLatests.getTotalPages()); + } catch (Exception e) { + throw new BaseException(ErrorCode.DATABASE_ERROR); + } + } + /* view all top 조회 */ @Transactional public List findRankViewAllTop() {