feat(backend,website): proxy LAPIS calls through backend with structured query API (POC)#6498
Open
fhennig wants to merge 22 commits into
Open
feat(backend,website): proxy LAPIS calls through backend with structured query API (POC)#6498fhennig wants to merge 22 commits into
fhennig wants to merge 22 commits into
Conversation
Adds a generic wildcard proxy at `/{organism}/lapis/**` that forwards
GET and POST requests to the upstream LAPIS service, enabling the
website to route all LAPIS calls through the backend instead of hitting
LAPIS directly.
Changes:
- Add `lapisUrl` field to `InstanceConfig` so each organism config
carries its upstream LAPIS URL
- New `LapisProxyController` using Java 21 `HttpClient`, streaming
response via `StreamingResponseBody` (handles large FASTA downloads
without memory buffering), strips `Authorization` and hop-by-hop
headers
- `SecurityConfig`: permit `/*/lapis/**` for GET + POST, disable CSRF
- `WebConfig`: exclude `/*/lapis/**` from `ReadOnlyModeInterceptor`
(LAPIS POSTs are reads, not writes)
- Test fixture JSONs: add `lapisUrl` to each organism entry
- Helm `_common-metadata.tpl`: inject `lapisUrl` per organism into
backend config using internal K8s service name
- Helm `_urls.tpl`: add `loculus.generateInternalLapisUrlsViaBackend`
helper that builds LAPIS URLs routed through the backend proxy
- `loculus-website-config.yaml`: serverSide `lapisUrls` now point at
the backend proxy (`backend-service:8079/{organism}/lapis`)
- `vitest.setup.ts`: test config `lapisUrls` updated to match the
backend-proxy URL pattern
Website TypeScript changes (remove `lapisUrls` from `ServiceUrls` type,
rewrite `getLapisUrl()`) are tracked in POC_PLAN.md and will follow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`lapisUrls` is no longer injected into `runtime_config.json` — the
LAPIS URL for any organism is now derived as
`{backendUrl}/{organism}/lapis`, matching the backend proxy routes
added in the previous commit.
Changes:
- `runtimeConfig.ts`: remove `lapisUrls` field from `serviceUrls` Zod
schema
- `config.ts`: rewrite `getLapisUrl()` to compute from `backendUrl`
instead of reading a config map; no more runtime error on unknown
organisms
- `GroupPage.tsx`: replace `clientConfig.lapisUrls[key]` (with null
guard) with `getLapisUrl(clientConfig, key)`
- `SeqSetRecordsTableWithMetadata.tsx`: add `organisms: string[]` prop;
iterate that list and compute each URL via `getLapisUrl` — removes the
hacky `lapisUrls` dummy-organism filter
- `SeqSetItem.tsx`: thread `organisms` prop through to
`SeqSetRecordsTableWithMetadata`
- `seqsets/[seqSetId].[version].astro`: pass
`Object.keys(websiteConfig.organisms)` as `organisms`
- `api-documentation/index.astro`: compute LAPIS doc links from
`backendUrl` + organism keys (already available as `organismKeys`)
- `loculus-info/index.ts`: compute `lapis` response field from
`backendUrl` + organism keys instead of reading `lapisUrls`
- `_common-metadata.tpl`: remove `lapisUrls` from `publicRuntimeConfig`
- `loculus-website-config.yaml`: remove `lapisUrls` from serverSide
block (URL is now computed client-side)
- test files: replace `testConfig.serverSide.lapisUrls[testOrganism]`
with `getLapisUrl(testConfig.serverSide, testOrganism)`
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d is local
When `disableBackend=true` (i.e. the backend runs locally outside the
cluster), the generated backend_config.json must point lapisUrl at
the port-forwarded LAPIS address (http://localhost:8080/{organism})
rather than the K8s-internal service name, which is unreachable from
outside the cluster.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds /query/{organism}/{versionGroup}/... endpoints as a typed alternative
to the generic LAPIS proxy. The versionGroup segment ("current" or
"allVersions") controls whether versionStatus=LATEST_VERSION is injected
into the forwarded LAPIS request body.
Extracts shared HTTP proxy logic into LapisProxyService so both
LapisProxyController and QueryController can reuse it without duplication.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Route metadata/aggregated/mutations/sequences queries through the new
structured /query/{organism}/{versionGroup}/... backend endpoints instead
of the generic LAPIS proxy, so the backend can inject version filters
automatically per path rather than requiring callers to supply them.
- Add getQueryUrl() to config.ts for building query endpoint URLs
- New queryApi.ts with Zodios endpoint definitions for QueryController paths
- LapisClient: add currentUrl/allVersionsUrl fields, requestFull() helper,
migrate 6 methods (getSequenceEntryVersionDetails, getLatestAccessionVersion,
getAllSequenceEntryHistoryForAccession, getSequenceMutations nucleotide,
getUnalignedSequences); remove explicit versionStatus from getLatestAccessionVersion
- serviceHooks: optional queryCurrentUrl param routes useAggregated/useDetails
through QueryController when provided, falls back to proxy otherwise
- SearchFullUI: compute and pass queryCurrentUrl to lapisClientHooks
- GroupPage: use getQueryUrl for sequence count and time series queries
- SeqSetRecordsTableWithMetadata: use allVersions for versioned accessions,
current for bare accessions
- Update vitest mocks for new endpoint paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…te download button
GET /query/{organism}/{versionGroup}/{path} endpoints mirror the existing
POSTs but accept query string parameters and inject versionStatus as a
query param (for the current group) rather than into a JSON body. This
enables browser-navigable download URLs to go through the structured
endpoint instead of the raw LAPIS proxy.
Backend:
- Add prepareQuery() that appends versionStatus=LATEST_VERSION for current
- Add get() helper mirroring post()
- Add GET endpoints: metadata, sequences, sequences/{segment},
sequencesAligned, sequencesAligned/{referenceName}, translations/{gene}
- Remove @hidden annotation so all QueryController endpoints show in Swagger
Website:
- DownloadUrlGenerator: use downloadBaseUrl + new paths (/metadata,
/sequences, /sequencesAligned, /translations/{gene}) instead of the
old /sample/... LAPIS proxy paths
- SearchFullUI: pass queryCurrentUrl to DownloadUrlGenerator
- Update DownloadDialog tests to match new endpoint paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s' groups /query/** now requires a valid JWT. Each request has the user's group IDs injected as a LAPIS filter so users only see sequences belonging to their own groups. Super users bypass the filter and see all data. Users with no group memberships receive a sentinel groupId of -1, guaranteeing an empty result set. The website passes the session access token to the query Zodios client so requests are properly authenticated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
f76b251 to
afb792a
Compare
afb792a to
c7d11fc
Compare
…file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch is a proof-of-concept for routing all LAPIS data queries through the Loculus backend instead of having the website call LAPIS directly.
Motivation:
What changed
Backend
LapisProxyController— a wildcard proxy that forwards/{organism}/lapis/**requests to the upstream LAPIS service for that organismQueryController— a new structured query API at/query/{organism}/{versionGroup}/...that mirrors LAPIS data endpoints, adding backend-owned paths and authcurrent= latest sequence versions onlyallVersions= includes historical versions / exact accession versionsLapisAccessFilter— restricts query endpoints so users only access their own groups' data (demo of what group-based auth looks like)QueryControlleralongside POST, download button migrated to use themQueryOpenApiCustomizerto generate per-organism OpenAPI docsWebsite
lapisUrlsremoved fromruntime_config.json— no longer needed since all traffic routes through the backendlapisClient.tsto a newqueryApi.tsservice pointing at the backendQueryControllerDocs
api-refactoring-docs/lapis-proxy.md— arc42-style architecture documentation (single file, sections 1–12 + 20)POC_PLAN.md— full implementation plan with call inventory, routing diagrams, and stepsArchitecture
🚀 Preview: https://feat-lapis-proxy.loculus.org