fleet-platform/app/routers/push.py

110 lines
3 KiB
Python

import hmac
import json
from typing import Any, Literal
from fastapi import APIRouter, HTTPException, Request
from psycopg.types.json import Jsonb
from app.config import get_settings
from app.db import get_pool
from app.rate_limit import limiter
router = APIRouter(prefix="/push/jimi", tags=["push"])
MAX_ITEMS_PER_POST = 5000
SUCCESS = {"code": 0, "msg": "success"}
MsgType = Literal[
"pushobd",
"pushfaultinfo",
"pushalarm",
"pushgps",
"pushhb",
"pushtripreport",
"pushevent",
]
def _verify_token(token: str) -> None:
expected = get_settings().tracksolid_push_token
if not expected:
raise HTTPException(status_code=503, detail="push token not configured")
if not hmac.compare_digest(token, expected):
raise HTTPException(status_code=401, detail="invalid token")
def _coerce_payload(raw: str) -> dict[str, Any]:
if not raw:
return {"_raw": ""}
try:
parsed = json.loads(raw)
except (json.JSONDecodeError, TypeError):
return {"_raw": raw}
if isinstance(parsed, list):
if len(parsed) > MAX_ITEMS_PER_POST:
parsed = parsed[:MAX_ITEMS_PER_POST]
return {"_list": parsed}
if isinstance(parsed, dict):
return parsed
return {"_raw": raw}
async def _ingest(msg_type: str, raw_data: str) -> None:
pool = await get_pool()
payload = _coerce_payload(raw_data)
async with pool.connection() as conn, conn.cursor() as cur:
await cur.execute(
"INSERT INTO events.raw (source, msg_type, payload) VALUES (%s, %s, %s)",
("tracksolid_push", msg_type, Jsonb(payload)),
)
async def _handle(request: Request, msg_type: MsgType) -> dict[str, Any]:
form = await request.form()
token = str(form.get("token", ""))
_verify_token(token)
raw_data = str(form.get("data") or form.get("data_list") or "")
await _ingest(msg_type, raw_data)
return SUCCESS
@router.post("/pushobd")
@limiter.limit("1000/minute")
async def push_obd(request: Request) -> dict[str, Any]:
return await _handle(request, "pushobd")
@router.post("/pushfaultinfo")
@limiter.limit("1000/minute")
async def push_fault(request: Request) -> dict[str, Any]:
return await _handle(request, "pushfaultinfo")
@router.post("/pushalarm")
@limiter.limit("1000/minute")
async def push_alarm(request: Request) -> dict[str, Any]:
return await _handle(request, "pushalarm")
@router.post("/pushgps")
@limiter.limit("1000/minute")
async def push_gps(request: Request) -> dict[str, Any]:
return await _handle(request, "pushgps")
@router.post("/pushhb")
@limiter.limit("1000/minute")
async def push_hb(request: Request) -> dict[str, Any]:
return await _handle(request, "pushhb")
@router.post("/pushtripreport")
@limiter.limit("1000/minute")
async def push_trip(request: Request) -> dict[str, Any]:
return await _handle(request, "pushtripreport")
@router.post("/pushevent")
@limiter.limit("1000/minute")
async def push_event(request: Request) -> dict[str, Any]:
return await _handle(request, "pushevent")