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);