From 80113eb0b9065c398c3190787bfcce7e35092cf2 Mon Sep 17 00:00:00 2001 From: rene Date: Fri, 17 Apr 2026 23:54:49 +0200 Subject: [PATCH] Fix: services.py fehlte nach Worktree-Merge --- backend/routes/services.py | 146 +++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 backend/routes/services.py diff --git a/backend/routes/services.py b/backend/routes/services.py new file mode 100644 index 0000000..0696f5f --- /dev/null +++ b/backend/routes/services.py @@ -0,0 +1,146 @@ +"""BAN YARO — Service-Angebote (Sitting & Walks Matching)""" + +import math +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel +from typing import Optional +from database import db +from auth import get_current_user + +router = APIRouter() + +ALLOWED_TYPES = {'sitting', 'walks'} + + +def _haversine(lat1, lon1, lat2, lon2): + R = 6371.0 + dlat = math.radians(lat2 - lat1) + dlon = math.radians(lon2 - lon1) + a = (math.sin(dlat / 2) ** 2 + + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) + * math.sin(dlon / 2) ** 2) + return R * 2 * math.asin(math.sqrt(a)) + + +# ------------------------------------------------------------------ +# Schemas +# ------------------------------------------------------------------ +class ServiceCreate(BaseModel): + type: str + beschreibung: Optional[str] = None + preis_pro_tag: Optional[float] = None + lat: Optional[float] = None + lon: Optional[float] = None + radius_km: int = 10 + + +# ------------------------------------------------------------------ +# GET /api/services — Angebote in der Nähe +# ------------------------------------------------------------------ +@router.get("") +async def list_services( + type: str, + lat: Optional[float] = None, + lon: Optional[float] = None, + radius: float = 20, # km +): + if type not in ALLOWED_TYPES: + raise HTTPException(400, f"type muss 'sitting' oder 'walks' sein.") + + with db() as conn: + rows = conn.execute(""" + SELECT so.*, u.name AS anbieter_name + FROM service_offers so + JOIN users u ON u.id = so.user_id + WHERE so.type = ? AND so.aktiv = 1 + LIMIT 200 + """, (type,)).fetchall() + + result = [] + for r in rows: + d = dict(r) + 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_km'] = round(dist, 1) + else: + d['distanz_km'] = None + result.append(d) + + if lat is not None: + result.sort(key=lambda x: x.get('distanz_km') or 9999) + + return result[:50] + + +# ------------------------------------------------------------------ +# GET /api/services/me — eigene Angebote +# ------------------------------------------------------------------ +@router.get("/me") +async def my_services(user=Depends(get_current_user)): + with db() as conn: + rows = conn.execute( + "SELECT * FROM service_offers WHERE user_id = ? ORDER BY type", + (user['id'],) + ).fetchall() + return [dict(r) for r in rows] + + +# ------------------------------------------------------------------ +# POST /api/services — Angebot erstellen oder aktualisieren (Upsert) +# ------------------------------------------------------------------ +@router.post("", status_code=201) +async def upsert_service(data: ServiceCreate, user=Depends(get_current_user)): + if data.type not in ALLOWED_TYPES: + raise HTTPException(400, "type muss 'sitting' oder 'walks' sein.") + + with db() as conn: + existing = conn.execute( + "SELECT id FROM service_offers WHERE user_id = ? AND type = ?", + (user['id'], data.type) + ).fetchone() + + if existing: + conn.execute(""" + UPDATE service_offers + SET beschreibung = ?, preis_pro_tag = ?, lat = ?, lon = ?, + radius_km = ?, aktiv = 1 + WHERE user_id = ? AND type = ? + """, (data.beschreibung, data.preis_pro_tag, data.lat, data.lon, + data.radius_km, user['id'], data.type)) + row = conn.execute( + "SELECT * FROM service_offers WHERE user_id = ? AND type = ?", + (user['id'], data.type) + ).fetchone() + else: + cur = conn.execute(""" + INSERT INTO service_offers + (user_id, type, beschreibung, preis_pro_tag, lat, lon, radius_km) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, (user['id'], data.type, data.beschreibung, data.preis_pro_tag, + data.lat, data.lon, data.radius_km)) + row = conn.execute( + "SELECT * FROM service_offers WHERE id = ?", (cur.lastrowid,) + ).fetchone() + + return dict(row) + + +# ------------------------------------------------------------------ +# DELETE /api/services/{id} — Angebot deaktivieren +# ------------------------------------------------------------------ +@router.delete("/{offer_id}") +async def deactivate_service(offer_id: int, user=Depends(get_current_user)): + with db() as conn: + offer = conn.execute( + "SELECT * FROM service_offers WHERE id = ?", (offer_id,) + ).fetchone() + if not offer: + raise HTTPException(404, "Angebot nicht gefunden.") + if offer['user_id'] != user['id']: + raise HTTPException(403, "Kein Zugriff.") + conn.execute( + "UPDATE service_offers SET aktiv = 0 WHERE id = ?", (offer_id,) + ) + return {"ok": True}