"""Initial schema baseline Revision ID: 001_initial_schema Revises: Create Date: 2025-12-04 12:00:00.000000 This is the baseline migration that captures the current database schema. For existing databases, this migration should be marked as applied without running it. For new databases, this creates the complete initial schema. """ from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import mysql # revision identifiers, used by Alembic. revision = '001_initial_schema' down_revision = None branch_labels = None depends_on = None def upgrade() -> None: """ Create all tables for a fresh database installation. """ # Users table op.create_table('users', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('username', sa.String(length=50), nullable=False), sa.Column('password', sa.String(length=255), nullable=False), sa.Column('role', sa.Enum('ADMINISTRATOR', 'OPERATOR', 'READ_ONLY', name='userrole'), nullable=False), sa.Column('email', sa.String(length=128), nullable=True), sa.Column('full_name', sa.String(length=100), nullable=True), sa.Column('is_active', sa.Integer(), nullable=False, server_default='1'), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('username'), mysql_engine='InnoDB', mysql_charset='utf8mb4', mysql_collate='utf8mb4_unicode_ci' ) op.create_index('idx_username', 'users', ['username']) op.create_index('idx_email', 'users', ['email']) # Main PPR submissions table op.create_table('submitted', sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False), sa.Column('status', sa.Enum('NEW', 'CONFIRMED', 'CANCELED', 'LANDED', 'DELETED', 'DEPARTED', name='pprstatus'), nullable=False, server_default='NEW'), sa.Column('ac_reg', sa.String(length=16), nullable=False), sa.Column('ac_type', sa.String(length=32), nullable=False), sa.Column('ac_call', sa.String(length=16), nullable=True), sa.Column('captain', sa.String(length=64), nullable=False), sa.Column('fuel', sa.String(length=16), nullable=True), sa.Column('in_from', sa.String(length=64), nullable=False), sa.Column('eta', sa.DateTime(), nullable=False), sa.Column('pob_in', sa.Integer(), nullable=False), sa.Column('out_to', sa.String(length=64), nullable=True), sa.Column('etd', sa.DateTime(), nullable=True), sa.Column('pob_out', sa.Integer(), nullable=True), sa.Column('email', sa.String(length=128), nullable=True), sa.Column('phone', sa.String(length=16), nullable=True), sa.Column('notes', sa.Text(), nullable=True), sa.Column('landed_dt', sa.DateTime(), nullable=True), sa.Column('departed_dt', sa.DateTime(), nullable=True), sa.Column('created_by', sa.String(length=16), nullable=True), sa.Column('submitted_dt', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.Column('public_token', sa.String(length=128), nullable=True), sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('public_token'), mysql_engine='InnoDB', mysql_charset='utf8mb4', mysql_collate='utf8mb4_unicode_ci' ) op.create_index('idx_status', 'submitted', ['status']) op.create_index('idx_eta', 'submitted', ['eta']) op.create_index('idx_etd', 'submitted', ['etd']) op.create_index('idx_ac_reg', 'submitted', ['ac_reg']) op.create_index('idx_submitted_dt', 'submitted', ['submitted_dt']) op.create_index('idx_created_by', 'submitted', ['created_by']) op.create_index('idx_public_token', 'submitted', ['public_token']) # Activity journal table op.create_table('journal', sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False), sa.Column('ppr_id', sa.BigInteger(), nullable=False), sa.Column('entry', sa.Text(), nullable=False), sa.Column('user', sa.String(length=50), nullable=False), sa.Column('ip', sa.String(length=45), nullable=False), sa.Column('entry_dt', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(['ppr_id'], ['submitted.id'], ondelete='CASCADE'), mysql_engine='InnoDB', mysql_charset='utf8mb4', mysql_collate='utf8mb4_unicode_ci' ) op.create_index('idx_ppr_id', 'journal', ['ppr_id']) op.create_index('idx_entry_dt', 'journal', ['entry_dt']) op.create_index('idx_user', 'journal', ['user']) # Airports reference table op.create_table('airports', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('icao', sa.String(length=4), nullable=False), sa.Column('iata', sa.String(length=3), nullable=True), sa.Column('name', sa.String(length=255), nullable=False), sa.Column('country', sa.String(length=100), nullable=False), sa.Column('city', sa.String(length=100), nullable=True), sa.Column('timezone', sa.String(length=50), nullable=True), sa.Column('latitude', mysql.DECIMAL(precision=10, scale=8), nullable=True), sa.Column('longitude', mysql.DECIMAL(precision=11, scale=8), nullable=True), sa.Column('elevation', sa.Integer(), nullable=True), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('icao', name='unique_icao'), mysql_engine='InnoDB', mysql_charset='utf8mb4', mysql_collate='utf8mb4_unicode_ci' ) op.create_index('idx_iata', 'airports', ['iata']) op.create_index('idx_country', 'airports', ['country']) op.create_index('idx_name', 'airports', ['name']) # Aircraft reference table op.create_table('aircraft', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('icao24', sa.String(length=6), nullable=True), sa.Column('registration', sa.String(length=25), nullable=True), sa.Column('manufacturer_icao', sa.String(length=50), nullable=True), sa.Column('type_code', sa.String(length=30), nullable=True), sa.Column('manufacturer_name', sa.String(length=255), nullable=True), sa.Column('model', sa.String(length=255), nullable=True), sa.Column('clean_reg', sa.String(length=25), sa.Computed("UPPER(REPLACE(REPLACE(registration, '-', ''), ' ', ''))", persisted=True), nullable=True), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False), sa.PrimaryKeyConstraint('id'), mysql_engine='InnoDB', mysql_charset='utf8mb4', mysql_collate='utf8mb4_unicode_ci' ) op.create_index('idx_registration', 'aircraft', ['registration']) op.create_index('idx_clean_reg', 'aircraft', ['clean_reg']) op.create_index('idx_icao24', 'aircraft', ['icao24']) op.create_index('idx_type_code', 'aircraft', ['type_code']) # Insert default admin user (password: admin123) # This should be changed immediately in production op.execute(""" INSERT INTO users (username, password, role, email, full_name) VALUES ('admin', '$2b$12$BJOha2yRxkxuHL./BaMfpu2fMDgGMYISuRV2.B1sSklVpRjz3Y4a6', 'ADMINISTRATOR', 'admin@ppr.local', 'System Administrator') """) def downgrade() -> None: """ Drop all tables - USE WITH CAUTION IN PRODUCTION """ op.drop_table('journal') op.drop_table('submitted') op.drop_table('users') op.drop_table('aircraft') op.drop_table('airports')