feat: MTG database integration with Redis caching

- 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.
This commit is contained in:
2026-07-18 18:41:05 +00:00
parent 167a352d44
commit 915242b330
20 changed files with 1804 additions and 287 deletions
+90
View File
@@ -0,0 +1,90 @@
"""Redis client and caching layer."""
import json
import logging
from typing import Any, Optional
import redis.asyncio as aioredis
from app.core.settings import get_settings
logger = logging.getLogger(__name__)
settings = get_settings()
# Redis client instance
redis_client: Optional[aioredis.Redis] = None
async def get_redis() -> aioredis.Redis:
"""Get Redis client instance."""
global redis_client
if redis_client is None:
try:
redis_client = aioredis.from_url(
settings.REDIS_URL,
decode_responses=True,
socket_connect_timeout=5,
socket_timeout=5,
)
await redis_client.ping()
logger.info("Connected to Redis")
except Exception as e:
logger.warning(f"Failed to connect to Redis: {e}")
redis_client = None
return redis_client
async def close_redis():
"""Close Redis connection."""
global redis_client
if redis_client:
await redis_client.close()
redis_client = None
logger.info("Closed Redis connection")
async def cache_get(key: str) -> Optional[str]:
"""Get cached value."""
try:
client = await get_redis()
if not client:
return None
value = await client.get(key)
return value
except Exception as e:
logger.error(f"Cache get error for key {key}: {e}")
return None
async def cache_set(key: str, value: str, ttl: int = 3600):
"""Set cached value with TTL (default 1 hour)."""
try:
client = await get_redis()
if not client:
return
await client.set(key, value, ex=ttl)
except Exception as e:
logger.error(f"Cache set error for key {key}: {e}")
async def cache_delete(key: str):
"""Delete cached value."""
try:
client = await get_redis()
if not client:
return
await client.delete(key)
except Exception as e:
logger.error(f"Cache delete error for key {key}: {e}")
async def cache_invalidate_pattern(pattern: str):
"""Invalidate all cached keys matching pattern."""
try:
client = await get_redis()
if not client:
return
keys = await client.keys(pattern)
if keys:
await client.delete(*keys)
except Exception as e:
logger.error(f"Cache invalidate error for pattern {pattern}: {e}")