-- Migration 039: Multi-Projekt-Architektur -- Enables multiple compliance projects per tenant (Cloud-Ready Multi-Tenancy) -- -- Changes: -- 1. New table compliance_projects (project metadata) -- 2. sdk_states: Drop UNIQUE(tenant_id), add project_id column with FK -- 3. Migrate existing data: Create default project for each existing sdk_states row -- ============================================================================= -- 1. New table: compliance_projects -- ============================================================================= CREATE TABLE IF NOT EXISTS compliance_projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id VARCHAR(255) NOT NULL, name VARCHAR(500) NOT NULL, description TEXT DEFAULT '', customer_type VARCHAR(20) DEFAULT 'new', -- 'new' | 'existing' status VARCHAR(20) DEFAULT 'active', -- 'active' | 'archived' | 'deleted' project_version INTEGER DEFAULT 1, completion_percentage INTEGER DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), archived_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_compliance_projects_tenant ON compliance_projects(tenant_id); CREATE INDEX IF NOT EXISTS idx_compliance_projects_status ON compliance_projects(tenant_id, status); -- ============================================================================= -- 2. sdk_states: Add project_id, adjust constraints -- Only runs if sdk_states table exists (it may not yet exist if no state -- has been saved via the frontend state API) -- ============================================================================= DO $$ BEGIN -- Check if sdk_states exists IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_name = 'sdk_states' ) THEN -- Drop the old UNIQUE constraint on tenant_id (allows multiple states per tenant) ALTER TABLE sdk_states DROP CONSTRAINT IF EXISTS sdk_states_tenant_id_key; -- Add project_id column (nullable initially for migration) ALTER TABLE sdk_states ADD COLUMN IF NOT EXISTS project_id UUID; -- ===================================================================== -- 3. Data migration: Create default projects for existing states -- ===================================================================== -- For each existing sdk_states row without a project, create a default project INSERT INTO compliance_projects (id, tenant_id, name, customer_type, status) SELECT gen_random_uuid(), s.tenant_id, COALESCE(s.state->'companyProfile'->>'companyName', 'Projekt 1'), COALESCE(s.state->>'customerType', 'new'), 'active' FROM sdk_states s WHERE s.project_id IS NULL ON CONFLICT DO NOTHING; -- Link existing states to their newly created projects UPDATE sdk_states s SET project_id = p.id FROM compliance_projects p WHERE s.tenant_id = p.tenant_id AND s.project_id IS NULL; -- ===================================================================== -- 4. Add constraints after migration -- ===================================================================== -- Make project_id NOT NULL if all rows have a value IF NOT EXISTS (SELECT 1 FROM sdk_states WHERE project_id IS NULL) THEN ALTER TABLE sdk_states ALTER COLUMN project_id SET NOT NULL; END IF; -- Unique constraint: one state per (tenant, project) IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'uq_sdk_states_tenant_project' ) THEN ALTER TABLE sdk_states ADD CONSTRAINT uq_sdk_states_tenant_project UNIQUE (tenant_id, project_id); END IF; -- Foreign key to compliance_projects IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'fk_sdk_states_project' ) THEN ALTER TABLE sdk_states ADD CONSTRAINT fk_sdk_states_project FOREIGN KEY (project_id) REFERENCES compliance_projects(id) ON DELETE CASCADE; END IF; RAISE NOTICE 'sdk_states migration completed successfully'; ELSE RAISE NOTICE 'sdk_states table does not exist yet — skipping sdk_states migration (will be applied on first state save)'; END IF; END $$;