Renaming a rom via PUT /roms/{id} only updated fs_name and its
derivatives, leaving regions/languages/tags/revision/version stale
against the new filename. Re-parse them whenever fs_name changes so
edits like "patapon (Fr En)" -> "Patapon (Fr, En)" are reflected in
the database.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Starlette's TestClient (httpx-based) does not expose body kwargs on the
delete() convenience method; client.request(\"DELETE\", ..., json=...) is
the correct approach. Also switch datetime.utcnow() to
datetime.now(timezone.utc) to silence Python 3.13 deprecation warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch content= to data= for DELETE requests (Starlette TestClient is
requests-based and does not accept content= keyword argument)
- Fix test_bumps_updated_at to record time before the API call and use >=
comparison, avoiding false failures when MariaDB truncates DATETIME to
whole seconds and creation/update land in the same second
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace client.delete(json=...) with content=json.dumps()+Content-Type header
(Starlette TestClient does not forward json= on DELETE requests)
- Adjust duplicate-name test to expect HTTP 500 matching CollectionAlreadyExistsException
- Add description="" to collections created without it to satisfy Pydantic schema
- Strip tzinfo before comparing updated_at to avoid offset-naive/aware TypeError
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace full rom_ids list replacement with atomic POST/DELETE endpoints
that add or remove individual ROMs from a collection. This prevents
concurrent rapid clicks from overwriting each other (last-write-wins).
Also fix missing session.flush() in add_rom_user() and add collection
endpoint tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend API for collecting and querying play sessions, modeled after
the Argosy session data format. Clients submit batches per device,
recording both the session window and screen-on time.
- Use atomic getdel for pairing code exchange
- Add cascade="all, delete-orphan" to User.client_tokens
- Move generate/hash_client_token into AuthHandler as static methods
- Extract endpoint helpers to utils/client_tokens.py
Three tests were also implemented to check initial implementation that now invalidates expired access and refresh tokens and also rotating refresh tokens.
Since I introduced wrapper functions for create_oauth_token to distinguish between access and refresh token there is no need to set the token type in the data dict, since the type is now enforced in the wrapper functions create_access_token and create_refresh_token.
By convention I renamed create_oauth_token to _create_oauth_token as it is considered a private helper function now.