Skip to content

chore: update prod from main#1215

Merged
fhennig merged 11 commits into
prodfrom
main
May 28, 2026
Merged

chore: update prod from main#1215
fhennig merged 11 commits into
prodfrom
main

Conversation

@github-actions
Copy link
Copy Markdown

This pull request updates the prod branch with the latest changes from the main branch.

Make sure to merge this creating a merge commit.

Do not squash-merge this PR. Do not rebase and merge.

Bumps [uuid](https://github.com/uuidjs/uuid) from 11.1.0 to 14.0.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](uuidjs/uuid@v11.1.0...v14.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboards Ready Ready Preview, Comment May 28, 2026 6:32am

Request Review

…es (#1210)

Bumps the minorandpatch group in /backend with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [org.jetbrains.exposed:exposed-spring-boot-starter](https://github.com/JetBrains/Exposed) | `1.2.0` | `1.3.0` |
| [org.jetbrains.exposed:exposed-json](https://github.com/JetBrains/Exposed) | `1.2.0` | `1.3.0` |
| [org.jetbrains.exposed:exposed-jdbc](https://github.com/JetBrains/Exposed) | `1.2.0` | `1.3.0` |
| [org.jetbrains.exposed:exposed-kotlin-datetime](https://github.com/JetBrains/Exposed) | `1.2.0` | `1.3.0` |
| [gradle-wrapper](https://github.com/gradle/gradle) | `9.5.0` | `9.5.1` |


Updates `org.jetbrains.exposed:exposed-spring-boot-starter` from 1.2.0 to 1.3.0
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](JetBrains/Exposed@1.2.0...1.3.0)

Updates `org.jetbrains.exposed:exposed-json` from 1.2.0 to 1.3.0
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](JetBrains/Exposed@1.2.0...1.3.0)

Updates `org.jetbrains.exposed:exposed-jdbc` from 1.2.0 to 1.3.0
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](JetBrains/Exposed@1.2.0...1.3.0)

Updates `org.jetbrains.exposed:exposed-kotlin-datetime` from 1.2.0 to 1.3.0
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](JetBrains/Exposed@1.2.0...1.3.0)

Updates `gradle-wrapper` from 9.5.0 to 9.5.1
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](gradle/gradle@v9.5.0...v9.5.1)

---
updated-dependencies:
- dependency-name: org.jetbrains.exposed:exposed-spring-boot-starter
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minorandpatch
- dependency-name: org.jetbrains.exposed:exposed-json
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minorandpatch
- dependency-name: org.jetbrains.exposed:exposed-jdbc
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minorandpatch
- dependency-name: org.jetbrains.exposed:exposed-kotlin-datetime
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minorandpatch
- dependency-name: gradle-wrapper
  dependency-version: 9.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minorandpatch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… IDs, use in website code (#1201)

* feat(backend): add users table with GitHub sync and internal IDs

Adds a users_table (BIGSERIAL PK, nullable github_id) with upsert via
POST /users/sync and lookup via GET /users/{id}. Migrates owned_by and
user_id columns in collections and subscriptions from GitHub ID strings
to BIGINT foreign keys referencing the new table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* format

* refactor(backend): extract now() utility, add PublicUser DTO for GET /users/{id}

Extracts the shared now() instant helper into util/InstantProvider.kt to
avoid duplication across models. Adds PublicUser DTO (id + name only) so
the public GET /users/{id} endpoint does not expose email.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend): apply linter formatting to CollectionModel and UsersControllerTest

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend): restore nullValue import in UsersControllerTest

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e): sync user before creating collections in tests

Collections now require a Long internal user ID. Tests previously passed
string GitHub IDs directly, causing 400/500 errors. Now each test suite
syncs the user via POST /users/sync first and uses the returned internal ID.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* feat(backend): validate user existence before creating or listing subscriptions/collections

Raises a 404 NotFoundException when an unknown userId is passed to
postSubscriptions, getSubscriptions, createCollection, or getCollections,
instead of silently creating orphaned records or returning empty results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend): recreate indexes on owned_by and user_id after bigint migration

V1.3 drops the old varchar columns (and their indexes) and renames the
new bigint FK columns into place. PostgreSQL does not auto-index FK
referencing columns, so the indexes must be recreated explicitly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(e2e): extract shared backend setup helpers for collection tests

