Skip to content

Commit

Permalink
Merge pull request #4 from angelodpadron/feature/save-product
Browse files Browse the repository at this point in the history
Feature: save products as bookmarks
  • Loading branch information
angelodpadron authored Oct 15, 2024
2 parents e693492 + fa52e57 commit b900112
Show file tree
Hide file tree
Showing 24 changed files with 542 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class SecurityConfiguration(
"/v3/api-docs"
).permitAll()
.requestMatchers(HttpMethod.POST, "api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/products/search").permitAll()
.requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
.anyRequest().authenticated()

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

import io.swagger.v3.oas.annotations.Operation
import org.meliapp.backend.dto.ApiResponse
import org.meliapp.backend.dto.apc.auth.AuthRequestBody
import org.meliapp.backend.dto.auth.AuthRequestBody
import org.meliapp.backend.service.AuthService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.CrossOrigin
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.RestController
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/auth")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.meliapp.backend.controller

import org.meliapp.backend.dto.ApiResponse
import org.meliapp.backend.dto.bookmark.BookmarkDetails
import org.meliapp.backend.dto.bookmark.BookmarkRequestBody
import org.meliapp.backend.dto.bookmark.BookmarkSummary
import org.meliapp.backend.service.BookmarkService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/bookmarks")
class BookmarkController(
private val bookmarkService: BookmarkService
) {

@GetMapping
fun getBookmarks(): ResponseEntity<ApiResponse<List<BookmarkSummary>>> {
return ResponseEntity.ok(ApiResponse(bookmarkService.getUserBookmarks()))
}

@GetMapping("/{bookmarkId}")
fun getBookmarkDetails(@PathVariable bookmarkId: Long): ResponseEntity<ApiResponse<BookmarkDetails>> {
return ResponseEntity.ok(ApiResponse(bookmarkService.getBookmarkDetails(bookmarkId)))
}

@PostMapping
fun bookmarkProduct(@RequestBody request: BookmarkRequestBody): ResponseEntity<ApiResponse<BookmarkDetails>> {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse(bookmarkService.bookmarkProduct(request)))
}

@PutMapping("/{bookmarkId}")
fun editBookmark(
@PathVariable bookmarkId: Long,
@RequestBody request: BookmarkRequestBody
): ResponseEntity<ApiResponse<BookmarkDetails>> {
return ResponseEntity.ok(ApiResponse(bookmarkService.editBookmark(bookmarkId, request)))
}

