Skip to content

Stage A: backend-only rebuild on rollback base (contract-verified against live frontend) - #8

Merged
Privex-chat merged 2 commits into
codex/rollback-d5f02f1-working-statefrom
rebuild/stage-a-backend
Jul 11, 2026
Merged

Stage A: backend-only rebuild on rollback base (contract-verified against live frontend)#8
Privex-chat merged 2 commits into
codex/rollback-d5f02f1-working-statefrom
rebuild/stage-a-backend

Conversation

@Privex-chat

@Privex-chat Privex-chat commented Jul 11, 2026

Copy link
Copy Markdown
Owner

What this is

The careful restart. Base is the rollback (d5f02f1 + App.js tweak) that runs in production. This PR re-applies only the backend-side performance and data-integrity work, with the original architecture and API contract untouched.

Hard guarantees, tested:

  • Zero changes to sessions.py, scoring.py, rooms.py, daily.py, or any frontend file (git diff against base for those paths is empty)
  • A contract test replays the production frontend's exact call sequence — guest session → playlist load → /sessions/start with track_ids/scores/submit → session-complete → /tracks/art/stats/activity → audio-proxy — all pass, and omitting track_ids still 422s exactly as production does today
  • Response changes are additive only (warning string the frontend already displays, fetch_complete flag it ignores)
  • No new dependencies; the fetch_complete column self-migrates at startup

What it fixes

  • Scraped previews get trashed → re-fetches can no longer overwrite recovered preview URLs or truncate the playlist link table (the weekly 500→97 cycle)
  • Large playlists → batched saves (3 statements vs ~1,400 round-trips), single-flight fetches, parallel metadata pages, fail-fast on Spotify's multi-hour 429 penalties with honest totals + 6-hour re-fetch of truncated records, on-demand preview fill (fresh big playlist playable in minutes)
  • players today is broken (500s) in the original code — SQL type fix
  • bcrypt/Cloudinary off the event loop, capped avatar reads, limiter eviction, 90-day sessions, 72-byte bcrypt cap re-applied

Verified

Contract suite + three integration suites (batch save/link guard, preview fill, penalty/totals) against Postgres 16; full app imports with the original 33 routes.

Deploy (backend only — frontend untouched)

cd ~/audyn && git pull            # after merge, on the rollback branch
pm2 restart audyn-backend-8000 audyn-backend-8001
pm2 start ecosystem.config.js --only audyn-preview-worker && pm2 save

Then one-time in Supabase: UPDATE playlists SET fetched_at = NOW() - INTERVAL '8 days';
Smoke test: load a playlist and start a demo game.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added automatic recovery of missing track previews, with retry handling and unavailable-track status updates.
    • Added configurable JWT expiration, defaulting to 90 days.
    • Added warnings when playlist data is incomplete or partially unavailable.
  • Bug Fixes

    • Improved playlist caching to preserve recovered metadata and avoid losing complete results after partial refreshes.
    • Improved Spotify rate-limit handling and concurrent playlist loading.
    • Fixed activity statistics errors and prevented stale rate-limit data from accumulating.
    • Limited avatar uploads to 5 MB and improved upload responsiveness.

…rollback base

Re-applies, onto the known-working d5f02f1 state, ONLY the backend-side work
— verified against the production frontend's exact API contract. Zero
changes to sessions.py, scoring.py, rooms.py, daily.py, or any frontend
file. /sessions/start still requires track_ids exactly as the live frontend
sends it (contract-tested, including the 422 shape when omitted).

Large playlists & permanent previews:
- Batched playlist saves: 3 statements instead of ~2 per track
- Preview URLs recovered by the worker are never overwritten with empty
  strings on re-fetch (CASE guard), and degraded fetches can no longer
  truncate the playlist link table or shrink stored totals — this is the
  'scraped links get trashed' bug
- Single-flight playlist fetches; parallel metadata pages with
  Retry-After-aware 429 handling that fails fast on Spotify's multi-hour
  penalty boxes instead of hanging the user's request
- Honest totals + fetch_complete flag (self-migrated column): truncated
  records re-fetch after 6h instead of being served for 7 days
- On-demand preview fill: a fresh large playlist becomes fully playable in
  minutes; worker remains the janitor (bounded cycles, batched writes,
  startup banner for pm2 logs)