Moves repeated user sync and collection create/delete API calls into
a backendClient.ts helper, reducing boilerplate in beforeAll/afterAll
across collectionForm and collectionDetail specs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): sync user with backend on login, use internal user ID (#1202)

* feat(website): sync user with backend on login, use internal user ID for ownership

On GitHub login, mapProfileToUser calls POST /users/sync to upsert the user
in the backend and stores the returned internal Long ID as internalUserId in
the better-auth session (stateless JWE cookie). This replaces the previous
use of the GitHub ID for ownership checks on collections and subscriptions.

- auth.ts: async mapProfileToUser syncs user, adds internalUserId additional field
- authMiddleware.ts: reads internalUserId from session user
- backendProxy.ts: forwards internalUserId as userId query param instead of githubId
- Collection.ts: ownedBy changed from string to number to match backend Long
- PublicUser.ts: new Zod schema for GET /users/{id} response
- backendService.ts: adds getUser() to resolve owner names
- pages/api/users/[id].ts: new proxy route for public user lookup
- collection detail/edit pages: use internalUserId for ownership, display owner name
- E2E tests: sync user via POST /users/sync in beforeAll to get internal ID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

fix(website): fix lint errors in auth.ts and authMiddleware.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

refactor(website): rename internalUserId to gsUserId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

fix(website): make login fail if backend user sync fails, simplify gsUserId handling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

fix(website): make ownerName non-optional, simplify collection detail page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

refactor(website): simplify gsUserId assignment in authMiddleware

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

foo

* fix(website): fix collection edit button never showing for owners

better-auth serialises all session fields as strings in the JWE cookie
regardless of the declared field type, so comparing gsUserId (string)
against collection.ownedBy (number) with === always returned false.
Fix by coercing gsUserId to a number in the middleware, and align the
e2e auth cookie helper to use the real backend user ID instead of a
hardcoded '1'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update website/src/auth.ts

Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com>

* fix(website): cast profile.name to nullable to satisfy linter

better-auth types profile.name as string, but GitHub can return null
at runtime. Cast to string | null | undefined so the ?? fallback to
profile.login is not flagged as unnecessary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(website): suppress no-unnecessary-condition for profile.name nullability

The linter sees through `as` casts, so the rule fires even with the
cast to string | null | undefined. Use eslint-disable to document that
the ?? chain is intentional — GitHub can return null for name at runtime
despite the better-auth types declaring it as string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com>
… AdvancedQueryFilter (#1197)

* feat(website): replace plain textarea in VariantEditor with validated AdvancedQueryFilter

The advanced query mode in the collection variant editor now uses the
same validated query input as the baseline filter, restricting users to
lineage fields only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(website): wrap VariantEditor with QueryClientProvider in browser spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): set tooltip-top on AdvancedQueryFilter in VariantEditor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…eaders (#1219)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backend): add script to seed resistance mutation collections

Creates three COVID collections (3CLpro, RdRp, Spike mAb) from the
resistance mutation sets defined in resistanceMutations.ts, each with
one filterObject variant per individual mutation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* initial implementation

* feat(backend): support session token auth in resistance collections script

When SESSION_TOKEN is set, the script hits the website proxy at /api/collections
(which injects the user ID from the session) and passes the auth cookie.
Automatically uses the __Secure- cookie prefix for HTTPS targets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): source resistance mutations from backend collections

Instead of hard-coding mutation lists in the codebase, fetch them at
runtime from the backend collections API (one collection per set:
3CLpro, RdRp, Spike). The seed script now uses mature protein names
(offset-adjusted) as variant names, so the name calculation is no
longer needed in client code. Offset and matureName are removed from
the frontend entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(website): simplify resistance mutation types and hook

useResistanceMutationSets now returns ResistanceData directly
(mutationAnnotations + displayMutationsBySet), removing the intermediate
ResistanceMutationSet type and resistanceMutations.ts entirely.
The selector/filter chain now takes plain string[] for set names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(backend): remove resistance mutation collection seeding script

The example data seeder on feat/example-data-seeder handles this now.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): resolve wasap config server-side to support staging overrides

Add wastewaterOrganismStagingConfigs with staging resistance collection IDs
(1/2/3 for 3CLpro/RdRp/Spike). Wasap.astro now selects prod vs staging config
and passes it directly to WasapPage, removing the env dependency from React.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(website): remove resolved TODOs and set 1h stale time for resistance collections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(website): set correct prod resistance collection IDs (1, 2, 3)

IDs confirmed at https://genspectrum.org/api/collections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(website): update test fixture collection IDs to match prod (1, 2, 3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(website): remove parallel fetching TODO from WasapPage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* format

* refactor(website): fetch resistance data server-side in Wasap.astro

Moves resistance collection fetching from a React Query hook to the Astro
SSR layer, so data is available before the page reaches the browser.
WasapPage now receives resistanceData as a prop instead of fetching it
client-side. Removes useResistanceMutationSets hook; logic lives in
resistanceData.ts as a plain async function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix typo

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix log

* foo

* use getErrorLogMessage

* resistance set name cannot be null

* handle load failure for reistance sets in page state selector

* Add new test

* foo

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
#1218)

* feat(backend, website): add API key authentication for collections API (#1200)

Implements one-key-per-user API key auth as an alternative to OAuth sessions,
allowing programmatic/script access via Authorization: Bearer <key> header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(website): sort imports in api-keys page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(backend, website): add tests for API key auth; fix linting issues

Backend: ApiKeyClient + ApiKeyControllerTest covering all CRUD endpoints
and the internal validate endpoint.

Frontend: Playwright e2e test for the full lifecycle — generate key via UI,
create a collection with Bearer auth, verify it, revoke the key, confirm
the revoked key returns 401.

Also fixes ktlint issues: rename package api_key → apikey, wrap long
description line in ApiKeyController; fix eslint naming-convention
violations in authMiddleware and apiKey.spec.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(backend): add index on api_keys_table.key_hash for fast lookup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* re-use SecureRandom

* review(backend, website): address PR feedback on API key feature

Enable pgcrypto extension in migration; use BackendError instanceof check instead of structural cast for 404 handling in ApiKeyManager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend): handle race condition in generateApiKey

Catch the unique constraint violation (SQLState 23505) on insert so concurrent requests for the same user get a 409 instead of a 500. The pre-check remains as a fast path for the common case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend): use DataIntegrityViolationException in generateApiKey

Simpler and more idiomatic than catching SQLException and checking SQLState.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend): explicit UTF-8 charset in sha256

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(backend): align ApiKeyControllerTest names with GIVEN/WHEN/THEN convention

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): rename api-keys page to api-key; add link from user menu

The page is singular since a user can only have one key. Also adds an
"API Key" link to the user dropdown menu.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): show generated API key inline with copy-to-clipboard button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): inline copy button inside generated key display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(website): tighten generatedKey locator to avoid matching inline code in description

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): add API Key entry to hamburger menu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(website): use data-testid instead of CSS class selector for generated API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ons endpoint (#1224)

* feat(backend): add variantCount field and includeVariants param to collections endpoint

GET /collections now includes variantCount on every response, and accepts
includeVariants=false (default) to omit the full variant list for lighter
list-view requests. GET /collections/{id} always returns variants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(website): adapt collections overview to use variantCount and CollectionSummary type

- Split collection schema into CollectionSummary (list, no variants) and
  Collection (single/create/update, with variants)
- Rename getCollections -> getCollectionSummaries for the lightweight endpoint;
  add getCollections with includeVariants=true returning full Collection[]
- CollectionsOverview uses getCollectionSummaries and reads variantCount directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf(backend): use COUNT query instead of full variant load when includeVariants=false

When variants are not requested, replace the batch fetch of all variant
rows with a single COUNT(*) GROUP BY collection_id query. This avoids
loading variant data (including large JSONB filter_object columns) for
every collection, which was causing >7s response times at scale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* mark variant Count as int and non-negative.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* update example

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* use joins to get variants for collections

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…1220)

* feat(backend): add system user auto-creation from application YAML

Adds SystemUserConfig to DashboardsConfig, upsertApiKey() to ApiKeyModel,
and a SystemUserInitializer ApplicationRunner that creates/updates a bot
account with an optional deterministic API key on startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend): use unique keys in SystemUserInitializerTest to avoid cross-test collision

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(backend): enforce minimum length of 32 for systemUser.apiKey

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(backend): mask apiKey in SystemUserConfig.toString and simplify log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(backend): add test for system user created without apiKey

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(backend): rename tests to follow GIVEN...WHEN...THEN convention

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ging (#1230)

Wires DASHBOARDS_SYSTEM_USER_API_KEY env var; startup fails if unset.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@fhennig fhennig merged commit bb815b3 into prod May 28, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant