From 12d22c3d72ff3cb9a1009207d0fdf1ce4844fb06 Mon Sep 17 00:00:00 2001 From: Angelo Padron Date: Wed, 6 Nov 2024 19:10:02 -0300 Subject: [PATCH] add purchase feature and completed purchases listing --- .../backend/controller/PurchaseController.kt | 26 +++++++ .../dto/product/ProductPurchaseResponse.kt | 12 +++ .../backend/dto/purchase/PurchaseRequest.kt | 9 +++ .../backend/dto/purchase/PurchaseResponse.kt | 15 ++++ .../org/meliapp/backend/model/Purchase.kt | 22 ++++++ .../backend/repository/PurchaseRepository.kt | 11 +++ .../backend/service/PurchaseService.kt | 59 ++++++++++++++ .../service/PurchaseServiceUnitTest.kt | 78 +++++++++++++++++++ 8 files changed, 232 insertions(+) create mode 100644 src/main/kotlin/org/meliapp/backend/controller/PurchaseController.kt create mode 100644 src/main/kotlin/org/meliapp/backend/dto/product/ProductPurchaseResponse.kt create mode 100644 src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseRequest.kt create mode 100644 src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseResponse.kt create mode 100644 src/main/kotlin/org/meliapp/backend/model/Purchase.kt create mode 100644 src/main/kotlin/org/meliapp/backend/repository/PurchaseRepository.kt create mode 100644 src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt create mode 100644 src/test/kotlin/org/meliapp/backend/service/PurchaseServiceUnitTest.kt diff --git a/src/main/kotlin/org/meliapp/backend/controller/PurchaseController.kt b/src/main/kotlin/org/meliapp/backend/controller/PurchaseController.kt new file mode 100644 index 0000000..807dbce --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/controller/PurchaseController.kt @@ -0,0 +1,26 @@ +package org.meliapp.backend.controller + +import org.meliapp.backend.dto.ApiResponse +import org.meliapp.backend.dto.purchase.PurchaseRequest +import org.meliapp.backend.dto.purchase.PurchaseResponse +import org.meliapp.backend.service.PurchaseService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api/purchases") +class PurchaseController( + private val purchaseService: PurchaseService +) { + + @PostMapping + fun buy(@RequestBody purchaseRequest: PurchaseRequest): ResponseEntity> { + return ResponseEntity.ok(ApiResponse(purchaseService.buy(purchaseRequest))) + } + + @GetMapping + fun purchases(): ResponseEntity>> { + return ResponseEntity.ok(ApiResponse(purchaseService.purchases())) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/dto/product/ProductPurchaseResponse.kt b/src/main/kotlin/org/meliapp/backend/dto/product/ProductPurchaseResponse.kt new file mode 100644 index 0000000..cad96e4 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/dto/product/ProductPurchaseResponse.kt @@ -0,0 +1,12 @@ +package org.meliapp.backend.dto.product + +import com.fasterxml.jackson.annotation.JsonProperty +import java.math.BigDecimal + +data class ProductPurchaseResponse( + @JsonProperty("meli_id") + val meliId: String, + val title: String, + val thumbnail: String, + val price: BigDecimal, +) diff --git a/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseRequest.kt b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseRequest.kt new file mode 100644 index 0000000..bb55ea1 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseRequest.kt @@ -0,0 +1,9 @@ +package org.meliapp.backend.dto.purchase + +import com.fasterxml.jackson.annotation.JsonProperty + +data class PurchaseRequest( + @JsonProperty(value = "meli_id") + val meliId: String, + val quantity: Int +) diff --git a/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseResponse.kt b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseResponse.kt new file mode 100644 index 0000000..5875764 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseResponse.kt @@ -0,0 +1,15 @@ +package org.meliapp.backend.dto.purchase + +import com.fasterxml.jackson.annotation.JsonProperty +import org.meliapp.backend.dto.product.ProductPurchaseResponse +import java.math.BigDecimal +import java.time.LocalDateTime + +data class PurchaseResponse( + val id: Long, + val quantity: Int, + val product: ProductPurchaseResponse, + @JsonProperty("created_at") + val createdAt: LocalDateTime, + val total: BigDecimal, +) diff --git a/src/main/kotlin/org/meliapp/backend/model/Purchase.kt b/src/main/kotlin/org/meliapp/backend/model/Purchase.kt new file mode 100644 index 0000000..8b99b87 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/model/Purchase.kt @@ -0,0 +1,22 @@ +package org.meliapp.backend.model + +import jakarta.persistence.* +import org.hibernate.annotations.CreationTimestamp +import java.math.BigDecimal +import java.time.LocalDateTime + +@Entity +class Purchase { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long = 0 + @ManyToOne + lateinit var user: User + @ManyToOne + lateinit var product: Product + var quantity: Int = 0 + @CreationTimestamp + var purchaseDate: LocalDateTime = LocalDateTime.now() + var totalPrice: BigDecimal = BigDecimal.ZERO +} \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/repository/PurchaseRepository.kt b/src/main/kotlin/org/meliapp/backend/repository/PurchaseRepository.kt new file mode 100644 index 0000000..1fe2399 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/repository/PurchaseRepository.kt @@ -0,0 +1,11 @@ +package org.meliapp.backend.repository + +import org.meliapp.backend.model.Purchase +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param + +interface PurchaseRepository : JpaRepository { + @Query("SELECT p FROM Purchase p WHERE p.user.id = :id") + fun findByUserId(@Param("id") id: Long): List +} \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt b/src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt new file mode 100644 index 0000000..4957015 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt @@ -0,0 +1,59 @@ +package org.meliapp.backend.service + +import org.meliapp.backend.dto.product.ProductPurchaseResponse +import org.meliapp.backend.dto.purchase.PurchaseRequest +import org.meliapp.backend.dto.purchase.PurchaseResponse +import org.meliapp.backend.model.Product +import org.meliapp.backend.model.Purchase +import org.meliapp.backend.repository.PurchaseRepository +import org.springframework.stereotype.Service +import java.math.BigDecimal + +@Service +class PurchaseService( + private val purchaseRepository: PurchaseRepository, + private val authService: AuthService, + private val productService: ProductService +) { + + fun buy(purchaseRequest: PurchaseRequest): PurchaseResponse { + val user = authService.getUserAuthenticated() + val product = productService.findByMeliId(purchaseRequest.meliId) + + val purchase = Purchase() + purchase.user = user + purchase.quantity = purchaseRequest.quantity + purchase.product = product + purchase.totalPrice = product.price * BigDecimal(purchaseRequest.quantity) + + purchaseRepository.save(purchase) + + return toPurchaseResponse(purchase) + + } + + fun purchases(): List { + val user = authService.getUserAuthenticated() + return purchaseRepository.findByUserId(user.id).map { toPurchaseResponse(it) } + } + + private fun toPurchaseResponse(purchase: Purchase): PurchaseResponse { + return PurchaseResponse( + purchase.id, + purchase.quantity, + toProductPurchaseResponse(purchase.product), + purchase.purchaseDate, + purchase.totalPrice + ) + } + + private fun toProductPurchaseResponse(product: Product): ProductPurchaseResponse { + return ProductPurchaseResponse( + product.meliId, + product.title, + product.thumbnail, + product.price) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/org/meliapp/backend/service/PurchaseServiceUnitTest.kt b/src/test/kotlin/org/meliapp/backend/service/PurchaseServiceUnitTest.kt new file mode 100644 index 0000000..3a1864d --- /dev/null +++ b/src/test/kotlin/org/meliapp/backend/service/PurchaseServiceUnitTest.kt @@ -0,0 +1,78 @@ +package org.meliapp.backend.service + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.meliapp.backend.dto.purchase.PurchaseRequest +import org.meliapp.backend.model.Product +import org.meliapp.backend.model.Purchase +import org.meliapp.backend.model.User +import org.meliapp.backend.repository.PurchaseRepository +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.kotlin.* +import org.springframework.test.context.junit.jupiter.SpringExtension +import java.math.BigDecimal +import kotlin.test.assertEquals + +@ExtendWith(SpringExtension::class) +class PurchaseServiceUnitTest { + + @Mock + lateinit var purchaseRepository: PurchaseRepository + + @Mock + lateinit var productService: ProductService + + @Mock + lateinit var authService: AuthService + + @InjectMocks + lateinit var purchaseService: PurchaseService + + @BeforeEach + fun setup() { + val user: User = mock() + whenever(user.id).thenReturn(1) + whenever(authService.getUserAuthenticated()).thenReturn(user) + } + + @AfterEach + fun cleanup() { + reset(purchaseRepository, productService, authService) + } + + @Test + fun `can buy an existing product`() { + val meliId = "MELI_ID" + val productTitle = "TITLE" + val product: Product = mock() + + whenever(product.meliId).thenReturn(meliId) + whenever(product.price).thenReturn(BigDecimal.ZERO) + whenever(product.title).thenReturn(productTitle) + whenever(productService.findByMeliId(meliId)).thenReturn(product) + + val result = purchaseService.buy(PurchaseRequest(meliId, 1)) + + assertEquals(meliId, result.product.meliId) + assertEquals(productTitle, result.product.title) + assertEquals(BigDecimal.ZERO, result.product.price) + assertEquals(1, result.quantity) + assertEquals(BigDecimal.ZERO, result.total) + + verify(purchaseRepository, times(1)).save(any()) + + } + + @Test + fun `can get all the purchases from current user`() { + whenever(purchaseRepository.findByUserId(any())).thenReturn(listOf()) + + val result = purchaseService.purchases() + + assertEquals(0, result.size) + } + +} \ No newline at end of file