Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 12 additions & 5 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"
}
Expand All @@ -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") {
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion backend/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,27 @@
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 {
EvaluationError,
}
}

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 {
ConditionMet,
}
}

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, OrganismConfig>,
) {
data class DashboardsConfig(val organisms: Map<String, OrganismConfig>) {
fun getOrganismConfig(organism: String) = organisms[organism]
?: throw IllegalArgumentException("No configuration found for organism $organism")
}

data class OrganismConfig(
val lapis: LapisConfig,
val externalNavigationLinks: List<ExternalNavigationLink>?,
)
data class OrganismConfig(val lapis: LapisConfig, val externalNavigationLinks: List<ExternalNavigationLink>?)

data class LapisConfig(
val url: String,
val mainDateField: String,
val additionalFilters: Map<String, String>?,
)
data class LapisConfig(val url: String, val mainDateField: String, val additionalFilters: Map<String, String>?)

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)
Original file line number Diff line number Diff line change
Expand Up @@ -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<Subscription> {
return subscriptionModel.getSubscriptions(userId)
}
fun getSubscriptions(@UserIdParameter @RequestParam userId: String): List<Subscription> =
subscriptionModel.getSubscriptions(userId)

@PostMapping("/subscriptions")
@ResponseStatus(HttpStatus.CREATED)
Expand All @@ -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)
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Subscription> {
return SubscriptionEntity.find {
SubscriptionTable.userId eq userId
}.map {
it.toSubscription()
}
fun getSubscriptions(userId: String): List<Subscription> = SubscriptionEntity.find {
SubscriptionTable.userId eq userId
}.map {
it.toSubscription()
}

fun postSubscriptions(request: SubscriptionRequest, userId: String): Subscription {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AggregatedData>,
val info: LapisInfo,
) : LapisResponse
data class LapisAggregatedResponse(val data: List<AggregatedData>, 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
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ class TriggerEvaluator(
)
}

private fun additionalFilters(organism: String): Map<String, String> {
return dashboardsConfig.getOrganismConfig(organism).lapis.additionalFilters ?: emptyMap()
}
private fun additionalFilters(organism: String): Map<String, String> =
dashboardsConfig.getOrganismConfig(organism).lapis.additionalFilters ?: emptyMap()
}

private interface TriggerComputation {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("/"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -261,13 +259,12 @@ class CountTriggerEvaluatorTest(
assertThat(exception.message, containsString("Failed to deserialize error response from LAPIS"))
}

private fun isEvaluationErrorWith(message: Matcher<String>, statusCode: Int): Matcher<TriggerEvaluationResult> {
return allOf(
private fun isEvaluationErrorWith(message: Matcher<String>, statusCode: Int): Matcher<TriggerEvaluationResult> =
allOf(
instanceOf(TriggerEvaluationResult.EvaluationError::class.java),
hasProperty("message", message),
hasProperty("statusCode", `is`(statusCode)),
)
}

private fun requestingAggregatedDataWith(organism: String, body: String): HttpRequest? = request()
.withMethod("POST")
Expand Down
Loading
Loading