diff --git a/deepwell/migrations/20220906103252_deepwell.sql b/deepwell/migrations/20220906103252_deepwell.sql index 1bb44a061d..cbc16f5785 100644 --- a/deepwell/migrations/20220906103252_deepwell.sql +++ b/deepwell/migrations/20220906103252_deepwell.sql @@ -279,7 +279,7 @@ CREATE TABLE page ( latest_revision_id BIGINT, -- nullable to avoid an initial page_revision dependency cycle page_category_id BIGINT NOT NULL REFERENCES page_category(category_id), slug TEXT NOT NULL, - discussion_thread_id BIGINT, -- TODO: add REFERENCES to forum threads + discussion_thread_id BIGINT, -- FK added after forum_thread is declared layout TEXT, -- page-specific override for DOM layout UNIQUE (site_id, slug, deleted_at) @@ -746,6 +746,194 @@ CREATE TABLE filter ( UNIQUE (site_id, regex, deleted_at) ); +-- +-- Forums +-- + +-- Groups contain categories, and are site-local. +CREATE TABLE forum_group ( + forum_group_id BIGSERIAL PRIMARY KEY, + site_id BIGINT NOT NULL REFERENCES site(site_id), + created_by BIGINT NOT NULL REFERENCES "user"(user_id), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_by BIGINT REFERENCES "user"(user_id), + updated_at TIMESTAMP WITH TIME ZONE, + deleted_by BIGINT REFERENCES "user"(user_id), + deleted_at TIMESTAMP WITH TIME ZONE, + name TEXT NOT NULL, + description TEXT NOT NULL, + visible BOOLEAN NOT NULL DEFAULT true, + sort_index INTEGER NOT NULL CHECK (sort_index >= 0), + from_wikidot BOOLEAN NOT NULL DEFAULT false, + + UNIQUE (site_id, sort_index), + UNIQUE (forum_group_id, site_id), + CHECK ((updated_by IS NULL) = (updated_at IS NULL)), + CHECK ((deleted_by IS NULL) = (deleted_at IS NULL)) +); + +-- Categories belong to a group, and are site-local. +CREATE TABLE forum_category ( + forum_category_id BIGSERIAL PRIMARY KEY, + forum_group_id BIGINT NOT NULL REFERENCES forum_group(forum_group_id), + site_id BIGINT NOT NULL REFERENCES site(site_id), + created_by BIGINT NOT NULL REFERENCES "user"(user_id), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_by BIGINT REFERENCES "user"(user_id), + updated_at TIMESTAMP WITH TIME ZONE, + deleted_by BIGINT REFERENCES "user"(user_id), + deleted_at TIMESTAMP WITH TIME ZONE, + name TEXT NOT NULL, + description TEXT NOT NULL, + sort_index INTEGER NOT NULL CHECK (sort_index >= 0), + from_wikidot BOOLEAN NOT NULL DEFAULT false, + + -- Category settings: + max_nest_level SMALLINT CHECK (0 <= max_nest_level AND max_nest_level <= 10), + per_page_discussion BOOLEAN DEFAULT false, + layout TEXT, + + UNIQUE (forum_group_id, sort_index), + -- Required for (forum_category_id, site_id) composite FKs from denormalized child rows. + UNIQUE (forum_category_id, site_id), + CHECK ((updated_by IS NULL) = (updated_at IS NULL)), + CHECK ((deleted_by IS NULL) = (deleted_at IS NULL)), + FOREIGN KEY (forum_group_id, site_id) REFERENCES forum_group(forum_group_id, site_id) +); + +-- Threads live in a category (but can be moved between them). +CREATE TABLE forum_thread ( + forum_thread_id BIGSERIAL PRIMARY KEY, + forum_category_id BIGINT NOT NULL REFERENCES forum_category(forum_category_id), + forum_group_id BIGINT NOT NULL REFERENCES forum_group(forum_group_id), + site_id BIGINT NOT NULL REFERENCES site(site_id), + page_id BIGINT REFERENCES page(page_id) UNIQUE, -- For page discussion threads (NULL = regular thread) + created_by BIGINT NOT NULL REFERENCES "user"(user_id), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_by BIGINT REFERENCES "user"(user_id), + updated_at TIMESTAMP WITH TIME ZONE, + deleted_by BIGINT REFERENCES "user"(user_id), + deleted_at TIMESTAMP WITH TIME ZONE, + title TEXT NOT NULL, + description TEXT NOT NULL, + from_wikidot BOOLEAN NOT NULL DEFAULT false, + sticky BOOLEAN NOT NULL DEFAULT false, + + CHECK ((updated_by IS NULL) = (updated_at IS NULL)), + CHECK ((deleted_by IS NULL) = (deleted_at IS NULL)), + -- Required for (forum_thread_id, site_id) composite FKs from denormalized child rows. + UNIQUE (forum_thread_id, site_id), + FOREIGN KEY (forum_category_id, site_id) REFERENCES forum_category(forum_category_id, site_id), + FOREIGN KEY (forum_group_id, site_id) REFERENCES forum_group(forum_group_id, site_id) +); + +-- Locks on threads (one active lock per thread at a time). +CREATE TABLE forum_thread_lock ( + forum_thread_lock_id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE, + deleted_at TIMESTAMP WITH TIME ZONE, + expires_at TIMESTAMP WITH TIME ZONE, + from_wikidot BOOLEAN NOT NULL DEFAULT false, + forum_thread_id BIGINT NOT NULL REFERENCES forum_thread(forum_thread_id), + user_id BIGINT NOT NULL REFERENCES "user"(user_id), + reason TEXT NOT NULL, + lock_type TEXT NOT NULL, + allow_new_posts BOOLEAN NOT NULL DEFAULT false, + allow_post_edits BOOLEAN NOT NULL DEFAULT false, + allow_post_deletions BOOLEAN NOT NULL DEFAULT false, + + UNIQUE (forum_thread_id, deleted_at) +); + +-- Posts within a thread, optionally nested. +CREATE TABLE forum_post ( + forum_post_id BIGSERIAL PRIMARY KEY, + parent_post_id BIGINT REFERENCES forum_post(forum_post_id), + forum_thread_id BIGINT NOT NULL REFERENCES forum_thread(forum_thread_id), + forum_category_id BIGINT NOT NULL REFERENCES forum_category(forum_category_id), + forum_group_id BIGINT NOT NULL REFERENCES forum_group(forum_group_id), + site_id BIGINT NOT NULL REFERENCES site(site_id), + user_id BIGINT NOT NULL REFERENCES "user"(user_id), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE, + deleted_by BIGINT REFERENCES "user"(user_id), + deleted_at TIMESTAMP WITH TIME ZONE, + from_wikidot BOOLEAN NOT NULL DEFAULT false, + latest_revision_id BIGINT, + + CHECK ((deleted_by IS NULL) = (deleted_at IS NULL)), + -- Required for (forum_post_id, site_id) composite FKs from denormalized child rows. + UNIQUE (forum_post_id, site_id), + FOREIGN KEY (forum_thread_id, site_id) REFERENCES forum_thread(forum_thread_id, site_id), + FOREIGN KEY (forum_category_id, site_id) REFERENCES forum_category(forum_category_id, site_id), + FOREIGN KEY (forum_group_id, site_id) REFERENCES forum_group(forum_group_id, site_id) +); + +-- Locks on posts (one active lock per post at a time). +CREATE TABLE forum_post_lock ( + forum_post_lock_id BIGSERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE, + deleted_at TIMESTAMP WITH TIME ZONE, + expires_at TIMESTAMP WITH TIME ZONE, + forum_post_id BIGINT NOT NULL REFERENCES forum_post(forum_post_id), + user_id BIGINT NOT NULL REFERENCES "user"(user_id), + reason TEXT NOT NULL, + lock_type TEXT NOT NULL, + cascading BOOLEAN NOT NULL, + + UNIQUE (forum_post_id, deleted_at) +); + +-- Revisions of posts. +CREATE TABLE forum_post_revision ( + forum_post_revision_id BIGSERIAL PRIMARY KEY, + forum_post_id BIGINT NOT NULL REFERENCES forum_post(forum_post_id), + forum_thread_id BIGINT NOT NULL REFERENCES forum_thread(forum_thread_id), + forum_category_id BIGINT NOT NULL REFERENCES forum_category(forum_category_id), + forum_group_id BIGINT NOT NULL REFERENCES forum_group(forum_group_id), + site_id BIGINT NOT NULL REFERENCES site(site_id), + user_id BIGINT NOT NULL REFERENCES "user"(user_id), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE, + revision_number INTEGER NOT NULL CHECK (revision_number >= 0), + from_wikidot BOOLEAN NOT NULL DEFAULT false, + title TEXT NOT NULL, + wikitext_hash BYTEA NOT NULL REFERENCES text(hash), + compiled_html_hash BYTEA NOT NULL REFERENCES text(hash), + compiled_at TIMESTAMP WITH TIME ZONE NOT NULL, + compiled_generator TEXT NOT NULL, + comments TEXT NOT NULL, + + UNIQUE (forum_post_id, revision_number), + FOREIGN KEY (forum_thread_id, site_id) REFERENCES forum_thread(forum_thread_id, site_id), + FOREIGN KEY (forum_category_id, site_id) REFERENCES forum_category(forum_category_id, site_id), + FOREIGN KEY (forum_group_id, site_id) REFERENCES forum_group(forum_group_id, site_id), + FOREIGN KEY (forum_post_id, site_id) REFERENCES forum_post(forum_post_id, site_id) +); + +-- Latest revision FK on posts, now that the revision table exists. +ALTER TABLE forum_post + ADD CONSTRAINT forum_post_latest_revision_fk + FOREIGN KEY (latest_revision_id) REFERENCES forum_post_revision(forum_post_revision_id); + +-- Pages can point to their dedicated discussion threads. +ALTER TABLE page + ADD CONSTRAINT page_discussion_thread_fk + FOREIGN KEY (discussion_thread_id) REFERENCES forum_thread(forum_thread_id); + +-- Forum indexes +CREATE INDEX forum_group_sort_idx ON forum_group (site_id, sort_index); +CREATE INDEX forum_category_sort_idx ON forum_category (forum_group_id, sort_index); +CREATE INDEX forum_category_site_sort_idx ON forum_category (site_id, sort_index); +CREATE INDEX forum_thread_activity_idx ON forum_thread (forum_category_id, sticky DESC, COALESCE(updated_at, created_at) DESC); +CREATE INDEX forum_thread_created_idx ON forum_thread (forum_category_id, created_at DESC); +CREATE INDEX forum_post_thread_created_idx ON forum_post (forum_thread_id, created_at); +CREATE INDEX forum_post_parent_idx ON forum_post (parent_post_id); +CREATE INDEX forum_post_latest_revision_idx ON forum_post (latest_revision_id); +CREATE INDEX forum_post_revision_lookup_idx ON forum_post_revision (forum_post_id, revision_number DESC); + -- -- Audit Log -- diff --git a/deepwell/src/database/seeder/mod.rs b/deepwell/src/database/seeder/mod.rs index bbf8c3cfc6..efd9beaf0a 100644 --- a/deepwell/src/database/seeder/mod.rs +++ b/deepwell/src/database/seeder/mod.rs @@ -511,12 +511,17 @@ pub async fn seed(state: &ServerState) -> Result<()> { .await .or_raise(make_error)?; - /* - * TODO: tables which don't exist yet: - * restart_sequence_with(&txn, < forum category seq >, 9000000).await.or_raise(make_error)?; - * restart_sequence_with(&txn, < forum thread seq >, 30000000).await.or_raise(make_error)?; - * restart_sequence_with(&txn, < forum post seq >, 7000000).await.or_raise(make_error)?; - */ + restart_sequence_with(&txn, "forum_category_forum_category_id_seq", 9000000) + .await + .or_raise(make_error)?; + + restart_sequence_with(&txn, "forum_thread_forum_thread_id_seq", 30000000) + .await + .or_raise(make_error)?; + + restart_sequence_with(&txn, "forum_post_forum_post_id_seq", 7000000) + .await + .or_raise(make_error)?; txn.commit().await.or_raise(make_error)?; info!("Finished running seeder."); diff --git a/deepwell/src/services/text.rs b/deepwell/src/services/text.rs index c7f9e93fea..1ad3ec8e44 100644 --- a/deepwell/src/services/text.rs +++ b/deepwell/src/services/text.rs @@ -30,7 +30,7 @@ use crate::models::message_draft::{self, Entity as MessageDraft}; use crate::models::message_record::{self, Entity as MessageRecord}; use crate::models::page_revision::{self, Entity as PageRevision}; use crate::models::text::{self, Entity as Text}; -use sea_query::Query; +use sea_query::{Alias, Query}; #[derive(Debug)] pub struct TextService; @@ -235,8 +235,15 @@ impl TextService { .add(not_in_column!( MessageRecord, message_record::Column::CompiledHash, + )) + .add(not_in_column!( + Alias::new("forum_post_revision"), + Alias::new("wikitext_hash"), + )) + .add(not_in_column!( + Alias::new("forum_post_revision"), + Alias::new("compiled_html_hash"), )), - // TODO add forum_post_revision ) .exec(txn) .await