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.
|
"""Öffentliche API für die Lagebild-Seite auf aegissight.de.
|
||||||
|
|
||||||
Authentifizierung via X-API-Key Header (getrennt von der JWT-Auth).
|
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 json
|
||||||
import logging
|
import logging
|
||||||
@@ -50,14 +50,23 @@ def _in_clause(ids):
|
|||||||
return ",".join(str(int(i)) for i in 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)):
|
# Shared-Logik für Lagebild-Responses
|
||||||
"""Liefert das aktuelle Lagebild (Irankonflikt) mit allen Daten."""
|
# ──────────────────────────────────────────────────────────────────
|
||||||
ids = _in_clause(IRAN_INCIDENT_IDS)
|
|
||||||
|
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)
|
# Haupt-Incident laden (für Summary, Sources)
|
||||||
cursor = await db.execute(
|
cursor = await db.execute(
|
||||||
"SELECT * FROM incidents WHERE id = ?", (PRIMARY_INCIDENT_ID,)
|
"SELECT * FROM incidents WHERE id = ?", (primary_id,)
|
||||||
)
|
)
|
||||||
incident = await cursor.fetchone()
|
incident = await cursor.fetchone()
|
||||||
if not incident:
|
if not incident:
|
||||||
@@ -72,7 +81,7 @@ async def get_lagebild(db=Depends(db_dependency)):
|
|||||||
except (json.JSONDecodeError, TypeError):
|
except (json.JSONDecodeError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Alle Artikel aus allen Iran-Incidents laden
|
# Alle Artikel laden
|
||||||
cursor = await db.execute(
|
cursor = await db.execute(
|
||||||
f"""SELECT id, headline, headline_de, source, source_url, language,
|
f"""SELECT id, headline, headline_de, source, source_url, language,
|
||||||
published_at, collected_at, verification_status, incident_id
|
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()]
|
articles = [dict(r) for r in await cursor.fetchall()]
|
||||||
|
|
||||||
# Alle Faktenchecks aus allen Iran-Incidents laden
|
# Alle Faktenchecks laden
|
||||||
cursor = await db.execute(
|
cursor = await db.execute(
|
||||||
f"""SELECT id, claim, status, sources_count, evidence, status_history, checked_at, incident_id
|
f"""SELECT id, claim, status, sources_count, evidence, status_history, checked_at, incident_id
|
||||||
FROM fact_checks WHERE incident_id IN ({ids})
|
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"]
|
source_count = (await cursor.fetchone())["cnt"]
|
||||||
|
|
||||||
# Snapshots aus allen Iran-Incidents
|
# Snapshots
|
||||||
cursor = await db.execute(
|
cursor = await db.execute(
|
||||||
f"""SELECT id, incident_id, article_count, fact_check_count, created_at
|
f"""SELECT id, incident_id, article_count, fact_check_count, created_at
|
||||||
FROM incident_snapshots WHERE incident_id IN ({ids})
|
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)])
|
@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)}
|
return {"ok": True, "inserted": inserted, "total_sent": len(events)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/globe-incidents", dependencies=[Depends(verify_api_key)])
|
@router.get("/globe-incidents", dependencies=[Depends(verify_api_key)])
|
||||||
async def get_globe_incidents(db=Depends(db_dependency)):
|
async def get_globe_incidents(db=Depends(db_dependency)):
|
||||||
"""Liste aller oeffentlichen aktiven Lagen fuer Globe-Auswahl."""
|
"""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)])
|
@router.get("/lagebild/snapshot/{snapshot_id}", dependencies=[Depends(verify_api_key)])
|
||||||
async def get_snapshot(snapshot_id: int, db=Depends(db_dependency)):
|
async def get_snapshot(snapshot_id: int, db=Depends(db_dependency)):
|
||||||
"""Liefert einen historischen Snapshot."""
|
"""Liefert einen historischen Snapshot (Irankonflikt, abwärtskompatibel)."""
|
||||||
ids = _in_clause(IRAN_INCIDENT_IDS)
|
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(
|
cursor = await db.execute(
|
||||||
f"""SELECT id, summary, sources_json, article_count, fact_check_count, created_at
|
"SELECT id FROM incidents WHERE id = ? AND visibility = 'public'",
|
||||||
FROM incident_snapshots
|
(incident_id,),
|
||||||
WHERE id = ? AND incident_id IN ({ids})""",
|
|
||||||
(snapshot_id,),
|
|
||||||
)
|
)
|
||||||
snap = await cursor.fetchone()
|
if not await cursor.fetchone():
|
||||||
if not snap:
|
raise HTTPException(status_code=404, detail="Lage nicht gefunden oder nicht öffentlich")
|
||||||
raise HTTPException(status_code=404, detail="Snapshot not found")
|
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