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

+48
View File
@@ -0,0 +1,48 @@
{{define "bodyclass"}}page-admin{{end}}
{{define "content"}}
{{$c := category .Data.List.Category}}
<div class="container admin">
<a class="back-link" href="/admin">{{icon "arrow-left"}}All lists</a>
<header class="admin-head">
<div class="admin-head-title">
<span class="shelf-icon">{{icon $c.Icon}}</span>
<div>
<h1 data-bind="title">{{.Data.List.Title}}</h1>
<p class="muted"><span data-bind="count">{{.Data.List.ItemCount}}</span> of <span data-bind="max">{{.Data.List.MaxItems}}</span> · {{$c.Label}}<span data-bind="hidden">{{if not .Data.List.Visible}} · Hidden{{end}}</span></p>
</div>
</div>
<div class="admin-head-actions">
<a class="btn btn--ghost" data-bind="public" href="/l/{{.Data.List.Slug}}">{{icon "external"}}View</a>
<button class="btn" type="button" data-action="edit-list">{{icon "settings"}}List settings</button>
</div>
</header>
<div class="meter" aria-hidden="true"><span data-bind="meter" style="width: {{pct .Data.List.ItemCount .Data.List.MaxItems}}%"></span></div>
<div class="editor">
<section class="panel editor-search" aria-labelledby="add-h">
<div class="panel-head">
<h2 id="add-h">Add {{$c.Label}}</h2>
<span class="muted small" data-bind="provider"></span>
</div>
<form class="search-bar" data-search role="search">
{{icon "search"}}
<input type="search" name="q" placeholder="Search for a {{$c.Singular}}…" autocomplete="off" aria-label="Search">
<span class="spinner" hidden></span>
</form>
<div class="search-results" data-results aria-live="polite"></div>
<button type="button" class="btn btn--ghost btn--block" data-action="manual">{{icon "pencil"}}Add one manually</button>
</section>
<section class="panel editor-items" aria-labelledby="items-h">
<div class="panel-head">
<h2 id="items-h">Ranking</h2>
<span class="muted small">Drag, use the arrows, or type a new rank.</span>
</div>
<ol class="rank-list" data-rank-list></ol>
<nav class="pager" data-pager aria-label="Pages"></nav>
</section>
</div>
</div>
<script type="application/json" id="boot">{{.Data.Boot}}</script>
{{end}}
{{define "scripts"}}<script type="module" src="/static/js/admin-list.js?v={{.AssetVer}}"></script>{{end}}