feat: add database schema and SQLx migrations

- 5 reversible migrations: extensions/enums, tables, indexes, views, config seed
- Tables: event, user, session, upload, hashtag, upload_hashtag, comment,
  comment_hashtag, like, export_job, config
- Views: v_feed (uploads with like/comment counts), v_hashtag_counts
- Indexes optimised for feed queries, session lookup, hashtag filtering
- Config table seeded with default rate limits and quotas
- db.rs module: PgPool creation with auto-migration on startup
- docker-compose.override.yml: expose db port 5432 for local dev
- Fix crate names: async_zip, tower_governor (underscore, not hyphen)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fabi
2026-03-31 21:15:25 +02:00
parent b89b1d6ffa
commit e976f0f670
15 changed files with 4492 additions and 3 deletions

View File

@@ -0,0 +1,112 @@
-- event
CREATE TABLE event (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
cover_image_path TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
uploads_locked_at TIMESTAMPTZ,
export_released_at TIMESTAMPTZ,
export_zip_ready BOOLEAN NOT NULL DEFAULT FALSE,
export_html_ready BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- user
CREATE TABLE "user" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES event(id) ON DELETE CASCADE,
display_name TEXT NOT NULL,
role user_role NOT NULL DEFAULT 'guest',
is_banned BOOLEAN NOT NULL DEFAULT FALSE,
uploads_hidden BOOLEAN NOT NULL DEFAULT FALSE,
recovery_pin_hash TEXT NOT NULL,
total_upload_bytes BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- session
CREATE TABLE session (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
token_hash TEXT NOT NULL UNIQUE,
expires_at TIMESTAMPTZ NOT NULL,
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- upload
CREATE TABLE upload (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES event(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id),
original_path TEXT NOT NULL,
preview_path TEXT,
thumbnail_path TEXT,
mime_type TEXT NOT NULL,
original_size_bytes BIGINT NOT NULL,
caption TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
-- hashtag
CREATE TABLE hashtag (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES event(id) ON DELETE CASCADE,
tag TEXT NOT NULL,
UNIQUE (event_id, tag)
);
-- upload_hashtag (junction)
CREATE TABLE upload_hashtag (
upload_id UUID NOT NULL REFERENCES upload(id) ON DELETE CASCADE,
hashtag_id UUID NOT NULL REFERENCES hashtag(id) ON DELETE CASCADE,
PRIMARY KEY (upload_id, hashtag_id)
);
-- comment
CREATE TABLE comment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
upload_id UUID NOT NULL REFERENCES upload(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id),
body TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
-- comment_hashtag (junction)
CREATE TABLE comment_hashtag (
comment_id UUID NOT NULL REFERENCES comment(id) ON DELETE CASCADE,
hashtag_id UUID NOT NULL REFERENCES hashtag(id) ON DELETE CASCADE,
PRIMARY KEY (comment_id, hashtag_id)
);
-- like
CREATE TABLE "like" (
upload_id UUID NOT NULL REFERENCES upload(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (upload_id, user_id)
);
-- export_job
CREATE TABLE export_job (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES event(id) ON DELETE CASCADE,
type export_type NOT NULL,
status export_status NOT NULL DEFAULT 'pending',
progress_pct SMALLINT NOT NULL DEFAULT 0 CHECK (progress_pct BETWEEN 0 AND 100),
file_path TEXT,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
UNIQUE (event_id, type)
);
-- config (admin-configurable runtime settings)
CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);