110 lines
3 KiB
Python
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")
|