fix: resolve collection-code vs join_code mismatches (dashboard 404s, search 403, bridge-app self-stop)#492
Conversation
The DJ dashboard "Search for Song" tool reuses the public guest search
endpoint /api/events/{code}/search, gated by require_verified_human_soft.
With human_verification_enforced=True in production the authenticated DJ —
who holds a JWT but no guest wrzdj_human cookie — was rejected with 403 on
their own event.
Add get_current_user_optional (non-raising optional bearer auth) and let the
authenticated event owner skip the human gate; anonymous guests still pass
through it. The dashboard now sends its JWT on eventSearch so the bypass
applies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After the collection-code / join_code split, three live-display endpoints
(/api/public/e/{code}/nowplaying|bridge-status|history) resolve strictly by
join_code (post-#324/#328 public-URL contract), but callers holding the
collection code 404'd:
- The DJ dashboard event page polled them with the collection code →
console-spam 404s in a 5s loop. It now polls by event.join_code, kept in a
second fetch batch so the request queue is not delayed behind getEvent.
- The bridge-app health check hit the join-code-only nowplaying endpoint with
the collection code → 404 → "Event was deleted" → it stopped a perfectly
live bridge every cycle. It now uses the dual-resolver
/api/public/events/{code}, which accepts either code (same 200/404/410).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR fixes two join-code endpoint resolution bugs (bridge health checks now call ChangesJoin-Code Endpoint Resolution
Event Search Owner Bypass
Sequence Diagram(s)sequenceDiagram
participant DJ as DJ Browser
participant Dashboard as EventQueuePage
participant API as Dashboard API Client
participant Server as WrzDJ Server
rect rgba(70, 130, 180, 0.5)
note over Dashboard,Server: Phase 1 — resolve event
Dashboard->>API: getEvent(collectionCode)
API->>Server: GET /api/events/{code}
Server-->>API: eventData { join_code }
API-->>Dashboard: eventData
end
rect rgba(60, 179, 113, 0.5)
note over Dashboard,Server: Phase 2 — live display via join_code
Dashboard->>API: getNowPlaying(join_code)
Dashboard->>API: getBridgeStatus(join_code)
Dashboard->>API: getPlayHistory(join_code)
API->>Server: GET /api/public/events/{join_code}/...
Server-->>Dashboard: live display data
end
sequenceDiagram
participant Owner as Authenticated Owner
participant Guest as Anonymous Guest
participant Search as event_search endpoint
participant Dep as get_current_user_optional
participant HV as require_verified_human_soft
Owner->>Search: GET /search [Bearer token]
Search->>Dep: resolve token → User
Dep-->>Search: User (is_owner = true)
Search-->>Owner: 200 search results
Guest->>Search: GET /search [no token]
Search->>Dep: resolve token → None
Dep-->>Search: None (is_owner = false)
Search->>HV: check verification cookie
HV-->>Search: 403 human_verification_required
Search-->>Guest: 403
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
dashboard/app/(dj)/events/[code]/page.tsx (1)
363-388:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSecond batch still blocks core queue/event rendering.
The code awaits live-display calls before committing
event/requestsstate, so slow now-playing/bridge/history responses can delay the primary queue view even though the comment says they’re non-critical.Suggested restructure
- const [historyData, nowPlayingData, bridgeStatusData] = await Promise.all([ - api.getPlayHistory(liveCode).catch((): undefined => undefined), - api.getNowPlaying(liveCode).catch((): undefined => undefined), - api.getBridgeStatus(liveCode).catch(() => ({ connected: false, device_name: null, last_seen: null, circuit_breaker_state: null, buffer_size: null, plugin_id: null, deck_count: null, uptime_seconds: null })), - ]); setEvent(eventData); setRequests(requestsData.requests); setRequestTotal(requestsData.total); setStatusCounts(normalizeStatusCounts(requestsData.status_counts)); + void Promise.all([ + api.getPlayHistory(liveCode).catch((): undefined => undefined), + api.getNowPlaying(liveCode).catch((): undefined => undefined), + api.getBridgeStatus(liveCode).catch(() => ({ connected: false, device_name: null, last_seen: null, circuit_breaker_state: null, buffer_size: null, plugin_id: null, deck_count: null, uptime_seconds: null })), + ]).then(([historyData, nowPlayingData, bridgeStatusData]) => { + if (historyData !== undefined) { + setPlayHistory(historyData.items); + setPlayHistoryTotal(historyData.total); + } + if (nowPlayingData !== undefined) { + setNowPlaying(nowPlayingData ?? null); + } + setBridgeConnected(bridgeStatusData.connected); + setBridgeDetails({ + circuitBreakerState: bridgeStatusData.circuit_breaker_state, + bufferSize: bridgeStatusData.buffer_size, + pluginId: bridgeStatusData.plugin_id, + deckCount: bridgeStatusData.deck_count, + uptimeSeconds: bridgeStatusData.uptime_seconds, + }); + });🤖 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 `@dashboard/app/`(dj)/events/[code]/page.tsx around lines 363 - 388, The second Promise.all() awaiting getPlayHistory, getNowPlaying, and getBridgeStatus is blocking state commit of the core eventData and requestsData. To fix this, restructure the code to commit the eventData and requestsData state immediately after the first Promise.all() resolves (which fetches getEvent, getRequests, getDisplaySettings, getTidalStatus, and getBeatportStatus), then handle the second batch of live-display calls for historyData, nowPlayingData, and bridgeStatusData independently without awaiting them before the primary state update. This allows the queue and event rendering to proceed immediately while the non-critical live-display data loads in the background.
🤖 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.
Outside diff comments:
In `@dashboard/app/`(dj)/events/[code]/page.tsx:
- Around line 363-388: The second Promise.all() awaiting getPlayHistory,
getNowPlaying, and getBridgeStatus is blocking state commit of the core
eventData and requestsData. To fix this, restructure the code to commit the
eventData and requestsData state immediately after the first Promise.all()
resolves (which fetches getEvent, getRequests, getDisplaySettings,
getTidalStatus, and getBeatportStatus), then handle the second batch of
live-display calls for historyData, nowPlayingData, and bridgeStatusData
independently without awaiting them before the primary state update. This allows
the queue and event rendering to proceed immediately while the non-critical
live-display data loads in the background.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a98d3eec-99a2-4765-ad8c-15ea951dbce5
📒 Files selected for processing (8)
bridge-app/src/main/__tests__/event-health-service.test.tsbridge-app/src/main/event-health-service.tsdashboard/app/(dj)/events/[code]/__tests__/page.test.tsxdashboard/app/(dj)/events/[code]/page.tsxdashboard/lib/api.tsserver/app/api/deps.pyserver/app/api/events.pyserver/tests/test_events.py
loadData awaited the live-display batch (now-playing / bridge-status / history) before committing any event or request state, so a slow live endpoint delayed the primary queue view and the page spinner — the exact opposite of what the code comment promised. Commit the core event/queue/settings state immediately after the first round-trip resolves, then fire the live-display batch in the background and commit its results when they arrive. The spinner (`loading`, cleared in `finally`) and the request queue now render as soon as their data is in. join_code routing for the live endpoints is unchanged. Addresses CodeRabbit review feedback on #492. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai Addressed your outside-diff comment on
|
|
Tip For best results, initiate chat on the files or code changes.
|
…requests (#495) POST /api/events/{code}/requests hard-depended on require_verified_human_soft, which raises 403 when human_verification_enforced=True for any caller without a valid wrzdj_human cookie. The DJ dashboard "Add" button in the song-search modal reuses this public endpoint; the DJ holds a JWT but no guest human cookie, so on their own event the enforced gate rejected them with 403 — the track never added. Mirror the owner-bypass already applied to event_search (#492): accept an optional bearer (get_current_user_optional) + Response, compute is_owner, and only run require_verified_human_soft for non-owners. Guests and non-owner authenticated users are still gated; create_request already tolerates a null guest_id, so the owner path is safe. Regression: TestSubmitRequestOwnerBypass (owner bypasses, anonymous 403, non-owner 403), mirroring TestEventSearchOwnerBypass. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…endpoint drift (#537) Regenerate via types:export + types:generate. No source changes — reflects already-merged backend drift: #530 default_factory fields drop default:[], owner-JWT bypass adds security scheme to submit/search (#492/#495), and the event_search docstring. Generated-types diff is JSDoc-only; no TS shape change. Closes #536 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Why
After
Event.code(collection code, DJ surfaces) andEvent.join_code(live/QR public surfaces) became distinct values, several callers written when the two were identical kept passing the collection code into endpoints that resolve strictly byjoin_code. On production event4GX43Tthis surfaced as:/api/public/e/{code}/nowplaying|bridge-status|history.wrzdj_humancookie, and human-verification is enforced in prod)./nowplayingwith the collection code → 404 →"Event was deleted".The four join-code-only endpoints are a deliberate, test-locked contract (
test_collection_code_is_rejected, post-#324/#328). So every fix corrects the caller — none loosen those endpoints.What
(dj)/events/[code]/page.tsx): poll the three live-display endpoints byevent.join_code, fetched in a second batch so the request queue isn't delayed behindgetEvent.deps.py,events.py,lib/api.ts): new non-raisingget_current_user_optional; the authenticated event owner skips the human-verification gate while anonymous guests still go through it. The dashboard now sends its JWT oneventSearch.event-health-service.ts): health check now uses the dual-resolver/api/public/events/{code}(accepts either code; same 200/404/410 semantics). Bridge core + CLI were already correct.A repo-wide audit (40+ code-bearing endpoints, all client surfaces incl. kiosk) found no other mismatches.
Testing
🤖 Co-authored by Claude Opus 4.8 (1M context).
Summary by CodeRabbit
Release Notes
Bug Fixes
New Features
Tests