This commit is contained in:
user committed 2025-07-02 19:25:20 +03:00
1 parent 21964fa986
commit 797f379648
68 files changed
+23871 -1271

No files matched your search

+59 -20
View File
@@ -1,17 +1,36 @@
from logging.config import fileConfig
"""Alembic environment configuration for async database migrations."""
from sqlalchemy import engine_from_config
from sqlalchemy import pool
import asyncio
import os
from logging.config import fileConfig
from typing import Any
from alembic import context
from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
# Import your models here
from app.core.models.base import Base
from app.core.models.user import User, APIKey, UserSession
from app.core.models.content import Content, ContentVersion, FileUpload, UserSubscription
from app.core.models.blockchain import Wallet, Transaction, BlockchainNFT, BlockchainDeFiPosition, BlockchainStaking, BlockchainTokenBalance
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
from app.core.models import AlchemyBase
target_metadata = AlchemyBase.metadata
# Set the target metadata for autogenerate support
target_metadata = Base.metadata
# Configure database URL from environment variable
database_url = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:password@localhost:5432/myuploader")
config.set_main_option("sqlalchemy.url", database_url)
def run_migrations_offline() -> None:
@@ -19,12 +38,11 @@ def run_migrations_offline() -> None:
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
@@ -32,32 +50,53 @@ def run_migrations_offline() -> None:
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
compare_server_default=True,
include_schemas=True,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
def do_run_migrations(connection: Connection) -> None:
"""Execute migrations with the given connection."""
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
compare_server_default=True,
include_schemas=True,
render_as_batch=True, # For better SQLite compatibility if needed
)
In this scenario we need to create an Engine
and associate a connection with the context.
with context.begin_transaction():
context.run_migrations()
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
async def run_async_migrations() -> None:
"""Run migrations in async mode."""
configuration = config.get_section(config.config_ini_section, {})
# Override the database URL if it's set in environment
if database_url:
configuration["sqlalchemy.url"] = database_url
connectable = async_engine_from_config(
configuration,
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
with context.begin_transaction():
context.run_migrations()
await connectable.dispose()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
asyncio.run(run_async_migrations())
if context.is_offline_mode():
+6 -6
View File
@@ -5,22 +5,22 @@ Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
"""Upgrade database schema."""
${upgrades if upgrades else "pass"}
def downgrade() -> None:
"""Downgrade database schema."""
${downgrades if downgrades else "pass"}
+382
View File
@@ -0,0 +1,382 @@
"""Initial database tables
Revision ID: 001
Revises:
Create Date: 2025-01-02 16:51:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '001'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Create initial database tables."""
# Create users table
op.create_table(
'users',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('username', sa.String(50), nullable=False, unique=True),
sa.Column('email', sa.String(255), nullable=False, unique=True),
sa.Column('password_hash', sa.String(255), nullable=False),
sa.Column('first_name', sa.String(100)),
sa.Column('last_name', sa.String(100)),
sa.Column('is_active', sa.Boolean(), default=True, nullable=False),
sa.Column('is_verified', sa.Boolean(), default=False, nullable=False),
sa.Column('is_superuser', sa.Boolean(), default=False, nullable=False),
sa.Column('avatar_url', sa.String(500)),
sa.Column('bio', sa.Text()),
sa.Column('last_login_at', sa.DateTime(timezone=True)),
sa.Column('login_count', sa.Integer(), default=0),
sa.Column('settings', postgresql.JSONB()),
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),
)
# Create indexes for users
op.create_index('ix_users_username', 'users', ['username'])
op.create_index('ix_users_email', 'users', ['email'])
op.create_index('ix_users_created_at', 'users', ['created_at'])
op.create_index('ix_users_is_active', 'users', ['is_active'])
# Create API keys table
op.create_table(
'api_keys',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
sa.Column('name', sa.String(100), nullable=False),
sa.Column('key_hash', sa.String(255), nullable=False, unique=True),
sa.Column('key_prefix', sa.String(20), nullable=False),
sa.Column('permissions', postgresql.JSONB(), default={}),
sa.Column('is_active', sa.Boolean(), default=True, nullable=False),
sa.Column('expires_at', sa.DateTime(timezone=True)),
sa.Column('last_used_at', sa.DateTime(timezone=True)),
sa.Column('usage_count', sa.Integer(), default=0),
sa.Column('rate_limit', sa.Integer(), default=1000),
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),
)
# Create indexes for API keys
op.create_index('ix_api_keys_user_id', 'api_keys', ['user_id'])
op.create_index('ix_api_keys_key_hash', 'api_keys', ['key_hash'])
op.create_index('ix_api_keys_is_active', 'api_keys', ['is_active'])
# Create user sessions table
op.create_table(
'user_sessions',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
sa.Column('session_token', sa.String(255), nullable=False, unique=True),
sa.Column('refresh_token', sa.String(255), nullable=False, unique=True),
sa.Column('user_agent', sa.String(500)),
sa.Column('ip_address', sa.String(45)),
sa.Column('is_active', sa.Boolean(), default=True, nullable=False),
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('last_activity_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
)
# Create indexes for user sessions
op.create_index('ix_user_sessions_user_id', 'user_sessions', ['user_id'])
op.create_index('ix_user_sessions_session_token', 'user_sessions', ['session_token'])
op.create_index('ix_user_sessions_is_active', 'user_sessions', ['is_active'])
op.create_index('ix_user_sessions_expires_at', 'user_sessions', ['expires_at'])
# Create content table
op.create_table(
'content',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
sa.Column('title', sa.String(255), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('content_type', sa.String(50), nullable=False),
sa.Column('file_path', sa.String(500)),
sa.Column('file_size', sa.BigInteger()),
sa.Column('file_hash', sa.String(64)),
sa.Column('mime_type', sa.String(100)),
sa.Column('is_public', sa.Boolean(), default=False, nullable=False),
sa.Column('is_featured', sa.Boolean(), default=False, nullable=False),
sa.Column('view_count', sa.Integer(), default=0),
sa.Column('download_count', sa.Integer(), default=0),
sa.Column('like_count', sa.Integer(), default=0),
sa.Column('metadata', postgresql.JSONB()),
sa.Column('tags', postgresql.ARRAY(sa.String(50))),
sa.Column('thumbnail_url', sa.String(500)),
sa.Column('preview_url', sa.String(500)),
sa.Column('status', sa.String(20), default='draft', nullable=False),
sa.Column('published_at', sa.DateTime(timezone=True)),
sa.Column('expires_at', sa.DateTime(timezone=True)),
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),
)
# Create indexes for content
op.create_index('ix_content_user_id', 'content', ['user_id'])
op.create_index('ix_content_content_type', 'content', ['content_type'])
op.create_index('ix_content_is_public', 'content', ['is_public'])
op.create_index('ix_content_status', 'content', ['status'])
op.create_index('ix_content_created_at', 'content', ['created_at'])
op.create_index('ix_content_published_at', 'content', ['published_at'])
op.create_index('ix_content_file_hash', 'content', ['file_hash'])
op.create_index('ix_content_tags', 'content', ['tags'], postgresql_using='gin')
# Create content versions table
op.create_table(
'content_versions',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('content_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('content.id', ondelete='CASCADE'), nullable=False),
sa.Column('version_number', sa.Integer(), nullable=False),
sa.Column('title', sa.String(255), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('file_path', sa.String(500)),
sa.Column('file_size', sa.BigInteger()),
sa.Column('file_hash', sa.String(64)),
sa.Column('metadata', postgresql.JSONB()),
sa.Column('change_summary', sa.Text()),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
)
# Create indexes for content versions
op.create_index('ix_content_versions_content_id', 'content_versions', ['content_id'])
op.create_index('ix_content_versions_version_number', 'content_versions', ['version_number'])
op.create_index('ix_content_versions_created_at', 'content_versions', ['created_at'])
# Create file uploads table
op.create_table(
'file_uploads',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
sa.Column('filename', sa.String(255), nullable=False),
sa.Column('original_filename', sa.String(255), nullable=False),
sa.Column('file_path', sa.String(500)),
sa.Column('file_size', sa.BigInteger(), nullable=False),
sa.Column('file_hash', sa.String(64)),
sa.Column('mime_type', sa.String(100)),
sa.Column('chunk_size', sa.Integer()),
sa.Column('total_chunks', sa.Integer()),
sa.Column('uploaded_chunks', sa.Integer(), default=0),
sa.Column('upload_session_id', sa.String(100)),
sa.Column('status', sa.String(20), default='pending', nullable=False),
sa.Column('processed', sa.Boolean(), default=False, nullable=False),
sa.Column('processing_started_at', sa.DateTime(timezone=True)),
sa.Column('processing_completed_at', sa.DateTime(timezone=True)),
sa.Column('error_message', sa.Text()),
sa.Column('retry_count', sa.Integer(), default=0),
sa.Column('metadata', postgresql.JSONB()),
sa.Column('expires_at', sa.DateTime(timezone=True)),
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),
)
# Create indexes for file uploads
op.create_index('ix_file_uploads_user_id', 'file_uploads', ['user_id'])
op.create_index('ix_file_uploads_status', 'file_uploads', ['status'])
op.create_index('ix_file_uploads_processed', 'file_uploads', ['processed'])
op.create_index('ix_file_uploads_upload_session_id', 'file_uploads', ['upload_session_id'])
op.create_index('ix_file_uploads_file_hash', 'file_uploads', ['file_hash'])
op.create_index('ix_file_uploads_expires_at', 'file_uploads', ['expires_at'])
# Create user subscriptions table
op.create_table(
'user_subscriptions',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
sa.Column('plan_name', sa.String(50), nullable=False),
sa.Column('status', sa.String(20), default='active', nullable=False),
sa.Column('storage_limit', sa.BigInteger(), nullable=False),
sa.Column('bandwidth_limit', sa.BigInteger(), nullable=False),
sa.Column('file_count_limit', sa.Integer(), nullable=False),
sa.Column('features', postgresql.JSONB()),
sa.Column('price', sa.Numeric(10, 2)),
sa.Column('currency', sa.String(3), default='USD'),
sa.Column('billing_cycle', sa.String(20), default='monthly'),
sa.Column('starts_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('expires_at', sa.DateTime(timezone=True)),
sa.Column('auto_renew', sa.Boolean(), default=True, 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),
)
# Create indexes for user subscriptions
op.create_index('ix_user_subscriptions_user_id', 'user_subscriptions', ['user_id'])
op.create_index('ix_user_subscriptions_status', 'user_subscriptions', ['status'])
op.create_index('ix_user_subscriptions_expires_at', 'user_subscriptions', ['expires_at'])
# Create wallets table
op.create_table(
'wallets',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
sa.Column('address', sa.String(100), nullable=False, unique=True),
sa.Column('network', sa.String(20), default='mainnet', nullable=False),
sa.Column('wallet_type', sa.String(20), default='ton', nullable=False),
sa.Column('balance', sa.Numeric(20, 8), default=0),
sa.Column('public_key', sa.String(200)),
sa.Column('encrypted_private_key', sa.Text()),
sa.Column('derivation_path', sa.String(100)),
sa.Column('is_active', sa.Boolean(), default=True, nullable=False),
sa.Column('is_primary', sa.Boolean(), default=False, nullable=False),
sa.Column('last_sync_at', sa.DateTime(timezone=True)),
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),
)
# Create indexes for wallets
op.create_index('ix_wallets_user_id', 'wallets', ['user_id'])
op.create_index('ix_wallets_address', 'wallets', ['address'])
op.create_index('ix_wallets_network', 'wallets', ['network'])
op.create_index('ix_wallets_is_active', 'wallets', ['is_active'])
# Create transactions table
op.create_table(
'transactions',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('wallet_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('wallets.id', ondelete='CASCADE'), nullable=False),
sa.Column('tx_hash', sa.String(100), unique=True),
sa.Column('from_address', sa.String(100), nullable=False),
sa.Column('to_address', sa.String(100), nullable=False),
sa.Column('amount', sa.Numeric(20, 8), nullable=False),
sa.Column('fee', sa.Numeric(20, 8)),
sa.Column('gas_limit', sa.BigInteger()),
sa.Column('gas_used', sa.BigInteger()),
sa.Column('gas_price', sa.Numeric(20, 8)),
sa.Column('nonce', sa.BigInteger()),
sa.Column('block_number', sa.BigInteger()),
sa.Column('block_hash', sa.String(100)),
sa.Column('transaction_index', sa.Integer()),
sa.Column('status', sa.String(20), default='pending', nullable=False),
sa.Column('transaction_type', sa.String(20), default='transfer', nullable=False),
sa.Column('confirmations', sa.Integer(), default=0),
sa.Column('metadata', postgresql.JSONB()),
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),
)
# Create indexes for transactions
op.create_index('ix_transactions_wallet_id', 'transactions', ['wallet_id'])
op.create_index('ix_transactions_tx_hash', 'transactions', ['tx_hash'])
op.create_index('ix_transactions_from_address', 'transactions', ['from_address'])
op.create_index('ix_transactions_to_address', 'transactions', ['to_address'])
op.create_index('ix_transactions_status', 'transactions', ['status'])
op.create_index('ix_transactions_created_at', 'transactions', ['created_at'])
op.create_index('ix_transactions_block_number', 'transactions', ['block_number'])
# Create blockchain NFTs table
op.create_table(
'blockchain_nfts',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('wallet_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('wallets.id', ondelete='CASCADE'), nullable=False),
sa.Column('token_id', sa.String(100), nullable=False),
sa.Column('collection_address', sa.String(100), nullable=False),
sa.Column('owner_address', sa.String(100), nullable=False),
sa.Column('token_uri', sa.String(500)),
sa.Column('metadata', postgresql.JSONB()),
sa.Column('name', sa.String(255)),
sa.Column('description', sa.Text()),
sa.Column('image_url', sa.String(500)),
sa.Column('attributes', postgresql.JSONB()),
sa.Column('rarity_score', sa.Numeric(10, 4)),
sa.Column('last_price', sa.Numeric(20, 8)),
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),
)
# Create unique constraint for NFTs
op.create_unique_constraint('uq_blockchain_nfts_token_collection', 'blockchain_nfts', ['token_id', 'collection_address'])
# Create indexes for blockchain NFTs
op.create_index('ix_blockchain_nfts_wallet_id', 'blockchain_nfts', ['wallet_id'])
op.create_index('ix_blockchain_nfts_collection_address', 'blockchain_nfts', ['collection_address'])
op.create_index('ix_blockchain_nfts_owner_address', 'blockchain_nfts', ['owner_address'])
# Create blockchain token balances table
op.create_table(
'blockchain_token_balances',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('wallet_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('wallets.id', ondelete='CASCADE'), nullable=False),
sa.Column('token_address', sa.String(100), nullable=False),
sa.Column('token_name', sa.String(100)),
sa.Column('token_symbol', sa.String(20)),
sa.Column('balance', sa.Numeric(30, 18), default=0, nullable=False),
sa.Column('decimals', sa.Integer(), default=18),
sa.Column('usd_value', sa.Numeric(20, 8)),
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),
)
# Create unique constraint for token balances
op.create_unique_constraint('uq_token_balances_wallet_token', 'blockchain_token_balances', ['wallet_id', 'token_address'])
# Create indexes for token balances
op.create_index('ix_blockchain_token_balances_wallet_id', 'blockchain_token_balances', ['wallet_id'])
op.create_index('ix_blockchain_token_balances_token_address', 'blockchain_token_balances', ['token_address'])
# Create blockchain DeFi positions table
op.create_table(
'blockchain_defi_positions',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('wallet_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('wallets.id', ondelete='CASCADE'), nullable=False),
sa.Column('protocol_name', sa.String(100), nullable=False),
sa.Column('position_type', sa.String(50), nullable=False),
sa.Column('pool_address', sa.String(100)),
sa.Column('token_symbols', postgresql.ARRAY(sa.String(20))),
sa.Column('balance', sa.Numeric(30, 18), default=0),
sa.Column('usd_value', sa.Numeric(20, 8)),
sa.Column('yield_rate', sa.Numeric(10, 4)),
sa.Column('metadata', postgresql.JSONB()),
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),
)
# Create indexes for DeFi positions
op.create_index('ix_blockchain_defi_positions_wallet_id', 'blockchain_defi_positions', ['wallet_id'])
op.create_index('ix_blockchain_defi_positions_protocol_name', 'blockchain_defi_positions', ['protocol_name'])
# Create blockchain staking table
op.create_table(
'blockchain_staking',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('wallet_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('wallets.id', ondelete='CASCADE'), nullable=False),
sa.Column('validator_address', sa.String(100), nullable=False),
sa.Column('staked_amount', sa.Numeric(20, 8), nullable=False),
sa.Column('rewards_earned', sa.Numeric(20, 8), default=0),
sa.Column('status', sa.String(20), default='active', nullable=False),
sa.Column('delegation_time', sa.DateTime(timezone=True), nullable=False),
sa.Column('unlock_time', sa.DateTime(timezone=True)),
sa.Column('apy', sa.Numeric(10, 4)),
sa.Column('metadata', postgresql.JSONB()),
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),
)
# Create indexes for staking
op.create_index('ix_blockchain_staking_wallet_id', 'blockchain_staking', ['wallet_id'])
op.create_index('ix_blockchain_staking_validator_address', 'blockchain_staking', ['validator_address'])
op.create_index('ix_blockchain_staking_status', 'blockchain_staking', ['status'])
def downgrade() -> None:
"""Drop all database tables."""
# Drop tables in reverse order to avoid foreign key constraints
op.drop_table('blockchain_staking')
op.drop_table('blockchain_defi_positions')
op.drop_table('blockchain_token_balances')
op.drop_table('blockchain_nfts')
op.drop_table('transactions')
op.drop_table('wallets')
op.drop_table('user_subscriptions')
op.drop_table('file_uploads')
op.drop_table('content_versions')
op.drop_table('content')
op.drop_table('user_sessions')
op.drop_table('api_keys')
op.drop_table('users')