from fastapi import APIRouter, Depends, Request, Query from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from ..database import get_db from ..models import Serie, Set, Card router = APIRouter() templates = Jinja2Templates(directory="app/templates") @router.get("/", response_class=HTMLResponse) async def index(request: Request, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Serie).options(selectinload(Serie.sets).selectinload(Set.cards)).order_by(Serie.name) ) series = result.scalars().all() return templates.TemplateResponse("index.html", {"request": request, "series": series}) @router.get("/series", response_class=HTMLResponse) async def list_series(request: Request, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Serie).options(selectinload(Serie.sets).selectinload(Set.cards)).order_by(Serie.name) ) series = result.scalars().all() return templates.TemplateResponse("series.html", {"request": request, "series": series}) @router.get("/series/{serie_name}", response_class=HTMLResponse) async def serie_detail(request: Request, serie_name: str, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Serie).where(Serie.name == serie_name).options( selectinload(Serie.sets).selectinload(Set.cards) ) ) serie = result.scalar_one_or_none() if not serie: return templates.TemplateResponse("404.html", {"request": request}, status_code=404) return templates.TemplateResponse("serie_detail.html", {"request": request, "serie": serie}) @router.get("/sets", response_class=HTMLResponse) async def list_sets(request: Request, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Set).options(selectinload(Set.serie), selectinload(Set.cards)).order_by(Set.code) ) sets = result.scalars().all() return templates.TemplateResponse("sets.html", {"request": request, "sets": sets}) @router.get("/sets/{set_code}", response_class=HTMLResponse) async def set_detail(request: Request, set_code: str, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Set).where(Set.code == set_code.upper()).options( selectinload(Set.cards), selectinload(Set.serie) ) ) s = result.scalar_one_or_none() if not s: return templates.TemplateResponse("404.html", {"request": request}, status_code=404) return templates.TemplateResponse("set_detail.html", {"request": request, "set": s}) @router.get("/card/{set_code}/{number}", response_class=HTMLResponse) async def card_detail(request: Request, set_code: str, number: str, db: AsyncSession = Depends(get_db)): # Prøv med og uden leading zero (robust overfor "50" vs "050") candidates = set([number, number.zfill(3), number.lstrip("0")]) for num in candidates: result = await db.execute( select(Card) .join(Card.set) .where(Set.code == set_code.upper(), Card.number == num) .options(selectinload(Card.set).selectinload(Set.serie)) ) card = result.scalar_one_or_none() if card: return templates.TemplateResponse("card_detail.html", {"request": request, "card": card}) return templates.TemplateResponse("404.html", {"request": request}, status_code=404) @router.get("/search", response_class=HTMLResponse) async def search_page(request: Request): return templates.TemplateResponse("search.html", {"request": request}) # ─── JSON API ─────────────────────────────────────────────────────── @router.get("/api/series") async def api_list_series(db: AsyncSession = Depends(get_db)): result = await db.execute( select(Serie).options(selectinload(Serie.sets)).order_by(Serie.name) ) return [{"id": s.id, "name": s.name, "description": s.description or "", "set_count": len(s.sets)} for s in result.scalars().all()] @router.get("/api/series/{serie_name}/sets") async def api_series_sets(serie_name: str, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Serie).where(Serie.name == serie_name).options( selectinload(Serie.sets).selectinload(Set.cards) ) ) serie = result.scalar_one_or_none() if not serie: return {"error": "Serie ikke fundet"} return [{"code": s.code, "name": s.name, "card_count": len(s.cards)} for s in serie.sets] @router.get("/api/sets") async def api_list_sets(db: AsyncSession = Depends(get_db)): result = await db.execute( select(Set).options(selectinload(Set.serie), selectinload(Set.cards)).order_by(Set.code) ) return [{"code": s.code, "name": s.name, "serie": s.serie.name, "card_count": len(s.cards)} for s in result.scalars().all()] @router.get("/api/sets/{set_code}/cards") async def api_set_cards(set_code: str, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Set).where(Set.code == set_code.upper()).options(selectinload(Set.cards)) ) s = result.scalar_one_or_none() if not s: return [] return [{"id": c.id, "number": c.number, "name": c.name, "full_code": c.full_code, "image_url": c.image_url} for c in s.cards] @router.get("/api/search") async def search_cards(q: str = Query(""), db: AsyncSession = Depends(get_db)): """Search cards by full code (CRI-068) or by name.""" q = q.strip() if not q: return [] if "\u002D" in q or " " in q or "=" in q: # Split på bindestreg, mellemrum eller =: "CRI-068" eller "CRI 068" sep = "\u002D" if "\u002D" in q else (" " if " " in q else "=") parts = q.split(sep, 1) code_part = parts[0].strip().upper() num_part = parts[1].strip() candidates = set([num_part, num_part.zfill(3), num_part.lstrip("0")]) stmt = select(Card).join(Card.set).where( Set.code == code_part, Card.number.in_(list(candidates)) ).options(selectinload(Card.set).selectinload(Set.serie)) else: stmt = ( select(Card) .join(Card.set) .where(Card.name.ilike(f"%{q}%")) .options(selectinload(Card.set).selectinload(Set.serie)) .limit(50) ) result = await db.execute(stmt) cards = result.scalars().all() return [{ "id": c.id, "full_code": c.full_code, "name": c.name, "set_name": c.set.name, "set_code": c.set.code, "serie_name": c.set.serie.name, "image_url": c.image_url, } for c in cards]