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

Feature/12 : 구매 완료 상품, 판매 완료 상품 조회 #20

Merged
merged 4 commits into from
Oct 19, 2024
Merged
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 @@ -2,9 +2,12 @@ package com.get_offer.auction.controller

import ApiResponse
import com.get_offer.auction.service.AuctionService
import com.get_offer.auction.service.BuyProductListDto
import com.get_offer.auction.service.SellProductListDto
import com.get_offer.auction.service.BuyAuctionDetailDto
import com.get_offer.auction.service.BuyAuctionDto
import com.get_offer.auction.service.SellAuctionDetailDto
import com.get_offer.auction.service.SellAuctionDto
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
Expand All @@ -19,15 +22,31 @@ class AuctionController(
* 사용자가 판매한 모든 상품을 조회 한다.
*/
@GetMapping("/sellHistory")
fun getUserSellHistory(@RequestParam userId: String): ApiResponse<List<SellProductListDto>> {
fun getUserSellHistory(@RequestParam userId: String): ApiResponse<List<SellAuctionDto>> {
return ApiResponse.success(auctionService.getSellHistory(userId.toLong()))
}

/**
* 사용자가 경매에서 낙찰된 모든 상품을 조회한다.
*/
@GetMapping("/buyHistory")
fun getUserBuyHistory(@RequestParam userId: String): ApiResponse<List<BuyProductListDto>> {
fun getUserBuyHistory(@RequestParam userId: String): ApiResponse<List<BuyAuctionDto>> {
return ApiResponse.success(auctionService.getBuyHistory(userId.toLong()))
}

@GetMapping("{auctionId}/sold")
fun getSoldAuctionDetail(
@RequestParam userId: String,
@PathVariable auctionId: Long
): ApiResponse<SellAuctionDetailDto> {
return ApiResponse.success(auctionService.getSoldAuctionDetail(userId.toLong(), auctionId))
}

@GetMapping("{id}/bought")
fun getBoughtAuctionDetail(
@RequestParam userId: String,
@PathVariable id: Long
): ApiResponse<BuyAuctionDetailDto> {
return ApiResponse.success(auctionService.getBoughtAuctionDetail(userId.toLong(), id))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ class AuctionResult(

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val id: Long = 0L,
) : AuditingTimeEntity()
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.get_offer.product.domain.Product
import com.get_offer.product.domain.ProductStatus
import java.time.LocalDateTime

data class AuctionProductListDto(
data class AuctionProductUnitDto(
val id: Long?,
val writerId: Long,
val name: String,
Expand All @@ -15,11 +15,10 @@ data class AuctionProductListDto(
val status: ProductStatus,
val startDate: LocalDateTime,
val endDate: LocalDateTime,
val isMine: Boolean,
) {
companion object {
fun of(product: Product, userId: Long?): AuctionProductListDto {
return AuctionProductListDto(
fun of(product: Product): AuctionProductUnitDto {
return AuctionProductUnitDto(
id = product.id,
writerId = product.writerId,
name = product.title,
Expand All @@ -29,7 +28,6 @@ data class AuctionProductListDto(
status = product.status,
startDate = product.startDate,
endDate = product.endDate,
isMine = product.writerId == userId,
)
}
}
Expand Down
47 changes: 43 additions & 4 deletions src/main/kotlin/com/get_offer/auction/service/AuctionService.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,68 @@
package com.get_offer.auction.service

import com.get_offer.auction.controller.repository.AuctionResultRepository
import com.get_offer.auction.domain.AuctionResult
import com.get_offer.common.exception.NotFoundException
import com.get_offer.common.exception.UnAuthorizationException
import com.get_offer.product.domain.Product
import com.get_offer.product.repository.ProductRepository
import com.get_offer.user.domain.User
import com.get_offer.user.repository.UserRepository
import org.springframework.stereotype.Service

@Service
class AuctionService(
private val productRepository: ProductRepository,
private val auctionRepository: AuctionResultRepository,
private val userRepository: UserRepository,
) {
fun getSellHistory(userId: Long): List<SellProductListDto> {
fun getSellHistory(userId: Long): List<SellAuctionDto> {
val productList = productRepository.findAllByWriterIdOrderByEndDateDesc(userId)

return productList.map { SellProductListDto.of(it, userId) }
return productList.map { SellAuctionDto.of(it, userId) }
}

fun getBuyHistory(userId: Long): List<BuyProductListDto> {
fun getBuyHistory(userId: Long): List<BuyAuctionDto> {
// 옥션 최종 정보에서 buyer가 나인걸 찾음
val auctionResults = auctionRepository.findAllByBuyerIdOrderByCreatedAtDesc(userId)
// 해당 상품 정보를 찾음
return auctionResults.map {
val product = productRepository.findById(it.productId)
.orElseThrow { NotFoundException("${it.productId} 의 상품은 존재하지 않습니다.") }
BuyProductListDto.of(it, product, userId)
BuyAuctionDto.of(it, product)
}
}

fun getSoldAuctionDetail(userId: Long, auctionId: Long): SellAuctionDetailDto {
val (auction, product) = getAuctionAndProduct(auctionId)
if (userId != product.writerId) throw UnAuthorizationException()

val buyer = getUser(auction.buyerId)

return SellAuctionDetailDto.of(product, buyer, auction)
}

fun getBoughtAuctionDetail(userId: Long, auctionId: Long): BuyAuctionDetailDto {
val (auction, product) = getAuctionAndProduct(auctionId)
if (userId != auction.buyerId) throw UnAuthorizationException()

val seller = getUser(product.writerId)

return BuyAuctionDetailDto.of(product, seller, auction)
}

private fun getAuctionAndProduct(auctionId: Long): Pair<AuctionResult, Product> {
val auction = auctionRepository.findById(auctionId)
Copy link
Collaborator

Choose a reason for hiding this comment

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

getSoldAuctionDetail와 중복된 조회 로직은 줄여보시죠

.orElseThrow { NotFoundException("$auctionId 의 경매 내역은 존재하지 않습니다.") }

val product = productRepository.findById(auction.productId)
.orElseThrow { NotFoundException("${auction.productId} 의 상품은 존재하지 않습니다.") }

return Pair(auction, product)
}

private fun getUser(userId: Long): User {
return userRepository.findById(userId)
.orElseThrow { NotFoundException("$userId 의 사용자는 존재하지 않습니다.") }
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/com/get_offer/auction/service/AuctionUserDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.get_offer.auction.service

import com.get_offer.user.domain.User

data class AuctionUserDto(
val id: Long,
val profileImage: String,
val nickname: String,
) {
companion object {
fun of(user: User): AuctionUserDto {
return AuctionUserDto(
id = user.id,
profileImage = user.image,
nickname = user.nickname
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.get_offer.auction.service

import com.get_offer.auction.domain.AuctionResult
import com.get_offer.auction.domain.AuctionStatus
import com.get_offer.product.domain.Product
import com.get_offer.user.domain.User

data class BuyAuctionDetailDto(
val auctionId: Long,
val auctionStatus: AuctionStatus,
val product: AuctionProductUnitDto,
val seller: AuctionUserDto,
) {
companion object {
fun of(product: Product, seller: User, auction: AuctionResult): BuyAuctionDetailDto {
return BuyAuctionDetailDto(
auctionId = auction.id,
auctionStatus = auction.auctionStatus,
product = AuctionProductUnitDto.of(product),
seller = AuctionUserDto.of(seller),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.get_offer.product.domain.Category
import com.get_offer.product.domain.Product
import java.time.LocalDateTime

data class BuyProductListDto(
data class BuyAuctionDto(
val productId: Long?,
val writerId: Long,
val buyerId: Long,
Expand All @@ -20,8 +20,8 @@ data class BuyProductListDto(
val endDate: LocalDateTime,
) {
companion object {
fun of(auctionResult: AuctionResult, product: Product, userId: Long?): BuyProductListDto {
return BuyProductListDto(
fun of(auctionResult: AuctionResult, product: Product): BuyAuctionDto {
return BuyAuctionDto(
productId = product.id,
writerId = product.writerId,
buyerId = auctionResult.buyerId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.get_offer.auction.service

import com.get_offer.auction.domain.AuctionResult
import com.get_offer.auction.domain.AuctionStatus
import com.get_offer.product.domain.Product
import com.get_offer.user.domain.User

data class SellAuctionDetailDto(
val auctionId: Long,
val auctionStatus: AuctionStatus,
val product: AuctionProductUnitDto,
val buyer: AuctionUserDto,
) {
companion object {
fun of(product: Product, buyer: User, auction: AuctionResult): SellAuctionDetailDto {
return SellAuctionDetailDto(
auctionId = auction.id,
auctionStatus = auction.auctionStatus,
product = AuctionProductUnitDto.of(product),
buyer = AuctionUserDto.of(buyer),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.get_offer.product.domain.Product
import com.get_offer.product.domain.ProductStatus
import java.time.LocalDateTime

data class SellProductListDto(
data class SellAuctionDto(
val id: Long?,
val writerId: Long,
val title: String,
Expand All @@ -18,8 +18,8 @@ data class SellProductListDto(
val isMine: Boolean,
) {
companion object {
fun of(product: Product, userId: Long?): SellProductListDto {
return SellProductListDto(
fun of(product: Product, userId: Long?): SellAuctionDto {
return SellAuctionDto(
id = product.id,
writerId = product.writerId,
title = product.title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package com.get_offer.common.exception
/**
* custom exception 처리를 위해 남겨 놓았습니다.
*/
class NotFoundException(override val message: String) : RuntimeException(message)
class NotFoundException(override val message: String) : RuntimeException(message)
class UnAuthorizationException : RuntimeException()
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ class ExceptionControllerAdvice {
fun handleNotFoundException(ex: NotFoundException): ResponseEntity<ApiResponse<Any>> {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.error(ex.message))
}

@ExceptionHandler
fun handleUnAuthorizationException(ex: UnAuthorizationException): ResponseEntity<ApiResponse<Any>> {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.error("인가되지 않은 사용자입니다"))
}
}
9 changes: 9 additions & 0 deletions src/test/kotlin/com/get_offer/TestFixtures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.get_offer.product.domain.Category
import com.get_offer.product.domain.Product
import com.get_offer.product.domain.ProductImagesVo
import com.get_offer.product.domain.ProductStatus
import com.get_offer.user.domain.User
import java.time.LocalDateTime

object TestFixtures {
Expand Down Expand Up @@ -63,5 +64,13 @@ object TestFixtures {
)
}

fun createBuyer(buyerId: Long?): User {
return User(
nickname = "buyer1",
image = "image.png",
id = buyerId ?: 1L,
)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ class AuctionIntegrationTest(
@Test
fun auctionSellIntegrationTest() {
mockMvc.perform(
MockMvcRequestBuilders.get("/auctions/sellHistory")
.param("userId", "1")
MockMvcRequestBuilders.get("/auctions/sellHistory").param("userId", "1")
).andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.data.size()").value(3))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].id").value("1"))
Expand All @@ -40,12 +39,11 @@ class AuctionIntegrationTest(
.andExpect(MockMvcResultMatchers.jsonPath("$.data[2].category").value("FURNITURE"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[2].thumbnail").value("https://picsum.photos/200/300"))
}

@Test
fun auctionBuyIntegrationTest() {
mockMvc.perform(
MockMvcRequestBuilders.get("/auctions/buyHistory")
.param("userId", "2")
MockMvcRequestBuilders.get("/auctions/buyHistory").param("userId", "2")
).andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.data.size()").value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].productId").value("1"))
Expand All @@ -57,4 +55,32 @@ class AuctionIntegrationTest(
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].thumbnail").value("https://picsum.photos/200/300"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].finalPrice").value("10000"))
}

@Test
fun getSoldAuctionDetailIntegrationTest() {
mockMvc.perform(
MockMvcRequestBuilders.get("/auctions/1/sold").param("userId", "1")
).andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.data.auctionId").value("1"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.auctionStatus").value("COMPLETED"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.product.id").value("1"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.product.writerId").value("1"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.product.name").value("nintendo"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.buyer.id").value("2"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.buyer.nickname").value("test2"))
}

@Test
fun getBoughtAuctionDetailIntegrationTest() {
mockMvc.perform(
MockMvcRequestBuilders.get("/auctions/1/bought").param("userId", "2")
).andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.data.auctionId").value("1"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.auctionStatus").value("COMPLETED"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.product.id").value("1"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.product.writerId").value("1"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.product.name").value("nintendo"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.seller.id").value("1"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.seller.nickname").value("test"))
}
}
Loading
Loading