Skip to content

feat(playlist): tenant-scoped CRUD nested under profile (Phase 1.b.5c-B) - #10

Merged
InstaZDLL merged 1 commit into
mainfrom
feat/playlist-crud-1-b-5c-b
May 30, 2026
Merged

feat(playlist): tenant-scoped CRUD nested under profile (Phase 1.b.5c-B)#10
InstaZDLL merged 1 commit into
mainfrom
feat/playlist-crud-1-b-5c-b

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented May 30, 2026

Copy link
Copy Markdown
Owner

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 PostgresPlaylistRepository from 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 shorter playlist → profile → user.

Changes

  • Migration 20260530000004_playlist.sqlplaylist table with id BIGSERIAL, profile_id BIGINT NOT NULL REFERENCES profile(id) ON DELETE CASCADE, brand defaults DEFAULT 'violet' / DEFAULT 'music' mirroring the desktop. All numeric flags (is_smart, cover_is_auto, position) are BIGINT so they round-trip into Playlist.<field>: i64 from waveflow-core without the SMALLINT narrowing trap (same lesson as track.rating in 1.b.5b). cover_is_auto DEFAULT 1 matches the desktop convention (auto-managed slot, 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.
  • src/api/playlists.rs — 5 verbs with full #[utoipa::path] annotations including 400. Wire format PlaylistResponse drops profile_id (URL-derived) and cover_path (server stub) — keeps the rest in lockstep with the desktop Playlist DTO.
  • Boundary validationname trimmed and rejected when blank on POST; PATCH applies the same Some("") / Some(" ") rejection (None stays legitimate, COALESCE preserves).
  • src/api/mod.rsplaylists_router gated identically to libraries / tracks: 503 in prod, require_user_id when WAVEFLOW_DEV_AUTH=1.
  • tests/playlists.rs — 11 integration tests:
    • Sticky-flag invariants on the freshly-created shape: 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.
    • 401 gate, default color/icon fall-back, explicit color/icon override, blank name → 400 + nothing persisted, foreign profile → 404
    • Tenant isolation matrix: user B can't see, GET, PATCH, or DELETE user A's playlist via either own profile or A's profile
    • Update round-trip, partial PATCH preservation (COALESCE), PATCH blank rejection with original preserved
    • Delete 204 → 404
    • profile CASCADE to playlists via still-owned profile proxy (the real cascade canary)
    • Prod-gate 503
  • tests/ready.rs + tests/openapi.rsplaylist table existence canary + path-presence assertions.

Test plan

  • cargo check --all-targets
  • cargo fmt --all --check
  • cargo clippy --all-targets -- -D warnings
  • cargo 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

    • Added playlist management functionality with complete CRUD operations (create, read, update, delete).
    • Users can now create and organize playlists with customizable properties including colors and icons.
    • Playlists support smart playlist configuration for advanced content organization.
  • Tests

    • Added comprehensive test coverage for playlist functionality and database schema validation.

Review Change Stack

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>
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d9248f17-7442-4000-bef2-6d1031f123e2

📥 Commits

Reviewing files that changed from the base of the PR and between 51c444a and 9f31177.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock, !*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • migrations/20260530000004_playlist.sql
  • src/api/mod.rs
  • src/api/playlists.rs
  • tests/openapi.rs
  • tests/playlists.rs
  • tests/ready.rs

📝 Walkthrough

Walkthrough

Ajout d'une API REST multi-tenant CRUD pour les playlists sous /api/v1/profiles/{profile_id}/playlists. Crée une table playlist avec FK en cascade vers profile, intègre un routeur tenant-scoped avec gating d'authentification dev, implémente cinq endpoints (list/create/get/update/delete) avec validation de name et 627 lignes de tests e2e couvrant authentification, isolation multi-tenant, cascade et production gating.

Changes

Playlist CRUD API with multi-tenant isolation

Layer / File(s) Summary
Upstream waveflow-core dependency update
Cargo.toml
waveflow-core git revision updated to latest commit while preserving postgres feature flag and default-features=false.
Playlist database schema and migration
migrations/20260530000004_playlist.sql, tests/ready.rs
Multi-tenant playlist table linked to profile with cascade deletion, smart playlist support, cover management fields, position-based ordering, and composite index. Migration test validates table creation via embedded MIGRATOR.
API routing and tenant-scoped middleware
src/api/mod.rs
Playlist module declared, conditional playlists_router constructed based on dev_auth_enabled config to apply require_user_id or reject_dev_auth_disabled middleware, then merged into OpenApiRouter with updated documentation.
Playlist CRUD endpoints and response contracts
src/api/playlists.rs
PlaylistResponse, CreatePlaylistRequest, UpdatePlaylistRequest types. Five handlers: list (filters by user/profile), create (validates trimmed name, applies color/icon defaults, 201/404/500), get (ownership check, 200/404/500), update (partial patch with name validation, 200/404/500), delete (returns 204/404/500). All distinguish 404 (not owned) from errors.
End-to-end tests and API validation
tests/playlists.rs, tests/openapi.rs
627-line test suite covering: authentication (401 without x-user-id), basic CRUD with defaults and invariants, explicit field persistence, name validation (400 on empty), multi-tenant isolation (404 across users, no leakage), partial PATCH field preservation, cascade deletion on profile removal, production gate (503 when auth disabled). OpenAPI assertions confirm playlist routes in spec.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • InstaZDLL/waveflow-server#8: Cargo.toml bumps same waveflow-core git rev and src/api/mod.rs extends same tenant-scoped router gating pattern (libraries vs playlists).
  • InstaZDLL/waveflow-server#6: Main PR reuses same config.dev_auth_enabled / X-User-Id middleware pattern and tenant-scoped resource isolation introduced by profiles CRUD.
  • InstaZDLL/waveflow-server#3: Main PR extends same sqlx embedded-migrations/MIGRATOR test harness by adding tests/ready.rs check for playlist table creation.

Poem

🎵 Des playlists prennent forme dans les profils,
Multi-tenant, isolées, imperméables aux fils,
Créer, lire, patcher, détruire en cascade—
Tests qui ne tremblent pas, validation en parade! 🎶

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Le titre décrit précisément le changement principal : implémentation du CRUD tenant-scoped pour les playlists imbriquées sous le profil, ce qui correspond exactement aux modifications du code et tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/playlist-crud-1-b-5c-b

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

@InstaZDLL InstaZDLL self-assigned this May 30, 2026
@InstaZDLL
InstaZDLL merged commit c66478e into main May 30, 2026
8 checks passed
@InstaZDLL
InstaZDLL deleted the feat/playlist-crud-1-b-5c-b branch May 30, 2026 16:52
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