osaet/backend/migrations/001_admin_schema.sql
yarnom f0b50d13ea feat: add admin publishing workflow and yar theme
Add Go/Postgres admin APIs, Angular admin UI, manual build flow, asset uploads, markdown import/export, configurable slug generation, and the Yar reading theme. Exclude local docs and generated development artifacts from version control.
2026-06-01 15:48:04 +08:00

100 lines
4 KiB
SQL

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_login_at TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
summary TEXT NOT NULL DEFAULT '',
body_markdown TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'draft',
cover TEXT NOT NULL DEFAULT '',
version INTEGER NOT NULL DEFAULT 1,
slug_source TEXT NOT NULL DEFAULT 'manual',
slug_locked BOOLEAN NOT NULL DEFAULT false,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT posts_status_check CHECK (status IN ('draft', 'published', 'archived', 'deleted')),
CONSTRAINT posts_version_check CHECK (version >= 1)
);
CREATE INDEX IF NOT EXISTS idx_posts_status ON posts(status);
CREATE INDEX IF NOT EXISTS idx_posts_published_at ON posts(published_at DESC);
CREATE INDEX IF NOT EXISTS idx_posts_updated_at ON posts(updated_at DESC);
CREATE TABLE IF NOT EXISTS post_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
version INTEGER NOT NULL,
title TEXT NOT NULL,
summary TEXT NOT NULL,
body_markdown TEXT NOT NULL,
status TEXT NOT NULL,
reason TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT post_versions_status_check CHECK (status IN ('draft', 'published', 'archived', 'deleted')),
CONSTRAINT post_versions_reason_check CHECK (reason IN ('save', 'publish', 'unpublish', 'archive', 'restore', 'import', 'rollback')),
CONSTRAINT post_versions_version_check CHECK (version >= 1),
UNIQUE (post_id, version)
);
CREATE INDEX IF NOT EXISTS idx_post_versions_post_id_created_at ON post_versions(post_id, created_at DESC);
CREATE TABLE IF NOT EXISTS tags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE,
slug TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS post_tags (
post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
CREATE INDEX IF NOT EXISTS idx_post_tags_tag_id ON post_tags(tag_id);
CREATE TABLE IF NOT EXISTS build_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
trigger TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued',
post_id UUID REFERENCES posts(id) ON DELETE SET NULL,
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
log TEXT NOT NULL DEFAULT '',
error TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT build_jobs_trigger_check CHECK (trigger IN ('publish', 'manual', 'import', 'sync')),
CONSTRAINT build_jobs_status_check CHECK (status IN ('queued', 'running', 'success', 'failed', 'cancelled'))
);
CREATE INDEX IF NOT EXISTS idx_build_jobs_status_created_at ON build_jobs(status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_build_jobs_post_id_created_at ON build_jobs(post_id, created_at DESC);
CREATE TABLE IF NOT EXISTS assets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
path TEXT NOT NULL UNIQUE,
original_name TEXT NOT NULL,
mime_type TEXT NOT NULL,
size_bytes BIGINT NOT NULL,
sha256 TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT assets_size_bytes_check CHECK (size_bytes >= 0)
);
CREATE INDEX IF NOT EXISTS idx_assets_sha256 ON assets(sha256);