Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 입찰 기능 추가 #13

Closed
wants to merge 2 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
import java.util.List;

import org.auction.domain.auction.domain.enums.AuctionType;
import org.springframework.format.annotation.DateTimeFormat;

import com.fasterxml.jackson.annotation.JsonFormat;

public record AuctionDetailResponse(
String title,
String content,
Integer startPrice,
Integer bidUnit,
AuctionType auctionType,
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime startDate,
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime endDate,
List<String> imageKeys // Include image keys
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.auction.client.bid.application;

import java.util.Comparator;
import java.util.List;

import org.auction.client.bid.interfaces.request.CreateBidRequest;
import org.auction.client.bid.interfaces.response.BidResponse;
import org.auction.client.bid.interfaces.response.CreateBidResponse;
import org.auction.domain.auction.domain.entity.AuctionEntity;
import org.auction.domain.auction.domain.enums.AuctionStatus;
import org.auction.domain.auction.infrastructure.AuctionRepository;
import org.auction.domain.bid.entity.BidEntity;
import org.auction.domain.bid.infrastructure.BidRepository;
import org.auction.domain.user.domain.entity.MemberEntity;
import org.auction.domain.user.infrastructure.MemberRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class BidService {

private final BidRepository bidRepository;
private final AuctionRepository auctionRepository;
private final MemberRepository memberRepository;

@Transactional(readOnly = true)
public List<BidResponse> findBidList(
Long auction_id
) {

AuctionEntity auctionEntity = auctionRepository.findById(auction_id).orElseThrow();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외 부분 제가 수정했으니, 나중에 제꺼 PR 받으면 추가하면 될 것 같아ㅛ.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 작업이 더 필요한데 아직 진행하지 않은 사항에 대해서는 TODO를 붙이면 좋을 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 알겠습니다. 앞으로 붙이도록 하겠습니다.


return bidRepository.findALlByAuctionEntity(auctionEntity).stream()
.sorted(Comparator.comparing(BidEntity::getCreatedAt).reversed())
.map(BidResponse::toDto)
.toList();
Comment on lines +36 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분을 service까지 끌고와서 sorting하지 않고 DB에서 처리해서 가져오기로 했던걸로 기억하는데 아직 수정이 안된걸까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내부적으로는 그렇게 했었고 추후 멘토링과정에서 '변경이 있을까' 라는 생각에서 남겨두게 되었습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내부적으로는 DB단에서 하기로 했었습니다만 멘토링과정에서 다르게 될 여지도 있다고 생각해서 이후에 수정하는게 맞다고 생각했습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다! 오늘 멘토링 이후에 수정하도록 하죠!


}

@Transactional
public CreateBidResponse addBid(
Long auction_id,
Long buyerId,
CreateBidRequest createBidRequest
) {

MemberEntity memberEntity = memberRepository.findById(buyerId)
.orElseThrow(() -> new RuntimeException("Member not found"));

AuctionEntity auctionEntity = auctionRepository.findById(auction_id)
.orElseThrow(() -> new RuntimeException("Auction not found"));

if (!checkLastBid(memberEntity, createBidRequest.getBidPrice())) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

바로 이전에 내가 입찰을 했으면 입찰 연속으로 못하는 예외는 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드에 반영하도록 하겠습니다. 감사합니다.

throw new RuntimeException("Last bid price is incorrect");
}

if (!checkAutionStatus(auctionEntity)) {
throw new RuntimeException("Auction status is incorrect");
}

BidEntity bidEntity = BidEntity.builder()
.auctionEntity(auctionEntity)
.memberEntity(memberEntity)
.bidPrice(createBidRequest.getBidPrice())
.build();

bidRepository.save(bidEntity);

return CreateBidResponse.toDto(bidEntity);

}

private boolean checkLastBid(
MemberEntity memberEntity,
Long bidPrice
) {
BidEntity beforeBidEntity = bidRepository.findTopByMemberEntityOrderByBidPriceDesc(memberEntity);

if (beforeBidEntity != null) {
return beforeBidEntity.getBidPrice() < bidPrice;
}
Comment on lines +80 to +84
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repository에서 데이터를 조회하는 경우 null이 존재할 수 있다면 entity를 바로 가져오는 것보다는 Optional로 감싸서 받아오는게 좋다고 생각합니다.
현재 로직은 간단해서 문제가 되지 않지만 추후 로직이 복잡해진다면 사람이 실수할 수 있는 내용이라서요!

return true;

}

private boolean checkAutionStatus(AuctionEntity auctionEntity) {

if (!(auctionEntity.getAuctionStatus() == AuctionStatus.BIDDING)) {
return false;
}
return true;

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.auction.client.bid.interfaces;

import java.util.List;

import org.auction.client.bid.interfaces.request.CreateBidRequest;
import org.auction.client.bid.interfaces.response.BidResponse;
import org.auction.client.bid.interfaces.response.CreateBidResponse;
import org.auction.client.common.dto.ResultDto;
import org.springframework.http.ResponseEntity;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;

public interface BidApi {

@Operation(summary = "입찰 목록 가져오기", description = "경매의 입찰 목록을 가져옵니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "입찰 목록을 성공적으로 가져왔습니다.",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ResultDto.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청입니다.",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ResultDto.class))),
@ApiResponse(responseCode = "500", description = "서버 오류가 발생했습니다.",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ResultDto.class)))
})
ResponseEntity<ResultDto<List<BidResponse>>> bidList(
@Parameter(description = "입찰 목록을 확인 할 경매", required = true) Long auction_id

);

