fix(api): parse form-urlencoded POST body in fleet-dashboard handler
The Fleet Trips SPA posts application/x-www-form-urlencoded, but the
POST /webhook/fleet-dashboard handler read the body with request.json().
That threw on every request, the except swallowed it to body={}, and all
filters (vehicle_numbers, cost_centre, assigned_city) plus period/dates
were dropped — so every query returned the full unfiltered fleet (1,266
trips) regardless of the dropdowns. The map/KPIs/trips never changed,
which read as "the dropdowns don't work."
Parse by Content-Type: urllib.parse.parse_qs for form bodies (no new
dependency — avoids python-multipart), JSON still accepted defensively
for n8n-compat callers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
26fa1a4dc5
commit
f1387d1476
1 changed files with 16 additions and 4 deletions
|
|
@ -31,9 +31,11 @@ is the base URL (the `N8N_BASE` constant in each dashboard SPA):
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from datetime import date, datetime, timedelta, timezone
|
from datetime import date, datetime, timedelta, timezone
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
import psycopg2.extras
|
import psycopg2.extras
|
||||||
from fastapi import FastAPI, Request
|
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")
|
@app.post("/webhook/fleet-dashboard")
|
||||||
async def fleet_trips(request: Request):
|
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:
|
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:
|
except Exception:
|
||||||
body = {}
|
body = {}
|
||||||
if not isinstance(body, dict):
|
if isinstance(body.get("body"), dict):
|
||||||
body = {}
|
body = body["body"]
|
||||||
body = body.get("body", body) if isinstance(body.get("body"), dict) else body
|
|
||||||
|
|
||||||
start, end = _preset_to_range(
|
start, end = _preset_to_range(
|
||||||
body.get("period"), body.get("start_date"), body.get("end_date")
|
body.get("period"), body.get("start_date"), body.get("end_date")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue