diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/api/handlers_media.go | 7 | ||||
| -rw-r--r-- | internal/api/handlers_more_test.go | 21 | ||||
| -rw-r--r-- | internal/api/handlers_test.go | 51 | ||||
| -rw-r--r-- | internal/api/middleware.go | 23 | ||||
| -rw-r--r-- | internal/api/server.go | 2 | ||||
| -rw-r--r-- | internal/service/auth.go | 10 | ||||
| -rw-r--r-- | internal/service/browse.go | 28 | ||||
| -rw-r--r-- | internal/service/media_test.go | 6 | ||||
| -rw-r--r-- | internal/service/mock.go | 27 | ||||
| -rw-r--r-- | internal/service/mock_test.go | 7 | ||||
| -rw-r--r-- | internal/service/service.go | 27 |
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. |
