create table sections ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, -- Title of the section title varchar(255) UNIQUE NOT NULL, -- Name to use when routing (this just makes for prettier URLs) shortname varchar(32) UNIQUE NOT NULL, -- Optional description of the section description text, -- Wether to show the section in the default list on the homepage is_default boolean NOT NULL DEFAULT false, -- Wether the posts should contain titles or not has_titles boolean NOT NULL DEFAULT true, -- Wether posts in this section should be shown publicly is_private boolean NOT NULL DEFAULT false ); create index sections_shortname_index on sections(shortname); create table posts ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, -- Posts shouldn't get deleted when we delete a section, as they're the -- most valuable part of a blog section_id uuid NOT NULL REFERENCES sections(id) ON DELETE SET NULL, -- Wether a post should be private is_private boolean NOT NULL DEFAULT false, -- Wether the post is archived is_archived boolean NOT NULL DEFAULT false ); create table versions ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, -- A version should be deleted when its referenced post is deleted post_id uuid NOT NULL REFERENCES posts(id) ON DELETE CASCADE, -- Title of the post. Wether this is NULL or not is enforced using the -- enforce_post_titles trigger. title varchar(255), -- Publish date publish_date date, -- Content of the post, in Markdown content text NOT NULL DEFAULT '', -- Wether the version is still a draft is_draft boolean NOT NULL default true, -- This check allows draft posts to be created without having to enter a -- publish date, but forces them to have one if they're not a draft. CONSTRAINT no_null_published_date CHECK (is_draft OR publish_date IS NOT NULL) ); create table tags ( post_id uuid NOT NULL REFERENCES posts(id) ON DELETE CASCADE, value varchar(64) NOT NULL, PRIMARY KEY (post_id, value) ); create function enforce_version_titles() returns trigger as $$ begin -- Draft versions shouldn't be evaluated. if new.is_draft then return new; end if; -- Check for a wrongfully null title if new.title is null and exists ( select 1 from posts inner join sections on posts.section_id = sections.id where posts.id = new.post_id and sections.has_titles ) then raise exception 'Expected a post title, but got null.'; end if; if new.title is not null and exists ( select 1 from posts inner join sections on posts.section_id = sections.id where posts.id = new.post_id and not sections.has_titles ) then raise exception 'Expected an empty post title, but got a value.'; end if; return new; end; $$ language plpgsql; create trigger insert_enforce_version_titles before insert on versions for each row execute function enforce_version_titles(); create trigger update_enforce_version_titles before update of title on versions for each row when (old.title is distinct from new.title) execute function enforce_version_titles();