b781193d44
- 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)
80 lines
1.8 KiB
Python
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)
|