Repository navigation
Conversation
Users can link a Steam account (SteamID64, profile link or custom URL name)
and sync the games it owns. The sync links each owned game to the rom that
already stands for it on win, linux or mac, matched by Steam app id, and
gives the rest a store-only rom on win (created with its folder when the
library has none). Store-only roms have no file: `roms.storefront` marks
them and, like physical games, keeps them out of the missing-file sync,
the missing cleanup, the feeds and the exporters. Store-only roms that no
source has matched yet are queued for a rom-id scan after each sync.
- STEAM_API_KEY enables it; the heartbeat reports STEAM_LIBRARY_ENABLED.
- PUT/DELETE /api/users/{id}/steam link and unlink an account,
POST /api/users/{id}/steam/sync queues the sync for one user.
- sync_steam_libraries syncs every linked user on a schedule
(ENABLE_SCHEDULED_STEAM_LIBRARY_SYNC, SCHEDULED_STEAM_LIBRARY_SYNC_CRON).
- steam_library_entries holds each user's owned games with playtime and
last played time; migration 0155 adds it, the user columns and
roms.storefront.
- A rom-id scan no longer marks a physical or store-only rom missing.
- New store_only rom filter, part of HAS_FILE_ON_DISK_FILTERS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
|
✅ Preview build completed! Docker image: |
Adds the frontend for the Steam library import:
- A Steam section in the user profile links an account by SteamID64,
profile link or custom URL name, starts a sync, shows how many games
the account owns, and unlinks it after a confirmation. It only shows
when the server has a Steam API key.
- An "Owned on Steam" gallery filter, kept in the URL and usable in
smart collections. The backend gains an `owned_on_steam` rom filter
and `GET /api/users/{id}/steam/library` for the owned app ids.
- "Open in Steam" shows only on owned games once a linked account's
library has loaded; without a linked account it behaves as before.
Smart collections filtering on ownership are refreshed when a sync,
relink or unlink changes what a user owns. The sync task is renamed
"Steam library sync" since users start it too, and its completion
notification opens their profile rather than the admin page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
[Medium impact] The latest changes appear safe to merge; no new blocking issue was found. SummaryThis PR adds per-user Steam library imports, store-only games, ownership filters, and profile controls.
Reviews (4) · Last reviewed commit: "fix(steam): keep the newest owned list a..." · Reviewed by Greptile |
Renumbers the Steam library migration to 0156 so it follows master's 0155_rom_release_types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
…types The merge renamed the file to 0156 but left its revision ids behind, giving alembic two heads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
There was a problem hiding this comment.
🟡 Changes recommended
Metadata scanning remains blocked, account changes can race with ownership writes, and connection failures can abort scheduled synchronization.
4 open issues
6 files not reviewed
frontend/src/__generated__/index.ts: Generated filefrontend/src/__generated__/models/Body_link_steam_account_api_users__id__steam_put.ts: Generated filefrontend/src/__generated__/models/DetailedRomSchema.ts: Generated filefrontend/src/__generated__/models/MetadataSourcesDict.ts: Generated filefrontend/src/__generated__/models/SimpleRomSchema.ts: Generated filefrontend/src/__generated__/models/UserSchema.ts: Generated file
What changed in this PR
Adds per-user Steam library imports to RomM, integrating account linking, background synchronization, ownership filtering, and store-only games into the existing library.
Changes:
- Adds Steam API access, ownership storage, account endpoints, and scheduled synchronization.
- Extends file-less ROM handling and connects ownership to gallery filters and smart collections.
- Adds profile controls, ownership-aware Steam actions, translations, generated types, and tests.
| File | Description |
|---|---|
| frontend/src/views/Settings/UserProfile.vue | Shows Steam settings when configured. |
| frontend/src/utils/tasks.ts | Defines the Steam sync task identifier. |
| frontend/src/utils/smartCollectionCriteria.ts | Serializes and summarizes ownership criteria. |
| frontend/src/utils/smartCollectionCriteria.test.ts | Tests ownership criteria serialization. |
| frontend/src/utils/rom.fixtures.ts | Adds storefront fixture data. |
| frontend/src/utils/notifications.ts | Routes Steam task notifications to the profile. |
| frontend/src/stores/steamLibrary.ts | Loads ownership and refreshes after sync notifications. |
| frontend/src/stores/heartbeat.ts | Defaults Steam library availability to disabled. |
| frontend/src/stores/galleryRoms.ts | Passes ownership into gallery requests. |
| frontend/src/stores/galleryFilter.ts | Tracks and resets ownership filtering. |
| frontend/src/services/cache/api.test.ts | Tests ownership query parameters. |
| frontend/src/services/api/user.ts | Adds Steam account and library API methods. |
| frontend/src/services/api/rom.ts | Adds the ownership query parameter. |
| frontend/src/locales/zh_TW/settings.json | Adds Traditional Chinese Steam settings text. |
| frontend/src/locales/zh_TW/platform.json | Adds Traditional Chinese ownership filter labels. |
| frontend/src/locales/zh_CN/settings.json | Adds Simplified Chinese Steam settings text. |
| frontend/src/locales/zh_CN/platform.json | Adds Simplified Chinese ownership filter labels. |
| frontend/src/locales/tr_TR/settings.json | Adds Turkish Steam settings text. |
| frontend/src/locales/tr_TR/platform.json | Adds Turkish ownership filter labels. |
| frontend/src/locales/ru_RU/settings.json | Adds Russian Steam settings text. |
| frontend/src/locales/ru_RU/platform.json | Adds Russian ownership filter labels. |
| frontend/src/locales/ro_RO/settings.json | Adds Romanian Steam settings text. |
| frontend/src/locales/ro_RO/platform.json | Adds Romanian ownership filter labels. |
| frontend/src/locales/pt_BR/settings.json | Adds Brazilian Portuguese Steam settings text. |
| frontend/src/locales/pt_BR/platform.json | Adds Brazilian Portuguese ownership filter labels. |
| frontend/src/locales/pl_PL/settings.json | Adds Polish Steam settings text. |
| frontend/src/locales/pl_PL/platform.json | Adds Polish ownership filter labels. |
| frontend/src/locales/ko_KR/settings.json | Adds Korean Steam settings text. |
| frontend/src/locales/ko_KR/platform.json | Adds Korean ownership filter labels. |
| frontend/src/locales/ja_JP/settings.json | Adds Japanese Steam settings text. |
| frontend/src/locales/ja_JP/platform.json | Adds Japanese ownership filter labels. |
| frontend/src/locales/it_IT/settings.json | Adds Italian Steam settings text. |
| frontend/src/locales/it_IT/platform.json | Adds Italian ownership filter labels. |
| frontend/src/locales/hu_HU/settings.json | Adds Hungarian Steam settings text. |
| frontend/src/locales/hu_HU/platform.json | Adds Hungarian ownership filter labels. |
| frontend/src/locales/fr_FR/settings.json | Adds French Steam settings text. |
| frontend/src/locales/fr_FR/platform.json | Adds French ownership filter labels. |
| frontend/src/locales/es_ES/settings.json | Adds Spanish Steam settings text. |
| frontend/src/locales/es_ES/platform.json | Adds Spanish ownership filter labels. |
| frontend/src/locales/en_US/settings.json | Adds source Steam settings text. |
| frontend/src/locales/en_US/platform.json | Adds source ownership filter labels. |
| frontend/src/locales/en_GB/settings.json | Adds British English Steam settings text. |
| frontend/src/locales/en_GB/platform.json | Adds British English ownership filter labels. |
| frontend/src/locales/de_DE/settings.json | Adds German Steam settings text. |
| frontend/src/locales/de_DE/platform.json | Adds German ownership filter labels. |
| frontend/src/locales/cs_CZ/settings.json | Adds Czech Steam settings text. |
| frontend/src/locales/cs_CZ/platform.json | Adds Czech ownership filter labels. |
| frontend/src/locales/bg_BG/settings.json | Adds Bulgarian Steam settings text. |
| frontend/src/locales/bg_BG/platform.json | Adds Bulgarian ownership filter labels. |
| frontend/src/composables/useGameActions/index.ts | Gates Steam actions by loaded ownership. |
| frontend/src/composables/useGameActions/index.test.ts | Tests ownership-based Steam actions. |
| frontend/src/composables/useGalleryFilterUrl/index.ts | Persists ownership filtering in URLs. |
| frontend/src/components/Settings/SteamLibrarySection.vue | Adds linking, syncing, and confirmed unlinking. |
| frontend/src/components/Settings/SteamLibrarySection.test.ts | Tests Steam profile interactions. |
| frontend/src/components/GameDetails/RelatedGameCard.vue | Adds storefront to synthetic ROM data. |
| frontend/src/components/Gallery/listRowFixture.ts | Updates gallery fixture shape. |
| frontend/src/components/Gallery/GalleryShell.vue | Counts and watches ownership filtering. |
| frontend/src/components/Gallery/FilterDrawer.vue | Adds the tri-state ownership control. |
| frontend/src/components/Dialogs/CreateSmartCollectionDialog.vue | Saves ownership criteria in collections. |
| frontend/src/__generated__/models/UserSchema.ts | Exposes linked Steam account fields. |
| frontend/src/__generated__/models/SimpleRomSchema.ts | Exposes storefront on simple ROMs. |
| frontend/src/__generated__/models/MetadataSourcesDict.ts | Exposes Steam library availability. |
| frontend/src/__generated__/models/DetailedRomSchema.ts | Exposes storefront on detailed ROMs. |
| frontend/src/__generated__/models/Body_link_steam_account_api_users__id__steam_put.ts | Defines the account-link request type. |
| frontend/src/__generated__/index.ts | Exports the generated link request type. |
| env.template | Documents API key and schedule settings. |
| docs/BACKEND_ARCHITECTURE.md | Documents Steam storage, endpoints, and scheduling. |
| backend/utils/roms_columns.py | Registers the storefront column. |
| backend/tests/test_migrations.py | Adds migration replay coverage. |
| backend/tests/tasks/test_sync_steam_libraries.py | Tests sync orchestration and failure handling. |
| backend/tests/models/test_rom.py | Tests store-only file availability. |
| backend/tests/handler/test_steam_library_handler.py | Tests profile resolution and library import. |
| backend/tests/handler/test_db_handler.py | Tests linked users and file-less filtering. |
| backend/tests/handler/database/test_roms_owned_on_steam_filter.py | Tests per-user ownership matching. |
| backend/tests/endpoints/test_user_steam.py | Tests Steam endpoints and collection refresh. |
| backend/tests/endpoints/sockets/test_scan.py | Tests bypassing file-less existence checks. |
| backend/tests/adapters/services/test_steam_web_api.py | Tests Steam API responses and errors. |
| backend/tasks/scheduled/sync_steam_libraries.py | Orchestrates imports and follow-up scans. |
| backend/tasks/registry.py | Registers configurable Steam synchronization. |
| backend/models/user.py | Stores linked Steam account details. |
| backend/models/steam_library.py | Defines per-user game ownership records. |
| backend/models/rom.py | Adds storefront and shared file-less predicates. |
| backend/handler/steam_library_handler.py | Resolves profiles and imports owned games. |
| backend/handler/scan_handler.py | Preserves storefront during metadata scanning. |
| backend/handler/database/users_handler.py | Filters users by linked Steam accounts. |
| backend/handler/database/steam_library_handler.py | Matches ROMs and maintains ownership records. |
| backend/handler/database/roms_handler.py | Adds ownership and store-only filtering. |
| backend/handler/database/rom_filters.py | Defines new ROM filter parameters. |
| backend/handler/database/__init__.py | Exports the Steam library database handler. |
| backend/endpoints/user.py | Adds account linking, unlinking, listing, and sync. |
| backend/endpoints/tasks.py | Exposes the queued-task response builder. |
| backend/endpoints/sockets/scan.py | Adjusts file-less checks and task-initiated scans. |
| backend/endpoints/responses/rom.py | Adds storefront to ROM responses. |
| backend/endpoints/responses/identity.py | Adds linked Steam account response fields. |
| backend/endpoints/responses/heartbeat.py | Defines the Steam availability response flag. |
| backend/endpoints/heartbeat.py | Reports Steam library configuration status. |
| backend/config/__init__.py | Loads Steam API key and schedule settings. |
| backend/alembic/versions/0155_steam_library.py | Adds account fields, storefront, and ownership table. |
| backend/adapters/services/steam_web_api.py | Implements the keyed Steam API client. |
| backend/adapters/services/steam_types.py | Defines Steam account and ownership response types. |
Review effort: Balanced
- Queue the metadata scan for unmatched store-only games as UNMATCHED: a QUICK scan only reconciles an existing rom's files, so these games never got covers or metadata. - Re-check the linked account under a row lock before a sync writes its ownership rows, and clear the account before forgetting its games on unlink or relink, so a sync still in flight can't bring them back. - Report a connection failure or a non-JSON reply from Steam as a Steam error, so one user's failure no longer stops the nightly sync. - Refetch the owned list when a tab becomes visible again, since a scheduled sync notifies nobody. - Move link, unlink and the smart collection refresh into the Steam library handler, out of the endpoints. - Use object-form props in the test stubs and trim the migration docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
A selected-rom scan rebuilt each rom's files from disk before matching metadata, so a store-only or physical game failed there and never got its cover. Such a rom now goes straight to metadata, and a quick scan leaves it alone. Raised by Copilot on the Steam library import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
A slow earlier request for the owned Steam apps could overwrite a newer reply, so only the latest request now writes. Also trims two docstrings to the one-sentence rule. Addresses Greptile's second review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
Master reverted the release-type classification and the Open in Steam action. The Steam library migration goes back to 0155 on top of 0154_ownerless_own_only, and this branch drops the Open in Steam ownership gating along with the action it gated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh
Dismissed because a newer commit was pushed; Greptile will re-review the current head.


