尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
A searchable archive of K-pop music show wins since 2014, with artist and song leaderboards and detailed win histories
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Music Show Wins is a small Django community resource for browsing K-pop music show wins from 2014 onward. Wikipedia is the automated source in this phase.
Copy .env.example to .env, then install the locked environment and start
PostgreSQL:
copy .env.example .env
uv sync --frozen
docker compose -f docker-compose-db.yaml up -d
uv run python manage.py migrate
Restore the tracked, cleaned 2014–2025 domain snapshot into an empty database:
uv run python manage.py restore_bootstrap
The restore is atomic, validates all references and expected counts, and refuses
to run when any domain table already contains data. The snapshot restores 6
shows, 283 artists, 858 songs and 2,906 dated wins. It also creates 12 reviewed
legacy discrepancy issues and 33 rejected legacy_undated issues for the
aggregate-only Music Core 2016 history. Those issues remain as audit data, not
public dated wins. A different JSON file may be supplied explicitly with
--input; local paths do not need to be committed.
Start the temporary Django UI with:
uv run python manage.py runserver
The UI provides artist and song leaderboards, artist search and details, and a dated wins view. It is intentionally small while the backend is being revived.
The public Next.js frontend lives in frontend/.
Win videos come from the offline operator tools and reach Django only through reviewed, exported manifests. Follow the video workflow.
Synchronization runs sequentially through Wikimedia's Action API. It retries
transient failures, sends maxlag, checks the page revision before fetching
HTML, and treats the revision as the cache key. The default User-Agent is:
KpopWins/0.1 (https://github.com/badmagick329/musicshowwins/issues)
The GitHub Issues page is the public contact channel for this User-Agent. The default sync target is the current and previous calendar year across active shows:
uv run python manage.py sync_wikipedia
uv run python manage.py sync_wikipedia --year 2026 --show music-bank
uv run python manage.py sync_wikipedia --year 2024 --year 2025 --show inkigayo
uv run python manage.py sync_wikipedia --dry-run
uv run python manage.py sync_wikipedia --dry-run --format json
uv run python manage.py sync_wikipedia --dry-run --strict
uv run python manage.py approve_wikipedia_source --show music-bank --year 2026
Use repeatable --year and --show options for historical backfills (2014 or
later). --format text is the default human-readable report; --format json
writes a machine-readable report to stdout only. A complete page is validated
before it is applied atomically. New wins are added with source page and
revision provenance; conflicting winners are quarantined as import issues, and
missing historical rows are reported but never deleted. Exact credited
collaboration names are kept as one artist.
Source table expansion is bounded before allocation: at most 64 columns and
colspan, 1,000 rowspan, and page totals of 10,000 actual rows, 250,000 expanded
cells and 500,000 work units (row traversal, carried spans, declared span area
and output width). Spans must be complete positive ASCII integers; overlapping
spans are rejected. A limit or malformed table fails the page as INVALID_SOURCE
with a useful error and applies no partial catalogue or provenance changes.
--dry-run does not write ImportRun, SourcePage, ImportIssue, revision or
domain data. Use it to review every proposed addition, conflict and missing
legacy win before a real sync. --strict exits unsuccessfully whenever source
failures or reconciliation work remains. A real sync is an explicit approval
step after reviewing the dry-run report. New show/year sources are denied by
default; approve one explicitly with approve_wikipedia_source after reviewing
its dry-run report. A real sync reports unapproved pages without writing them.
Use --revoke to disable a previously approved source. Music Core 2016 is a
known unavailable Wikipedia page; its original aggregate records are retained
as rejected audit issues and are not restored as dated wins.
The API is read-only apart from public correction submissions:
/api/v1/shows
/api/v1/artists
/api/v1/artists/{slug-or-id}
/api/v1/songs
/api/v1/songs/{id}
/api/v1/wins
/api/v1/leaderboards/artists
/api/v1/leaderboards/songs
/api/v1/sitemap
/api/v1/corrections (POST)
/api/schema/
/api/docs/
Collection endpoints use page-number pagination (100 by default) and support search, artist, song, show, year, date range, ordering, and leaderboard filters where applicable. Anonymous requests are throttled at 60 per minute.
The Next.js frontend caches public API GET responses for 24 hours under one
public-archive tag. Cache keys include the full URL, so pages, searches,
filters, pagination, and detail records are cached independently. The Wikipedia
sync and win-reference import commands invalidate the shared tag after every
successful non-dry-run execution. After invalidation, the frontend immediately
warms the API responses used by the unfiltered home, artists, songs, wins, and
shows pages. The 24-hour lifetime is a fallback if an invalidation callback is
missed.
Refresh uses two authenticated requests to /api/revalidate: first
?action=invalidate, then ?action=warm. Next applies tag invalidation at request
completion, so warming in that same request would expire the new entries.
Production must set the same INTERNAL_API_SECRET for Django and Next.js. Public
frontend cache misses carry X-KpopWins-Public-Read: 1 and share a database-backed
budget of 600 GETs per fixed minute across workers and replicas. Cache hits
remain usable after exhaustion; rejected misses receive 429 with Retry-After.
Trusted private consumers and explicit warming omit that marker and bypass the
anonymous throttle. Production must also set the same CACHE_REVALIDATION_SECRET
for both services, and configure Django's CACHE_REVALIDATION_URL to the frontend's
/api/revalidate endpoint. Public POSTs remain uncached and retain their
per-visitor throttling.
The frontend accepts POST only for corrections and caps their body at 32 KiB, checking declared length before reading and actual stream bytes while reading. Oversized requests return 413 without reaching Django.
Tests use an isolated in-memory SQLite database; local development and deployed instances use PostgreSQL.
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run python manage.py check
uv run python manage.py makemigrations --check --dry-run
Frontend checks:
cd frontend
pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm test
pnpm build
The root Dockerfile builds one Linux AMD64 image containing the locked Django
environment, collected Django static files, Gunicorn, and the standalone Next.js
server with its Node.js runtime. The same immutable image can run either process:
docker build --platform linux/amd64 --tag kpopwins:0123456789ab .
docker run --rm --env-file production.env kpopwins:0123456789ab gunicorn musicshowwins.wsgi:application --bind 0.0.0.0:8000
docker run --rm kpopwins:0123456789ab node /app/frontend/server.js
docker run --rm kpopwins:0123456789ab python --version
docker run --rm kpopwins:0123456789ab node --version
Production orchestration and host configuration are intentionally kept outside version control. The public repository only defines and verifies the portable application image.
The runtime defaults to DEBUG=0 and refuses Django startup without a
non-development SECRET_KEY. Create production.env outside version control
with a randomly generated production secret, ALLOWED_HOSTS, BASE_URL,
CSRF_TRUSTED_ORIGINS, and the deployment's database settings. Configure the
shared API/cache secrets described above for both services. The build-only
static collection secret is never a runtime environment default. Local setup
uses the explicit DEBUG=1 in .env.example.
CI actions use full commit pins and contents: read; uv is pinned to the same
exact version as the Docker build. To refresh them, review each publisher's
release notes and action inputs, resolve the release tag with
git ls-remote https://github.com/<publisher>/<action>.git refs/tags/<version>,
and verify the commit in that publisher's release before updating its SHA and
version comment. For an annotated tag, use its peeled commit (^{}). Update
both uv versions together. Run all CI jobs, including the production image's
missing-secret and configured-startup checks, before accepting a refresh.
Deployment orchestration runs migrations and synchronizes the committed artist-moment content before starting the API. The content file declares each moment's draft or published state and is authoritative for copy and citations; omitted entries are not deleted. Cache refresh runs separately after the API and frontend are healthy, and a refresh failure does not stop the application.