diff --git a/dashboard_api_rev.py b/dashboard_api_rev.py index 6537e83..92985c4 100644 --- a/dashboard_api_rev.py +++ b/dashboard_api_rev.py @@ -31,9 +31,11 @@ is the base URL (the `N8N_BASE` constant in each dashboard SPA): from __future__ import annotations +import json import os from contextlib import asynccontextmanager from datetime import date, datetime, timedelta, timezone +from urllib.parse import parse_qs import psycopg2.extras from fastapi import FastAPI, Request @@ -202,13 +204,23 @@ def _preset_to_range(period: str | None, start_date, end_date): @app.post("/webhook/fleet-dashboard") async def fleet_trips(request: Request): + # The dashboard SPA posts application/x-www-form-urlencoded (not JSON), so + # parse by content-type. Reading the raw body + parse_qs avoids pulling in + # python-multipart. JSON is still accepted defensively (n8n-compat callers). + body: dict = {} + ctype = request.headers.get("content-type", "").lower() try: - body = await request.json() + raw = await request.body() + if "application/json" in ctype: + parsed = json.loads(raw or b"{}") + body = parsed if isinstance(parsed, dict) else {} + else: + # x-www-form-urlencoded — parse_qs yields lists; keep the last value. + body = {k: v[-1] for k, v in parse_qs(raw.decode("utf-8", "replace")).items()} except Exception: body = {} - if not isinstance(body, dict): - body = {} - body = body.get("body", body) if isinstance(body.get("body"), dict) else body + if isinstance(body.get("body"), dict): + body = body["body"] start, end = _preset_to_range( body.get("period"), body.get("start_date"), body.get("end_date")