266 lines
14 KiB
Python
266 lines
14 KiB
Python
"""initial user schema
|
|
|
|
Revision ID: 001
|
|
Revises:
|
|
Create Date: 2026-01-01 00:00:00.000000
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = '001'
|
|
down_revision: Union[str, None] = '000'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
"""Create all user data tables."""
|
|
|
|
# 1. User Sessions Table
|
|
op.create_table(
|
|
'user_sessions',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('session_token_hash', sa.String(255), unique=True, nullable=False),
|
|
sa.Column('ip_address', sa.String(45), nullable=True),
|
|
sa.Column('user_agent', sa.Text(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
sa.Column('expires_at', sa.DateTime(), nullable=False),
|
|
sa.Column('is_active', sa.Boolean(), default=True),
|
|
)
|
|
op.create_index('idx_sessions_user', 'user_sessions', ['user_id'])
|
|
op.create_index('idx_sessions_token', 'user_sessions', ['session_token_hash'])
|
|
op.create_index('idx_sessions_expires', 'user_sessions', ['expires_at'])
|
|
|
|
# 2. Deck Versions Table
|
|
op.create_table(
|
|
'deck_versions',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('deck_id', sa.Integer(), sa.ForeignKey('mtgonline_decklist_files.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('version_number', sa.Integer(), nullable=False),
|
|
sa.Column('content', sa.Text(), nullable=False),
|
|
sa.Column('status', sa.String(20), server_default='DRAFT'),
|
|
sa.Column('comment', sa.Text(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_index('idx_deck_versions_deck', 'deck_versions', ['deck_id'])
|
|
|
|
# 4. Game Replays Table
|
|
op.create_table(
|
|
'game_replays',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('game_uuid', sa.String(36), unique=True, nullable=False),
|
|
sa.Column('room_id', sa.Integer(), sa.ForeignKey('mtgonline_rooms.id'), nullable=True),
|
|
sa.Column('game_type', sa.String(50), nullable=True),
|
|
sa.Column('format', sa.String(50), nullable=True),
|
|
sa.Column('duration_seconds', sa.Integer(), nullable=True),
|
|
sa.Column('start_time', sa.DateTime(), nullable=False),
|
|
sa.Column('end_time', sa.DateTime(), nullable=True),
|
|
sa.Column('status', sa.String(20), server_default='IN_PROGRESS'),
|
|
sa.Column('replay_data', sa.JSON(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now()),
|
|
)
|
|
op.create_index('idx_replays_room', 'game_replays', ['room_id'])
|
|
op.create_index('idx_replays_start', 'game_replays', ['start_time'])
|
|
op.create_index('idx_replays_status', 'game_replays', ['status'])
|
|
|
|
# 5. Replay Players Table
|
|
op.create_table(
|
|
'replay_players',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('replay_id', sa.BigInteger(), sa.ForeignKey('game_replays.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('position', sa.Integer(), nullable=True),
|
|
sa.Column('deck_id', sa.Integer(), sa.ForeignKey('mtgonline_decklist_files.id'), nullable=True),
|
|
sa.Column('won', sa.Boolean(), nullable=True),
|
|
sa.Column('lost', sa.Boolean(), nullable=True),
|
|
sa.Column('concession', sa.Boolean(), default=False),
|
|
sa.Column('turn_one', sa.Boolean(), default=False),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_index('idx_replay_players_replay', 'replay_players', ['replay_id'])
|
|
op.create_index('idx_replay_players_user', 'replay_players', ['user_id'])
|
|
|
|
# 6. Game Outcomes Table
|
|
op.create_table(
|
|
'game_outcomes',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('game_uuid', sa.String(36), sa.ForeignKey('game_replays.game_uuid'), nullable=False),
|
|
sa.Column('outcome', sa.String(20), nullable=False),
|
|
sa.Column('opponent_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=True),
|
|
sa.Column('format', sa.String(50), nullable=True),
|
|
sa.Column('rating_before', sa.Integer(), nullable=True),
|
|
sa.Column('rating_after', sa.Integer(), nullable=True),
|
|
sa.Column('rating_change', sa.Integer(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_index('idx_outcomes_user', 'game_outcomes', ['user_id'])
|
|
op.create_index('idx_outcomes_game', 'game_outcomes', ['game_uuid'])
|
|
op.create_index('idx_outcomes_outcome', 'game_outcomes', ['outcome'])
|
|
|
|
# 7. User Statistics Table
|
|
op.create_table(
|
|
'user_statistics',
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), primary_key=True),
|
|
sa.Column('total_games', sa.Integer(), default=0),
|
|
sa.Column('total_wins', sa.Integer(), default=0),
|
|
sa.Column('total_losses', sa.Integer(), default=0),
|
|
sa.Column('total_concessions', sa.Integer(), default=0),
|
|
sa.Column('win_rate', sa.Float(), default=0.0),
|
|
sa.Column('current_streak', sa.Integer(), default=0),
|
|
sa.Column('best_streak', sa.Integer(), default=0),
|
|
sa.Column('average_rating', sa.Float(), default=0.0),
|
|
sa.Column('last_game_date', sa.DateTime(), nullable=True),
|
|
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now()),
|
|
)
|
|
|
|
# 8. User Card Collection Table
|
|
op.create_table(
|
|
'user_card_collection',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('card_id', sa.Integer(), nullable=False),
|
|
sa.Column('quantity', sa.Integer(), default=1),
|
|
sa.Column('condition', sa.String(20), server_default='NEAR_MINT'),
|
|
sa.Column('language', sa.String(5), server_default='EN'),
|
|
sa.Column('is_foil', sa.Boolean(), default=False),
|
|
sa.Column('is_alt_art', sa.Boolean(), default=False),
|
|
sa.Column('acquired_date', sa.DateTime(), server_default=sa.func.now()),
|
|
sa.Column('acquisition_method', sa.String(50), nullable=True),
|
|
sa.Column('notes', sa.Text(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now()),
|
|
)
|
|
op.create_index('idx_collection_user', 'user_card_collection', ['user_id'])
|
|
op.create_index('idx_collection_card', 'user_card_collection', ['card_id'])
|
|
op.create_unique_constraint('uq_collection_unique', 'user_card_collection', ['user_id', 'card_id', 'is_foil', 'is_alt_art'])
|
|
|
|
# 9. Card Wishlist Table
|
|
op.create_table(
|
|
'card_wishlist',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('card_id', sa.Integer(), nullable=False),
|
|
sa.Column('max_price', sa.Float(), nullable=True),
|
|
sa.Column('notes', sa.Text(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_unique_constraint('uq_wishlist_user_card', 'card_wishlist', ['user_id', 'card_id'])
|
|
|
|
# 10. User Groups Table
|
|
op.create_table(
|
|
'user_groups',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('name', sa.String(100), nullable=False),
|
|
sa.Column('description', sa.Text(), nullable=True),
|
|
sa.Column('owner_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('is_public', sa.Boolean(), default=True),
|
|
sa.Column('max_members', sa.Integer(), default=50),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now()),
|
|
)
|
|
op.create_index('idx_groups_owner', 'user_groups', ['owner_id'])
|
|
|
|
# 11. Group Members Table
|
|
op.create_table(
|
|
'group_members',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('group_id', sa.BigInteger(), sa.ForeignKey('user_groups.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('role', sa.String(20), server_default='MEMBER'),
|
|
sa.Column('joined_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_index('idx_members_group', 'group_members', ['group_id'])
|
|
op.create_index('idx_members_user', 'group_members', ['user_id'])
|
|
op.create_unique_constraint('uq_group_member', 'group_members', ['group_id', 'user_id'])
|
|
|
|
# 12. Group Chat Messages Table
|
|
op.create_table(
|
|
'group_chat_messages',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('group_id', sa.BigInteger(), sa.ForeignKey('user_groups.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('sender_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('message', sa.Text(), nullable=False),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_index('idx_group_messages_group', 'group_chat_messages', ['group_id'])
|
|
op.create_index('idx_group_messages_sender', 'group_chat_messages', ['sender_id'])
|
|
op.create_index('idx_group_messages_created', 'group_chat_messages', ['created_at'])
|
|
|
|
# 13. User Networks Table
|
|
op.create_table(
|
|
'user_networks',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('name', sa.String(100), nullable=False),
|
|
sa.Column('description', sa.Text(), nullable=True),
|
|
sa.Column('creator_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('is_public', sa.Boolean(), default=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
|
|
# 14. Network Members Table
|
|
op.create_table(
|
|
'network_members',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('network_id', sa.BigInteger(), sa.ForeignKey('user_networks.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('role', sa.String(20), server_default='MEMBER'),
|
|
sa.Column('joined_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_index('idx_network_members_network', 'network_members', ['network_id'])
|
|
op.create_index('idx_network_members_user', 'network_members', ['user_id'])
|
|
op.create_unique_constraint('uq_network_member', 'network_members', ['network_id', 'user_id'])
|
|
|
|
# 15. User Preferences Table
|
|
op.create_table(
|
|
'user_preferences',
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), primary_key=True),
|
|
sa.Column('theme', sa.String(20), server_default='light'),
|
|
sa.Column('notifications_enabled', sa.Boolean(), default=True),
|
|
sa.Column('email_notifications', sa.Boolean(), default=True),
|
|
sa.Column('auto_save_decks', sa.Boolean(), default=True),
|
|
sa.Column('default_format', sa.String(50), server_default='standard'),
|
|
sa.Column('language', sa.String(5), server_default='EN'),
|
|
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now()),
|
|
)
|
|
|
|
# 16. User Activity Log Table
|
|
op.create_table(
|
|
'user_activity_log',
|
|
sa.Column('id', sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True),
|
|
sa.Column('user_id', sa.Integer(), sa.ForeignKey('mtgonline_users.id'), nullable=False),
|
|
sa.Column('activity_type', sa.String(50), nullable=False),
|
|
sa.Column('activity_data', sa.JSON(), nullable=True),
|
|
sa.Column('ip_address', sa.String(45), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
|
|
)
|
|
op.create_index('idx_activity_user', 'user_activity_log', ['user_id'])
|
|
op.create_index('idx_activity_type', 'user_activity_log', ['activity_type'])
|
|
op.create_index('idx_activity_created', 'user_activity_log', ['created_at'])
|
|
|
|
|
|
def downgrade() -> None:
|
|
"""Drop all user data tables."""
|
|
op.drop_table('user_activity_log')
|
|
op.drop_table('user_preferences')
|
|
op.drop_table('network_members')
|
|
op.drop_table('user_networks')
|
|
op.drop_table('group_chat_messages')
|
|
op.drop_table('group_members')
|
|
op.drop_table('user_groups')
|
|
op.drop_table('card_wishlist')
|
|
op.drop_table('user_card_collection')
|
|
op.drop_table('user_statistics')
|
|
op.drop_table('game_outcomes')
|
|
op.drop_table('replay_players')
|
|
op.drop_table('game_replays')
|
|
op.drop_table('deck_versions')
|
|
op.drop_table('user_sessions')
|