"""BAN YARO — Gassi-Zeiten-Pool (regelmäßige Gassi-Zeiten mit Gleichgesinnten)""" import json 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 from math_utils import haversine_m logger = logging.getLogger(__name__) router = APIRouter() 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_m(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,))