mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-27 21:57:17 +01:00
feat: implement multi-library support for GetAlbumList and GetAlbumList2 endpoints
- Enhanced selectedMusicFolderIds helper to validate and filter library IDs - Added ApplyLibraryFilter function in filter/filters.go for library filtering - Updated getAlbumList to support musicFolderId parameter filtering - Added comprehensive tests for multi-library functionality - Supports single and multiple musicFolderId values - Falls back to all accessible libraries when no musicFolderId provided - Validates library access permissions for user security
This commit is contained in:
parent
bbc6d0722c
commit
b9e7b7cad7
3 changed files with 148 additions and 0 deletions
|
|
@ -61,6 +61,10 @@ func (api *Router) getAlbumList(r *http.Request) (model.Albums, int64, error) {
|
|||
return nil, 0, newError(responses.ErrorGeneric, "type '%s' not implemented", typ)
|
||||
}
|
||||
|
||||
// Get optional library IDs from musicFolderId parameter
|
||||
musicFolderIds, _ := selectedMusicFolderIds(r, false)
|
||||
opts = filter.ApplyLibraryFilter(opts, musicFolderIds)
|
||||
|
||||
opts.Offset = p.IntOr("offset", 0)
|
||||
opts.Max = min(p.IntOr("size", 10), 500)
|
||||
albums, err := api.ds.Album(r.Context()).GetAll(opts)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
|
@ -63,6 +64,74 @@ var _ = Describe("Album Lists", func() {
|
|||
errors.As(err, &subErr)
|
||||
Expect(subErr.code).To(Equal(responses.ErrorGeneric))
|
||||
})
|
||||
|
||||
Context("with musicFolderId parameter", func() {
|
||||
var user model.User
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
user = model.User{
|
||||
ID: "test-user",
|
||||
Libraries: []model.Library{
|
||||
{ID: 1, Name: "Library 1"},
|
||||
{ID: 2, Name: "Library 2"},
|
||||
{ID: 3, Name: "Library 3"},
|
||||
},
|
||||
}
|
||||
ctx = request.WithUser(context.Background(), user)
|
||||
})
|
||||
|
||||
It("should filter albums by specific library when musicFolderId is provided", func() {
|
||||
r := newGetRequest("type=newest", "musicFolderId=1")
|
||||
r = r.WithContext(ctx)
|
||||
mockRepo.SetData(model.Albums{
|
||||
{ID: "1"}, {ID: "2"},
|
||||
})
|
||||
|
||||
resp, err := router.GetAlbumList(w, r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.AlbumList.Album).To(HaveLen(2))
|
||||
// Verify that library filter was applied
|
||||
query, args, _ := mockRepo.Options.Filters.ToSql()
|
||||
Expect(query).To(ContainSubstring("library_id IN (?)"))
|
||||
Expect(args).To(ContainElement(1))
|
||||
})
|
||||
|
||||
It("should filter albums by multiple libraries when multiple musicFolderId are provided", func() {
|
||||
r := newGetRequest("type=newest", "musicFolderId=1", "musicFolderId=2")
|
||||
r = r.WithContext(ctx)
|
||||
mockRepo.SetData(model.Albums{
|
||||
{ID: "1"}, {ID: "2"},
|
||||
})
|
||||
|
||||
resp, err := router.GetAlbumList(w, r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.AlbumList.Album).To(HaveLen(2))
|
||||
// Verify that library filter was applied
|
||||
query, args, _ := mockRepo.Options.Filters.ToSql()
|
||||
Expect(query).To(ContainSubstring("library_id IN (?,?)"))
|
||||
Expect(args).To(ContainElements(1, 2))
|
||||
})
|
||||
|
||||
It("should return all accessible albums when no musicFolderId is provided", func() {
|
||||
r := newGetRequest("type=newest")
|
||||
r = r.WithContext(ctx)
|
||||
mockRepo.SetData(model.Albums{
|
||||
{ID: "1"}, {ID: "2"},
|
||||
})
|
||||
|
||||
resp, err := router.GetAlbumList(w, r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.AlbumList.Album).To(HaveLen(2))
|
||||
// Verify that library filter was applied
|
||||
query, args, _ := mockRepo.Options.Filters.ToSql()
|
||||
Expect(query).To(ContainSubstring("library_id IN (?,?,?)"))
|
||||
Expect(args).To(ContainElements(1, 2, 3))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetAlbumList2", func() {
|
||||
|
|
@ -100,5 +169,65 @@ var _ = Describe("Album Lists", func() {
|
|||
errors.As(err, &subErr)
|
||||
Expect(subErr.code).To(Equal(responses.ErrorGeneric))
|
||||
})
|
||||
|
||||
Context("with musicFolderId parameter", func() {
|
||||
var user model.User
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
user = model.User{
|
||||
ID: "test-user",
|
||||
Libraries: []model.Library{
|
||||
{ID: 1, Name: "Library 1"},
|
||||
{ID: 2, Name: "Library 2"},
|
||||
{ID: 3, Name: "Library 3"},
|
||||
},
|
||||
}
|
||||
ctx = request.WithUser(context.Background(), user)
|
||||
})
|
||||
|
||||
It("should filter albums by specific library when musicFolderId is provided", func() {
|
||||
r := newGetRequest("type=newest", "musicFolderId=1")
|
||||
r = r.WithContext(ctx)
|
||||
mockRepo.SetData(model.Albums{
|
||||
{ID: "1"}, {ID: "2"},
|
||||
})
|
||||
|
||||
resp, err := router.GetAlbumList2(w, r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.AlbumList2.Album).To(HaveLen(2))
|
||||
// Verify that library filter was applied
|
||||
Expect(mockRepo.Options.Filters).ToNot(BeNil())
|
||||
})
|
||||
|
||||
It("should filter albums by multiple libraries when multiple musicFolderId are provided", func() {
|
||||
r := newGetRequest("type=newest", "musicFolderId=1", "musicFolderId=2")
|
||||
r = r.WithContext(ctx)
|
||||
mockRepo.SetData(model.Albums{
|
||||
{ID: "1"}, {ID: "2"},
|
||||
})
|
||||
|
||||
resp, err := router.GetAlbumList2(w, r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.AlbumList2.Album).To(HaveLen(2))
|
||||
// Verify that library filter was applied
|
||||
Expect(mockRepo.Options.Filters).ToNot(BeNil())
|
||||
})
|
||||
|
||||
It("should return all accessible albums when no musicFolderId is provided", func() {
|
||||
r := newGetRequest("type=newest")
|
||||
r = r.WithContext(ctx)
|
||||
mockRepo.SetData(model.Albums{
|
||||
{ID: "1"}, {ID: "2"},
|
||||
})
|
||||
|
||||
resp, err := router.GetAlbumList2(w, r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.AlbumList2.Album).To(HaveLen(2))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -123,6 +123,21 @@ func SongsByArtistTitleWithLyricsFirst(artist, title string) Options {
|
|||
})
|
||||
}
|
||||
|
||||
func ApplyLibraryFilter(opts Options, musicFolderIds []int) Options {
|
||||
if len(musicFolderIds) == 0 {
|
||||
return opts
|
||||
}
|
||||
|
||||
libraryFilter := Eq{"library_id": musicFolderIds}
|
||||
if opts.Filters == nil {
|
||||
opts.Filters = libraryFilter
|
||||
} else {
|
||||
opts.Filters = And{opts.Filters, libraryFilter}
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func ByGenre(genre string) Options {
|
||||
return addDefaultFilters(Options{
|
||||
Sort: "name",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue