diff --git a/src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt b/src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt index 0c6064cf..c9131dbb 100644 --- a/src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt +++ b/src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt @@ -1,22 +1,42 @@ package com.dh.baro.cart.application -import com.dh.baro.cart.domain.CartItem import com.dh.baro.cart.domain.CartService import com.dh.baro.cart.presentation.dto.AddItemRequest +import com.dh.baro.core.ErrorMessage +import com.dh.baro.identity.domain.service.UserService +import com.dh.baro.product.domain.service.ProductQueryService import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional @Service class CartFacade( private val cartService: CartService, + private val userService: UserService, + private val productQueryService: ProductQueryService, ) { - fun getCartItems(userId: Long): List = - cartService.getItems(userId) + @Transactional(readOnly = true) + fun getCartItems(userId: Long): List { + userService.checkUserExists(userId) + val cartItems = cartService.getItems(userId) + val productIds = cartItems.map { it.productId } + val products = productQueryService.getAllByIds(productIds).associateBy { it.id } + return cartItems.map { cartItem -> + val product = products[cartItem.productId] + ?: throw IllegalStateException(ErrorMessage.PRODUCT_NOT_FOUND.format(cartItem.productId)) + + CartItemBundle(cartItem, product) + } + } + + @Transactional fun addItem(userId: Long, request: AddItemRequest) { + userService.checkUserExists(userId) + productQueryService.checkProductsExists(listOf(request.productId)) cartService.addItem( userId = userId, - productId = request.productId.toLong(), + productId = request.productId, quantity = request.quantity, ) } diff --git a/src/main/kotlin/com/dh/baro/cart/application/CartItemBundle.kt b/src/main/kotlin/com/dh/baro/cart/application/CartItemBundle.kt new file mode 100644 index 00000000..3db0e36b --- /dev/null +++ b/src/main/kotlin/com/dh/baro/cart/application/CartItemBundle.kt @@ -0,0 +1,9 @@ +package com.dh.baro.cart.application + +import com.dh.baro.cart.domain.CartItem +import com.dh.baro.product.domain.Product + +data class CartItemBundle( + val cartItem: CartItem, + val product: Product, +) diff --git a/src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt b/src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt index f16ab4ec..bdcc86f5 100644 --- a/src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt +++ b/src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt @@ -2,8 +2,6 @@ package com.dh.baro.cart.domain import com.dh.baro.core.BaseTimeEntity import com.dh.baro.core.IdGenerator -import com.dh.baro.identity.domain.User -import com.dh.baro.product.domain.Product import jakarta.persistence.* @Entity @@ -16,13 +14,11 @@ class CartItem( @Column(name = "id") val id: Long, - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - val user: User, + @Column(name = "user_id", nullable = false) + val userId: Long, - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "product_id", nullable = false) - val product: Product, + @Column(name = "product_id", nullable = false) + val productId: Long, @Column(name = "quantity", nullable = false) var quantity: Int = 1 @@ -39,11 +35,11 @@ class CartItem( } companion object { - fun newCartItem(user: User, product: Product, quantity: Int): CartItem { + fun newCartItem(userId: Long, productId: Long, quantity: Int): CartItem { return CartItem( id = IdGenerator.generate(), - user = user, - product = product, + userId = userId, + productId = productId, quantity = quantity, ) } diff --git a/src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt b/src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt index a0cc03db..70f5820f 100644 --- a/src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt +++ b/src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt @@ -1,11 +1,9 @@ package com.dh.baro.cart.domain -import org.springframework.data.jpa.repository.EntityGraph import org.springframework.data.jpa.repository.JpaRepository interface CartItemRepository : JpaRepository { - @EntityGraph(attributePaths = ["product"]) fun findByUserId(userId: Long): List fun findByUserIdAndProductId(userId: Long, productId: Long): CartItem? diff --git a/src/main/kotlin/com/dh/baro/cart/domain/CartService.kt b/src/main/kotlin/com/dh/baro/cart/domain/CartService.kt index b03bc41c..3d871962 100644 --- a/src/main/kotlin/com/dh/baro/cart/domain/CartService.kt +++ b/src/main/kotlin/com/dh/baro/cart/domain/CartService.kt @@ -1,17 +1,13 @@ package com.dh.baro.cart.domain import com.dh.baro.core.ErrorMessage -import com.dh.baro.identity.domain.repository.UserRepository -import com.dh.baro.product.domain.repository.ProductRepository -import org.springframework.data.repository.findByIdOrNull +import org.springframework.dao.DataIntegrityViolationException import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class CartService( private val cartItemRepository: CartItemRepository, - private val userRepository: UserRepository, - private val productRepository: ProductRepository, private val cartPolicy: CartPolicy, ) { @@ -23,20 +19,20 @@ class CartService( fun addItem(userId: Long, productId: Long, quantity: Int): CartItem { cartItemRepository.findByUserIdAndProductId(userId, productId) ?.also { it.addQuantity(quantity); return it } - validateCartLimit(userId) - val user = userRepository.findByIdOrNull(userId) - ?: throw IllegalArgumentException(ErrorMessage.USER_NOT_FOUND.format(userId)) - val product = productRepository.findByIdOrNull(productId) - ?: throw IllegalArgumentException(ErrorMessage.PRODUCT_NOT_FOUND.format(productId)) - - return cartItemRepository.save(CartItem.newCartItem(user, product, quantity)) + return try { + cartItemRepository.save(CartItem.newCartItem(userId, productId, quantity)) + } catch (e: DataIntegrityViolationException) { + val existingItem = cartItemRepository.findByUserIdAndProductId(userId, productId)?: throw e + existingItem.addQuantity(quantity) + existingItem + } } private fun validateCartLimit(userId: Long) { - val currentCnt = cartItemRepository.countByUserId(userId) - if (!cartPolicy.canAddMoreItems(currentCnt)) { + val currentItemCnt = cartItemRepository.countByUserId(userId) + if (!cartPolicy.canAddMoreItems(currentItemCnt)) { throw IllegalStateException(ErrorMessage.CART_ITEM_LIMIT_EXCEEDED.message) } } diff --git a/src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt b/src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt index 15c3f642..218fdbd0 100644 --- a/src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt +++ b/src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt @@ -21,8 +21,10 @@ class CartController( @GetMapping @ResponseStatus(HttpStatus.OK) - override fun getCart(@CurrentUser userId: Long): CartResponse = - CartResponse.from(cartFacade.getCartItems(userId)) + override fun getCart(@CurrentUser userId: Long): CartResponse { + val cartItemBundles = cartFacade.getCartItems(userId) + return CartResponse.from(cartItemBundles) + } @PostMapping("/items") @ResponseStatus(HttpStatus.CREATED) @@ -35,14 +37,14 @@ class CartController( @ResponseStatus(HttpStatus.NO_CONTENT) override fun updateQuantity( @CurrentUser userId: Long, - @PathVariable itemId: String, + @PathVariable itemId: Long, @Valid @RequestBody request: UpdateQuantityRequest, - ) = cartFacade.updateQuantity(userId, itemId.toLong(), request.quantity) + ) = cartFacade.updateQuantity(userId, itemId, request.quantity) @DeleteMapping("/items/{itemId}") @ResponseStatus(HttpStatus.NO_CONTENT) override fun removeItem( @CurrentUser userId: Long, - @PathVariable itemId: String, - ) = cartFacade.removeItem(userId, itemId.toLong()) + @PathVariable itemId: Long, + ) = cartFacade.removeItem(userId, itemId) } diff --git a/src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt b/src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt index ab5f1fc8..103504c3 100644 --- a/src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt +++ b/src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt @@ -1,11 +1,11 @@ package com.dh.baro.cart.presentation.dto import jakarta.validation.constraints.Min -import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull data class AddItemRequest( - @field:NotBlank(message = "상품 ID를 입력해주세요.") - val productId: String, + @field:NotNull(message = "상품 ID를 입력해주세요.") + val productId: Long, @field:Min(value = 1, message = "수량은 1개 이상이어야 합니다.") val quantity: Int, ) diff --git a/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt b/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt index 47f3fd40..422439d3 100644 --- a/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt +++ b/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt @@ -1,13 +1,41 @@ package com.dh.baro.cart.presentation.dto +import com.dh.baro.cart.application.CartItemBundle +import com.dh.baro.core.LongToStringSerializer +import com.fasterxml.jackson.databind.annotation.JsonSerialize import java.math.BigDecimal +import java.math.RoundingMode data class CartItemResponse( - val itemId: String, - val productId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val itemId: Long, + @JsonSerialize(using = LongToStringSerializer::class) + val productId: Long, val productName: String, val productThumbnailUrl: String?, val price: BigDecimal, val quantity: Int, val subtotal: BigDecimal, -) +) { + + companion object { + private const val SCALE_NONE = 0 + + fun from(bundle: CartItemBundle): CartItemResponse { + val cartItem = bundle.cartItem + val product = bundle.product + + return CartItemResponse( + itemId = cartItem.id, + productId = product.id, + productName = product.getName(), + productThumbnailUrl = product.getThumbnailUrl(), + price = product.getPrice(), + quantity = cartItem.quantity, + subtotal = product.getPrice() + .multiply(BigDecimal(cartItem.quantity)) + .setScale(SCALE_NONE, RoundingMode.HALF_UP) + ) + } + } +} diff --git a/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartResponse.kt b/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartResponse.kt index 12fe0b90..f666eeef 100644 --- a/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartResponse.kt +++ b/src/main/kotlin/com/dh/baro/cart/presentation/dto/CartResponse.kt @@ -1,6 +1,6 @@ package com.dh.baro.cart.presentation.dto -import com.dh.baro.cart.domain.CartItem +import com.dh.baro.cart.application.CartItemBundle import java.math.BigDecimal import java.math.RoundingMode @@ -11,23 +11,16 @@ data class CartResponse( companion object { private const val SCALE_NONE = 0 - fun from(cartItems: List): CartResponse { - val responses = cartItems.map { it.toResponse() } - val total = responses.fold(BigDecimal.ZERO) { sum, item -> sum + item.subtotal } - .setScale(SCALE_NONE, RoundingMode.HALF_UP) - return CartResponse(responses, total) - } + fun from(bundles: List): CartResponse { + val items = bundles.map { bundle -> + CartItemResponse.from(bundle) + } + + val totalPrice = items.fold(BigDecimal.ZERO) { sum, item -> + sum + item.subtotal + }.setScale(SCALE_NONE, RoundingMode.HALF_UP) - private fun CartItem.toResponse() = CartItemResponse( - itemId = id.toString(), - productId = product.id.toString(), - productName = product.getName(), - productThumbnailUrl = product.getThumbnailUrl(), - price = product.getPrice(), - quantity = quantity, - subtotal = product.getPrice() - .multiply(BigDecimal(quantity)) - .setScale(SCALE_NONE, RoundingMode.HALF_UP) - ) + return CartResponse(items, totalPrice) + } } } diff --git a/src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt b/src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt index fa2a25fc..babface5 100644 --- a/src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt +++ b/src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt @@ -121,7 +121,7 @@ interface CartSwagger { @PatchMapping("/items/{itemId}") fun updateQuantity( @Parameter(hidden = true) userId: Long, - @PathVariable itemId: String, + @PathVariable itemId: Long, @RequestBody request: UpdateQuantityRequest ) @@ -145,6 +145,6 @@ interface CartSwagger { @DeleteMapping("/items/{itemId}") fun removeItem( @Parameter(hidden = true) userId: Long, - @PathVariable itemId: String + @PathVariable itemId: Long ) } diff --git a/src/main/kotlin/com/dh/baro/core/Cursor.kt b/src/main/kotlin/com/dh/baro/core/Cursor.kt index b360cf93..b4327a20 100644 --- a/src/main/kotlin/com/dh/baro/core/Cursor.kt +++ b/src/main/kotlin/com/dh/baro/core/Cursor.kt @@ -1,5 +1,8 @@ package com.dh.baro.core +import com.fasterxml.jackson.databind.annotation.JsonSerialize + class Cursor ( - val id: String, + @JsonSerialize(using = LongToStringSerializer::class) + val id: Long, ) diff --git a/src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt b/src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt new file mode 100644 index 00000000..1a75ce71 --- /dev/null +++ b/src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt @@ -0,0 +1,15 @@ +package com.dh.baro.core + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider + +class LongToStringSerializer : JsonSerializer() { + override fun serialize(value: Long?, gen: JsonGenerator, serializers: SerializerProvider) { + if (value != null) { + gen.writeString(value.toString()) + } else { + gen.writeNull() + } + } +} diff --git a/src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt b/src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt index 52252e14..95bce318 100644 --- a/src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt +++ b/src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt @@ -1,10 +1,13 @@ package com.dh.baro.identity.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.identity.domain.User import com.dh.baro.identity.domain.UserRole +import com.fasterxml.jackson.databind.annotation.JsonSerialize data class UserProfileResponse( - val id: String, + @JsonSerialize(using = LongToStringSerializer::class) + val id: Long, val name: String, val email: String, val phoneNumber: String?, @@ -14,7 +17,7 @@ data class UserProfileResponse( companion object { fun from(user: User) = UserProfileResponse( - id = user.id.toString(), + id = user.id, name = user.getName(), email = user.getEmail(), phoneNumber = user.getPhoneNumber(), diff --git a/src/main/kotlin/com/dh/baro/look/presentation/LookController.kt b/src/main/kotlin/com/dh/baro/look/presentation/LookController.kt index 0ddcd8d9..3d7db973 100644 --- a/src/main/kotlin/com/dh/baro/look/presentation/LookController.kt +++ b/src/main/kotlin/com/dh/baro/look/presentation/LookController.kt @@ -30,11 +30,11 @@ class LookController( @ResponseStatus(HttpStatus.NO_CONTENT) override fun recordReaction( @CurrentUser userId: Long, - @PathVariable lookId: String, + @PathVariable lookId: Long, @Valid @RequestBody request: ReactionRequest, ) = lookReactionFacade.recordReaction( userId = userId, - lookId = lookId.toLong(), + lookId = lookId, reactionType = request.reactionType, ) @@ -42,28 +42,28 @@ class LookController( @ResponseStatus(HttpStatus.NO_CONTENT) override fun cancelReaction( @CurrentUser userId: Long, - @PathVariable lookId: String, - ) = lookReactionFacade.cancelReaction(userId, lookId.toLong()) + @PathVariable lookId: Long, + ) = lookReactionFacade.cancelReaction(userId, lookId) @GetMapping("/swipe") @ResponseStatus(HttpStatus.OK) override fun getSwipeLooks( @CurrentUser userId: Long, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(defaultValue = "10") size: Int, ): SliceResponse { - val slice = lookFacade.getSwipeLooks(userId, cursorId?.toLong(), size) + val slice = lookFacade.getSwipeLooks(userId, cursorId, size) return SliceResponse.from( slice, mapper = LookDto::from, - cursorExtractor = { Cursor(it.id.toString()) } + cursorExtractor = { Cursor(it.id) } ) } @GetMapping("/{lookId}") @ResponseStatus(HttpStatus.OK) - override fun getLookDetail(@PathVariable lookId: String): LookDetailResponse { - val lookDetailBundle = lookFacade.getLookDetail(lookId.toLong()) + override fun getLookDetail(@PathVariable lookId: Long): LookDetailResponse { + val lookDetailBundle = lookFacade.getLookDetail(lookId) return LookDetailResponse.from(lookDetailBundle) } } diff --git a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt index e3e8b045..40fbfd6b 100644 --- a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt +++ b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt @@ -1,6 +1,7 @@ package com.dh.baro.look.presentation.dto import com.dh.baro.look.application.LookCreateCommand +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size @@ -18,7 +19,7 @@ data class LookCreateRequest( val imageUrls: List<@NotBlank String>, @field:Size(min = 1) - val productIds: List, + val productIds: List, ) { fun toCommand(creatorId: Long) = LookCreateCommand( creatorId = creatorId, @@ -26,6 +27,6 @@ data class LookCreateRequest( description = description, thumbnailUrl = thumbnailUrl, imageUrls = imageUrls, - productIds = productIds.map { it.toLong() }, + productIds = productIds, ) } diff --git a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt index eed91431..096fc989 100644 --- a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt +++ b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt @@ -1,15 +1,18 @@ package com.dh.baro.look.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.look.domain.Look +import com.fasterxml.jackson.databind.annotation.JsonSerialize data class LookCreateResponse( - val lookId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val lookId: Long, ) { companion object { fun from(look: Look): LookCreateResponse { return LookCreateResponse( - lookId = look.id.toString(), + lookId = look.id, ) } } diff --git a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt index 8aeccc25..b1f45b79 100644 --- a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt +++ b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt @@ -1,10 +1,13 @@ package com.dh.baro.look.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.look.application.dto.LookDetailBundle +import com.fasterxml.jackson.databind.annotation.JsonSerialize import java.math.BigDecimal data class LookDetailResponse( - val lookId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val lookId: Long, val title: String, val description: String?, val thumbnailUrl: String, @@ -14,7 +17,8 @@ data class LookDetailResponse( ) { data class ProductItemDto( - val productId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val productId: Long, val storeName: String, val productName: String, val price: BigDecimal, @@ -34,7 +38,7 @@ data class LookDetailResponse( val store = storeMap[product.storeId]?: return@mapNotNull null ProductItemDto( - productId = product.id.toString(), + productId = product.id, storeName = store.getName(), productName = product.getName(), price = product.getPrice(), @@ -43,7 +47,7 @@ data class LookDetailResponse( } return LookDetailResponse( - lookId = look.id.toString(), + lookId = look.id, title = look.getTitle(), description = look.getDescription(), thumbnailUrl = look.getThumbnailUrl(), diff --git a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt index e7ad196e..88c677ea 100644 --- a/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt +++ b/src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt @@ -1,15 +1,18 @@ package com.dh.baro.look.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.look.domain.Look +import com.fasterxml.jackson.databind.annotation.JsonSerialize data class LookDto( - val lookId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val lookId: Long, val title: String, val thumbnailUrl: String, ) { companion object { fun from(look: Look) = LookDto( - lookId = look.id.toString(), + lookId = look.id, title = look.getTitle(), thumbnailUrl = look.getThumbnailUrl(), ) diff --git a/src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt b/src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt index f6dc654e..8b7bcb22 100644 --- a/src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt +++ b/src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt @@ -91,7 +91,7 @@ interface LookSwagger { @PutMapping("/{lookId}/reaction") fun recordReaction( @Parameter(hidden = true) userId: Long, - @PathVariable lookId: String, + @PathVariable lookId: Long, @RequestBody request: ReactionRequest, ) @@ -113,7 +113,7 @@ interface LookSwagger { @DeleteMapping("/{lookId}/reaction") fun cancelReaction( @Parameter(hidden = true) userId: Long, - @PathVariable lookId: String, + @PathVariable lookId: Long, ) /* ───────────────────────── 스와이프 피드(무한 스크롤) ───────────────────────── */ @@ -160,7 +160,7 @@ interface LookSwagger { @GetMapping("/swipe") fun getSwipeLooks( @Parameter(hidden = true) userId: Long, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(defaultValue = "10") size: Int, ): SliceResponse @@ -203,7 +203,7 @@ interface LookSwagger { ] ) @GetMapping("/{lookId}") - fun getLookDetail(@PathVariable lookId: String): LookDetailResponse + fun getLookDetail(@PathVariable lookId: Long): LookDetailResponse /* ──────────────── 문서 전용 예시 DTO ─────────────── */ @Schema(hidden = true) diff --git a/src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt b/src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt index 529d6e37..9a4541de 100644 --- a/src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt +++ b/src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt @@ -19,7 +19,7 @@ data class OrderCreateCommand( val productMap = productList.associateBy { it.id } val orderItems = request.orderItems.map { requestItem -> - val product = productMap[requestItem.productId.toLong()] + val product = productMap[requestItem.productId] ?: throw IllegalArgumentException("상품을 찾을 수 없습니다: ${requestItem.productId}") OrderItem( diff --git a/src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt b/src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt index c6a8d18b..ce4898ac 100644 --- a/src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt +++ b/src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt @@ -28,7 +28,7 @@ class OrderFacade( fun placeOrder(userId: Long, request: OrderCreateRequest): Order { userService.checkUserExists(userId) - val productList = productQueryService.getProductsExists(request.orderItems.map { orderItem -> orderItem.productId.toLong() }) + val productList = productQueryService.getProductsExists(request.orderItems.map { orderItem -> orderItem.productId }) val cmd = OrderCreateCommand.toCommand(userId, productList, request) val order = orderService.createOrder(cmd) return order @@ -39,7 +39,7 @@ class OrderFacade( userService.checkUserExists(userId) val productList = productQueryService.getProductsExists( - request.orderItems.map { orderItem -> orderItem.productId.toLong() }, + request.orderItems.map { orderItem -> orderItem.productId }, ) val cmd = OrderCreateCommand.toCommand(userId, productList, request) diff --git a/src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt b/src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt index c8c46f87..aebdfeea 100644 --- a/src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt +++ b/src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt @@ -42,9 +42,9 @@ class OrderController( @ResponseStatus(HttpStatus.OK) override fun getOrderDetail( @CurrentUser userId: Long, - @PathVariable orderId: String, + @PathVariable orderId: Long, ): OrderDetailResponse { - val order = orderFacade.getOrderDetail(userId, orderId.toLong()) + val order = orderFacade.getOrderDetail(userId, orderId) return OrderDetailResponse.from(order) } @@ -52,14 +52,14 @@ class OrderController( @ResponseStatus(HttpStatus.OK) override fun getOrdersByCursor( @CurrentUser userId: Long, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(defaultValue = "10") size: Int, ): SliceResponse { - val slice = orderFacade.getOrdersByCursor(userId, cursorId?.toLong(), size) + val slice = orderFacade.getOrdersByCursor(userId, cursorId, size) return SliceResponse.from( slice = slice, mapper = OrderSummary::from, - cursorExtractor = { Cursor(it.id.toString()) } + cursorExtractor = { Cursor(it.id) } ) } } diff --git a/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt b/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt index aaba2c09..4e112085 100644 --- a/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt +++ b/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt @@ -1,7 +1,9 @@ package com.dh.baro.order.presentation.dto +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import jakarta.validation.Valid import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Positive import jakarta.validation.constraints.Size @@ -16,17 +18,11 @@ data class OrderCreateRequest( ) { data class OrderItem( - @field:NotBlank(message = "상품 ID를 입력해주세요.") - val productId: String, + @field:NotNull(message = "상품 ID를 입력해주세요.") + val productId: Long, @field:Positive(message = "수량은 1개 이상이어야 합니다.") val quantity: Int, - ) { - fun toLongProductId() = productId.toLong() - } - - fun convertToLongIds() = OrderCreateRequest( - shippingAddress = shippingAddress, - orderItems = orderItems.map { OrderItem(it.productId.toLong().toString(), it.quantity) } ) + } diff --git a/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt b/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt index 844cd515..ccf1b25f 100644 --- a/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt +++ b/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt @@ -1,12 +1,15 @@ package com.dh.baro.order.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.order.domain.Order import com.dh.baro.order.domain.OrderStatus +import com.fasterxml.jackson.databind.annotation.JsonSerialize import java.math.BigDecimal import java.time.Instant data class OrderDetailResponse( - val orderId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val orderId: Long, val orderStatus: OrderStatus, val shippingAddress: String, val totalPrice: BigDecimal, @@ -15,7 +18,8 @@ data class OrderDetailResponse( ) { data class Item( - val productId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val productId: Long, val productName: String, val thumbnailUrl: String, val quantity: Int, @@ -25,7 +29,7 @@ data class OrderDetailResponse( companion object { fun from(order: Order): OrderDetailResponse { return OrderDetailResponse( - orderId = order.id.toString(), + orderId = order.id, orderStatus = order.status, shippingAddress = order.shippingAddress, totalPrice = order.totalPrice, @@ -33,7 +37,7 @@ data class OrderDetailResponse( items = order.items .map { item -> Item( - productId = item.productId.toString(), + productId = item.productId, productName = item.name, thumbnailUrl = item.thumbnailUrl, quantity = item.quantity, diff --git a/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt b/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt index 4abdcc41..3cf79ab9 100644 --- a/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt +++ b/src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt @@ -1,12 +1,15 @@ package com.dh.baro.order.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.order.domain.Order import com.dh.baro.order.domain.OrderStatus +import com.fasterxml.jackson.databind.annotation.JsonSerialize import java.math.BigDecimal import java.time.Instant data class OrderSummary( - val orderId: String, + @JsonSerialize(using = LongToStringSerializer::class) + val orderId: Long, val totalPrice: BigDecimal, val orderStatus: OrderStatus, val orderedAt: Instant?, @@ -14,7 +17,7 @@ data class OrderSummary( companion object { fun from(order: Order) = OrderSummary( - orderId = order.id.toString(), + orderId = order.id, totalPrice = order.totalPrice, orderStatus = order.status, orderedAt = order.createdAt, diff --git a/src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt b/src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt index 80b6a864..75d8f8f3 100644 --- a/src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt +++ b/src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt @@ -149,7 +149,7 @@ interface OrderSwagger { @GetMapping("/{orderId}") fun getOrderDetail( @Parameter(hidden = true) userId: Long, - @PathVariable orderId: String + @PathVariable orderId: Long ): OrderDetailResponse /* ────────────────────────────────────────── 주문 목록(무한 스크롤) ───────────────────────────── */ @@ -200,7 +200,7 @@ interface OrderSwagger { @GetMapping fun getOrdersByCursor( @Parameter(hidden = true) userId: Long, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(defaultValue = "10") size: Int ): SliceResponse } diff --git a/src/main/kotlin/com/dh/baro/product/application/CategoryFacade.kt b/src/main/kotlin/com/dh/baro/product/application/CategoryFacade.kt index 97c2bdc9..442a9431 100644 --- a/src/main/kotlin/com/dh/baro/product/application/CategoryFacade.kt +++ b/src/main/kotlin/com/dh/baro/product/application/CategoryFacade.kt @@ -11,5 +11,5 @@ class CategoryFacade( ) { fun createCategory(request: CategoryCreateRequest): Category = - categoryService.createCategory(request.id.toLong(), request.name) + categoryService.createCategory(request.id, request.name) } diff --git a/src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt b/src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt index 5c191a6f..b5452fc1 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt @@ -35,38 +35,38 @@ class ProductController( @GetMapping("/popular") @ResponseStatus(HttpStatus.OK) override fun getPopularProducts( - @RequestParam(required = false) categoryId: String?, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) categoryId: Long?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(required = false) cursorLikes: Int?, @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) size: Int, ): SliceResponse { if ((cursorId == null) xor (cursorLikes == null)) throw IllegalArgumentException(ErrorMessage.INVALID_POPULAR_PRODUCT_CURSOR.message) - val productSliceBundle = productFacade.getPopularProducts(categoryId?.toLong(), cursorLikes, cursorId?.toLong(), size) + val productSliceBundle = productFacade.getPopularProducts(categoryId, cursorLikes, cursorId, size) val storeMap = productSliceBundle.storeList.associateBy { it.id } return SliceResponse.fromNullable( slice = productSliceBundle.productSlice, mapper = { p -> ProductListItem.ofOrNull(p, storeMap) }, - cursorExtractor = { PopularCursor(it.id.toString(), it.getLikesCount()) }, + cursorExtractor = { PopularCursor(it.id, it.getLikesCount()) }, ) } @GetMapping("/newest") @ResponseStatus(HttpStatus.OK) override fun getNewestProducts( - @RequestParam(required = false) categoryId: String?, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) categoryId: Long?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) size: Int, ): SliceResponse { - val productSliceBundle = productFacade.getNewestProducts(categoryId?.toLong(), cursorId?.toLong(), size) + val productSliceBundle = productFacade.getNewestProducts(categoryId, cursorId, size) val storeMap = productSliceBundle.storeList.associateBy { it.id } return SliceResponse.fromNullable( slice = productSliceBundle.productSlice, mapper = { p -> ProductListItem.ofOrNull(p, storeMap) }, - cursorExtractor = { Cursor(it.id.toString()) }, + cursorExtractor = { Cursor(it.id) }, ) } diff --git a/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt b/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt index 451f892e..ef8c0583 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt @@ -1,11 +1,11 @@ package com.dh.baro.product.presentation.dto -import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Size data class CategoryCreateRequest( - @field:NotBlank - val id: String, + @field:NotNull + val id: Long, @field:Size(min = 1, max = 50) val name: String, diff --git a/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt b/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt index b9ac81c3..66d7d5a8 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt @@ -1,15 +1,18 @@ package com.dh.baro.product.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.product.domain.Category +import com.fasterxml.jackson.databind.annotation.JsonSerialize data class CategoryResponse( - val id: String, + @JsonSerialize(using = LongToStringSerializer::class) + val id: Long, val name: String, ) { companion object { fun from(entity: Category) = CategoryResponse( - id = entity.id.toString(), + id = entity.id, name = entity.name, ) } diff --git a/src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt b/src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt index e581e410..4c71527c 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt @@ -1,6 +1,10 @@ package com.dh.baro.product.presentation.dto +import com.dh.baro.core.LongToStringSerializer +import com.fasterxml.jackson.databind.annotation.JsonSerialize + data class PopularCursor( - val id: String, + @JsonSerialize(using = LongToStringSerializer::class) + val id: Long, val likes: Int, ) diff --git a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt index 4dc1c483..925a72fc 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt @@ -8,8 +8,8 @@ data class ProductCreateRequest( @field:Size(min = 1, max = 100) val name: String, - @field:NotBlank - val storeId: String, + @field:NotNull + val storeId: Long, @field:Positive val price: BigDecimal, @@ -26,7 +26,7 @@ data class ProductCreateRequest( val thumbnailUrl: String, @field:NotEmpty - val categoryIds: List, + val categoryIds: List, @field:Size(min = 1, message = "최소 1개 이상의 이미지가 필요합니다.") val imageUrls: List<@NotBlank String>, @@ -34,13 +34,13 @@ data class ProductCreateRequest( fun toCommand(): ProductCreateCommand = ProductCreateCommand( name = name, - storeId = storeId.toLong(), + storeId = storeId, price = price, quantity = quantity, description = description, likesCount = likesCount, thumbnailUrl = thumbnailUrl, - categoryIds = categoryIds.map { it.toLong() }, + categoryIds = categoryIds, imageUrls = imageUrls, ) } diff --git a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt index fb564d27..ad7a680f 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt @@ -1,15 +1,18 @@ package com.dh.baro.product.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.product.domain.Product +import com.fasterxml.jackson.databind.annotation.JsonSerialize data class ProductCreateResponse( - val id: String, + @JsonSerialize(using = LongToStringSerializer::class) + val id: Long, val name: String, ) { companion object { fun from(product: Product) = ProductCreateResponse( - id = product.id.toString(), + id = product.id, name = product.getName(), ) } diff --git a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt index 75513936..c11a2820 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt @@ -1,11 +1,14 @@ package com.dh.baro.product.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.identity.domain.Store import com.dh.baro.product.domain.Product +import com.fasterxml.jackson.databind.annotation.JsonSerialize import java.math.BigDecimal data class ProductDetail( - val id: String, + @JsonSerialize(using = LongToStringSerializer::class) + val id: Long, val storeName: String, val productName: String, val price: BigDecimal, @@ -16,7 +19,7 @@ data class ProductDetail( companion object { fun from(product: Product, store: Store) = ProductDetail( - id = product.id.toString(), + id = product.id, storeName = store.getName(), productName = product.getName(), price = product.getPrice(), diff --git a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt index 9580dcfe..dfe66488 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt @@ -1,11 +1,14 @@ package com.dh.baro.product.presentation.dto +import com.dh.baro.core.LongToStringSerializer import com.dh.baro.identity.domain.Store import com.dh.baro.product.domain.Product +import com.fasterxml.jackson.databind.annotation.JsonSerialize import java.math.BigDecimal data class ProductListItem( - val id: String, + @JsonSerialize(using = LongToStringSerializer::class) + val id: Long, val storeName: String, val productName: String, val price: BigDecimal, @@ -15,7 +18,7 @@ data class ProductListItem( fun ofOrNull(product: Product, storeMap: Map): ProductListItem? { val store = storeMap[product.storeId]?: return null return ProductListItem( - id = product.id.toString(), + id = product.id, storeName = store.getName(), productName = product.getName(), price = product.getPrice(), diff --git a/src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt b/src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt index 5fed3bc6..59ebe8eb 100644 --- a/src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt +++ b/src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt @@ -111,8 +111,8 @@ interface ProductSwagger { ) @GetMapping("/popular") fun getPopularProducts( - @RequestParam(required = false) categoryId: String?, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) categoryId: Long?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(required = false) cursorLikes: Int?, @RequestParam(defaultValue = "21") size: Int ): SliceResponse @@ -154,8 +154,8 @@ interface ProductSwagger { ) @GetMapping("/newest") fun getNewestProducts( - @RequestParam(required = false) categoryId: String?, - @RequestParam(required = false) cursorId: String?, + @RequestParam(required = false) categoryId: Long?, + @RequestParam(required = false) cursorId: Long?, @RequestParam(defaultValue = "21") size: Int ): SliceResponse