Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3654f91
tests/qg-xxx: исправлены тесты ics-календарей
d-r-q Mar 2, 2026
385b13c
build/qg-xxx: Spring Boot обновлён до 3.5.11
d-r-q Mar 1, 2026
c5feffe
build/qg-xxx: Spring Boot обновлён до 4.0.3 (без исправлений ошибок к…
d-r-q Mar 2, 2026
c03fe58
build/qg-xxx: обновлён импорт org.springframework.data.core.TypeInfor…
d-r-q Mar 1, 2026
9b7f74f
build/qg-xxx: исправлены ошибки компиляции, вызванные тем, что в Spri…
d-r-q Mar 3, 2026
95ae935
build/qg-xxx: testcontainers обновлены до 2.0.3
d-r-q Mar 1, 2026
4c00923
build/qg-xxx: исправлены ошибки компиляции связанные с распилом и пер…
d-r-q Mar 2, 2026
bd6d3cd
build/qg-xxx: исправлена ошибка с почему-то сломавшимся синтаксически…
d-r-q Mar 2, 2026
22d1feb
build/qg-xxx: исправлены ошибки компиляции из-за несовместимых измене…
d-r-q Mar 2, 2026
490c4ca
build/qg-xxx: добавлен Spring Boot Flyway Starter
d-r-q Mar 1, 2026
855c61e
build/qg-xxx: проект переведён на Jackson 3
d-r-q Mar 1, 2026
2e8ae85
build/qg-xxx: RestAssured обновлён до 6.0.0
d-r-q Mar 1, 2026
e8735ba
build/qg-xxx: версия платформы Jetty в тестах зафиксирована на 12.1
d-r-q Mar 2, 2026
aaaeb2a
build/qg-xxx: удалены костыли вытаскивания DataAccessException из DbA…
d-r-q Mar 2, 2026
8e7bbd1
build/qg-xxx: восстановлен возврат пустого тела со статусом 413 при п…
d-r-q Mar 2, 2026
106509d
build/qg-xxx: зависимость от api jackarta validation заменена на соот…
d-r-q Mar 3, 2026
c6c34af
build/qg-xxx: исправлена конфигурация AuthenticationProvider-а Passwo…
d-r-q Mar 3, 2026
d20ebb8
tests/qg-xxx: тесты переведены на новый RestTestClient
d-r-q Mar 3, 2026
df5201f
tests/qg-xxx: повышена стабильность мигающего теста ical-календарей
d-r-q Mar 3, 2026
838b51b
build/qg-xxx: Kotlin обновлён до версии 2.3.10
d-r-q Mar 3, 2026
7d1a2be
chore/qg-xxx: исправлены все варнинги компилятора и Gradle-а
d-r-q Mar 3, 2026
1455473
build/qg-xxx: Gradle обновлён до 9.3.1
d-r-q Mar 3, 2026
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
30 changes: 24 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ plugins {
group = "pro.qyoga"
version = "0.0.1-SNAPSHOT"

configurations.matching { it.name == "compileClasspath" }.all {
exclude(group = "com.fasterxml.jackson.core", module = "jackson-core")
exclude(group = "com.fasterxml.jackson.core", module = "jackson-databind")
}

configurations.matching { it.name in setOf("testCompileClasspath", "testFixturesCompileClasspath") }.all {
exclude(group = "com.fasterxml.jackson")
exclude(group = "com.fasterxml.jackson.core")
}

dependencies {
implementation(kotlin("reflect"))
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
Expand All @@ -29,11 +39,12 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.springframework.boot:spring-boot-starter-flyway")
implementation(libs.caffeine)

implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("tools.jackson.module:jackson-module-kotlin")
implementation("org.flywaydb:flyway-database-postgresql")
implementation(libs.jackarta.validation)
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation(libs.thymeleaf.extras.java8time)
implementation(libs.postgres)
implementation(libs.minio)
Expand All @@ -49,6 +60,9 @@ dependencies {
implementation(libs.bouncycastle)

developmentOnly("org.springframework.boot:spring-boot-docker-compose")
runtimeOnly(platform("com.fasterxml.jackson:jackson-bom:2.20.2"))
runtimeOnly("com.fasterxml.jackson.core:jackson-core")
runtimeOnly("com.fasterxml.jackson.core:jackson-databind")

testFixturesApi("org.springframework.boot:spring-boot-testcontainers")
testFixturesApi(testLibs.kotest.assertions)
Expand All @@ -71,18 +85,22 @@ dependencies {
testFixturesImplementation("org.springframework.boot:spring-boot-starter-web")
testFixturesImplementation("org.springframework.boot:spring-boot-starter-security")
testFixturesImplementation("org.springframework.boot:spring-boot-starter-oauth2-client")
testFixturesImplementation("com.fasterxml.jackson.core:jackson-databind")
testFixturesImplementation(enforcedPlatform(testLibs.jetty))
testFixturesImplementation(enforcedPlatform(testLibs.jetty.ee10))
testFixturesImplementation("tools.jackson.core:jackson-databind")
testFixturesImplementation(libs.minio)
testFixturesImplementation(libs.ical4j)

testFixturesImplementation("org.springframework.boot:spring-boot-starter-test")
testFixturesImplementation("org.springframework.security:spring-security-test")
testFixturesImplementation("org.springframework.boot:spring-boot-starter-webflux")
testFixturesImplementation("org.testcontainers:junit-jupiter")
testFixturesImplementation("org.testcontainers:postgresql")
testFixturesImplementation("org.testcontainers:testcontainers-junit-jupiter")
testFixturesImplementation("org.testcontainers:testcontainers-postgresql")
testFixturesImplementation(testLibs.testcontainers.minio)

testImplementation(testFixtures(project(":app")))
testImplementation(enforcedPlatform(testLibs.jetty))
testImplementation(enforcedPlatform(testLibs.jetty.ee10))
testImplementation(testLibs.bundles.restassured)
testImplementation(testLibs.archunit)
testImplementation(testLibs.mockito.kotlin)
Expand Down Expand Up @@ -220,7 +238,7 @@ configurations.matching { it.name == "detekt" }.all {
resolutionStrategy.eachDependency {
if (requested.group == "org.jetbrains.kotlin") {
@Suppress("UnstableApiUsage")
useVersion(io.gitlab.arturbosch.detekt.getSupportedKotlinVersion())
useVersion(dev.detekt.gradle.plugin.getSupportedKotlinVersion())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package pro.azhidkov.platform.file_storage.internal

import org.springframework.data.core.TypeInformation
import org.springframework.data.jdbc.core.JdbcAggregateOperations
import org.springframework.data.jdbc.core.convert.JdbcConverter
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository
import org.springframework.data.mapping.model.BasicPersistentEntity
import org.springframework.data.util.TypeInformation
import org.springframework.stereotype.Repository
import pro.azhidkov.platform.file_storage.api.FileMetaData

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package pro.azhidkov.platform.spring.jdbc

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.core.convert.support.DefaultConversionService
import org.springframework.jdbc.core.DataClassRowMapper
import org.springframework.jdbc.core.RowMapper
import pro.azhidkov.platform.spring.sdj.converters.PGIntervalToDurationConverter
import pro.azhidkov.platform.spring.sdj.converters.StringToSecretChars
import pro.azhidkov.platform.spring.sdj.converters.UuidToAggregateReferenceConverter
import tools.jackson.databind.ObjectMapper


inline fun <reified T> rowMapperFor(objectMapper: ObjectMapper, columnName: String? = null) = RowMapper<T> { rs, _ ->
inline fun <reified T> rowMapperFor(objectMapper: ObjectMapper, columnName: String? = null) = RowMapper<T?> { rs, _ ->
val json: String? = if (columnName != null) {
rs.getString(columnName)
} else {
Expand All @@ -18,10 +18,11 @@ inline fun <reified T> rowMapperFor(objectMapper: ObjectMapper, columnName: Stri
json?.let { objectMapper.readValue(it, T::class.java) }
}

inline fun <reified T> taDataClassRowMapper() = DataClassRowMapper.newInstance(T::class.java).apply {
conversionService = DefaultConversionService().apply {
inline fun <reified T : Any> taDataClassRowMapper(): DataClassRowMapper<T> =
DataClassRowMapper.newInstance(T::class.java).apply {
conversionService = DefaultConversionService().apply {
addConverter(PGIntervalToDurationConverter())
addConverter(UuidToAggregateReferenceConverter)
addConverter(StringToSecretChars())
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package pro.azhidkov.platform.spring.sdj

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.ContextualDeserializer
import com.fasterxml.jackson.databind.type.TypeFactory
import org.springframework.data.jdbc.core.mapping.AggregateReference
import pro.azhidkov.platform.spring.sdj.ergo.hydration.AggregateReferenceTarget
import pro.azhidkov.platform.spring.sdj.ergo.hydration.Identifiable
import tools.jackson.core.JsonParser
import tools.jackson.databind.*
import tools.jackson.databind.type.TypeFactory
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.jvmErasure


class AggregateReferenceDeserializer(
private var type: JavaType = TypeFactory.unknownType()
) : JsonDeserializer<AggregateReference<*, *>>(), ContextualDeserializer {
) : ValueDeserializer<AggregateReference<*, *>>() {

override fun deserialize(parser: JsonParser, context: DeserializationContext): AggregateReference<*, *>? {
val node = parser.codec.readTree<JsonNode>(parser)
val node = parser.readValueAsTree<JsonNode>()
val propertyNames = node.properties().map { it.key }.toSet()
return if (propertyNames == setOf(ID_FIELD_NAME)) {
val idType = getIdType(type)
Expand All @@ -28,7 +27,7 @@ class AggregateReferenceDeserializer(
}
}

override fun createContextual(ctx: DeserializationContext, property: BeanProperty): JsonDeserializer<*> {
override fun createContextual(ctx: DeserializationContext, property: BeanProperty): ValueDeserializer<*> {
return AggregateReferenceDeserializer(property.type.containedType(0))
}

Expand All @@ -44,4 +43,4 @@ private fun getIdType(type: JavaType): Class<out Any> =
.first { it.name == AggregateReferenceDeserializer.ID_FIELD_NAME }
.returnType
.jvmErasure
.java
.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package pro.azhidkov.platform.spring.sdj

import com.fasterxml.jackson.databind.module.SimpleModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.jdbc.core.mapping.AggregateReference
import pro.azhidkov.platform.spring.sdj.ergo.ErgoPersistenceExceptionTranslator
import com.fasterxml.jackson.databind.Module as JacksonModule
import tools.jackson.databind.JacksonModule
import tools.jackson.databind.module.SimpleModule


@Configuration
Expand All @@ -21,8 +20,4 @@ class ErgoSdjConfig {
fun aggregateReferenceBindingAdvice() =
AggregateReferenceBindingAdvice()

@Bean
fun persistenceExceptionTranslator() =
ErgoPersistenceExceptionTranslator()

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest

fun <T, R> Page<T>.mapContent(f: (MutableList<T>) -> List<R>): Page<R> {
fun <T : Any, R : Any> Page<T>.mapContent(f: (List<T>) -> List<R>): Page<R> {
return PageImpl(f(this.content), PageRequest.of(this.number, this.size), this.totalElements)
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package pro.azhidkov.platform.spring.sdj.converters

import com.fasterxml.jackson.databind.ObjectMapper
import org.postgresql.util.PGobject
import org.springframework.core.convert.converter.Converter
import org.springframework.data.convert.ReadingConverter
import org.springframework.data.convert.WritingConverter
import tools.jackson.databind.ObjectMapper
import kotlin.reflect.KClass


@WritingConverter
fun interface ObjectToJsonbWriter<T : Any> : Converter<T, PGobject>

@ReadingConverter
fun interface JsonbToObjectReader<T : Any> : Converter<PGobject, T>
fun interface JsonbToObjectReader<T> : Converter<PGobject, T?>

abstract class JacksonObjectToJsonbWriter<T : Any>(
private val objectMapper: ObjectMapper
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import org.springframework.data.jdbc.core.JdbcAggregateOperations
import org.springframework.data.jdbc.core.convert.EntityRowMapper
import org.springframework.data.jdbc.core.convert.JdbcConverter
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository
import org.springframework.data.relational.core.conversion.DbActionExecutionException
import org.springframework.data.relational.core.mapping.RelationalMappingContext
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity
import org.springframework.data.relational.core.query.Query
Expand Down Expand Up @@ -48,7 +47,7 @@ class ErgoRepository<T : Any, ID : Any>(

protected fun <S : T> saveAndMapDuplicatedKeyException(aggregate: T, map: (DuplicateKeyException) -> Throwable): S {
val res = runCatching { super.save(aggregate) }
when (val ex = (res.exceptionOrNull() as? DbActionExecutionException)?.cause as? DuplicateKeyException) {
when (val ex = res.exceptionOrNull() as? DuplicateKeyException) {
is DuplicateKeyException -> throw map(ex)
}

Expand Down Expand Up @@ -162,13 +161,19 @@ class ErgoRepository<T : Any, ID : Any>(
queryBuilder: QueryBuilder.() -> Unit = {}
): Page<T> {
val query = query(queryBuilder)
val res = jdbcAggregateTemplate.findAll(query, relationalPersistentEntity.type, pageRequest)
return res.mapContent {
jdbcAggregateTemplate.hydrate(
res,
FetchSpec(fetch)
)
}

val content = jdbcAggregateTemplate.findAll(query.with(pageRequest), relationalPersistentEntity.type)
.let { jdbcAggregateTemplate.hydrate(it, FetchSpec(fetch)) }

val total = jdbcAggregateTemplate.count(query, relationalPersistentEntity.type)

val page = PageImpl(
content,
pageRequest,
total
)

return page
}

fun findSlice(
Expand Down Expand Up @@ -263,4 +268,4 @@ class ErgoRepository<T : Any, ID : Any>(
@Suppress("UNCHECKED_CAST")
private fun <T : Any> RelationalMappingContext.getRelationPersistentEntity(
type: KClass<T>
): RelationalPersistentEntity<T> = getRequiredPersistentEntity(type.java) as RelationalPersistentEntity<T>
): RelationalPersistentEntity<T> = getRequiredPersistentEntity(type.java) as RelationalPersistentEntity<T>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import kotlin.reflect.jvm.jvmErasure

data class PropertyFetchSpec<T : Any?, V>(
val property: KProperty1<T, V?>,
val fetchSpec: FetchSpec<*> = FetchSpec(
val fetchSpec: FetchSpec<out Any> = FetchSpec(
emptyList<PropertyFetchSpec<Any, Any>>()
)
)
Expand Down Expand Up @@ -42,15 +42,14 @@ fun <T : Any> JdbcAggregateOperations.hydrate(
return (entities as? List<T>) ?: entities.toList()
}

val refs: Map<KProperty1<*, AggregateReference<*, *>?>, Map<Any, Any>> =
val refs: Map<KProperty1<*, *>, Map<Any, Any>> =
fetchSpec.propertyFetchSpecs.filter {
detectRefType(
it.property
) != null
}
.associate { it: PropertyFetchSpec<T, *> ->
val property = it.property as KProperty1<*, AggregateReference<*, *>?>
property to fetchPropertyRefs(entities, it)
it.property to fetchPropertyRefs(entities, it)
}

if (refs.isEmpty()) {
Expand All @@ -69,11 +68,19 @@ private fun <T : Any> JdbcAggregateOperations.fetchPropertyRefs(
val property = propertyFetchSpec.property
val ids = fetchIds(entities, property)
val targetType = (property.returnType.arguments[0].type!!.classifier!! as KClass<*>).java
val refs = hydrate(this.findAllById(ids, targetType), propertyFetchSpec.fetchSpec as FetchSpec<Any>)
val refs = hydrateAny(this.findAllById(ids, targetType), propertyFetchSpec.fetchSpec)
.associateBy { (it as Identifiable<*>).id }
return refs
}

@Suppress("UNCHECKED_CAST")
private fun JdbcAggregateOperations.hydrateAny(
entities: Iterable<*>,
fetchSpec: FetchSpec<out Any>
): List<Any> {
return hydrate(entities as Iterable<Any>, fetchSpec as FetchSpec<Any>)
}

private fun <T : Any> fetchIds(
entities: Iterable<T>,
property: KProperty1<T, Any?>
Expand All @@ -87,12 +94,12 @@ private fun <T : Any> fetchIds(
else -> error("Unsupported property type: $property")
}

private fun <T : Any> hydrateEntity(entity: T, refs: Map<KProperty1<*, AggregateReference<*, *>?>, Map<Any, Any>>): T {
private fun <T : Any> hydrateEntity(entity: T, refs: Map<KProperty1<*, *>, Map<Any, Any>>): T {
val constructorParams = entity::class.primaryConstructor!!.parameters
val paramValues = constructorParams.associateWith { param ->
val prop =
entity::class.memberProperties.find { prop -> param.name == prop.name }!! as KProperty1<T, AggregateReference<*, *>?>
val currentValue: Any? = prop.invoke(entity)
entity::class.memberProperties.find { prop -> param.name == prop.name }!!
val currentValue = prop.getter.call(entity)
val newValue = if (prop in refs) {
val id = (currentValue as AggregateReference<*, *>?)?.id
if (id != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ interface TransactionalService {
val transactionTemplate: TransactionTemplate

fun <T : Any?> transaction(body: () -> T): T {
val res = transactionTemplate.execute {
return transactionTemplate.execute {
body()
}
@Suppress("UNCHECKED_CAST")
return res as T
}

}
}
Loading
Loading