Generischen Lagebild-API-Endpunkt hinzufügen
Shared-Logik extrahiert (_build_lagebild_response, _get_snapshot_response).
Neue Endpunkte:
- GET /api/public/lagebild/{incident_id} für beliebige öffentliche Lagen
- GET /api/public/lagebild/{incident_id}/snapshot/{snapshot_id}
Bestehende Iran-Endpunkte bleiben abwärtskompatibel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -1,7 +1,7 @@
|
||||
"""Öffentliche API für die Lagebild-Seite auf aegissight.de.
|
||||
|
||||
Authentifizierung via X-API-Key Header (getrennt von der JWT-Auth).
|
||||
Exponiert den Irankonflikt (alle zugehörigen Incidents) als read-only.
|
||||
Exponiert öffentliche Lagen als read-only.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
@@ -50,14 +50,23 @@ def _in_clause(ids):
|
||||
return ",".join(str(int(i)) for i in ids)
|
||||
|
||||
|
||||
@router.get("/lagebild", dependencies=[Depends(verify_api_key)])
|
||||
async def get_lagebild(db=Depends(db_dependency)):
|
||||
"""Liefert das aktuelle Lagebild (Irankonflikt) mit allen Daten."""
|
||||
ids = _in_clause(IRAN_INCIDENT_IDS)
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
# Shared-Logik für Lagebild-Responses
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
|
||||
async def _build_lagebild_response(db, incident_ids: list, primary_id: int) -> dict:
|
||||
"""Baut die Lagebild-Response für beliebige Incidents.
|
||||
|
||||
Args:
|
||||
db: Datenbankverbindung
|
||||
incident_ids: Liste der Incident-IDs (für Iran: [6,18,19,20], sonst: [55])
|
||||
primary_id: ID des Haupt-Incidents für Metadaten
|
||||
"""
|
||||
ids = _in_clause(incident_ids)
|
||||
|
||||
# Haupt-Incident laden (für Summary, Sources)
|
||||
cursor = await db.execute(
|
||||
"SELECT * FROM incidents WHERE id = ?", (PRIMARY_INCIDENT_ID,)
|
||||
"SELECT * FROM incidents WHERE id = ?", (primary_id,)
|
||||
)
|
||||
incident = await cursor.fetchone()
|
||||
if not incident:
|
||||
@@ -72,7 +81,7 @@ async def get_lagebild(db=Depends(db_dependency)):
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
# Alle Artikel aus allen Iran-Incidents laden
|
||||
# Alle Artikel laden
|
||||
cursor = await db.execute(
|
||||
f"""SELECT id, headline, headline_de, source, source_url, language,
|
||||
published_at, collected_at, verification_status, incident_id
|
||||
@@ -81,7 +90,7 @@ async def get_lagebild(db=Depends(db_dependency)):
|
||||
)
|
||||
articles = [dict(r) for r in await cursor.fetchall()]
|
||||
|
||||
# Alle Faktenchecks aus allen Iran-Incidents laden
|
||||
# Alle Faktenchecks laden
|
||||
cursor = await db.execute(
|
||||
f"""SELECT id, claim, status, sources_count, evidence, status_history, checked_at, incident_id
|
||||
FROM fact_checks WHERE incident_id IN ({ids})
|
||||
@@ -102,7 +111,7 @@ async def get_lagebild(db=Depends(db_dependency)):
|
||||
)
|
||||
source_count = (await cursor.fetchone())["cnt"]
|
||||
|
||||
# Snapshots aus allen Iran-Incidents
|
||||
# Snapshots
|
||||
cursor = await db.execute(
|
||||
f"""SELECT id, incident_id, article_count, fact_check_count, created_at
|
||||
FROM incident_snapshots WHERE incident_id IN ({ids})
|
||||
@@ -160,8 +169,39 @@ async def get_lagebild(db=Depends(db_dependency)):
|
||||
}
|
||||
|
||||
|
||||
async def _get_snapshot_response(db, snapshot_id: int, incident_ids: list) -> dict:
|
||||
"""Liefert einen historischen Snapshot für die angegebenen Incidents."""
|
||||
ids = _in_clause(incident_ids)
|
||||
cursor = await db.execute(
|
||||
f"""SELECT id, summary, sources_json, article_count, fact_check_count, created_at
|
||||
FROM incident_snapshots
|
||||
WHERE id = ? AND incident_id IN ({ids})""",
|
||||
(snapshot_id,),
|
||||
)
|
||||
snap = await cursor.fetchone()
|
||||
if not snap:
|
||||
raise HTTPException(status_code=404, detail="Snapshot not found")
|
||||
|
||||
snap = dict(snap)
|
||||
try:
|
||||
snap["sources_json"] = json.loads(snap.get("sources_json") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
snap["sources_json"] = []
|
||||
|
||||
return snap
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
# Endpunkte
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/lagebild", dependencies=[Depends(verify_api_key)])
|
||||
async def get_lagebild(db=Depends(db_dependency)):
|
||||
"""Liefert das aktuelle Lagebild (Irankonflikt) mit allen Daten.
|
||||
|
||||
Abwärtskompatibel — aggregiert die Iran-Incidents 6, 18, 19, 20.
|
||||
"""
|
||||
return await _build_lagebild_response(db, IRAN_INCIDENT_IDS, PRIMARY_INCIDENT_ID)
|
||||
|
||||
|
||||
@router.post("/globe-ingest", dependencies=[Depends(verify_api_key)])
|
||||
@@ -230,7 +270,6 @@ async def globe_ingest(
|
||||
return {"ok": True, "inserted": inserted, "total_sent": len(events)}
|
||||
|
||||
|
||||
|
||||
@router.get("/globe-incidents", dependencies=[Depends(verify_api_key)])
|
||||
async def get_globe_incidents(db=Depends(db_dependency)):
|
||||
"""Liste aller oeffentlichen aktiven Lagen fuer Globe-Auswahl."""
|
||||
@@ -349,24 +388,34 @@ async def get_globe_feed(
|
||||
}
|
||||
|
||||
|
||||
# WICHTIG: Snapshot-Routen VOR der generischen /{incident_id}-Route,
|
||||
# damit /lagebild/snapshot/123 nicht als incident_id="snapshot" gematcht wird.
|
||||
|
||||
@router.get("/lagebild/snapshot/{snapshot_id}", dependencies=[Depends(verify_api_key)])
|
||||
async def get_snapshot(snapshot_id: int, db=Depends(db_dependency)):
|
||||
"""Liefert einen historischen Snapshot."""
|
||||
ids = _in_clause(IRAN_INCIDENT_IDS)
|
||||
"""Liefert einen historischen Snapshot (Irankonflikt, abwärtskompatibel)."""
|
||||
return await _get_snapshot_response(db, snapshot_id, IRAN_INCIDENT_IDS)
|
||||
|
||||
|
||||
@router.get("/lagebild/{incident_id}/snapshot/{snapshot_id}", dependencies=[Depends(verify_api_key)])
|
||||
async def get_snapshot_by_incident(incident_id: int, snapshot_id: int, db=Depends(db_dependency)):
|
||||
"""Liefert einen historischen Snapshot für eine beliebige öffentliche Lage."""
|
||||
cursor = await db.execute(
|
||||
f"""SELECT id, summary, sources_json, article_count, fact_check_count, created_at
|
||||
FROM incident_snapshots
|
||||
WHERE id = ? AND incident_id IN ({ids})""",
|
||||
(snapshot_id,),
|
||||
"SELECT id FROM incidents WHERE id = ? AND visibility = 'public'",
|
||||
(incident_id,),
|
||||
)
|
||||
snap = await cursor.fetchone()
|
||||
if not snap:
|
||||
raise HTTPException(status_code=404, detail="Snapshot not found")
|
||||
if not await cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Lage nicht gefunden oder nicht öffentlich")
|
||||
return await _get_snapshot_response(db, snapshot_id, [incident_id])
|
||||
|
||||
snap = dict(snap)
|
||||
try:
|
||||
snap["sources_json"] = json.loads(snap.get("sources_json") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
snap["sources_json"] = []
|
||||
|
||||
return snap
|
||||
@router.get("/lagebild/{incident_id}", dependencies=[Depends(verify_api_key)])
|
||||
async def get_lagebild_by_id(incident_id: int, db=Depends(db_dependency)):
|
||||
"""Liefert das Lagebild für eine beliebige öffentliche Lage."""
|
||||
cursor = await db.execute(
|
||||
"SELECT id FROM incidents WHERE id = ? AND visibility = 'public'",
|
||||
(incident_id,),
|
||||
)
|
||||
if not await cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Lage nicht gefunden oder nicht öffentlich")
|
||||
return await _build_lagebild_response(db, [incident_id], incident_id)
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren