feat: add realtime GNSS monitor with telemetry websocket
All checks were successful
Deploy to Production / deploy (push) Successful in 1m10s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m10s
This commit is contained in:
@@ -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)]
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
63
backend/app/api/routes/telemetry.py
Normal file
63
backend/app/api/routes/telemetry.py
Normal 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
|
||||
Reference in New Issue
Block a user