feat: init
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
-- 011: Announcement Channel — articles, comments, reactions, follows, cross-posts
|
||||
--
|
||||
-- New tables:
|
||||
-- article, article_comment, article_reaction,
|
||||
-- channel_follow, article_cross_post
|
||||
|
||||
-- ============================================================
|
||||
-- 1. Articles
|
||||
-- ============================================================
|
||||
|
||||
-- models/channels/articles.rs → article
|
||||
CREATE TABLE IF NOT EXISTS article (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
|
||||
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
summary TEXT NULL,
|
||||
body TEXT NOT NULL,
|
||||
cover_image_url TEXT NULL,
|
||||
status TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL,
|
||||
tags TEXT[] NOT NULL,
|
||||
published_at TIMESTAMPTZ NULL,
|
||||
published_by UUID NULL REFERENCES "user"(id) ON DELETE SET NULL,
|
||||
scheduled_at TIMESTAMPTZ NULL,
|
||||
unpublished_at TIMESTAMPTZ NULL,
|
||||
views_count BIGINT NOT NULL DEFAULT 0,
|
||||
comments_count BIGINT NOT NULL DEFAULT 0,
|
||||
reactions_count BIGINT NOT NULL DEFAULT 0,
|
||||
cross_posted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
cross_posted_from UUID NULL REFERENCES article(id) ON DELETE SET NULL,
|
||||
metadata JSONB NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
deleted_at TIMESTAMPTZ NULL,
|
||||
CONSTRAINT uq_article_channel_slug UNIQUE (channel_id, slug)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_channel_id ON article (channel_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_author_id ON article (author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_status ON article (status);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_published_at ON article (published_at DESC) WHERE published_at IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_article_cross_posted_from ON article (cross_posted_from) WHERE cross_posted_from IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_article_deleted ON article (deleted_at) WHERE deleted_at IS NOT NULL;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. Article Comments
|
||||
-- ============================================================
|
||||
|
||||
-- models/channels/article_comments.rs → article_comment
|
||||
CREATE TABLE IF NOT EXISTS article_comment (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
article_id UUID NOT NULL REFERENCES article(id) ON DELETE CASCADE,
|
||||
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
|
||||
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
parent_comment_id UUID NULL REFERENCES article_comment(id) ON DELETE CASCADE,
|
||||
body TEXT NOT NULL,
|
||||
edited_at TIMESTAMPTZ NULL,
|
||||
deleted_at TIMESTAMPTZ NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_comment_article_id ON article_comment (article_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_comment_author_id ON article_comment (author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_comment_parent ON article_comment (parent_comment_id) WHERE parent_comment_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_article_comment_deleted ON article_comment (deleted_at) WHERE deleted_at IS NOT NULL;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. Article Reactions
|
||||
-- ============================================================
|
||||
|
||||
-- models/channels/article_reactions.rs → article_reaction
|
||||
CREATE TABLE IF NOT EXISTS article_reaction (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
article_id UUID NOT NULL REFERENCES article(id) ON DELETE CASCADE,
|
||||
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
CONSTRAINT uq_article_reaction UNIQUE (article_id, user_id, content)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_reaction_article_id ON article_reaction (article_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_reaction_user_id ON article_reaction (user_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. Channel Follows
|
||||
-- ============================================================
|
||||
|
||||
-- models/channels/channel_follows.rs → channel_follow
|
||||
CREATE TABLE IF NOT EXISTS channel_follow (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
source_channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
|
||||
target_workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
|
||||
target_channel_id UUID NULL REFERENCES channel(id) ON DELETE SET NULL,
|
||||
webhook_url TEXT NULL,
|
||||
webhook_secret_ciphertext TEXT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
followed_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
unfollowed_at TIMESTAMPTZ NULL,
|
||||
last_delivery_at TIMESTAMPTZ NULL,
|
||||
last_delivery_status TEXT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
CONSTRAINT uq_channel_follow_source_target UNIQUE (source_channel_id, target_workspace_id, target_channel_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_follow_source ON channel_follow (source_channel_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_follow_target_ws ON channel_follow (target_workspace_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_follow_target_channel ON channel_follow (target_channel_id) WHERE target_channel_id IS NOT NULL;
|
||||
|
||||
-- ============================================================
|
||||
-- 5. Article Cross-Posts
|
||||
-- ============================================================
|
||||
|
||||
-- models/channels/article_cross_posts.rs → article_cross_post
|
||||
CREATE TABLE IF NOT EXISTS article_cross_post (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
article_id UUID NOT NULL REFERENCES article(id) ON DELETE CASCADE,
|
||||
follow_id UUID NOT NULL REFERENCES channel_follow(id) ON DELETE CASCADE,
|
||||
target_workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
|
||||
target_channel_id UUID NULL REFERENCES channel(id) ON DELETE SET NULL,
|
||||
status TEXT NOT NULL,
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
last_error TEXT NULL,
|
||||
sent_at TIMESTAMPTZ NULL,
|
||||
delivered_at TIMESTAMPTZ NULL,
|
||||
failed_at TIMESTAMPTZ NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_cross_post_article_id ON article_cross_post (article_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_cross_post_follow_id ON article_cross_post (follow_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_cross_post_status ON article_cross_post (status);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_cross_post_target_ws ON article_cross_post (target_workspace_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 6. Triggers — auto-refresh updated_at
|
||||
-- ============================================================
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_article_updated_at ON article;
|
||||
CREATE TRIGGER trg_article_updated_at BEFORE UPDATE ON article FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_article_comment_updated_at ON article_comment;
|
||||
CREATE TRIGGER trg_article_comment_updated_at BEFORE UPDATE ON article_comment FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_channel_follow_updated_at ON channel_follow;
|
||||
CREATE TRIGGER trg_channel_follow_updated_at BEFORE UPDATE ON channel_follow FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
Reference in New Issue
Block a user