All checks were successful
build-and-release / build (cron) (push) Successful in 58s
build-and-release / build (companies) (push) Successful in 1m59s
build-and-release / build (customers) (push) Successful in 2m9s
build-and-release / build (employees) (push) Successful in 2m17s
build-and-release / build (developers) (push) Successful in 2m29s
build-and-release / build (catering-services) (push) Successful in 2m45s
build-and-release / build (gateway) (push) Successful in 49s
build-and-release / build (jobs) (push) Successful in 42s
build-and-release / build (fitness-trainers) (push) Successful in 2m38s
build-and-release / build (graphic-designers) (push) Successful in 1m57s
build-and-release / build (payments) (push) Successful in 1m54s
build-and-release / build (job-seekers) (push) Successful in 2m57s
build-and-release / build (makeup-artists) (push) Successful in 2m47s
build-and-release / build (photographers) (push) Successful in 2m48s
build-and-release / build (social-media-managers) (push) Successful in 2m59s
backend-integration-tests / ai-credits (push) Successful in 50s
build-and-release / build (tutors) (push) Successful in 2m45s
build-and-release / build (ugc-content-creators) (push) Successful in 2m50s
build-and-release / build (video-editors) (push) Successful in 2m40s
build-and-release / build (users) (push) Successful in 4m47s
Reconciling the local dev DB (63 migrations behind) surfaced 4 genuine bugs in the migration chain itself -- not just this DB's legacy scripts/init-db.sql seeding -- confirmed by replaying the full chain against a brand new, completely empty database from scratch: - 20260318235959: customer_profiles.status is UPDATEd by 20260319090000's backfill 4 months before it's ever ADDed (20260721020000). Adds it early (IF NOT EXISTS). - 20260401235959: 20260317190000 creates the old users-linked `employees` shape; 20260402030000's CREATE TABLE IF NOT EXISTS no-ops against it and fails creating an index on a column that was never added. Conditionally drops the old shape first (only if it's still pre-transition -- gated on the missing `email` column), restoring 20260402030000's own documented original intent. - 20260419235959: the schema `20260420000003_external_role_modules` needs (persona_types, modules, role_module_access, ...) was only ever defined in a disabled .up.sql.skip (its version slot was taken by ...seed.sql); the seed data that depends on it was never skipped. Creates that schema early -- verbatim content, purely additive. - 20260421235959: same disabled-migration pattern for the role_permissions -> role_admin_permissions / dashboard_configs -> role_sidebar_configs / runtime_configs -> role_runtime_configs / user_roles -> user_role_assignments renames that 20260422000000's widget seed needs. Conditionally renames each pair (only if old name exists and new name doesn't), verified against live Rust code that already queries the new names exclusively. payments/invoices were NOT chain bugs -- confirmed clean on the fresh bootstrap test -- so no fix needed for those; they only conflicted on this one DB because of its scripts/init-db.sql legacy seed, which is already a documented, known gap. Verified twice: applied cleanly as no-ops against the now-fully-migrated local dev DB, and applied successfully end-to-end (0 manual steps) against a brand new empty database created from scratch. Runbook updated with the full pending-migration list and today's findings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
164 lines
7.3 KiB
SQL
164 lines
7.3 KiB
SQL
-- 20260420000003_external_role_modules.up.sql was renamed to .up.sql.skip
|
|
-- (its version slot was already claimed by ...seed.sql), which meant this
|
|
-- schema was never actually created by any active migration -- only the
|
|
-- seed data that depends on it (persona_types, modules, module_actions,
|
|
-- role_module_widgets) runs. Any fresh bootstrap hits "relation ... does
|
|
-- not exist" at that seed step. This is the .skip file's content, applied
|
|
-- one migration earlier under its own version so the seed step succeeds.
|
|
-- Verified: purely additive (CREATE TABLE/INDEX IF NOT EXISTS, ADD COLUMN
|
|
-- IF NOT EXISTS throughout), safe no-op on any environment that already
|
|
-- has this schema by other means.
|
|
|
|
-- ============================================
|
|
-- ADD COLUMNS TO ROLES for external role settings
|
|
-- ============================================
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS persona_type varchar(50);
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS onboarding_schema_key varchar(100);
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS verification_required boolean DEFAULT true;
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS switch_services_enabled boolean DEFAULT false;
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS is_publicly_discoverable boolean DEFAULT true;
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS external_role_description text;
|
|
ALTER TABLE roles ADD COLUMN IF NOT EXISTS sort_order integer DEFAULT 0;
|
|
|
|
-- ============================================
|
|
-- persona_types (categories for external roles)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS persona_types (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code varchar(50) UNIQUE NOT NULL,
|
|
name varchar(100) NOT NULL,
|
|
description text,
|
|
is_active boolean DEFAULT true,
|
|
created_at timestamptz DEFAULT NOW(),
|
|
updated_at timestamptz DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- modules (module registry)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS modules (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_key varchar(50) UNIQUE NOT NULL,
|
|
module_name varchar(100) NOT NULL,
|
|
category varchar(50), -- core/content/marketplace/work/financial
|
|
description text,
|
|
backend_domain varchar(100),
|
|
default_route varchar(255),
|
|
default_sidebar_label varchar(100),
|
|
icon_key varchar(50),
|
|
is_core boolean DEFAULT false,
|
|
is_active boolean DEFAULT true,
|
|
created_at timestamptz DEFAULT NOW(),
|
|
updated_at timestamptz DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_modules_category ON modules(category);
|
|
CREATE INDEX IF NOT EXISTS idx_modules_active ON modules(is_active);
|
|
|
|
-- ============================================
|
|
-- role_module_access (module visibility per role)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS role_module_access (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
module_id uuid NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
|
is_enabled boolean DEFAULT true,
|
|
is_sidebar_visible boolean DEFAULT true,
|
|
sidebar_label_override varchar(100),
|
|
route_override varchar(255),
|
|
sort_order integer DEFAULT 0,
|
|
created_at timestamptz DEFAULT NOW(),
|
|
UNIQUE(role_id, module_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_access_role ON role_module_access(role_id);
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_access_module ON role_module_access(module_id);
|
|
|
|
-- ============================================
|
|
-- module_actions (CRUD actions per module)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS module_actions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id uuid NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
|
action_key varchar(50) NOT NULL,
|
|
action_name varchar(100) NOT NULL,
|
|
description text,
|
|
is_active boolean DEFAULT true,
|
|
created_at timestamptz DEFAULT NOW(),
|
|
UNIQUE(module_id, action_key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_module_actions_module ON module_actions(module_id);
|
|
|
|
-- ============================================
|
|
-- role_module_permissions (permissions per module per role)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS role_module_permissions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
module_id uuid NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
|
can_view boolean DEFAULT false,
|
|
can_list boolean DEFAULT false,
|
|
can_create boolean DEFAULT false,
|
|
can_update boolean DEFAULT false,
|
|
can_delete boolean DEFAULT false,
|
|
extra_actions_json jsonb DEFAULT '{}',
|
|
created_at timestamptz DEFAULT NOW(),
|
|
UNIQUE(role_id, module_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_permissions_role ON role_module_permissions(role_id);
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_permissions_module ON role_module_permissions(module_id);
|
|
|
|
-- ============================================
|
|
-- role_module_widgets (widgets per module per role)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS role_module_widgets (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
module_id uuid NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
|
widget_key varchar(50),
|
|
is_enabled boolean DEFAULT true,
|
|
sort_order integer DEFAULT 0,
|
|
created_at timestamptz DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_widgets_role ON role_module_widgets(role_id);
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_widgets_module ON role_module_widgets(module_id);
|
|
|
|
-- ============================================
|
|
-- module_variants (role-specific module variants)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS module_variants (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id uuid NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
|
variant_key varchar(50) NOT NULL,
|
|
variant_name varchar(100) NOT NULL,
|
|
role_code varchar(50), -- target role (e.g., PHOTOGRAPHER, TUTOR)
|
|
persona_type varchar(50), -- target persona (e.g., PROFESSIONAL)
|
|
schema_key varchar(100),
|
|
ui_template_key varchar(100),
|
|
is_active boolean DEFAULT true,
|
|
created_at timestamptz DEFAULT NOW(),
|
|
updated_at timestamptz DEFAULT NOW(),
|
|
UNIQUE(module_id, variant_key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_module_variants_module ON module_variants(module_id);
|
|
CREATE INDEX IF NOT EXISTS idx_module_variants_role ON module_variants(role_code);
|
|
|
|
-- ============================================
|
|
-- role_module_variant_mapping (which variants a role uses)
|
|
-- ============================================
|
|
CREATE TABLE IF NOT EXISTS role_module_variant_mapping (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
module_id uuid NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
|
|
module_variant_id uuid NOT NULL REFERENCES module_variants(id) ON DELETE CASCADE,
|
|
is_active boolean DEFAULT true,
|
|
created_at timestamptz DEFAULT NOW(),
|
|
UNIQUE(role_id, module_id, module_variant_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_variant_mapping_role ON role_module_variant_mapping(role_id);
|
|
CREATE INDEX IF NOT EXISTS idx_role_module_variant_mapping_module ON role_module_variant_mapping(module_id);
|