Complete backend verification: fix syntax errors, create __init__.py files, add setup_db.py, update docker-compose for two PostgreSQL containers

This commit is contained in:
2026-07-18 23:02:44 +00:00
parent 3a70feaba5
commit 02ef8e36bc
27 changed files with 687 additions and 121 deletions
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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)
+7 -7
View File
@@ -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
+3 -3
View File
@@ -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
+16 -16
View File
@@ -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
+103
View File
@@ -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
+4 -4
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
# Core package
+3 -3
View File
@@ -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
+9 -6
View File
@@ -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",
+104 -3
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
# Models package
+19 -19
View File
@@ -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())
+8 -8
View File
@@ -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}")
+1
View File
@@ -0,0 +1 @@
# Routers package
+1 -1
View File
@@ -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 []
+1
View File
@@ -0,0 +1 @@
# Games package
+1
View File
@@ -0,0 +1 @@
# Schemas package
+1 -1
View File
@@ -1,4 +1,4 @@
"""Cockatrice protocol constants and message definitions."""
"""MTG Online protocol constants and message definitions."""
import enum
+6 -6
View File
@@ -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 = ['<cockatrice_deck version="1">']
"""Convert cards to MTG Online native XML format."""
xml_lines = ['<mtgonline_deck version="1">']
# 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' <card name="{clean_name}" count="{card.count}" />')
xml_lines.append(' </zone>')
xml_lines.append('</cockatrice_deck>')
xml_lines.append('</mtgonline_deck>')
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("<cockatrice_deck"):
if text.strip().startswith("<mtgonline_deck"):
return parser.from_native_xml(text)
else:
return parser.parse_plain_text(text)
+2 -2
View File
@@ -239,7 +239,7 @@ async def game_websocket_endpoint(websocket: WebSocket, game_id: int):
await room.send_to_player(player_id, {
"type": "pong",
"timestamp": datetime.now().isoformat(),
}))
})
except WebSocketDisconnect:
# Player disconnected
await room.remove_player(player_id)
@@ -297,7 +297,7 @@ async def process_game_command(room: GameRoom, player_id: int, command: dict):
await room.send_to_player(player_id, {
"type": "error",
"message": f"Invalid command type: {cmd_type}",
}))
})
return
# Broadcast command as game event
+223
View File
@@ -0,0 +1,223 @@
"""
Database initialization script for MTG Online backend.
Creates all necessary tables matching the SQLAlchemy ORM models
for both databases.
"""
import asyncio
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
async def setup_mtg_online_database():
"""Create tables for the mtgonline database."""
settings = get_settings()
engine = create_async_engine(settings.MTGO_DATABASE_URL)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
# Create tables
tables = [
"""
CREATE TABLE IF NOT EXISTS mtgonline_users (
id SERIAL PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
password_hash VARCHAR(128) NOT NULL,
salt VARCHAR(128) NOT NULL,
email VARCHAR(255),
country VARCHAR(2),
real_name VARCHAR(128),
avatar_bmp TEXT,
privlevel VARCHAR(50) DEFAULT 'User',
is_active BOOLEAN DEFAULT TRUE,
is_banned BOOLEAN DEFAULT FALSE,
ban_reason TEXT,
ban_ends TIMESTAMP,
vip_status INTEGER DEFAULT 0,
vip_expiry TIMESTAMP,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS mtgonline_decklist_folders (
id SERIAL PRIMARY KEY,
owner_id INTEGER REFERENCES mtgonline_users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
parent_id INTEGER REFERENCES mtgonline_decklist_folders(id) ON DELETE CASCADE,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS mtgonline_decklist_files (
id SERIAL PRIMARY KEY,
folder_id INTEGER REFERENCES mtgonline_decklist_folders(id) ON DELETE CASCADE,
owner_id INTEGER REFERENCES mtgonline_users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
format VARCHAR(50) DEFAULT 'native',
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS mtgonline_rooms (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_password_protected BOOLEAN DEFAULT FALSE,
password_hash VARCHAR(128),
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS mtgonline_rooms_gametypes (
id SERIAL PRIMARY KEY,
room_id INTEGER REFERENCES mtgonline_rooms(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT
)
""",
"""
CREATE TABLE IF NOT EXISTS mtgonline_bans (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES mtgonline_users(id) ON DELETE CASCADE,
server_id INTEGER,
reason TEXT NOT NULL,
moderators VARCHAR(255),
ip_address VARCHAR(45),
expiration_time TIMESTAMP,
active BOOLEAN DEFAULT TRUE,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS mtgonline_log (
id SERIAL PRIMARY KEY,
room_id INTEGER REFERENCES mtgonline_rooms(id) ON DELETE CASCADE,
player_id INTEGER REFERENCES mtgonline_users(id) ON DELETE CASCADE,
message TEXT NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS mtgonline_audit (
id SERIAL PRIMARY KEY,
admin_id INTEGER REFERENCES mtgonline_users(id) ON DELETE SET NULL,
action_type VARCHAR(50) NOT NULL,
target_user_id INTEGER REFERENCES mtgonline_users(id) ON DELETE SET NULL,
details TEXT,
ip_address VARCHAR(45),
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
]
for table_sql in tables:
await session.execute(text(table_sql))
# Create indexes
indexes = [
"CREATE INDEX IF NOT EXISTS idx_decks_owner ON mtgonline_decklist_files(owner_id);",
"CREATE INDEX IF NOT EXISTS idx_decks_folder ON mtgonline_decklist_files(folder_id);",
"CREATE INDEX IF NOT EXISTS idx_bans_active ON mtgonline_bans(active);",
"CREATE INDEX IF NOT EXISTS idx_log_timestamp ON mtgonline_log(timestamp);",
]
for idx_sql in indexes:
await session.execute(text(idx_sql))
print("✓ All mtgonline tables created")
await engine.dispose()
async def setup_mtg_data_database():
"""Create tables for the mtgdata database."""
settings = get_settings()
engine = create_async_engine(settings.MTG_DATABASE_URL)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
# Create tables
tables = [
"""
CREATE TABLE IF NOT EXISTS mtg_sets (
id SERIAL PRIMARY KEY,
code VARCHAR(10) UNIQUE NOT NULL,
name VARCHAR(255),
type VARCHAR(100),
release_date DATE,
base_set_size INTEGER,
total_size INTEGER,
is_foil_only BOOLEAN,
is_non_foil_only BOOLEAN,
digital BOOLEAN,
icon_svg_url TEXT,
parent_code VARCHAR(10),
mtgo_code VARCHAR(10),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS mtg_cards (
id SERIAL PRIMARY KEY,
set_id INTEGER REFERENCES mtg_sets(id) ON DELETE CASCADE,
name VARCHAR(255),
mana_cost VARCHAR(255),
type_line VARCHAR(255),
oracle_text TEXT,
power VARCHAR(50),
toughness VARCHAR(50),
rarity VARCHAR(50),
layout VARCHAR(50),
artist VARCHAR(255),
flavor_text TEXT,
numbers VARCHAR(100),
identifiers TEXT,
images TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
]
for table_sql in tables:
await session.execute(text(table_sql))
# Create indexes
indexes = [
"CREATE INDEX IF NOT EXISTS idx_mtg_cards_name ON mtg_cards(name);",
"CREATE INDEX IF NOT EXISTS idx_mtg_cards_type ON mtg_cards(type_line);",
"CREATE INDEX IF NOT EXISTS idx_mtg_cards_rarity ON mtg_cards(rarity);",
"CREATE INDEX IF NOT EXISTS idx_mtg_cards_set_id ON mtg_cards(set_id);",
]
for idx_sql in indexes:
await session.execute(text(idx_sql))
print("✓ All mtgdata tables created")
await engine.dispose()
async def main():
"""Main initialization function."""
print("MTG Online Database Initialization")
print("=" * 50)
# Setup mtgonline database
print("\nSetting up mtgonline database...")
await setup_mtg_online_database()
# Setup mtgdata database
print("\nSetting up mtgdata database...")
await setup_mtg_data_database()
print("\n✓ Database initialization complete!")
if __name__ == "__main__":
asyncio.run(main())
+26 -16
View File
@@ -3,7 +3,7 @@
Backend System Test Script
Tests all backend components:
- Database connections (Cockatrice + MTG)
- Database connections (MTG Online + MTG)
- Redis connection and caching
- Card database queries
- API endpoints
@@ -21,10 +21,20 @@ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy import text
# Database URLs
COCKATRICE_DB = "postgresql+asyncpg://cockatrice:cockatrice_pass@localhost:5432/cockatrice"
MTG_DB = "postgresql+asyncpg://cockatrice:cockatrice_pass@localhost:5432/mtgdata"
COCKATRICE_DB = "postgresql+asyncpg://mtgonline:mtgonline_pass@localhost:5432/mtgonline"
MTG_DB = "postgresql+asyncpg://mtgonline:mtgonline_pass@localhost:5432/mtgdata"
REDIS_URL = "redis://localhost:6379/0"
# Test URLs - detect environment
import socket
try:
# Try Docker network first
socket.getaddrinfo('mtg_backend', 8000)
API_BASE = 'http://mtg_backend:8000'
except:
# Fall back to host port
API_BASE = 'http://localhost:8001'
print(f"Using API base: {API_BASE}")
results = {
"tests_run": 0,
"tests_passed": 0,
@@ -97,29 +107,29 @@ async def test_cache_operations() -> 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()
+4 -4
View File
@@ -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
+62 -14
View File
@@ -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:
+2 -2
View File
@@ -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";
+73
View File
@@ -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);