Ten members and a handful of songs a week never needed a database server,
and the server was the last thing making this a two-container deployment.
modernc.org/sqlite is pure Go, so CGO_ENABLED=0 survives and the dependency
count is unchanged: pgx out, sqlite in.
The port stayed small because the driver matches $1-style placeholders
against argument ordinals exactly as pgx does, so no query needed rewriting
for parameters. What did change:
- timestamptz becomes timestamp holding UTC 'YYYY-MM-DD HH:MM:SS'. The
declared type is what makes the driver return time.Time, and the
fixed-width UTC string is what makes ordering and comparison against
datetime('now') mean what they say.
- interval has no equivalent: sessions.idle_ttl is seconds, and the review
edit window travels as a SQLite date modifier string.
- No stddev_pop, so the divisive and unified boards spell the population
formula out, guarded with max(0.0, ...) because cancellation returns a
tiny negative when every score is identical.
- foreign_keys is off by default, so the cascades only exist because the
pragma is set on every connection.
Drops the postgres service, its healthcheck, the depends_on gate, the
startup retry loop and POSTGRES_PASSWORD. ./storage is now the whole
backup. Tests get a fresh database file per test and run everywhere
instead of skipping without TEST_DATABASE_URL.
96 lines
3.5 KiB
SQL
96 lines
3.5 KiB
SQL
-- 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'))
|
|
);
|