Skip to content

Commit 458b587

Browse files
feat(backend): add Scalar API reference
1 parent 6a2e02b commit 458b587

5 files changed

Lines changed: 48 additions & 65 deletions

File tree

backend/src/main/kotlin/org/loculus/backend/config/BackendSpringConfig.kt

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class BackendSpringConfig {
175175
return@OperationCustomizer operation
176176
}
177177

178-
val endpointDocs = QUERY_ENDPOINT_DOCS[handlerMethod.method.name]
178+
val endpointDocs = queryEndpointDocs(handlerMethod.method.name)
179179
operation.tags = listOf("Query")
180180
operation.summary = endpointDocs?.summary ?: operation.summary
181181
operation.description = endpointDocs?.description ?: operation.description
@@ -206,6 +206,9 @@ class BackendSpringConfig {
206206
private companion object {
207207
data class QueryEndpointDocs(val summary: String, val description: String)
208208

209+
fun queryEndpointDocs(methodName: String) =
210+
QUERY_ENDPOINT_DOCS[methodName] ?: methodName.removeSuffix("Get").let { QUERY_ENDPOINT_DOCS[it] }
211+
209212
val QUERY_ENDPOINT_DOCS = mapOf(
210213
"metadata" to QueryEndpointDocs(
211214
"Query metadata",
@@ -267,66 +270,6 @@ class BackendSpringConfig {
267270
"Aggregate amino acid mutations",
268271
"Return aggregated amino acid mutations for one gene.",
269272
),
270-
"metadataGet" to QueryEndpointDocs(
271-
"Download metadata",
272-
"Download metadata rows for sequence entries visible to the authenticated user.",
273-
),
274-
"aggregatedGet" to QueryEndpointDocs(
275-
"Download aggregated metadata",
276-
"Download aggregated metadata counts for visible sequence entries.",
277-
),
278-
"sequencesGet" to QueryEndpointDocs(
279-
"Download unaligned nucleotide sequences",
280-
"Download unaligned nucleotide sequences for visible sequence entries.",
281-
),
282-
"sequencesForSegmentGet" to QueryEndpointDocs(
283-
"Download unaligned nucleotide sequences by segment",
284-
"Download unaligned nucleotide sequences for one segment.",
285-
),
286-
"sequencesAlignedGet" to QueryEndpointDocs(
287-
"Download aligned nucleotide sequences",
288-
"Download aligned nucleotide sequences for visible sequence entries.",
289-
),
290-
"sequencesAlignedForSegmentGet" to QueryEndpointDocs(
291-
"Download aligned nucleotide sequences by reference",
292-
"Download aligned nucleotide sequences for one reference.",
293-
),
294-
"sequencesAlignedMutationsGet" to QueryEndpointDocs(
295-
"Download nucleotide mutations",
296-
"Download nucleotide mutation records for visible sequence entries.",
297-
),
298-
"sequencesAlignedInsertionsGet" to QueryEndpointDocs(
299-
"Download nucleotide insertions",
300-
"Download nucleotide insertion records for visible sequence entries.",
301-
),
302-
"sequencesAlignedAggregatedMutationsGet" to QueryEndpointDocs(
303-
"Download aggregated nucleotide mutations",
304-
"Download aggregated nucleotide mutations for visible sequence entries.",
305-
),
306-
"sequencesAlignedForSegmentMutationsGet" to QueryEndpointDocs(
307-
"Download nucleotide mutations by reference",
308-
"Download nucleotide mutation records for one reference.",
309-
),
310-
"sequencesAlignedForSegmentAggregatedMutationsGet" to QueryEndpointDocs(
311-
"Download aggregated nucleotide mutations by reference",
312-
"Download aggregated nucleotide mutations for one reference.",
313-
),
314-
"translationsGet" to QueryEndpointDocs(
315-
"Download aligned amino acid sequences",
316-
"Download aligned amino acid sequences for one gene.",
317-
),
318-
"translationsMutationsGet" to QueryEndpointDocs(
319-
"Download amino acid mutations",
320-
"Download amino acid mutation records for visible sequence entries.",
321-
),
322-
"translationsInsertionsGet" to QueryEndpointDocs(
323-
"Download amino acid insertions",
324-
"Download amino acid insertion records for visible sequence entries.",
325-
),
326-
"translationsAggregatedMutationsGet" to QueryEndpointDocs(
327-
"Download aggregated amino acid mutations",
328-
"Download aggregated amino acid mutations for one gene.",
329-
),
330273
)
331274
}
332275
}

backend/src/main/kotlin/org/loculus/backend/config/SecurityConfig.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class SecurityConfig {
8888
"/api-docs**",
8989
"/api-docs/**",
9090
"/swagger-ui/**",
91+
"/scalar-api-reference",
9192
).permitAll()
9293
auth.requestMatchers(HttpMethod.GET, *getEndpointsThatArePublic).permitAll()
9394
auth.requestMatchers(HttpMethod.HEAD, *headEndpointsThatArePublic).permitAll()

backend/src/main/kotlin/org/loculus/backend/controller/InfoController.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,28 @@ class InfoController(@Value("\${${BackendSpringProperty.DEBUG_MODE}}") private v
2626
<body>
2727
<h1>Welcome to the $PROJECT_NAME Backend</h1>
2828
<a href="${request.requestURL}swagger-ui/index.html">Visit our swagger-ui</a>
29+
<a href="${request.requestURL}scalar-api-reference">Visit our Scalar API reference</a>
30+
</body>
31+
</html>
32+
""".trimIndent()
33+
34+
@RequestMapping("/scalar-api-reference", produces = [MediaType.TEXT_HTML_VALUE])
35+
fun scalarApiReference() = """
36+
<!DOCTYPE html>
37+
<html lang="en">
38+
<head>
39+
<meta charset="UTF-8">
40+
<meta name="viewport" content="width=device-width, initial-scale=1">
41+
<title>$PROJECT_NAME Scalar API Reference</title>
42+
</head>
43+
<body>
44+
<div id="app"></div>
45+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
46+
<script>
47+
Scalar.createApiReference('#app', {
48+
url: '/api-docs'
49+
})
50+
</script>
2951
</body>
3052
</html>
3153
""".trimIndent()
@@ -39,6 +61,6 @@ class InfoController(@Value("\${${BackendSpringProperty.DEBUG_MODE}}") private v
3961
data class Info(
4062
val name: String = "$PROJECT_NAME backend",
4163
val status: String = "Healthy",
42-
val documentation: String = "visit /swagger-ui/index.html",
64+
val documentation: String = "visit /swagger-ui/index.html or /scalar-api-reference",
4365
val isInDebugMode: Boolean,
4466
)

backend/src/test/kotlin/org/loculus/backend/SwaggerUiTest.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
1010
import org.junit.jupiter.api.Test
1111
import org.springframework.beans.factory.annotation.Autowired
1212
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
13+
import org.springframework.http.MediaType
1314
import org.springframework.test.web.servlet.MockMvc
1415
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
1516
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
@@ -28,6 +29,15 @@ class SwaggerUiTest(@Autowired val mockMvc: MockMvc) {
2829
.andExpect(content().string(containsString("Swagger UI")))
2930
}
3031

32+
@Test
33+
fun `Scalar API reference endpoint is reachable`() {
34+
mockMvc.perform(get("/scalar-api-reference"))
35+
.andExpect(status().isOk)
36+
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
37+
.andExpect(content().string(containsString("Scalar.createApiReference")))
38+
.andExpect(content().string(containsString("url: '/api-docs'")))
39+
}
40+
3141
@Test
3242
fun `JSON API docs are available`() {
3343
mockMvc.perform(get("/api-docs"))
@@ -71,6 +81,8 @@ class SwaggerUiTest(@Autowired val mockMvc: MockMvc) {
7181

7282
assertTrue(!paths.has("/query/{organism}/{versionGroup}/metadata"))
7383
assertEquals("Query metadata", metadataOperation.get("summary").asText())
84+
assertEquals(metadataOperation.get("summary").asText(), metadataGetOperation.get("summary").asText())
85+
assertEquals(metadataOperation.get("description").asText(), metadataGetOperation.get("description").asText())
7486
assertEquals(listOf("Query: dummyOrganism"), metadataOperation.get("tags").map { it.asText() })
7587
assertTrue(tagNames.contains("Query: dummyOrganism"))
7688
assertTrue(!tagNames.contains("Query"))

website/src/pages/api-documentation/index.astro

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,14 @@ const BUTTON_CLASS =
5959
<div class='mb-4'>
6060
Please note that Loculus is under continuous development and the endpoints are subject to change.
6161
</div>
62-
<a class={BUTTON_CLASS} href={clientConfig.backendUrl + '/swagger-ui/index.html'}>
63-
View backend API documentation
64-
</a>
62+
<div class='flex flex-wrap gap-3'>
63+
<a class={BUTTON_CLASS} href={clientConfig.backendUrl + '/swagger-ui/index.html'}>
64+
View backend API documentation (Swagger)
65+
</a>
66+
<a class={BUTTON_CLASS} href={clientConfig.backendUrl + '/scalar-api-reference'}>
67+
View backend API documentation (Scalar)
68+
</a>
69+
</div>
6570
<div class='mt-8'>
6671
<span class='font-medium'>URL of backend server:</span>
6772
<code>{clientConfig.backendUrl}</code>

0 commit comments

Comments
 (0)