"""initial schema Revision ID: 0001 Revises: Create Date: 2026-04-13 Creates the full core schema plus seeds the default cookie categories. """ import uuid from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision: str = '0001' down_revision: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Upgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### op.create_table('cookie_categories', sa.Column('name', sa.String(length=50), nullable=False), sa.Column('slug', sa.String(length=50), nullable=False), sa.Column('description', sa.Text(), nullable=True), sa.Column('is_essential', sa.Boolean(), nullable=False), sa.Column('display_order', sa.Integer(), server_default='0', nullable=False), sa.Column('tcf_purpose_ids', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gcm_consent_types', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('name'), sa.UniqueConstraint('slug') ) op.create_table('organisations', sa.Column('name', sa.String(length=255), nullable=False), sa.Column('slug', sa.String(length=100), nullable=False), sa.Column('contact_email', sa.String(length=255), nullable=True), sa.Column('billing_plan', sa.String(length=50), server_default='free', nullable=False), sa.Column('notes', sa.Text(), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_organisations_slug'), 'organisations', ['slug'], unique=True) op.create_table('known_cookies', sa.Column('name_pattern', sa.String(length=255), nullable=False), sa.Column('domain_pattern', sa.String(length=255), nullable=False), sa.Column('category_id', sa.UUID(), nullable=False), sa.Column('vendor', sa.String(length=255), nullable=True), sa.Column('description', sa.Text(), nullable=True), sa.Column('is_regex', sa.Boolean(), nullable=False), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['category_id'], ['cookie_categories.id'], ondelete='RESTRICT'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('name_pattern', 'domain_pattern', name='uq_known_cookies_name_domain') ) op.create_index(op.f('ix_known_cookies_name_pattern'), 'known_cookies', ['name_pattern'], unique=False) op.create_table('org_configs', sa.Column('organisation_id', sa.UUID(), nullable=False), sa.Column('blocking_mode', sa.String(length=20), nullable=True), sa.Column('regional_modes', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('tcf_enabled', sa.Boolean(), nullable=True), sa.Column('tcf_publisher_cc', sa.String(length=2), nullable=True), sa.Column('gpp_enabled', sa.Boolean(), nullable=True), sa.Column('gpp_supported_apis', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gpc_enabled', sa.Boolean(), nullable=True), sa.Column('gpc_jurisdictions', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gpc_global_honour', sa.Boolean(), nullable=True), sa.Column('gcm_enabled', sa.Boolean(), nullable=True), sa.Column('gcm_default', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('shopify_privacy_enabled', sa.Boolean(), nullable=True), sa.Column('banner_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('privacy_policy_url', sa.Text(), nullable=True), sa.Column('terms_url', sa.Text(), nullable=True), sa.Column('scan_schedule_cron', sa.String(length=100), nullable=True), sa.Column('scan_max_pages', sa.Integer(), nullable=True), sa.Column('consent_expiry_days', sa.Integer(), nullable=True), sa.Column('consent_retention_days', sa.Integer(), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['organisation_id'], ['organisations.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('organisation_id') ) op.create_table('site_groups', sa.Column('organisation_id', sa.UUID(), nullable=False), sa.Column('name', sa.String(length=255), nullable=False), sa.Column('description', sa.Text(), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(['organisation_id'], ['organisations.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('organisation_id', 'name', name='uq_site_groups_org_name') ) op.create_index(op.f('ix_site_groups_organisation_id'), 'site_groups', ['organisation_id'], unique=False) op.create_table('users', sa.Column('organisation_id', sa.UUID(), nullable=False), sa.Column('email', sa.String(length=255), nullable=False), sa.Column('password_hash', sa.String(length=255), nullable=False), sa.Column('full_name', sa.String(length=255), nullable=False), sa.Column('role', sa.String(length=20), server_default='viewer', nullable=False), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(['organisation_id'], ['organisations.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) op.create_index(op.f('ix_users_organisation_id'), 'users', ['organisation_id'], unique=False) op.create_table('site_group_configs', sa.Column('site_group_id', sa.UUID(), nullable=False), sa.Column('blocking_mode', sa.String(length=20), nullable=True), sa.Column('regional_modes', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('tcf_enabled', sa.Boolean(), nullable=True), sa.Column('tcf_publisher_cc', sa.String(length=2), nullable=True), sa.Column('gpp_enabled', sa.Boolean(), nullable=True), sa.Column('gpp_supported_apis', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gpc_enabled', sa.Boolean(), nullable=True), sa.Column('gpc_jurisdictions', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gpc_global_honour', sa.Boolean(), nullable=True), sa.Column('gcm_enabled', sa.Boolean(), nullable=True), sa.Column('gcm_default', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('shopify_privacy_enabled', sa.Boolean(), nullable=True), sa.Column('banner_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('privacy_policy_url', sa.Text(), nullable=True), sa.Column('terms_url', sa.Text(), nullable=True), sa.Column('scan_schedule_cron', sa.String(length=100), nullable=True), sa.Column('scan_max_pages', sa.Integer(), nullable=True), sa.Column('consent_expiry_days', sa.Integer(), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['site_group_id'], ['site_groups.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('site_group_id') ) op.create_table('sites', sa.Column('organisation_id', sa.UUID(), nullable=False), sa.Column('domain', sa.String(length=255), nullable=False), sa.Column('display_name', sa.String(length=255), nullable=False), sa.Column('is_active', sa.Boolean(), nullable=False), sa.Column('additional_domains', postgresql.ARRAY(sa.String(length=255)), nullable=True), sa.Column('site_group_id', sa.UUID(), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(['organisation_id'], ['organisations.id'], ondelete='CASCADE'), sa.ForeignKeyConstraint(['site_group_id'], ['site_groups.id'], ondelete='SET NULL'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('organisation_id', 'domain', name='uq_sites_org_domain') ) op.create_index(op.f('ix_sites_domain'), 'sites', ['domain'], unique=False) op.create_index(op.f('ix_sites_organisation_id'), 'sites', ['organisation_id'], unique=False) op.create_index(op.f('ix_sites_site_group_id'), 'sites', ['site_group_id'], unique=False) op.create_table('consent_records', sa.Column('site_id', sa.UUID(), nullable=False), sa.Column('visitor_id', sa.String(length=255), nullable=False), sa.Column('ip_hash', sa.String(length=64), nullable=True), sa.Column('user_agent_hash', sa.String(length=64), nullable=True), sa.Column('action', sa.String(length=30), nullable=False), sa.Column('categories_accepted', postgresql.JSONB(astext_type=sa.Text()), nullable=False), sa.Column('categories_rejected', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('tc_string', sa.Text(), nullable=True), sa.Column('gcm_state', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gpp_string', sa.Text(), nullable=True), sa.Column('gpc_detected', sa.Boolean(), nullable=True), sa.Column('gpc_honoured', sa.Boolean(), nullable=True), sa.Column('ab_test_id', sa.UUID(), nullable=True), sa.Column('ab_variant_id', sa.UUID(), nullable=True), sa.Column('page_url', sa.Text(), nullable=True), sa.Column('country_code', sa.String(length=5), nullable=True), sa.Column('region_code', sa.String(length=10), nullable=True), sa.Column('consented_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('id', sa.UUID(), nullable=False), sa.ForeignKeyConstraint(['site_id'], ['sites.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_consent_records_ab_test_id'), 'consent_records', ['ab_test_id'], unique=False) op.create_index(op.f('ix_consent_records_consented_at'), 'consent_records', ['consented_at'], unique=False) op.create_index(op.f('ix_consent_records_site_id'), 'consent_records', ['site_id'], unique=False) op.create_index(op.f('ix_consent_records_visitor_id'), 'consent_records', ['visitor_id'], unique=False) op.create_table('cookie_allow_list', sa.Column('site_id', sa.UUID(), nullable=False), sa.Column('category_id', sa.UUID(), nullable=False), sa.Column('name_pattern', sa.String(length=255), nullable=False), sa.Column('domain_pattern', sa.String(length=255), nullable=False), sa.Column('description', sa.Text(), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['category_id'], ['cookie_categories.id'], ondelete='RESTRICT'), sa.ForeignKeyConstraint(['site_id'], ['sites.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('site_id', 'name_pattern', 'domain_pattern', name='uq_allow_list_site_name_domain') ) op.create_index(op.f('ix_cookie_allow_list_site_id'), 'cookie_allow_list', ['site_id'], unique=False) op.create_table('cookies', sa.Column('site_id', sa.UUID(), nullable=False), sa.Column('category_id', sa.UUID(), nullable=True), sa.Column('name', sa.String(length=255), nullable=False), sa.Column('domain', sa.String(length=255), nullable=False), sa.Column('storage_type', sa.String(length=30), server_default='cookie', nullable=False), sa.Column('description', sa.Text(), nullable=True), sa.Column('vendor', sa.String(length=255), nullable=True), sa.Column('path', sa.String(length=500), nullable=True), sa.Column('max_age_seconds', sa.Integer(), nullable=True), sa.Column('is_http_only', sa.Boolean(), nullable=True), sa.Column('is_secure', sa.Boolean(), nullable=True), sa.Column('same_site', sa.String(length=10), nullable=True), sa.Column('review_status', sa.String(length=20), server_default='pending', nullable=False), sa.Column('first_seen_at', sa.String(length=50), nullable=True), sa.Column('last_seen_at', sa.String(length=50), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['category_id'], ['cookie_categories.id'], ondelete='SET NULL'), sa.ForeignKeyConstraint(['site_id'], ['sites.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('site_id', 'name', 'domain', 'storage_type', name='uq_cookies_site_name_domain_type') ) op.create_index(op.f('ix_cookies_category_id'), 'cookies', ['category_id'], unique=False) op.create_index(op.f('ix_cookies_name'), 'cookies', ['name'], unique=False) op.create_index(op.f('ix_cookies_site_id'), 'cookies', ['site_id'], unique=False) op.create_table('scan_jobs', sa.Column('site_id', sa.UUID(), nullable=False), sa.Column('status', sa.String(length=20), server_default='pending', nullable=False), sa.Column('trigger', sa.String(length=20), server_default='manual', nullable=False), sa.Column('pages_scanned', sa.Integer(), server_default='0', nullable=False), sa.Column('pages_total', sa.Integer(), nullable=True), sa.Column('cookies_found', sa.Integer(), server_default='0', nullable=False), sa.Column('error_message', sa.Text(), nullable=True), sa.Column('started_at', sa.DateTime(timezone=True), nullable=True), sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['site_id'], ['sites.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_scan_jobs_site_id'), 'scan_jobs', ['site_id'], unique=False) op.create_index(op.f('ix_scan_jobs_status'), 'scan_jobs', ['status'], unique=False) op.create_table('site_configs', sa.Column('site_id', sa.UUID(), nullable=False), sa.Column('blocking_mode', sa.String(length=20), server_default='opt_in', nullable=False), sa.Column('regional_modes', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('tcf_enabled', sa.Boolean(), nullable=False), sa.Column('tcf_publisher_cc', sa.String(length=2), nullable=True), sa.Column('gpp_enabled', sa.Boolean(), nullable=False), sa.Column('gpp_supported_apis', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gpc_enabled', sa.Boolean(), nullable=False), sa.Column('gpc_jurisdictions', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('gpc_global_honour', sa.Boolean(), nullable=False), sa.Column('gcm_enabled', sa.Boolean(), nullable=False), sa.Column('gcm_default', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('shopify_privacy_enabled', sa.Boolean(), nullable=False), sa.Column('banner_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('display_mode', sa.String(length=30), server_default='bottom_banner', nullable=False), sa.Column('privacy_policy_url', sa.Text(), nullable=True), sa.Column('terms_url', sa.Text(), nullable=True), sa.Column('scan_schedule_cron', sa.String(length=100), nullable=True), sa.Column('scan_max_pages', sa.Integer(), server_default='50', nullable=False), sa.Column('consent_expiry_days', sa.Integer(), server_default='365', nullable=False), sa.Column('consent_retention_days', sa.Integer(), nullable=True), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['site_id'], ['sites.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('site_id') ) op.create_table('translations', sa.Column('site_id', sa.UUID(), nullable=False), sa.Column('locale', sa.String(length=10), nullable=False), sa.Column('strings', postgresql.JSONB(astext_type=sa.Text()), nullable=False), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['site_id'], ['sites.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('site_id', 'locale', name='uq_translations_site_locale') ) op.create_index(op.f('ix_translations_site_id'), 'translations', ['site_id'], unique=False) op.create_table('scan_results', sa.Column('scan_job_id', sa.UUID(), nullable=False), sa.Column('page_url', sa.Text(), nullable=False), sa.Column('cookie_name', sa.String(length=255), nullable=False), sa.Column('cookie_domain', sa.String(length=255), nullable=False), sa.Column('storage_type', sa.String(length=30), server_default='cookie', nullable=False), sa.Column('attributes', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('script_source', sa.Text(), nullable=True), sa.Column('auto_category', sa.String(length=50), nullable=True), sa.Column('initiator_chain', postgresql.ARRAY(sa.Text()), nullable=True, comment='Ordered script URLs from root initiator to leaf'), sa.Column('found_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('id', sa.UUID(), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['scan_job_id'], ['scan_jobs.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_scan_results_scan_job_id'), 'scan_results', ['scan_job_id'], unique=False) # ### end Alembic commands ### # ── Seed default cookie categories ─────────────────────────────── cookie_categories_table = sa.table( "cookie_categories", sa.column("id", sa.UUID()), sa.column("name", sa.String), sa.column("slug", sa.String), sa.column("description", sa.Text), sa.column("is_essential", sa.Boolean), sa.column("display_order", sa.Integer), sa.column("tcf_purpose_ids", postgresql.JSONB), sa.column("gcm_consent_types", postgresql.JSONB), ) op.bulk_insert( cookie_categories_table, [ { "id": uuid.UUID("10000000-0000-0000-0000-000000000001"), "name": "Necessary", "slug": "necessary", "description": ( "Essential cookies required for the website to function. " "These cannot be disabled." ), "is_essential": True, "display_order": 0, "tcf_purpose_ids": None, "gcm_consent_types": ["functionality_storage", "security_storage"], }, { "id": uuid.UUID("10000000-0000-0000-0000-000000000002"), "name": "Functional", "slug": "functional", "description": ( "Cookies that enable enhanced functionality and personalisation, " "such as remembering preferences." ), "is_essential": False, "display_order": 1, "tcf_purpose_ids": [1], "gcm_consent_types": ["functionality_storage", "personalization_storage"], }, { "id": uuid.UUID("10000000-0000-0000-0000-000000000003"), "name": "Analytics", "slug": "analytics", "description": ( "Cookies used to collect information about how visitors use the website, " "helping to improve the site." ), "is_essential": False, "display_order": 2, "tcf_purpose_ids": [7, 8, 9], "gcm_consent_types": ["analytics_storage"], }, { "id": uuid.UUID("10000000-0000-0000-0000-000000000004"), "name": "Marketing", "slug": "marketing", "description": ( "Cookies used to deliver personalised advertisements and " "track advertising campaign performance." ), "is_essential": False, "display_order": 3, "tcf_purpose_ids": [2, 3, 4, 5, 6, 10, 11], "gcm_consent_types": ["ad_storage", "ad_user_data", "ad_personalization"], }, { "id": uuid.UUID("10000000-0000-0000-0000-000000000005"), "name": "Personalisation", "slug": "personalisation", "description": ( "Cookies that enable content personalisation based on " "user profiles and browsing behaviour." ), "is_essential": False, "display_order": 4, "tcf_purpose_ids": [3, 4, 6], "gcm_consent_types": ["personalization_storage"], }, ], ) def downgrade() -> None: """Downgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### op.drop_index(op.f('ix_scan_results_scan_job_id'), table_name='scan_results') op.drop_table('scan_results') op.drop_index(op.f('ix_translations_site_id'), table_name='translations') op.drop_table('translations') op.drop_table('site_configs') op.drop_index(op.f('ix_scan_jobs_status'), table_name='scan_jobs') op.drop_index(op.f('ix_scan_jobs_site_id'), table_name='scan_jobs') op.drop_table('scan_jobs') op.drop_index(op.f('ix_cookies_site_id'), table_name='cookies') op.drop_index(op.f('ix_cookies_name'), table_name='cookies') op.drop_index(op.f('ix_cookies_category_id'), table_name='cookies') op.drop_table('cookies') op.drop_index(op.f('ix_cookie_allow_list_site_id'), table_name='cookie_allow_list') op.drop_table('cookie_allow_list') op.drop_index(op.f('ix_consent_records_visitor_id'), table_name='consent_records') op.drop_index(op.f('ix_consent_records_site_id'), table_name='consent_records') op.drop_index(op.f('ix_consent_records_consented_at'), table_name='consent_records') op.drop_index(op.f('ix_consent_records_ab_test_id'), table_name='consent_records') op.drop_table('consent_records') op.drop_index(op.f('ix_sites_site_group_id'), table_name='sites') op.drop_index(op.f('ix_sites_organisation_id'), table_name='sites') op.drop_index(op.f('ix_sites_domain'), table_name='sites') op.drop_table('sites') op.drop_table('site_group_configs') op.drop_index(op.f('ix_users_organisation_id'), table_name='users') op.drop_index(op.f('ix_users_email'), table_name='users') op.drop_table('users') op.drop_index(op.f('ix_site_groups_organisation_id'), table_name='site_groups') op.drop_table('site_groups') op.drop_table('org_configs') op.drop_index(op.f('ix_known_cookies_name_pattern'), table_name='known_cookies') op.drop_table('known_cookies') op.drop_index(op.f('ix_organisations_slug'), table_name='organisations') op.drop_table('organisations') op.drop_table('cookie_categories') # ### end Alembic commands ###