mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-23 15:51:13 +00:00
0bb94da3c4
- app.py: replace inline component wiring with build_pipeline(env=cfg.env) - Store processor as app.state.processor (and backwards-compat pipeline_components) - RuntimeConfig replaces get_settings(); MAVLink stop() on shutdown - deps.py: get_flight_processor prefers app.state.processor from lifespan - Falls back to build_pipeline() for test contexts without lifespan - Per-request repo/streamer swap preserved
93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
import logging
|
|
from typing import Annotated
|
|
|
|
from fastapi import Depends, HTTPException, Request
|
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from gps_denied.config import RuntimeConfig, get_settings
|
|
from gps_denied.core.processor import FlightProcessor
|
|
from gps_denied.core.sse import SSEEventStreamer
|
|
from gps_denied.db.engine import get_session
|
|
from gps_denied.db.repository import FlightRepository
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Singleton instance of SSE Event Streamer
|
|
_sse_streamer = SSEEventStreamer()
|
|
|
|
# Singleton FlightProcessor (one per process, reused across requests)
|
|
_processor: FlightProcessor | None = None
|
|
|
|
# JWT Bearer scheme (auto_error=False — ми самі обробляємо помилки)
|
|
_bearer = HTTPBearer(auto_error=False)
|
|
|
|
|
|
async def verify_token(
|
|
credentials: HTTPAuthorizationCredentials | None = Depends(_bearer),
|
|
) -> None:
|
|
"""JWT перевірка. При AUTH_ENABLED=false — пропускає все."""
|
|
settings = get_settings()
|
|
if not settings.auth.enabled:
|
|
return # dev/SITL: автентифікація вимкнена
|
|
|
|
if credentials is None:
|
|
raise HTTPException(status_code=401, detail="Authorization header required")
|
|
|
|
try:
|
|
import jwt
|
|
|
|
jwt.decode(
|
|
credentials.credentials,
|
|
settings.auth.secret_key,
|
|
algorithms=[settings.auth.algorithm],
|
|
)
|
|
except ImportError:
|
|
logger.warning("PyJWT not installed — JWT validation skipped")
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=401, detail=f"Invalid token: {exc}") from exc
|
|
|
|
|
|
def get_sse_streamer() -> SSEEventStreamer:
|
|
return _sse_streamer
|
|
|
|
|
|
async def get_repository(
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> FlightRepository:
|
|
return FlightRepository(session)
|
|
|
|
|
|
async def get_flight_processor(
|
|
request: Request,
|
|
repo: FlightRepository = Depends(get_repository),
|
|
sse: SSEEventStreamer = Depends(get_sse_streamer),
|
|
) -> FlightProcessor:
|
|
global _processor
|
|
if _processor is None:
|
|
# Prefer the processor already built by lifespan (via build_pipeline)
|
|
lifespan_processor = getattr(request.app.state, "processor", None)
|
|
if lifespan_processor is not None:
|
|
_processor = lifespan_processor
|
|
else:
|
|
# Fallback: build pipeline directly (e.g. in tests without lifespan)
|
|
from gps_denied.pipeline import build_pipeline
|
|
_settings = RuntimeConfig()
|
|
_processor = build_pipeline(
|
|
env=_settings.env,
|
|
config=_settings,
|
|
repository=repo,
|
|
streamer=sse,
|
|
)
|
|
# Оновлюємо repo та streamer (нова сесія на кожен запит)
|
|
_processor.repository = repo
|
|
_processor.streamer = sse
|
|
return _processor
|
|
|
|
|
|
# Аліаси для зручності в роутерах
|
|
SessionDep = Annotated[AsyncSession, Depends(get_session)]
|
|
RepoDep = Annotated[FlightRepository, Depends(get_repository)]
|
|
ProcessorDep = Annotated[FlightProcessor, Depends(get_flight_processor)]
|
|
AuthDep = Annotated[None, Depends(verify_token)]
|