fixing issues

This commit is contained in:
Davide Scaini
2026-03-31 22:40:35 +02:00
parent 77c30150b0
commit e2870c3344
4 changed files with 36 additions and 15 deletions
+25 -8
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import json
import re
import shutil
from pathlib import Path
from typing import Any
@@ -17,14 +18,23 @@ site_url: str = "http://localhost:4321"
app = FastAPI(title="BincioActivity Edit Server", docs_url=None, redoc_url=None)
# Allow the Astro dev server (and any local origin) to call the write API
# Allow localhost origins only — this server is never meant to be public
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
allow_origin_regex=r"https?://localhost(:\d+)?",
allow_methods=["GET", "POST", "DELETE"],
allow_headers=["Content-Type"],
)
_VALID_ACTIVITY_ID = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9\-]{0,250}$')
def _check_id(activity_id: str) -> str:
"""Reject activity IDs that contain path traversal sequences."""
if not _VALID_ACTIVITY_ID.match(activity_id):
raise HTTPException(400, "Invalid activity ID")
return activity_id
SPORTS = ["cycling", "running", "hiking", "walking", "swimming", "skiing", "other"]
STAT_PANELS = ["elevation", "speed", "heart_rate", "cadence", "power"]
@@ -348,6 +358,7 @@ async def edit_page(activity_id: str) -> str:
@app.get("/api/activity/{activity_id}")
async def get_activity(activity_id: str) -> JSONResponse:
dd = _get_data_dir()
_check_id(activity_id)
json_path = dd / "activities" / f"{activity_id}.json"
if not json_path.exists():
raise HTTPException(404, f"Activity {activity_id!r} not found")
@@ -383,6 +394,7 @@ async def get_activity(activity_id: str) -> JSONResponse:
@app.post("/api/activity/{activity_id}")
async def save_activity(activity_id: str, payload: dict[str, Any]) -> JSONResponse:
dd = _get_data_dir()
_check_id(activity_id)
if not (dd / "activities" / f"{activity_id}.json").exists():
raise HTTPException(404, f"Activity {activity_id!r} not found")
@@ -401,9 +413,9 @@ async def save_activity(activity_id: str, payload: dict[str, Any]) -> JSONRespon
lines.append("highlight: true")
if payload.get("private"):
lines.append("private: true")
hide = payload.get("hide_stats") or []
hide = [s for s in (payload.get("hide_stats") or []) if s in STAT_PANELS]
if hide:
lines.append(f"hide_stats: [{', '.join(str(s) for s in hide)}]")
lines.append(f"hide_stats: [{', '.join(hide)}]")
description = (payload.get("description") or "").strip()
@@ -423,6 +435,7 @@ async def save_activity(activity_id: str, payload: dict[str, Any]) -> JSONRespon
@app.post("/api/activity/{activity_id}/images")
async def upload_image(activity_id: str, file: UploadFile = File(...)) -> JSONResponse:
dd = _get_data_dir()
_check_id(activity_id)
if not (dd / "activities" / f"{activity_id}.json").exists():
raise HTTPException(404, f"Activity {activity_id!r} not found")
if not file.filename:
@@ -532,7 +545,7 @@ async def upload_activity(file: UploadFile = File(...)) -> JSONResponse:
"""Accept a FIT/GPX/TCX file, extract it, update index.json, and re-merge."""
dd = _get_data_dir()
name = file.filename or "upload.fit"
name = Path(file.filename or "upload.fit").name # strip any path components
suffix = _file_suffix(name)
if suffix not in _SUPPORTED_SUFFIXES:
raise HTTPException(400, f"Unsupported file type '{Path(name).suffix}'. Expected FIT, GPX, or TCX.")
@@ -585,7 +598,11 @@ async def upload_activity(file: UploadFile = File(...)) -> JSONResponse:
@app.delete("/api/activity/{activity_id}/images/{filename}")
async def delete_image(activity_id: str, filename: str) -> JSONResponse:
dd = _get_data_dir()
target = dd / "edits" / "images" / activity_id / filename
_check_id(activity_id)
safe_name = Path(filename).name # strip any path traversal
if not safe_name:
raise HTTPException(400, "Invalid filename")
target = dd / "edits" / "images" / activity_id / safe_name
if target.exists() and target.is_file():
target.unlink()
# Remove empty parent dir