feat: FEAT-05 — DTC history sync to remote server
- Server: dtc_events table (migrate_phase27_dtc.sql), db.py helpers (upsert_dtc_snapshot, list_dtc_events, delete_dtc_event) - Server API: POST /api/v1/dtc/batch (device auth), GET/DELETE /api/v1/portal/dtc - Server portal: /dtc page with code, description, MIL, status, timestamps, delete - Server nav: DTC history in sidebar + mobile bottom nav - Pi: _upload_dtc_snapshot() in app_upload.py — uploads when snapshot changes, skips when OBD disconnected or unchanged
This commit is contained in:
@@ -423,6 +423,12 @@ def portal_device_settings_page():
|
||||
)
|
||||
|
||||
|
||||
@app.get("/dtc")
|
||||
@require_login
|
||||
def portal_dtc_page():
|
||||
return render_template("dtc.html")
|
||||
|
||||
|
||||
# ── Portal JSON API ────────────────────────────────────────────────────────
|
||||
|
||||
@app.get("/api/v1/portal/stats")
|
||||
@@ -850,6 +856,45 @@ def api_device_config_update():
|
||||
return jsonify(config)
|
||||
|
||||
|
||||
# ── DTC sync (FEAT-05) ────────────────────────────────────────────────────────
|
||||
|
||||
@app.post("/api/v1/dtc/batch")
|
||||
@require_device_auth
|
||||
def upload_dtc_batch():
|
||||
"""Receive a DTC snapshot from the Pi. Body: {mil_on: bool, codes: [{code, description}, ...]}."""
|
||||
body = request.get_json(silent=True) or {}
|
||||
mil_on = bool(body.get("mil_on", False))
|
||||
codes = body.get("codes")
|
||||
if not isinstance(codes, list):
|
||||
return jsonify(error="expected 'codes' as a list"), 400
|
||||
for c in codes:
|
||||
if not isinstance(c.get("code"), str) or not c["code"].strip():
|
||||
return jsonify(error="each code must have a non-empty 'code' string"), 400
|
||||
result = db.upsert_dtc_snapshot(g.device_id, mil_on, codes)
|
||||
return jsonify(result), 200
|
||||
|
||||
|
||||
@app.get("/api/v1/portal/dtc")
|
||||
@require_login
|
||||
def portal_dtc():
|
||||
device_id = _portal_device_id()
|
||||
if device_id is None:
|
||||
return jsonify(error="device not configured"), 404
|
||||
return jsonify(db.list_dtc_events(device_id))
|
||||
|
||||
|
||||
@app.delete("/api/v1/portal/dtc/<int:dtc_id>")
|
||||
@require_login
|
||||
def portal_dtc_delete(dtc_id):
|
||||
device_id = _portal_device_id()
|
||||
if device_id is None:
|
||||
return jsonify(error="device not configured"), 404
|
||||
deleted = db.delete_dtc_event(device_id, dtc_id)
|
||||
if not deleted:
|
||||
return jsonify(error="DTC event not found"), 404
|
||||
return jsonify(status="ok")
|
||||
|
||||
|
||||
def main():
|
||||
app.run(host="0.0.0.0", port=8098)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user