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.
This commit is contained in:
parent
b78f4b39c9
commit
f0b50d13ea
121 changed files with 27139 additions and 550 deletions
100
backend/migrations/001_admin_schema.sql
Normal file
100
backend/migrations/001_admin_schema.sql
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
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);
|
||||
11
backend/migrations/002_admin_sessions.sql
Normal file
11
backend/migrations/002_admin_sessions.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE IF NOT EXISTS admin_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_admin_sessions_user_id ON admin_sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_admin_sessions_expires_at ON admin_sessions(expires_at);
|
||||
Loading…
Add table
Add a link
Reference in a new issue