feat: add realtime GNSS monitor with telemetry websocket
All checks were successful
Deploy to Production / deploy (push) Successful in 1m10s

This commit is contained in:
魏风
2026-03-20 11:48:04 +08:00
parent e8846f4831
commit e81aadcd99
16 changed files with 1951 additions and 22 deletions

View File

@@ -27,7 +27,7 @@ SessionDep = Annotated[Session, Depends(get_db)]
TokenDep = Annotated[str, Depends(reusable_oauth2)]
def get_current_user(session: SessionDep, token: TokenDep) -> User:
def resolve_user_from_token(*, session: Session, token: str) -> User:
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
@@ -46,6 +46,10 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
return user
def get_current_user(session: SessionDep, token: TokenDep) -> User:
return resolve_user_from_token(session=session, token=token)
CurrentUser = Annotated[User, Depends(get_current_user)]

View File

@@ -1,6 +1,6 @@
from fastapi import APIRouter
from app.api.routes import locations, login, private, users, utils
from app.api.routes import locations, login, private, telemetry, users, utils
from app.core.config import settings
api_router = APIRouter()
@@ -8,8 +8,8 @@ api_router.include_router(login.router)
api_router.include_router(users.router)
api_router.include_router(utils.router)
api_router.include_router(locations.router)
api_router.include_router(telemetry.router)
if settings.ENVIRONMENT == "local":
api_router.include_router(private.router)

View File

@@ -0,0 +1,63 @@
from __future__ import annotations
import asyncio
from typing import Any
from fastapi import APIRouter, HTTPException, Query, WebSocket, status
from fastapi.encoders import jsonable_encoder
from fastapi.websockets import WebSocketDisconnect
from sqlmodel import Session
from app.api.deps import CurrentUser, SessionDep, resolve_user_from_token
from app.core.db import engine
from app.mqtt.telemetry import build_telemetry_snapshot
router = APIRouter(prefix="/telemetry", tags=["telemetry"])
STREAM_INTERVAL_SECONDS = 1.0
@router.get("/latest")
def read_telemetry_latest(
session: SessionDep,
_current_user: CurrentUser,
device_id: str | None = None,
limit: int = Query(default=50, ge=1, le=200),
) -> dict[str, Any]:
"""
Return the latest parsed GNSS telemetry snapshot.
"""
return build_telemetry_snapshot(session=session, device_id=device_id, limit=limit)
@router.websocket("/ws")
async def telemetry_websocket(
websocket: WebSocket,
token: str | None = Query(default=None),
device_id: str | None = Query(default=None),
limit: int = Query(default=50, ge=1, le=200),
) -> None:
if not token:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
with Session(engine) as session:
try:
resolve_user_from_token(session=session, token=token)
except HTTPException:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
await websocket.accept()
try:
while True:
with Session(engine) as session:
payload = build_telemetry_snapshot(
session=session,
device_id=device_id,
limit=limit,
)
await websocket.send_json(jsonable_encoder(payload))
await asyncio.sleep(STREAM_INTERVAL_SECONDS)
except (WebSocketDisconnect, RuntimeError):
return