Compare commits

..

No commits in common. "6371967f8d74c4f3d1dd8bfc4011512dc00d8e51" and "f42eef66531d6100e3e5531665df198db959bb87" have entirely different histories.

View file

@ -109,36 +109,46 @@ 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 "", []
# Jimi integration push format (observed live): # ── Try JSON body first (integration push format) ──────────────────────────
# Content-Type: application/x-www-form-urlencoded if "application/json" in content_type or body.lstrip()[:1] == b"{":
# Body: msgType=<topic>&data=<URL-encoded JSON object or array> try:
# The `data` field holds a single JSON object per event, not an array. payload = json.loads(body)
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 parse failed", exc_info=True) log.warning("push: form body parse failed", exc_info=True)
return "", []
token = str(form.get("token", "")) return "", []
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]:
@ -341,11 +351,9 @@ async def push_alarm(request: Request):
for item in items: for item in items:
try: try:
cur.execute("SAVEPOINT sp") cur.execute("SAVEPOINT sp")
# Jimi integration push uses `imei` + `alarmTime`, NOT the imei = clean(item.get("deviceImei"))
# `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("alarmTime") or item.get("gateTime")) alarm_time = clean_ts(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")