Snapshots: Liste ohne Volltext, Lazy-Load + serverseitige Suche
Backend:
- GET /{id}/snapshots liefert nur noch schlanke Shape (Metadaten +
SUBSTR(summary,1,300) AS summary_preview), kein Volltext, kein sources_json.
- Neuer Endpunkt GET /{id}/snapshots/{snapshot_id} fuer Volltext-Lazy-Load.
- Neuer Endpunkt GET /{id}/snapshots/search?q=... fuer serverseitige
Volltextsuche ueber alle Snapshots einer Lage.
Frontend:
- api.js: getSnapshot() und searchSnapshots() ergaenzt.
- app.js: _snapshotFullCache, Volltext wird beim Aufklappen eines
Snapshot-Eintrags per lazyLoadSnapshotDetail() nachgeladen und gecacht.
- Suche ueber Snapshots filtert weiterhin clientseitig ueber summary_preview.
Hintergrund: Bei grossen Lagen (Iran-Lage: 347 Snapshots) fiel die
Snapshots-Listenantwort mit Volltext-Summaries auf ~54 MB. Die Liste
faellt damit auf ~150 KB; Volltexte werden nur on-demand geladen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -337,12 +337,17 @@ async def get_snapshots(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Lageberichte (Snapshots) einer Lage abrufen."""
|
||||
"""Lageberichte (Snapshots) einer Lage abrufen — schlanke Liste.
|
||||
|
||||
Liefert nur Metadaten und einen 300-Zeichen-Preview des Summary.
|
||||
Der Volltext (summary + sources_json) wird per Einzel-Endpunkt
|
||||
``GET /{incident_id}/snapshots/{snapshot_id}`` bei Bedarf geladen.
|
||||
"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
await _check_incident_access(db, incident_id, current_user["id"], tenant_id)
|
||||
cursor = await db.execute(
|
||||
"""SELECT id, incident_id, summary, sources_json,
|
||||
article_count, fact_check_count, created_at
|
||||
"""SELECT id, incident_id, article_count, fact_check_count, created_at,
|
||||
SUBSTR(summary, 1, 300) AS summary_preview
|
||||
FROM incident_snapshots WHERE incident_id = ?
|
||||
ORDER BY created_at DESC""",
|
||||
(incident_id,),
|
||||
@@ -351,6 +356,55 @@ async def get_snapshots(
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
@router.get("/{incident_id}/snapshots/search")
|
||||
async def search_snapshots(
|
||||
incident_id: int,
|
||||
q: str = Query(..., min_length=2, max_length=200),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Volltextsuche über alle Snapshots einer Lage.
|
||||
|
||||
Liefert dieselbe schlanke Shape wie der Listen-Endpunkt,
|
||||
gefiltert per ``summary LIKE '%q%'``.
|
||||
"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
await _check_incident_access(db, incident_id, current_user["id"], tenant_id)
|
||||
like = f"%{q}%"
|
||||
cursor = await db.execute(
|
||||
"""SELECT id, incident_id, article_count, fact_check_count, created_at,
|
||||
SUBSTR(summary, 1, 300) AS summary_preview
|
||||
FROM incident_snapshots
|
||||
WHERE incident_id = ? AND summary LIKE ?
|
||||
ORDER BY created_at DESC""",
|
||||
(incident_id, like),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
@router.get("/{incident_id}/snapshots/{snapshot_id}")
|
||||
async def get_snapshot(
|
||||
incident_id: int,
|
||||
snapshot_id: int,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Einzelnen Snapshot mit vollem Summary + sources_json abrufen (Lazy-Load)."""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
await _check_incident_access(db, incident_id, current_user["id"], tenant_id)
|
||||
cursor = await db.execute(
|
||||
"""SELECT id, incident_id, summary, sources_json,
|
||||
article_count, fact_check_count, created_at
|
||||
FROM incident_snapshots WHERE id = ? AND incident_id = ?""",
|
||||
(snapshot_id, incident_id),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Snapshot nicht gefunden")
|
||||
return dict(row)
|
||||
|
||||
|
||||
@router.get("/{incident_id}/factchecks")
|
||||
async def get_factchecks(
|
||||
incident_id: int,
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren