This commit is contained in:
Iliyan Angelov
2025-12-06 03:27:35 +02:00
parent 7667eb5eda
commit 5a8ca3c475
2211 changed files with 28086 additions and 37066 deletions

View File

@@ -1,5 +1,5 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import create_engine
from sqlalchemy import pool
from alembic import context
import os
@@ -15,17 +15,19 @@ config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
database_url = settings.database_url
config.set_main_option('sqlalchemy.url', database_url)
target_metadata = Base.metadata
def run_migrations_offline() -> None:
url = config.get_main_option('sqlalchemy.url')
"""Run migrations in 'offline' mode."""
url = database_url
context.configure(url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={'paramstyle': 'named'})
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
connectable = engine_from_config(config.get_section(config.config_ini_section, {}), prefix='sqlalchemy.', poolclass=pool.NullPool)
"""Run migrations in 'online' mode."""
# Create engine directly from URL to avoid ConfigParser interpolation issues
connectable = create_engine(database_url, poolclass=pool.NullPool)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():

View File

@@ -0,0 +1,92 @@
"""create_user_sessions_table
Revision ID: 54e4d0db31a3
Revises: d709b14aa24a
Create Date: 2025-12-06 01:12:15.123456
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '54e4d0db31a3'
down_revision = 'd709b14aa24a'
branch_labels = None
depends_on = None
def upgrade() -> None:
# Check if table exists before creating
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = inspector.get_table_names()
if 'user_sessions' not in tables:
# Create user sessions table
op.create_table(
'user_sessions',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('session_token', sa.String(length=255), nullable=False),
sa.Column('refresh_token', sa.String(length=255), nullable=True),
sa.Column('ip_address', sa.String(length=45), nullable=True),
sa.Column('user_agent', sa.String(length=500), nullable=True),
sa.Column('device_info', sa.Text(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default='1'),
sa.Column('last_activity', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.Column('expires_at', sa.DateTime(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# Create indexes
op.create_index(op.f('ix_user_sessions_id'), 'user_sessions', ['id'], unique=False)
op.create_index(op.f('ix_user_sessions_user_id'), 'user_sessions', ['user_id'], unique=False)
op.create_index(op.f('ix_user_sessions_session_token'), 'user_sessions', ['session_token'], unique=True)
op.create_index(op.f('ix_user_sessions_refresh_token'), 'user_sessions', ['refresh_token'], unique=True)
op.create_index(op.f('ix_user_sessions_is_active'), 'user_sessions', ['is_active'], unique=False)
op.create_index(op.f('ix_user_sessions_last_activity'), 'user_sessions', ['last_activity'], unique=False)
op.create_index(op.f('ix_user_sessions_expires_at'), 'user_sessions', ['expires_at'], unique=False)
def downgrade() -> None:
# Drop indexes
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = inspector.get_table_names()
if 'user_sessions' in tables:
try:
op.drop_index(op.f('ix_user_sessions_expires_at'), table_name='user_sessions')
except Exception:
pass
try:
op.drop_index(op.f('ix_user_sessions_last_activity'), table_name='user_sessions')
except Exception:
pass
try:
op.drop_index(op.f('ix_user_sessions_is_active'), table_name='user_sessions')
except Exception:
pass
try:
op.drop_index(op.f('ix_user_sessions_refresh_token'), table_name='user_sessions')
except Exception:
pass
try:
op.drop_index(op.f('ix_user_sessions_session_token'), table_name='user_sessions')
except Exception:
pass
try:
op.drop_index(op.f('ix_user_sessions_user_id'), table_name='user_sessions')
except Exception:
pass
try:
op.drop_index(op.f('ix_user_sessions_id'), table_name='user_sessions')
except Exception:
pass
# Drop table
op.drop_table('user_sessions')

View File

@@ -0,0 +1,114 @@
"""create_gdpr_requests_table
Revision ID: d709b14aa24a
Revises: b1c4d7c154ec
Create Date: 2025-12-06 01:10:15.233886
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'd709b14aa24a'
down_revision = 'b1c4d7c154ec'
branch_labels = None
depends_on = None
def upgrade() -> None:
# Check if table exists before creating
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = inspector.get_table_names()
if 'gdpr_requests' not in tables:
# Create GDPR requests table
op.create_table(
'gdpr_requests',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('request_type', sa.Enum('data_export', 'data_deletion', 'data_rectification', 'consent_withdrawal', name='gdprrequesttype'), nullable=False),
sa.Column('status', sa.Enum('pending', 'processing', 'completed', 'rejected', 'cancelled', name='gdprrequeststatus'), nullable=False, server_default='pending'),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('user_email', sa.String(length=255), nullable=False),
sa.Column('is_anonymous', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('request_data', sa.JSON(), nullable=True),
sa.Column('verification_token', sa.String(length=255), nullable=True),
sa.Column('verified_at', sa.DateTime(), nullable=True),
sa.Column('processed_by', sa.Integer(), nullable=True),
sa.Column('processed_at', sa.DateTime(), nullable=True),
sa.Column('processing_notes', sa.Text(), nullable=True),
sa.Column('export_file_path', sa.String(length=500), nullable=True),
sa.Column('deletion_log', sa.JSON(), nullable=True),
sa.Column('ip_address', sa.String(length=45), nullable=True),
sa.Column('user_agent', sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')),
sa.Column('expires_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['processed_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# Create indexes
op.create_index(op.f('ix_gdpr_requests_id'), 'gdpr_requests', ['id'], unique=False)
op.create_index(op.f('ix_gdpr_requests_request_type'), 'gdpr_requests', ['request_type'], unique=False)
op.create_index(op.f('ix_gdpr_requests_status'), 'gdpr_requests', ['status'], unique=False)
op.create_index(op.f('ix_gdpr_requests_user_id'), 'gdpr_requests', ['user_id'], unique=False)
op.create_index(op.f('ix_gdpr_requests_is_anonymous'), 'gdpr_requests', ['is_anonymous'], unique=False)
op.create_index(op.f('ix_gdpr_requests_created_at'), 'gdpr_requests', ['created_at'], unique=False)
op.create_index(op.f('ix_gdpr_requests_verification_token'), 'gdpr_requests', ['verification_token'], unique=True)
else:
# Table already exists, ensure it has the is_anonymous column if missing
columns = [col['name'] for col in inspector.get_columns('gdpr_requests')]
if 'is_anonymous' not in columns:
op.add_column('gdpr_requests', sa.Column('is_anonymous', sa.Boolean(), nullable=False, server_default='0'))
op.create_index(op.f('ix_gdpr_requests_is_anonymous'), 'gdpr_requests', ['is_anonymous'], unique=False)
# Ensure user_id is nullable
# Note: This might fail if there are existing non-null values, but for a new table it should be fine
try:
op.alter_column('gdpr_requests', 'user_id', existing_type=sa.Integer(), nullable=True)
except Exception:
pass # Column might already be nullable
def downgrade() -> None:
# Drop indexes
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = inspector.get_table_names()
if 'gdpr_requests' in tables:
try:
op.drop_index(op.f('ix_gdpr_requests_verification_token'), table_name='gdpr_requests')
except Exception:
pass
try:
op.drop_index(op.f('ix_gdpr_requests_created_at'), table_name='gdpr_requests')
except Exception:
pass
try:
op.drop_index(op.f('ix_gdpr_requests_is_anonymous'), table_name='gdpr_requests')
except Exception:
pass
try:
op.drop_index(op.f('ix_gdpr_requests_user_id'), table_name='gdpr_requests')
except Exception:
pass
try:
op.drop_index(op.f('ix_gdpr_requests_status'), table_name='gdpr_requests')
except Exception:
pass
try:
op.drop_index(op.f('ix_gdpr_requests_request_type'), table_name='gdpr_requests')
except Exception:
pass
try:
op.drop_index(op.f('ix_gdpr_requests_id'), table_name='gdpr_requests')
except Exception:
pass
# Drop table
op.drop_table('gdpr_requests')

View File

@@ -0,0 +1,110 @@
"""add_email_verification_and_enhance_user_security
Revision ID: fe519abcefe7
Revises: 54e4d0db31a3
Create Date: 2025-12-06 01:53:10.797944
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'fe519abcefe7'
down_revision = '54e4d0db31a3'
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = inspector.get_table_names()
# Add email_verified column to users table
if 'users' in tables:
columns = [col['name'] for col in inspector.get_columns('users')]
if 'email_verified' not in columns:
# Use MySQL-compatible boolean default
op.add_column('users', sa.Column('email_verified', sa.Boolean(), nullable=False, server_default=sa.text('0')))
# Check if index exists before creating
try:
op.create_index('ix_users_email_verified', 'users', ['email_verified'], unique=False)
except Exception:
pass # Index might already exist
# Add password expiry fields
if 'password_changed_at' not in columns:
op.add_column('users', sa.Column('password_changed_at', sa.DateTime(), nullable=True))
# Check if index exists before creating
try:
op.create_index('ix_users_password_changed_at', 'users', ['password_changed_at'], unique=False)
except Exception:
pass # Index might already exist
# Create password_history table
if 'password_history' not in tables:
op.create_table(
'password_history',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('password_hash', sa.String(length=255), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_password_history_id'), 'password_history', ['id'], unique=False)
op.create_index(op.f('ix_password_history_user_id'), 'password_history', ['user_id'], unique=False)
op.create_index(op.f('ix_password_history_created_at'), 'password_history', ['created_at'], unique=False)
# Create email_verification_tokens table
if 'email_verification_tokens' not in tables:
op.create_table(
'email_verification_tokens',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('token', sa.String(length=255), nullable=False),
sa.Column('email', sa.String(length=100), nullable=False),
sa.Column('expires_at', sa.DateTime(), nullable=False),
sa.Column('used', sa.Boolean(), nullable=False, server_default=sa.text('0')),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_email_verification_tokens_id'), 'email_verification_tokens', ['id'], unique=False)
op.create_index(op.f('ix_email_verification_tokens_user_id'), 'email_verification_tokens', ['user_id'], unique=False)
op.create_index(op.f('ix_email_verification_tokens_token'), 'email_verification_tokens', ['token'], unique=True)
op.create_index(op.f('ix_email_verification_tokens_expires_at'), 'email_verification_tokens', ['expires_at'], unique=False)
def downgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = inspector.get_table_names()
# Drop email_verification_tokens table
if 'email_verification_tokens' in tables:
op.drop_index(op.f('ix_email_verification_tokens_expires_at'), table_name='email_verification_tokens')
op.drop_index(op.f('ix_email_verification_tokens_token'), table_name='email_verification_tokens')
op.drop_index(op.f('ix_email_verification_tokens_user_id'), table_name='email_verification_tokens')
op.drop_index(op.f('ix_email_verification_tokens_id'), table_name='email_verification_tokens')
op.drop_table('email_verification_tokens')
# Remove password_history table
if 'password_history' in tables:
op.drop_index(op.f('ix_password_history_created_at'), table_name='password_history')
op.drop_index(op.f('ix_password_history_user_id'), table_name='password_history')
op.drop_index(op.f('ix_password_history_id'), table_name='password_history')
op.drop_table('password_history')
# Remove email_verified and password_changed_at columns from users table
if 'users' in tables:
columns = [col['name'] for col in inspector.get_columns('users')]
if 'password_changed_at' in columns:
op.drop_index('ix_users_password_changed_at', table_name='users')
op.drop_column('users', 'password_changed_at')
if 'email_verified' in columns:
op.drop_index('ix_users_email_verified', table_name='users')
op.drop_column('users', 'email_verified')