Skip to content
Open
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
28 changes: 27 additions & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,40 @@ You have to provide config information to the backend:
* Dashboards configuration, e.g. the LAPIS instances of the organisms.
We have profiles available that only need to be activated via `spring.profiles.active`.
* Database connection configuration: values need to be passed via [external properties](https://docs.spring.io/spring-boot/reference/features/external-config.html).
For local development, we have a `local-db` profile available.
For local development, we have a `local-db` profile available.
You can also check that for required properties.

### Start local database

Start the local PostgreSQL database using Docker Compose:

```bash
docker-compose -f docker-compose.dev.yaml up -d
```

Stop the database:

```bash
docker-compose -f docker-compose.dev.yaml down
```

Stop and remove data volumes:

```bash
docker-compose -f docker-compose.dev.yaml down -v
```

### Run the backend

To run the backend locally, you can use the following command:
```bash
./gradlew bootRun --args='--spring.profiles.active=local-db,dashboards-prod'
```

The backend will be available at:
- Base URL: `http://localhost:8080`
- Swagger UI: `http://localhost:8080/swagger-ui/index.html`

Run tests:

```bash
Expand Down
20 changes: 20 additions & 0 deletions backend/docker-compose.dev.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a Docker compose file at the root with a Postgres - would that also do the job?

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
postgres:
image: postgres:16-alpine
container_name: dashboards-backend-db
environment:
POSTGRES_DB: dashboards-backend-db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: unsecure
ports:
- "9022:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

volumes:
postgres_data:
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.genspectrum.dashboardsbackend.api

import io.swagger.v3.oas.annotations.media.Schema

@Schema(
description = "A collection of variants",
example = """
{
"id": 1,
"name": "My Collection",
"ownedBy": "user123",
"organism": "covid",
"description": "A collection of interesting variants",
"variants": []
}
""",
)
data class Collection(
val id: Long,
val name: String,
val ownedBy: String,
val organism: String,
val description: String?,
val variants: List<Variant>,
)

@Schema(
description = "Request to create a collection",
example = """
{
"name": "My Collection",
"organism": "covid",
"description": "A collection of interesting variants",
"variants": [
{
"type": "query",
"name": "BA.2 in USA",
"description": "BA.2 lineage cases in USA",
"countQuery": "country='USA' & lineage='BA.2'",
"coverageQuery": "country='USA'"
}
]
}
""",
)
data class CollectionRequest(
val name: String,
val organism: String,
val description: String? = null,
val variants: List<VariantRequest>,
)

@Schema(
description = "Request to update a collection",
example = """
{
"name": "Updated Collection Name",
"description": "Updated description",
"variants": [
{
"type": "query",
"id": 1,
"name": "BA.2 in USA",
"description": "BA.2 lineage cases in USA",
"countQuery": "country='USA' & lineage='BA.2'",
"coverageQuery": "country='USA'"
},
{
"type": "query",
"name": "New Variant Without ID",
"countQuery": "country='Germany'"
}
]
}
""",
)
data class CollectionUpdate(
val name: String? = null,
val description: String? = null,
val variants: List<VariantUpdate>? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.genspectrum.dashboardsbackend.api

import com.fasterxml.jackson.annotation.JsonAnyGetter
import com.fasterxml.jackson.annotation.JsonAnySetter
import com.fasterxml.jackson.annotation.JsonIgnore

/**
* A JSON object with mutation lists (keys: aaMutations, nucMutations, ...)
* as well as lineage filtering (keys are defined by the organism config)
*/
data class MutationListDefinition(
val aaMutations: List<String>? = null,
val nucMutations: List<String>? = null,
val aaInsertions: List<String>? = null,
val nucInsertions: List<String>? = null,
) {
@JsonIgnore
private val lineageFiltersInternal: MutableMap<String, String> = mutableMapOf()

val lineageFilters: Map<String, String>
get() = lineageFiltersInternal

@get:JsonAnyGetter
val additionalProperties: Map<String, String>
get() = lineageFiltersInternal

@JsonAnySetter
fun put(key: String, value: Any) {
if (key !in KNOWN_FIELDS && value is String) {
lineageFiltersInternal[key] = value
}
}
Comment on lines +27 to +32

companion object {
private val KNOWN_FIELDS = setOf("aaMutations", "nucMutations", "aaInsertions", "nucInsertions")

fun create(
aaMutations: List<String>? = null,
nucMutations: List<String>? = null,
aaInsertions: List<String>? = null,
nucInsertions: List<String>? = null,
lineageFilters: Map<String, String> = emptyMap(),
): MutationListDefinition {
val definition = MutationListDefinition(aaMutations, nucMutations, aaInsertions, nucInsertions)
lineageFilters.forEach { (key, value) ->
definition.put(key, value)
}
return definition
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package org.genspectrum.dashboardsbackend.api

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import io.swagger.v3.oas.annotations.media.Schema
import org.genspectrum.dashboardsbackend.api.Variant.MutationListVariant
import org.genspectrum.dashboardsbackend.api.Variant.QueryVariant

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type",
)
@JsonSubTypes(
JsonSubTypes.Type(value = QueryVariant::class, name = "query"),
JsonSubTypes.Type(value = MutationListVariant::class, name = "mutationList"),
)
@Schema(
description = "Base interface for different variant types",
)
sealed interface Variant {
val id: Long
val collectionId: Long

enum class QueryVariantType {
@JsonProperty("query")
QUERY,
}

enum class MutationListVariantType {
@JsonProperty("mutationList")
MUTATION_LIST,
}

@Schema(
description = "A variant defined by LAPIS queries",
example = """
{
"type": "query",
"id": 1,
"collectionId": 2,
"name": "BA.2 in USA",
"description": "BA.2 lineage cases in USA",
"countQuery": "country='USA' & lineage='BA.2'",
"coverageQuery": "country='USA'"
}
""",
)
data class QueryVariant @JsonCreator constructor(
override val id: Long,
override val collectionId: Long,
val name: String,
val description: String?,
val countQuery: String,
val coverageQuery: String? = null,
) : Variant {
val type: QueryVariantType = QueryVariantType.QUERY
}

@Schema(
description = "A variant defined by a list of mutations",
example = """
{
"type": "mutationList",
"id": 1,
"collectionId": 2,
"name": "Omicron mutations",
"description": "Key mutations for Omicron",
"mutationList": {
"aaMutations": ["S:N501Y", "S:E484K", "S:K417N"]
}
}
""",
)
data class MutationListVariant @JsonCreator constructor(
override val id: Long,
override val collectionId: Long,
val name: String,
val description: String?,
val mutationList: MutationListDefinition,
) : Variant {
val type: MutationListVariantType = MutationListVariantType.MUTATION_LIST
}
}

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type",
)
@JsonSubTypes(
JsonSubTypes.Type(value = VariantRequest.QueryVariantRequest::class, name = "query"),
JsonSubTypes.Type(value = VariantRequest.MutationListVariantRequest::class, name = "mutationList"),
)
@Schema(
description = "Request to create a variant",
)
sealed interface VariantRequest {
@Schema(
description = "Request to create a query variant",
example = """
{
"type": "query",
"name": "BA.2 in USA",
"description": "BA.2 lineage cases in USA",
"countQuery": "country='USA' & lineage='BA.2'",
"coverageQuery": "country='USA'"
}
""",
)
data class QueryVariantRequest(
val name: String,
val description: String? = null,
val countQuery: String,
val coverageQuery: String? = null,
) : VariantRequest

@Schema(
description = "Request to create a mutation list variant",
example = """
{
"type": "mutationList",
"name": "Omicron mutations",
"description": "Key mutations for Omicron",
"mutationList": {
"aaMutations": ["S:N501Y", "S:E484K", "S:K417N"]
}
}
""",
)
data class MutationListVariantRequest(
val name: String,
val description: String? = null,
val mutationList: MutationListDefinition,
) : VariantRequest
}

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type",
)
@JsonSubTypes(
JsonSubTypes.Type(value = VariantUpdate.QueryVariantUpdate::class, name = "query"),
JsonSubTypes.Type(value = VariantUpdate.MutationListVariantUpdate::class, name = "mutationList"),
)
@Schema(
description = "Request to update or create a variant",
)
sealed interface VariantUpdate {
val id: Long?

@Schema(
description = "Request to update or create a query variant",
example = """
{
"type": "query",
"id": 1,
"name": "BA.2 in USA",
"description": "BA.2 lineage cases in USA",
"countQuery": "country='USA' & lineage='BA.2'",
"coverageQuery": "country='USA'"
}
""",
)
data class QueryVariantUpdate(
override val id: Long? = null,
val name: String,
val description: String? = null,
val countQuery: String,
val coverageQuery: String? = null,
) : VariantUpdate

@Schema(
description = "Request to update or create a mutation list variant",
example = """
{
"type": "mutationList",
"id": 1,
"name": "Omicron mutations",
"description": "Key mutations for Omicron",
"mutationList": {
"aaMutations": ["S:N501Y", "S:E484K", "S:K417N"]
}
}
""",
)
data class MutationListVariantUpdate(
override val id: Long? = null,
val name: String,
val description: String? = null,
val mutationList: MutationListDefinition,
) : VariantUpdate
}
Loading
Loading