diff --git a/src/main/kotlin/com/example/demo/AppConfig.kt b/src/main/kotlin/com/example/demo/AppConfig.kt index e0e091f..0035e04 100644 --- a/src/main/kotlin/com/example/demo/AppConfig.kt +++ b/src/main/kotlin/com/example/demo/AppConfig.kt @@ -1,8 +1,11 @@ package com.example.demo +import com.example.demo.provider.CityRepository import com.example.demo.provider.MockedShopProvider import com.example.demo.provider.ProductRepository import com.example.demo.provider.ShopProvider +import com.example.demo.services.CityService +import com.example.demo.services.CityServiceImpl import com.example.demo.services.ProductService import com.example.demo.services.ProductServiceImpl import org.springframework.beans.factory.annotation.Autowired @@ -17,7 +20,8 @@ class AppConfig { } @Bean - fun productService(@Autowired productRepository: ProductRepository): ProductService { - return ProductServiceImpl(productRepository = productRepository) - } + fun productService(@Autowired productRepository: ProductRepository): ProductService = ProductServiceImpl(productRepository) + + @Bean + fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository) } diff --git a/src/main/kotlin/com/example/demo/models/City.kt b/src/main/kotlin/com/example/demo/models/City.kt index fc21b14..bacd05b 100644 --- a/src/main/kotlin/com/example/demo/models/City.kt +++ b/src/main/kotlin/com/example/demo/models/City.kt @@ -1,15 +1,20 @@ package com.example.demo.models import com.example.demo.models.serializables.OffsetDateTimeSerialization +import com.example.demo.models.serializables.UuidSerialization import kotlinx.serialization.Serializable +import org.springframework.data.annotation.Id import org.springframework.data.relational.core.mapping.Column import org.springframework.data.relational.core.mapping.Table import java.time.OffsetDateTime import java.util.* @Table("city") +@Serializable data class City( + @Id val id: Long?, + @Serializable(with = UuidSerialization::class) val guid: UUID, val name: String, @Serializable(with = OffsetDateTimeSerialization::class) @@ -21,4 +26,6 @@ data class City( @Serializable(with = OffsetDateTimeSerialization::class) @Column(value = "deleted_at") val deletedAt: OffsetDateTime?, -) \ No newline at end of file +) { + fun isDeleted(): Boolean = deletedAt != null +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/demo/provider/CityRepository.kt b/src/main/kotlin/com/example/demo/provider/CityRepository.kt new file mode 100644 index 0000000..c61afdd --- /dev/null +++ b/src/main/kotlin/com/example/demo/provider/CityRepository.kt @@ -0,0 +1,17 @@ +package com.example.demo.provider + +import com.example.demo.models.City +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 CityRepository: CrudRepository { + fun findByGuid(guid: UUID): City? + + @Query(value = "UPDATE City SET deleted_at = :deletedAt WHERE guid = :guid RETURNING *") + fun softDelete(@Param("guid") guid: UUID, @Param("deletedAt") deletedAt: OffsetDateTime): City? +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/demo/services/CityService.kt b/src/main/kotlin/com/example/demo/services/CityService.kt new file mode 100644 index 0000000..c81b643 --- /dev/null +++ b/src/main/kotlin/com/example/demo/services/CityService.kt @@ -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.City +import org.springframework.stereotype.Service +import java.util.* + +@Service +interface CityService { + fun findByGuid(guid: UUID): City? + + fun create(name: String): City? + + @Throws(NotFoundException::class, UnprocessableException::class) + fun delete(guid: UUID): City? +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/demo/services/CityServiceImpl.kt b/src/main/kotlin/com/example/demo/services/CityServiceImpl.kt new file mode 100644 index 0000000..376f92c --- /dev/null +++ b/src/main/kotlin/com/example/demo/services/CityServiceImpl.kt @@ -0,0 +1,46 @@ +package com.example.demo.services + +import com.example.demo.exceptions.NotFoundException +import com.example.demo.exceptions.UnprocessableException +import com.example.demo.models.City +import com.example.demo.provider.CityRepository +import java.time.OffsetDateTime +import java.util.* + +class CityServiceImpl( + private val cityRepository: CityRepository +): CityService { + override fun findByGuid(guid: UUID): City? = cityRepository.findByGuid(guid) + + override fun create(name: String): City? { + val city = City( + id = null, + guid = UUID.randomUUID(), + name = name, + createdAt = OffsetDateTime.now(), + updatedAt = null, + deletedAt = null, + ) + + return cityRepository.save(city) + } + + override fun delete(guid: UUID): City? { + val city = findByGuid(guid) ?: throw NotFoundException() + + if (city.isDeleted()) { + throw UnprocessableException("city already deleted") + } + + val deletedCity = city.copy( + id = city.id!!, + guid = city.guid, + name = city.name, + createdAt = city.createdAt, + updatedAt = city.updatedAt, + deletedAt = OffsetDateTime.now(), + ) + + return cityRepository.save(deletedCity) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/demo/services/ProductServiceImpl.kt b/src/main/kotlin/com/example/demo/services/ProductServiceImpl.kt index 0e1b6df..89ff5fc 100644 --- a/src/main/kotlin/com/example/demo/services/ProductServiceImpl.kt +++ b/src/main/kotlin/com/example/demo/services/ProductServiceImpl.kt @@ -7,7 +7,9 @@ import com.example.demo.provider.ProductRepository import java.time.OffsetDateTime import java.util.* -class ProductServiceImpl(private val productRepository: ProductRepository): ProductService { +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 { diff --git a/src/test/kotlin/com/example/demo/services/CityServiceImplTest.kt b/src/test/kotlin/com/example/demo/services/CityServiceImplTest.kt new file mode 100644 index 0000000..e6b40f2 --- /dev/null +++ b/src/test/kotlin/com/example/demo/services/CityServiceImplTest.kt @@ -0,0 +1,46 @@ +package com.example.demo.services + +import com.example.demo.BaseFeatureTest +import com.example.demo.models.City +import com.example.demo.provider.CityRepository +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.ContextConfiguration +import kotlin.test.* + +@ContextConfiguration(classes = [CityRepository::class, CityServiceImpl::class]) +class CityServiceImplTest: BaseFeatureTest() { + @Autowired + private lateinit var cityRepository: CityRepository + @Autowired + private lateinit var cityServiceImpl: CityServiceImpl + + @Test + fun createFindDelete_success() { + val name = "Some city" + var city: City? = null + + try { + city = cityServiceImpl.create(name = name) + assertNotNull(city) + assertNotNull(city.id) + assertEquals(name, city.name) + + val dbCity = cityServiceImpl.findByGuid(city.guid) + assertNotNull(dbCity) + assertEquals(city.id, dbCity.id) + assertFalse(dbCity.isDeleted()) + + val deletedCity = cityServiceImpl.delete(city.guid) + assertNotNull(deletedCity) + assertEquals(city.id, deletedCity.id) + assertNotNull(deletedCity.deletedAt) + assertTrue(deletedCity.isDeleted()) + } finally { + val id = city?.id + if (id != null) { + cityRepository.deleteById(id) + } + + } + } +} \ No newline at end of file