Features: - Admin: Referral-Tab (Virality Factor, Top-Werber, letzte Einladungen) - Karte: Regenradar (RainViewer, zoom→7, color=4), Temperatur-Layer (OWM) mit Zahlen-Grid + Legende - Wetter-Chip: Umschwung-Warnung bei ≥40%-Sprung in Niederschlagswahrscheinlichkeit - Freundschaftsanfragen: Accept/Decline direkt in Notifications (kein Pro nötig) - Freunde-Seite für Standard-User freigeschaltet Pro-Gates: - KI-Trainer, Routenvorschläge, Regenradar, Temperatur-Layer jetzt Pro-Feature - Pro-Badge (P) auf Chips für Admins/Mods in allen Welten + Welten-einrichten - Oranger Banner auf Pro-Seiten für Admin/Mod/Manager Bugfixes: - onDogChange: uebungen.js (Cache leeren + _render), trainingsplaene.js (war leer) - robots.txt vereinfacht (nur Disallow, kein Allow-Durcheinander) - Hintergrund-Foto: Querformat-Filter korrigiert (kein Fallback auf Hochformat) - Staging Media: FileResponse mit korrektem MIME-Type, no-cache statt immutable - Staging Docker: MEDIA_DIR=/data/media + /prod-media:ro Fallback-Handler - Staging-Fix: Bild-Upload auf zweitem Hund (war Read-only file system)
119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
"""
|
|
BAN YARO — Wetter-API
|
|
GET /api/weather?lat=&lon= → aktuelles Wetter + Zecken-Warnung für Nutzerstandort
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
from fastapi import APIRouter, Query, HTTPException, Depends
|
|
import weather as weather_module
|
|
from auth import get_current_user
|
|
from database import db
|
|
|
|
router = APIRouter()
|
|
|
|
OWM_API_KEY = os.getenv("OPENWEATHERMAP_KEY", "")
|
|
|
|
|
|
ALLOWED_OWM_LAYERS = {"temp_new", "clouds_new", "wind_new", "pressure_new", "precipitation_new"}
|
|
|
|
|
|
@router.get('/radar-tiles')
|
|
async def radar_tile_config(user=Depends(get_current_user)):
|
|
"""Regenradar-Tile-Config (RainViewer)."""
|
|
return {"provider": "rainviewer"}
|
|
|
|
|
|
@router.get('/layer-tiles')
|
|
async def layer_tile_config(
|
|
layer: str = "temp_new",
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""OWM-Tile-URL für Wetter-Layer (Key bleibt server-seitig)."""
|
|
if layer not in ALLOWED_OWM_LAYERS:
|
|
raise HTTPException(400, f"Unbekannter Layer. Erlaubt: {', '.join(ALLOWED_OWM_LAYERS)}")
|
|
if not OWM_API_KEY:
|
|
raise HTTPException(503, "OWM nicht konfiguriert.")
|
|
return {
|
|
"url": f"https://tile.openweathermap.org/map/{layer}/{{z}}/{{x}}/{{y}}.png?appid={OWM_API_KEY}",
|
|
"maxNativeZoom": 18,
|
|
"opacity": 0.6,
|
|
}
|
|
|
|
|
|
@router.get('')
|
|
async def get_weather(
|
|
lat: float = Query(..., ge=-90, le=90),
|
|
lon: float = Query(..., ge=-180, le=180),
|
|
):
|
|
try:
|
|
return await weather_module.get_weather_for_location(lat, lon)
|
|
except Exception as exc:
|
|
raise HTTPException(503, f'Wetter nicht verfügbar: {exc}')
|
|
|
|
|
|
@router.get('/forecast')
|
|
async def get_weather_forecast(
|
|
lat: float = Query(..., ge=-90, le=90),
|
|
lon: float = Query(..., ge=-180, le=180),
|
|
user=Depends(get_current_user),
|
|
):
|
|
try:
|
|
return await weather_module.get_forecast(lat, lon)
|
|
except Exception as exc:
|
|
raise HTTPException(503, f'Wettervorhersage nicht verfügbar: {exc}')
|
|
|
|
|
|
@router.get('/records')
|
|
async def weather_records(user=Depends(get_current_user)):
|
|
"""Persönliche Wetterrekorde aus diary-Einträgen mit weather_json."""
|
|
uid = user["id"]
|
|
with db() as conn:
|
|
rows = conn.execute("""
|
|
SELECT d.datum, d.weather_json, d.titel
|
|
FROM diary d
|
|
JOIN dogs dog ON dog.id = d.dog_id
|
|
WHERE dog.user_id = ? AND d.weather_json IS NOT NULL
|
|
ORDER BY d.datum ASC
|
|
""", (uid,)).fetchall()
|
|
|
|
if not rows:
|
|
return {"records": None}
|
|
|
|
entries = []
|
|
for r in rows:
|
|
try:
|
|
w = json.loads(r["weather_json"])
|
|
entries.append({
|
|
"datum": r["datum"],
|
|
"titel": r["titel"],
|
|
"temp_c": w.get("temp_c"),
|
|
"wind_kmh": w.get("wind_kmh"),
|
|
"precip_prob": w.get("precip_prob"),
|
|
"desc": w.get("desc", ""),
|
|
"weathercode": w.get("weathercode"),
|
|
})
|
|
except Exception:
|
|
pass
|
|
|
|
if not entries:
|
|
return {"records": None}
|
|
|
|
temps = [e for e in entries if e["temp_c"] is not None]
|
|
winds = [e for e in entries if e["wind_kmh"] is not None]
|
|
|
|
records = {}
|
|
if temps:
|
|
kaeltester = min(temps, key=lambda e: e["temp_c"])
|
|
heissester = max(temps, key=lambda e: e["temp_c"])
|
|
records["kaeltester"] = kaeltester
|
|
records["heissester"] = heissester
|
|
if winds:
|
|
stuermischster = max(winds, key=lambda e: e["wind_kmh"])
|
|
records["stuermischster"] = stuermischster
|
|
|
|
regen_count = sum(1 for e in entries if (e.get("precip_prob") or 0) > 60)
|
|
records["regen_eintraege"] = regen_count
|
|
records["gesamt_eintraege"] = len(entries)
|
|
|
|
return {"records": records}
|