diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-04 08:09:27 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-04 08:09:27 +0300 |
| commit | fd60df1a2861de0d622f89cc04d164cb60e3d930 (patch) | |
| tree | 750b808b3b0d6e19778f1393cb540cb60a4c80c4 /internal/api | |
| parent | 71e004fb4e53ee4ac5c8fb202bad5f5c00dd8dac (diff) | |
task 3: decouple API layer from repository with MediaQueryFilter and AuthService abstraction
Diffstat (limited to 'internal/api')
| -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 |
5 files changed, 58 insertions, 46 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 |
