P0 blockers: - Fix migration numbering conflict and duplicate indexes - Change comments.post_id FK to ON DELETE CASCADE - Restrict public post detail endpoint to published posts only - Fix rate-limiting IP extraction and fallback to ConnectInfo - Harden HTML sanitizer: deny unknown URL schemes, restrict data URIs - Remove session token from login response body - Enforce image pixel/dimension limits on upload and serving P1 high-risk: - Validate uploads by magic bytes and decode GIF/WebP - Add pagination/rate-limiting to search, tag posts, and comments - Make first-admin registration and slug uniqueness check atomic - HTML-escape comment author fields - Improve HTML minify cache key and skip admin/error responses - Add mobile navigation menu P2 accessibility/quality: - Associate form labels with inputs - Key PostDetail article by slug to re-init scripts on navigation - Improve image viewer keyboard accessibility - Make theme toggle SSR-friendly and add aria-label - Invalidate slug 404 cache on create and pending count on new comment - Deduplicate tags case-insensitively P3 cleanup: - Remove unused tower-http dependency, expand make clean - Configure DB pool timeouts and verified recycling - Run background cleanup tasks immediately on startup - Use SHA-256 for stable disk cache keys - Log DB errors with Display instead of Debug - Update README migration instructions All tests pass (321), clippy clean, dx check clean.
42 lines
1.4 KiB
SQL
42 lines
1.4 KiB
SQL
CREATE TABLE IF NOT EXISTS posts (
|
|
id SERIAL PRIMARY KEY,
|
|
author_id INT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
|
|
|
title VARCHAR(200) NOT NULL,
|
|
slug VARCHAR(200) NOT NULL,
|
|
summary VARCHAR(500),
|
|
|
|
content_md TEXT NOT NULL,
|
|
content_html TEXT,
|
|
cover_image VARCHAR(500),
|
|
|
|
status TEXT NOT NULL DEFAULT 'draft',
|
|
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'))
|
|
);
|
|
|
|
CREATE INDEX idx_posts_status_published ON posts(status, published_at DESC) WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_posts_slug ON posts(slug) WHERE deleted_at IS NULL;
|
|
CREATE UNIQUE INDEX idx_posts_slug_unique ON posts(slug) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS tags (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(50) UNIQUE NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS post_tags (
|
|
post_id INT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
tag_id INT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (post_id, tag_id)
|
|
);
|
|
|
|
CREATE INDEX idx_post_tags_post ON post_tags(post_id);
|
|
CREATE INDEX idx_post_tags_tag ON post_tags(tag_id);
|
|
|
|
-- 为封面图添加索引
|
|
CREATE INDEX idx_posts_cover ON posts(cover_image) WHERE cover_image IS NOT NULL; |