- Added MTG card ORM models (mtg_cards, mtg_sets tables) - Created card_database service with search, get_by_name, get_by_set - Added Redis client with caching layer (3600s TTL default) - Created card router with caching on all endpoints: - Search cards (5min cache) - Get card by name (10min cache) - Get cards by set (15min cache) - Get card types/rarities (30min cache) - Get sets (1hr cache) - Get statistics (1hr cache) - Updated settings.py: - Added JWT_SECRET_KEY field - Added DB_CONFIG and REDIS_CONFIG dictionaries - Updated security.py to use JWT_SECRET_KEY with fallback - Updated auth.py to use timezone-aware datetimes - Updated refresh_mtg.py to use settings instead of os.environ - Updated mtg_monitor.py to use settings for connections - Added services package with __init__.py All 20 tests passing.
213 lines
5.4 KiB
Python
213 lines
5.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("/{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}
|