Make the platform list shared across users; keep games private

Platforms no longer have a user_id: there's one global list, unique by
name, that every authenticated user reads from and writes to. Games are
unchanged - still private per user, still referencing platform_id.

- Migration: dropped platform.user_id and its FK, UNIQUE(user_id, name) ->
  UNIQUE(name), index on change_seq alone instead of (user_id, change_seq).
  Amended the one init migration in place rather than stacking a second one,
  since nothing is deployed yet.
- db/query.sql: platform queries no longer take a user_id parameter.
  DeletePlatform's NOT EXISTS guard already checked games from any user (it
  never filtered the subquery by user_id), so deleting a shared platform is
  correctly blocked by any user's games on it, not just the deleter's.
- routes.go: platform handlers still require auth (the list isn't public to
  anonymous requests) but no longer scope by user.
- Tests: updated the mocks for the new signatures, added
  TestListPlatformsIsSharedAcrossUsers.

Verified against a real Postgres: user A creates "PC", user B sees it
immediately, both can add games against it, each only sees their own
games, deleting the platform is blocked while either user has a game on
it, and the name stays globally unique.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
SansanandClaude Sonnet 5 committed 2026-10-03 11:34:47 +02:00
1 parent 5072919b61
commit 7fbd403d30
8 files changed
+93 -86

No files matched your search

+7 -6
View File
@@ -12,23 +12,24 @@ import (
type Querier interface {
CreateGame(ctx context.Context, arg CreateGameParams) (CreateGameRow, error)
CreatePlatform(ctx context.Context, arg CreatePlatformParams) (CreatePlatformRow, error)
CreatePlatform(ctx context.Context, name string) (CreatePlatformRow, error)
CreateSession(ctx context.Context, arg CreateSessionParams) error
CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error)
DeleteGame(ctx context.Context, arg DeleteGameParams) (int64, error)
// Soft delete, so the FK on game.platform_id is never touched and never
// blocks this on its own; the NOT EXISTS guard is what stops a platform
// that still has active games from being deleted. Call GetPlatform first to
// tell "not found" apart from "blocked by games" when this affects 0 rows.
DeletePlatform(ctx context.Context, arg DeletePlatformParams) (int64, error)
// that still has active games - from any user, since the list is shared -
// from being deleted. Call GetPlatform first to tell "not found" apart from
// "blocked by games" when this affects 0 rows.
DeletePlatform(ctx context.Context, id uuid.UUID) (int64, error)
DeleteSession(ctx context.Context, tokenHash []byte) error
GetGame(ctx context.Context, arg GetGameParams) (GetGameRow, error)
GetPlatform(ctx context.Context, arg GetPlatformParams) (GetPlatformRow, error)
GetPlatform(ctx context.Context, id uuid.UUID) (GetPlatformRow, error)
GetSessionAndUser(ctx context.Context, tokenHash []byte) (GetSessionAndUserRow, error)
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
GetUserByID(ctx context.Context, id uuid.UUID) (GetUserByIDRow, error)
ListGames(ctx context.Context, userID uuid.UUID) ([]ListGamesRow, error)
ListPlatforms(ctx context.Context, userID uuid.UUID) ([]ListPlatformsRow, error)
ListPlatforms(ctx context.Context) ([]ListPlatformsRow, error)
TouchSession(ctx context.Context, tokenHash []byte) error
UpdateGame(ctx context.Context, arg UpdateGameParams) (UpdateGameRow, error)
UpdatePlatform(ctx context.Context, arg UpdatePlatformParams) (UpdatePlatformRow, error)