Push geo-filter: Giftköder-Alert nur im 30km-Radius, Standort via Alerts-Check gespeichert

This commit is contained in:
rene 2026-04-24 09:35:55 +02:00
parent 9213b58d3c
commit b5e4eab84d
4 changed files with 44 additions and 5 deletions

View file

@ -1009,3 +1009,11 @@ def _migrate(conn_factory):
CREATE INDEX IF NOT EXISTS idx_wp_dog_week ON weekly_praise(dog_id, week_key DESC); CREATE INDEX IF NOT EXISTS idx_wp_dog_week ON weekly_praise(dog_id, week_key DESC);
""") """)
logger.info("Migration: weekly_praise Tabelle bereit.") logger.info("Migration: weekly_praise Tabelle bereit.")
# Push: Standort-Filter (last_lat/lon für geo-basierte Alerts)
for col in ["last_lat REAL", "last_lon REAL"]:
try:
conn.execute(f"ALTER TABLE push_subscriptions ADD COLUMN {col}")
except Exception:
pass
logger.info("Migration: push_subscriptions last_lat/lon bereit.")

View file

@ -1,9 +1,10 @@
"""BAN YARO — Nearby Alerts (Giftköder + Vermisste Hunde)""" """BAN YARO — Nearby Alerts (Giftköder + Vermisste Hunde)"""
import math import math
from datetime import datetime from datetime import datetime
from fastapi import APIRouter from fastapi import APIRouter, Depends
from database import db from database import db
from auth import get_current_user_optional as get_optional_user
router = APIRouter() router = APIRouter()
@ -20,7 +21,7 @@ def _haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
@router.get("") @router.get("")
async def nearby_alerts(lat: float, lon: float): async def nearby_alerts(lat: float, lon: float, user=Depends(get_optional_user)):
now = datetime.utcnow().isoformat() now = datetime.utcnow().isoformat()
with db() as conn: with db() as conn:
poisons = conn.execute( poisons = conn.execute(
@ -29,6 +30,13 @@ async def nearby_alerts(lat: float, lon: float):
lost = conn.execute( lost = conn.execute(
"SELECT lat, lon FROM lost_dogs WHERE is_active=1" "SELECT lat, lon FROM lost_dogs WHERE is_active=1"
).fetchall() ).fetchall()
# Letzten Standort des Users für geo-basierte Push-Filter speichern
if user:
conn.execute(
"""UPDATE push_subscriptions SET last_lat=?, last_lon=?
WHERE user_id=?""",
(lat, lon, user["id"])
)
has_poison = any(_haversine(lat, lon, r["lat"], r["lon"]) <= _RADIUS_M for r in poisons) has_poison = any(_haversine(lat, lon, r["lat"], r["lon"]) <= _RADIUS_M for r in poisons)
has_lost = any(_haversine(lat, lon, r["lat"], r["lon"]) <= _RADIUS_M for r in lost) has_lost = any(_haversine(lat, lon, r["lat"], r["lon"]) <= _RADIUS_M for r in lost)

View file

@ -7,7 +7,7 @@ from pydantic import BaseModel
from typing import Optional from typing import Optional
from database import db from database import db
from auth import get_current_user from auth import get_current_user
from routes.push import send_push_to_all from routes.push import send_push_nearby
from media_utils import convert_media from media_utils import convert_media
from ratelimit import check as rl_check from ratelimit import check as rl_check
@ -91,8 +91,8 @@ async def report_poison(data: PoisonCreate, request: Request,
).fetchone() ).fetchone()
entry = dict(row) entry = dict(row)
# Push-Notification an alle User # Push nur an User im Umkreis von 30 km
send_push_to_all({ send_push_nearby(data.lat, data.lon, 30_000, {
"type": "poison_alert", "type": "poison_alert",
"title": "⚠️ Giftköder gemeldet!", "title": "⚠️ Giftköder gemeldet!",
"body": f"{data.typ or 'Verdächtiger Fund'} in deiner Nähe — bitte vorsichtig sein.", "body": f"{data.typ or 'Verdächtiger Fund'} in deiner Nähe — bitte vorsichtig sein.",

View file

@ -140,3 +140,26 @@ def send_push_to_all(payload: dict):
sent += 1 sent += 1
logger.info(f"Push an {sent}/{len(rows)} Subscriptions gesendet.") logger.info(f"Push an {sent}/{len(rows)} Subscriptions gesendet.")
return sent return sent
def send_push_nearby(lat: float, lon: float, radius_m: float, payload: dict):
"""Schickt Push nur an User deren letzter bekannter Standort innerhalb radius_m liegt.
User ohne gespeicherten Standort werden übersprungen."""
import math
def _dist(la1, lo1, la2, lo2):
R = 6_371_000
p1, p2 = math.radians(la1), math.radians(la2)
a = math.sin(math.radians(la2-la1)/2)**2 + math.cos(p1)*math.cos(p2)*math.sin(math.radians(lo2-lo1)/2)**2
return 2*R*math.asin(math.sqrt(a))
with db() as conn:
rows = conn.execute(
"SELECT * FROM push_subscriptions WHERE last_lat IS NOT NULL"
).fetchall()
sent = 0
for row in rows:
if _dist(lat, lon, row["last_lat"], row["last_lon"]) <= radius_m:
if send_push(row, payload):
sent += 1
logger.info(f"Push nearby ({radius_m/1000:.0f}km): {sent}/{len(rows)} gesendet.")
return sent