feat(playlist): tenant-scoped CRUD nested under profile (Phase 1.b.5c-B) - #10
Conversation
Wires `PostgresPlaylistRepository` (waveflow#188) to a new
`/api/v1/profiles/{profile_id}/playlists/*` resource. Same tenancy
pattern as libraries (same depth: profile -> playlist), boundary
validation matches the rest of the 1.b.5 suite. Closes Phase 1.b.5.
- New migration `20260530000004_playlist.sql`: BIGSERIAL pk,
profile_id FK with ON DELETE CASCADE, brand defaults
(color_id='violet', icon_id='music') mirroring the desktop. All
the numeric flags (is_smart, cover_is_auto, position) are BIGINT
to round-trip cleanly into Playlist.<field>: i64 — same lesson
as track.rating in 1.b.5b. cover_is_auto DEFAULT 1 matches the
desktop convention (auto-managed slot, cf. waveflow#188 CR).
Composite index (profile_id, position ASC, updated_at DESC)
matches the ORDER BY in list_for_profile and covers the FK
fan-out.
- New src/api/playlists.rs: 5 verbs with OpenAPI annotations
including 400. Wire format drops profile_id (URL-derived) and
cover_path (server stub) — keeps the rest in lockstep with the
desktop Playlist DTO.
- Boundary validation: name trimmed and rejected when blank on
POST; PATCH applies the same Some("") / Some(" ") rejection
(None stays legitimate, COALESCE preserves).
- src/api/mod.rs: playlists_router gated identically to the others.
- tests/playlists.rs: 11 integration tests including the
sticky-flag invariants (is_smart=0, smart_rules=NULL,
cover_is_auto=1, cover_hash=NULL, track_count=0,
total_duration_ms=0) — drift on any of these would quietly
break a future server-side smart-playlist or auto-cover pipeline.
Plus the standard battery: 401 gate, blank name 400, foreign
profile 404, proxy-attack matrix, partial PATCH preservation,
PATCH blank rejection, delete 204→404, profile CASCADE to
playlists via still-owned profile proxy, prod-gate 503.
- tests/ready.rs: playlist table existence canary.
- tests/openapi.rs: playlists collection + item path assertions.
- Cargo.toml bumps waveflow-core rev to 25b9ada (#188 merge).
Signed-off-by: InstaZDLL <github.105mh@8shield.net>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAjout d'une API REST multi-tenant CRUD pour les playlists sous ChangesPlaylist CRUD API with multi-tenant isolation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Summary
Closes Phase 1.b.5 — tenant-scoped CRUD for the playlist resource, the last piece of the multi-tenant chain on the wire. Wires
PostgresPlaylistRepositoryfrom waveflow#188 to/api/v1/profiles/{profile_id}/playlists/*. A playlist sits at the same depth as a library (direct child of a profile), so the ownership chain is the shorterplaylist → profile → user.Changes
20260530000004_playlist.sql—playlisttable withid BIGSERIAL,profile_id BIGINT NOT NULL REFERENCES profile(id) ON DELETE CASCADE, brand defaultsDEFAULT 'violet'/DEFAULT 'music'mirroring the desktop. All numeric flags (is_smart,cover_is_auto,position) are BIGINT so they round-trip intoPlaylist.<field>: i64from waveflow-core without the SMALLINT narrowing trap (same lesson astrack.ratingin 1.b.5b).cover_is_auto DEFAULT 1matches the desktop convention (auto-managed slot, waveflow#188 CR). Composite index(profile_id, position ASC, updated_at DESC)matches the ORDER BY inlist_for_profileand covers the FK fan-out.src/api/playlists.rs— 5 verbs with full#[utoipa::path]annotations including 400. Wire formatPlaylistResponsedropsprofile_id(URL-derived) andcover_path(server stub) — keeps the rest in lockstep with the desktopPlaylistDTO.nametrimmed and rejected when blank on POST; PATCH applies the sameSome("") / Some(" ")rejection (None stays legitimate, COALESCE preserves).src/api/mod.rs—playlists_routergated identically to libraries / tracks: 503 in prod,require_user_idwhenWAVEFLOW_DEV_AUTH=1.tests/playlists.rs— 11 integration tests:is_smart=0,smart_rules=NULL,cover_is_auto=1,cover_hash=NULL,track_count=0,total_duration_ms=0. Drift here would silently break a future server-side smart-playlist or auto-cover pipeline — same kind of bug the waveflow#188 CR caught.tests/ready.rs+tests/openapi.rs—playlisttable existence canary + path-presence assertions.Test plan
cargo check --all-targetscargo fmt --all --checkcargo clippy --all-targets -- -D warningscargo test --all(CI runs this against a Postgres service container)Refs: waveflow#188, waveflow-server#8, waveflow-server#9, RFC-001 §6.5. Closes Phase 1.b.5.
Summary by CodeRabbit
New Features
Tests