"""BAN YARO — Giftköder-Alarm Routes""" import os, uuid, math from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from pydantic import BaseModel from typing import Optional from database import db from auth import get_current_user router = APIRouter() MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media") # ------------------------------------------------------------------ # Haversine-Distanz in Metern # ------------------------------------------------------------------ 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 PoisonCreate(BaseModel): lat: float lon: float beschreibung: Optional[str] = None typ: str = "unbekannt" # ------------------------------------------------------------------ # GET /api/poison — aktive Meldungen im Umkreis (kein Login nötig) # ------------------------------------------------------------------ @router.get("") async def list_poison(lat: float, lon: float, radius: int = 5000): now = datetime.utcnow().isoformat() with db() as conn: rows = conn.execute( """SELECT p.*, u.name AS melder_name FROM poison p LEFT JOIN users u ON u.id = p.user_id WHERE p.geloest = 0 AND p.expires_at > ? ORDER BY p.created_at DESC""", (now,) ).fetchall() results = [] for r in rows: entry = dict(r) dist = _haversine(lat, lon, entry["lat"], entry["lon"]) if dist <= radius: entry["distanz_m"] = round(dist) results.append(entry) results.sort(key=lambda x: x["distanz_m"]) return results # ------------------------------------------------------------------ # POST /api/poison — neue Meldung (Login erforderlich) # ------------------------------------------------------------------ @router.post("", status_code=201) async def report_poison(data: PoisonCreate, user=Depends(get_current_user)): expires_at = (datetime.utcnow() + timedelta(days=7)).isoformat() with db() as conn: conn.execute( """INSERT INTO poison (user_id, lat, lon, beschreibung, typ, expires_at) VALUES (?, ?, ?, ?, ?, ?)""", (user["id"], data.lat, data.lon, data.beschreibung, data.typ, expires_at) ) row = conn.execute( "SELECT * FROM poison WHERE user_id=? ORDER BY id DESC LIMIT 1", (user["id"],) ).fetchone() return dict(row) # ------------------------------------------------------------------ # POST /api/poison/{id}/confirm — Community-Bestätigung # Verlängert die Laufzeit um 7 weitere Tage ab jetzt # ------------------------------------------------------------------ @router.post("/{poison_id}/confirm") async def confirm_poison(poison_id: int, user=Depends(get_current_user)): with db() as conn: entry = conn.execute( "SELECT * FROM poison WHERE id=?", (poison_id,) ).fetchone() if not entry: raise HTTPException(404, "Meldung nicht gefunden.") if dict(entry)["user_id"] == user["id"]: raise HTTPException(400, "Eigene Meldung kann nicht bestätigt werden.") new_expires = (datetime.utcnow() + timedelta(days=7)).isoformat() conn.execute( """UPDATE poison SET bestaetigt=1, bestaetigt_von=?, expires_at=? WHERE id=?""", (user["id"], new_expires, poison_id) ) row = conn.execute("SELECT * FROM poison WHERE id=?", (poison_id,)).fetchone() return dict(row) # ------------------------------------------------------------------ # POST /api/poison/{id}/resolve — als erledigt markieren # Nur der Melder selbst oder ein Admin # ------------------------------------------------------------------ @router.post("/{poison_id}/resolve") async def resolve_poison(poison_id: int, user=Depends(get_current_user)): with db() as conn: entry = conn.execute( "SELECT * FROM poison WHERE id=?", (poison_id,) ).fetchone() if not entry: raise HTTPException(404, "Meldung nicht gefunden.") e = dict(entry) if e["user_id"] != user["id"] and user.get("rolle") != "admin": raise HTTPException(403, "Keine Berechtigung.") conn.execute("UPDATE poison SET geloest=1 WHERE id=?", (poison_id,)) return {"ok": True} # ------------------------------------------------------------------ # POST /api/poison/{id}/photo — Foto nachträglich anhängen # Nur vom Melder selbst # ------------------------------------------------------------------ @router.post("/{poison_id}/photo") async def upload_photo( poison_id: int, file: UploadFile = File(...), user=Depends(get_current_user), ): with db() as conn: entry = conn.execute( "SELECT id FROM poison WHERE id=? AND user_id=?", (poison_id, user["id"]) ).fetchone() if not entry: raise HTTPException(404, "Meldung nicht gefunden oder keine Berechtigung.") ext = os.path.splitext(file.filename or "")[1] or ".jpg" filename = f"poison_{poison_id}_{uuid.uuid4().hex[:8]}{ext}" path = os.path.join(MEDIA_DIR, "poison", filename) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "wb") as f: f.write(await file.read()) foto_url = f"/media/poison/{filename}" with db() as conn: conn.execute("UPDATE poison SET foto_url=? WHERE id=?", (foto_url, poison_id)) return {"foto_url": foto_url}