Homepage Redesign

This commit is contained in:
Eric Gullickson
2025-11-03 14:06:54 -06:00
parent 54d97a98b5
commit eeb20543fa
71 changed files with 3925 additions and 1340 deletions

View File

@@ -0,0 +1,88 @@
import redis.asyncio as redis
import json
import logging
from typing import Any, Optional
logger = logging.getLogger(__name__)
class CacheService:
"""Redis cache service with JSON serialization"""
def __init__(self, redis_client: Optional[redis.Redis], enabled: bool = True, default_ttl: int = 3600):
self.redis = redis_client
self.enabled = enabled and redis_client is not None
self.default_ttl = default_ttl
async def get(self, key: str) -> Optional[Any]:
"""Get value from cache"""
if not self.enabled:
return None
try:
value = await self.redis.get(key)
if value:
return json.loads(value)
return None
except Exception as e:
logger.error(f"Cache get error for key {key}: {e}")
return None
async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
"""Set value in cache"""
if not self.enabled:
return False
try:
ttl = ttl or self.default_ttl
json_value = json.dumps(value, default=str) # Handle datetime objects
await self.redis.setex(key, ttl, json_value)
return True
except Exception as e:
logger.error(f"Cache set error for key {key}: {e}")
return False
async def delete(self, key: str) -> bool:
"""Delete key from cache"""
if not self.enabled:
return False
try:
deleted = await self.redis.delete(key)
return deleted > 0
except Exception as e:
logger.error(f"Cache delete error for key {key}: {e}")
return False
async def invalidate_dropdown_cache(self) -> int:
"""Invalidate all dropdown cache entries"""
if not self.enabled:
return 0
try:
pattern = "dropdown:*"
keys = await self.redis.keys(pattern)
if keys:
deleted = await self.redis.delete(*keys)
logger.info(f"Invalidated {deleted} dropdown cache entries")
return deleted
return 0
except Exception as e:
logger.error(f"Cache invalidation error: {e}")
return 0
async def get_stats(self) -> dict:
"""Get cache statistics"""
if not self.enabled:
return {"enabled": False}
try:
info = await self.redis.info("memory")
return {
"enabled": True,
"used_memory": info.get("used_memory_human"),
"used_memory_peak": info.get("used_memory_peak_human"),
"connected_clients": await self.redis.client_list()
}
except Exception as e:
logger.error(f"Cache stats error: {e}")
return {"enabled": True, "error": str(e)}

View File

@@ -0,0 +1,58 @@
import asyncpg
from typing import List, Dict
from ..services.cache_service import CacheService
from ..repositories.vehicles_repository import VehiclesRepository
class VehiclesService:
def __init__(self, cache: CacheService, repo: VehiclesRepository | None = None):
self.cache = cache
self.repo = repo or VehiclesRepository()
async def get_years(self, db: asyncpg.Connection) -> List[int]:
cache_key = "dropdown:years"
cached = await self.cache.get(cache_key)
if cached:
return cached
years = await self.repo.get_years(db)
await self.cache.set(cache_key, years, ttl=6 * 3600)
return years
async def get_makes(self, db: asyncpg.Connection, year: int) -> List[Dict]:
cache_key = f"dropdown:makes:{year}"
cached = await self.cache.get(cache_key)
if cached:
return cached
makes = await self.repo.get_makes(db, year)
await self.cache.set(cache_key, makes, ttl=6 * 3600)
return makes
async def get_models(self, db: asyncpg.Connection, year: int, make_id: int) -> List[Dict]:
cache_key = f"dropdown:models:{year}:{make_id}"
cached = await self.cache.get(cache_key)
if cached:
return cached
models = await self.repo.get_models(db, year, make_id)
await self.cache.set(cache_key, models, ttl=6 * 3600)
return models
async def get_trims(self, db: asyncpg.Connection, year: int, model_id: int) -> List[Dict]:
cache_key = f"dropdown:trims:{year}:{model_id}"
cached = await self.cache.get(cache_key)
if cached:
return cached
trims = await self.repo.get_trims(db, year, model_id)
await self.cache.set(cache_key, trims, ttl=6 * 3600)
return trims
async def get_engines(
self, db: asyncpg.Connection, year: int, model_id: int, trim_id: int
) -> List[Dict]:
cache_key = f"dropdown:engines:{year}:{model_id}:{trim_id}"
cached = await self.cache.get(cache_key)
if cached:
return cached
engines = await self.repo.get_engines(db, year, model_id, trim_id)
await self.cache.set(cache_key, engines, ttl=6 * 3600)
return engines