Skip to content

Connect Steam & import owned games - #105

Open
mforce wants to merge 2 commits into
mainfrom
feat/steam-import
Open

Connect Steam & import owned games#105
mforce wants to merge 2 commits into
mainfrom
feat/steam-import

Conversation

@mforce

@mforce mforce commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Closes #101

Spec-first: docs/platform-import.md (v3) reviewed independently by Claude Code and Codex; findings folded in. Steam-only first, architected for more stores later.

What

Lets a user link their Steam account (OpenID) and import games they already own into their collection. DLC/free-to-play/products are user-selected (we list all owned apps, they pick). Disconnecting Steam keeps imported games and the provenance ledger.

Backend

  • EF migration AddStoreImport: GameStoreConnection, GameStoreOwnedTitle (idempotency ledger keyed (OwnerId, Store, ExternalGameId)), SteamAuthRequest. Games gets (Id, OwnerId) alternate key; ledger uses composite FK (GameId, OwnerId) with Restrict (deleting a game nulls the link rather than violating FK).
  • SteamStoreImportService — transactional, owner-scoped, idempotent import; disconnect keeps games.
  • SteamOpenIdVerifier — strict check_authentication (endpoint, return_to byte-match, SteamID64, signed coverage) + state+cookie second factor.
  • SteamStoreEndpoints — public callback + authenticated /api/accounts/steam/*.
  • GamesEndpoints DELETE updated for the new composite FK.
  • Testable seams ISteamClient / ISteamOpenIdVerifier.

Frontend

  • services/steam.ts hooks, pages/ImportSteam.tsx (connect → select → import), route /import/steam, entry in GamesList.

Tests

  • 11 new xUnit (callback accept/reject, import, idempotency, owner-scoping, disconnect-keeps-games, games preview).
  • 6 new Vitest for ImportSteam.
  • Full suites green: server 377/377, client 114/114, npm run build passes.

Notes

Kyoder added 2 commits August 15, 2026 00:04
Implements the spec-first feature from issue #101 (spec v3 after Codex +
Claude review). Steam-only now, architected for more stores later.

Backend:
- New entities + EF migration AddStoreImport: GameStoreConnection,
  GameStoreOwnedTitle (import provenance ledger, idempotency key
  (OwnerId, Store, ExternalGameId)), SteamAuthRequest. Games gains
  (Id, OwnerId) alternate key; ledger uses composite FK (GameId, OwnerId)
  with Restrict so deleting a game nulls the link instead of violating FK.
- SteamStoreImportService: transactional, owner-scoped, idempotent import;
  disconnect keeps games + ledger.
- SteamOpenIdVerifier: strict check_authentication round-trip (endpoint,
  return_to byte-match, SteamID64, signed coverage) + state+cookie 2nd factor.
- SteamStoreEndpoints: public callback + /api/accounts/steam/* group.
- GamesEndpoints DELETE nulls ledger GameId before delete.
- ISteamClient / ISteamOpenIdVerifier testable seams.

Frontend:
- services/steam.ts hooks, pages/ImportSteam.tsx (connect -> select -> import),
  route /import/steam, entry in GamesList.

Tests: 11 new xUnit (endpoints, callback, idempotency, owner scoping,
disconnect) + 6 Vitest (ImportSteam). Full suite green.
Codex + Claude reviews of PR #105 flagged critical and should-fix issues;
the following are now addressed and verified (407 backend tests, 116 client
tests, clean client production build):

Backend correctness/security:
- SteamId64 parser: require /openid/id/<steamid64> (3 segments) so real
  Steam callbacks no longer fail and crafted claimed-ids can't throw.
- OpenID checkid_setup now uses identifier_select instead of a bare prefix.
- Callback consumes state + upserts the connection atomically, with a local
  pre-check before the outbound Steam round-trip; public callback is
  rate-limited.
- Startup SteamAuthRequest sweep is best-effort so existing Postgres
  (EnsureCreated) DBs don't crash the app at boot.
- State tokens now CSPRNG-backed (RandomNumberGenerator) not Guid.
- Import cap enforced with HTTP 400; import request null/{} body handled;
  concurrent-import unique races no longer 500; LastSyncedAt written on
  successful fetch.
- Owned-games preview is a discriminated result ({ status, titles, truncated })
  so the client can distinguish ok / empty / provider-unavailable instead of
  a blank 200 [].
- Secure cookie derived from request (plain-HTTP self-hosted compatible).

DLC schema hook (provider-agnostic, import stays flat):
- Game.ParentGameId nullable self-FK (Restrict) + GameStoreOwnedTitle
  ParentExternalGameId; migration AddGameDlcParent; docs/data-model.md updated.

Tests:
- New SteamOpenIdVerifierTests, SteamId64Tests, delete-an-imported-game
  integration test, unavailable-status coverage; frontend ImportSteam tests
  updated for the new preview shape + search filter + empty/unavailable states.

Dead code: removed unused CompleteConnectAsync and SteamId64.IsValidSteamId.
@mforce

mforce commented Aug 15, 2026

Copy link
Copy Markdown
Owner Author

@codex

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.

Connect Steam & import owned games (platform import)

1 participant