7dcb1e6dd0
- Add bincio/extract/ingest.py as a facade over the extract internals (ingest_parsed, strava_sync), reducing coupling from 6+ imports to one - Add merge_one() to merge.py — fast single-activity path for interactive edits (rewrites one file + index, skips full directory rebuild) - Rewrite edit/ops.py to delegate to the new facade; fix broken run_strava_sync return (was referencing undefined locals) - Remove duplicated SPORTS, STAT_PANELS, VALID_ACTIVITY_ID from edit/server.py — now imported from ops.py
83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
"""Pure write operations used by both the single-user edit server and the
|
|
multi-user serve server.
|
|
|
|
No FastAPI, no globals — all context is passed as explicit arguments.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
# ── Shared constants (imported by edit/server.py and serve/server.py) ─────────
|
|
|
|
SPORTS = ["cycling", "running", "hiking", "walking", "swimming", "skiing", "other"]
|
|
STAT_PANELS = ["elevation", "speed", "heart_rate", "cadence", "power"]
|
|
VALID_ACTIVITY_ID = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9\-]{0,250}$')
|
|
|
|
|
|
def apply_sidecar_edit(activity_id: str, payload: dict[str, Any], data_dir: Path) -> None:
|
|
"""Write a sidecar .md file and trigger merge_all().
|
|
|
|
Args:
|
|
activity_id: Validated activity ID (caller must validate).
|
|
payload: Dict with optional keys: title, sport, gear, description,
|
|
highlight, private, hide_stats.
|
|
data_dir: Per-user data directory (contains activities/, edits/).
|
|
"""
|
|
edits_dir = data_dir / "edits"
|
|
edits_dir.mkdir(exist_ok=True)
|
|
sidecar_path = edits_dir / f"{activity_id}.md"
|
|
|
|
lines: list[str] = []
|
|
if payload.get("title"):
|
|
lines.append(f"title: {json.dumps(payload['title'])}")
|
|
if payload.get("sport") and payload["sport"] in SPORTS and payload["sport"] != "other":
|
|
lines.append(f"sport: {payload['sport']}")
|
|
if payload.get("gear"):
|
|
lines.append(f"gear: {json.dumps(payload['gear'])}")
|
|
if payload.get("highlight"):
|
|
lines.append("highlight: true")
|
|
if payload.get("private"):
|
|
lines.append("private: true")
|
|
hide = [s for s in (payload.get("hide_stats") or []) if s in STAT_PANELS]
|
|
if hide:
|
|
lines.append(f"hide_stats: [{', '.join(hide)}]")
|
|
|
|
description = (payload.get("description") or "").strip()
|
|
|
|
content = "---\n" + "\n".join(lines) + "\n---\n"
|
|
if description:
|
|
content += "\n" + description + "\n"
|
|
|
|
sidecar_path.write_text(content, encoding="utf-8")
|
|
|
|
from bincio.render.merge import merge_one
|
|
merge_one(data_dir, activity_id)
|
|
|
|
|
|
def run_strava_sync(data_dir: Path, client_id: str, client_secret: str) -> dict[str, Any]:
|
|
"""Fetch new Strava activities and write them into data_dir.
|
|
|
|
Args:
|
|
data_dir: Per-user data directory.
|
|
client_id: Strava OAuth client ID.
|
|
client_secret: Strava OAuth client secret.
|
|
|
|
Returns:
|
|
Dict with keys: ok, imported, skipped, error_count, errors.
|
|
|
|
Raises:
|
|
RuntimeError: If Strava credentials are missing or API calls fail.
|
|
"""
|
|
from bincio.extract.ingest import strava_sync as _strava_sync
|
|
from bincio.render.merge import merge_all
|
|
|
|
result = _strava_sync(data_dir, client_id, client_secret)
|
|
if result["imported"]:
|
|
merge_all(data_dir)
|
|
|
|
return result
|