Phase 5: Complete Core Game Engine implementation
- Added MTGOnlineGame, GamePlayer, GameHistory models - Implemented full game CRUD endpoints (create, list, get, join, leave, start, end) - Integrated WebSocket game server with database persistence - Added Commander deck validation service (100-card min, color identity, ban list) - Created Alembic migration 006 for new game tables - All game endpoints now database-backed (no more stubs)
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Add game models: mtgonline_games, game_players, game_history
|
||||
|
||||
Revision ID: 006
|
||||
Revises: 005
|
||||
Create Date: 2026-01-05 00:00:00.000000
|
||||
|
||||
This migration creates tables for the Core Game Engine:
|
||||
- mtgonline_games: Active game sessions
|
||||
- game_players: Player associations with games
|
||||
- game_history: Historical game records
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '006'
|
||||
down_revision: Union[str, None] = '005'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Create game engine tables."""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 1. MTG Online Games Table (mtgonline_games)
|
||||
# Mirrors: app/models/models.py – class MTGOnlineGame
|
||||
# ------------------------------------------------------------------
|
||||
op.create_table(
|
||||
'mtgonline_games',
|
||||
sa.Column('id', sa.Integer(), primary_key=True, index=True),
|
||||
sa.Column('room_id', sa.Integer(),
|
||||
sa.ForeignKey('mtgonline_rooms.id'),
|
||||
nullable=False, index=True),
|
||||
sa.Column('host_id', sa.Integer(),
|
||||
sa.ForeignKey('mtgonline_users.id'),
|
||||
nullable=False, index=True),
|
||||
sa.Column('game_type', sa.String(50), nullable=False,
|
||||
server_default='Commander'),
|
||||
sa.Column('state', sa.String(20), nullable=False,
|
||||
server_default='WAITING'),
|
||||
sa.Column('is_password_protected', sa.Boolean(),
|
||||
nullable=False, server_default='false'),
|
||||
sa.Column('max_players', sa.Integer(), nullable=False,
|
||||
server_default='4'),
|
||||
sa.Column('current_players', sa.Integer(), nullable=False,
|
||||
server_default='0'),
|
||||
sa.Column('created_at', sa.DateTime(),
|
||||
server_default=sa.func.now()),
|
||||
sa.Column('started_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('finished_at', sa.DateTime(), nullable=True),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 2. Game Players Table (game_players)
|
||||
# Mirrors: app/models/models.py – class GamePlayer
|
||||
# ------------------------------------------------------------------
|
||||
op.create_table(
|
||||
'game_players',
|
||||
sa.Column('id', sa.Integer(), primary_key=True, index=True),
|
||||
sa.Column('game_id', sa.Integer(),
|
||||
sa.ForeignKey('mtgonline_games.id', ondelete='CASCADE'),
|
||||
nullable=False, index=True),
|
||||
sa.Column('user_id', sa.Integer(),
|
||||
sa.ForeignKey('mtgonline_users.id'),
|
||||
nullable=False, index=True),
|
||||
sa.Column('seat_number', sa.Integer(), nullable=True),
|
||||
sa.Column('life_total', sa.Integer(), nullable=False,
|
||||
server_default='20'),
|
||||
sa.Column('is_ready', sa.Boolean(), nullable=False,
|
||||
server_default='false'),
|
||||
sa.Column('is_host', sa.Boolean(), nullable=False,
|
||||
server_default='false'),
|
||||
sa.Column('joined_at', sa.DateTime(),
|
||||
server_default=sa.func.now()),
|
||||
)
|
||||
# Unique constraint + indexes
|
||||
op.create_unique_constraint(
|
||||
'uq_game_player_unique', 'game_players',
|
||||
['game_id', 'user_id'])
|
||||
op.create_index('idx_game_players_game', 'game_players', ['game_id'])
|
||||
op.create_index('idx_game_players_user', 'game_players', ['user_id'])
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 3. Game History Table (game_history)
|
||||
# Mirrors: app/models/models.py – class GameHistory
|
||||
# ------------------------------------------------------------------
|
||||
op.create_table(
|
||||
'game_history',
|
||||
sa.Column('id', sa.Integer(), primary_key=True, index=True),
|
||||
sa.Column('game_id', sa.Integer(),
|
||||
sa.ForeignKey('mtgonline_games.id'),
|
||||
nullable=True),
|
||||
sa.Column('room_id', sa.Integer(),
|
||||
sa.ForeignKey('mtgonline_rooms.id'),
|
||||
nullable=True, index=True),
|
||||
sa.Column('game_type', sa.String(50), nullable=False,
|
||||
server_default='Commander'),
|
||||
sa.Column('winner_id', sa.Integer(),
|
||||
sa.ForeignKey('mtgonline_users.id'),
|
||||
nullable=True, index=True),
|
||||
sa.Column('duration_seconds', sa.Integer(), nullable=True),
|
||||
sa.Column('result_type', sa.String(50), nullable=True),
|
||||
sa.Column('replay_data', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(),
|
||||
server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Drop game engine tables in reverse dependency order."""
|
||||
op.drop_table('game_history')
|
||||
op.drop_table('game_players')
|
||||
op.drop_table('mtgonline_games')
|
||||
Reference in New Issue
Block a user