"""BAN YARO — Gassi-Routen""" import json, math from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from typing import Optional, List from database import db from auth import get_current_user router = APIRouter() def _haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float: R = 6_371_000 p1 = math.radians(lat1) p2 = math.radians(lat2) dp = math.radians(lat2 - lat1) dl = math.radians(lon2 - lon1) a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 return 2 * R * math.asin(math.sqrt(a)) # ------------------------------------------------------------------ # Schemas # ------------------------------------------------------------------ class GPSPoint(BaseModel): lat: float lon: float class RouteCreate(BaseModel): name: str beschreibung: Optional[str] = None gps_track: List[GPSPoint] distanz_km: Optional[float] = None dauer_min: Optional[int] = None schwierigkeit: Optional[str] = "leicht" # leicht | mittel | anspruchsvoll untergrund: Optional[str] = None # wald | asphalt | wiese | mix schatten: Optional[bool] = None leine_empfohlen: Optional[bool] = None class RouteUpdate(BaseModel): name: Optional[str] = None beschreibung: Optional[str] = None schwierigkeit: Optional[str] = None untergrund: Optional[str] = None schatten: Optional[bool] = None leine_empfohlen: Optional[bool] = None def _parse(row) -> dict: d = dict(row) if isinstance(d.get('gps_track'), str): d['gps_track'] = json.loads(d['gps_track']) for k in ('schatten', 'leine_empfohlen'): if d.get(k) is not None: d[k] = bool(d[k]) return d # ------------------------------------------------------------------ # GET /api/routes — Routen (optional: Umkreis vom Startpunkt) # ------------------------------------------------------------------ @router.get("") async def list_routes( lat: Optional[float] = None, lon: Optional[float] = None, radius: int = 10000, ): with db() as conn: rows = conn.execute(""" SELECT r.id, r.user_id, r.name, r.beschreibung, r.distanz_km, r.dauer_min, r.schwierigkeit, r.untergrund, r.schatten, r.leine_empfohlen, r.bewertung, r.anz_bewertungen, r.created_at, u.name AS user_name, json_extract(r.gps_track, '$[0].lat') AS start_lat, json_extract(r.gps_track, '$[0].lon') AS start_lon, json_extract(r.gps_track, '$[-1].lat') AS end_lat, json_extract(r.gps_track, '$[-1].lon') AS end_lon FROM routes r LEFT JOIN users u ON u.id = r.user_id ORDER BY r.created_at DESC """).fetchall() result = [] for row in rows: d = dict(row) for k in ('schatten', 'leine_empfohlen'): if d.get(k) is not None: d[k] = bool(d[k]) result.append(d) if lat is not None and lon is not None: result = [ r for r in result if r['start_lat'] and _haversine(lat, lon, r['start_lat'], r['start_lon']) <= radius ] return result # ------------------------------------------------------------------ # POST /api/routes — neue Route speichern (Login erforderlich) # ------------------------------------------------------------------ @router.post("", status_code=201) async def create_route(data: RouteCreate, user=Depends(get_current_user)): if len(data.gps_track) < 2: raise HTTPException(400, "GPS-Track braucht mindestens 2 Punkte.") gps_json = json.dumps([p.model_dump() for p in data.gps_track]) with db() as conn: cur = conn.execute(""" INSERT INTO routes (user_id, name, beschreibung, gps_track, distanz_km, dauer_min, schwierigkeit, untergrund, schatten, leine_empfohlen) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( user['id'], data.name, data.beschreibung, gps_json, data.distanz_km, data.dauer_min, data.schwierigkeit, data.untergrund, int(data.schatten) if data.schatten is not None else None, int(data.leine_empfohlen) if data.leine_empfohlen is not None else None, )) row = conn.execute("SELECT * FROM routes WHERE id = ?", (cur.lastrowid,)).fetchone() return _parse(row) # ------------------------------------------------------------------ # GET /api/routes/{id} — Route mit vollem GPS-Track # ------------------------------------------------------------------ @router.get("/{route_id}") async def get_route(route_id: int): with db() as conn: row = conn.execute( "SELECT r.*, u.name AS user_name FROM routes r LEFT JOIN users u ON u.id = r.user_id WHERE r.id = ?", (route_id,) ).fetchone() if not row: raise HTTPException(404, "Route nicht gefunden.") return _parse(row) # ------------------------------------------------------------------ # PATCH /api/routes/{id} # ------------------------------------------------------------------ @router.patch("/{route_id}") async def update_route(route_id: int, data: RouteUpdate, user=Depends(get_current_user)): with db() as conn: row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone() if not row: raise HTTPException(404, "Route nicht gefunden.") if row['user_id'] != user['id']: raise HTTPException(403, "Nicht berechtigt.") updates = data.model_dump(exclude_none=True) if updates: for key in ('schatten', 'leine_empfohlen'): if key in updates: updates[key] = int(updates[key]) cols = ', '.join(f"{k} = ?" for k in updates) conn.execute(f"UPDATE routes SET {cols} WHERE id = ?", [*updates.values(), route_id]) row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone() return _parse(row) # ------------------------------------------------------------------ # DELETE /api/routes/{id} # ------------------------------------------------------------------ @router.delete("/{route_id}", status_code=204) async def delete_route(route_id: int, user=Depends(get_current_user)): with db() as conn: row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone() if not row: raise HTTPException(404, "Route nicht gefunden.") if row['user_id'] != user['id']: raise HTTPException(403, "Nicht berechtigt.") conn.execute("DELETE FROM routes WHERE id = ?", (route_id,))