"""BAN YARO — Hundesitting""" import json import 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() SERVICES = {'tagesbetreuung', 'uebernachtung', 'gassi', 'hausbesuch'} 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)) # ------------------------------------------------------------------ # Schemas # ------------------------------------------------------------------ class SitterCreate(BaseModel): beschreibung: Optional[str] = None preis_pro_tag: float = 0 max_hunde: int = 1 lat: Optional[float] = None lon: Optional[float] = None radius_km: int = 20 services: List[str] = [] class SitterUpdate(BaseModel): beschreibung: Optional[str] = None preis_pro_tag: Optional[float] = None max_hunde: Optional[int] = None lat: Optional[float] = None lon: Optional[float] = None radius_km: Optional[int] = None services: Optional[List[str]] = None aktiv: Optional[int] = None class RequestCreate(BaseModel): sitter_id: int dog_ids: List[int] = [] von: str # YYYY-MM-DD bis: str nachricht: Optional[str] = None class RequestUpdate(BaseModel): status: str # angenommen | abgelehnt | abgebrochen # ------------------------------------------------------------------ # GET /api/sitting — Sitter-Liste # ------------------------------------------------------------------ @router.get("") async def list_sitters( lat: Optional[float] = None, lon: Optional[float] = None, radius: int = 30000, service: Optional[str] = None, ): with db() as conn: rows = conn.execute(""" SELECT s.*, u.name AS sitter_name FROM sitters s JOIN users u ON u.id = s.user_id WHERE s.aktiv = 1 """).fetchall() result = [] for r in rows: d = dict(r) d['services'] = json.loads(d['services'] or '[]') if service and service not in d['services']: continue if lat is not None and lon is not None and d['lat'] and d['lon']: dist = _haversine(lat, lon, d['lat'], d['lon']) if dist > radius: continue d['distanz_m'] = round(dist) result.append(d) if lat is not None: result.sort(key=lambda x: x.get('distanz_m', 999999)) return result # ------------------------------------------------------------------ # GET /api/sitting/me — eigenes Sitter-Profil # ------------------------------------------------------------------ @router.get("/me") async def get_my_sitter_profile(user=Depends(get_current_user)): with db() as conn: row = conn.execute( "SELECT * FROM sitters WHERE user_id = ?", (user['id'],) ).fetchone() if not row: return None d = dict(row) d['services'] = json.loads(d['services'] or '[]') return d # ------------------------------------------------------------------ # POST /api/sitting — Sitter-Profil erstellen oder aktualisieren # ------------------------------------------------------------------ @router.post("", status_code=201) async def create_sitter(data: SitterCreate, user=Depends(get_current_user)): services_json = json.dumps([s for s in data.services if s in SERVICES]) with db() as conn: existing = conn.execute( "SELECT id FROM sitters WHERE user_id = ?", (user['id'],) ).fetchone() if existing: raise HTTPException(409, "Du hast bereits ein Sitter-Profil. Nutze PATCH zum Aktualisieren.") cur = conn.execute(""" INSERT INTO sitters (user_id, beschreibung, preis_pro_tag, max_hunde, lat, lon, radius_km, services) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, (user['id'], data.beschreibung, data.preis_pro_tag, data.max_hunde, data.lat, data.lon, data.radius_km, services_json)) row = conn.execute("SELECT * FROM sitters WHERE id = ?", (cur.lastrowid,)).fetchone() d = dict(row) d['services'] = json.loads(d['services'] or '[]') return d # ------------------------------------------------------------------ # PATCH /api/sitting/me — eigenes Profil bearbeiten # ------------------------------------------------------------------ @router.patch("/me") async def update_sitter(data: SitterUpdate, user=Depends(get_current_user)): with db() as conn: sitter = conn.execute( "SELECT * FROM sitters WHERE user_id = ?", (user['id'],) ).fetchone() if not sitter: raise HTTPException(404, "Kein Sitter-Profil gefunden.") updates = data.model_dump(exclude_none=True) if 'services' in updates: updates['services'] = json.dumps([s for s in updates['services'] if s in SERVICES]) if updates: cols = ', '.join(f"{k} = ?" for k in updates) conn.execute(f"UPDATE sitters SET {cols} WHERE user_id = ?", [*updates.values(), user['id']]) row = conn.execute("SELECT * FROM sitters WHERE user_id = ?", (user['id'],)).fetchone() d = dict(row) d['services'] = json.loads(d['services'] or '[]') return d # ------------------------------------------------------------------ # GET /api/sitting/requests — meine Anfragen (als Anfragender) # ------------------------------------------------------------------ @router.get("/requests") async def list_my_requests(user=Depends(get_current_user)): with db() as conn: rows = conn.execute(""" SELECT sr.*, u.name AS sitter_name FROM sitting_requests sr JOIN sitters s ON s.id = sr.sitter_id JOIN users u ON u.id = s.user_id WHERE sr.user_id = ? ORDER BY sr.created_at DESC """, (user['id'],)).fetchall() result = [] for r in rows: d = dict(r) d['dog_ids'] = json.loads(d['dog_ids'] or '[]') result.append(d) return result # ------------------------------------------------------------------ # GET /api/sitting/inbox — eingehende Anfragen (als Sitter) # ------------------------------------------------------------------ @router.get("/inbox") async def sitter_inbox(user=Depends(get_current_user)): with db() as conn: sitter = conn.execute( "SELECT id FROM sitters WHERE user_id = ?", (user['id'],) ).fetchone() if not sitter: return [] rows = conn.execute(""" SELECT sr.*, u.name AS anfragender_name FROM sitting_requests sr JOIN users u ON u.id = sr.user_id WHERE sr.sitter_id = ? ORDER BY sr.created_at DESC """, (sitter['id'],)).fetchall() result = [] for r in rows: d = dict(r) d['dog_ids'] = json.loads(d['dog_ids'] or '[]') result.append(d) return result # ------------------------------------------------------------------ # POST /api/sitting/requests — Anfrage senden # ------------------------------------------------------------------ @router.post("/requests", status_code=201) async def create_request(data: RequestCreate, user=Depends(get_current_user)): with db() as conn: sitter = conn.execute( "SELECT * FROM sitters WHERE id = ?", (data.sitter_id,) ).fetchone() if not sitter or not sitter['aktiv']: raise HTTPException(404, "Sitter nicht gefunden oder nicht aktiv.") if sitter['user_id'] == user['id']: raise HTTPException(400, "Du kannst keine Anfrage an dich selbst schicken.") cur = conn.execute(""" INSERT INTO sitting_requests (user_id, sitter_id, dog_ids, von, bis, nachricht) VALUES (?, ?, ?, ?, ?, ?) """, (user['id'], data.sitter_id, json.dumps(data.dog_ids), data.von, data.bis, data.nachricht)) row = conn.execute("SELECT * FROM sitting_requests WHERE id = ?", (cur.lastrowid,)).fetchone() d = dict(row) d['dog_ids'] = json.loads(d['dog_ids'] or '[]') return d # ------------------------------------------------------------------ # PATCH /api/sitting/requests/{id} — Status ändern # ------------------------------------------------------------------ @router.patch("/requests/{req_id}") async def update_request(req_id: int, data: RequestUpdate, user=Depends(get_current_user)): allowed = {'angenommen', 'abgelehnt', 'abgebrochen'} if data.status not in allowed: raise HTTPException(400, f"Status muss einer von {allowed} sein.") with db() as conn: req = conn.execute("SELECT * FROM sitting_requests WHERE id = ?", (req_id,)).fetchone() if not req: raise HTTPException(404, "Anfrage nicht gefunden.") # Anfragender kann nur abbrechen; Sitter kann annehmen/ablehnen sitter = conn.execute( "SELECT * FROM sitters WHERE id = ?", (req['sitter_id'],) ).fetchone() is_requester = req['user_id'] == user['id'] is_sitter = sitter['user_id'] == user['id'] if not is_requester and not is_sitter: raise HTTPException(403, "Kein Zugriff.") if is_requester and data.status != 'abgebrochen': raise HTTPException(403, "Du kannst die Anfrage nur abbrechen.") if is_sitter and data.status == 'abgebrochen': raise HTTPException(403, "Als Sitter kannst du nur annehmen oder ablehnen.") conn.execute( "UPDATE sitting_requests SET status = ? WHERE id = ?", (data.status, req_id) ) row = conn.execute("SELECT * FROM sitting_requests WHERE id = ?", (req_id,)).fetchone() d = dict(row) d['dog_ids'] = json.loads(d['dog_ids'] or '[]') return d