Incident-Response: sources_json nur noch via Lazy-Endpunkt, Sidebar schlank
Backend:
- IncidentResponse: sources_json-Feld entfernt (Detail-GET liefert es
nicht mehr mit).
- Neues Schema IncidentListItem fuer GET /incidents (Sidebar):
Ohne summary, ohne sources_json. Ein has_summary-Bit fuer
Erster-Refresh-Erkennung, description bleibt fuer das Edit-Modal.
- list_incidents selektiert nur die noetigen Spalten (kein SELECT *)
— spart bei grossen Lagen Speicher + Serialisierung.
- Neuer Endpunkt GET /incidents/{id}/sources liefert geparstes
Sources-Array fuer Zitate-Lookups (Lazy).
Frontend:
- api.js: getIncidentSources(id).
- app.js: loadIncidentDetail laedt /sources parallel, speichert Array
in _currentSources. Alle renderSummary/Zusammenfassung/
LatestDevelopments-Aufrufe bekommen jetzt _currentSources statt
incident.sources_json. inc.summary-Checks -> inc.has_summary.
- components.js: _parseSources(input) akzeptiert Array ODER String
(Rueckwaertskompatibilitaet). renderZusammenfassung, renderSummary,
renderLatestDevelopments nutzen den Helper.
Hintergrund: Die Sidebar-Liste lieferte bei 17 Lagen 1,23 MB
(Iran allein 386 KB wegen sources_json + summary). Detail-Endpunkt
lieferte sources_json (324 KB bei Iran) bei jedem Oeffnen mit.
Beides jetzt radikal kleiner — die 324 KB Sources gibt's nur
einmalig auf Anfrage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -1,7 +1,7 @@
|
||||
"""Incidents-Router: Lagen verwalten (Multi-Tenant)."""
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from models import IncidentCreate, IncidentUpdate, IncidentResponse, SubscriptionUpdate, SubscriptionResponse, DescriptionEnhanceRequest
|
||||
from models import IncidentCreate, IncidentUpdate, IncidentResponse, IncidentListItem, SubscriptionUpdate, SubscriptionResponse, DescriptionEnhanceRequest
|
||||
from auth import get_current_user
|
||||
from middleware.license_check import require_writable_license
|
||||
from database import db_dependency, get_db
|
||||
@@ -69,17 +69,30 @@ async def _enrich_incident(db: aiosqlite.Connection, row: aiosqlite.Row) -> dict
|
||||
return incident
|
||||
|
||||
|
||||
@router.get("", response_model=list[IncidentResponse])
|
||||
@router.get("", response_model=list[IncidentListItem])
|
||||
async def list_incidents(
|
||||
status_filter: str = None,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Alle Lagen des Tenants auflisten (oeffentliche + eigene private)."""
|
||||
"""Alle Lagen des Tenants auflisten (oeffentliche + eigene private).
|
||||
|
||||
Liefert schlanke Sidebar-Items — ohne summary, description, sources_json.
|
||||
Volltexte kommen erst beim Oeffnen der Lage per GET /incidents/{id}.
|
||||
"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
user_id = current_user["id"]
|
||||
|
||||
query = "SELECT * FROM incidents WHERE tenant_id = ? AND (visibility = 'public' OR created_by = ?)"
|
||||
# Nur die fuer Sidebar + Edit-Dialog noetigen Spalten selektieren
|
||||
# (spart bei Iran: 324 KB sources_json + 32 KB summary).
|
||||
# has_summary als Bit — Frontend nutzt es zur Erkennung "erster Refresh".
|
||||
query = (
|
||||
"SELECT id, title, description, type, status, refresh_mode, refresh_interval, "
|
||||
"refresh_start_time, retention_days, visibility, "
|
||||
"international_sources, include_telegram, created_by, created_at, updated_at, "
|
||||
"CASE WHEN summary IS NOT NULL AND summary != '' THEN 1 ELSE 0 END AS has_summary "
|
||||
"FROM incidents WHERE tenant_id = ? AND (visibility = 'public' OR created_by = ?)"
|
||||
)
|
||||
params = [tenant_id, user_id]
|
||||
|
||||
if status_filter:
|
||||
@@ -239,12 +252,41 @@ async def get_incident(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Einzelne Lage abrufen."""
|
||||
"""Einzelne Lage abrufen.
|
||||
|
||||
sources_json wird NICHT mitgeliefert — fuer Zitate-Lookups
|
||||
stattdessen GET /incidents/{id}/sources verwenden (lazy).
|
||||
"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
row = await _check_incident_access(db, incident_id, current_user["id"], tenant_id)
|
||||
return await _enrich_incident(db, row)
|
||||
|
||||
|
||||
@router.get("/{incident_id}/sources")
|
||||
async def get_incident_sources(
|
||||
incident_id: int,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Sources-Array einer Lage (geparst aus sources_json) fuer Zitate-Lookups."""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
await _check_incident_access(db, incident_id, current_user["id"], tenant_id)
|
||||
cursor = await db.execute(
|
||||
"SELECT sources_json FROM incidents WHERE id = ?",
|
||||
(incident_id,),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
sources: list = []
|
||||
if row and row["sources_json"]:
|
||||
try:
|
||||
parsed = json.loads(row["sources_json"])
|
||||
if isinstance(parsed, list):
|
||||
sources = parsed
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
sources = []
|
||||
return {"incident_id": incident_id, "sources": sources}
|
||||
|
||||
|
||||
@router.put("/{incident_id}", response_model=IncidentResponse)
|
||||
async def update_incident(
|
||||
incident_id: int,
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren