Commit Graph
4 Commits
Author SHA1 Message Date
Esa Kataja 4f337b6202 Add lyrics: paste, fetch, and read them while reviewing
Lyrics are suggested at submission and never imposed. The conversion worker
makes one LRCLIB lookup with whatever metadata exists, and the waiting page
has a Hae sanoitukset button that re-queries with whatever title and artist
are currently typed — which is the case that matters, since our metadata comes
from ID3 tags and YouTube uploaders. Neither path overwrites typed text.

- lyrics text on both submissions and songs, copied across at publish. Nothing
  has launched, so the column goes into 001_init.sql rather than a migration
- The lock does not cover lyrics: it freezes what the song claims to be, and
  nobody reviewed the lyrics. So the submitter can still fix them afterwards,
  or paste them for an old song a year later
- The review strip gained a second pane: lyrics on the left, review on the
  right, so following the words costs no scrolling. No lyrics means no pane,
  not an empty one. Below 1024px the panes stack
- LRC timestamps are stored but stripped for reading — they belong to the
  player, not the reader
- The lyrics box is capped and scrolls inside itself, so a long song cannot
  stretch the strip past the screen

Fixes a real bug found on the way: saveMetadata cleared any field the request
did not carry, so publishing wiped the lyrics the worker had just fetched.
Fields absent from a request now keep their stored value.

The client identifies itself to LRCLIB as "levyraati" and nothing more.

Tests cover cleanLyrics keeping line breaks, and fetchLyrics against a local
server: synced beats plain, instrumentals and wrong-length takes are skipped,
and a miss is empty with no error.
2026-08-01 00:21:31 +03:00
Esa Kataja f1e907bac3 Add stats, profiles, avatars and palaute
Step 6. The surfaces around the review loop.

- Nine leaderboards, ordered and limited in SQL, each with a deterministic
  tie-break so a tied board doesn't reshuffle between reloads. Min 3 reviews
  to qualify, for reviewer boards too
- Profiles show counts and history-wide averages and the member's songs,
  never a list of their reviews — per-song opinion stays gated
- Avatars: 5MB in, 256px JPEG out, ffmpeg's re-encode being the validation.
  No upload still means initials, and avatars are public
- Changing your own password requires the current one and drops your other
  sessions
- Palaute: free text plus the page you were on, carried in a footer link, and
  the user agent from the header. Reporters see their own; the admin resolves
  them with a timestamp rather than a status enum
- Admin gained the song list with delete, the reports page, and an open-report
  count on the dashboard

Two theme fixes the screenshots caught: leaderboard ranks need a CSS counter
because display:grid suppresses list markers, and count-based boards were
printing 3.0 where they mean 3.
2026-07-31 22:34:04 +03:00
Esa Kataja 91e136055c Add the YouTube submission path
Step 5. A URL goes through the same pipeline as an upload — it just gains a
download step and a source_url.

- The host allowlist is checked on the parsed hostname before yt-dlp is
  invoked, so lookalikes and userinfo tricks are refused too
- yt-dlp -J reads metadata synchronously with a 15s timeout; a timeout leaves
  the fields blank rather than failing the submission
- Over-long tracks are refused from that metadata, before a byte is downloaded
- Failed URL submissions offer Yritä uudelleen with the typed text intact;
  uploads cannot retry, so they offer re-upload

Prefill takes track then title, and artist then creator then uploader, and
leaves a field blank rather than inventing one. testdata/ytdlp-noose.json is a
real dump of an ordinary upload, which has none of the music fields.

Also fixes a URL-only submit being blocked by the file input's required
attribute — HTML cannot express "one of these two", so the server says it.

The image now takes yt-dlp from Alpine 3.24 instead of pip, which drops
python3 and pip entirely; see decision 19.
2026-07-31 21:54:15 +03:00
Esa Kataja 80d3e36679 Add the submission pipeline and the review loop
Steps 3 and 4 of the build order. A member can now upload a song, watch it
convert, publish it, and review what everyone else has published.

Pipeline:
- ffprobe reads tags synchronously at submit so prefill never races typing;
  ffmpeg converts to Opus in the background, two at a time
- ffmpeg succeeding is the validation — no container sniffing
- publish moves the file inside the transaction, so a song row and its .ogg
  appear together or neither does
- five submissions per rolling 24h, failures excluded

Reviews and the reveal rule:
- the queue is unreviewed songs only, oldest first, never your own
- other people's reviews and the average are withheld in the query, not the
  template — a hidden average is never sent
- 30 minutes to edit or delete your own review, enforced in the WHERE clause
- deleting the last review unlocks the song for its submitter again

The waiting page has one button: the metadata form autosaves after a pause in
typing, and Julkaise submits it and publishes in the same request, so nothing
is lost without JS.

Genres store an English code and render a Finnish label.
2026-07-31 21:42:49 +03:00