Performance & fixes:
- players-today endpoint 500s in the original code (COALESCE type error) — fixed
- bcrypt + Cloudinary moved off the event loop; avatar reads capped at 5MB
- Rate limiter dicts evict idle IPs; app-level gzip removed (nginx does it)
- Sessions last 90 days (was 72h with no refresh = weekly forced logouts)
- bcrypt 72-byte password cap (re-applied from 5d15bf3)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
audyn Ready Ready Preview, Comment Jul 11, 2026 9:32am

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b55a5434-6da7-496b-ac0c-aef8af3145d8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rebuild/stage-a-backend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
backend/auth.py (1)

118-120: 🔒 Security & Privacy | 🔵 Trivial

90-day token lifetime with no revocation broadens the compromise window.

Because token lifetime is the session lifetime and there's no refresh/rotation flow, a leaked token stays valid for up to ~90 days, and neither logout nor password change can invalidate it. If you keep the long expiry, consider a server-side invalidation lever — e.g. a token_version/password_changed_at claim checked at auth time, or a short deny-list — so compromised or post-logout tokens can be cut off before natural expiry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/auth.py` around lines 118 - 120, Add server-side token invalidation
to the authentication flow using a token_version or password_changed_at value
included in issued JWTs and checked during request authentication. Ensure logout
and password changes advance or invalidate that value so existing tokens are
rejected, while preserving the configurable JWT_EXPIRE_HOURS lifetime.
backend/database.py (1)

26-32: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider surfacing genuine migration failures instead of swallowing them.

The broad except Exception (Ruff BLE001) makes the self-migration resilient to the benign "column already exists" case, but it also silences a real ALTER failure. Since save_playlist writes/reads the fetch_complete column unconditionally, a truly failed migration would turn into harder-to-trace runtime errors later while startup reports success. ADD COLUMN IF NOT EXISTS is already idempotent, so the try/except mainly guards against permission/transient errors — at minimum this is worth verifying doesn't hide a persistent failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/database.py` around lines 26 - 32, Update the self-migration around
the ALTER TABLE statement to stop broadly swallowing migration failures: let
genuine permission, connectivity, or SQL errors from conn.execute propagate and
fail startup, while retaining the idempotent ADD COLUMN IF NOT EXISTS behavior.
Remove or narrow the Exception handler and adjust logging only if needed to
preserve visibility of actual failures.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/auth.py`:
- Around line 118-120: Add server-side token invalidation to the authentication
flow using a token_version or password_changed_at value included in issued JWTs
and checked during request authentication. Ensure logout and password changes
advance or invalidate that value so existing tokens are rejected, while
preserving the configurable JWT_EXPIRE_HOURS lifetime.

In `@backend/database.py`:
- Around line 26-32: Update the self-migration around the ALTER TABLE statement
to stop broadly swallowing migration failures: let genuine permission,
connectivity, or SQL errors from conn.execute propagate and fail startup, while
retaining the idempotent ADD COLUMN IF NOT EXISTS behavior. Remove or narrow the
Exception handler and adjust logging only if needed to preserve visibility of
actual failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f88797d-5799-48a7-a6a5-18f7602bd9fb

📥 Commits

Reviewing files that changed from the base of the PR and between 0e85b29 and fc87524.

📒 Files selected for processing (8)
  • backend/auth.py
  • backend/database.py
  • backend/db_playlists.py
  • backend/preview_store.py
  • backend/preview_worker.py
  • backend/schema.sql
  • backend/server.py
  • backend/stats.py

Per CodeRabbit: the broad 'except Exception' silently swallowed permission
and SQL errors, which would leave the fetch_complete column missing while
save_playlist references it — breaking every playlist save quietly. Now only
UndefinedTableError (fresh DB, schema.sql not yet run) is tolerated; genuine
failures propagate and fail startup loudly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Privex-chat
Privex-chat merged commit 8b788ac into codex/rollback-d5f02f1-working-state Jul 11, 2026
3 checks passed
Privex-chat added a commit that referenced this pull request Jul 11, 2026
Stage A: backend-only rebuild on rollback base (contract-verified against live frontend)
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.

1 participant