Merge pull request 'fix: parse real Jimi push format (msgType+data)' (#8) from quality-program-2026-04-12 into main
This commit is contained in:
commit
6371967f8d
1 changed files with 26 additions and 34 deletions
|
|
@ -109,47 +109,37 @@ async def _parse_request(request: Request) -> tuple[str, list[dict]]:
|
||||||
Some older/configured endpoints may still use form-encoded. This helper
|
Some older/configured endpoints may still use form-encoded. This helper
|
||||||
handles both so each endpoint doesn't need to know which format arrived.
|
handles both so each endpoint doesn't need to know which format arrived.
|
||||||
"""
|
"""
|
||||||
content_type = request.headers.get("content-type", "")
|
|
||||||
body = await request.body()
|
body = await request.body()
|
||||||
|
|
||||||
# TEMP DIAGNOSTIC: log every push so we can see what Jimi actually sends.
|
|
||||||
log.info("push %s: content-type=%r body=%.300s",
|
|
||||||
request.url.path, content_type,
|
|
||||||
body.decode("utf-8", errors="replace") if body else "<empty>")
|
|
||||||
|
|
||||||
if not body:
|
if not body:
|
||||||
return "", []
|
return "", []
|
||||||
|
|
||||||
# ── Try JSON body first (integration push format) ──────────────────────────
|
# Jimi integration push format (observed live):
|
||||||
if "application/json" in content_type or body.lstrip()[:1] == b"{":
|
# Content-Type: application/x-www-form-urlencoded
|
||||||
try:
|
# Body: msgType=<topic>&data=<URL-encoded JSON object or array>
|
||||||
payload = json.loads(body)
|
# The `data` field holds a single JSON object per event, not an array.
|
||||||
token = str(payload.get("token", ""))
|
|
||||||
raw_dl = payload.get("data_list", [])
|
|
||||||
if isinstance(raw_dl, list):
|
|
||||||
items = raw_dl[:MAX_ITEMS_PER_POST]
|
|
||||||
elif isinstance(raw_dl, str):
|
|
||||||
items = _parse_data_list(raw_dl)
|
|
||||||
else:
|
|
||||||
items = []
|
|
||||||
log.info("push: parsed JSON body — %d items", len(items))
|
|
||||||
return token, items
|
|
||||||
except (json.JSONDecodeError, TypeError):
|
|
||||||
log.warning("push: JSON body parse failed")
|
|
||||||
|
|
||||||
# ── Fall back to form-encoded ───────────────────────────────────────────────
|
|
||||||
try:
|
try:
|
||||||
form = await request.form()
|
form = await request.form()
|
||||||
token = str(form.get("token", ""))
|
|
||||||
raw_dl = str(form.get("data_list", ""))
|
|
||||||
items = _parse_data_list(raw_dl) if raw_dl else []
|
|
||||||
log.info("push: parsed form body — %d items", len(items))
|
|
||||||
return token, items
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning("push: form body parse failed", exc_info=True)
|
log.warning("push: form parse failed", exc_info=True)
|
||||||
|
|
||||||
return "", []
|
return "", []
|
||||||
|
|
||||||
|
token = str(form.get("token", ""))
|
||||||
|
raw_data = form.get("data") or form.get("data_list") or ""
|
||||||
|
if not raw_data:
|
||||||
|
return token, []
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = json.loads(raw_data)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
log.warning("push: data JSON parse failed — %.200s", raw_data)
|
||||||
|
return token, []
|
||||||
|
|
||||||
|
items = parsed if isinstance(parsed, list) else [parsed]
|
||||||
|
if len(items) > MAX_ITEMS_PER_POST:
|
||||||
|
log.warning("push: truncated %d → %d items", len(items), MAX_ITEMS_PER_POST)
|
||||||
|
items = items[:MAX_ITEMS_PER_POST]
|
||||||
|
return token, items
|
||||||
|
|
||||||
|
|
||||||
def unix_to_ts(v) -> Optional[str]:
|
def unix_to_ts(v) -> Optional[str]:
|
||||||
"""Convert Unix timestamp (seconds or milliseconds) to ISO string."""
|
"""Convert Unix timestamp (seconds or milliseconds) to ISO string."""
|
||||||
|
|
@ -351,9 +341,11 @@ async def push_alarm(request: Request):
|
||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
cur.execute("SAVEPOINT sp")
|
cur.execute("SAVEPOINT sp")
|
||||||
imei = clean(item.get("deviceImei"))
|
# Jimi integration push uses `imei` + `alarmTime`, NOT the
|
||||||
|
# `deviceImei` + `gateTime` fields shown in the API docs.
|
||||||
|
imei = clean(item.get("imei") or item.get("deviceImei"))
|
||||||
alarm_type = clean(item.get("alarmType"))
|
alarm_type = clean(item.get("alarmType"))
|
||||||
alarm_time = clean_ts(item.get("gateTime"))
|
alarm_time = clean_ts(item.get("alarmTime") or item.get("gateTime"))
|
||||||
# [BUG-02] Also guard alarm_type — NULL alarm_type violates NOT NULL constraint.
|
# [BUG-02] Also guard alarm_type — NULL alarm_type violates NOT NULL constraint.
|
||||||
if not imei or not alarm_time or not alarm_type:
|
if not imei or not alarm_time or not alarm_type:
|
||||||
cur.execute("RELEASE SAVEPOINT sp")
|
cur.execute("RELEASE SAVEPOINT sp")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue