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: product purchasing #9

Merged
merged 5 commits into from
Nov 9, 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
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@
<artifactId>kotlin-test-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito.kotlin</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.meliapp.backend.controller

import org.meliapp.backend.dto.ApiResponse
import org.meliapp.backend.dto.meli.MeliSearchResponse
import org.meliapp.backend.dto.product.ProductResponse
import org.meliapp.backend.dto.product.ProductDetailsResponse
import org.meliapp.backend.service.MeliSearchService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
Expand All @@ -17,7 +17,7 @@ class MeliSearchController(private val meliSearchService: MeliSearchService) {
}

@GetMapping("/{id}")
fun findById(@PathVariable id: String): ResponseEntity<ApiResponse<ProductResponse>> {
fun findById(@PathVariable id: String): ResponseEntity<ApiResponse<ProductDetailsResponse>> {
return ResponseEntity.ok(ApiResponse(meliSearchService.findById(id)))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ApiResponse<PurchaseResponse>> =
ResponseEntity.ok(ApiResponse(purchaseService.buy(purchaseRequest)))


@GetMapping
fun purchases(): ResponseEntity<ApiResponse<List<PurchaseResponse>>> =
ResponseEntity.ok(ApiResponse(purchaseService.purchases()))


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package org.meliapp.backend.dto.meli

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import org.meliapp.backend.dto.product.ProductResponse
import org.meliapp.backend.dto.product.ProductListResponse

data class MeliSearchResponse @JsonCreator constructor(
val results: List<ProductResponse> = emptyList(),
val results: List<ProductListResponse> = emptyList(),
val filters: List<SearchFilter> = emptyList(),
@JsonProperty(value = "available_filters")
val availableFilters: List<SearchFilter> = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.meliapp.backend.dto.product

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import java.math.BigDecimal

data class ProductDetailsResponse(
@JsonProperty("id")
val meliId: String,
val title: String,
val price: BigDecimal,
val thumbnail: String,
val pictures: List<PicturesListResponse>,
var description: String?,

)

data class DescriptionResponse(
@JsonProperty("plain_text")
val description: String
)

data class PicturesListResponse @JsonCreator constructor(
val url: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import java.math.BigDecimal

data class ProductResponse @JsonCreator constructor(
data class ProductListResponse @JsonCreator constructor(
@JsonProperty("id")
val id: String = "",
@JsonProperty("title")
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -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,
)
21 changes: 21 additions & 0 deletions src/main/kotlin/org/meliapp/backend/model/Purchase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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
}
Original file line number Diff line number Diff line change
@@ -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<Purchase, Long> {
@Query("SELECT p FROM Purchase p WHERE p.user.id = :id")
fun findByUserId(@Param("id") id: Long): List<Purchase>
}
19 changes: 3 additions & 16 deletions src/main/kotlin/org/meliapp/backend/service/BookmarkService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import org.meliapp.backend.dto.bookmark.BookmarkRequestBody
import org.meliapp.backend.dto.bookmark.BookmarkSummary
import org.meliapp.backend.exception.apc.BookmarkNotFoundException
import org.meliapp.backend.model.Bookmark
import org.meliapp.backend.model.Product
import org.meliapp.backend.repository.BookmarkRepository
import org.meliapp.backend.repository.ProductRepository
import org.springframework.stereotype.Service

@Service
class BookmarkService(
private val meliSearchService: MeliSearchService,
private val authService: AuthService,
private val bookmarkRepository: BookmarkRepository,
private val productRepository: ProductRepository,
private val productService: ProductService,
) {

fun getUserBookmarks(): List<BookmarkSummary> {
Expand Down Expand Up @@ -47,18 +44,8 @@ class BookmarkService(
@Transactional
fun bookmarkProduct(request: BookmarkRequestBody): BookmarkDetails {
val currentUser = authService.getUserAuthenticated()
val productResponse = meliSearchService.findById(request.meliId)

val savedProduct = productRepository
.findByMeliId(productResponse.id)
.orElseGet {
productRepository.save(Product().apply {
meliId = productResponse.id
title = productResponse.title
thumbnail = productResponse.thumbnail
price = productResponse.price
})
}

val savedProduct = productService.findByMeliId(request.meliId)

val bookmark = Bookmark().apply {
product = savedProduct
Expand Down
16 changes: 13 additions & 3 deletions src/main/kotlin/org/meliapp/backend/service/MeliSearchService.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.meliapp.backend.service

import org.meliapp.backend.dto.meli.MeliSearchResponse
import org.meliapp.backend.dto.product.ProductResponse
import org.meliapp.backend.dto.product.DescriptionResponse
import org.meliapp.backend.dto.product.ProductDetailsResponse
import org.meliapp.backend.exception.apc.ProductNotFoundException
import org.springframework.beans.factory.annotation.Value
import org.springframework.core.ParameterizedTypeReference
Expand Down Expand Up @@ -36,7 +37,7 @@ class MeliSearchService(

}

fun findById(id: String): ProductResponse {
fun findById(id: String): ProductDetailsResponse {
val queryString = "/items/${id}"

return restClient
Expand All @@ -45,7 +46,16 @@ class MeliSearchService(
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus({ it.is4xxClientError }) { _, _ -> throw ProductNotFoundException(id) }
.body(object : ParameterizedTypeReference<ProductResponse>() {})!!
.body(object : ParameterizedTypeReference<ProductDetailsResponse>() {})!!
.also {
it.description = restClient
.get()
.uri("$queryString/description")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(object : ParameterizedTypeReference<DescriptionResponse>() {})
?.description
}

}

Expand Down
32 changes: 32 additions & 0 deletions src/main/kotlin/org/meliapp/backend/service/ProductService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.meliapp.backend.service

import org.meliapp.backend.model.Product
import org.meliapp.backend.repository.ProductRepository
import org.springframework.stereotype.Service
import java.util.*

@Service
class ProductService(
private val productRepository: ProductRepository,
private val meliSearchService: MeliSearchService
) {

fun findByMeliId(id: String): Product {
return productRepository.findByMeliId(id)
.orElseGet {
meliSearchService.findById(id).let {
productRepository.save(Product().apply {
title = it.title
price = it.price
thumbnail = it.thumbnail
meliId = id
})
}
}
}

fun findByMeliIdOld(meliId: String): Optional<Product> {
return productRepository.findByMeliId(meliId)
}

}
54 changes: 54 additions & 0 deletions src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 =
toPurchaseResponse(
purchaseRepository.save(
Purchase().apply {
this.user = authService.getUserAuthenticated()
this.product = productService.findByMeliId(purchaseRequest.meliId)
quantity = purchaseRequest.quantity
totalPrice = product.price * BigDecimal(quantity)
}
)
)

fun purchases(): List<PurchaseResponse> =
purchaseRepository
.findByUserId(authService.getUserAuthenticated().id)
.map { toPurchaseResponse(it) }

private fun toPurchaseResponse(purchase: Purchase): PurchaseResponse =
PurchaseResponse(
purchase.id,
purchase.quantity,
toProductPurchaseResponse(purchase.product),
purchase.purchaseDate,
purchase.totalPrice
)

private fun toProductPurchaseResponse(product: Product): ProductPurchaseResponse =
ProductPurchaseResponse(
product.meliId,
product.title,
product.thumbnail,
product.price
)


}
Loading