Files
bincio-activity/bincio/serve/server.py
T
Davide Scaini b781193d44 feat: bulk delete + merge activities in feed
- Select mode in ActivityFeed: toggle with Select button (logged-in only),
  cards become clickable with checkmark indicator, action bar fixed at bottom
- Bulk delete: calls existing DELETE /api/activity/{id} for each selected,
  removes from local feed state immediately
- Bulk merge: POST /api/merge sorts by started_at (earliest = primary),
  sums distance/duration/elevation, weighted-averages HR/power, concatenates
  geojson and timeseries; backs up originals to _merge_backup/ for recovery
- GET /api/merges returns per-user hidden list; feed filters secondaries
  client-side on load so static shards don't need a rebuild to hide them
- POST /api/unmerge/{id} restores primary from backup, unhides secondaries
- ActivityDetail: shows "Merged (N)" badge + Unmerge button for owners
- Fix: edit button now works from personal profile feed (handle was missing
  from year-shard activities; injected from filterHandle on sessionStorage write)
2026-06-03 10:32:02 +02:00

80 lines
1.8 KiB
Python

"""bincio serve — multi-user FastAPI application server.
Handles auth, user management, and auth-gated write operations.
nginx serves static files; this server only handles /api/* routes.
Run via `bincio serve` CLI command.
"""
from __future__ import annotations
import glob as _glob
import threading
from pathlib import Path
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from bincio.serve import deps, tasks
from bincio.serve.routers import (
activities,
admin,
auth,
budget,
download,
feed,
garmin,
gear,
ideas,
me,
merge,
ogimage,
segments,
strava,
uploads,
)
app = FastAPI(title="BincioActivity Serve")
@app.on_event("startup")
async def _on_startup() -> None:
"""Startup tasks: clean orphaned tmp zips; launch site-rebuild worker if --webroot set."""
data_dir = deps._get_data_dir()
for p in _glob.glob(str(data_dir / "*" / "tmp*.zip")):
try:
Path(p).unlink()
except OSError:
pass
if deps.webroot is not None:
threading.Thread(target=tasks._site_rebuild_worker, daemon=True, name="site-rebuild").start()
app.add_middleware(GZipMiddleware, minimum_size=1024)
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"https?://localhost(:\d+)?|https://[a-z0-9-]+\.bincio\.org",
allow_credentials=True,
allow_methods=["GET", "POST", "DELETE"],
allow_headers=["Content-Type"],
)
for _router in [
feed.router,
auth.router,
me.router,
admin.router,
activities.router,
merge.router,
budget.router,
download.router,
uploads.router,
segments.router,
strava.router,
garmin.router,
ideas.router,
ogimage.router,
gear.router,
]:
app.include_router(_router)