mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
add service layer with ProductService, add deleted_at and unique index to product table
This commit is contained in:
@@ -28,15 +28,12 @@ dependencies {
|
|||||||
implementation("org.flywaydb:flyway-core")
|
implementation("org.flywaydb:flyway-core")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.postgresql:postgresql")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-jdbc")
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-mustache")
|
implementation("org.springframework.boot:spring-boot-starter-mustache")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
|
|
||||||
runtimeOnly("org.postgresql:postgresql")
|
|
||||||
|
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
||||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
|
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.example.demo.exceptions
|
||||||
|
|
||||||
|
class UnprocessableException(override val message: String): RuntimeException(message)
|
||||||
@@ -27,6 +27,11 @@ data class Product(
|
|||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
@Column(value = "updated_at")
|
@Column(value = "updated_at")
|
||||||
val updatedAt: OffsetDateTime?,
|
val updatedAt: OffsetDateTime?,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
|
@Column(value = "deleted_at")
|
||||||
|
val deletedAt: OffsetDateTime?,
|
||||||
) {
|
) {
|
||||||
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
|
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
|
||||||
|
|
||||||
|
fun isDeleted(): Boolean = deletedAt != null
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
package com.example.demo.provider
|
package com.example.demo.provider
|
||||||
|
|
||||||
import com.example.demo.models.Product
|
import com.example.demo.models.Product
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jdbc.repository.query.Query
|
||||||
import org.springframework.data.repository.CrudRepository
|
import org.springframework.data.repository.CrudRepository
|
||||||
|
import org.springframework.data.repository.query.Param
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.time.OffsetDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface ProductRepository: CrudRepository<Product, Long> {
|
interface ProductRepository: CrudRepository<Product, Long> {
|
||||||
@Query(value = "SELECT * FROM Product WHERE guid = :guid")
|
|
||||||
fun findByGuid(guid: UUID): Product?
|
fun findByGuid(guid: UUID): Product?
|
||||||
|
|
||||||
|
@Query(value = "UPDATE Product SET deleted_at = :deletedAt WHERE guid = :guid RETURNING *")
|
||||||
|
fun softDelete(@Param("guid") guid: UUID, @Param("deletedAt") deletedAt: OffsetDateTime): Product?
|
||||||
}
|
}
|
||||||
17
src/main/kotlin/com/example/demo/services/ProductService.kt
Normal file
17
src/main/kotlin/com/example/demo/services/ProductService.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.example.demo.services
|
||||||
|
|
||||||
|
import com.example.demo.exceptions.NotFoundException
|
||||||
|
import com.example.demo.exceptions.UnprocessableException
|
||||||
|
import com.example.demo.models.Product
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Service
|
||||||
|
interface ProductService {
|
||||||
|
fun findByGuid(guid: UUID): Product?
|
||||||
|
|
||||||
|
fun create(name: String, price: Long, description: String?): Product
|
||||||
|
|
||||||
|
@Throws(NotFoundException::class, UnprocessableException::class)
|
||||||
|
fun delete(guid: UUID): Product?
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.example.demo.services
|
||||||
|
|
||||||
|
import com.example.demo.exceptions.NotFoundException
|
||||||
|
import com.example.demo.exceptions.UnprocessableException
|
||||||
|
import com.example.demo.models.Product
|
||||||
|
import com.example.demo.provider.ProductRepository
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ProductServiceImpl(private val productRepository: ProductRepository): ProductService {
|
||||||
|
override fun findByGuid(guid: UUID): Product? = productRepository.findByGuid(guid)
|
||||||
|
|
||||||
|
override fun create(name: String, price: Long, description: String?): Product {
|
||||||
|
val product = Product(
|
||||||
|
id = null,
|
||||||
|
guid = UUID.randomUUID(),
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
price = price,
|
||||||
|
createdAt = OffsetDateTime.now(),
|
||||||
|
updatedAt = null,
|
||||||
|
deletedAt = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
return productRepository.save(product)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(guid: UUID): Product? {
|
||||||
|
val product = findByGuid(guid) ?: throw NotFoundException()
|
||||||
|
|
||||||
|
if (product.isDeleted()) {
|
||||||
|
throw UnprocessableException("product already deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
val deletedProduct = product.copy(
|
||||||
|
id = product.id!!,
|
||||||
|
guid = product.guid,
|
||||||
|
name = product.name,
|
||||||
|
description = product.description,
|
||||||
|
price = product.price,
|
||||||
|
createdAt = product.createdAt,
|
||||||
|
updatedAt = product.updatedAt,
|
||||||
|
deletedAt = OffsetDateTime.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return productRepository.save(deletedProduct)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table product add deleted_at timestamptz default null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
CREATE UNIQUE INDEX product_guid_idx ON product (guid);
|
||||||
Reference in New Issue
Block a user