@DeleteMapping("/{bookmarkId}")
fun deleteBookmark(@PathVariable bookmarkId: Long): ResponseEntity<ApiResponse<Any>> {
bookmarkService.deleteBookmark(bookmarkId)
return ResponseEntity.ok(ApiResponse(null, "Bookmark deleted"))
}

}
Original file line number Diff line number Diff line change
@@ -1,71 +1,24 @@
package org.meliapp.backend.controller

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.Parameters
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.media.Schema
import org.meliapp.backend.dto.ApiResponse
import org.meliapp.backend.dto.meli.MeliSearchResponse
import org.meliapp.backend.dto.product.ProductResponse
import org.meliapp.backend.service.MeliSearchService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import io.swagger.v3.oas.annotations.responses.ApiResponse as SwaggerApiResponse
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/products")
class MeliSearchController(private val meliSearchService: MeliSearchService) {

@GetMapping("/search")
@Operation(
summary = "Find items by keyword",
description = "Searches for items using a keyword in the MercadoLibre API.",
responses = [
SwaggerApiResponse(
responseCode = "200",
description = "Successful search results",
content = [
Content(
mediaType = "application/json",
schema = Schema(implementation = MeliSearchResponse::class)
)
]
),
SwaggerApiResponse(
responseCode = "500",
description = "Internal server error",
content = [Content()]
)
]
)
@Parameters(
Parameter(
name = "keyword",
description = "The keyword used to search for items.",
required = true,
example = "don satur"
),
Parameter(
name = "params",
description = "Filters to be applied on the search. This is a dynamic set of key-value pairs.",
required = false,
schema = Schema(
type = "object",
),
examples = [
ExampleObject(
name = "filters",
value = "{\"discount\": \"10-100\", \"shipping\": \"mercadoenvios\"}"
)
],
)
)
fun findByKeyword(@RequestParam keyword: String, @RequestParam(required = false) filters: Map<String, String>): ResponseEntity<ApiResponse<MeliSearchResponse>> {
return ResponseEntity.ok(ApiResponse(meliSearchService.findByKeyword(keyword, filters)))
}

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

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.meliapp.backend.dto.apc.auth
package org.meliapp.backend.dto.auth

import com.fasterxml.jackson.annotation.JsonCreator

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.meliapp.backend.dto.bookmark

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty

data class BookmarkDetails @JsonCreator constructor(
val id: Long,
@JsonProperty(value = "product_title")
val productTitle: String,
@JsonProperty(value = "post_id")
val postId: String,
val thumbnail: String,
val stars: Int,
val comment: String,

)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.meliapp.backend.dto.bookmark

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty

data class BookmarkRequestBody @JsonCreator constructor(
@JsonProperty(value = "meli_id")
val meliId: String,
val stars: Int,
val comment: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.meliapp.backend.dto.bookmark

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty

data class BookmarkSummary @JsonCreator constructor(
val id: Long,
@JsonProperty(value = "product_title")
val productTitle: String,
val stars: Int,
val thumbnail: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package org.meliapp.backend.dto.product

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

data class ProductResponse @JsonCreator constructor(
@JsonProperty("id")
val id: String = "",
@JsonProperty("title")
val title: String = "",
@JsonProperty("price")
val price: Double = 0.0,
val price: BigDecimal,
@JsonProperty("thumbnail")
val thumbnail: String = "",
@JsonProperty("available_quantity")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.meliapp.backend.exception.apc

open class BookmarkException(message: String) : RuntimeException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.meliapp.backend.exception.apc

class BookmarkNotFoundException(id: Long) : BookmarkException("Bookmark with id $id not found")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.meliapp.backend.exception.apc

class ProductNotFoundException(id: String) : RuntimeException("Product with id $id not found") {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.meliapp.backend.exception.apc.handlers
import io.jsonwebtoken.JwtException
import org.meliapp.backend.dto.ApiResponse
import org.meliapp.backend.exception.apc.BookmarkNotFoundException
import org.meliapp.backend.exception.apc.ProductNotFoundException
import org.meliapp.backend.exception.apc.UserAlreadyRegisteredException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
Expand All @@ -21,4 +23,9 @@ class GlobalExceptionHandler {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse(null, e.localizedMessage))
}

@ExceptionHandler(ProductNotFoundException::class, BookmarkNotFoundException::class)
fun handleNotFoundException(e: RuntimeException): ResponseEntity<ApiResponse<Any>> {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse(null, e.localizedMessage))
}

}
21 changes: 21 additions & 0 deletions src/main/kotlin/org/meliapp/backend/model/Bookmark.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.meliapp.backend.model

import jakarta.persistence.*

@Entity
@Table(uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "product_id"])])
class Bookmark {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0

@ManyToOne
lateinit var product: Product

@ManyToOne
lateinit var user: User

var stars: Int = 0
var comment: String = ""

}
16 changes: 16 additions & 0 deletions src/main/kotlin/org/meliapp/backend/model/Product.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.meliapp.backend.model

import jakarta.persistence.*
import java.math.BigDecimal

@Entity
class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0
@Column(unique = true)
lateinit var meliId: String
lateinit var title: String
lateinit var thumbnail: String
lateinit var price: BigDecimal
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.meliapp.backend.repository

import org.meliapp.backend.model.Bookmark
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository
import java.util.*

@Repository
interface BookmarkRepository : JpaRepository<Bookmark, Long> {
@Query("SELECT b FROM Bookmark b WHERE b.user.id = :userId")
fun findByUserId(@Param("userId") userId: Long): List<Bookmark>

@Query("SELECT b FROM Bookmark b WHERE b.id = :bookmarkId AND b.user.id = :userId")
fun findByIdAndUserId(@Param("bookmarkId") id: Long, @Param("userId") userId: Long): Optional<Bookmark>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.meliapp.backend.repository

import org.meliapp.backend.model.Product
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.util.*

@Repository
interface ProductRepository : JpaRepository<Product, Long> {
fun findByMeliId(meliId: String): Optional<Product>
}
8 changes: 7 additions & 1 deletion src/main/kotlin/org/meliapp/backend/service/AuthService.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.meliapp.backend.service

import org.meliapp.backend.config.security.filters.jwt.JWTHelper
import org.meliapp.backend.dto.apc.auth.AuthRequestBody
import org.meliapp.backend.dto.auth.AuthRequestBody
import org.meliapp.backend.exception.apc.UserAlreadyRegisteredException
import org.meliapp.backend.model.RoleName
import org.meliapp.backend.model.User
Expand Down Expand Up @@ -66,4 +66,10 @@ class AuthService(
if (userRepository.existsByEmail(email)) throw UserAlreadyRegisteredException(email)
}

fun getUserAuthenticated(): User {
val authentication = SecurityContextHolder.getContext().authentication
val email = authentication.name
return userRepository.findByEmail(email).orElseThrow { RuntimeException("Cannot retrieve authenticated user with email: $email") }
}

}
Loading

0 comments on commit b900112

Please sign in to comment.