Description
This PR imports a user's Steam library into RomM, end to end. Each user links a Steam account. RomM then tracks which games that account owns, adds the owned games to the library, and lets the user filter by them.
How it works:
STEAM_API_KEYcovers the whole server.ENABLE_SCHEDULED_STEAM_LIBRARY_SYNCandSCHEDULED_STEAM_LIBRARY_SYNC_CRONcontrol the nightly sync of every linked user.STEAM_LIBRARY_ENABLED, and the UI hides everything Steam-related when no key is set.PUT /api/users/{id}/steamaccepts a SteamID64, a profile link (/profiles/<id>or/id/<name>), or a custom URL name.ResolveVanityURLandGetPlayerSummaries, then storessteam_id(as a string, since a SteamID64 overflows JS integers) andsteam_persona_name.DELETE /api/users/{id}/steamunlinks the account.POST /api/users/{id}/steam/sync(202) queues thesync_steam_librariestask for that user. The scheduled run of the same task syncs every linked user.GetOwnedGames.steam_idon a PC-family platform links to that rom. A rom with a file is preferred.win, withstorefront = "steam"and itssteam_idpreset. Itsfs_pathis<win>/.steam, a folder no scan lists (the same approach as physical games).UNMATCHEDmetadata scan by rom id, because a library scan never reaches a game without a file.winplatform and its folder are created if missing; otherwisemark_missing_platformswould flag them.steam_library_entries. One row per user per owned app, with playtime and last played. It cascades on user and rom delete.GET /api/users/{id}/steam/librarylists the owned app ids.GET /api/romsgains a per-userowned_on_steamfilter. It matches every rom carrying an owned app id, on any platform.store_onlyfilter.Rom.never_has_filein Python,rom_without_file()in SQL.HAS_FILE_ON_DISK_FILTERSused by feeds and exporters gainsstore_only: False.?filterOwnedOnSteam=) and can be saved as a smart collection. It stays listed when set from a shared link, so it can be cleared.0155_steam_library(follows0154_ownerless_own_only). It addsroms.storefront(throughensure_roms_columns), the two user columns and the new table. Each step is guarded so a partial run can be replayed. Upgrade, downgrade and upgrade again were run on MariaDB.flowchart LR subgraph Frontend PS[Profile: SteamLibrarySection] FD[Gallery: Owned on Steam filter] SL[(steamLibrary store)] end PS -->|PUT / DELETE /users/id/steam| EP[endpoints/user.py] PS -->|POST /users/id/steam/sync| EP SL -->|GET /users/id/steam/library| EP PS --> SL FD -->|GET /roms?owned_on_steam| ROMS[roms_handler filter] EP -->|enqueue| T[tasks/scheduled/sync_steam_libraries] CRON[Scheduler: SCHEDULED_STEAM_LIBRARY_SYNC_CRON] --> T T --> H[handler/steam_library_handler] EP -->|link / unlink / resolve| H H --> API[adapters/services/steam_web_api] H -->|link existing rom or create store-only rom on win| DB[(roms + steam_library_entries)] H -->|ownership changes| SC[smart collection refresh] T -->|unmatched store-only rom ids| SCAN[UNMATCHED scan by rom id] T -.->|task completed / failed notification| SL ROMS --> DBAI assistance: Claude Code (an AI coding agent) wrote this PR, including the design discussion, implementation, tests, migration, translations and this description. A human directed it and made the design calls (the
winplatform, per-user ownership, one server-wide key, and filtering by ownership). The new strings are machine-translated into all 18 locales and would benefit from a native speaker's review. The work was not run end to end against the real Steam API, because no real key was available. Steam was reached, and correctly rejected a placeholder key.Checklist
Local verification:
owned_on_steamfilter._identify_romfor roms with no file.SteamLibrarySection: link then sync, a rejected profile, the owned count, and unlink after confirmation.mypy,trunk check,vue-tsc, and both i18n checks are clean.?filterOwnedOnSteam=trueleaves the five owned games and hides an unowned Steam game.Screenshots (if applicable)
I took the screenshots from the running app during the browser pass. This session has no way to upload images to a PR, so the author will attach them:
🤖 Generated with Claude Code
https://claude.ai/code/session_01XGA3D2oZ1hq3QUdnubMNXh