feat(core): tenant-scoped PostgresTrackRepository (Phase 1.b.5b) - #186
Conversation
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>
|
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 (2)
π WalkthroughWalkthroughCe 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 ChangesImplΓ©mentation du dΓ©pΓ΄t de pistes multi-tenant
Estimated code review effortπ― 4 (Complex) | β±οΈ ~45 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 |
There was a problem hiding this comment.
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
π Files selected for processing (3)
src-tauri/crates/core/src/repository/postgres/mod.rssrc-tauri/crates/core/src/repository/postgres/track.rssrc-tauri/crates/core/src/repository/track.rs
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>
Summary
Tier 3 of the multi-tenant chain (
track β library β profile β user). Same design asPostgresLibraryRepository(#185), one level deeper. Does NOT implementTrackRepositoryβ 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β NewTrackDraft(insert payload) andTrackUpdate(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βPostgresTrackRepositorywith 5*_for_library(library_id, profile_id, user_id)inherent methods:list_for_libraryβORDER BY added_at DESC, EXISTS clause walkslibrary β profile β userget_for_libraryβOption<TrackRow>, blurs missing / foreign-library / foreign-profile / foreign-user into a singleNoneinsert_for_libraryβINSERT β¦ SELECT FROM library JOIN profile WHERE β¦ AND p.user_id = $, atomic with the ownership check; returns the row viaRETURNINGwithNULLcasts for the joined columnsupdate_for_libraryβUPDATE β¦ RETURNING *withCOALESCEper field, race-free against concurrent deletedelete_for_libraryβbool, same no-leak blurNULL::<pg_type>casts so the wireTrackRowshape 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-targetscargo fmt --all --checkcargo clippy --manifest-path src-tauri/Cargo.toml --workspace --all-targets -- -D warningscargo 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
Chores