diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 6beb719a3..325522696 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -1,10 +1,10 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat plugins { - kotlin("jvm") version "2.0.21" - kotlin("plugin.spring") version "2.0.21" + kotlin("jvm") version "2.3.10" + kotlin("plugin.spring") version "2.3.10" id("org.springframework.boot") version "3.4.0" - id("io.spring.dependency-management") version "1.1.6" + id("io.spring.dependency-management") version "1.1.7" id("org.jlleitschuh.gradle.ktlint") version "12.1.2" id("org.springdoc.openapi-gradle-plugin") version "1.9.0" } @@ -29,10 +29,10 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0") implementation("org.flywaydb:flyway-database-postgresql:11.0.0") - implementation("org.postgresql:postgresql:42.7.7") + implementation("org.postgresql:postgresql:42.7.9") implementation("org.jetbrains.exposed:exposed-spring-boot-starter:0.56.0") implementation("org.jetbrains.exposed:exposed-json:0.56.0") - implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-0.6.x-compat") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") testImplementation("org.springframework.boot:spring-boot-starter-test") { @@ -43,6 +43,13 @@ dependencies { testImplementation("org.testcontainers:testcontainers-postgresql:2.0.3") testImplementation("org.mock-server:mockserver-netty:5.15.0") testImplementation("org.mock-server:mockserver-spring-test-listener:5.15.0") + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +// taken from https://github.com/JLLeitschuh/ktlint-gradle/issues/809#issuecomment-2515514826 +ktlint { + version.set("1.4.1") } kotlin { diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties index d4081da47..37f78a6af 100644 --- a/backend/gradle/wrapper/gradle-wrapper.properties +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/Subscription.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/Subscription.kt index 3b6cf88b2..cba4e4aaf 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/Subscription.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/Subscription.kt @@ -48,10 +48,7 @@ sealed interface Trigger { } @Schema(description = "A trigger that is triggered when a certain count is reached") - data class CountTrigger @JsonCreator constructor( - val count: Int, - val filter: LapisFilter, - ) : Trigger { + data class CountTrigger @JsonCreator constructor(val count: Int, val filter: LapisFilter) : Trigger { val type: CountTriggerType = CountTriggerType.COUNT } diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/TriggerEvaluation.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/TriggerEvaluation.kt index 5595af6a5..44c4f1922 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/TriggerEvaluation.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/TriggerEvaluation.kt @@ -1,14 +1,9 @@ package org.genspectrum.dashboardsbackend.api -data class TriggerEvaluationResponse( - val result: TriggerEvaluationResult, -) +data class TriggerEvaluationResponse(val result: TriggerEvaluationResult) sealed interface TriggerEvaluationResult { - data class EvaluationError( - val message: String, - val statusCode: Int, - ) : TriggerEvaluationResult { + data class EvaluationError(val message: String, val statusCode: Int) : TriggerEvaluationResult { val type = EvaluationErrorType.EvaluationError enum class EvaluationErrorType { @@ -16,11 +11,8 @@ sealed interface TriggerEvaluationResult { } } - data class ConditionMet( - val evaluatedValue: Number, - val threshold: Number, - val lapisDataVersion: String?, - ) : TriggerEvaluationResult { + data class ConditionMet(val evaluatedValue: Number, val threshold: Number, val lapisDataVersion: String?) : + TriggerEvaluationResult { val type = ConditionMetType.ConditionMet enum class ConditionMetType { @@ -28,11 +20,8 @@ sealed interface TriggerEvaluationResult { } } - data class ConditionNotMet( - val evaluatedValue: Number?, - val threshold: Number, - val lapisDataVersion: String?, - ) : TriggerEvaluationResult { + data class ConditionNotMet(val evaluatedValue: Number?, val threshold: Number, val lapisDataVersion: String?) : + TriggerEvaluationResult { val type = ConditionNotMetType.ConditionNotMet enum class ConditionNotMetType { diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/config/DashboardsConfig.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/config/DashboardsConfig.kt index 160ede853..6db054045 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/config/DashboardsConfig.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/config/DashboardsConfig.kt @@ -3,27 +3,13 @@ package org.genspectrum.dashboardsbackend.config import org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties(prefix = "dashboards") -data class DashboardsConfig( - val organisms: Map, -) { +data class DashboardsConfig(val organisms: Map) { fun getOrganismConfig(organism: String) = organisms[organism] ?: throw IllegalArgumentException("No configuration found for organism $organism") } -data class OrganismConfig( - val lapis: LapisConfig, - val externalNavigationLinks: List?, -) +data class OrganismConfig(val lapis: LapisConfig, val externalNavigationLinks: List?) -data class LapisConfig( - val url: String, - val mainDateField: String, - val additionalFilters: Map?, -) +data class LapisConfig(val url: String, val mainDateField: String, val additionalFilters: Map?) -data class ExternalNavigationLink( - val url: String, - val label: String, - val menuIcon: String, - val description: String, -) +data class ExternalNavigationLink(val url: String, val label: String, val menuIcon: String, val description: String) diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsController.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsController.kt index 4a0c172cb..3920287b7 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsController.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsController.kt @@ -34,21 +34,18 @@ class SubscriptionsController( fun getSubscription( @IdParameter @PathVariable id: String, @UserIdParameter @RequestParam userId: String, - ): Subscription { - return subscriptionModel.getSubscription( - subscriptionId = id, - userId = userId, - ) - } + ): Subscription = subscriptionModel.getSubscription( + subscriptionId = id, + userId = userId, + ) @GetMapping("/subscriptions", produces = [MediaType.APPLICATION_JSON_VALUE]) @Operation( summary = "Get all subscriptions of a user", description = "Returns a list of all subscriptions of a user.", ) - fun getSubscriptions(@UserIdParameter @RequestParam userId: String): List { - return subscriptionModel.getSubscriptions(userId) - } + fun getSubscriptions(@UserIdParameter @RequestParam userId: String): List = + subscriptionModel.getSubscriptions(userId) @PostMapping("/subscriptions") @ResponseStatus(HttpStatus.CREATED) @@ -59,12 +56,10 @@ class SubscriptionsController( fun postSubscriptions( @RequestBody subscription: SubscriptionRequest, @UserIdParameter @RequestParam userId: String, - ): Subscription { - return subscriptionModel.postSubscriptions( - request = subscription, - userId = userId, - ) - } + ): Subscription = subscriptionModel.postSubscriptions( + request = subscription, + userId = userId, + ) @DeleteMapping("/subscriptions/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) @@ -88,13 +83,11 @@ class SubscriptionsController( @RequestBody subscription: SubscriptionUpdate, @IdParameter @PathVariable id: String, @UserIdParameter @RequestParam userId: String, - ): Subscription { - return subscriptionModel.putSubscription( - subscriptionId = id, - subscriptionUpdate = subscription, - userId = userId, - ) - } + ): Subscription = subscriptionModel.putSubscription( + subscriptionId = id, + subscriptionUpdate = subscription, + userId = userId, + ) @GetMapping("/subscriptions/evaluateTrigger") @Operation( diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/subscription/SubscriptionModel.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/subscription/SubscriptionModel.kt index 5b86b717f..55c6fa4dd 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/subscription/SubscriptionModel.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/subscription/SubscriptionModel.kt @@ -14,26 +14,20 @@ import javax.sql.DataSource @Service @Transactional -class SubscriptionModel( - pool: DataSource, - private val dashboardsConfig: DashboardsConfig, -) { +class SubscriptionModel(pool: DataSource, private val dashboardsConfig: DashboardsConfig) { init { Database.connect(pool) } - fun getSubscription(subscriptionId: String, userId: String): Subscription { - return SubscriptionEntity.findForUser(convertToUuid(subscriptionId), userId) + fun getSubscription(subscriptionId: String, userId: String): Subscription = + SubscriptionEntity.findForUser(convertToUuid(subscriptionId), userId) ?.toSubscription() ?: throw NotFoundException("Subscription $subscriptionId not found") - } - fun getSubscriptions(userId: String): List { - return SubscriptionEntity.find { - SubscriptionTable.userId eq userId - }.map { - it.toSubscription() - } + fun getSubscriptions(userId: String): List = SubscriptionEntity.find { + SubscriptionTable.userId eq userId + }.map { + it.toSubscription() } fun postSubscriptions(request: SubscriptionRequest, userId: String): Subscription { diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/LapisClient.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/LapisClient.kt index 516b0b132..b596234ad 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/LapisClient.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/LapisClient.kt @@ -99,35 +99,21 @@ class LapisClient( } } - private fun String.truncateAfter(maxLength: Int): String { - return if (length > maxLength) { - substring(0, maxLength) + "..." - } else { - this - } + private fun String.truncateAfter(maxLength: Int): String = if (length > maxLength) { + substring(0, maxLength) + "..." + } else { + this } } sealed interface LapisResponse -data class LapisAggregatedResponse( - val data: List, - val info: LapisInfo, -) : LapisResponse +data class LapisAggregatedResponse(val data: List, val info: LapisInfo) : LapisResponse -data class AggregatedData( - val count: Int, -) +data class AggregatedData(val count: Int) -data class LapisError( - val error: ProblemDetail, - val info: LapisInfo, -) : LapisResponse +data class LapisError(val error: ProblemDetail, val info: LapisInfo) : LapisResponse -data class LapisInfo( - val dataVersion: String? = null, -) +data class LapisInfo(val dataVersion: String? = null) -data class LapisNotReachableError( - val message: String, -) : LapisResponse +data class LapisNotReachableError(val message: String) : LapisResponse diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/TriggerEvaluator.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/TriggerEvaluator.kt index cd2372e55..8c0f48475 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/TriggerEvaluator.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/TriggerEvaluator.kt @@ -59,9 +59,8 @@ class TriggerEvaluator( ) } - private fun additionalFilters(organism: String): Map { - return dashboardsConfig.getOrganismConfig(organism).lapis.additionalFilters ?: emptyMap() - } + private fun additionalFilters(organism: String): Map = + dashboardsConfig.getOrganismConfig(organism).lapis.additionalFilters ?: emptyMap() } private interface TriggerComputation { diff --git a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/util/DateProvider.kt b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/util/DateProvider.kt index f012ef85b..b3a36f8a2 100644 --- a/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/util/DateProvider.kt +++ b/backend/src/main/kotlin/org/genspectrum/dashboardsbackend/util/DateProvider.kt @@ -1,9 +1,9 @@ package org.genspectrum.dashboardsbackend.util -import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import org.springframework.stereotype.Component +import kotlin.time.Clock @Component class DateProvider { diff --git a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/SwaggerUiTest.kt b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/SwaggerUiTest.kt index b9c2016b9..13e5c697d 100644 --- a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/SwaggerUiTest.kt +++ b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/SwaggerUiTest.kt @@ -17,7 +17,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @SpringBootTest @AutoConfigureMockMvc -class SwaggerUiTest(@Autowired val mockMvc: MockMvc) { +class SwaggerUiTest(@param:Autowired val mockMvc: MockMvc) { @Test fun `Swagger UI endpoint is reachable`() { diff --git a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/InfoControllerTest.kt b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/InfoControllerTest.kt index 19d7c16f0..f120a90a7 100644 --- a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/InfoControllerTest.kt +++ b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/InfoControllerTest.kt @@ -13,7 +13,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @SpringBootTest @AutoConfigureMockMvc -class InfoControllerTest(@Autowired val mockMvc: MockMvc) { +class InfoControllerTest(@param:Autowired val mockMvc: MockMvc) { @Test fun `WHEN calling root THEN responds with a hello page`() { mockMvc.perform(get("/")) diff --git a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsClient.kt b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsClient.kt index 341c439d3..4d672007c 100644 --- a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsClient.kt +++ b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsClient.kt @@ -15,10 +15,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -class SubscriptionsClient( - private val mockMvc: MockMvc, - private val objectMapper: ObjectMapper, -) { +class SubscriptionsClient(private val mockMvc: MockMvc, private val objectMapper: ObjectMapper) { fun getSubscriptionRaw(id: String, userId: String) = mockMvc.perform(get("/subscriptions/$id?userId=$userId")) fun getSubscription(id: String, userId: String): Subscription = deserializeJsonResponse( diff --git a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTest.kt b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTest.kt index df7f63f33..bf1a7eb6a 100644 --- a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTest.kt +++ b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTest.kt @@ -23,16 +23,12 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPat import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import java.util.* -fun getNewUserId(): String { - return UUID.randomUUID().toString() -} +fun getNewUserId(): String = UUID.randomUUID().toString() @SpringBootTest @AutoConfigureMockMvc @Import(SubscriptionsClient::class) -class SubscriptionsControllerTest( - @Autowired private val subscriptionsClient: SubscriptionsClient, -) { +class SubscriptionsControllerTest(@param:Autowired private val subscriptionsClient: SubscriptionsClient) { @Test fun `GIVEN I created a subscription WHEN getting subscriptions THEN contains created subscription`() { val userId = getNewUserId() diff --git a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTriggerEvaluationTest.kt b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTriggerEvaluationTest.kt index f5e127a44..5325d5f43 100644 --- a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTriggerEvaluationTest.kt +++ b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTriggerEvaluationTest.kt @@ -30,7 +30,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @AutoConfigureMockMvc @Import(SubscriptionsClient::class) class SubscriptionsControllerTriggerEvaluationTest( - @Autowired private val subscriptionsClient: SubscriptionsClient, + @param:Autowired private val subscriptionsClient: SubscriptionsClient, ) { @MockkBean private lateinit var lapisClientProviderMock: LapisClientProvider diff --git a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/CountTriggerEvaluatorTest.kt b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/CountTriggerEvaluatorTest.kt index a75d70bc8..af52a630e 100644 --- a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/CountTriggerEvaluatorTest.kt +++ b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/CountTriggerEvaluatorTest.kt @@ -41,9 +41,7 @@ import org.springframework.boot.test.context.SpringBootTest ], ) @MockServerTest -class CountTriggerEvaluatorTest( - @Autowired private val underTest: TriggerEvaluator, -) { +class CountTriggerEvaluatorTest(@param:Autowired private val underTest: TriggerEvaluator) { private lateinit var mockServerClient: MockServerClient @MockkBean @@ -261,13 +259,12 @@ class CountTriggerEvaluatorTest( assertThat(exception.message, containsString("Failed to deserialize error response from LAPIS")) } - private fun isEvaluationErrorWith(message: Matcher, statusCode: Int): Matcher { - return allOf( + private fun isEvaluationErrorWith(message: Matcher, statusCode: Int): Matcher = + allOf( instanceOf(TriggerEvaluationResult.EvaluationError::class.java), hasProperty("message", message), hasProperty("statusCode", `is`(statusCode)), ) - } private fun requestingAggregatedDataWith(organism: String, body: String): HttpRequest? = request() .withMethod("POST") diff --git a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/ProportionTriggerEvaluatorTest.kt b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/ProportionTriggerEvaluatorTest.kt index 1542c947e..97da43a05 100644 --- a/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/ProportionTriggerEvaluatorTest.kt +++ b/backend/src/test/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/ProportionTriggerEvaluatorTest.kt @@ -39,9 +39,7 @@ import org.springframework.boot.test.context.SpringBootTest ], ) @MockServerTest -class ProportionTriggerEvaluatorTest( - @Autowired private val underTest: TriggerEvaluator, -) { +class ProportionTriggerEvaluatorTest(@param:Autowired private val underTest: TriggerEvaluator) { private lateinit var mockServerClient: MockServerClient @MockkBean @@ -333,13 +331,12 @@ class ProportionTriggerEvaluatorTest( """.replace("\\s".toRegex(), ""), ) - private fun isEvaluationErrorWith(message: Matcher, statusCode: Int): Matcher { - return allOf( + private fun isEvaluationErrorWith(message: Matcher, statusCode: Int): Matcher = + allOf( instanceOf(TriggerEvaluationResult.EvaluationError::class.java), hasProperty("message", message), hasProperty("statusCode", `is`(statusCode)), ) - } private fun requestingAggregatedDataWith(organism: String, body: String) = request() .withMethod("POST")