mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-30 06:57:57 +01:00
refactor library service to use REST repository pattern and remove CRUD operations
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
5c01eb06ae
commit
d2a2c43ef5
11 changed files with 476 additions and 406 deletions
170
core/library.go
170
core/library.go
|
|
@ -2,9 +2,12 @@ package core
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/deluan/rest"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
|
|
@ -12,17 +15,11 @@ import (
|
|||
|
||||
// Library provides business logic for library management and user-library associations
|
||||
type Library interface {
|
||||
// Library CRUD operations
|
||||
GetAll(ctx context.Context) (model.Libraries, error)
|
||||
Get(ctx context.Context, id int) (*model.Library, error)
|
||||
Create(ctx context.Context, library *model.Library) error
|
||||
Update(ctx context.Context, library *model.Library) error
|
||||
Delete(ctx context.Context, id int) error
|
||||
|
||||
// User-library association operations
|
||||
GetUserLibraries(ctx context.Context, userID string) (model.Libraries, error)
|
||||
SetUserLibraries(ctx context.Context, userID string, libraryIDs []int) error
|
||||
ValidateLibraryAccess(ctx context.Context, userID string, libraryID int) error
|
||||
|
||||
NewRepository(ctx context.Context) rest.Repository
|
||||
}
|
||||
|
||||
type libraryService struct {
|
||||
|
|
@ -34,46 +31,6 @@ func NewLibrary(ds model.DataStore) Library {
|
|||
return &libraryService{ds: ds}
|
||||
}
|
||||
|
||||
// Library CRUD operations
|
||||
|
||||
func (s *libraryService) GetAll(ctx context.Context) (model.Libraries, error) {
|
||||
return s.ds.Library(ctx).GetAll()
|
||||
}
|
||||
|
||||
func (s *libraryService) Get(ctx context.Context, id int) (*model.Library, error) {
|
||||
return s.ds.Library(ctx).Get(id)
|
||||
}
|
||||
|
||||
func (s *libraryService) Create(ctx context.Context, library *model.Library) error {
|
||||
if err := s.validateLibrary(library); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.ds.Library(ctx).Put(library)
|
||||
}
|
||||
|
||||
func (s *libraryService) Update(ctx context.Context, library *model.Library) error {
|
||||
if err := s.validateLibrary(library); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify library exists
|
||||
if _, err := s.ds.Library(ctx).Get(library.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.ds.Library(ctx).Put(library)
|
||||
}
|
||||
|
||||
func (s *libraryService) Delete(ctx context.Context, id int) error {
|
||||
// Verify library exists
|
||||
if _, err := s.ds.Library(ctx).Get(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.ds.Library(ctx).Delete(id)
|
||||
}
|
||||
|
||||
// User-library association operations
|
||||
|
||||
func (s *libraryService) GetUserLibraries(ctx context.Context, userID string) (model.Libraries, error) {
|
||||
|
|
@ -139,20 +96,110 @@ func (s *libraryService) ValidateLibraryAccess(ctx context.Context, userID strin
|
|||
return fmt.Errorf("%w: user does not have access to library %d", model.ErrNotAuthorized, libraryID)
|
||||
}
|
||||
|
||||
// REST repository wrapper
|
||||
|
||||
func (s *libraryService) NewRepository(ctx context.Context) rest.Repository {
|
||||
repo := s.ds.Library(ctx)
|
||||
wrapper := &libraryRepositoryWrapper{
|
||||
ctx: ctx,
|
||||
LibraryRepository: repo,
|
||||
Repository: repo.(rest.Repository),
|
||||
ds: s.ds,
|
||||
service: s,
|
||||
}
|
||||
return wrapper
|
||||
}
|
||||
|
||||
type libraryRepositoryWrapper struct {
|
||||
rest.Repository
|
||||
model.LibraryRepository
|
||||
ctx context.Context
|
||||
ds model.DataStore
|
||||
service *libraryService
|
||||
}
|
||||
|
||||
func (r *libraryRepositoryWrapper) Save(entity interface{}) (string, error) {
|
||||
lib := entity.(*model.Library)
|
||||
if err := r.validateLibrary(lib); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err := r.LibraryRepository.Put(lib)
|
||||
if err != nil {
|
||||
return "", r.mapError(err)
|
||||
}
|
||||
|
||||
return strconv.Itoa(lib.ID), nil
|
||||
}
|
||||
|
||||
func (r *libraryRepositoryWrapper) Update(id string, entity interface{}, cols ...string) error {
|
||||
lib := entity.(*model.Library)
|
||||
libID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid library ID: %s", id)
|
||||
}
|
||||
|
||||
lib.ID = libID
|
||||
if err := r.validateLibrary(lib); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify library exists
|
||||
if _, err := r.Get(libID); err != nil {
|
||||
return r.mapError(err)
|
||||
}
|
||||
|
||||
err = r.LibraryRepository.Put(lib)
|
||||
return r.mapError(err)
|
||||
}
|
||||
|
||||
func (r *libraryRepositoryWrapper) Delete(id string) error {
|
||||
libID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return rest.ValidationError{Errors: map[string]string{
|
||||
"id": "invalid library ID format",
|
||||
}}
|
||||
}
|
||||
|
||||
err = r.LibraryRepository.Delete(libID)
|
||||
return r.mapError(err)
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
func (s *libraryService) validateLibrary(library *model.Library) error {
|
||||
func (r *libraryRepositoryWrapper) mapError(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, model.ErrNotFound):
|
||||
return rest.ErrNotFound
|
||||
case errors.Is(err, model.ErrNotAuthorized):
|
||||
return rest.ErrPermissionDenied
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (r *libraryRepositoryWrapper) validateLibrary(library *model.Library) error {
|
||||
if library.Name == "" {
|
||||
return fmt.Errorf("%w: library name is required", model.ErrValidation)
|
||||
return rest.ValidationError{Errors: map[string]string{
|
||||
"name": "library name is required",
|
||||
}}
|
||||
}
|
||||
|
||||
if library.Path == "" {
|
||||
return fmt.Errorf("%w: library path is required", model.ErrValidation)
|
||||
return rest.ValidationError{Errors: map[string]string{
|
||||
"path": "library path is required",
|
||||
}}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *libraryService) validateLibraryIDs(ctx context.Context, libraryIDs []int) error {
|
||||
// Use CountAll with IN filter to efficiently check if all library IDs exist
|
||||
if len(libraryIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use CountAll to efficiently validate library IDs exist
|
||||
count, err := s.ds.Library(ctx).CountAll(model.QueryOptions{
|
||||
Filters: squirrel.Eq{"id": libraryIDs},
|
||||
})
|
||||
|
|
@ -161,24 +208,7 @@ func (s *libraryService) validateLibraryIDs(ctx context.Context, libraryIDs []in
|
|||
}
|
||||
|
||||
if int(count) != len(libraryIDs) {
|
||||
// Find which library IDs don't exist for better error message
|
||||
existingLibraries, err := s.ds.Library(ctx).GetAll(model.QueryOptions{
|
||||
Filters: squirrel.Eq{"id": libraryIDs},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting libraries: %w", err)
|
||||
}
|
||||
|
||||
libraryMap := make(map[int]bool)
|
||||
for _, lib := range existingLibraries {
|
||||
libraryMap[lib.ID] = true
|
||||
}
|
||||
|
||||
for _, libID := range libraryIDs {
|
||||
if !libraryMap[libID] {
|
||||
return fmt.Errorf("%w: library ID %d does not exist", model.ErrNotFound, libID)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%w: one or more library IDs are invalid", model.ErrValidation)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package core
|
||||
package core_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/deluan/rest"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
|
|
@ -12,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Library Service", func() {
|
||||
var service Library
|
||||
var service core.Library
|
||||
var ds *tests.MockDataStore
|
||||
var libraryRepo *tests.MockLibraryRepo
|
||||
var userRepo *tests.MockedUserRepo
|
||||
|
|
@ -27,60 +29,23 @@ var _ = Describe("Library Service", func() {
|
|||
ds.MockedLibrary = libraryRepo
|
||||
ds.MockedUser = userRepo
|
||||
|
||||
service = NewLibrary(ds)
|
||||
service = core.NewLibrary(ds)
|
||||
ctx = context.Background()
|
||||
})
|
||||
|
||||
Describe("Library CRUD Operations", func() {
|
||||
Describe("GetAll", func() {
|
||||
It("returns all libraries", func() {
|
||||
libraries := model.Libraries{
|
||||
{ID: 1, Name: "Library 1", Path: "/music1"},
|
||||
{ID: 2, Name: "Library 2", Path: "/music2"},
|
||||
}
|
||||
libraryRepo.SetData(libraries)
|
||||
var repo rest.Persistable
|
||||
|
||||
result, err := service.GetAll(ctx)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result).To(HaveLen(2))
|
||||
Expect(result[0].Name).To(Equal("Library 1"))
|
||||
Expect(result[1].Name).To(Equal("Library 2"))
|
||||
})
|
||||
|
||||
It("returns empty list when no libraries exist", func() {
|
||||
result, err := service.GetAll(ctx)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result).To(HaveLen(0))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Get", func() {
|
||||
It("returns a specific library", func() {
|
||||
library := &model.Library{ID: 1, Name: "Test Library", Path: "/music"}
|
||||
libraryRepo.SetData(model.Libraries{*library})
|
||||
|
||||
result, err := service.Get(ctx, 1)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.Name).To(Equal("Test Library"))
|
||||
Expect(result.Path).To(Equal("/music"))
|
||||
})
|
||||
|
||||
It("returns error when library not found", func() {
|
||||
_, err := service.Get(ctx, 999)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(model.ErrNotFound))
|
||||
})
|
||||
BeforeEach(func() {
|
||||
r := service.NewRepository(ctx)
|
||||
repo = r.(rest.Persistable)
|
||||
})
|
||||
|
||||
Describe("Create", func() {
|
||||
It("creates a new library successfully", func() {
|
||||
library := &model.Library{ID: 1, Name: "New Library", Path: "/new/music"}
|
||||
|
||||
err := service.Create(ctx, library)
|
||||
_, err := repo.Save(library)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(libraryRepo.Data[1].Name).To(Equal("New Library"))
|
||||
|
|
@ -90,7 +55,7 @@ var _ = Describe("Library Service", func() {
|
|||
It("fails when library name is empty", func() {
|
||||
library := &model.Library{Path: "/music"}
|
||||
|
||||
err := service.Create(ctx, library)
|
||||
_, err := repo.Save(library)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("library name is required"))
|
||||
|
|
@ -99,7 +64,7 @@ var _ = Describe("Library Service", func() {
|
|||
It("fails when library path is empty", func() {
|
||||
library := &model.Library{Name: "Test"}
|
||||
|
||||
err := service.Create(ctx, library)
|
||||
_, err := repo.Save(library)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("library path is required"))
|
||||
|
|
@ -116,7 +81,7 @@ var _ = Describe("Library Service", func() {
|
|||
It("updates an existing library successfully", func() {
|
||||
library := &model.Library{ID: 1, Name: "Updated Library", Path: "/updated"}
|
||||
|
||||
err := service.Update(ctx, library)
|
||||
err := repo.Update("1", library)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(libraryRepo.Data[1].Name).To(Equal("Updated Library"))
|
||||
|
|
@ -126,7 +91,7 @@ var _ = Describe("Library Service", func() {
|
|||
It("fails when library doesn't exist", func() {
|
||||
library := &model.Library{ID: 999, Name: "Non-existent", Path: "/path"}
|
||||
|
||||
err := service.Update(ctx, library)
|
||||
err := repo.Update("999", library)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(model.ErrNotFound))
|
||||
|
|
@ -135,7 +100,7 @@ var _ = Describe("Library Service", func() {
|
|||
It("fails when library name is empty", func() {
|
||||
library := &model.Library{ID: 1, Path: "/music"}
|
||||
|
||||
err := service.Update(ctx, library)
|
||||
err := repo.Update("1", library)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("library name is required"))
|
||||
|
|
@ -150,14 +115,14 @@ var _ = Describe("Library Service", func() {
|
|||
})
|
||||
|
||||
It("deletes an existing library successfully", func() {
|
||||
err := service.Delete(ctx, 1)
|
||||
err := repo.Delete("1")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(libraryRepo.Data).To(HaveLen(0))
|
||||
})
|
||||
|
||||
It("fails when library doesn't exist", func() {
|
||||
err := service.Delete(ctx, 999)
|
||||
err := repo.Delete("999")
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(model.ErrNotFound))
|
||||
|
|
@ -238,14 +203,14 @@ var _ = Describe("Library Service", func() {
|
|||
err := service.SetUserLibraries(ctx, "user1", []int{999})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("library ID 999 does not exist"))
|
||||
Expect(err.Error()).To(ContainSubstring("one or more library IDs are invalid"))
|
||||
})
|
||||
|
||||
It("fails when some libraries don't exist", func() {
|
||||
err := service.SetUserLibraries(ctx, "user1", []int{1, 999, 2})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("library ID 999 does not exist"))
|
||||
Expect(err.Error()).To(ContainSubstring("one or more library IDs are invalid"))
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
234
core/mock_library_service.go
Normal file
234
core/mock_library_service.go
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/deluan/rest"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
)
|
||||
|
||||
// MockLibraryService provides a mock implementation of core.Library interface
|
||||
// that can be used in tests to prevent nil pointer panics
|
||||
type MockLibraryService struct {
|
||||
Library
|
||||
Err error
|
||||
Libraries model.Libraries
|
||||
}
|
||||
|
||||
// NewMockLibraryService creates a new mock library service with some default test data
|
||||
func NewMockLibraryService() *MockLibraryService {
|
||||
return &MockLibraryService{
|
||||
Libraries: model.Libraries{
|
||||
{ID: 1, Name: "Test Library 1", Path: "/music/library1"},
|
||||
{ID: 2, Name: "Test Library 2", Path: "/music/library2"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) GetAll(ctx context.Context) (model.Libraries, error) {
|
||||
if m.Err != nil {
|
||||
return nil, m.Err
|
||||
}
|
||||
return m.Libraries, nil
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) Get(ctx context.Context, id int) (*model.Library, error) {
|
||||
if m.Err != nil {
|
||||
return nil, m.Err
|
||||
}
|
||||
for _, lib := range m.Libraries {
|
||||
if lib.ID == id {
|
||||
return &lib, nil
|
||||
}
|
||||
}
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) Create(ctx context.Context, library *model.Library) error {
|
||||
if m.Err != nil {
|
||||
return m.Err
|
||||
}
|
||||
if library.Name == "" {
|
||||
return fmt.Errorf("%w: library name is required", model.ErrValidation)
|
||||
}
|
||||
if library.Path == "" {
|
||||
return fmt.Errorf("%w: library path is required", model.ErrValidation)
|
||||
}
|
||||
// Add to mock data
|
||||
library.ID = len(m.Libraries) + 1
|
||||
m.Libraries = append(m.Libraries, *library)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) Update(ctx context.Context, library *model.Library) error {
|
||||
if m.Err != nil {
|
||||
return m.Err
|
||||
}
|
||||
if library.Name == "" {
|
||||
return fmt.Errorf("%w: library name is required", model.ErrValidation)
|
||||
}
|
||||
if library.Path == "" {
|
||||
return fmt.Errorf("%w: library path is required", model.ErrValidation)
|
||||
}
|
||||
|
||||
// Find and update in mock data
|
||||
for i, lib := range m.Libraries {
|
||||
if lib.ID == library.ID {
|
||||
m.Libraries[i] = *library
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return model.ErrNotFound
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) Delete(ctx context.Context, id int) error {
|
||||
if m.Err != nil {
|
||||
return m.Err
|
||||
}
|
||||
|
||||
// Find and remove from mock data
|
||||
for i, lib := range m.Libraries {
|
||||
if lib.ID == id {
|
||||
m.Libraries = append(m.Libraries[:i], m.Libraries[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return model.ErrNotFound
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) GetUserLibraries(ctx context.Context, userID string) (model.Libraries, error) {
|
||||
if m.Err != nil {
|
||||
return nil, m.Err
|
||||
}
|
||||
if userID == "non-existent" {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
// Return all libraries for simplicity in tests
|
||||
return m.Libraries, nil
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) SetUserLibraries(ctx context.Context, userID string, libraryIDs []int) error {
|
||||
if m.Err != nil {
|
||||
return m.Err
|
||||
}
|
||||
if userID == "non-existent" {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
if userID == "admin-1" {
|
||||
return fmt.Errorf("%w: cannot manually assign libraries to admin users", model.ErrValidation)
|
||||
}
|
||||
if len(libraryIDs) == 0 {
|
||||
return fmt.Errorf("%w: at least one library must be assigned to non-admin users", model.ErrValidation)
|
||||
}
|
||||
// Validate all library IDs exist
|
||||
for _, id := range libraryIDs {
|
||||
found := false
|
||||
for _, lib := range m.Libraries {
|
||||
if lib.ID == id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("%w: library ID %d does not exist", model.ErrValidation, id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) ValidateLibraryAccess(ctx context.Context, userID string, libraryID int) error {
|
||||
if m.Err != nil {
|
||||
return m.Err
|
||||
}
|
||||
// For testing purposes, allow access to all libraries
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockLibraryService) NewRepository(ctx context.Context) rest.Repository {
|
||||
return &mockLibraryRepository{service: m, ctx: ctx}
|
||||
}
|
||||
|
||||
// mockLibraryRepository provides a REST repository wrapper for the mock service
|
||||
type mockLibraryRepository struct {
|
||||
service *MockLibraryService
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) Count(options ...rest.QueryOptions) (int64, error) {
|
||||
libs, err := r.service.GetAll(r.ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(len(libs)), nil
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) Read(id string) (interface{}, error) {
|
||||
idInt, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return nil, rest.ErrNotFound
|
||||
}
|
||||
lib, err := r.service.Get(r.ctx, idInt)
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, rest.ErrNotFound
|
||||
}
|
||||
return lib, err
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
||||
return r.service.GetAll(r.ctx)
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) EntityName() string {
|
||||
return "library"
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) NewInstance() interface{} {
|
||||
return &model.Library{}
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) Save(entity interface{}) (string, error) {
|
||||
lib := entity.(*model.Library)
|
||||
err := r.service.Create(r.ctx, lib)
|
||||
if errors.Is(err, model.ErrValidation) {
|
||||
return "", &rest.ValidationError{Errors: map[string]string{"validation": err.Error()}}
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(lib.ID), nil
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) Update(id string, entity interface{}, cols ...string) error {
|
||||
lib := entity.(*model.Library)
|
||||
idInt, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return &rest.ValidationError{Errors: map[string]string{"id": "invalid library ID format"}}
|
||||
}
|
||||
lib.ID = idInt
|
||||
err = r.service.Update(r.ctx, lib)
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return rest.ErrNotFound
|
||||
}
|
||||
if errors.Is(err, model.ErrValidation) {
|
||||
return &rest.ValidationError{Errors: map[string]string{"validation": err.Error()}}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *mockLibraryRepository) Delete(id string) error {
|
||||
idInt, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return &rest.ValidationError{Errors: map[string]string{"id": "invalid library ID format"}}
|
||||
}
|
||||
err = r.service.Delete(r.ctx, idInt)
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return rest.ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var _ Library = (*MockLibraryService)(nil)
|
||||
var _ rest.Repository = (*mockLibraryRepository)(nil)
|
||||
|
|
@ -5,23 +5,22 @@ import (
|
|||
)
|
||||
|
||||
type Library struct {
|
||||
ID int
|
||||
Name string
|
||||
Path string
|
||||
RemotePath string
|
||||
LastScanAt time.Time
|
||||
LastScanStartedAt time.Time
|
||||
FullScanInProgress bool
|
||||
UpdatedAt time.Time
|
||||
CreatedAt time.Time
|
||||
|
||||
TotalSongs int
|
||||
TotalAlbums int
|
||||
TotalArtists int
|
||||
TotalFolders int
|
||||
TotalFiles int
|
||||
TotalMissingFiles int
|
||||
TotalSize int64
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
RemotePath string `json:"remotePath"`
|
||||
LastScanAt time.Time `json:"lastScanAt"`
|
||||
LastScanStartedAt time.Time `json:"lastScanStartedAt"`
|
||||
FullScanInProgress bool `json:"fullScanInProgress"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
TotalSongs int `json:"totalSongs"`
|
||||
TotalAlbums int `json:"totalAlbums"`
|
||||
TotalArtists int `json:"totalArtists"`
|
||||
TotalFolders int `json:"totalFolders"`
|
||||
TotalFiles int `json:"totalFiles"`
|
||||
TotalMissingFiles int `json:"totalMissingFiles"`
|
||||
TotalSize int64 `json:"totalSize"`
|
||||
}
|
||||
|
||||
type Libraries []Library
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package persistence
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -85,22 +86,26 @@ func (r *libraryRepository) Put(l *model.Library) error {
|
|||
remote_path = excluded.remote_path, updated_at = excluded.updated_at`)
|
||||
_, err := r.executeSQL(sq)
|
||||
if err != nil {
|
||||
libLock.Lock()
|
||||
defer libLock.Unlock()
|
||||
libCache[l.ID] = l.Path
|
||||
return err
|
||||
}
|
||||
|
||||
// Auto-assign library to all admin users
|
||||
if err == nil {
|
||||
sql := Expr(
|
||||
"INSERT INTO user_library (user_id, library_id) SELECT id, ? FROM user WHERE is_admin = true ON CONFLICT (user_id, library_id) DO NOTHING",
|
||||
l.ID,
|
||||
)
|
||||
if _, addErr := r.executeSQL(sql); addErr != nil {
|
||||
return fmt.Errorf("failed to assign library to admin users: %w", addErr)
|
||||
}
|
||||
// Auto-assign all libraries to all admin users
|
||||
sql := Expr(`
|
||||
INSERT INTO user_library (user_id, library_id)
|
||||
SELECT u.id, l.id
|
||||
FROM user u
|
||||
CROSS JOIN library l
|
||||
WHERE u.is_admin = true
|
||||
ON CONFLICT (user_id, library_id) DO NOTHING;`,
|
||||
)
|
||||
if _, err = r.executeSQL(sql); err != nil {
|
||||
return fmt.Errorf("failed to assign library to admin users: %w", err)
|
||||
}
|
||||
return err
|
||||
|
||||
libLock.Lock()
|
||||
defer libLock.Unlock()
|
||||
libCache[l.ID] = l.Path
|
||||
return nil
|
||||
}
|
||||
|
||||
const hardCodedMusicFolderID = 1
|
||||
|
|
@ -214,7 +219,7 @@ func (r *libraryRepository) RefreshStats(id int) error {
|
|||
|
||||
func (r *libraryRepository) Delete(id int) error {
|
||||
if !isAdmin(r.ctx) {
|
||||
return rest.ErrPermissionDenied
|
||||
return model.ErrNotAuthorized
|
||||
}
|
||||
|
||||
err := r.delete(Eq{"id": id})
|
||||
|
|
@ -256,4 +261,53 @@ func (r *libraryRepository) GetUsersWithLibraryAccess(libraryID int) (model.User
|
|||
return res, err
|
||||
}
|
||||
|
||||
// REST interface methods
|
||||
|
||||
func (r *libraryRepository) Count(options ...rest.QueryOptions) (int64, error) {
|
||||
return r.CountAll(r.parseRestOptions(r.ctx, options...))
|
||||
}
|
||||
|
||||
func (r *libraryRepository) Read(id string) (interface{}, error) {
|
||||
idInt, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
log.Trace(r.ctx, "invalid library id: %s", id, err)
|
||||
return nil, rest.ErrNotFound
|
||||
}
|
||||
return r.Get(idInt)
|
||||
}
|
||||
|
||||
func (r *libraryRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
||||
return r.GetAll(r.parseRestOptions(r.ctx, options...))
|
||||
}
|
||||
|
||||
func (r *libraryRepository) EntityName() string {
|
||||
return "library"
|
||||
}
|
||||
|
||||
func (r *libraryRepository) NewInstance() interface{} {
|
||||
return &model.Library{}
|
||||
}
|
||||
|
||||
func (r *libraryRepository) Save(entity interface{}) (string, error) {
|
||||
lib := entity.(*model.Library)
|
||||
lib.ID = 0 // Reset ID to ensure we create a new library
|
||||
err := r.Put(lib)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(lib.ID), nil
|
||||
}
|
||||
|
||||
func (r *libraryRepository) Update(id string, entity interface{}, cols ...string) error {
|
||||
lib := entity.(*model.Library)
|
||||
idInt, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid library ID: %s", id)
|
||||
}
|
||||
|
||||
lib.ID = idInt
|
||||
return r.Put(lib)
|
||||
}
|
||||
|
||||
var _ model.LibraryRepository = (*libraryRepository)(nil)
|
||||
var _ rest.Repository = (*libraryRepository)(nil)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/auth"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
|
|
@ -28,7 +29,7 @@ var _ = Describe("Config API", func() {
|
|||
conf.Server.DevUIShowConfig = true // Enable config endpoint for tests
|
||||
ds = &tests.MockDataStore{}
|
||||
auth.Init(ds)
|
||||
nativeRouter := New(ds, nil, nil, nil, nil)
|
||||
nativeRouter := New(ds, nil, nil, nil, core.NewMockLibraryService())
|
||||
router = server.JWTVerifier(nativeRouter)
|
||||
|
||||
// Create test users
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
|
|
@ -13,20 +12,6 @@ import (
|
|||
"github.com/navidrome/navidrome/model"
|
||||
)
|
||||
|
||||
// Library management endpoints (admin only)
|
||||
func (n *Router) addLibraryRoute(r chi.Router) {
|
||||
r.Route("/library", func(r chi.Router) {
|
||||
r.Get("/", getLibraries(n.libs))
|
||||
r.Post("/", createLibrary(n.libs))
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
r.Use(parseIDMiddleware)
|
||||
r.Get("/", getLibrary(n.libs))
|
||||
r.Put("/", updateLibrary(n.libs))
|
||||
r.Delete("/", deleteLibrary(n.libs))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// User-library association endpoints (admin only)
|
||||
func (n *Router) addUserLibraryRoute(r chi.Router) {
|
||||
r.Route("/user/{id}/library", func(r chi.Router) {
|
||||
|
|
@ -36,20 +21,6 @@ func (n *Router) addUserLibraryRoute(r chi.Router) {
|
|||
})
|
||||
}
|
||||
|
||||
// Middleware to parse library ID from URL
|
||||
func parseIDMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := chi.URLParam(r, "id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid library ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), "libraryID", id)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// Middleware to parse user ID from URL
|
||||
func parseUserIDMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -63,126 +34,6 @@ func parseUserIDMiddleware(next http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
// Library CRUD handlers
|
||||
|
||||
func getLibraries(service core.Library) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
libraries, err := service.GetAll(r.Context())
|
||||
if err != nil {
|
||||
log.Error(r.Context(), "Error getting libraries", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(libraries); err != nil {
|
||||
log.Error(r.Context(), "Error encoding libraries response", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getLibrary(service core.Library) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
libraryID := r.Context().Value("libraryID").(int)
|
||||
|
||||
lib, err := service.Get(r.Context(), libraryID)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
http.Error(w, "Library not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
log.Error(r.Context(), "Error getting library", "libraryID", libraryID, err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(lib); err != nil {
|
||||
log.Error(r.Context(), "Error encoding library response", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createLibrary(service core.Library) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var lib model.Library
|
||||
if err := json.NewDecoder(r.Body).Decode(&lib); err != nil {
|
||||
log.Error(r.Context(), "Error decoding library request", err)
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := service.Create(r.Context(), &lib); err != nil {
|
||||
log.Error(r.Context(), "Error creating library", err)
|
||||
if errors.Is(err, model.ErrValidation) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Failed to create library", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(lib); err != nil {
|
||||
log.Error(r.Context(), "Error encoding library response", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateLibrary(service core.Library) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
libraryID := r.Context().Value("libraryID").(int)
|
||||
|
||||
var lib model.Library
|
||||
if err := json.NewDecoder(r.Body).Decode(&lib); err != nil {
|
||||
log.Error(r.Context(), "Error decoding library request", err)
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the ID matches the URL parameter
|
||||
lib.ID = libraryID
|
||||
|
||||
if err := service.Update(r.Context(), &lib); err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
http.Error(w, "Library not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, model.ErrValidation) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log.Error(r.Context(), "Error updating library", "libraryID", libraryID, err)
|
||||
http.Error(w, "Failed to update library", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(lib); err != nil {
|
||||
log.Error(r.Context(), "Error encoding library response", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteLibrary(service core.Library) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
libraryID := r.Context().Value("libraryID").(int)
|
||||
|
||||
if err := service.Delete(r.Context(), libraryID); err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
http.Error(w, "Library not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
log.Error(r.Context(), "Error deleting library", "libraryID", libraryID, err)
|
||||
http.Error(w, "Failed to delete library", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// User-library association handlers
|
||||
|
||||
func getUserLibraries(service core.Library) http.HandlerFunc {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ var _ = Describe("Library API", func() {
|
|||
DeferCleanup(configtest.SetupConfig())
|
||||
ds = &tests.MockDataStore{}
|
||||
auth.Init(ds)
|
||||
nativeRouter := New(ds, nil, nil, nil, &mockLibraryService{})
|
||||
nativeRouter := New(ds, nil, nil, nil, core.NewMockLibraryService())
|
||||
router = server.JWTVerifier(nativeRouter)
|
||||
|
||||
// Create test users
|
||||
|
|
@ -126,7 +126,7 @@ var _ = Describe("Library API", func() {
|
|||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
Expect(w.Code).To(Equal(http.StatusNotFound))
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -142,13 +142,7 @@ var _ = Describe("Library API", func() {
|
|||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusCreated))
|
||||
|
||||
var created model.Library
|
||||
err := json.Unmarshal(w.Body.Bytes(), &created)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(created.Name).To(Equal("New Library"))
|
||||
Expect(created.Path).To(Equal("/music/new"))
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
It("validates required fields", func() {
|
||||
|
|
@ -226,7 +220,7 @@ var _ = Describe("Library API", func() {
|
|||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusNoContent))
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
It("returns 404 for non-existent library", func() {
|
||||
|
|
@ -441,91 +435,3 @@ func Split(s, sep string) []string {
|
|||
result = append(result, s[start:])
|
||||
return result
|
||||
}
|
||||
|
||||
// Mock library service for testing
|
||||
type mockLibraryService struct {
|
||||
core.Library
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) GetAll(ctx context.Context) (model.Libraries, error) {
|
||||
return model.Libraries{
|
||||
{ID: 1, Name: "Test Library 1", Path: "/music/library1"},
|
||||
{ID: 2, Name: "Test Library 2", Path: "/music/library2"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) Get(ctx context.Context, id int) (*model.Library, error) {
|
||||
if id == 999 {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
return &model.Library{ID: 1, Name: "Test Library 1", Path: "/music/library1"}, nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) Create(ctx context.Context, library *model.Library) error {
|
||||
if library.Name == "" {
|
||||
return fmt.Errorf("%w: library name is required", model.ErrValidation)
|
||||
}
|
||||
if library.Path == "" {
|
||||
return fmt.Errorf("%w: library path is required", model.ErrValidation)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) Update(ctx context.Context, library *model.Library) error {
|
||||
if library.ID == 999 {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
if library.Name == "" {
|
||||
return fmt.Errorf("%w: library name is required", model.ErrValidation)
|
||||
}
|
||||
if library.Path == "" {
|
||||
return fmt.Errorf("%w: library path is required", model.ErrValidation)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) Delete(ctx context.Context, id int) error {
|
||||
if id == 999 {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) GetUserLibraries(ctx context.Context, userID string) (model.Libraries, error) {
|
||||
if userID == "non-existent" {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
// If setting multiple libraries, return all requested ones
|
||||
if userID == "user-1" {
|
||||
return model.Libraries{
|
||||
{ID: 1, Name: "Test Library 1", Path: "/music/library1"},
|
||||
{ID: 2, Name: "Test Library 2", Path: "/music/library2"},
|
||||
}, nil
|
||||
}
|
||||
return model.Libraries{
|
||||
{ID: 1, Name: "Test Library 1", Path: "/music/library1"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) SetUserLibraries(ctx context.Context, userID string, libraryIDs []int) error {
|
||||
if userID == "non-existent" {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
if userID == "admin-1" {
|
||||
return fmt.Errorf("%w: cannot manually assign libraries to admin users", model.ErrValidation)
|
||||
}
|
||||
if len(libraryIDs) == 0 {
|
||||
return fmt.Errorf("%w: at least one library must be assigned to non-admin users", model.ErrValidation)
|
||||
}
|
||||
for _, id := range libraryIDs {
|
||||
if id == 999 {
|
||||
return fmt.Errorf("%w: library ID 999 does not exist", model.ErrValidation)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockLibraryService) ValidateLibraryAccess(ctx context.Context, userID string, libraryID int) error {
|
||||
// For testing purposes, allow admin access and check basic user access
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ func (n *Router) routes() http.Handler {
|
|||
r.With(adminOnlyMiddleware).Group(func(r chi.Router) {
|
||||
n.addInspectRoute(r)
|
||||
n.addConfigRoute(r)
|
||||
n.addLibraryRoute(r)
|
||||
n.addUserLibraryRoute(r)
|
||||
n.RX(r, "/library", n.libs.NewRepository, true)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/auth"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
|
|
@ -94,7 +95,7 @@ var _ = Describe("Song Endpoints", func() {
|
|||
mfRepo.SetData(testSongs)
|
||||
|
||||
// Create the native API router and wrap it with the JWTVerifier middleware
|
||||
nativeRouter := New(ds, nil, nil, nil, nil)
|
||||
nativeRouter := New(ds, nil, nil, nil, core.NewMockLibraryService())
|
||||
router = server.JWTVerifier(nativeRouter)
|
||||
w = httptest.NewRecorder()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
package tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/deluan/rest"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
)
|
||||
|
||||
|
|
@ -155,4 +158,30 @@ func (m *MockLibraryRepo) GetUsersWithLibraryAccess(libraryID int) (model.Users,
|
|||
return model.Users{}, nil
|
||||
}
|
||||
|
||||
var _ model.LibraryRepository = &MockLibraryRepo{}
|
||||
func (m *MockLibraryRepo) Count(options ...rest.QueryOptions) (int64, error) {
|
||||
return m.CountAll()
|
||||
}
|
||||
|
||||
func (m *MockLibraryRepo) Read(id string) (interface{}, error) {
|
||||
idInt, _ := strconv.Atoi(id)
|
||||
mf, err := m.Get(idInt)
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, rest.ErrNotFound
|
||||
}
|
||||
return mf, err
|
||||
}
|
||||
|
||||
func (m *MockLibraryRepo) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
||||
return m.GetAll()
|
||||
}
|
||||
|
||||
func (m *MockLibraryRepo) EntityName() string {
|
||||
return "library"
|
||||
}
|
||||
|
||||
func (m *MockLibraryRepo) NewInstance() interface{} {
|
||||
return &model.Library{}
|
||||
}
|
||||
|
||||
var _ model.LibraryRepository = (*MockLibraryRepo)(nil)
|
||||
var _ model.ResourceRepository = (*MockLibraryRepo)(nil)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue