Files
ppr-ng/backend/alembic/versions/001_initial_schema.py
2025-12-04 17:54:49 +00:00

166 lines
8.1 KiB
Python

"""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')