"""BAN YARO — Gassi-Zeiten-Pool (regelmäßige Gassi-Zeiten mit Gleichgesinnten)""" import json import math import logging 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 logger = logging.getLogger(__name__) router = APIRouter() def _haversine(lat1, lon1, lat2, lon2): R = 6_371_000 p1, p2 = math.radians(lat1), 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)) class GassiZeitCreate(BaseModel): dog_id: Optional[int] = None wochentage: List[str] # ["mo", "mi", "fr"] uhrzeit: str # "17:00" ort_name: Optional[str] = None lat: Optional[float] = None lon: Optional[float] = None radius_m: int = 500 notiz: Optional[str] = None class GassiZeitUpdate(BaseModel): aktiv: Optional[int] = None # ------------------------------------------------------------------ # GET /api/gassi-zeiten — alle in der Nähe (oder eigene) # ------------------------------------------------------------------ @router.get("") async def list_gassi_zeiten( lat: Optional[float] = None, lon: Optional[float] = None, radius: int = 5000, # Meter nur_eigene: bool = False, user=Depends(get_current_user), ): with db() as conn: if nur_eigene: rows = conn.execute(""" SELECT gz.*, u.name AS user_name, u.avatar_url, d.name AS dog_name, d.foto_url AS dog_foto_url, d.rasse AS dog_rasse FROM gassi_zeiten gz LEFT JOIN users u ON u.id = gz.user_id LEFT JOIN dogs d ON d.id = gz.dog_id WHERE gz.user_id = ? ORDER BY gz.uhrzeit ASC """, (user["id"],)).fetchall() else: rows = conn.execute(""" SELECT gz.*, u.name AS user_name, u.avatar_url, d.name AS dog_name, d.foto_url AS dog_foto_url, d.rasse AS dog_rasse FROM gassi_zeiten gz LEFT JOIN users u ON u.id = gz.user_id LEFT JOIN dogs d ON d.id = gz.dog_id WHERE gz.aktiv = 1 ORDER BY gz.uhrzeit ASC """).fetchall() result = [] for r in rows: d = dict(r) # wochentage JSON parsen try: d["wochentage"] = json.loads(d["wochentage"]) if isinstance(d["wochentage"], str) else d["wochentage"] except Exception: d["wochentage"] = [] d["is_mine"] = (d["user_id"] == user["id"]) # Distanz-Filter if lat is not None and lon is not None and d.get("lat") and d.get("lon"): dist = _haversine(lat, lon, d["lat"], d["lon"]) if not nur_eigene and dist > radius: continue d["distance_m"] = int(dist) else: d["distance_m"] = None result.append(d) # Sortierung: eigene zuerst, dann nach Distanz result.sort(key=lambda x: (0 if x["is_mine"] else 1, x.get("distance_m") or 99999)) return result # ------------------------------------------------------------------ # POST /api/gassi-zeiten — eigene Zeit anlegen # ------------------------------------------------------------------ @router.post("", status_code=201) async def create_gassi_zeit(data: GassiZeitCreate, user=Depends(get_current_user)): if not data.wochentage: raise HTTPException(400, "Mindestens ein Wochentag muss angegeben werden.") if not data.uhrzeit: raise HTTPException(400, "Uhrzeit muss angegeben werden.") wochentage_json = json.dumps(data.wochentage) with db() as conn: # Hund-Prüfung if data.dog_id: dog = conn.execute( "SELECT id FROM dogs WHERE id=? AND user_id=?", (data.dog_id, user["id"]) ).fetchone() if not dog: raise HTTPException(403, "Hund nicht gefunden oder gehört nicht dir.") cur = conn.execute(""" INSERT INTO gassi_zeiten (user_id, dog_id, wochentage, uhrzeit, ort_name, lat, lon, radius_m, notiz) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, (user["id"], data.dog_id, wochentage_json, data.uhrzeit, data.ort_name, data.lat, data.lon, data.radius_m, data.notiz)) row = conn.execute(""" SELECT gz.*, u.name AS user_name, d.name AS dog_name, d.foto_url AS dog_foto_url FROM gassi_zeiten gz LEFT JOIN users u ON u.id = gz.user_id LEFT JOIN dogs d ON d.id = gz.dog_id WHERE gz.id = ? """, (cur.lastrowid,)).fetchone() result = dict(row) try: result["wochentage"] = json.loads(result["wochentage"]) except Exception: pass result["is_mine"] = True return result # ------------------------------------------------------------------ # PATCH /api/gassi-zeiten/{id} — pausieren / aktivieren # ------------------------------------------------------------------ @router.patch("/{gz_id}") async def update_gassi_zeit(gz_id: int, data: GassiZeitUpdate, user=Depends(get_current_user)): with db() as conn: gz = conn.execute( "SELECT * FROM gassi_zeiten WHERE id=?", (gz_id,) ).fetchone() if not gz: raise HTTPException(404, "Gassi-Zeit nicht gefunden.") if gz["user_id"] != user["id"]: raise HTTPException(403, "Nicht deine Gassi-Zeit.") updates = data.model_dump(exclude_none=True) if updates: cols = ", ".join(f"{k} = ?" for k in updates) conn.execute(f"UPDATE gassi_zeiten SET {cols} WHERE id=?", [*updates.values(), gz_id]) row = conn.execute( "SELECT * FROM gassi_zeiten WHERE id=?", (gz_id,) ).fetchone() result = dict(row) try: result["wochentage"] = json.loads(result["wochentage"]) except Exception: pass result["is_mine"] = True return result # ------------------------------------------------------------------ # DELETE /api/gassi-zeiten/{id} # ------------------------------------------------------------------ @router.delete("/{gz_id}", status_code=204) async def delete_gassi_zeit(gz_id: int, user=Depends(get_current_user)): with db() as conn: gz = conn.execute( "SELECT * FROM gassi_zeiten WHERE id=?", (gz_id,) ).fetchone() if not gz: raise HTTPException(404, "Gassi-Zeit nicht gefunden.") if gz["user_id"] != user["id"]: raise HTTPException(403, "Nicht deine Gassi-Zeit.") conn.execute("DELETE FROM gassi_zeiten WHERE id=?", (gz_id,))