-- Timestamps are declared `timestamp` and hold UTC 'YYYY-MM-DD HH:MM:SS': the declared type is what -- makes the driver hand them back as time.Time, and a fixed-width UTC string is what makes -- `order by created_at` and `expires_at > datetime('now')` mean what they say. create table users ( id integer primary key autoincrement, name text not null, email text not null unique, password_hash text not null, avatar text, banned integer not null default 0, created_at timestamp not null default (datetime('now')) ); create table sessions ( token text primary key, user_id integer not null references users (id) on delete cascade, idle_ttl integer not null, -- seconds; SQLite has no interval type expires_at timestamp not null, created_at timestamp not null default (datetime('now')) ); create index sessions_user on sessions (user_id); create table invites ( id integer primary key autoincrement, code text not null unique, is_valid integer not null default 1, created_at timestamp not null default (datetime('now')) ); create table songs ( id integer primary key autoincrement, title text not null, artist text not null, genre text not null, description text, -- LRC or plain text, told apart by whether the first line starts with '['. Not covered by the -- lock: nobody reviewed the lyrics. lyrics text, audio_file text not null, duration_seconds integer not null, source_url text, submitted_by integer not null references users (id), created_at timestamp not null default (datetime('now')) ); create index songs_created_at on songs (created_at desc); create table submissions ( id integer primary key autoincrement, user_id integer not null references users (id) on delete cascade, status text not null default 'queued', status_msg text, source_url text, tmp_path text, title text, artist text, genre text, description text, lyrics text, created_at timestamp not null default (datetime('now')), constraint submissions_status check ( status in ('queued', 'downloading', 'converting', 'ready', 'failed') ) ); -- The submission quota (5 per rolling 24h, failures excluded) reads this. create index submissions_user_created on submissions (user_id, created_at desc); create table reviews ( id integer primary key autoincrement, song_id integer not null references songs (id) on delete cascade, reviewer_id integer not null references users (id), score integer not null check (score between 1 and 100), text text not null, created_at timestamp not null default (datetime('now')), updated_at timestamp not null default (datetime('now')), unique (song_id, reviewer_id) ); create index reviews_song on reviews (song_id); -- The queue asks "songs this member has not reviewed" — that lookup is by reviewer. create index reviews_reviewer_song on reviews (reviewer_id, song_id); create table reports ( id integer primary key autoincrement, user_id integer not null references users (id) on delete cascade, body text not null, page text, user_agent text, resolved_at timestamp, created_at timestamp not null default (datetime('now')) );