mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +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.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||
implementation("org.springframework.boot:spring-boot-starter-jdbc")
|
||||
implementation("org.postgresql:postgresql")
|
||||
implementation("org.springframework.boot:spring-boot-starter-mustache")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||
|
||||
runtimeOnly("org.postgresql:postgresql")
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
|
||||
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)
|
||||
@Column(value = "updated_at")
|
||||
val updatedAt: OffsetDateTime?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "deleted_at")
|
||||
val deletedAt: OffsetDateTime?,
|
||||
) {
|
||||
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
|
||||
|
||||
fun isDeleted(): Boolean = deletedAt != null
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.example.demo.provider
|
||||
|
||||
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.query.Param
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
|
||||
@Repository
|
||||
interface ProductRepository: CrudRepository<Product, Long> {
|
||||
@Query(value = "SELECT * FROM Product WHERE guid = :guid")
|
||||
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