Add skeleton: config, migrations, startup sweep, two listeners
Step 1 of the build order in docs/decisions.md. Boots, applies migrations before serving, and serves a health check and an empty admin page. - 001_init.sql is the full schema from docs/spec.md, including the check constraints and indexes the old app lacked - The startup sweep fails submissions left mid-conversion by a restart; an in-process goroutine dies with the process and those rows would otherwise say converting forever - Admin is Basic Auth from env on its own listener, fatal at startup when ADMIN_PASSWORD is unset
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
create table users (
|
||||
id bigserial primary key,
|
||||
name text not null,
|
||||
email text not null unique,
|
||||
password_hash text not null,
|
||||
avatar text,
|
||||
banned boolean not null default false,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table sessions (
|
||||
token text primary key,
|
||||
user_id bigint not null references users (id) on delete cascade,
|
||||
idle_ttl interval not null,
|
||||
expires_at timestamptz not null,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index on sessions (user_id);
|
||||
|
||||
create table invites (
|
||||
id bigserial primary key,
|
||||
code text not null unique,
|
||||
is_valid boolean not null default true,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table songs (
|
||||
id bigserial primary key,
|
||||
title text not null,
|
||||
artist text not null,
|
||||
genre text not null,
|
||||
description text,
|
||||
audio_file text not null,
|
||||
duration_seconds integer not null,
|
||||
source_url text,
|
||||
submitted_by bigint not null references users (id),
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index on songs (created_at desc);
|
||||
|
||||
create table submissions (
|
||||
id bigserial primary key,
|
||||
user_id bigint 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,
|
||||
created_at timestamptz not null default 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 on submissions (user_id, created_at desc);
|
||||
|
||||
create table reviews (
|
||||
id bigserial primary key,
|
||||
song_id bigint not null references songs (id) on delete cascade,
|
||||
reviewer_id bigint not null references users (id),
|
||||
score integer not null check (score between 1 and 100),
|
||||
text text not null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (song_id, reviewer_id)
|
||||
);
|
||||
|
||||
create index on reviews (song_id);
|
||||
|
||||
-- The queue asks "songs this member has not reviewed" — that lookup is by reviewer.
|
||||
create index on reviews (reviewer_id, song_id);
|
||||
|
||||
create table reports (
|
||||
id bigserial primary key,
|
||||
user_id bigint not null references users (id) on delete cascade,
|
||||
body text not null,
|
||||
page text,
|
||||
user_agent text,
|
||||
resolved_at timestamptz,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
Reference in New Issue
Block a user