
MIDI Library with Voting — misc.webworld.nu
===========================================

This bundle contains:
- S3 front-end (static): /midi/ player with ratings
- AWS SAM backend: API (vote, scores) + DynamoDB tables
- CloudFront Function: basic hotlink protection for .mid files

Folder structure
----------------
s3/midi/
  index.html       -> The player page (English, matches misc.webworld.nu style)
  midi.css         -> Minimal styles for the table and controls
  player.js        -> Tone.js-based MIDI playback + voting UX
  midi.json        -> Track manifest you control

api/
  vote.mjs         -> Lambda for POST /vote (validates Turnstile, origin, sets HttpOnly cookie)
  scores.mjs       -> Lambda for GET /scores (returns aggregates)
  package.json     -> AWS SDK v3 deps
  template.yaml    -> SAM template (DynamoDB tables + HTTP API routes)

cloudfront/
  viewer-request.js -> CloudFront Function to block hotlinking of MIDI files (403)

How it works
------------
1) The page loads `midi.json`, renders the list, and plays MIDI via Tone.js.
2) When a user clicks a star, a Turnstile token is included. The API:
   - Checks `Origin` + `Referer` are `https://misc.webworld.nu`
   - Verifies the Turnstile token serverside
   - Issues an HttpOnly cookie `miditoken` (SameSite=Lax, Secure, domain=.webworld.nu) on first vote
   - Records one vote per (file_id, miditoken) and atomically updates aggregates
3) The scores endpoint returns `{ scores: { [id]: { votes, sum, avg }}}` for quick rendering.

Deploy — Front-end (S3 + CloudFront)
------------------------------------
- Upload `s3/midi/*` to `s3://misc.webworld.nu/midi/`
- Put MIDI files under `s3://misc.webworld.nu/midi/files/` and reference them in `midi.json`
- (Optional) Attach `cloudfront/viewer-request.js` as a **CloudFront Function** to the distribution that serves `/midi/files/*` and return 403 when Referer/Origin isn't `misc.webworld.nu`.

Deploy — Backend (SAM)
----------------------
- In `api/`, run: `npm install`
- Deploy with SAM:
    sam build
    sam deploy --guided
  Set parameters:
    CorsOrigin = https://misc.webworld.nu
    CookieDomain = .webworld.nu
    TurnstileSecret = (from Cloudflare Turnstile Admin)

- After deploy, map the API to `https://api.misc.webworld.nu` (custom domain in API Gateway). Update `apiBase` in `/midi/index.html` accordingly.

Cloudflare Turnstile
--------------------
- Create a widget for `misc.webworld.nu`.
- Replace `YOUR_TURNSTILE_SITEKEY` in `/midi/index.html`.
- Pass the secret to the Lambda via the SAM parameter `TurnstileSecret`.

CORS & Cookies
--------------
- The API sets `Access-Control-Allow-Origin: https://misc.webworld.nu` and `Access-Control-Allow-Credentials: true`.
- Front-end fetch calls use `credentials: "include"` so the HttpOnly `miditoken` cookie is attached automatically.

Duplicate voting
----------------
- A user can vote **once per file** (enforced by `(file_id#miditoken)` key).
- Subsequent attempts return the current aggregate without changes.

Notes on playback
-----------------
- Native `<audio>` can't reliably play `.mid` across browsers. We use Tone.js to parse and synthesize in the browser.
- For higher fidelity (GM instruments), you can switch to WebAudioFont or a GM soundfont pipeline later.

Security notes
--------------
- Referer checks in CloudFront reduce casual hotlinking but can be spoofed. For stronger protection, keep `.mid` private in S3 and route playback through a signed CloudFront URL or a streaming Lambda that validates the origin.
- Turnstile plus cookie + origin checks greatly reduces automated voting. Add WAF rate limits if needed.

Manifest example
----------------
midi.json:
{
  "tracks": [
    { "id": "fur-elise", "title": "Für Elise", "file": "https://misc.webworld.nu/midi/files/fur-elise.mid" }
  ]
}

Support
-------
All UI strings are in English and styled to fit WebWorld.nu. You can further tweak with `/style.css`.
