feat: frictionless incident capture #13
1 changed files with 58 additions and 1 deletions
59
app/rest.py
59
app/rest.py
|
|
@ -39,8 +39,10 @@ from app.services.search import (
|
||||||
stats_summary as _stats,
|
stats_summary as _stats,
|
||||||
format_results,
|
format_results,
|
||||||
)
|
)
|
||||||
|
from app.services.diagnose import diagnose as _diagnose
|
||||||
|
|
||||||
DB_PATH = Path(os.environ.get("TURNSTONE_DB", Path(__file__).parent.parent / "data" / "turnstone.db"))
|
DB_PATH = Path(os.environ.get("TURNSTONE_DB", Path(__file__).parent.parent / "data" / "turnstone.db"))
|
||||||
|
PREFS_PATH = DB_PATH.parent / "preferences.json"
|
||||||
DIST_DIR = Path(__file__).parent.parent / "web" / "dist"
|
DIST_DIR = Path(__file__).parent.parent / "web" / "dist"
|
||||||
SOURCE_HOST = os.environ.get("TURNSTONE_SOURCE_HOST", "unknown")
|
SOURCE_HOST = os.environ.get("TURNSTONE_SOURCE_HOST", "unknown")
|
||||||
BUNDLE_ENDPOINT = os.environ.get("TURNSTONE_BUNDLE_ENDPOINT", "")
|
BUNDLE_ENDPOINT = os.environ.get("TURNSTONE_BUNDLE_ENDPOINT", "")
|
||||||
|
|
@ -50,7 +52,7 @@ app = FastAPI(title="Turnstone API", version="0.1.0", docs_url="/turnstone/docs"
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
allow_methods=["GET", "POST", "DELETE"],
|
allow_methods=["GET", "POST", "DELETE", "PATCH"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -60,6 +62,29 @@ def _startup() -> None:
|
||||||
ensure_schema(DB_PATH)
|
ensure_schema(DB_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_prefs() -> dict[str, str]:
|
||||||
|
if PREFS_PATH.exists():
|
||||||
|
try:
|
||||||
|
return json.loads(PREFS_PATH.read_text())
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
return {"entry_point_style": "topbar"}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_prefs(data: dict[str, str]) -> None:
|
||||||
|
PREFS_PATH.write_text(json.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnoseRequest(BaseModel):
|
||||||
|
query: str
|
||||||
|
since: str | None = None
|
||||||
|
until: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsBody(BaseModel):
|
||||||
|
entry_point_style: str
|
||||||
|
|
||||||
|
|
||||||
class IncidentCreate(BaseModel):
|
class IncidentCreate(BaseModel):
|
||||||
label: str
|
label: str
|
||||||
issue_type: str = ""
|
issue_type: str = ""
|
||||||
|
|
@ -167,6 +192,38 @@ def diagnose(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/diagnose")
|
||||||
|
def diagnose_post(body: DiagnoseRequest) -> dict:
|
||||||
|
if not body.query.strip():
|
||||||
|
return {
|
||||||
|
"summary": {
|
||||||
|
"total": 0, "window_start": None, "window_end": None,
|
||||||
|
"time_detected": False, "by_severity": {}, "by_source": {},
|
||||||
|
},
|
||||||
|
"entries": [],
|
||||||
|
}
|
||||||
|
result = _diagnose(DB_PATH, query=body.query, since=body.since, until=body.until)
|
||||||
|
return {
|
||||||
|
"summary": result["summary"],
|
||||||
|
"entries": [dataclasses.asdict(r) for r in result["entries"]],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/settings")
|
||||||
|
def get_settings() -> dict:
|
||||||
|
return _load_prefs()
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/api/settings")
|
||||||
|
def patch_settings(body: SettingsBody) -> dict:
|
||||||
|
if body.entry_point_style not in ("topbar", "fab"):
|
||||||
|
raise HTTPException(status_code=422, detail="entry_point_style must be 'topbar' or 'fab'")
|
||||||
|
prefs = _load_prefs()
|
||||||
|
prefs["entry_point_style"] = body.entry_point_style
|
||||||
|
_save_prefs(prefs)
|
||||||
|
return prefs
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/sources")
|
@router.get("/api/sources")
|
||||||
def list_sources() -> dict:
|
def list_sources() -> dict:
|
||||||
return {"sources": _list_sources(DB_PATH)}
|
return {"sources": _list_sources(DB_PATH)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue