feat(backend): add users table with GitHub sync and internal IDs#1201
feat(backend): add users table with GitHub sync and internal IDs#1201fhennig wants to merge 6 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR introduces a first-class users table with internal numeric IDs, adds endpoints to sync/fetch users, and migrates existing ownership fields in collections/subscriptions from GitHub ID strings to internal BIGINT user references. It also updates backend tests and website E2E tests to create/sync a user first and then use the internal user ID in requests.
Changes:
- Add
users_table(BIGSERIAL PK, nullablegithub_id) plus/users/syncupsert and/users/{id}public lookup. - Migrate
collections_table.owned_byandsubscriptions_table.user_idfromVARCHARGitHub IDs toBIGINTFKs referencingusers_table. - Update backend + website E2E tests/clients to work with internal
Longuser IDs.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| website/tests/collections/collectionForm.spec.ts | Sync a user via /users/sync and use internal user ID for collection create/delete in E2E. |
| website/tests/collections/collectionDetail.spec.ts | Same as above for collection detail E2E setup/teardown. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/UsersControllerTest.kt | New controller tests for user sync/get endpoints. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/UsersClient.kt | New MockMvc client helper for /users endpoints used by tests. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTriggerEvaluationTest.kt | Switch subscription tests to internal Long user IDs via UsersClient. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsControllerTest.kt | Switch subscription tests to internal Long user IDs via UsersClient. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsClient.kt | Update test client to accept Long user IDs. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/CollectionsPutTest.kt | Switch collections PUT tests to internal Long user IDs via UsersClient. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/CollectionsPostTest.kt | Switch collections POST tests to internal Long user IDs via UsersClient. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/CollectionsGetTest.kt | Switch collections GET tests to internal Long user IDs via UsersClient. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/CollectionsDeleteTest.kt | Switch collections DELETE tests to internal Long user IDs via UsersClient. |
| backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/CollectionsClient.kt | Update test client to accept Long user IDs. |
| backend/src/main/resources/db/migration/V1.3__add_users_table.sql | Add users table and migrate ownership columns to bigint FKs. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/util/InstantProvider.kt | Centralize millisecond-truncated “now” helper. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/user/UserTable.kt | Exposed table/entity for users_table plus mapping to API models. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/user/UserModel.kt | Sync/upsert and fetch logic for users. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/triggerevaluation/TriggerEvaluationModel.kt | Update trigger evaluation to use Long user IDs. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/subscription/SubscriptionTable.kt | Change user_id column type to long. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/subscription/SubscriptionModel.kt | Update subscription model APIs to accept Long user IDs. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/collection/CollectionTable.kt | Change owned_by column type to long. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/collection/CollectionModel.kt | Update collection model APIs to accept Long user IDs and use shared now(). |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/controller/UsersController.kt | New /users/sync and /users/{id} endpoints. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/controller/SubscriptionsController.kt | Change request param userId type to Long. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/controller/CollectionsController.kt | Change request param userId type to Long. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/User.kt | Add API models: User, UserSyncRequest, PublicUser. |
| backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/Collection.kt | Change ownedBy field type from String to Long. |
Comments suppressed due to low confidence (2)
backend/src/main/kotlin/org/genspectrum/dashboardsbackend/model/collection/CollectionModel.kt:90
- With the new FK from collections.owned_by -> users_table.id, creating a collection with an unknown userId will currently bubble up as an unexpected exception (500). Please validate that the user exists (and return 404/400) before inserting, or translate the FK violation into a client error so callers get an actionable response.
fun createCollection(request: CollectionRequest, userId: Long): Collection {
dashboardsConfig.validateIsValidOrganism(request.organism)
dashboardsConfig.validateCollectionsEnabled(request.organism)
val now = now()
val collectionEntity = CollectionEntity.new {
name = request.name
organism = request.organism
description = request.description
ownedBy = userId
createdAt = now
updatedAt = now
}
backend/src/test/kotlin/org/genspectrum/dashboardsbackend/controller/CollectionsGetTest.kt:114
- The test case says "nonexistent user", but it now creates a real user via
usersClient.createUser(). Either rename the variable/test to reflect that it's an existing user with zero collections, or use an ID that is guaranteed not to exist (e.g., a very large Long) to keep testing the original scenario.
@Test
fun `GIVEN user has no collections WHEN getting collections for user THEN returns empty array`() {
val nonexistentUserId = usersClient.createUser()
val collections = collectionsClient.getCollections(userId = nonexistentUserId)
assertThat(collections, empty())
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @GetMapping("/users/{id}", produces = [MediaType.APPLICATION_JSON_VALUE]) | ||
| @Operation( | ||
| summary = "Get user by internal ID", | ||
| description = "Returns public user info by internal UUID.", |
| fun syncUser(request: UserSyncRequest): User { | ||
| val now = now() | ||
| val existing = UserEntity.findByGithubId(request.githubId) | ||
| return if (existing != null) { | ||
| existing.name = request.name | ||
| existing.email = request.email | ||
| existing.updatedAt = now | ||
| existing.toUser() | ||
| } else { | ||
| UserEntity.new { | ||
| githubId = request.githubId | ||
| name = request.name | ||
| email = request.email | ||
| createdAt = now | ||
| updatedAt = now | ||
| }.toUser() | ||
| } |
| @@ -40,14 +40,14 @@ class SubscriptionModel(private val dashboardsConfig: DashboardsConfig) { | |||
| .toSubscription() | |||
| create index idx_users_github_id on users_table(github_id); | ||
|
|
| -- Migrate collections_table.owned_by to bigint FK | ||
| alter table collections_table add column owned_by_id bigint; | ||
| update collections_table c set owned_by_id = u.id from users_table u where u.github_id = c.owned_by; | ||
| alter table collections_table alter column owned_by_id set not null; | ||
| alter table collections_table add constraint fk_collections_user foreign key (owned_by_id) references users_table(id); | ||
| alter table collections_table drop column owned_by; | ||
| alter table collections_table rename column owned_by_id to owned_by; | ||
|
|
||
| -- Migrate subscriptions_table.user_id to bigint FK | ||
| alter table subscriptions_table add column user_id_new bigint; | ||
| update subscriptions_table s set user_id_new = u.id from users_table u where u.github_id = s.user_id; | ||
| alter table subscriptions_table alter column user_id_new set not null; | ||
| alter table subscriptions_table add constraint fk_subscriptions_user foreign key (user_id_new) references users_table(id); | ||
| alter table subscriptions_table drop column user_id; | ||
| alter table subscriptions_table rename column user_id_new to user_id; |
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>
…/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>
…trollerTest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
resolves #1179
Summary
userstable (BIGSERIAL PK, nullablegithub_id) with upsert viaPOST /users/syncand lookup viaGET /users/{id}. Migratesowned_by/user_idFK columns in collections and subscriptions from GitHub ID strings to BIGINT references to the new table.PR Checklist
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com