feat: initial public release
ConsentOS — a privacy-first cookie consent management platform. Self-hosted, source-available alternative to OneTrust, Cookiebot, and CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant architecture with role-based access, configuration cascade (system → org → group → site → region), dark-pattern detection in the scanner, and a tamper-evident consent record audit trail. This is the initial public release. Prior development history is retained internally. See README.md for the feature list, architecture overview, and quick-start instructions. Licensed under the Elastic Licence 2.0 — self-host freely; do not resell as a managed service.
This commit is contained in:
442
apps/api/alembic/versions/0001_initial_schema.py
Normal file
442
apps/api/alembic/versions/0001_initial_schema.py
Normal file
@@ -0,0 +1,442 @@
|
||||
"""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 ###
|
||||
Reference in New Issue
Block a user