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")