Files
qbit-cms/docs/ddl/postgres.sql
T
james.maesandClaude Opus 5.5 eb9d163840 feat: CMS qbit skeleton with feature flags and the CORE feature
Phase 1 of the qbit-cms spec (QRun-IO/qqq#1083), first slice.

- CmsQBitProducer builds everything from CmsQBitConfig (no package scan):
  validation, namespaces for a second install, and a collision check
  against names already on the instance.
- CmsFeature flags with dependencies; CORE implemented, the others
  rejected until they ship.
- CORE tables as standard QQQ tables (site, domain, redirect, redirect
  type, page, page type, page section, page section type, snippet, menu,
  menu item): prefixed names equal to the SQL names, snake_case columns,
  possible-value sources, child lists and associations, permission
  rules, field audit when the host has QQQ audit tables.
- The CMS app with one home per area (Sites, Pages, Navigation): real
  data read as the user (PermissionEnforcement.ENFORCE), honest empty
  states, readable at any width in light and dark mode.
- CmsRole bundles, CmsSecurity.applySessionPermissions, openReads.
- A prefix-parameterized Liquibase changelog (1.0.0 baseline, kof22
  column types, generic reference data, feature gating, halt on a
  foreign table with a CMS name) and generated reference DDL.
- Startup schema check (FAIL_FAST, DEGRADED, SKIP).
- Checkstyle wired into verify; 22 tests incl. Testcontainers PostgreSQL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:35:25 -05:00

373 lines
12 KiB
SQL

-- qbit-cms reference DDL (PostgreSQL), generated from the qbit's Liquibase changelog
-- (src/main/resources/qbit-cms/db/changelog/qbit-cms.changelog.yaml) with cms.tablePrefix=cms_ and every feature on.
-- For hosts that do not run Liquibase. The changelog is the source of truth; regenerate on schema changes.
CREATE TABLE public.cms_domain (
id bigint NOT NULL,
name character varying(40) NOT NULL,
short_name character varying(20) NOT NULL,
description character varying(255),
fqdn character varying(255) NOT NULL,
is_primary boolean NOT NULL,
site_id bigint NOT NULL,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_domain ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_domain_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_menu (
id bigint NOT NULL,
name character varying(100) NOT NULL,
slug character varying(100) NOT NULL,
description character varying(255),
site_id bigint NOT NULL,
menu_type character varying(50) NOT NULL,
is_active boolean,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_menu ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_menu_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_menu_item (
id bigint NOT NULL,
menu_id bigint NOT NULL,
parent_id bigint,
name character varying(100) NOT NULL,
slug character varying(100) NOT NULL,
item_type character varying(50) NOT NULL,
link_url character varying(500),
route_path character varying(200),
target character varying(20),
icon character varying(100),
description character varying(255),
display_order integer,
is_active boolean,
css_classes character varying(200),
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_menu_item ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_menu_item_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_page (
id bigint NOT NULL,
name character varying(100) NOT NULL,
slug character varying(100) NOT NULL,
title character varying(200) NOT NULL,
content text,
page_type_id bigint NOT NULL,
template character varying(50),
is_published boolean NOT NULL,
publish_date timestamp without time zone,
sort_order integer NOT NULL,
parent_page_id bigint,
site_id bigint NOT NULL,
meta_description character varying(500),
meta_keywords character varying(255),
og_image character varying(255),
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_page ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_page_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_page_section (
id bigint NOT NULL,
name character varying(100) NOT NULL,
css_class_name character varying(255),
section_type_id bigint NOT NULL,
content text,
sort_order integer NOT NULL,
page_id bigint NOT NULL,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_page_section ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_page_section_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_page_section_type (
id bigint NOT NULL,
name character varying(100) NOT NULL,
code character varying(50) NOT NULL,
description character varying(255),
is_active boolean NOT NULL,
sort_order integer NOT NULL,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_page_section_type ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_page_section_type_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_page_type (
id bigint NOT NULL,
name character varying(100) NOT NULL,
code character varying(50) NOT NULL,
description character varying(255),
is_active boolean NOT NULL,
sort_order integer NOT NULL,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_page_type ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_page_type_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_redirect (
id bigint NOT NULL,
name character varying(100) NOT NULL,
description character varying(500),
source_path character varying(500) NOT NULL,
target_path character varying(500) NOT NULL,
redirect_type_id bigint NOT NULL,
match_type character varying(20) NOT NULL,
is_active boolean NOT NULL,
priority integer NOT NULL,
preserve_query_params boolean NOT NULL,
hit_count bigint NOT NULL,
last_hit_date timestamp without time zone,
expiry_date timestamp without time zone,
site_id bigint NOT NULL,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_redirect ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_redirect_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_redirect_type (
id bigint NOT NULL,
name character varying(100) NOT NULL,
code character varying(50) NOT NULL,
description character varying(500),
http_status_code integer NOT NULL,
is_active boolean NOT NULL,
sort_order integer NOT NULL,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_redirect_type ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_redirect_type_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_site (
id bigint NOT NULL,
name character varying(40) NOT NULL,
short_name character varying(20) NOT NULL,
description character varying(255),
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_site ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_site_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE public.cms_snippet (
id bigint NOT NULL,
name character varying(100) NOT NULL,
short_name character varying(50) NOT NULL,
content text,
css_class_name character varying(255),
is_active boolean NOT NULL,
sort_order integer NOT NULL,
site_id bigint NOT NULL,
create_date timestamp without time zone NOT NULL,
modify_date timestamp without time zone NOT NULL
);
ALTER TABLE public.cms_snippet ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.cms_snippet_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
ALTER TABLE ONLY public.cms_domain
ADD CONSTRAINT pk_cms_domain PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_menu
ADD CONSTRAINT pk_cms_menu PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_menu_item
ADD CONSTRAINT pk_cms_menu_item PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_page
ADD CONSTRAINT pk_cms_page PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_page_section
ADD CONSTRAINT pk_cms_page_section PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_page_section_type
ADD CONSTRAINT pk_cms_page_section_type PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_page_type
ADD CONSTRAINT pk_cms_page_type PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_redirect
ADD CONSTRAINT pk_cms_redirect PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_redirect_type
ADD CONSTRAINT pk_cms_redirect_type PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_site
ADD CONSTRAINT pk_cms_site PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_snippet
ADD CONSTRAINT pk_cms_snippet PRIMARY KEY (id);
ALTER TABLE ONLY public.cms_domain
ADD CONSTRAINT uq_cms_domain_fqdn UNIQUE (fqdn);
ALTER TABLE ONLY public.cms_domain
ADD CONSTRAINT uq_cms_domain_name UNIQUE (name);
ALTER TABLE ONLY public.cms_domain
ADD CONSTRAINT uq_cms_domain_short_name UNIQUE (short_name);
ALTER TABLE ONLY public.cms_menu
ADD CONSTRAINT uq_cms_menu_slug_site_id UNIQUE (slug, site_id);
ALTER TABLE ONLY public.cms_page_section_type
ADD CONSTRAINT uq_cms_page_section_type_code UNIQUE (code);
ALTER TABLE ONLY public.cms_page_section_type
ADD CONSTRAINT uq_cms_page_section_type_name UNIQUE (name);
ALTER TABLE ONLY public.cms_page
ADD CONSTRAINT uq_cms_page_slug_parent_page_id UNIQUE (slug, parent_page_id);
ALTER TABLE ONLY public.cms_page
ADD CONSTRAINT uq_cms_page_slug_site_id UNIQUE (slug, site_id);
ALTER TABLE ONLY public.cms_page_type
ADD CONSTRAINT uq_cms_page_type_code UNIQUE (code);
ALTER TABLE ONLY public.cms_page_type
ADD CONSTRAINT uq_cms_page_type_name UNIQUE (name);
ALTER TABLE ONLY public.cms_redirect_type
ADD CONSTRAINT uq_cms_redirect_type_code UNIQUE (code);
ALTER TABLE ONLY public.cms_site
ADD CONSTRAINT uq_cms_site_name UNIQUE (name);
ALTER TABLE ONLY public.cms_snippet
ADD CONSTRAINT uq_cms_snippet_short_name_site_id UNIQUE (short_name, site_id);
CREATE INDEX idx_cms_menu_item_menu_id ON public.cms_menu_item USING btree (menu_id);
CREATE INDEX idx_cms_page_section_page_id ON public.cms_page_section USING btree (page_id);
CREATE INDEX idx_cms_page_site_id_is_published ON public.cms_page USING btree (site_id, is_published);
CREATE INDEX idx_cms_redirect_site_id_source_path ON public.cms_redirect USING btree (site_id, source_path);
ALTER TABLE ONLY public.cms_domain
ADD CONSTRAINT fk_cms_domain_site_id FOREIGN KEY (site_id) REFERENCES public.cms_site(id);
ALTER TABLE ONLY public.cms_menu_item
ADD CONSTRAINT fk_cms_menu_item_menu_id FOREIGN KEY (menu_id) REFERENCES public.cms_menu(id);
ALTER TABLE ONLY public.cms_menu_item
ADD CONSTRAINT fk_cms_menu_item_parent_id FOREIGN KEY (parent_id) REFERENCES public.cms_menu_item(id);
ALTER TABLE ONLY public.cms_menu
ADD CONSTRAINT fk_cms_menu_site_id FOREIGN KEY (site_id) REFERENCES public.cms_site(id);
ALTER TABLE ONLY public.cms_page
ADD CONSTRAINT fk_cms_page_page_type_id FOREIGN KEY (page_type_id) REFERENCES public.cms_page_type(id);
ALTER TABLE ONLY public.cms_page
ADD CONSTRAINT fk_cms_page_parent_page_id FOREIGN KEY (parent_page_id) REFERENCES public.cms_page(id);
ALTER TABLE ONLY public.cms_page_section
ADD CONSTRAINT fk_cms_page_section_page_id FOREIGN KEY (page_id) REFERENCES public.cms_page(id);
ALTER TABLE ONLY public.cms_page_section
ADD CONSTRAINT fk_cms_page_section_section_type_id FOREIGN KEY (section_type_id) REFERENCES public.cms_page_section_type(id);
ALTER TABLE ONLY public.cms_page
ADD CONSTRAINT fk_cms_page_site_id FOREIGN KEY (site_id) REFERENCES public.cms_site(id);
ALTER TABLE ONLY public.cms_redirect
ADD CONSTRAINT fk_cms_redirect_redirect_type_id FOREIGN KEY (redirect_type_id) REFERENCES public.cms_redirect_type(id);
ALTER TABLE ONLY public.cms_redirect
ADD CONSTRAINT fk_cms_redirect_site_id FOREIGN KEY (site_id) REFERENCES public.cms_site(id);
ALTER TABLE ONLY public.cms_snippet
ADD CONSTRAINT fk_cms_snippet_site_id FOREIGN KEY (site_id) REFERENCES public.cms_site(id);