Add multi-user automation features and per-user error tracking. - Database migrations: add workflow_configs/workflow_runs (004), app_errors (005), pipeline_configs/pipeline_runs (006), and add user_token_hash to app_errors (007). - Backend: introduce per-request token handling (X-API-Token) via app.api.deps and update many API routes (auth, automations, bank, characters, dashboard, events, exchange, logs) to use user-scoped Artifacts client and character scoping. Auth endpoints no longer store tokens server-side (validate-only); clear is a no-op on server. - New Errors API and services: endpoint to list, filter, resolve, and report errors scoped to the requesting user; add error models, schemas, middleware/error handler and error_service for recording/hashing tokens. - Pipelines & Workflows: add API routers, models, schemas and engine modules (pipeline/worker/coordinator, workflow runner/conditions) and action_executor updates to support workflow/pipeline execution. - Logs: logs endpoint now prefers fetching recent action logs from the game API (with fallback to local DB), supports paging and filtering, and scopes results to the user. - Frontend: add pipeline/workflow builders, lists, progress components and hooks (use-errors, use-pipelines, use-workflows), sentry client config, and updates to API client/constants/types. - Misc: add middleware error handler, various engine strategy tweaks, tests adjusted. Overall this change enables per-user API tokens, scopes DB queries to each user, introduces pipelines/workflows runtime support, and centralizes application error tracking.
71 lines
2.2 KiB
Python
71 lines
2.2 KiB
Python
"""Global error-handling middleware.
|
|
|
|
Sets a per-request correlation ID and catches unhandled exceptions,
|
|
logging them to the database (and Sentry when configured).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
from starlette.requests import Request
|
|
from starlette.responses import JSONResponse, Response
|
|
|
|
from app.database import async_session_factory
|
|
from app.services.error_service import hash_token, log_error, new_correlation_id
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ErrorHandlerMiddleware(BaseHTTPMiddleware):
|
|
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
|
cid = new_correlation_id()
|
|
start = time.monotonic()
|
|
|
|
try:
|
|
response = await call_next(request)
|
|
return response
|
|
except Exception as exc:
|
|
duration = time.monotonic() - start
|
|
logger.exception(
|
|
"Unhandled exception on %s %s (cid=%s, %.3fs)",
|
|
request.method,
|
|
request.url.path,
|
|
cid,
|
|
duration,
|
|
)
|
|
|
|
# Try to capture in Sentry
|
|
try:
|
|
import sentry_sdk
|
|
|
|
sentry_sdk.capture_exception(exc)
|
|
except Exception:
|
|
pass
|
|
|
|
# Persist to DB
|
|
token = request.headers.get("X-API-Token")
|
|
await log_error(
|
|
async_session_factory,
|
|
severity="error",
|
|
source="middleware",
|
|
exc=exc,
|
|
context={
|
|
"method": request.method,
|
|
"path": request.url.path,
|
|
"query": str(request.url.query),
|
|
"duration_s": round(duration, 3),
|
|
},
|
|
correlation_id=cid,
|
|
user_token_hash=hash_token(token) if token else None,
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"detail": "Internal server error",
|
|
"correlation_id": cid,
|
|
},
|
|
)
|