From d6f1c37ac33783746fd7a7da50589e5b57bbc850 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Sat, 13 Jun 2026 22:06:07 +0200 Subject: [PATCH] refactor: extract proxy token store --- internal/playback/proxy_tokens.go | 69 +++++++++++++++++++++++++++++++ internal/playback/service.go | 63 ---------------------------- 2 files changed, 69 insertions(+), 63 deletions(-) create mode 100644 internal/playback/proxy_tokens.go diff --git a/internal/playback/proxy_tokens.go b/internal/playback/proxy_tokens.go new file mode 100644 index 0000000..01365cf --- /dev/null +++ b/internal/playback/proxy_tokens.go @@ -0,0 +1,69 @@ +package playback + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "sync" + "time" +) + +type proxyTokenTarget struct { + targetURL string + referer string + scope string + expiresAt time.Time +} + +type proxyTokenStore struct { + mu sync.Mutex + tokens map[string]proxyTokenTarget +} + +func newProxyTokenStore() *proxyTokenStore { + return &proxyTokenStore{ + tokens: make(map[string]proxyTokenTarget), + } +} + +func (s *proxyTokenStore) create(targetURL, referer, scope string, ttl time.Duration, now time.Time) (string, error) { + tokenBytes := make([]byte, 32) + if _, err := rand.Read(tokenBytes); err != nil { + return "", fmt.Errorf("generate proxy token: %w", err) + } + + token := base64.RawURLEncoding.EncodeToString(tokenBytes) + s.mu.Lock() + defer s.mu.Unlock() + s.pruneExpiredLocked(now) + s.tokens[token] = proxyTokenTarget{ + targetURL: targetURL, + referer: referer, + scope: scope, + expiresAt: now.Add(ttl), + } + return token, nil +} + +func (s *proxyTokenStore) resolve(token string, now time.Time) (proxyTokenTarget, error) { + s.mu.Lock() + defer s.mu.Unlock() + + target, ok := s.tokens[token] + if !ok { + return proxyTokenTarget{}, fmt.Errorf("invalid proxy token") + } + if !target.expiresAt.After(now) { + delete(s.tokens, token) + return proxyTokenTarget{}, fmt.Errorf("proxy token expired") + } + return target, nil +} + +func (s *proxyTokenStore) pruneExpiredLocked(now time.Time) { + for token, target := range s.tokens { + if !target.expiresAt.After(now) { + delete(s.tokens, token) + } + } +} diff --git a/internal/playback/service.go b/internal/playback/service.go index 85c1e6d..0d68c1a 100644 --- a/internal/playback/service.go +++ b/internal/playback/service.go @@ -3,9 +3,7 @@ package playback import ( "context" - "crypto/rand" "database/sql" - "encoding/base64" "encoding/json" "fmt" "io" @@ -19,7 +17,6 @@ import ( "sort" "strconv" "strings" - "sync" "time" "github.com/google/uuid" @@ -38,66 +35,6 @@ type playbackService struct { type ProxyTokenKey string -type proxyTokenTarget struct { - targetURL string - referer string - scope string - expiresAt time.Time -} - -type proxyTokenStore struct { - mu sync.Mutex - tokens map[string]proxyTokenTarget -} - -func newProxyTokenStore() *proxyTokenStore { - return &proxyTokenStore{ - tokens: make(map[string]proxyTokenTarget), - } -} - -func (s *proxyTokenStore) create(targetURL, referer, scope string, ttl time.Duration, now time.Time) (string, error) { - tokenBytes := make([]byte, 32) - if _, err := rand.Read(tokenBytes); err != nil { - return "", fmt.Errorf("generate proxy token: %w", err) - } - - token := base64.RawURLEncoding.EncodeToString(tokenBytes) - s.mu.Lock() - defer s.mu.Unlock() - s.pruneExpiredLocked(now) - s.tokens[token] = proxyTokenTarget{ - targetURL: targetURL, - referer: referer, - scope: scope, - expiresAt: now.Add(ttl), - } - return token, nil -} - -func (s *proxyTokenStore) resolve(token string, now time.Time) (proxyTokenTarget, error) { - s.mu.Lock() - defer s.mu.Unlock() - - target, ok := s.tokens[token] - if !ok { - return proxyTokenTarget{}, fmt.Errorf("invalid proxy token") - } - if !target.expiresAt.After(now) { - delete(s.tokens, token) - return proxyTokenTarget{}, fmt.Errorf("proxy token expired") - } - return target, nil -} - -func (s *proxyTokenStore) pruneExpiredLocked(now time.Time) { - for token, target := range s.tokens { - if !target.expiresAt.After(now) { - delete(s.tokens, token) - } - } -} - func NewPlaybackService(repo domain.PlaybackRepository, providers []domain.Provider, jikan *jikan.Client, episodes domain.EpisodeService, auditSvc domain.AuditService, proxyTokenKey ProxyTokenKey) domain.PlaybackService { return &playbackService{ repo: repo,