feat(track): tenant-scoped CRUD nested under library (Phase 1.b.5b-B) - #9
Conversation
Wires `PostgresTrackRepository` (waveflow#186 + hotfix #187) to a new
`/api/v1/profiles/{profile_id}/libraries/{library_id}/tracks/*`
resource. Same tenancy pattern as libraries, extended one level
deeper: path supplies (profile_id, library_id), middleware supplies
UserId, the repo SQL walks track -> library -> profile -> user
ownership inline. A foreign profile / library / track all 404 — no
existence leak.
- New migration `20260530000003_track.sql` (BIGSERIAL pk, library_id
FK with ON DELETE CASCADE, composite index on (library_id,
added_at DESC), UNIQUE (library_id, file_path). `rating SMALLINT
CHECK (rating BETWEEN 0 AND 255)` is defense in depth on top of
the `Option<u8>` type-level guarantee from waveflow-core).
- New `src/api/tracks.rs` with 5 verbs + full OpenAPI annotations
(200/201/204, 400, 401, 404, 500). Wire format drops the joined
album/artist/artwork columns (always null until those tables ship
on the server) — keeps the payload tight.
- Title + file_path trimmed and rejected when blank on POST; title
on PATCH gets the same Some("") / Some(" ") rejection (None
stays legitimate, COALESCE preserves).
- `src/api/mod.rs`: tracks_router gated identically to libraries_router
— 503 in prod, require_user_id when WAVEFLOW_DEV_AUTH=1.
- `tests/tracks.rs`: 12 integration tests including 401 gate, blank
title / file_path, out-of-range rating (256 rejected), foreign
library 404 on POST, full proxy-attack matrix for the tenant
isolation battery (profile_a+library_a, profile_b+library_a,
profile_a+library_b — none should leak A's track to B), update
round-trip with COALESCE field preservation, PATCH blank title
rejection, delete 204 then 404, library CASCADE to tracks,
profile CASCADE through library to tracks, duplicate file_path
current 5xx behaviour (locked in so a future 409 is explicit),
prod-gate 503.
- `tests/ready.rs`: track table existence canary.
- `tests/openapi.rs`: tracks collection + item path assertions.
- Cargo.toml bumps waveflow-core rev to 062c5509 (hotfix #187 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 selected for processing (1)
📝 WalkthroughWalkthroughLe PR ajoute la table SQL ChangesTracks feature: schema, API, and tests
Sequence Diagram(s)sequenceDiagram
participant Client
participant Handler as TracksHandler
participant Repo as PostgresTrackRepository
participant DB as PostgresDB
Client->>Handler: HTTP request (list/create/get/update/delete)
Handler->>Handler: extract user_id, profile_id, library_id, (track_id)
Handler->>Repo: call *_for_library(...)
Repo->>DB: SQL query with ownership checks
DB-->>Repo: rows / error
Repo-->>Handler: result or error
Handler-->>Client: HTTP 200/201/204/400/404/500
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 unit tests (beta)
Comment |
CI caught a sqlx 0.9 decode error on the PATCH round-trip test: SMALLINT cannot be narrowed to `Option<i64>` (the type on `waveflow_core::domain::track::TrackRow.rating`), so the `RETURNING … rating` projection from `UPDATE track …` returned a runtime type-mismatch and the handler 500'd. Earlier tests passed because the row's `rating` was still NULL (create + list + get with no PATCH) — Option<None> doesn't trigger the narrowing, so the bug only surfaced on the first PATCH that actually wrote a rating. Widening to BIGINT matches every other numeric column on this table (file_size, duration_ms, …) and lets the i64 read+write path stay cast-free at both ends. The CHECK (rating BETWEEN 0 AND 255) constraint is preserved — defense in depth on top of the TrackUpdate.rating: Option<u8> type-level guarantee from waveflow-core. The 6-byte storage delta vs SMALLINT isn't worth a ::bigint cast on every read site. The migration hasn't merged yet (still on this PR's branch), so an in-place edit doesn't violate the "migrations are immutable once merged" rule. Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Summary
Tier 3 of the multi-tenant chain on the wire —
/api/v1/profiles/{profile_id}/libraries/{library_id}/tracks/*. Wires thePostgresTrackRepositoryfrom waveflow#186 (plus hotfix waveflow#187 for the sqlx 0.9 SqlSafeStr issue) to a deeply-nested HTTP resource. Path supplies (profile_id, library_id), middleware supplies UserId, the repo SQL walkstrack → library → profile → userownership inline.Changes
20260530000003_track.sql—tracktable withid BIGSERIAL,library_id BIGINT NOT NULL REFERENCES library(id) ON DELETE CASCADE, file_path/file_size, title/duration_ms, ordering fields (track_number/disc_number/year), audio specs (bitrate/sample_rate/channels/bit_depth/codec/musical_key), added_at.rating SMALLINT CHECK (rating BETWEEN 0 AND 255)— defense in depth on top of theOption<u8>type-level guarantee in waveflow-core (promised in #186 CR reply). Composite index(library_id, added_at DESC)for per-tenant MRU + ON DELETE CASCADE fan-out coverage.UNIQUE (library_id, file_path).src/api/tracks.rs— 5 verbs (list/create/get/update/delete) with full#[utoipa::path]annotations including 400 (boundary validation) and 500. Wire formatTrackResponsedrops the joined album/artist/artwork columns since they're alwaysnulluntil those tables ship.titleandfile_pathtrimmed and rejected when blank on POST;titleon PATCH applies the sameSome("") / Some(" ")rejection (None stays legitimate, COALESCE preserves).src/api/mod.rs—tracks_routergated identically to libraries: 503 in prod,require_user_idwhenWAVEFLOW_DEV_AUTH=1.tests/tracks.rs— 12 integration tests:(proxy_profile, proxy_library)combination — none should leaktests/ready.rs+tests/openapi.rs— track table existence canary + path-presence assertions on the new OpenAPI routes.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#186, waveflow#187, waveflow-server#8, RFC-001 §6.5.
Summary by CodeRabbit
New Features
Tests
Chores
Bug fixes / Remarques