feat: bidirectional sync — upload local activities to remote instance
- Server: POST /api/upload/bas accepts pre-extracted BAS JSON (activity + optional timeseries/geojson), writes files and triggers merge_all - sync.ts: uploadLocalActivities reads unsynced local activities by original_path, POSTs to /api/upload/bas, marks synced_at on success - Settings: Upload toggle (Off / Upload local activities) in Sync section with subLabel dividers for Download / Upload groups - Feed: sync message includes uploaded count when activities are pushed
This commit is contained in:
@@ -487,6 +487,58 @@ async def get_activity_timeseries(
|
||||
raise HTTPException(404, "Timeseries not found")
|
||||
|
||||
|
||||
@app.post("/api/upload/bas")
|
||||
async def upload_bas_activity(
|
||||
request: Request,
|
||||
bincio_session: Optional[str] = Cookie(default=None),
|
||||
) -> JSONResponse:
|
||||
"""Accept a pre-extracted BAS activity JSON from the mobile app.
|
||||
|
||||
Body (JSON):
|
||||
activity – full BAS activity dict (required, must have 'id')
|
||||
timeseries – timeseries dict (optional)
|
||||
geojson – GeoJSON dict (optional)
|
||||
|
||||
Returns:
|
||||
{"ok": true, "id": "...", "status": "imported" | "duplicate"}
|
||||
"""
|
||||
user = _require_auth(request, bincio_session)
|
||||
body = await request.json()
|
||||
|
||||
activity = body.get("activity")
|
||||
if not activity or not activity.get("id"):
|
||||
raise HTTPException(400, "Missing activity.id")
|
||||
|
||||
activity_id = str(activity["id"])
|
||||
_check_id(activity_id)
|
||||
|
||||
user_dir = _get_data_dir() / user.handle
|
||||
acts_dir = user_dir / "activities"
|
||||
acts_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
out = acts_dir / f"{activity_id}.json"
|
||||
if out.exists():
|
||||
return JSONResponse({"ok": True, "id": activity_id, "status": "duplicate"})
|
||||
|
||||
out.write_text(json.dumps(activity, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
if body.get("timeseries"):
|
||||
ts_path = acts_dir / f"{activity_id}.timeseries.json"
|
||||
if not ts_path.exists():
|
||||
ts_path.write_text(json.dumps(body["timeseries"], ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
if body.get("geojson"):
|
||||
gj_path = acts_dir / f"{activity_id}.geojson"
|
||||
if not gj_path.exists():
|
||||
gj_path.write_text(json.dumps(body["geojson"], ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
from bincio.render.merge import merge_all
|
||||
merge_all(user_dir)
|
||||
|
||||
log.info("upload/bas[%s]: imported %s", user.handle, activity_id)
|
||||
return JSONResponse({"ok": True, "id": activity_id, "status": "imported"})
|
||||
|
||||
|
||||
@app.get("/api/wheel/version")
|
||||
async def wheel_version() -> JSONResponse:
|
||||
"""Public endpoint: current bincio wheel version for mobile app update checks."""
|
||||
|
||||
Reference in New Issue
Block a user