"""BAN YARO — Events (Hundeveranstaltungen)""" from datetime import date from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from typing import Optional from database import db from auth import get_current_user from math_utils import haversine_m router = APIRouter() TYPEN = {'ausstellung', 'training', 'treffen', 'markt', 'wettkampf', 'sonstiges'} # ------------------------------------------------------------------ # Schemas # ------------------------------------------------------------------ class RsvpCreate(BaseModel): status: str = Field('going', max_length=20) # 'going' | 'maybe' class EventCreate(BaseModel): titel: str = Field(..., min_length=3, max_length=200) datum: str = Field(..., max_length=32) # YYYY-MM-DD uhrzeit: Optional[str] = Field(None, max_length=20) lat: Optional[float] = None lon: Optional[float] = None ort_name: Optional[str] = Field(None, max_length=300) typ: str = Field('sonstiges', max_length=50) beschreibung: Optional[str] = Field(None, max_length=10000) link: Optional[str] = Field(None, max_length=500) class EventUpdate(BaseModel): titel: Optional[str] = Field(None, max_length=200) datum: Optional[str] = Field(None, max_length=32) uhrzeit: Optional[str] = Field(None, max_length=20) lat: Optional[float] = None lon: Optional[float] = None ort_name: Optional[str] = Field(None, max_length=300) typ: Optional[str] = Field(None, max_length=50) beschreibung: Optional[str] = Field(None, max_length=10000) link: Optional[str] = Field(None, max_length=500) # ------------------------------------------------------------------ # GET /api/events # ------------------------------------------------------------------ @router.get("") async def list_events( lat: Optional[float] = None, lon: Optional[float] = None, radius: int = 50000, typ: Optional[str] = None, alle: bool = False, quelle: Optional[str] = None, ): today = date.today().isoformat() with db() as conn: q = """ SELECT e.*, CASE WHEN e.user_id = 0 THEN 'VDH' ELSE u.name END AS veranstalter_name, e.quelle, (SELECT COUNT(*) FROM event_rsvp r WHERE r.event_id = e.id AND r.status = 'going') AS rsvp_count FROM events e LEFT JOIN users u ON u.id = e.user_id AND e.user_id != 0 WHERE e.status = 'aktiv' """ if not alle: q += f" AND e.datum >= '{today}'" if typ and typ in TYPEN: q += f" AND e.typ = '{typ}'" if quelle: q += f" AND e.quelle = '{quelle}'" q += " ORDER BY e.datum ASC, e.uhrzeit ASC" rows = conn.execute(q).fetchall() result = [dict(r) for r in rows] if lat is not None and lon is not None: result = [r for r in result if r['lat'] is None or haversine_m(lat, lon, r['lat'], r['lon']) <= radius] return result # ------------------------------------------------------------------ # POST /api/events # ------------------------------------------------------------------ @router.post("", status_code=201) async def create_event(data: EventCreate, user=Depends(get_current_user)): if data.typ not in TYPEN: raise HTTPException(400, f"Ungültiger Typ. Erlaubt: {', '.join(TYPEN)}") with db() as conn: cur = conn.execute(""" INSERT INTO events (user_id, titel, datum, uhrzeit, lat, lon, ort_name, typ, beschreibung, link, quelle) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'nutzer') """, (user['id'], data.titel, data.datum, data.uhrzeit, data.lat, data.lon, data.ort_name, data.typ, data.beschreibung, data.link)) row = conn.execute( "SELECT e.*, CASE WHEN e.user_id = 0 THEN 'VDH' ELSE u.name END AS veranstalter_name, e.quelle, " "0 AS rsvp_count " "FROM events e LEFT JOIN users u ON u.id = e.user_id AND e.user_id != 0 WHERE e.id = ?", (cur.lastrowid,) ).fetchone() return dict(row) # ------------------------------------------------------------------ # GET /api/events/{id} # ------------------------------------------------------------------ @router.get("/{event_id}") async def get_event(event_id: int): with db() as conn: row = conn.execute( "SELECT e.*, CASE WHEN e.user_id = 0 THEN 'VDH' ELSE u.name END AS veranstalter_name, e.quelle, " "(SELECT COUNT(*) FROM event_rsvp r WHERE r.event_id = e.id AND r.status = 'going') AS rsvp_count " "FROM events e LEFT JOIN users u ON u.id = e.user_id AND e.user_id != 0 WHERE e.id = ?", (event_id,) ).fetchone() if not row: raise HTTPException(404, "Event nicht gefunden.") return dict(row) # ------------------------------------------------------------------ # PATCH /api/events/{id} # ------------------------------------------------------------------ @router.patch("/{event_id}") async def update_event(event_id: int, data: EventUpdate, user=Depends(get_current_user)): with db() as conn: ev = conn.execute("SELECT * FROM events WHERE id = ?", (event_id,)).fetchone() if not ev: raise HTTPException(404, "Event nicht gefunden.") if ev['user_id'] == 0 or ev['user_id'] != user['id']: raise HTTPException(403, "Nur der Veranstalter kann das Event bearbeiten.") updates = data.model_dump(exclude_none=True) if updates: if 'typ' in updates and updates['typ'] not in TYPEN: raise HTTPException(400, "Ungültiger Typ.") cols = ', '.join(f"{k} = ?" for k in updates) conn.execute(f"UPDATE events SET {cols} WHERE id = ?", [*updates.values(), event_id]) row = conn.execute( "SELECT e.*, CASE WHEN e.user_id = 0 THEN 'VDH' ELSE u.name END AS veranstalter_name, e.quelle, " "(SELECT COUNT(*) FROM event_rsvp r WHERE r.event_id = e.id AND r.status = 'going') AS rsvp_count " "FROM events e LEFT JOIN users u ON u.id = e.user_id AND e.user_id != 0 WHERE e.id = ?", (event_id,) ).fetchone() return dict(row) # ------------------------------------------------------------------ # DELETE /api/events/{id} # ------------------------------------------------------------------ @router.delete("/{event_id}", status_code=204) async def delete_event(event_id: int, user=Depends(get_current_user)): with db() as conn: ev = conn.execute("SELECT * FROM events WHERE id = ?", (event_id,)).fetchone() if not ev: raise HTTPException(404, "Event nicht gefunden.") if ev['user_id'] == 0 or ev['user_id'] != user['id']: raise HTTPException(403, "Nur der Veranstalter kann das Event löschen.") conn.execute("UPDATE events SET status = 'geloescht' WHERE id = ?", (event_id,)) # ------------------------------------------------------------------ # POST /api/events/{id}/rsvp # ------------------------------------------------------------------ @router.post("/{event_id}/rsvp", status_code=201) async def rsvp_event(event_id: int, data: RsvpCreate, user=Depends(get_current_user)): if data.status not in ('going', 'maybe'): raise HTTPException(400, "Status muss 'going' oder 'maybe' sein.") with db() as conn: ev = conn.execute("SELECT id FROM events WHERE id = ? AND status = 'aktiv'", (event_id,)).fetchone() if not ev: raise HTTPException(404, "Event nicht gefunden.") conn.execute( "INSERT OR REPLACE INTO event_rsvp (event_id, user_id, status) VALUES (?, ?, ?)", (event_id, user['id'], data.status) ) count = conn.execute( "SELECT COUNT(*) FROM event_rsvp WHERE event_id = ? AND status = 'going'", (event_id,) ).fetchone()[0] return {"event_id": event_id, "status": data.status, "rsvp_count": count} # ------------------------------------------------------------------ # DELETE /api/events/{id}/rsvp # ------------------------------------------------------------------ @router.delete("/{event_id}/rsvp", status_code=204) async def cancel_rsvp(event_id: int, user=Depends(get_current_user)): with db() as conn: conn.execute( "DELETE FROM event_rsvp WHERE event_id = ? AND user_id = ?", (event_id, user['id']) ) # ------------------------------------------------------------------ # GET /api/events/{id}/rsvp # ------------------------------------------------------------------ @router.get("/{event_id}/rsvp") async def list_rsvp(event_id: int): with db() as conn: rows = conn.execute( "SELECT r.user_id, u.name, r.status " "FROM event_rsvp r " "JOIN users u ON u.id = r.user_id " "WHERE r.event_id = ? " "ORDER BY r.created_at ASC", (event_id,) ).fetchall() return [dict(r) for r in rows]