Initial TopDeck: self-hosted ranked favorites

A single Go binary with SQLite that serves a public page of the owner's
ranked lists (movies, TV, albums, songs, video games, theater) and a
login-gated editor.

- Lists of 1–65,535 entries (100 recommended), several per category
- Search-to-add via TMDB, TheTVDB, IGDB, Deezer/MusicBrainz, Wikipedia;
  manual entry as a fallback
- Theater entries carry a production (Broadway, tour, …), venue and cast
- Artwork cached locally; URL override and uploads
- Drag, arrow and type-a-rank reordering; paginated for long lists
- 12 theme presets plus custom CSS
- First-run setup code or env-provided admin; bcrypt, rate-limited login,
  CSRF-guarded JSON API
- Dockerfile and docker-compose.yml

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-24 16:54:19 -07:00
commit e9a75a968a
53 files changed
+7078

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
// Package config loads runtime configuration from environment variables.
package config
import (
"os"
"strconv"
"strings"
)
// Config holds process-level settings. Anything the owner can change from the
// web UI lives in the database instead (see store.Settings); the API-key
// fields here only act as overrides when set.
type Config struct {
ListenAddr string
DataDir string
AdminUsername string
AdminPassword string
SecureCookies bool
TrustProxy bool
// Optional overrides for provider credentials. When set, the matching
// setting is locked in the web UI.
TMDBKey string
TVDBKey string
TVDBPin string
IGDBClientID string
IGDBClientSecret string
}
func Load() Config {
port := env("TOPDECK_PORT", "4242")
return Config{
ListenAddr: env("TOPDECK_LISTEN", ":"+port),
DataDir: env("TOPDECK_DATA_DIR", "./data"),
AdminUsername: env("TOPDECK_ADMIN_USERNAME", ""),
AdminPassword: env("TOPDECK_ADMIN_PASSWORD", ""),
SecureCookies: envBool("TOPDECK_SECURE_COOKIES", false),
TrustProxy: envBool("TOPDECK_TRUST_PROXY", false),
TMDBKey: env("TOPDECK_TMDB_API_KEY", ""),
TVDBKey: env("TOPDECK_TVDB_API_KEY", ""),
TVDBPin: env("TOPDECK_TVDB_PIN", ""),
IGDBClientID: env("TOPDECK_IGDB_CLIENT_ID", ""),
IGDBClientSecret: env("TOPDECK_IGDB_CLIENT_SECRET", ""),
}
}
func env(key, def string) string {
if v, ok := os.LookupEnv(key); ok && strings.TrimSpace(v) != "" {
return strings.TrimSpace(v)
}
return def
}
func envBool(key string, def bool) bool {
v, ok := os.LookupEnv(key)
if !ok {
return def
}
b, err := strconv.ParseBool(strings.TrimSpace(v))
if err != nil {
return def
}
return b
}