summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/api/handlers_media.go7
-rw-r--r--internal/api/handlers_more_test.go21
-rw-r--r--internal/api/handlers_test.go51
-rw-r--r--internal/api/middleware.go23
-rw-r--r--internal/api/server.go2
-rw-r--r--internal/service/auth.go10
-rw-r--r--internal/service/browse.go28
-rw-r--r--internal/service/media_test.go6
-rw-r--r--internal/service/mock.go27
-rw-r--r--internal/service/mock_test.go7
-rw-r--r--internal/service/service.go27
11 files changed, 142 insertions, 67 deletions
diff --git a/internal/api/handlers_media.go b/internal/api/handlers_media.go
index 6095c72..c986eb3 100644
--- a/internal/api/handlers_media.go
+++ b/internal/api/handlers_media.go
@@ -9,7 +9,6 @@ import (
"time"
"codeberg.org/snonux/player/internal/model"
- "codeberg.org/snonux/player/internal/repository"
"codeberg.org/snonux/player/internal/service"
)
@@ -157,9 +156,9 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
// ------------------------------------------------------------------
// parseMediaListQuery extracts and validates query parameters from the request
-// and returns a populated repository.MediaFilter with sensible defaults.
-func parseMediaListQuery(q url.Values) repository.MediaFilter {
- filter := repository.MediaFilter{
+// and returns a populated service.MediaQueryFilter with sensible defaults.
+func parseMediaListQuery(q url.Values) service.MediaQueryFilter {
+ filter := service.MediaQueryFilter{
Search: q.Get("search"),
Sort: q.Get("sort"),
Limit: 100,
diff --git a/internal/api/handlers_more_test.go b/internal/api/handlers_more_test.go
index 06b5502..379cc4c 100644
--- a/internal/api/handlers_more_test.go
+++ b/internal/api/handlers_more_test.go
@@ -161,7 +161,10 @@ func TestServer_ServeFile_success(t *testing.T) {
},
}
sm := auth.NewSessionManager(store, &clock.MockClock{T: time.Now()}, time.Hour)
- srv := newTestServer(t, store, nil, sm, &internal.Config{SessionTimeoutHours: 24}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
+ authSvc := &service.MockAuthService{
+ CountUsersFunc: func(ctx context.Context) (int, error) { return 1, nil },
+ }
+ srv := newTestServer(t, store, nil, sm, &internal.Config{SessionTimeoutHours: 24}, nil, nil, nil, nil, nil, nil, nil, nil, authSvc, nil)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookieForStore(t, store, sm, 1))
@@ -181,7 +184,10 @@ func TestServer_ServeFile_notFound(t *testing.T) {
},
}
sm := auth.NewSessionManager(store, &clock.MockClock{T: time.Now()}, time.Hour)
- srv := newTestServer(t, store, nil, sm, &internal.Config{SessionTimeoutHours: 24}, nil, nil, nil, nil, nil, nil, nil, nil, nil, fs)
+ authSvc := &service.MockAuthService{
+ CountUsersFunc: func(ctx context.Context) (int, error) { return 1, nil },
+ }
+ srv := newTestServer(t, store, nil, sm, &internal.Config{SessionTimeoutHours: 24}, nil, nil, nil, nil, nil, nil, nil, nil, authSvc, fs)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookieForStore(t, store, sm, 1))
@@ -1730,7 +1736,7 @@ func mustParseQuery(t *testing.T, raw string) url.Values {
func Test_parseMediaListQuery_defaults(t *testing.T) {
q := mustParseQuery(t, "/api/media")
got := parseMediaListQuery(q)
- want := repository.MediaFilter{Limit: 100, Offset: 0}
+ want := service.MediaQueryFilter{Limit: 100, Offset: 0}
if got.Search != want.Search || got.Sort != want.Sort || got.Limit != want.Limit || got.Offset != want.Offset {
t.Fatalf("unexpected defaults: %+v", got)
}
@@ -1844,10 +1850,13 @@ func TestServer_NilAdminSvc(t *testing.T) {
cfg := &internal.Config{SessionTimeoutHours: 24}
store := buildSessionStore(1)
sm := auth.NewSessionManager(store, &clock.MockClock{T: time.Now()}, time.Hour)
- store.UserRepo.GetUserByIDFunc = func(ctx context.Context, id int64) (*model.User, error) {
- return &model.User{ID: 1, IsAdmin: true}, nil
+ authSvc := &service.MockAuthService{
+ CountUsersFunc: func(ctx context.Context) (int, error) { return 1, nil },
+ GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) {
+ return &model.User{ID: 1, IsAdmin: true}, nil
+ },
}
- srv := newTestServer(t, store, nil, sm, cfg, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
+ srv := newTestServer(t, store, nil, sm, cfg, nil, nil, nil, nil, nil, nil, nil, nil, authSvc, nil)
cookie := addSessionCookie(t, store, sm, 1)
tests := []struct {
diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go
index 2741172..922da58 100644
--- a/internal/api/handlers_test.go
+++ b/internal/api/handlers_test.go
@@ -56,6 +56,12 @@ func newTestServer(t *testing.T, store repository.Store, hasher auth.Hasher, sm
"share.html": "share",
})
}
+ if authSvc == nil {
+ authSvc = &service.MockAuthService{
+ CountUsersFunc: func(context.Context) (int, error) { return 1, nil },
+ GetUserByIDFunc: func(context.Context, int64) (*model.User, error) { return &model.User{ID: 1, IsAdmin: true}, nil },
+ }
+ }
var rem probe.Remuxer
if len(remuxer) > 0 {
rem = remuxer[0]
@@ -116,14 +122,12 @@ func TestMiddleware_BootstrapRedirect(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- store := &repository.MockStore{
- UserRepo: repository.MockUserRepo{
- CountUsersFunc: func(ctx context.Context) (int, error) {
- return tt.userCount, tt.userErr
- },
+ authSvc := &service.MockAuthService{
+ CountUsersFunc: func(ctx context.Context) (int, error) {
+ return tt.userCount, tt.userErr
},
}
- mw := NewMiddleware(store, nil)
+ mw := NewMiddleware(authSvc, nil)
handler := mw.BootstrapRedirect(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
@@ -217,17 +221,15 @@ func TestMiddleware_RequireAdmin(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- store := &repository.MockStore{
- UserRepo: repository.MockUserRepo{
- GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) {
- if tt.ctxUser != nil {
- return tt.ctxUser, tt.userErr
- }
- return nil, tt.userErr
- },
+ authSvc := &service.MockAuthService{
+ GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) {
+ if tt.ctxUser != nil {
+ return tt.ctxUser, tt.userErr
+ }
+ return nil, tt.userErr
},
}
- mw := NewMiddleware(store, nil)
+ mw := NewMiddleware(authSvc, nil)
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
@@ -673,7 +675,7 @@ func TestServer_MediaList(t *testing.T) {
tests := []struct {
name string
- filter repository.MediaFilter
+ filter service.MediaQueryFilter
listResult []model.Media
listErr error
query string
@@ -693,7 +695,7 @@ func TestServer_MediaList(t *testing.T) {
{
name: "with query params",
query: "?set_id=1&type=video&search=foo&tags=bar,baz&favorites=true&min_duration=10&max_duration=100&sort=name&limit=5&offset=10",
- filter: repository.MediaFilter{SetID: intPtr(1), Type: (*model.MediaType)(func() *string { s := "video"; return &s }()), Search: "foo", Tags: []string{"bar", "baz"}, Favorites: true, MinDuration: floatPtr(10), MaxDuration: floatPtr(100), Sort: "name", Limit: 5, Offset: 10},
+ filter: service.MediaQueryFilter{SetID: intPtr(1), Type: (*model.MediaType)(func() *string { s := "video"; return &s }()), Search: "foo", Tags: []string{"bar", "baz"}, Favorites: true, MinDuration: floatPtr(10), MaxDuration: floatPtr(100), Sort: "name", Limit: 5, Offset: 10},
listResult: []model.Media{},
wantCode: http.StatusOK,
},
@@ -701,9 +703,9 @@ func TestServer_MediaList(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- var gotFilter repository.MediaFilter
+ var gotFilter service.MediaQueryFilter
ms := &service.MockMediaService{
- ListMediaFunc: func(ctx context.Context, userID int64, filter repository.MediaFilter) ([]model.Media, error) {
+ ListMediaFunc: func(ctx context.Context, userID int64, filter service.MediaQueryFilter) ([]model.Media, error) {
gotFilter = filter
return tt.listResult, tt.listErr
},
@@ -1169,6 +1171,12 @@ func TestServer_Shares(t *testing.T) {
func TestServer_AdminRoutes(t *testing.T) {
adminUser := &model.User{ID: 1, Username: "admin", IsAdmin: true}
+ authSvc := &service.MockAuthService{
+ CountUsersFunc: func(ctx context.Context) (int, error) { return 1, nil },
+ GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) {
+ return adminUser, nil
+ },
+ }
as := &service.MockAdminService{
ListTrashFunc: func(ctx context.Context) ([]model.Media, error) {
return []model.Media{}, nil
@@ -1185,12 +1193,9 @@ func TestServer_AdminRoutes(t *testing.T) {
}
store := buildSessionStore(1)
- store.UserRepo.GetUserByIDFunc = func(ctx context.Context, id int64) (*model.User, error) {
- return adminUser, nil
- }
sm := auth.NewSessionManager(store, &clock.MockClock{T: time.Now()}, time.Hour)
cfg := &internal.Config{SessionTimeoutHours: 24}
- srv := newTestServer(t, buildCountStore(1), nil, sm, cfg, nil, nil, nil, nil, nil, nil, as, nil, nil, nil)
+ srv := newTestServer(t, buildCountStore(1), nil, sm, cfg, nil, nil, nil, nil, nil, nil, as, nil, authSvc, nil)
cookie := addSessionCookie(t, store, sm, 1)
diff --git a/internal/api/middleware.go b/internal/api/middleware.go
index c6cc303..f341dc8 100644
--- a/internal/api/middleware.go
+++ b/internal/api/middleware.go
@@ -7,6 +7,7 @@ import (
"codeberg.org/snonux/player/internal/auth"
"codeberg.org/snonux/player/internal/model"
+ "codeberg.org/snonux/player/internal/service"
)
type ctxKey int
@@ -16,21 +17,15 @@ const (
userCtxKey
)
-// UserStore is the narrow interface Middleware needs for user lookups.
-type UserStore interface {
- CountUsers(ctx context.Context) (int, error)
- GetUserByID(ctx context.Context, id int64) (*model.User, error)
-}
-
// Middleware holds dependencies for middleware constructors.
type Middleware struct {
- store UserStore
- sm *auth.SessionManager
+ authSvc service.AuthService
+ sm *auth.SessionManager
}
// NewMiddleware creates middleware handlers.
-func NewMiddleware(store UserStore, sm *auth.SessionManager) *Middleware {
- return &Middleware{store: store, sm: sm}
+func NewMiddleware(authSvc service.AuthService, sm *auth.SessionManager) *Middleware {
+ return &Middleware{authSvc: authSvc, sm: sm}
}
// RequireSession validates the session cookie and injects the session into request context.
@@ -74,7 +69,7 @@ func (mw *Middleware) RequireAdmin(next http.Handler) http.Handler {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
- user, err := mw.store.GetUserByID(r.Context(), sess.UserID)
+ user, err := mw.authSvc.GetUserByID(r.Context(), sess.UserID)
if err != nil || user == nil || !user.IsAdmin {
http.Error(w, "forbidden", http.StatusForbidden)
return
@@ -91,7 +86,11 @@ func (mw *Middleware) BootstrapRedirect(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
return
}
- count, err := mw.store.CountUsers(r.Context())
+ if mw.authSvc == nil {
+ http.Error(w, "internal server error", http.StatusInternalServerError)
+ return
+ }
+ count, err := mw.authSvc.CountUsers(r.Context())
if err != nil {
http.Error(w, "internal server error", http.StatusInternalServerError)
return
diff --git a/internal/api/server.go b/internal/api/server.go
index 87e2471..bcc8330 100644
--- a/internal/api/server.go
+++ b/internal/api/server.go
@@ -101,7 +101,7 @@ func NewServerWithLogger(
staticFS: staticFS,
remuxer: remuxer,
logger: logger,
- mw: NewMiddleware(store, sm),
+ mw: NewMiddleware(authSvc, sm),
}
s.routes()
return s
diff --git a/internal/service/auth.go b/internal/service/auth.go
index c52d2f0..af3543d 100644
--- a/internal/service/auth.go
+++ b/internal/service/auth.go
@@ -84,3 +84,13 @@ func (s *authService) Login(ctx context.Context, username, password string) (*Au
return &AuthResult{User: user, SessionID: sessID}, nil
}
+
+// CountUsers returns the number of user accounts.
+func (s *authService) CountUsers(ctx context.Context) (int, error) {
+ return s.store.CountUsers(ctx)
+}
+
+// GetUserByID returns a user by database ID.
+func (s *authService) GetUserByID(ctx context.Context, id int64) (*model.User, error) {
+ return s.store.GetUserByID(ctx, id)
+}
diff --git a/internal/service/browse.go b/internal/service/browse.go
index aa82049..8fa72ba 100644
--- a/internal/service/browse.go
+++ b/internal/service/browse.go
@@ -116,15 +116,31 @@ func (s *browseService) GetMediaDetail(ctx context.Context, mediaID, userID int6
}, nil
}
-func (s *browseService) ListMedia(ctx context.Context, userID int64, filter repository.MediaFilter) ([]model.Media, error) {
+func (s *browseService) ListMedia(ctx context.Context, userID int64, filter MediaQueryFilter) ([]model.Media, error) {
user, err := s.store.GetUserByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("get user: %w", err)
}
+ repoFilter := repository.MediaFilter{
+ SetID: filter.SetID,
+ SetIDs: filter.SetIDs,
+ Type: filter.Type,
+ Search: filter.Search,
+ Tags: filter.Tags,
+ Favorites: filter.Favorites,
+ MinDuration: filter.MinDuration,
+ MaxDuration: filter.MaxDuration,
+ MinFileSize: filter.MinFileSize,
+ MaxFileSize: filter.MaxFileSize,
+ Sort: filter.Sort,
+ Limit: filter.Limit,
+ Offset: filter.Offset,
+ }
+
if user != nil && user.IsAdmin {
- filter.UserID = userID
- return s.store.ListMedia(ctx, filter)
+ repoFilter.UserID = userID
+ return s.store.ListMedia(ctx, repoFilter)
}
perms, err := s.store.ListPermissionsByUser(ctx, userID)
@@ -136,9 +152,9 @@ func (s *browseService) ListMedia(ctx context.Context, userID int64, filter repo
for _, p := range perms {
allowed = append(allowed, p.SetID)
}
- filter.AllowedSetIDs = allowed
- filter.UserID = userID
- return s.store.ListMedia(ctx, filter)
+ repoFilter.AllowedSetIDs = allowed
+ repoFilter.UserID = userID
+ return s.store.ListMedia(ctx, repoFilter)
}
func (s *browseService) StreamMedia(ctx context.Context, mediaID, userID int64) (*FileResult, error) {
diff --git a/internal/service/media_test.go b/internal/service/media_test.go
index b14568c..d5b2006 100644
--- a/internal/service/media_test.go
+++ b/internal/service/media_test.go
@@ -1394,7 +1394,7 @@ func TestMediaService_UnauthorizedAccessDenied(t *testing.T) {
},
}
svc := NewMediaService(store, newMockClock(), "/tmp/media", nil, nil)
- res, err := svc.ListMedia(ctx, 9, repository.MediaFilter{})
+ res, err := svc.ListMedia(ctx, 9, MediaQueryFilter{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -1574,7 +1574,7 @@ func TestMediaService_ListMedia_AdminAndUserFiltering(t *testing.T) {
},
}
svc := NewMediaService(store, newMockClock(), "/tmp/media", nil, nil)
- res, err := svc.ListMedia(ctx, 1, repository.MediaFilter{})
+ res, err := svc.ListMedia(ctx, 1, MediaQueryFilter{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -1605,7 +1605,7 @@ func TestMediaService_ListMedia_AdminAndUserFiltering(t *testing.T) {
},
}
svc := NewMediaService(store, newMockClock(), "/tmp/media", nil, nil)
- res, err := svc.ListMedia(ctx, 2, repository.MediaFilter{})
+ res, err := svc.ListMedia(ctx, 2, MediaQueryFilter{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/service/mock.go b/internal/service/mock.go
index 0a52fab..8e6780c 100644
--- a/internal/service/mock.go
+++ b/internal/service/mock.go
@@ -7,7 +7,6 @@ import (
"time"
"codeberg.org/snonux/player/internal/model"
- "codeberg.org/snonux/player/internal/repository"
)
var (
@@ -25,7 +24,7 @@ var (
type MockMediaService struct {
ListSetsFunc func(ctx context.Context, userID int64) ([]model.Set, error)
GetMediaDetailFunc func(ctx context.Context, mediaID, userID int64) (*MediaDetail, error)
- ListMediaFunc func(ctx context.Context, userID int64, filter repository.MediaFilter) ([]model.Media, error)
+ ListMediaFunc func(ctx context.Context, userID int64, filter MediaQueryFilter) ([]model.Media, error)
StreamMediaFunc func(ctx context.Context, mediaID, userID int64) (*FileResult, error)
DownloadMediaFunc func(ctx context.Context, mediaID, userID int64) (*FileResult, error)
GetThumbnailFunc func(ctx context.Context, mediaID, userID int64) (*FileResult, error)
@@ -69,7 +68,7 @@ func (m *MockMediaService) GetMediaDetail(ctx context.Context, mediaID, userID i
}
// ListMedia calls ListMediaFunc or returns nil.
-func (m *MockMediaService) ListMedia(ctx context.Context, userID int64, filter repository.MediaFilter) ([]model.Media, error) {
+func (m *MockMediaService) ListMedia(ctx context.Context, userID int64, filter MediaQueryFilter) ([]model.Media, error) {
if m.ListMediaFunc != nil {
return m.ListMediaFunc(ctx, userID, filter)
}
@@ -355,8 +354,10 @@ func (m *MockAdminService) RevokePermission(ctx context.Context, setID, userID i
// MockAuthService is a fake AuthService for testing.
type MockAuthService struct {
- BootstrapFunc func(ctx context.Context, username, password string) (*AuthResult, error)
- LoginFunc func(ctx context.Context, username, password string) (*AuthResult, error)
+ BootstrapFunc func(ctx context.Context, username, password string) (*AuthResult, error)
+ LoginFunc func(ctx context.Context, username, password string) (*AuthResult, error)
+ CountUsersFunc func(ctx context.Context) (int, error)
+ GetUserByIDFunc func(ctx context.Context, id int64) (*model.User, error)
}
// Bootstrap calls BootstrapFunc or returns nil.
@@ -375,6 +376,22 @@ func (m *MockAuthService) Login(ctx context.Context, username, password string)
return nil, nil
}
+// CountUsers calls CountUsersFunc or returns 0.
+func (m *MockAuthService) CountUsers(ctx context.Context) (int, error) {
+ if m.CountUsersFunc != nil {
+ return m.CountUsersFunc(ctx)
+ }
+ return 0, nil
+}
+
+// GetUserByID calls GetUserByIDFunc or returns nil.
+func (m *MockAuthService) GetUserByID(ctx context.Context, id int64) (*model.User, error) {
+ if m.GetUserByIDFunc != nil {
+ return m.GetUserByIDFunc(ctx, id)
+ }
+ return nil, nil
+}
+
// MockProgressService is a fake ProgressService for testing.
type MockProgressService struct {
UpdateProgressFunc func(ctx context.Context, sessionID string, userID, mediaID int64, position float64) error
diff --git a/internal/service/mock_test.go b/internal/service/mock_test.go
index c7c3fdc..bb4801c 100644
--- a/internal/service/mock_test.go
+++ b/internal/service/mock_test.go
@@ -8,7 +8,6 @@ import (
"time"
"codeberg.org/snonux/player/internal/model"
- "codeberg.org/snonux/player/internal/repository"
)
func TestMockMediaService_Defaults(t *testing.T) {
@@ -18,7 +17,7 @@ func TestMockMediaService_Defaults(t *testing.T) {
// Methods that return nil, nil or zero-value defaults
m.ListSets(ctx, 1)
m.GetMediaDetail(ctx, 1, 1)
- m.ListMedia(ctx, 1, repository.MediaFilter{})
+ m.ListMedia(ctx, 1, MediaQueryFilter{})
m.ToggleFavorite(ctx, 1, 1)
m.AssignTag(ctx, 1, 1, "rock")
m.RemoveTag(ctx, 1, 1, "rock")
@@ -59,7 +58,7 @@ func TestMockMediaService_WithFuncs(t *testing.T) {
m := &MockMediaService{
ListSetsFunc: func(ctx context.Context, userID int64) ([]model.Set, error) { return nil, nil },
GetMediaDetailFunc: func(ctx context.Context, mediaID, userID int64) (*MediaDetail, error) { return nil, nil },
- ListMediaFunc: func(ctx context.Context, userID int64, filter repository.MediaFilter) ([]model.Media, error) { return nil, nil },
+ ListMediaFunc: func(ctx context.Context, userID int64, filter MediaQueryFilter) ([]model.Media, error) { return nil, nil },
StreamMediaFunc: func(ctx context.Context, mediaID, userID int64) (*FileResult, error) { return nil, nil },
DownloadMediaFunc: func(ctx context.Context, mediaID, userID int64) (*FileResult, error) { return nil, nil },
GetThumbnailFunc: func(ctx context.Context, mediaID, userID int64) (*FileResult, error) { return nil, nil },
@@ -83,7 +82,7 @@ func TestMockMediaService_WithFuncs(t *testing.T) {
m.ListSets(ctx, 1)
m.GetMediaDetail(ctx, 1, 1)
- m.ListMedia(ctx, 1, repository.MediaFilter{})
+ m.ListMedia(ctx, 1, MediaQueryFilter{})
m.StreamMedia(ctx, 1, 1)
m.DownloadMedia(ctx, 1, 1)
m.GetThumbnail(ctx, 1, 1)
diff --git a/internal/service/service.go b/internal/service/service.go
index 4519441..8033ab8 100644
--- a/internal/service/service.go
+++ b/internal/service/service.go
@@ -10,7 +10,6 @@ import (
"time"
"codeberg.org/snonux/player/internal/model"
- "codeberg.org/snonux/player/internal/repository"
)
// Sentinel errors returned by the service layer.
@@ -73,6 +72,24 @@ func guessMediaType(name string) model.MediaType {
}
}
+// MediaQueryFilter defines query parameters for listing media from the API layer.
+// It mirrors repository.MediaFilter but lives in the service layer to avoid coupling.
+type MediaQueryFilter struct {
+ SetID *int64 // SetID restricts results to one set.
+ SetIDs []int64 // SetIDs restricts results to multiple selected sets.
+ Type *model.MediaType // Type restricts results to one media type.
+ Search string // Search filters by filename or relative path.
+ Tags []string // Tags restricts results to media with all listed tags.
+ Favorites bool // Favorites restricts results to the current user's favorites.
+ MinDuration *float64 // MinDuration is the minimum duration in seconds.
+ MaxDuration *float64 // MaxDuration is the maximum duration in seconds.
+ MinFileSize *int64 // MinFileSize is the minimum file size in bytes.
+ MaxFileSize *int64 // MaxFileSize is the maximum file size in bytes.
+ Sort string // Sort chooses the order: name, date, duration, play_count, or random.
+ Limit int // Limit caps the number of returned rows.
+ Offset int // Offset skips rows before returning results.
+}
+
// MediaBrowseService handles read-only browsing and media streaming operations.
type MediaBrowseService interface {
// ListSets returns the sets visible to a user.
@@ -80,7 +97,7 @@ type MediaBrowseService interface {
// GetMediaDetail returns media metadata and user-specific related state.
GetMediaDetail(ctx context.Context, mediaID, userID int64) (*MediaDetail, error)
// ListMedia returns media visible to a user for the given filter.
- ListMedia(ctx context.Context, userID int64, filter repository.MediaFilter) ([]model.Media, error)
+ ListMedia(ctx context.Context, userID int64, filter MediaQueryFilter) ([]model.Media, error)
// StreamMedia returns a playable file for an authorized user.
StreamMedia(ctx context.Context, mediaID, userID int64) (*FileResult, error)
// DownloadMedia returns a downloadable file for an authorized user.
@@ -229,12 +246,16 @@ type AdminService interface {
RevokePermission(ctx context.Context, setID, userID int64) error
}
-// AuthService handles bootstrap and login operations.
+// AuthService handles bootstrap, login, and user lookups for middleware.
type AuthService interface {
// Bootstrap creates the first admin account and session.
Bootstrap(ctx context.Context, username, password string) (*AuthResult, error)
// Login authenticates a user and creates a session.
Login(ctx context.Context, username, password string) (*AuthResult, error)
+ // CountUsers returns the number of user accounts.
+ CountUsers(ctx context.Context) (int, error)
+ // GetUserByID returns a user by database ID.
+ GetUserByID(ctx context.Context, id int64) (*model.User, error)
}
// AuthResult contains the authenticated user and session ID.