jevstrudel.git / worker / migrations / 0006_performance_players.sql

Who played each performance, and when to the millisecond. Owner: src/listening-store.ts writes both (for src/listening.ts, which takes the player from the session cookie, never from the request's body); src/data.ts and src/activity.ts read them.

What is identifying here, and why (migrations/CLAUDE.md): a performance names the account that played it, when one was signed in, so a listener can find their own takes ("recently played", their profile) and everyone can see who played what (the activity feed, the data tab). Like every row it is public. A take outlives its player's account: deleting the account leaves the take, anonymous (ON DELETE SET NULL). Signed out, a take has no player, as every take did before this migration.

A performance of a listener's song is recorded too, its song being "listener:<id>" (the id convention the MCPs and the page use); 0003's CHECK on song (3 to 129 characters) already admits it.

18ALTER TABLE performances ADD COLUMN user_id TEXT REFERENCES users (id) ON DELETE SET NULL
19  CHECK (user_id IS NULL OR (length(user_id) = 22 AND user_id NOT GLOB '*[^A-Za-z0-9_-]*'));

When the take was stored, milliseconds since the epoch, UTC. Takes from before this migration kept only their day: they are dated noon UTC of it, which orders them correctly by day and says nothing false about the hour to anyone who reads day beside it. SQLite's ALTER TABLE cannot add a column that is NOT NULL without a default, so the column allows NULL, and none stays: the backfill below dates every take stored before, and the trigger every take the Worker live during the deploy stores without it (the deploy migrates before it publishes, worker/migrations/CLAUDE.md).

29ALTER TABLE performances ADD COLUMN recorded_at INTEGER CHECK (recorded_at IS NULL OR recorded_at > 0);
30UPDATE performances SET recorded_at = CAST(strftime('%s', day || ' 12:00:00') AS INTEGER) * 1000 WHERE recorded_at IS NULL;
31CREATE TRIGGER performances_dated AFTER INSERT ON performances WHEN NEW.recorded_at IS NULL
32BEGIN
33  UPDATE performances SET recorded_at = CAST(strftime('%s', NEW.day || ' 12:00:00') AS INTEGER) * 1000 WHERE id = NEW.id;
34END;

an account's takes, newest first (you tab, profiles); a song's, newest first (takes, moves)

37CREATE INDEX performances_by_player ON performances (user_id, recorded_at);
38CREATE INDEX performances_by_song_time ON performances (song, recorded_at);