Files
mtgonline/backend/app/routers/card_router.py
T
wall-o b170dfd577 feat: Add MTGJSON data integration
- Added MTGJSON data downloader (downloads all MTGJSON API v5 files)
- Added MTGJSON data loader (imports data into PostgreSQL)
- Added MTGJSON data uploader (alternative upsert logic)
- Fixed route ordering in card_router.py (/sets before /{card_name})
- Added load_mtgjson_data.py entry point script

MTGJSON data sources:
- AllPrintings.psql.gz (main cards database)
- AllSetFiles.zip (set and card data)
- AllDeckFiles.zip (deck data)
- AllIdentifiers.json.gz (card identifiers)
- CardTypes.json.gz (card types)
- DeckList.json.gz (deck list metadata)
- Keywords.json.gz (card keywords)
- SetList.json.gz (set list metadata)

Note: MTGJSON set.json does NOT contain image URLs. Only cards have image_uris.
Sets have iconSvgUrl (SVG icons) but no raster image URLs.
2026-07-20 00:15:25 +00:00

259 lines
6.4 KiB
Python

"""
Card search router for MTG card database.
Provides endpoints for searching and retrieving MTG card data
from the MTG PostgreSQL database with Redis caching.
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import mtg_get_db
from app.core.redis_client import cache_get, cache_set
from app.services.card_database import (
search_cards,
get_card_by_name,
get_cards_by_set,
get_card_types,
get_card_rarities,
get_sets,
get_set_by_code,
get_card_statistics,
)
router = APIRouter(prefix="/mtg/cards", tags=["MTG Cards"])
@router.get("/search")
async def search_cards_endpoint(
q: str = Query(..., min_length=1, description="Search query"),
limit: int = Query(100, ge=1, le=500, description="Maximum results"),
offset: int = Query(0, ge=0, description="Number of results to skip"),
db: AsyncSession = Depends(mtg_get_db),
):
"""
Search cards by name, type, or mana cost.
Uses Redis cache to improve performance for repeated searches.
"""
cache_key = f"card_search:{q}:{limit}:{offset}"
# Check cache first
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
# Query database
results = await search_cards(q, db, limit, offset)
# Cache results for 5 minutes
await cache_set(cache_key, str(results), ttl=300)
return {"cached": False, "results": results}
@router.get("/sets")
async def get_sets_endpoint(
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get all sets.
"""
cache_key = "all_sets:all"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
sets = await get_sets(db)
# Cache for 1 hour
await cache_set(cache_key, str(sets), ttl=3600)
return {"cached": False, "results": sets}
@router.get("/sets/{set_code}")
async def get_set_endpoint(
set_code: str,
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get a specific set by code.
"""
cache_key = f"set_by_code:{set_code}"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
mtg_set = await get_set_by_code(set_code, db)
if not mtg_set:
raise HTTPException(status_code=404, detail="Set not found")
# Cache for 1 hour
await cache_set(cache_key, str(mtg_set), ttl=3600)
return {"cached": False, "results": mtg_set}
@router.get("/{card_name}")
async def get_card_endpoint(
card_name: str,
set_code: str | None = Query(None, description="Filter by set code"),
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get a specific card by name.
Optional set_code filter to get a specific printing.
"""
cache_key = f"card_by_name:{card_name}:{set_code or 'all'}"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
card = await get_card_by_name(card_name, db, set_code)
if not card:
raise HTTPException(status_code=404, detail="Card not found")
# Cache for 10 minutes
await cache_set(cache_key, str(card), ttl=600)
return {"cached": False, "results": card}
@router.get("/set/{set_code}")
async def get_cards_by_set_endpoint(
set_code: str,
limit: int = Query(1000, ge=1, le=5000, description="Maximum results"),
offset: int = Query(0, ge=0, description="Number of results to skip"),
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get all cards in a specific set.
"""
cache_key = f"set_cards:{set_code}:{limit}:{offset}"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
results = await get_cards_by_set(set_code, db, limit, offset)
# Cache for 15 minutes
await cache_set(cache_key, str(results), ttl=900)
return {"cached": False, "results": results}
@router.get("/types")
async def get_card_types_endpoint(
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get all unique card types.
"""
cache_key = "card_types:all"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
types = await get_card_types(db)
# Cache for 30 minutes
await cache_set(cache_key, str(types), ttl=1800)
return {"cached": False, "results": types}
@router.get("/rarities")
async def get_card_rarities_endpoint(
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get all unique card rarities.
"""
cache_key = "card_rarities:all"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
rarities = await get_card_rarities(db)
# Cache for 30 minutes
await cache_set(cache_key, str(rarities), ttl=1800)
return {"cached": False, "results": rarities}
@router.get("/sets")
async def get_sets_endpoint(
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get all sets.
"""
cache_key = "all_sets:all"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
sets = await get_sets(db)
# Cache for 1 hour
await cache_set(cache_key, str(sets), ttl=3600)
return {"cached": False, "results": sets}
@router.get("/sets/{set_code}")
async def get_set_endpoint(
set_code: str,
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get a specific set by code.
"""
cache_key = f"set_by_code:{set_code}"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
mtg_set = await get_set_by_code(set_code, db)
if not mtg_set:
raise HTTPException(status_code=404, detail="Set not found")
# Cache for 1 hour
await cache_set(cache_key, str(mtg_set), ttl=3600)
return {"cached": False, "results": mtg_set}
@router.get("/statistics")
async def get_card_statistics_endpoint(
db: AsyncSession = Depends(mtg_get_db),
):
"""
Get overall card database statistics.
"""
cache_key = "card_statistics:all"
cached = await cache_get(cache_key)
if cached:
return {"cached": True, "results": cached}
stats = await get_card_statistics(db)
# Cache for 1 hour
await cache_set(cache_key, str(stats), ttl=3600)
return {"cached": False, "results": stats}