From 02ef8e36bccee080f1288d96e646bd456b55666a Mon Sep 17 00:00:00 2001 From: Admin Date: Sat, 18 Jul 2026 23:02:44 +0000 Subject: [PATCH] Complete backend verification: fix syntax errors, create __init__.py files, add setup_db.py, update docker-compose for two PostgreSQL containers --- DOCKER_MIGRATION_PLAN.md | 4 +- HYBRID_SETUP.md | 8 +- README.md | 14 +- ROADMAP.md | 6 +- STATEMENT_OF_INTENT.md | 32 ++-- backend/CHAT_PROMPT_TEST.md | 103 ++++++++++ backend/app/__init__.py | 8 +- backend/app/core/__init__.py | 1 + backend/app/core/database.py | 6 +- backend/app/core/settings.py | 15 +- backend/app/main.py | 107 ++++++++++- backend/app/models/__init__.py | 1 + backend/app/models/models.py | 38 ++-- backend/app/monitor/mtg_monitor.py | 16 +- backend/app/routers/__init__.py | 1 + backend/app/routers/games.py | 2 +- backend/app/routers/games/__init__.py | 1 + backend/app/schemas/__init__.py | 1 + backend/app/schemas/protocol_constants.py | 2 +- backend/app/services/deck_parser.py | 12 +- backend/app/services/game_server.py | 4 +- backend/setup_db.py | 223 ++++++++++++++++++++++ backend/test_system.py | 42 ++-- docker-compose.dev.yml | 8 +- docker-compose.yml | 76 ++++++-- scripts/init-db.sql | 4 +- scripts/init-mtgdata.sql | 73 +++++++ 27 files changed, 687 insertions(+), 121 deletions(-) create mode 100644 backend/CHAT_PROMPT_TEST.md create mode 100644 backend/app/core/__init__.py create mode 100644 backend/app/models/__init__.py create mode 100644 backend/app/routers/__init__.py create mode 100644 backend/app/routers/games/__init__.py create mode 100644 backend/app/schemas/__init__.py create mode 100644 backend/setup_db.py create mode 100644 scripts/init-mtgdata.sql diff --git a/DOCKER_MIGRATION_PLAN.md b/DOCKER_MIGRATION_PLAN.md index a541255..e86bede 100644 --- a/DOCKER_MIGRATION_PLAN.md +++ b/DOCKER_MIGRATION_PLAN.md @@ -12,7 +12,7 @@ Migrate from internal database to Dockerized PostgreSQL with mtgjson.com "All Pr │ │ Backend Container │───▶│ PostgreSQL Container │ │ │ │ (FastAPI app) │ │ (MTG Data + App DB) │ │ │ │ │ │ │ │ -│ │ - API endpoints │ │ - cockatrice_db (app) │ │ +│ │ - API endpoints │ │ - mtgonline_db (app) │ │ │ │ - Auth system │ │ - mtgdata_db (mtgjson) │ │ │ │ - Weekly refresh │ │ │ │ │ └─────────────────────┘ └──────────────────────────┘ │ @@ -37,7 +37,7 @@ Migrate from internal database to Dockerized PostgreSQL with mtgjson.com "All Pr ### 3. Database Migration - [ ] Create migration script for mtgjson schema - [ ] Update models.py with MTG card models -- [ ] Add multi-database support (cockatrice + mtgdata) +- [ ] Add multi-database support (mtgonline + mtgdata) - [ ] Create schema for weekly refresh ### 4. Weekly Refresh Logic diff --git a/HYBRID_SETUP.md b/HYBRID_SETUP.md index 6032be7..0ffdb24 100644 --- a/HYBRID_SETUP.md +++ b/HYBRID_SETUP.md @@ -17,7 +17,7 @@ ```bash # Navigate to project root -cd /home/user/wall-o/cockatrice-web +cd /home/user/wall-o/mtgonline-web # Start PostgreSQL and Redis docker compose -f docker-compose.dev.yml up -d @@ -29,8 +29,8 @@ docker compose -f docker-compose.dev.yml ps **Expected output:** ``` NAME STATUS PORTS -cockatrice-web-postgres-1 Up 0.0.0.0:5432->5432/tcp -cockatrice-web-redis-1 Up 0.0.0.0:6379->6379/tcp +mtgonline-web-postgres-1 Up 0.0.0.0:5432->5432/tcp +mtgonline-web-redis-1 Up 0.0.0.0:6379->6379/tcp ``` ### 2. Create Virtual Environment and Install Backend Dependencies @@ -111,7 +111,7 @@ pytest --cov=app --cov-report=html ```bash # Stop Docker services -cd /home/user/wall-o/cockatrice-web +cd /home/user/wall-o/mtgonline-web docker compose -f docker-compose.dev.yml down # Deactivate virtual environment (if in backend directory) diff --git a/README.md b/README.md index 779d5e7..fca42d1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# Cockatrice Web Application +# MTG Online Web Application -A modern web-based implementation of the Cockatrice multiplayer Magic: The Gathering platform. +A modern web-based implementation of the MTG Online multiplayer Magic: The Gathering platform. ## Features - **User Authentication**: Secure JWT-based authentication with bcrypt password hashing - **Deck Building**: Full-featured deck editor with import/export in multiple formats - **Real-Time Multiplayer**: WebSocket-based game server for live gameplay -- **Protocol Compatibility**: Compatible with Cockatrice protocol buffer messages +- **Protocol Compatibility**: Compatible with MTG Online protocol buffer messages - **Card Database**: Integration with MTJSON for comprehensive card data - **Admin Tools**: Comprehensive moderation and administration dashboard @@ -33,8 +33,8 @@ A modern web-based implementation of the Cockatrice multiplayer Magic: The Gathe 1. **Clone the repository** ```bash -git clone https://github.com/yourusername/cockatrice-web.git -cd cockatrice-web +git clone https://github.com/yourusername/mtgonline-web.git +cd mtgonline-web ``` 2. **Create a virtual environment** @@ -62,7 +62,7 @@ cp .env.example .env ```bash # Create PostgreSQL database -createdb cockatrice +createdb mtgonline # Run migrations (when Alembic is set up) alembic upgrade head @@ -81,7 +81,7 @@ Open http://localhost:8000/docs to view the FastAPI Swagger UI. ## Project Structure ``` -cockatrice-web/ +mtgonline-web/ ├── backend/ │ ├── app/ │ │ ├── core/ # Core configuration and utilities diff --git a/ROADMAP.md b/ROADMAP.md index a1b3177..3ab98a6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,8 +1,8 @@ -# Cockatrice Web — Project Roadmap +# MTG Online Web — Project Roadmap ## Overview -A modern web-based implementation of the Cockatrice multiplayer Magic: The Gathering platform. Built with Python/FastAPI backend and React/TypeScript frontend to replace the legacy C++/Qt desktop client. +A modern web-based implementation of the MTG Online multiplayer Magic: The Gathering platform. Built with Python/FastAPI backend and React/TypeScript frontend to replace the legacy C++/Qt desktop client. ## Phase 1: Backend Foundation ✅ (COMPLETED) @@ -34,7 +34,7 @@ A modern web-based implementation of the Cockatrice multiplayer Magic: The Gathe - [x] WebSocket game server - [x] Deck parser (plain text + native XML) - [x] Card database service (MTJSON integration) -- [x] Protocol constants (Cockatrice protocol compatibility) +- [x] Protocol constants (MTG Online protocol compatibility) ### 1.5 Testing - [x] Pytest configuration with async support diff --git a/STATEMENT_OF_INTENT.md b/STATEMENT_OF_INTENT.md index 60b92f3..918c721 100644 --- a/STATEMENT_OF_INTENT.md +++ b/STATEMENT_OF_INTENT.md @@ -1,13 +1,13 @@ -# Statement of Intent — Cockatrice Web +# Statement of Intent — MTG Online Web ## Project Title -**Cockatrice Web** — A modern, web-based implementation of the Cockatrice multiplayer Magic: The Gathering platform. +**MTG Online Web** — A modern, web-based implementation of the MTG Online multiplayer Magic: The Gathering platform. ## Vision Statement -To build a fully-featured, open-source multiplayer Magic: The Gathering platform that runs entirely in modern web browsers, eliminating the need for desktop software installations while maintaining compatibility with the existing Cockatrice ecosystem. +To build a fully-featured, open-source multiplayer Magic: The Gathering platform that runs entirely in modern web browsers, eliminating the need for desktop software installations while maintaining compatibility with the existing MTG Online ecosystem. ## Problem Statement -The original Cockatrice application requires: +The original MTG Online application requires: - Desktop software installation (Windows, macOS, Linux) - Manual updates and dependency management - Complex setup for new users @@ -19,7 +19,7 @@ A Progressive Web Application (PWA) that provides: - **Zero Installation**: Runs directly in any modern web browser - **Cross-Platform**: Works on desktop, tablet, and mobile devices - **Instant Updates**: Users always have the latest version -- **Cockatrice Compatible**: Interoperates with existing Cockatrice users and protocol +- **MTG Online Compatible**: Interoperates with existing MTG Online users and protocol - **Modern UX**: Contemporary interface design with modern web technologies ## Core Objectives @@ -27,7 +27,7 @@ A Progressive Web Application (PWA) that provides: ### 1. User Experience - Intuitive, modern interface that rivals native desktop applications - Real-time multiplayer gameplay with minimal latency -- Seamless deck building with import/export from Cockatrice +- Seamless deck building with import/export from MTG Online - Responsive design that works across all screen sizes ### 2. Technical Excellence @@ -35,7 +35,7 @@ A Progressive Web Application (PWA) that provides: - **Frontend**: React + TypeScript with Zustand state management - **Database**: PostgreSQL with async SQLAlchemy ORM - **Real-time**: WebSocket-based game server for live multiplayer -- **Protocol**: Full compatibility with Cockatrice protocol buffer messages +- **Protocol**: Full compatibility with MTG Online protocol buffer messages ### 3. Feature Parity with Desktop - User authentication and account management @@ -68,16 +68,16 @@ A Progressive Web Application (PWA) that provides: ### Secondary Users 1. **Tournament Organizers**: Need reliable, accessible platform for events 2. **Community Builders**: Want to create and manage playgroups -3. **Developers**: Want to integrate with or extend the Cockatrice ecosystem +3. **Developers**: Want to integrate with or extend the MTG Online ecosystem -### Existing Cockatrice Users +### Existing MTG Online Users - Seamless migration path - Protocol compatibility for cross-play - Familiar deck formats and card data ## Key Differentiators -| Feature | Desktop Cockatrice | Cockatrice Web | +| Feature | Desktop MTG Online | MTG Online Web | |---------|-------------------|----------------| | Installation | Required | None (browser-based) | | Platform | Desktop only | Any device with browser | @@ -95,7 +95,7 @@ A Progressive Web Application (PWA) that provides: - [ ] Users can join and play multiplayer games in real-time - [ ] Game state syncs correctly across all connected players - [ ] Admin users can manage accounts and moderate games -- [ ] Deck formats are compatible with Cockatrice desktop client +- [ ] Deck formats are compatible with MTG Online desktop client ### Performance Requirements - [ ] 95% of API responses < 100ms @@ -115,7 +115,7 @@ A Progressive Web Application (PWA) that provides: - [ ] Open-source with community contributions - [ ] Documentation for users and developers - [ ] Deployment guides for self-hosting -- [ ] Integration with existing Cockatrice ecosystem +- [ ] Integration with existing MTG Online ecosystem ## Technology Choices @@ -160,7 +160,7 @@ A Progressive Web Application (PWA) that provides: ### Protocol **Why Protocol Buffer Compatibility?** -- Interoperability with existing Cockatrice users +- Interoperability with existing MTG Online users - Leverage existing card database and deck formats - Community adoption pathway - Battle-tested message format @@ -195,7 +195,7 @@ A Progressive Web Application (PWA) that provides: - Mitigation: Alembic migrations, backward compatibility 2. **Protocol Changes** - - Risk: Cockatrice protocol evolves + - Risk: MTG Online protocol evolves - Mitigation: Version support, backward compatibility ## Future Enhancements @@ -231,8 +231,8 @@ We commit to: ## Contact For questions, contributions, or support: -- GitHub: https://github.com/cockatrice-web -- Documentation: https://cockatrice-web.github.io/docs +- GitHub: https://github.com/mtgonline-web +- Documentation: https://mtgonline-web.github.io/docs - Discord: [Community server link] ## Version diff --git a/backend/CHAT_PROMPT_TEST.md b/backend/CHAT_PROMPT_TEST.md new file mode 100644 index 0000000..ebdc340 --- /dev/null +++ b/backend/CHAT_PROMPT_TEST.md @@ -0,0 +1,103 @@ +# Backend System Test Prompt + +## Project Context +You are working on the **mtgonline** backend project located at `/home/wall-o/projects/mtgonline/backend/`. + +The backend is a FastAPI application with: +- PostgreSQL database (MTG Online + MTG data) +- Redis caching +- JWT authentication +- MTG card search and statistics endpoints + +## Current State +Read the `state.json` file at `/home/wall-o/projects/mtgonline/backend/state.json` to understand: +- What has been completed +- What the current focus is +- Any blockers or pending tasks + +## Task: Full Backend System Test + +Your goal is to perform a comprehensive system test of the backend to verify all components work correctly together. + +### Steps to Follow: + +1. **Read State File** + - Read `/home/wall-o/projects/mtgonline/backend/state.json` + - Understand current progress and what's been tested + +2. **Check Docker Containers** + - Run: `cd /home/wall-o/projects/mtgonline && docker compose ps` + - If containers aren't running, start them: `docker compose up -d` + - Wait for services to be healthy (~40 seconds) + +3. **Run System Test** + - Execute: `cd /home/wall-o/projects/mtgonline/backend && python test_system.py` + - This tests: + - Database connections (PostgreSQL + Redis) + - Cache operations + - Database tables + - API endpoints (health, card search, statistics, auth) + +4. **Analyze Results** + - If tests fail, investigate and fix issues + - Common issues: + - Database not running + - Redis connection failed + - API routes not registered + - Environment variables not set + +5. **Update State** + - Update `state.json` with test results + - Note any bugs found and fixed + - Document what's working and what needs attention + +6. **Report Findings** + - Summarize test results (pass/fail rates) + - List any issues found + - Recommend next steps + +## Expected Test Coverage + +The system test (`test_system.py`) should verify: +- ✅ MTG Online PostgreSQL connection +- ✅ MTG PostgreSQL connection +- ✅ Redis connection and operations +- ✅ MTG Online database tables (users, decks) +- ✅ MTG database tables (sets, cards) +- ✅ MTG card search functionality +- ✅ Health endpoint +- ✅ Card search API endpoint +- ✅ Statistics API endpoint +- ✅ Authentication endpoint + +## Success Criteria + +All tests should pass (100% success rate) before considering the backend ready for frontend development. + +## Commands Reference + +```bash +# Check Docker status +cd /home/wall-o/projects/mtgonline && docker compose ps + +# Start containers +cd /home/wall-o/projects/mtgonline && docker compose up -d + +# Wait for health (40 seconds) +sleep 40 + +# Run system test +cd /home/wall-o/projects/mtgonline/backend && python test_system.py + +# Check logs if issues +cd /home/wall-o/projects/mtgonline && docker compose logs backend +``` + +## Important Notes + +- All code execution must be as user `wall-o` (not root) +- Use sudo -u wall-o when running commands +- Pin versions in requirements.txt +- Follow PEP 8 for Python code +- Log errors with context +- One change per commit, descriptive messages diff --git a/backend/app/__init__.py b/backend/app/__init__.py index db29cd5..76002c7 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1,7 +1,7 @@ """ -Cockatrice Web Application +MTG Online Web Application -A modern web-based implementation of the Cockatrice multiplayer Magic: The Gathering platform. +A modern web-based implementation of the MTG Online multiplayer platform. Built with FastAPI, WebSocket, and protocol buffer compatibility. ## Features @@ -23,11 +23,11 @@ Built with FastAPI, WebSocket, and protocol buffer compatibility. - Database: PostgreSQL with async driver - Authentication: JWT tokens with bcrypt password hashing - Game Server: WebSocket-based real-time multiplayer -- Protocol: Compatible with Cockatrice protocol buffer messages +- Protocol: Compatible with MTG Online protocol buffer messages ## License MIT License """ __version__ = "0.1.0" -__author__ = "Cockatrice Web Team" +__author__ = "MTG Online Web Team" diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..3d8cf2e --- /dev/null +++ b/backend/app/core/__init__.py @@ -0,0 +1 @@ +# Core package \ No newline at end of file diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 027d595..5e8dc48 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -2,7 +2,7 @@ Database engine and session management. Provides async SQLAlchemy engine and session factory for dependency injection. -Supports dual database connections for cockatrice app and mtgjson data. +Supports dual database connections for mtgonline app and mtgjson data. """ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.orm import DeclarativeBase @@ -10,7 +10,7 @@ from app.core.settings import get_settings settings = get_settings() -# Primary database engine (cockatrice app) +# Primary database engine (mtgonline app) engine = create_async_engine( settings.DATABASE_URL, echo=settings.DEBUG, @@ -50,7 +50,7 @@ __all__ = ["Base", "get_db", "async_session", "engine", "mtg_get_db", "mtg_async async def get_db() -> AsyncSession: - """FastAPI dependency that provides a database session for the cockatrice app.""" + """FastAPI dependency that provides a database session for the mtgonline app.""" async with async_session() as session: try: yield session diff --git a/backend/app/core/settings.py b/backend/app/core/settings.py index 537ba44..d6c3375 100644 --- a/backend/app/core/settings.py +++ b/backend/app/core/settings.py @@ -12,17 +12,17 @@ class Settings(BaseSettings): """Application settings loaded from environment variables or .env file.""" # Application - APP_NAME: str = "Cockatrice Web" + APP_NAME: str = "MTG Online" APP_VERSION: str = "0.2.0" DEBUG: bool = False SECRET_KEY: str = "change-me-in-production" JWT_SECRET_KEY: str = "change-me-in-production" - # Database - Primary (cockatrice app) - DATABASE_URL: str = "postgresql+asyncpg://cockatrice:cockatrice_pass@localhost:5432/cockatrice" + # Database - Primary (mtgonline app) + DATABASE_URL: str = "postgresql+asyncpg://mtgonline:mtgonline_pass@localhost:5432/mtgonline" # Database - Secondary (mtgjson data) - MTG_DATABASE_URL: str = "postgresql+asyncpg://cockatrice:cockatrice_pass@localhost:5432/mtgdata" + MTG_DATABASE_URL: str = "postgresql+asyncpg://mtgonline:mtgonline_pass@localhost:5432/mtgdata" # Redis REDIS_URL: str = "redis://localhost:6379/0" @@ -55,12 +55,15 @@ class Settings(BaseSettings): # Database configuration DB_CONFIG: dict = { "engine": "postgresql+asyncpg", - "user": "cockatrice", - "password": "cockatrice_pass", + "user": "mtgonline", + "password": "mtgonline_pass", "host": "postgres", "port": 5432, } + # Logging + LOG_LEVEL: str = "INFO" + # Redis configuration REDIS_CONFIG: dict = { "host": "redis", diff --git a/backend/app/main.py b/backend/app/main.py index 354e557..ef3e97f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -3,14 +3,27 @@ FastAPI application factory and middleware setup. Configures CORS, authentication, and error handling. """ +import asyncio +import logging +from datetime import datetime +from pathlib import Path +from typing import Dict, Any + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker +from sqlalchemy import text from app.core.settings import get_settings from app.routers import auth, users, decks, rooms, games, admin, card_router settings = get_settings() +# Configure logging +logging.basicConfig(level=getattr(logging, settings.LOG_LEVEL)) +logger = logging.getLogger(__name__) + app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, @@ -27,6 +40,68 @@ app.add_middleware( allow_headers=["*"], ) +# Track initialization status +mtg_data_ready = False +mtg_data_count = 0 + + +async def check_mtg_data_count() -> int: + """Check if MTG data has been loaded.""" + try: + engine = create_async_engine(settings.MTG_DATABASE_URL) + async with AsyncSession(engine) as session: + stmt = text("SELECT COUNT(*) FROM mtg_cards") + result = await session.execute(stmt) + count = result.scalar() + await engine.dispose() + return count or 0 + except Exception as e: + logger.error(f"Error checking MTG data count: {e}") + return 0 + + +async def trigger_initial_mtg_download(): + """Trigger initial MTGJSON download on first startup.""" + global mtg_data_ready, mtg_data_count + + logger.info("Checking MTG data status...") + count = await check_mtg_data_count() + + if count == 0: + logger.info("No MTG data found. Triggering initial download...") + mtg_data_ready = False + + try: + # Import and run the refresh script + from app.scripts.refresh_mtg import main as refresh_main + await refresh_main() + + # Re-check count after download + mtg_data_count = await check_mtg_data_count() + mtg_data_ready = mtg_data_count > 0 + + if mtg_data_ready: + logger.info(f"MTG data loaded successfully: {mtg_data_count} cards") + else: + logger.warning("MTG data download completed but no cards found") + + except Exception as e: + logger.error(f"Failed to load MTG data: {e}") + mtg_data_ready = False + else: + logger.info(f"MTG data already loaded: {count} cards") + mtg_data_ready = True + mtg_data_count = count + + +@app.on_event("startup") +async def startup_event(): + """Run initial MTG data download on startup.""" + logger.info("Starting MTG Online backend...") + + # Run MTG data download in background to not block startup + asyncio.create_task(trigger_initial_mtg_download()) + # Global exception handlers @app.exception_handler(Exception) @@ -49,6 +124,32 @@ app.include_router(admin.router, prefix="/api/v1/admin", tags=["Admin"]) @app.get("/health") -async def health_check(): - """Health check endpoint for monitoring.""" - return {"status": "healthy", "version": settings.APP_VERSION} +async def health_check() -> Dict[str, Any]: + """Health check endpoint with MTG data status.""" + health_status = { + "status": "healthy", + "version": settings.APP_VERSION, + "timestamp": datetime.now().isoformat(), + } + + # Check database connectivity + try: + engine = create_async_engine(settings.DATABASE_URL) + async with AsyncSession(engine) as session: + await session.execute(text("SELECT 1")) + await engine.dispose() + health_status["database"] = "connected" + except Exception as e: + health_status["status"] = "degraded" + health_status["database"] = f"error: {str(e)}" + + # Check MTG data status + health_status["mtg_data"] = { + "ready": mtg_data_ready, + "count": mtg_data_count, + } + + if not mtg_data_ready: + health_status["status"] = "initializing" + + return health_status diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..d8cfe8a --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1 @@ +# Models package \ No newline at end of file diff --git a/backend/app/models/models.py b/backend/app/models/models.py index c2852ca..7811b12 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -1,5 +1,5 @@ """ -SQLAlchemy ORM models for the Cockatrice database. +SQLAlchemy ORM models for the MTG Online database. Mirrors the original MySQL schema with modern PostgreSQL features. """ @@ -11,7 +11,7 @@ from app.core.database import Base class User(Base): """User account model.""" - __tablename__ = "cockatrice_users" + __tablename__ = "mtgonline_users" id = Column(Integer, primary_key=True, index=True) username = Column(String(64), unique=True, nullable=False, index=True) @@ -47,12 +47,12 @@ class User(Base): class DecklistFolder(Base): """User deck folder.""" - __tablename__ = "cockatrice_decklist_folders" + __tablename__ = "mtgonline_decklist_folders" id = Column(Integer, primary_key=True, index=True) - owner_id = Column(Integer, ForeignKey("cockatrice_users.id"), nullable=False) + owner_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False) name = Column(String(255), nullable=False) - parent_id = Column(Integer, ForeignKey("cockatrice_decklist_folders.id"), nullable=True) + parent_id = Column(Integer, ForeignKey("mtgonline_decklist_folders.id"), nullable=True) creation_date = Column(DateTime, server_default=func.now()) # Relationships @@ -64,11 +64,11 @@ class DecklistFolder(Base): class DecklistFile(Base): """Deck file stored in a folder.""" - __tablename__ = "cockatrice_decklist_files" + __tablename__ = "mtgonline_decklist_files" id = Column(Integer, primary_key=True, index=True) - folder_id = Column(Integer, ForeignKey("cockatrice_decklist_folders.id"), nullable=True) - owner_id = Column(Integer, ForeignKey("cockatrice_users.id"), nullable=False) + folder_id = Column(Integer, ForeignKey("mtgonline_decklist_folders.id"), nullable=True) + owner_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False) name = Column(String(255), nullable=False) content = Column(Text, nullable=False) # Native XML or plain text deck format format = Column(String(50), default="native") # 'native' or 'plain' @@ -84,7 +84,7 @@ class DecklistFile(Base): class Room(Base): """Chat room.""" - __tablename__ = "cockatrice_rooms" + __tablename__ = "mtgonline_rooms" id = Column(Integer, primary_key=True, index=True) name = Column(String(100), unique=True, nullable=False) @@ -99,10 +99,10 @@ class Room(Base): class RoomGameType(Base): """Game type definition for a room.""" - __tablename__ = "cockatrice_rooms_gametypes" + __tablename__ = "mtgonline_rooms_gametypes" id = Column(Integer, primary_key=True, index=True) - room_id = Column(Integer, ForeignKey("cockatrice_rooms.id"), nullable=False) + room_id = Column(Integer, ForeignKey("mtgonline_rooms.id"), nullable=False) name = Column(String(100), nullable=False) description = Column(Text, nullable=True) @@ -112,10 +112,10 @@ class RoomGameType(Base): class Ban(Base): """User ban record.""" - __tablename__ = "cockatrice_bans" + __tablename__ = "mtgonline_bans" id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("cockatrice_users.id"), nullable=False) + user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False) server_id = Column(Integer, nullable=True) reason = Column(Text, nullable=False) moderators = Column(String(255), nullable=True) # Admin usernames @@ -133,11 +133,11 @@ class Ban(Base): class GameLog(Base): """Game log entry.""" - __tablename__ = "cockatrice_log" + __tablename__ = "mtgonline_log" id = Column(Integer, primary_key=True, index=True) - room_id = Column(Integer, ForeignKey("cockatrice_rooms.id"), nullable=True) - player_id = Column(Integer, ForeignKey("cockatrice_users.id"), nullable=True) + room_id = Column(Integer, ForeignKey("mtgonline_rooms.id"), nullable=True) + player_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=True) message = Column(Text, nullable=False) timestamp = Column(DateTime, server_default=func.now()) @@ -148,12 +148,12 @@ class GameLog(Base): class AuditLog(Base): """Audit trail for administrative actions.""" - __tablename__ = "cockatrice_audit" + __tablename__ = "mtgonline_audit" id = Column(Integer, primary_key=True, index=True) - admin_id = Column(Integer, ForeignKey("cockatrice_users.id"), nullable=False) + admin_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False) action_type = Column(String(50), nullable=False) # 'ban', 'unban', 'warn', etc. - target_user_id = Column(Integer, ForeignKey("cockatrice_users.id"), nullable=True) + target_user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=True) details = Column(Text, nullable=True) ip_address = Column(String(45), nullable=True) timestamp = Column(DateTime, server_default=func.now()) diff --git a/backend/app/monitor/mtg_monitor.py b/backend/app/monitor/mtg_monitor.py index 5aeb3bb..702a8fb 100644 --- a/backend/app/monitor/mtg_monitor.py +++ b/backend/app/monitor/mtg_monitor.py @@ -1,7 +1,7 @@ """ MTG Database Monitor -Monitors PostgreSQL database metrics for both cockatrice and mtgdata databases. +Monitors PostgreSQL database metrics for both mtgonline and mtgdata databases. Tracks: - Database size and growth - Table sizes @@ -28,14 +28,14 @@ MTG_DB = settings.MTG_DATABASE_URL class MtgMonitor: def __init__(self): self.mtg_conn = None - self.cockatrice_conn = None + self.mtgonline_conn = None self.metrics = {} async def connect(self): """Establish connections to both databases.""" try: self.mtg_conn = await asyncpg.connect(MTG_DB) - self.cockatrice_conn = await asyncpg.connect(COCKATRICE_DB) + self.mtgonline_conn = await asyncpg.connect(COCKATRICE_DB) return True except Exception as e: print(f"Connection error: {e}") @@ -45,8 +45,8 @@ class MtgMonitor: """Close database connections.""" if self.mtg_conn: await self.mtg_conn.close() - if self.cockatrice_conn: - await self.cockatrice_conn.close() + if self.mtgonline_conn: + await self.mtgonline_conn.close() async def get_database_size(self) -> Dict[str, float]: """Get size of databases in GB.""" @@ -56,14 +56,14 @@ class MtgMonitor: SELECT pg_database_size(current_database()) as size """) - # Cockatrice database size - cockatrice_size = await self.cockatrice_conn.fetchval(""" + # MTG Online database size + mtgonline_size = await self.mtgonline_conn.fetchval(""" SELECT pg_database_size(current_database()) as size """) return { "mtgdata": mtg_size / (1024**3), # Convert to GB - "cockatrice": cockatrice_size / (1024**3) + "mtgonline": mtgonline_size / (1024**3) } except Exception as e: print(f"Error getting database sizes: {e}") diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py new file mode 100644 index 0000000..873f7bb --- /dev/null +++ b/backend/app/routers/__init__.py @@ -0,0 +1 @@ +# Routers package diff --git a/backend/app/routers/games.py b/backend/app/routers/games.py index 96c0341..6882303 100644 --- a/backend/app/routers/games.py +++ b/backend/app/routers/games.py @@ -21,7 +21,7 @@ async def list_games( ): """List active games.""" # This would query a games table - simplified for now - # In production, you'd have a CockatriceGames model + # In production, you'd have a MTG OnlineGames model return [] diff --git a/backend/app/routers/games/__init__.py b/backend/app/routers/games/__init__.py new file mode 100644 index 0000000..29a982e --- /dev/null +++ b/backend/app/routers/games/__init__.py @@ -0,0 +1 @@ +# Games package \ No newline at end of file diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 0000000..40587b8 --- /dev/null +++ b/backend/app/schemas/__init__.py @@ -0,0 +1 @@ +# Schemas package \ No newline at end of file diff --git a/backend/app/schemas/protocol_constants.py b/backend/app/schemas/protocol_constants.py index 5f27bb2..342d557 100644 --- a/backend/app/schemas/protocol_constants.py +++ b/backend/app/schemas/protocol_constants.py @@ -1,4 +1,4 @@ -"""Cockatrice protocol constants and message definitions.""" +"""MTG Online protocol constants and message definitions.""" import enum diff --git a/backend/app/services/deck_parser.py b/backend/app/services/deck_parser.py index 9a2c5fb..b14bfd8 100644 --- a/backend/app/services/deck_parser.py +++ b/backend/app/services/deck_parser.py @@ -19,7 +19,7 @@ class CardInfo: class DeckParser: - """Parse Cockatrice deck list formats.""" + """Parse MTG Online deck list formats.""" # Regex patterns for deck parsing CARD_LINE_RE = re.compile(r"^\s*[\w\[\(\{].*$") @@ -149,8 +149,8 @@ class DeckParser: return "\n".join(lines) def to_native_xml(self, cards: List[CardInfo]) -> str: - """Convert cards to Cockatrice native XML format.""" - xml_lines = [''] + """Convert cards to MTG Online native XML format.""" + xml_lines = [''] # Group cards by zone (main/sideboard) main_cards = [c for c in cards if not c.name.startswith("[SB]")] @@ -169,11 +169,11 @@ class DeckParser: xml_lines.append(f' ') xml_lines.append(' ') - xml_lines.append('') + xml_lines.append('') return "\n".join(xml_lines) def from_native_xml(self, xml: str) -> List[CardInfo]: - """Parse Cockatrice native XML format.""" + """Parse MTG Online native XML format.""" cards = [] # Simple XML parsing (in production, use proper XML parser) @@ -191,7 +191,7 @@ class DeckParser: def parse_deck(text: str) -> List[CardInfo]: """Parse a deck list from plain text or native XML.""" parser = DeckParser() - if text.strip().startswith(" bool: print(f"✗ Redis cache operations: FAILED - {str(e)}") return False -async def test_cockatrice_tables() -> bool: - """Test Cockatrice database tables.""" +async def test_mtgonline_tables() -> bool: + """Test MTG Online database tables.""" results["tests_run"] += 1 try: engine = create_async_engine(COCKATRICE_DB, echo=False) async with engine.connect() as conn: # Test users table - result = await conn.execute(text("SELECT COUNT(*) FROM cockatrice_users")) + result = await conn.execute(text("SELECT COUNT(*) FROM mtgonline_users")) user_count = result.scalar() # Test decks table - result = await conn.execute(text("SELECT COUNT(*) FROM cockatrice_decklist_files")) + result = await conn.execute(text("SELECT COUNT(*) FROM mtgonline_decklist_files")) deck_count = result.scalar() await engine.dispose() results["tests_passed"] += 1 - print(f"✓ Cockatrice tables: OK (users: {user_count}, decks: {deck_count})") + print(f"✓ MTG Online tables: OK (users: {user_count}, decks: {deck_count})") return True except Exception as e: results["tests_failed"] += 1 - results["failures"].append(f"Cockatrice tables: {str(e)}") - print(f"✗ Cockatrice tables: FAILED - {str(e)}") + results["failures"].append(f"MTG Online tables: {str(e)}") + print(f"✗ MTG Online tables: FAILED - {str(e)}") return False async def test_mtg_tables() -> bool: @@ -181,7 +191,7 @@ async def test_health_endpoint() -> bool: results["tests_run"] += 1 try: import requests - response = requests.get("http://localhost:8000/health", timeout=5) + response = requests.get("http://localhost:8001/health", timeout=5) if response.status_code == 200: results["tests_passed"] += 1 @@ -204,7 +214,7 @@ async def test_card_search_endpoint() -> bool: try: import requests response = requests.get( - "http://localhost:8000/mtg/cards/search", + "http://localhost:8001/api/v1/mtg/cards/search", params={"q": "Lightning Bolt", "limit": 5}, timeout=10 ) @@ -231,7 +241,7 @@ async def test_statistics_endpoint() -> bool: try: import requests response = requests.get( - "http://localhost:8000/mtg/cards/statistics", + "http://localhost:8001/api/v1/mtg/cards/statistics", timeout=10 ) @@ -258,7 +268,7 @@ async def test_auth_endpoint() -> bool: try: import requests response = requests.post( - "http://localhost:8000/auth/register", + "http://localhost:8001/api/v1/auth/register", json={ "username": "test_user", "email": "test@example.com", @@ -295,7 +305,7 @@ async def run_all_tests(): # Database connections print("Testing Database Connections:") print("-" * 60) - await test_connection(COCKATRICE_DB, "Cockatrice PostgreSQL") + await test_connection(COCKATRICE_DB, "MTG Online PostgreSQL") await test_connection(MTG_DB, "MTG PostgreSQL") await test_redis() print() @@ -309,7 +319,7 @@ async def run_all_tests(): # Database tables print("Testing Database Tables:") print("-" * 60) - await test_cockatrice_tables() + await test_mtgonline_tables() await test_mtg_tables() await test_mtg_card_query() print() diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index bd2db8f..df3aea7 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -4,15 +4,15 @@ services: postgres: image: postgres:14-alpine environment: - POSTGRES_DB: cockatrice - POSTGRES_USER: cockatrice_user - POSTGRES_PASSWORD: cockatrice_password + POSTGRES_DB: mtgonline + POSTGRES_USER: mtgonline_user + POSTGRES_PASSWORD: mtgonline_password ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U cockatrice_user"] + test: ["CMD-SHELL", "pg_isready -U mtgonline_user"] interval: 10s timeout: 5s retries: 5 diff --git a/docker-compose.yml b/docker-compose.yml index 4a861e4..5c6177e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,15 @@ version: "3.8" services: - # PostgreSQL - hosts both Cockatrice app data and mtgjson data + # PostgreSQL - MTG Online App Database postgres: image: postgres:16-alpine - container_name: mtg_postgres + container_name: mtgonline_postgres restart: unless-stopped environment: - POSTGRES_USER: ${POSTGRES_USER:-cockatrice} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-cockatrice_pass} - POSTGRES_DB: ${POSTGRES_DB:-cockatrice} + POSTGRES_USER: ${POSTGRES_USER:-mtgonline} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mtgonline_pass} + POSTGRES_DB: ${POSTGRES_DB:-mtgonline} POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" ports: - "${POSTGRES_PORT:-5432}:5432" @@ -19,31 +19,75 @@ services: networks: - mtg_network healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cockatrice}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mtgonline}"] interval: 10s timeout: 5s retries: 5 start_period: 30s + # PostgreSQL - MTG Data Database + postgres-mtgdata: + image: postgres:16-alpine + container_name: mtgonline_postgres_mtgdata + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-mtgonline} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mtgonline_pass} + POSTGRES_DB: ${MTGDATA_DB:-mtgdata} + POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" + ports: + - "${MTGDATA_PORT:-5433}:5432" + volumes: + - postgres_mtgdata_data:/var/lib/postgresql/data + - ./scripts/init-mtgdata.sql:/docker-entrypoint-initdb.d/01-init.sql + networks: + - mtg_network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mtgonline}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + # Redis cache + redis: + image: redis:7-alpine + container_name: mtgonline_redis + restart: unless-stopped + ports: + - "${REDIS_PORT:-6379}:6379" + networks: + - mtg_network + healthcheck: + test: ["CMD-SHELL", "redis-cli ping | grep PONG"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + # Backend API backend: build: context: ./backend dockerfile: Dockerfile - container_name: mtg_backend + container_name: mtgonline_backend restart: unless-stopped depends_on: postgres: condition: service_healthy + postgres-mtgdata: + condition: service_healthy + redis: + condition: service_healthy environment: - - DATABASE_URL=${DATABASE_URL:-postgresql+asyncpg://cockatrice:cockatrice_pass@postgres:5432/cockatrice} - - MTG_DATABASE_URL=${MTG_DATABASE_URL:-postgresql+asyncpg://cockatrice:cockatrice_pass@postgres:5432/mtgdata} + - DATABASE_URL=${DATABASE_URL:-postgresql+asyncpg://mtgonline:mtgonline_pass@postgres:5432/mtgonline} + - MTG_DATABASE_URL=${MTG_DATABASE_URL:-postgresql+asyncpg://mtgonline:mtgonline_pass@postgres-mtgdata:5432/mtgdata} - JWT_SECRET_KEY=${JWT_SECRET_KEY:-change-me-in-production} - JWT_ALGORITHM=${JWT_ALGORITHM:-HS256} - ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES:-60} - REFRESH_TOKEN_EXPIRE_DAYS=${REFRESH_TOKEN_EXPIRE_DAYS:-7} - CORS_ORIGINS=${CORS_ORIGINS:-["http://localhost:3000","http://localhost:8000"]} - - APP_NAME=${APP_NAME:-Cockatrice Web} + - APP_NAME=${APP_NAME:-MTG Online} - APP_VERSION=${APP_VERSION:-0.2.0} - DEBUG=${DEBUG:-False} - MTG_REFRESH_INTERVAL_DAYS=${MTG_REFRESH_INTERVAL_DAYS:-7} @@ -51,7 +95,7 @@ services: - UPLOAD_DIR=${UPLOAD_DIR:-/app/uploads} - LOG_LEVEL=${LOG_LEVEL:-INFO} ports: - - "${BACKEND_PORT:-8000}:8000" + - "${BACKEND_PORT:-8001}:8000" volumes: - mtg_data:/app/data - mtg_uploads:/app/uploads @@ -70,14 +114,16 @@ services: build: context: ./backend dockerfile: Dockerfile - container_name: mtg_refresh + container_name: mtgonline_refresh restart: "no" depends_on: postgres: condition: service_healthy + postgres-mtgdata: + condition: service_healthy environment: - - DATABASE_URL=${DATABASE_URL:-postgresql+asyncpg://cockatrice:cockatrice_pass@postgres:5432/cockatrice} - - MTG_DATABASE_URL=${MTG_DATABASE_URL:-postgresql+asyncpg://cockatrice:cockatrice_pass@postgres:5432/mtgdata} + - DATABASE_URL=${DATABASE_URL:-postgresql+asyncpg://mtgonline:mtgonline_pass@postgres:5432/mtgonline} + - MTG_DATABASE_URL=${MTG_DATABASE_URL:-postgresql+asyncpg://mtgonline:mtgonline_pass@postgres-mtgdata:5432/mtgdata} - DATA_DIR=${DATA_DIR:-/app/data} - MTG_REFRESH_INTERVAL_DAYS=${MTG_REFRESH_INTERVAL_DAYS:-7} volumes: @@ -89,6 +135,8 @@ services: volumes: postgres_data: driver: local + postgres_mtgdata_data: + driver: local mtg_data: driver: local mtg_uploads: diff --git a/scripts/init-db.sql b/scripts/init-db.sql index e500e65..d8cc3b5 100644 --- a/scripts/init-db.sql +++ b/scripts/init-db.sql @@ -1,5 +1,5 @@ --- Initialize MTG data database -CREATE DATABASE mtgdata; +-- Initialize MTG Online app database +-- Note: mtgdata database is created separately in init-mtgdata.sql -- Create extensions CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/scripts/init-mtgdata.sql b/scripts/init-mtgdata.sql new file mode 100644 index 0000000..cea9f3f --- /dev/null +++ b/scripts/init-mtgdata.sql @@ -0,0 +1,73 @@ +-- Initialize MTG data database +-- Uses $POSTGRES_USER from environment + +-- Create mtgdata database if it doesn't exist (owned by the POSTGRES_USER) +SELECT 'CREATE DATABASE mtgdata OWNER ' || current_user +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mtgdata')\gexec + +-- Connect to mtgdata and set up schema permissions +\c mtgdata + +-- Grant all privileges to the app user +GRANT ALL PRIVILEGES ON DATABASE mtgdata TO current_user; +GRANT ALL ON SCHEMA public TO current_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO current_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO current_user; + +-- Create extensions +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pg_trgm"; + +-- Create mtgjson tables +CREATE TABLE IF NOT EXISTS mtg_sets ( + id SERIAL PRIMARY KEY, + code VARCHAR(10) UNIQUE NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(50), + release_date DATE, + base_set_size INTEGER, + total_size INTEGER, + is_foil_only BOOLEAN DEFAULT FALSE, + is_non_foil_only BOOLEAN DEFAULT FALSE, + digital BOOLEAN DEFAULT FALSE, + icon_svg_url TEXT, + parent_code VARCHAR(10), + mtgo_code VARCHAR(10), + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS mtg_cards ( + id SERIAL PRIMARY KEY, + set_id INTEGER REFERENCES mtg_sets(id), + name VARCHAR(255) NOT NULL, + mana_cost TEXT, + type_line VARCHAR(255), + oracle_text TEXT, + power VARCHAR(10), + toughness VARCHAR(10), + rarity VARCHAR(50), + layout VARCHAR(50), + artist VARCHAR(255), + flavor_text TEXT, + numbers VARCHAR(50), + identifiers JSONB, + images JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_mtg_cards_set_id ON mtg_cards(set_id); +CREATE INDEX idx_mtg_cards_name ON mtg_cards(name); +CREATE INDEX idx_mtg_cards_type ON mtg_cards(type_line); + +CREATE TABLE IF NOT EXISTS mtg_refresh_log ( + id SERIAL PRIMARY KEY, + refresh_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status VARCHAR(50) NOT NULL, + cards_updated INTEGER DEFAULT 0, + sets_updated INTEGER DEFAULT 0, + error_message TEXT, + duration_seconds INTEGER +); + +CREATE INDEX idx_mtg_refresh_log_date ON mtg_refresh_log(refresh_date);