@Operation(summary = "입찰하기", description = "해당 경매에 입찰합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "입찰에 성공하였습니다.",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ResultDto.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청입니다.",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ResultDto.class))),
@ApiResponse(responseCode = "500", description = "서버 오류가 발생했습니다.",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ResultDto.class)))
})
ResponseEntity<ResultDto<CreateBidResponse>> bidAdd(
@Parameter(description = "입찰 할 경매", required = true) Long auctionId,
@Parameter(description = "입찰 할 멤버", required = true) Long buyerId,
@Parameter(description = "입찰 금액", required = true) CreateBidRequest createBidRequest
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.auction.client.bid.interfaces;

import java.util.List;

import org.auction.client.bid.application.BidService;
import org.auction.client.bid.interfaces.request.CreateBidRequest;
import org.auction.client.bid.interfaces.response.BidResponse;
import org.auction.client.bid.interfaces.response.CreateBidResponse;
import org.auction.client.common.dto.ResultDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/auction")
public class BidController implements BidApi {

private final BidService bidService;

@GetMapping("/{auction_id}/bid")
public ResponseEntity<ResultDto<List<BidResponse>>> bidList(
@PathVariable("auction_id") Long auction_id

) {

List<BidResponse> bidList = bidService.findBidList(auction_id);

ResultDto<List<BidResponse>> resultDto = new ResultDto<>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Response 하는 부분도 수정했으니 제가 월요일에 설명드리겠습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 알겠습니다.

HttpStatus.OK, "성공적으로 입찰을 불러왔습니다.", bidList
);

return ResponseEntity.ok(resultDto);

}

@PostMapping("/{auction_id}/bid")
public ResponseEntity<ResultDto<CreateBidResponse>> bidAdd(
@PathVariable("auction_id") Long auctionId,
@RequestParam("buyer_id") Long buyerId,
@RequestBody CreateBidRequest createBidRequest
) {

CreateBidResponse createBidResponse = bidService.addBid(auctionId, buyerId, createBidRequest);

ResultDto<CreateBidResponse> resultDto = new ResultDto<>(
HttpStatus.OK, "성공적으로 입찰 되었습니다.", createBidResponse
);

return ResponseEntity.ok(resultDto);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.auction.client.bid.interfaces.request;

public record CreateBidRequest(
Long bidPrice

) {
public Long getBidPrice() {
return bidPrice;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.auction.client.bid.interfaces.response;

import java.time.LocalDateTime;

import org.auction.domain.bid.entity.BidEntity;

import com.fasterxml.jackson.annotation.JsonFormat;

public record BidResponse(
Long buyerId,
Long bidPrice,
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime createdAt
) {
public static BidResponse toDto(BidEntity bidEntity) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static BidResponse toDto(BidEntity bidEntity) {
		return new BidResponse(
			bidEntity.getMemberEntity().getMemberId(),
			bidEntity.getBidPrice(),
			bidEntity.getCreatedAt()
		);
	}

해당 코드는 매개변수가 한 눈에 들어올수 있도록 이런식으로 수정하면 좋을 것 같아요!

return new BidResponse(bidEntity.getMemberEntity().getMemberId(), bidEntity.getBidPrice(),
bidEntity.getCreatedAt());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.auction.client.bid.interfaces.response;

import java.time.LocalDateTime;

import org.auction.domain.bid.entity.BidEntity;

public record CreateBidResponse(
Long buyerId,
Long bidPrice,
LocalDateTime createAt
) {
public static CreateBidResponse toDto(BidEntity bidEntity) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위의 리뷰과 마찬가지 입니다~

return new CreateBidResponse(bidEntity.getMemberEntity().getMemberId(), bidEntity.getBidPrice(),
bidEntity.getCreatedAt());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.auction.domain.bid.entity;

import java.time.LocalDateTime;

import org.auction.domain.auction.domain.entity.AuctionEntity;
import org.auction.domain.user.domain.entity.MemberEntity;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity(name = "bids")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 뭔가요..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimeTrackableEntity 를 상속 받을 필요는 없다고 생각해서
createdAt 필드를 만들고 자동으로 값을 넣어주기 위해 사용했습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_at만 담당하는 추상 클래스를 만들어서 해당 클래스를 상속받도록 수정하면 좋을 것 같아요~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 알겠습니다.

public class BidEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "bid_id")
private Long bidId;

@Column(name = "bid_price")
private Long bidPrice;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cascade 부분 설명 부탁드립니다!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuctionEntity 해당 엔티티에 변경이 되면 관련된 BidEntity도 반영되어야 한다고 생각해서 했습니다.

@JoinColumn(name = "auction_id")
private AuctionEntity auctionEntity;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "member_id")
private MemberEntity memberEntity;

@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;

@Builder
public BidEntity(AuctionEntity auctionEntity, MemberEntity memberEntity, Long bidPrice) {
this.auctionEntity = auctionEntity;
this.memberEntity = memberEntity;
this.bidPrice = bidPrice;

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.auction.domain.bid.infrastructure;

import java.util.List;

import org.auction.domain.auction.domain.entity.AuctionEntity;
import org.auction.domain.bid.entity.BidEntity;
import org.auction.domain.user.domain.entity.MemberEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BidRepository extends JpaRepository<BidEntity, Long> {

BidEntity findTopByMemberEntityOrderByBidPriceDesc(MemberEntity memberEntity);

List<BidEntity> findALlByAuctionEntity(AuctionEntity auctionEntity);

}