diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 63dd40eb1..88d657f35 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -6,7 +6,6 @@ import ( "fmt" "maps" "slices" - "strconv" "strings" "sync" "time" @@ -185,42 +184,6 @@ func allRolesFilter(_ string, value interface{}) Sqlizer { return Like{"participants": fmt.Sprintf(`%%"%s"%%`, value)} } -func libraryIdFilter(field string, value interface{}) Sqlizer { - switch v := value.(type) { - case string: - // Handle single library ID as string - if libID, err := strconv.Atoi(v); err == nil { - return Eq{"library_id": libID} - } - return Eq{"library_id": v} - case int: - return Eq{"library_id": v} - case int64: - return Eq{"library_id": v} - case float64: - return Eq{"library_id": int(v)} - case []interface{}: - // Handle multiple library IDs - var ids []interface{} - for _, id := range v { - switch idVal := id.(type) { - case string: - if libID, err := strconv.Atoi(idVal); err == nil { - ids = append(ids, libID) - } else { - ids = append(ids, idVal) - } - case int, int64, float64: - ids = append(ids, idVal) - } - } - if len(ids) > 0 { - return Eq{"library_id": ids} - } - } - return Eq{"library_id": value} -} - func (r *albumRepository) CountAll(options ...model.QueryOptions) (int64, error) { query := r.newSelect() query = r.withAnnotation(query, "album.id") diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 8cfed88b3..fcb188d03 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -96,6 +96,7 @@ var mediaFileFilter = sync.OnceValue(func() map[string]filterFunc { "genre_id": tagIDFilter, "missing": booleanFilter, "artists_id": artistFilter, + "library_id": libraryIdFilter, } // Add all album tags as filters for tag := range model.TagMappings() { diff --git a/persistence/playlist_repository.go b/persistence/playlist_repository.go index faf5b1c62..80d54f3e9 100644 --- a/persistence/playlist_repository.go +++ b/persistence/playlist_repository.go @@ -379,6 +379,7 @@ func (r *playlistRepository) refreshCounters(pls *model.Playlist) error { } func (r *playlistRepository) loadTracks(sel SelectBuilder, id string) (model.PlaylistTracks, error) { + sel = r.applyLibraryFilter(sel, "f") tracksQuery := sel. Columns( "coalesce(starred, 0) as starred", diff --git a/persistence/playlist_track_repository.go b/persistence/playlist_track_repository.go index 80925aa88..ebd72c733 100644 --- a/persistence/playlist_track_repository.go +++ b/persistence/playlist_track_repository.go @@ -47,7 +47,8 @@ func (r *playlistRepository) Tracks(playlistId string, refreshSmartPlaylist bool p.db = r.db p.tableName = "playlist_tracks" p.registerModel(&model.PlaylistTrack{}, map[string]filterFunc{ - "missing": booleanFilter, + "missing": booleanFilter, + "library_id": libraryIdFilter, }) p.setSortMappings( map[string]string{ diff --git a/persistence/sql_base_repository.go b/persistence/sql_base_repository.go index be7848636..461609042 100644 --- a/persistence/sql_base_repository.go +++ b/persistence/sql_base_repository.go @@ -199,9 +199,14 @@ func (r sqlRepository) applyFilters(sq SelectBuilder, options ...model.QueryOpti return sq } +// libraryIdFilter is a filter function to be added to resources that have a library_id column. +func libraryIdFilter(_ string, value interface{}) Sqlizer { + return Eq{"library_id": value} +} + // applyLibraryFilter adds library filtering to queries for tables that have a library_id column // This ensures users only see content from libraries they have access to -func (r sqlRepository) applyLibraryFilter(sq SelectBuilder) SelectBuilder { +func (r sqlRepository) applyLibraryFilter(sq SelectBuilder, tableName ...string) SelectBuilder { user := loggedUser(r.ctx) // Admin users see all content @@ -216,9 +221,14 @@ func (r sqlRepository) applyLibraryFilter(sq SelectBuilder) SelectBuilder { return sq.Where(Eq{"1": "0"}) } + table := r.tableName + if len(tableName) > 0 { + table = tableName[0] + } + // Use subquery to filter by user's library access // This approach doesn't require DataStore in context - return sq.Where(Expr(r.tableName+".library_id IN ("+ + return sq.Where(Expr(table+".library_id IN ("+ "SELECT ul.library_id FROM user_library ul WHERE ul.user_id = ?)", userID)) } diff --git a/ui/src/dataProvider/wrapperDataProvider.js b/ui/src/dataProvider/wrapperDataProvider.js index c355d86d6..74d68a5f4 100644 --- a/ui/src/dataProvider/wrapperDataProvider.js +++ b/ui/src/dataProvider/wrapperDataProvider.js @@ -18,40 +18,48 @@ const getSelectedLibraries = () => { } } +// Function to apply library filtering to appropriate resources +const applyLibraryFilter = (resource, params) => { + // Content resources that should be filtered by selected libraries + const filteredResources = ['album', 'song', 'playlistTrack'] + + // Get selected libraries from localStorage + const selectedLibraries = getSelectedLibraries() + + // Add library filter for content resources if libraries are selected + if (filteredResources.includes(resource) && selectedLibraries.length > 0) { + if (!params.filter) { + params.filter = {} + } + params.filter.library_id = selectedLibraries + } + + return params +} + const mapResource = (resource, params) => { switch (resource) { + // /api/playlistTrack?playlist_id=123 => /api/playlist/123/tracks case 'playlistTrack': { - // /api/playlistTrack?playlist_id=123 => /api/playlist/123/tracks + params.filter = params.filter || {} + let plsId = '0' - if (params.filter) { - plsId = params.filter.playlist_id - if (!isAdmin()) { - params.filter.missing = false - } + plsId = params.filter.playlist_id + if (!isAdmin()) { + params.filter.missing = false } + params = applyLibraryFilter(resource, params) + return [`playlist/${plsId}/tracks`, params] } case 'album': case 'song': - case 'artist': - case 'playlist': { - // Content resources that should be filtered by selected libraries - const contentResources = ['album', 'song', 'artist', 'playlist'] - - // Get selected libraries from localStorage - const selectedLibraries = getSelectedLibraries() - - if (params.filter && !isAdmin()) { + case 'artist': { + params.filter = params.filter || {} + if (!isAdmin()) { params.filter.missing = false } - - // Add library filter for content resources if libraries are selected - if (contentResources.includes(resource) && selectedLibraries.length > 0) { - if (!params.filter) { - params.filter = {} - } - params.filter.library_id = selectedLibraries - } + params = applyLibraryFilter(resource, params) return [resource, params] }