feat(core): tenant-scoped PostgresPlaylistRepository (Phase 1.b.5c)#188
Conversation
Same pattern as PostgresLibraryRepository β a playlist belongs
directly to a profile (not to a library), so the ownership chain is
`playlist -> profile -> user`, same depth as library. The repo does
NOT implement the single-tenant PlaylistRepository trait β that
surface has no notion of tenancy.
- New profile_id: i64 field on Playlist with #[sqlx(default)] for
cross-backend compatibility. Same pattern as Library.profile_id.
Desktop call site in commands/playlist.rs initializes it to 0
(single-tenant sentinel).
- New postgres/playlist.rs with 5 *_for_profile inherent methods:
- list_for_profile: ORDER BY position ASC, updated_at DESC
(matches the desktop sidebar order)
- get_for_profile: Option<Playlist>, no existence leak
- insert_for_profile: writes a custom playlist (is_smart=0,
smart_rules=NULL, position=0, cover_hash=NULL, cover_is_auto=0
β smart-playlist support isn't on the server yet)
- update_for_profile: UPDATE ... RETURNING with COALESCE
- delete_for_profile: bool
- Counts (track_count, total_duration_ms) projected as 0::bigint
until playlist_track ships on the server.
- cover_path projected as NULL::text (resolved app-side from
cover_hash, same pattern as the desktop SELECT).
Wired in repository/postgres/mod.rs alongside Library + Profile +
Track.
Validated:
- cargo check --workspace --all-targets
- cargo check --features postgres --all-targets (on the core crate
β catches the sqlx Postgres-only path that workspace check misses,
the lesson from #187)
- cargo clippy --features postgres --all-targets -- -D warnings
- cargo test --workspace (passes, no test regression)
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)
π WalkthroughWalkthroughAjout du champ ChangesSupport multi-tenant Postgres pour playlists
Sequence Diagram(s)sequenceDiagram
participant Cmd as create_playlist (command)
participant Repo as PostgresPlaylistRepository
participant PG as PostgreSQL
Cmd->>Repo: insert_for_profile(draft, profile_id, user_id)
Repo->>PG: INSERT ... SELECT ... WHERE EXISTS(profile.id = $profile_id AND profile.user_id = $user_id)
PG-->>Repo: 0 or 1 row
Repo-->>Cmd: Ok(Some(Playlist)) or Ok(None)
Estimated code review effortπ― 4 (Complex) | β±οΈ ~45 minutes Possibly related PRs
Suggested labels
π₯ 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: 1
π€ 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/playlist.rs`:
- Around line 139-182: The INSERT in insert_for_profile creates a playlist with
cover_hash = NULL but sets cover_is_auto = 0, which incorrectly marks a playlist
as having a manual cover; change the SQL in insert_for_profile so the SELECT
uses cover_is_auto = 1 for new playlists with no cover (i.e. set cover_is_auto
to 1 instead of 0 in the INSERT ... SELECT), making the returned Playlist
reflect that no user cover exists.
πͺ 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: 03317c94-fa7e-4771-9ba5-f6a235f75e60
π Files selected for processing (4)
src-tauri/crates/app/src/commands/playlist.rssrc-tauri/crates/core/src/domain/playlist.rssrc-tauri/crates/core/src/repository/postgres/mod.rssrc-tauri/crates/core/src/repository/postgres/playlist.rs
CR caught a sticky-flag bug: the previous insert_for_profile wrote cover_is_auto=0 alongside cover_hash=NULL, which incorrectly marks the row as "user uploaded their own cover" β a future server-side auto-cover pipeline would skip it. Desktop convention: - migration `DEFAULT 1` (auto-managed, ready for the auto-cover pipeline) - `commands/playlist.rs::create_playlist` writes `cover_is_auto: 1` - `commands/playlist_cover.rs` flips it to `0` only when the user uploads a manual cover, so the auto-regen path stops touching the row Aligning the Postgres insert with that convention so a freshly created playlist is correctly auto-managed from the start. Doc expanded to call out the rationale + the desktop parity. (Also rewrapped the doc paragraph to avoid a clippy doc_lazy_continuation lint that fired on a stray `+` at the start of a line β markdown was parsing it as a list bullet.) Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Summary
Final tier of Phase 1.b.5. A playlist belongs directly to a profile (not nested under library), so the ownership chain is the shorter
playlist β profile β userβ same depth as library, different parent. Same design asPostgresLibraryRepository(#185) andPostgresTrackRepository(#186): inherent methods only, noPlaylistRepositorytrait impl on this backend (tenancy would leak through a trait dispatch).Changes
domain/playlist.rsβ Newprofile_id: i64field with#[sqlx(default)]for cross-backend compatibility. Desktop call site incommands/playlist.rs::create_playlistinitializes it to0(single-tenant sentinel). Same pattern asLibrary.profile_id.repository/postgres/playlist.rsβPostgresPlaylistRepositorywith 5 inherent*_for_profile(playlist_id, profile_id, user_id)methods:list_for_profileβORDER BY position ASC, updated_at DESCmatches the desktop sidebar orderget_for_profileβOption<Playlist>, no existence leak across profile / user boundariesinsert_for_profileβ writes a custom playlist (is_smart=0, smart_rules=NULL, position=0, cover_hash=NULL, cover_is_auto=0); smart-playlist support isn't on the server yet, it stays in [crate::smart_playlists] consuming the desktop SQLite path until a later phase ports itupdate_for_profileβUPDATE β¦ RETURNING *withCOALESCE, race-free against concurrent deletedelete_for_profileβbool, no-leak blur same as gettrack_count/total_duration_msprojected as0::bigintuntilplaylist_trackships on the server.cover_pathprojected asNULL::textβ resolved app-side fromcover_hash, mirrors the desktop SELECT pattern.Test plan
cargo check --manifest-path src-tauri/Cargo.toml --workspace --all-targetscargo check --manifest-path src-tauri/crates/core/Cargo.toml --features postgres --all-targets(the Postgres-feature path the workspace check doesn't activate β lesson from fix(core): inline TrackRow SELECT in postgres list/get methodsΒ #187)cargo clippy --manifest-path src-tauri/crates/core/Cargo.toml --features postgres --all-targets -- -D warningscargo test --manifest-path src-tauri/Cargo.toml --workspace(no regression)The Postgres methods themselves get exercised by integration tests in waveflow-server (next PR β 1.b.5c-PR-B).
Refs: RFC-001 Β§6.5, follows the pattern established by #185, #186.
Summary by CodeRabbit
New Features
Refactor
Chores