from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager import asyncpg import json from datetime import date, datetime # --- KONFIGURASJON --- DB_URL = "postgresql://teeoff_admin:teeoff_secret_password@db:5432/teeoff" def format_row(row): """ Vasker data fra databasen: 1. Konverterer datoer til ISO-format. 2. Tvinger tekst-JSON (stringified JSON) over til ekte Python objekter/lister. """ if row is None: return None d = dict(row) # 1. Håndter dato- og tidsformater for JSON-serialisering for key in ['status_updated_at', 'created_at']: if isinstance(d.get(key), (date, datetime)): d[key] = d[key].isoformat() # 2. Definer alle felter som inneholder JSON-data # Disse må parses manuelt hvis de kommer som strenger fra Postgres json_list_fields = [ 'course_statuses', 'courses', 'gallery', 'greenfee', 'faqs', 'shotzoom', 'social_links', 'holes' ] json_dict_fields = [ 'amenities', 'vtg', 'nsg_data', 'golfamore_data' ] # Vask list-felter for field in json_list_fields: if field in d: val = d[field] if val is None: d[field] = [] elif isinstance(val, str): try: d[field] = json.loads(val) except: d[field] = [] elif not isinstance(val, list): d[field] = [] # Vask objekt-felter for field in json_dict_fields: if field in d: val = d[field] if val is None: d[field] = {} elif isinstance(val, str): try: d[field] = json.loads(val) except: d[field] = {} elif not isinstance(val, dict): d[field] = {} return d @asynccontextmanager async def lifespan(app: FastAPI): # Opprett database-pool ved start try: app.state.pool = await asyncpg.create_pool( DB_URL, min_size=5, max_size=20, command_timeout=60 ) print("✅ Database tilkoblet og pool opprettet") except Exception as e: print(f"❌ Databasefeil under oppstart: {e}") raise e yield # Lukk pool ved avslutning await app.state.pool.close() app = FastAPI(title="TeeOff API v3.5", lifespan=lifespan) # CORS-oppsett slik at Next.js kan snakke med API-et app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) @app.get("/api/facilities") async def get_facilities(): """Henter alle golfanlegg med aggregert banestatus""" async with app.state.pool.acquire() as conn: rows = await conn.fetch(""" SELECT f.*, ( SELECT jsonb_agg(cs) FROM ( SELECT name, status FROM courses WHERE facility_id = f.id AND status != 'finnes_ingen_bane_to' ORDER BY is_main_course DESC, id ASC ) cs ) as course_statuses FROM facilities f ORDER BY f.name ASC """) return [format_row(row) for row in rows] @app.get("/api/facilities/{slug}") async def get_facility(slug: str): """Henter detaljer for ett spesifikt golfanlegg inkludert alle baner og hull""" async with app.state.pool.acquire() as conn: row = await conn.fetchrow(""" SELECT f.*, ( SELECT jsonb_agg(c_data) FROM ( SELECT c.*, ( SELECT jsonb_agg(h_data ORDER BY h_data.hole_number ASC) FROM (SELECT * FROM holes WHERE course_id = c.id) h_data ) as holes FROM courses c WHERE c.facility_id = f.id AND (c.is_main_course = true OR (c.status NOT IN ('finnes_ingen_bane_to', 'ukjent'))) ORDER BY c.is_main_course DESC, c.id ASC ) c_data ) as courses FROM facilities f WHERE f.slug = $1 """, slug) if not row: raise HTTPException(status_code=404, detail="Golfanlegget ble ikke funnet") return format_row(row) @app.get("/api/health") async def health_check(): """Enkel sjekk for å se at API og DB lever""" try: async with app.state.pool.acquire() as conn: await conn.execute("SELECT 1") return {"status": "healthy", "database": "connected"} except Exception as e: return {"status": "unhealthy", "error": str(e)} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)