-- @gsc/web-kit nav schema (canonical, apply verbatim, never edit) -- -- Creates the navigation tables consumed by @gsc/web-kit/chrome AdminShell: -- - nav.menu_items — per-app sidebar/topbar/subbar items -- - nav.menu_role_requirements — role gating (enforcement TBD) -- - nav.menu_permission_requirements -- - nav.menu_product_requirements -- - nav.apps — cross-app "browse apps" panel data -- -- Per-app philosophy: each app applies this schema to its OWN database -- (gsc_crm, gsc_chronos, gsc_admin, ...). Menu items are app-specific; -- the apps list is replicated from nav-apps-seed.up.sql so every app's -- "browse apps" panel shows the same canonical list. CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE SCHEMA IF NOT EXISTS nav; -- --------------------------------------------------------------------------- -- Enums -- --------------------------------------------------------------------------- DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE t.typname = 'menu_type' AND n.nspname = 'nav' ) THEN CREATE TYPE nav.menu_type AS ENUM ('sidebar', 'topbar', 'subbar'); END IF; END$$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE t.typname = 'requirement_logic' AND n.nspname = 'nav' ) THEN CREATE TYPE nav.requirement_logic AS ENUM ('OR', 'AND'); END IF; END$$; -- --------------------------------------------------------------------------- -- menu_items -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS nav.menu_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), menu_type nav.menu_type NOT NULL, parent_id UUID NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE, key VARCHAR(64) NOT NULL UNIQUE, translation_key VARCHAR(128) NOT NULL, url VARCHAR(256) NOT NULL, icon VARCHAR(64) NULL, sort_order INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE, is_system_required BOOLEAN NOT NULL DEFAULT FALSE, role_logic nav.requirement_logic NOT NULL DEFAULT 'OR', permission_logic nav.requirement_logic NOT NULL DEFAULT 'OR', metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_menu_items_type ON nav.menu_items (menu_type); CREATE INDEX IF NOT EXISTS idx_menu_items_parent ON nav.menu_items (parent_id); CREATE INDEX IF NOT EXISTS idx_menu_items_sort ON nav.menu_items (sort_order); -- --------------------------------------------------------------------------- -- menu requirement tables (role / permission / product) -- Cross-DB IDs stored as plain strings; no cross-DB FKs. -- Enforcement is not yet wired in v0.4.0 chrome. -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS nav.menu_role_requirements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), menu_item_id UUID NOT NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE, role_id VARCHAR(128) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (menu_item_id, role_id) ); CREATE TABLE IF NOT EXISTS nav.menu_permission_requirements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), menu_item_id UUID NOT NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE, permission_id VARCHAR(128) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (menu_item_id, permission_id) ); CREATE TABLE IF NOT EXISTS nav.menu_product_requirements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), menu_item_id UUID NOT NULL REFERENCES nav.menu_items(id) ON DELETE CASCADE, product_id VARCHAR(128) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (menu_item_id, product_id) ); -- --------------------------------------------------------------------------- -- apps — cross-app browse-apps panel data -- Either icon_class (Phosphor / FA class) or icon_url (logo image) may be set. -- icon_bg styles the colored square behind icon_class (e.g. 'bg-primary-lt'). -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS nav.apps ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), key VARCHAR(64) NOT NULL UNIQUE, name VARCHAR(128) NOT NULL, description VARCHAR(256) NOT NULL DEFAULT '', url VARCHAR(512) NOT NULL, icon_class VARCHAR(64) NULL, icon_url VARCHAR(512) NULL, icon_bg VARCHAR(32) NULL, sort_order INTEGER NOT NULL DEFAULT 0, enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_apps_sort_order ON nav.apps (sort_order);