Skip to content

feat(track): tenant-scoped CRUD nested under library (Phase 1.b.5b-B) - #9

Merged
InstaZDLL merged 2 commits into
mainfrom
feat/track-crud-1-b-5b-b
May 30, 2026
Merged

feat(track): tenant-scoped CRUD nested under library (Phase 1.b.5b-B)#9
InstaZDLL merged 2 commits into
mainfrom
feat/track-crud-1-b-5b-b

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Tier 3 of the multi-tenant chain on the wire — /api/v1/profiles/{profile_id}/libraries/{library_id}/tracks/*. Wires the PostgresTrackRepository from 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 walks track → library → profile → user ownership inline.

Changes

  • Migration 20260530000003_track.sqltrack table with id 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 the Option<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 format TrackResponse drops the joined album/artist/artwork columns since they're always null until those tables ship.
  • Boundary validationtitle and file_path trimmed and rejected when blank on POST; title on PATCH applies the same Some("") / Some(" ") rejection (None stays legitimate, COALESCE preserves).
  • src/api/mod.rstracks_router gated identically to libraries: 503 in prod, require_user_id when WAVEFLOW_DEV_AUTH=1.
  • tests/tracks.rs — 12 integration tests:
    • 401 gate, blank title / file_path 400, rating=256 client error
    • 404 on foreign library via own profile + via foreign profile
    • Full proxy-attack matrix for tenant isolation: user B tries to access user A's track via every (proxy_profile, proxy_library) combination — none should leak
    • Update round-trip preserves omitted fields (COALESCE), PATCH blank title rejection with original preserved via follow-up GET
    • Delete 204 then 404
    • library CASCADE to tracks via still-owned library proxy (the real cascade canary)
    • profile CASCADE through library to tracks — transitive chain canary
    • Duplicate file_path under same library currently 5xx (locked in so a future shift to 409 is explicit)
    • Prod-gate 503
  • tests/ready.rs + tests/openapi.rs — track table existence canary + path-presence assertions on the new OpenAPI routes.

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#186, waveflow#187, waveflow-server#8, RFC-001 §6.5.

Summary by CodeRabbit

  • New Features

    • API complète de gestion des pistes (tracks) : création, consultation, mise à jour et suppression par bibliothèque, avec contrôle d’accès.
  • Tests

    • Tests bout-à-bout couvrant CRUD, validations, isolation multi-tenant et cascade de suppression.
    • Test de migration confirmant la création de la table de pistes.
  • Chores

    • Mise à jour d’une dépendance interne.
  • Bug fixes / Remarques

    • Création en double sur même chemin renvoie actuellement une erreur serveur (comportement observé).

Review Change Stack

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>
@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: 02a6aa2c-e2d2-4af6-aa15-66d9ea9f220b

📥 Commits

Reviewing files that changed from the base of the PR and between bd8ae9c and 299f537.

📒 Files selected for processing (1)
  • migrations/20260530000003_track.sql

📝 Walkthrough

Walkthrough

Le PR ajoute la table SQL track, un routeur et cinq handlers CRUD pour la ressource imbriquée /api/v1/profiles/{profile_id}/libraries/{library_id}/tracks, met à jour le router global et ajoute des tests OpenAPI et E2E vérifiant validations, isolation multi-tenant et cascades.

Changes

Tracks feature: schema, API, and tests

Layer / File(s) Summary
Dependency update
Cargo.toml
Mise à jour de la révision Git de la dépendance waveflow-core (features inchangées).
Database schema and migration
migrations/20260530000003_track.sql, Cargo.toml
Création de la table track (PK id), FK library_id ON DELETE CASCADE, colonnes fichier/métadonnées, added_at, rating CHECK(0..255), UNIQUE(library_id,file_path) et index (library_id, added_at DESC).
API module docs and router wiring
src/api/mod.rs
Documentation actualisée et montage conditionnel du tracks router dans OpenApiRouter derrière le gate dev_auth_enabled.
API types, DTOs and router
src/api/tracks.rs, src/api/mod.rs
Ajout de TrackResponse, CreateTrackRequest, UpdateTrackRequest et définition du OpenApiRouter pour les endpoints tracks.
Handler implementations
src/api/tracks.rs
Cinq handlers CRUD (list/create/get/update/delete) : extraction user/profile/library, validation trim pour title/file_path, construction de Draft/Update, délégation à PostgresTrackRepository *_for_library, réponses 201/200/204/400/404/500 et journalisation des erreurs.
Tests and validation
tests/openapi.rs, tests/ready.rs, tests/tracks.rs
Assertions OpenAPI pour les chemins tracks, test de migration validant la création de public.track, et une suite E2E couvrant authentification (401), cycle CRUD, validations, isolation multi-tenant, cascades DB, doublons UNIQUE (actuellement 500), et gate dev-auth (503).

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • InstaZDLL/waveflow-server#8: Ajoute/étend le routage imbriqué libraries et partage le même pattern de montage conditionnel dans src/api/mod.rs.
  • InstaZDLL/waveflow-server#6: Établit la logique config.dev_auth_enabled et le shim d'auth development que ce PR étend aux routes tracks.
  • InstaZDLL/waveflow-server#4: Met en place l'infrastructure OpenAPI/routeur sur laquelle ce PR s'appuie pour fusionner des sous-routers.

Poem

🎶 Une table naît, les routes chantent,
Tracks capturées, tests qui s'alignent,
Cascade et isolation gardées,
Routers et DTOs bien câblés—
Le serveur apprend à écouter. 🎧

🚥 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 l'ajout principal : implémentation du CRUD multi-tenant pour les tracks imbriqué sous les bibliothèques, qui correspond exactement aux changements du diff (migration track, API REST complète avec handlers CRUD, tests d'intégration).
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/track-crud-1-b-5b-b

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

@InstaZDLL InstaZDLL self-assigned this May 30, 2026
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>
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