diff --git a/bincio/edit/server.py b/bincio/edit/server.py index 207e6f2..eda65cc 100644 --- a/bincio/edit/server.py +++ b/bincio/edit/server.py @@ -786,7 +786,10 @@ async def strava_reset(request: Request) -> JSONResponse: @app.post("/api/upload/strava-zip") -async def upload_strava_zip(file: UploadFile = File(...)) -> StreamingResponse: +async def upload_strava_zip( + file: UploadFile = File(...), + private: str = Form(default="false"), +) -> StreamingResponse: """Accept a Strava bulk export ZIP and stream SSE progress while processing. The ZIP is written to a temp file, processed activity-by-activity, then deleted. @@ -795,6 +798,8 @@ async def upload_strava_zip(file: UploadFile = File(...)) -> StreamingResponse: if not file.filename or not file.filename.lower().endswith(".zip"): raise HTTPException(400, "Please upload a .zip file") + privacy = "private" if private.lower() in ("true", "1", "yes") else "public" + dd = _get_data_dir() import tempfile tmp = tempfile.NamedTemporaryFile(suffix=".zip", delete=False, dir=dd) @@ -811,7 +816,7 @@ async def upload_strava_zip(file: UploadFile = File(...)) -> StreamingResponse: def event_stream(): any_imported = False try: - for event in strava_zip_iter(zip_path, dd): + for event in strava_zip_iter(zip_path, dd, privacy=privacy): yield f"data: {json.dumps(event)}\n\n" if event.get("type") == "progress" and event.get("status") == "imported": any_imported = True diff --git a/bincio/extract/strava_zip.py b/bincio/extract/strava_zip.py index 65777e7..8eda4d2 100644 --- a/bincio/extract/strava_zip.py +++ b/bincio/extract/strava_zip.py @@ -34,6 +34,7 @@ def strava_zip_iter( zip_path: Path, data_dir: Path, originals_dir: Optional[Path] = None, + privacy: str = "public", ) -> Generator[dict, None, None]: """Process a Strava export ZIP, yielding SSE-style progress dicts. @@ -120,7 +121,7 @@ def strava_zip_iter( orig_dest = originals_dir / entry_name shutil.copy2(tmp_path, orig_dest) - ingest_parsed(parsed, data_dir, privacy="public") + ingest_parsed(parsed, data_dir, privacy=privacy) imported += 1 yield {"type": "progress", "n": n, "total": total, "name": display_name, "status": "imported"} diff --git a/bincio/serve/server.py b/bincio/serve/server.py index 7c1f297..c26292d 100644 --- a/bincio/serve/server.py +++ b/bincio/serve/server.py @@ -590,6 +590,7 @@ async def upload_activity( @app.post("/api/upload/strava-zip") async def upload_strava_zip( file: UploadFile = File(...), + private: str = Form(default="false"), bincio_session: Optional[str] = Cookie(default=None), ) -> StreamingResponse: """Accept a Strava bulk export ZIP and stream SSE progress while processing. @@ -601,6 +602,8 @@ async def upload_strava_zip( if not file.filename or not file.filename.lower().endswith(".zip"): raise HTTPException(400, "Please upload a .zip file") + privacy = "private" if private.lower() in ("true", "1", "yes") else "public" + dd = _get_data_dir() / user.handle import tempfile tmp = tempfile.NamedTemporaryFile(suffix=".zip", delete=False, dir=dd) @@ -617,7 +620,7 @@ async def upload_strava_zip( def event_stream(): any_imported = False try: - for event in strava_zip_iter(zip_path, dd): + for event in strava_zip_iter(zip_path, dd, privacy=privacy): yield f"data: {json.dumps(event)}\n\n" if event.get("type") == "progress" and event.get("status") == "imported": any_imported = True diff --git a/site/src/layouts/Base.astro b/site/src/layouts/Base.astro index 9a36962..b531a45 100644 --- a/site/src/layouts/Base.astro +++ b/site/src/layouts/Base.astro @@ -338,6 +338,11 @@ try {
Drop your Strava export .zip
or click to browse
+

@@ -447,6 +452,7 @@ try { const zipInput = document.getElementById('zip-input'); const zipLabel = document.getElementById('zip-label'); const zipStatus = document.getElementById('zip-status'); + const zipPrivate = document.getElementById('zip-private'); const drop = document.getElementById('upload-drop'); const input = document.getElementById('upload-input'); const label = document.getElementById('upload-label'); @@ -690,6 +696,7 @@ try { const fd = new FormData(); fd.append('file', file); + fd.append('private', zipPrivate?.checked ? 'true' : 'false'); // POST the file; server responds with SSE stream immediately after receiving body const xhr = new XMLHttpRequest(); diff --git a/site/src/pages/invites/index.astro b/site/src/pages/invites/index.astro index 0262565..f8e76c2 100644 --- a/site/src/pages/invites/index.astro +++ b/site/src/pages/invites/index.astro @@ -47,6 +47,17 @@ import Base from '../../layouts/Base.astro'; return li; } + function fallbackCopy(text: string, done: () => void) { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + try { document.execCommand('copy'); done(); } catch (_) {} + document.body.removeChild(ta); + } + async function loadInvites() { try { const r = await fetch('/api/invites', { credentials: 'include' }); @@ -66,9 +77,16 @@ import Base from '../../layouts/Base.astro'; // Copy link buttons listEl.querySelectorAll('.copy-btn').forEach(btn => { btn.addEventListener('click', () => { - navigator.clipboard.writeText((btn as HTMLElement).dataset.link ?? ''); - btn.textContent = 'Copied!'; - setTimeout(() => { btn.textContent = 'Copy link'; }, 2000); + const text = (btn as HTMLElement).dataset.link ?? ''; + const done = () => { + btn.textContent = 'Copied!'; + setTimeout(() => { btn.textContent = 'Copy link'; }, 2000); + }; + if (navigator.clipboard) { + navigator.clipboard.writeText(text).then(done).catch(() => fallbackCopy(text, done)); + } else { + fallbackCopy(text, done); + } }); }); } catch (e: any) {