chore: restore tmdb project api key fallback and move requests nav

- tmdb client falls back to bundled project API key when none configured
- move Requests link next to Recommendations in the sidebar
This commit is contained in:
Silo Server Migration
2026-05-25 01:12:47 -04:00
parent 8a86e0cf08
commit 9cf5062bf0
3 changed files with 39 additions and 35 deletions
+8 -13
View File
@@ -17,16 +17,12 @@ import (
const (
defaultBaseURL = "https://api.themoviedb.org/3"
projectAPIKey = "4ef0d7355d9ffb5151e987764708ce96"
maxRetries = 3
maxResponseBody = 1 << 20 // 1 MB
maxCollectionPresetResults = 500
)
// ErrMissingAPIKey is returned by client methods when the client was
// constructed without an API key. Callers should source the key from
// configuration rather than relying on a built-in default.
var ErrMissingAPIKey = fmt.Errorf("tmdb: api key is required")
// Client is an HTTP client for the TMDB collection preset API surface.
type Client struct {
httpClient *http.Client
@@ -36,14 +32,16 @@ type Client struct {
}
// NewClient creates a TMDB API client with the given API key and rate limit
// (requests per second). The apiKey must be sourced from configuration; a
// client constructed with an empty key returns ErrMissingAPIKey from every
// API call so misconfiguration surfaces immediately rather than silently
// using a shared built-in key.
// (requests per second). If apiKey is empty, Silo's public project API key is
// used.
func NewClient(apiKey string, rateLimit int) *Client {
apiKey = strings.TrimSpace(apiKey)
if apiKey == "" {
apiKey = projectAPIKey
}
return &Client{
httpClient: &http.Client{Timeout: 30 * time.Second},
apiKey: strings.TrimSpace(apiKey),
apiKey: apiKey,
baseURL: defaultBaseURL,
limiter: rate.NewLimiter(rate.Limit(rateLimit), rateLimit),
}
@@ -57,9 +55,6 @@ func (c *Client) SetBaseURL(url string) {
// doGet executes a GET request against the TMDB API with rate limiting,
// exponential backoff on 5xx/429, and JSON decoding into dest.
func (c *Client) doGet(ctx context.Context, path string, dest any) error {
if c.apiKey == "" {
return ErrMissingAPIKey
}
if err := c.limiter.Wait(ctx); err != nil {
return err
}
+14 -5
View File
@@ -2,17 +2,26 @@ package tmdb
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
)
func TestNewClientRequiresAPIKey(t *testing.T) {
func TestNewClientUsesProjectAPIKeyWhenEmpty(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.URL.Query().Get("api_key"); got != projectAPIKey {
t.Fatalf("api_key query = %q, want project API key", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"page":1,"total_pages":1,"total_results":0,"results":[]}`))
}))
defer server.Close()
client := NewClient("", 1000)
_, err := client.GetCollectionPreset(context.Background(), "trending", "all", "day", 10)
if !errors.Is(err, ErrMissingAPIKey) {
t.Fatalf("err = %v, want ErrMissingAPIKey", err)
client.SetBaseURL(server.URL)
if _, err := client.GetCollectionPreset(context.Background(), "trending", "all", "day", 10); err != nil {
t.Fatalf("GetCollectionPreset returned error: %v", err)
}
}
+17 -17
View File
@@ -495,6 +495,23 @@ export default function AppSidebar({ onNavigate, collapsed = false }: AppSidebar
<SidebarLabel show={showLabels}>Recommendations</SidebarLabel>
</ViewTransitionLink>
</li>
<li>
<ViewTransitionLink
to="/requests"
onClick={onNavigate}
className={navLinkClass("/requests")}
aria-current={isActive("/requests") ? "page" : undefined}
>
{isActive("/requests") && (
<span
className="absolute top-1/2 left-0 h-[18px] w-[3px] -translate-y-1/2 rounded-r-sm"
style={{ background: "var(--primary)" }}
/>
)}
<Send className="h-[18px] w-[18px] shrink-0" />
<SidebarLabel show={showLabels}>Requests</SidebarLabel>
</ViewTransitionLink>
</li>
<li>
<ViewTransitionLink
to="/calendar"
@@ -587,23 +604,6 @@ export default function AppSidebar({ onNavigate, collapsed = false }: AppSidebar
<SidebarLabel show={showLabels}>Collections</SidebarLabel>
</ViewTransitionLink>
</li>
<li>
<ViewTransitionLink
to="/requests"
onClick={onNavigate}
className={navLinkClass("/requests")}
aria-current={isActive("/requests") ? "page" : undefined}
>
{isActive("/requests") && (
<span
className="absolute top-1/2 left-0 h-[18px] w-[3px] -translate-y-1/2 rounded-r-sm"
style={{ background: "var(--primary)" }}
/>
)}
<Send className="h-[18px] w-[18px] shrink-0" />
<SidebarLabel show={showLabels}>Requests</SidebarLabel>
</ViewTransitionLink>
</li>
<li>
<ViewTransitionLink
to={buildPersonalCatalogHref("history")}