Skip to content

feat(core): tenant-scoped PostgresTrackRepository (Phase 1.b.5b) - #186

Merged
InstaZDLL merged 2 commits into
mainfrom
feat/postgres-track-tenant-methods
May 30, 2026
Merged

feat(core): tenant-scoped PostgresTrackRepository (Phase 1.b.5b)#186
InstaZDLL merged 2 commits into
mainfrom
feat/postgres-track-tenant-methods

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Tier 3 of the multi-tenant chain (track β†’ library β†’ profile β†’ user). Same design as PostgresLibraryRepository (#185), one level deeper. Does NOT implement TrackRepository β€” the single-tenant trait has no notion of tenancy and a dispatch over this backend would let user A read user B's tracks.

Changes

  • repository/track.rs β€” New TrackDraft (insert payload) and TrackUpdate (PATCH payload) types. Insert covers the columns the Postgres repo writes; joined columns (album_id, primary_artist, artwork_id) are absent until the album / artist / artwork tables ship on the server. Update is intentionally narrow β€” title, track_number, disc_number, year, rating β€” the realistic hand-edit surface.
  • repository/postgres/track.rs β€” PostgresTrackRepository with 5 *_for_library(library_id, profile_id, user_id) inherent methods:
    • list_for_library β€” ORDER BY added_at DESC, EXISTS clause walks library β†’ profile β†’ user
    • get_for_library β€” Option<TrackRow>, blurs missing / foreign-library / foreign-profile / foreign-user into a single None
    • insert_for_library β€” INSERT … SELECT FROM library JOIN profile WHERE … AND p.user_id = $, atomic with the ownership check; returns the row via RETURNING with NULL casts for the joined columns
    • update_for_library β€” UPDATE … RETURNING * with COALESCE per field, race-free against concurrent delete
    • delete_for_library β€” bool, same no-leak blur
  • Joined columns project as NULL::<pg_type> casts so the wire TrackRow shape stays identical to the desktop β€” the client doesn't adapt when those tables land.

Test plan

  • cargo check --manifest-path src-tauri/Cargo.toml --workspace --all-targets
  • cargo fmt --all --check
  • cargo clippy --manifest-path src-tauri/Cargo.toml --workspace --all-targets -- -D warnings
  • cargo test --manifest-path src-tauri/Cargo.toml --workspace (45 passed)

The Postgres methods themselves get exercised by integration tests in waveflow-server (next PR β€” 1.b.5b-PR-B).

Refs: RFC-001 Β§6.5, follows the pattern established by #185.

Summary by CodeRabbit

  • New Features

    • Ajout d’un gestionnaire de pistes serveur offrant crΓ©ation, lecture, mise Γ  jour et suppression des pistes avec contrΓ΄le d’accΓ¨s scindΓ© par bibliothΓ¨que/profil/utilisateur.
    • Introduction de payloads d’insertion et de mise Γ  jour pour manipuler les mΓ©tadonnΓ©es des pistes (champs obligatoires et optionnels, mises Γ  jour partielles).
  • Chores

    • Exposition publique du module de gestion des pistes pour l’utilisation par le backend.

Review Change Stack

Same pattern as PostgresLibraryRepository, one level deeper. Every
method takes (track_id, library_id, profile_id, user_id) and the SQL
validates the full track -> library -> profile -> user ownership
chain inline. The repository does NOT implement the single-tenant
TrackRepository trait β€” that surface has no notion of tenancy and a
trait dispatch over this backend would let user A read user B's
tracks.

- New TrackDraft + TrackUpdate types in repository/track.rs. Insert
  payload mirrors the columns the Postgres repo writes (joined
  album / artist / artwork columns are absent because the server's
  1.b.5b schema doesn't ship those tables yet). Update payload is
  intentionally narrow β€” title + ordering fields + rating, the
  realistic hand-edit surface; bitrate / file_size / etc. stay
  scan-derived.
- New postgres/track.rs:
  - list_for_library: ORDER BY added_at DESC, EXISTS clause walks
    library -> profile -> user
  - get_for_library: Option<TrackRow>, no existence leak across
    library / profile / user boundaries
  - insert_for_library: INSERT ... SELECT FROM library JOIN profile
    WHERE ... AND p.user_id = $, atomic with the ownership check,
    returns the freshly-inserted row via RETURNING with NULL casts
    for the joined columns
  - update_for_library: UPDATE ... RETURNING * with COALESCE on
    every patch field, race-free against concurrent delete
  - delete_for_library: returns bool, no-leak blur same as get
- Joined columns (album_id, album_title, artist_*, artwork_*) are
  projected as `NULL::<pg_type>` casts so the wire shape stays
  identical to the desktop's TrackRow β€” the client doesn't need to
  adapt when album / artist / artwork tables ship on the server.

Wired in repository/postgres/mod.rs alongside PostgresLibraryRepository
+ PostgresProfileRepository. waveflow-server consumes this via its
existing waveflow-core git dep.

Validated:
- cargo check --workspace --all-targets
- cargo fmt --all --check
- cargo clippy --workspace --all-targets -- -D warnings
- cargo test --workspace (45 passed)

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@InstaZDLL InstaZDLL added scope: backend Rust/Tauri backend (src-tauri/) type: feat New feature size: l 200-500 lines labels May 30, 2026
@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: e1dc1300-82cc-49d4-b813-387a58521bba

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5c58b4a and 11c07f5.

πŸ“’ Files selected for processing (2)
  • src-tauri/crates/core/src/repository/postgres/track.rs
  • src-tauri/crates/core/src/repository/track.rs

πŸ“ Walkthrough

Walkthrough

Ce PR ajoute TrackDraft et TrackUpdate, implémente PostgresTrackRepository (construction + list/get/insert/update/delete) avec validation tenant-scopée via EXISTS (track→library→profile→user) et réexporte le dépôt depuis repository/postgres.

Changes

ImplΓ©mentation du dΓ©pΓ΄t de pistes multi-tenant

Layer / File(s) Summary
Data transfer objects
src-tauri/crates/core/src/repository/track.rs
DΓ©finit TrackDraft (payload d'insertion avec champs requis et mΓ©tadonnΓ©es optionnelles incluant now_ms) et TrackUpdate (patch partiel avec tous champs optionnels).
Repository structure and SQL projection
src-tauri/crates/core/src/repository/postgres/track.rs
DΓ©clare PostgresTrackRepository (injection PgPool) et la constante SQL SELECT_TRACK_ROW projetant TrackRow avec NULLs castΓ©s pour jointures absentes.
Read operations with tenant enforcement
src-tauri/crates/core/src/repository/postgres/track.rs
ImplΓ©mente list_for_library et get_for_library avec clause EXISTS pour garantir ownership et renvoi de Vec<TrackRow> / Option<TrackRow>.
Mutation operations with tenant enforcement
src-tauri/crates/core/src/repository/postgres/track.rs
ImplΓ©mente insert_for_library (INSERT…SELECT tenant-validated + RETURNING), update_for_library (mise Γ  jour partielle via COALESCE + RETURNING), et delete_for_library (DELETE tenant-scoped, retourne bool selon rows_affected).
Public module exports
src-tauri/crates/core/src/repository/postgres/mod.rs
Ajoute pub mod track; et pub use track::PostgresTrackRepository; pour exposer le dΓ©pΓ΄t publiquement.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • InstaZDLL/WaveFlow#181: LiΓ© Γ  l'infrastructure Postgres/sqlx et aux exports de repository/postgres/mod.rs utilisΓ©s ici.

Poem

🎢 Une piste arrive, contrôlée par le pool,
Le SQL veille: EXISTS garde le rΓ΄le,
INSERT, UPDATE, DELETE marchent Γ  l’unisson,
DTOs en poche, le repo fait sa mission,
Quiet CRUD, mais tenant-scoped, quel contrΓ΄le.

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Title check βœ… Passed Le titre suit la convention Conventional Commits avec un scope kebab-case (core) et dΓ©crit prΓ©cisΓ©ment l'ajout principal: le repositoraire tenant-scoped PostgresTrackRepository.
Description check βœ… Passed La description couvre tous les Γ©lΓ©ments clΓ©s: rΓ©sumΓ© dΓ©taillΓ© des changements, justification de la conception, plan de test avec rΓ©sultats concrets, et rΓ©fΓ©rences aux normes du projet.
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/postgres-track-tenant-methods

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

@InstaZDLL InstaZDLL self-assigned this May 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

πŸ€– 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.

Inline comments:
In `@src-tauri/crates/core/src/repository/postgres/track.rs`:
- Around line 75-308: Add unit/integration tests that assert tenant isolation
for the PostgresTrackRepository methods list_for_library, get_for_library,
insert_for_library, update_for_library, and delete_for_library: create fixtures
for two users/profiles/libraries and verify that calls from a non-owning
user/profile/library return an empty Vec / Ok(None) / Ok(false) and that
insert_for_library returns None when the (library, profile, user) chain does not
match; also include positive cases where the owning chain succeeds. Target tests
to exercise the full chain (track β†’ library β†’ profile β†’ user) and use the same
SQL-backed setup (PgPool) used by the repository so the EXISTS guards in each
method are actually validated.

In `@src-tauri/crates/core/src/repository/track.rs`:
- Around line 55-58: TrackUpdate.rating is currently Option<i64> but must be
constrained to 0..=255; change the type to Option<u8> in the TrackUpdate struct
(src/.../track.rs) and update any places binding patch.rating to the SQL
(repository code that executes "rating = COALESCE($5, rating)") so they use the
new u8-typed value, or add a pre-UPDATE validation to reject values outside
0..=255; additionally add a DB-level CHECK constraint in the migration
20260428000001_track_rating.sql to define rating INTEGER CHECK (rating BETWEEN 0
AND 255) so out-of-range values cannot be persisted.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 721ca734-2829-4621-95e1-7074d1f537e0

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 0473647 and 5c58b4a.

πŸ“’ Files selected for processing (3)
  • src-tauri/crates/core/src/repository/postgres/mod.rs
  • src-tauri/crates/core/src/repository/postgres/track.rs
  • src-tauri/crates/core/src/repository/track.rs

Comment thread src-tauri/crates/core/src/repository/postgres/track.rs
Comment thread src-tauri/crates/core/src/repository/track.rs Outdated
Aligns with the existing TrackRepository::set_rating(rating:
Option<u8>) convention so the 0..=255 invariant is enforced at the
type level β€” serde rejects 256+ at the HTTP deserialization boundary
before the patch ever reaches the repository.

`u8` isn't natively bindable on Postgres (no unsigned integer types),
so the SQL bind widens to i64 via `i64::from`. The cast is a no-op
semantically because the u8 already guarantees the range.

Defers the matching DB-level CHECK (rating BETWEEN 0 AND 255) to
1.b.5b-PR-B on waveflow-server, where the `track` migration actually
lives.

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

scope: backend Rust/Tauri backend (src-tauri/) size: l 200-500 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant