summaryrefslogtreecommitdiff
path: root/internal/api
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-04 08:09:27 +0300
committerPaul Buetow <paul@buetow.org>2026-05-04 08:09:27 +0300
commitfd60df1a2861de0d622f89cc04d164cb60e3d930 (patch)
tree750b808b3b0d6e19778f1393cb540cb60a4c80c4 /internal/api
parent71e004fb4e53ee4ac5c8fb202bad5f5c00dd8dac (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.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
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