From c68dfa90573ea7b3b3d15ae1f120f87abf0c38bb Mon Sep 17 00:00:00 2001 From: Davide Scaini Date: Thu, 16 Apr 2026 18:09:32 +0200 Subject: [PATCH] chore: update changelog, remove stale files, scrub VPS IP - CHANGELOG.md: add [Unreleased] 2026-04-16 section covering settings page, admin tools, password reset, re-extract, community page, SSE upload progress, and all bug fixes since 2026-04-10 - Remove docs-proposal.md (internal planning doc, not user-facing) - Remove publish/ directory (leftover artefacts from publish.sh, not meant to be tracked) - scripts/pull_feedback.sh: replace hardcoded default VPS IP with a required positional argument to avoid leaking server address - docs/squash-for-github.md: document the squash-for-github commit strategy for future reference --- CHANGELOG.md | 95 ++++++++++++ docs-proposal.md | 113 -------------- docs/squash-for-github.md | 78 ++++++++++ publish/CLAUDE.md | 223 ---------------------------- publish/extract_config.example.yaml | 31 ---- publish/manifest | 72 --------- scripts/pull_feedback.sh | 4 +- 7 files changed, 175 insertions(+), 441 deletions(-) delete mode 100644 docs-proposal.md create mode 100644 docs/squash-for-github.md delete mode 100644 publish/CLAUDE.md delete mode 100644 publish/extract_config.example.yaml delete mode 100644 publish/manifest diff --git a/CHANGELOG.md b/CHANGELOG.md index 16ce436..d608c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,100 @@ # Changelog +## [Unreleased] — 2026-04-16 + +### New feature — Self-service user settings page + +- **`site/src/pages/settings/index.astro`** — new `/settings/` page with three sections: + - **Account** — display name editor, storage quota view (uploaded activities + originals size) + - **Integrations** — per-user Strava client ID/secret (replaces instance-level credentials for + multi-user deployments); saved to `settings` table via `PATCH /api/me` + - **Danger zone** — two separate destructive actions: + - **Delete originals** — removes `{user_dir}/originals/` without touching activities + - **Delete all activities** — wipes all activities, edits, GeoJSON, and `_merged/`; triggers rebuild + - Nav visibility toggles — user can hide any combination of Feed / Stats / Athlete tabs from + their navigation; preference saved to `settings` table and applied in `Base.astro` + +### New feature — Upload overwrite option + +- **`POST /api/upload`** — new `overwrite: bool` form field; when true, an existing activity + with the same ID is replaced rather than returning 409. UI checkbox added to the upload modal. + +### New feature — Admin tools + +- **Ghost user detection** — `/admin/` now marks users whose handle has a data directory but + no entry in the `users` table (e.g. manually created dirs, or users deleted from DB); shown + with a "ghost" badge +- **Delete directory button** — admin can delete a user's entire data directory without + touching the DB entry; useful for cleaning up ghost dirs or corrupted accounts +- **Delete all activities** (`DELETE /api/admin/users/{handle}/activities`) — wipes + `activities/`, `edits/`, `_merged/`, and `index.json` for a handle, then triggers a rebuild; + admin page shows a confirmation `` before firing +- **"Admin" nav link** — visible in the top-right for admins only + +### New feature — Password reset (admin-generated one-time code) + +No email infrastructure required. Flow: + +1. Admin visits `/admin/` → clicks "Reset pwd" → a 24-hour code appears inline (click to copy) +2. Admin sends it out-of-band (Signal, Telegram, etc.) +3. User goes to `/reset-password/`, enters handle + code + new password + +- `POST /api/admin/users/{handle}/reset-password-code` (admin) → `{code, expires_in_hours: 24}` +- `POST /api/auth/reset-password` (public) → body `{handle, code, password}` +- `reset_codes` table in `instance.db`; generating a new code invalidates prior unused codes; + used codes kept for audit + +### New feature — Re-extract from Strava originals + +- **`POST /api/admin/reextract`** — re-runs the extract pipeline over all + `{user_dir}/originals/strava/*.json` files without hitting the Strava API again; + streams progress via SSE; useful after pipeline improvements +- Runs as a subprocess to avoid OOM (`malloc_trim` + `gc.collect` every 50 activities); + processes in batches of 100 to bound peak RSS + +### New feature — Community page + +- **`/community/` tab** — sortable table of all registered users: display name, handle, + member since, invited by; replaces the earlier inline community section on the about page + +### New feature — Streaming upload progress + +- **`POST /api/upload`** now returns `text/event-stream` instead of JSON +- Per-file progress events: `↓ 3/47 (6%) — morning_ride.fit` +- Final `done` event: `"12 added, 35 duplicates"` +- Vite proxy configured to not buffer the stream + +### Bug fixes + +- **`elevation_gain_m` null for modern Garmin FIT files** — session message `total_ascent` + field now read as fallback when per-point elevation gain is zero +- **Map flash on activity detail** — map container height set before `fitBounds` to prevent + a zero-height frame during load +- **Absolute `track_url` / `detail_url` paths** — `ActivityDetail` and `loadActivity` now + handle both relative and absolute paths in BAS JSON +- **Corrupted time streams causing OOM** — `metrics.py` guards against non-monotonic or + pathologically large time arrays before allocating the 1 Hz dense array +- **Merge race condition** — `merge_all` wipe + rewrite is now guarded; concurrent upload + triggers can no longer interleave a `shutil.rmtree` with a write from another request +- **Temp ZIP leak** — upload temp files now written to `/tmp/` and always deleted in a + `finally` block; a startup hook auto-cleans any leftovers +- **`bincio init` always overwrites `private`** — fixed to preserve existing value when + `index.json` already exists +- **Auth wall flash** — `Base.astro` now sets the auth state synchronously from a cookie + hint before the `fetch('/api/me')` resolves, eliminating the visible flash +- **Single-user redirect loop** — `index.astro` no longer redirects to `/u/{handle}/` on + private (multi-user) instances +- **Theme-aware Plot tooltips** — forced black text on white background; was rendering + grey-on-white (unreadable in light mode) and white-on-dark (unreadable in dark mode) +- **Theme-aware chart axis colors** — axis labels and tick marks now use the correct + foreground color in both light and dark themes +- **TS type annotation in `define:vars` script** — removed; Astro injects `define:vars` + blocks as plain JS, not TypeScript +- **Image refs with spaces/parens in filenames** — local image references in markdown + descriptions are now stripped before rendering to avoid broken inline `` tags + +--- + ## [Unreleased] — 2026-04-10 ### New feature — Per-instance user limit diff --git a/docs-proposal.md b/docs-proposal.md deleted file mode 100644 index bcfe977..0000000 --- a/docs-proposal.md +++ /dev/null @@ -1,113 +0,0 @@ -# Documentation proposal - -## Problem - -The project has no user-facing or developer-facing docs. Knowledge lives in `CLAUDE.md` -(written for AI context, not humans), scattered inline comments, and the code itself. -As the feature surface grows and more users join, we need: - -- A guide for **users** (how to upload, sync, edit, manage privacy) -- A guide for **admins** (how to run an instance, manage users, reset passwords) -- An **API reference** (what endpoints exist, what they expect, what they return) -- A **developer guide** (how to run locally, architecture, how to contribute) - ---- - -## Proposed structure - -``` -docs/ - index.md Overview and quick links - user-guide.md End-user: upload, sync, edit, privacy, settings - admin-guide.md Admin: deploy, init, invite users, reset passwords, rebuild - api.md API reference (hand-written, augmented by OpenAPI) - architecture.md BAS schema, data flow, shard model, federation - developer-guide.md Local setup, how to run tests, how to contribute -``` - -`CLAUDE.md` stays as-is — it is AI context, not user docs. The two serve different -audiences and should not be merged. - ---- - -## API documentation strategy - -FastAPI auto-generates an OpenAPI 3.1 spec from the route decorators. It is already -served at `/api/docs` (Swagger UI) and `/api/redoc` (ReDoc) when the server is running. -Right now the auto-docs are sparse because: - -- Most endpoints return bare `JSONResponse` instead of typed Pydantic response models -- Endpoint docstrings are minimal or absent -- Request bodies are raw `request.json()` instead of Pydantic models - -### Recommended approach: two-layer docs - -**Layer 1 — machine-readable (OpenAPI, auto-generated)** - -Incrementally add Pydantic request/response models to the endpoints that matter most -(auth, activity CRUD, admin actions). FastAPI will pick them up automatically and the -Swagger UI becomes usable. No extra tooling needed. - -Priority endpoints to type first: -- `POST /api/auth/login` / `logout` / `reset-password` -- `POST /api/register` -- `GET /api/me` -- `GET|POST /api/activity/{id}` -- `DELETE /api/activity/{id}` -- `POST /api/admin/users/{handle}/reset-password-code` -- `GET|POST /api/me/preferences` (once built) - -**Layer 2 — human-readable (`docs/api.md`)** - -A hand-written reference that groups endpoints by domain (auth, activities, admin, -sync), explains the overall auth model (cookie-based, httpOnly), rate limiting, and -covers things OpenAPI can't express well (SSE streams, error semantics, side effects -like rebuild triggers). - -The OpenAPI spec and the hand-written doc are complementary, not duplicates: -OpenAPI is precise and machine-readable; `api.md` gives context and explains *why*. - ---- - -## Tooling options - -| Option | Pros | Cons | -|--------|------|------| -| Plain markdown in `docs/` | Zero tooling, lives in repo, renders on GitHub | No search, no versioning, no sidebar nav | -| MkDocs + Material theme | Beautiful, search, auto-nav from folder structure, can embed OpenAPI via plugins | Needs Python dep + build step; another thing to deploy | -| Docusaurus | Great for open-source projects, versioning, i18n | Node toolchain, heavier | -| VitePress | Fast, Vite-based (already in the stack), markdown + Vue | Still a separate site to host | -| Just the Swagger UI at `/api/docs` | Auto-generated, always up-to-date | Only covers the API, not user/admin/architecture | - -**Recommendation:** Start with plain markdown in `docs/` — no build step, always -available, no new infrastructure. If the project goes public or the user base grows, -migrate to MkDocs Material (one `mkdocs.yml` + `pip install mkdocs-material`). - -For the API specifically: enable the Swagger UI on the live server (currently it may -be disabled in production) so admins can explore it directly at `/api/docs`. - ---- - -## Enabling Swagger UI in production - -By default FastAPI serves `/docs` and `/redoc`. In `bincio serve`, the FastAPI app is -created with: - -```python -app = FastAPI(docs_url=None, redoc_url=None) # check current value -``` - -For a private instance (auth-walled), it is safe to expose `/api/docs` — add a note -in `admin-guide.md` that it exists. Alternatively, serve it only when an env var is set. - ---- - -## Suggested first milestone - -1. Create `docs/` with `index.md`, `admin-guide.md`, `api.md` -2. `admin-guide.md`: deploy, init, invite, password reset, rebuild, reset data -3. `api.md`: auth endpoints + activity endpoints, hand-written -4. Enable Swagger UI on the server (or at least document that it exists at `/api/docs`) -5. Add Pydantic models to the 8 priority endpoints above - -Everything else (user guide, architecture, developer guide, MkDocs) is second milestone. diff --git a/docs/squash-for-github.md b/docs/squash-for-github.md new file mode 100644 index 0000000..53a6460 --- /dev/null +++ b/docs/squash-for-github.md @@ -0,0 +1,78 @@ +# squash-for-github branch strategy + +`squash-for-github` is a curated public-facing branch. It has its own orphan +history (unrelated to `main`) and grows by appending one large squash commit +each time you want to publish a batch of work. + +## When to use + +Whenever `main` has accumulated enough work worth publishing — typically after a +meaningful feature set or before tagging a release. + +## How it works + +`squash-for-github` and `main` have completely unrelated histories (different +root commits). Because of this, `git merge --squash` won't work. Instead, use +`git commit-tree` to create a new commit that carries **main's file tree** but +is **parented to the current squash-for-github tip**. + +## Steps + +1. **Collect commit messages** to write the summary: + + ```bash + git log --oneline main ^squash-for-github + ``` + +2. **Switch to the branch:** + + ```bash + git checkout squash-for-github + ``` + +3. **Create the squash commit** (replace the message with your summary): + + ```bash + NEW=$(git commit-tree main^{tree} -p HEAD -m "feat: your summary here") + git reset --hard $NEW + ``` + + Or as a one-liner with a heredoc for a multi-line message: + + ```bash + git reset --hard $(git commit-tree main^{tree} -p HEAD -m "$(cat <<'EOF' + feat: short title + + - bullet one + - bullet two + EOF + )") + ``` + +4. **Verify:** + + ```bash + git log --oneline squash-for-github | head -5 + ``` + +5. **Push** when ready: + + ```bash + git push origin squash-for-github + # or force-push if you've rewritten history on the remote: + git push --force origin squash-for-github + ``` + +6. **Return to main:** + + ```bash + git checkout main + ``` + +## Why not `git merge --squash`? + +The two branches share no common ancestor, so git refuses with +`fatal: refusing to merge unrelated histories`. `git commit-tree` bypasses this +by directly constructing the commit object: it takes the tree (file snapshot) +from `main`, sets the parent to the current `squash-for-github` tip, and +attaches your custom message — no merge machinery needed. diff --git a/publish/CLAUDE.md b/publish/CLAUDE.md deleted file mode 100644 index 683fec3..0000000 --- a/publish/CLAUDE.md +++ /dev/null @@ -1,223 +0,0 @@ -# BincioActivity — Context for Claude - -## What this project is - -BincioActivity is a federated, open-source, self-hosted activity stats platform -(think personal Strava). Two-stage pipeline: - -1. **`bincio extract`** (Python): GPX/FIT/TCX → BAS JSON data store -2. **`bincio render`** (Astro/Node): BAS data store → static website - -The BAS (BincioActivity Schema) JSON files are the federation protocol. -Anyone can publish their data as BAS JSON and others can include it. - -## Key design decisions - -- **No database, no server** — everything is static files -- **Python with uv** for the extract stage -- **Astro + Svelte + Tailwind + MapLibre GL + Observable Plot** for the site -- **Haversine** (not geopy) for distance calculations (10x faster) -- **Worker initializer pattern** for ProcessPoolExecutor — large shared data - (strava_lookup dict, known_hashes frozenset) is sent once per worker via - `initializer=`, not once per task -- **BAS activity IDs** always use UTC with Z suffix for URL safety -- **TCX files** from Garmin use both `http://` and `https://` namespace URIs — - parser handles both - -## Your data - -- Source: `~/your-activity-data/` - - `activities/` — Strava export (GPX, FIT, TCX, all with .gz variants) - - Any subdirectories with FIT files from Garmin/Karoo devices - - `activities.csv` — Strava metadata (names, descriptions, gear) -- Extracted output: `~/bincio_data/` (or `/tmp/bincio_test/` for testing) - -Configure input paths in `extract_config.yaml`. - -## Project structure - -``` -bincio/ Python package - extract/ - models.py DataPoint, ParsedActivity, LapData - parsers/ GPX, FIT, TCX parsers + factory - sport.py sport name normalisation - metrics.py haversine-based stats computation (single pass) - timeseries.py downsample to 1Hz, build BAS timeseries object - simplify.py RDP track simplification → GeoJSON - dedup.py exact (hash) + near-duplicate detection - strava_csv.py Strava activities.csv importer - writer.py BAS JSON + GeoJSON writer - config.py extract_config.yaml loader - cli.py `bincio extract` CLI - render/ - cli.py `bincio render` CLI (symlinks data, runs astro build/dev) - merge.py sidecar edit overlay (produces _merged/) - edit/ - cli.py `bincio edit` CLI - server.py FastAPI write API for the edit drawer -schema/ - bas-v1.schema.json JSON Schema for BAS -SCHEMA.md Human-readable BAS spec -site/ Astro project - src/ - layouts/Base.astro - pages/ - index.astro Activity feed (loads index.json client-side) - activity/[id].astro Single activity (SSG, loads detail JSON client-side) - stats/index.astro Heatmap + year totals - components/ - ActivityFeed.svelte Card grid, sport filter, pagination - ActivityDetail.svelte Map + stats + charts + photo gallery - ActivityMap.svelte MapLibre GL (gradient track, linked hover dot) - ActivityCharts.svelte Observable Plot (elevation/speed/HR/cadence tabs) - StatsView.svelte Yearly heatmap + totals - EditDrawer.svelte Slide-in edit panel (visible when PUBLIC_EDIT_URL set) - lib/ - types.ts BAS TypeScript types - format.ts formatDistance, formatDuration, sportIcon, etc. -``` - -## How to run - -```bash -# Extract -cd ~/src/bincio_activity -uv run bincio extract --input ~/your-activity-data/activities --output /tmp/bincio_test - -# Site dev server -cd site -ln -sf /tmp/bincio_test/_merged public/data # point at merged output -cp .env.example .env && $EDITOR .env # set BINCIO_DATA_DIR -npm run dev - -# Edit server (optional — enables edit drawer in the site) -uv run bincio edit --data-dir /tmp/bincio_test -# set PUBLIC_EDIT_URL=http://localhost:4041 in site/.env - -# Tests -uv run pytest -``` - -## MapLibre GL + Vite/Astro — known gotchas - -Learnt the hard way during debugging (March 2026): - -- **`maplibregl.workerUrl = ...` is the v3 API and silently no-ops in v4+.** - The v5 API is `maplibregl.setWorkerUrl(url)`, but you don't need it at all in a - normal Vite environment — MapLibre handles the blob worker automatically. - -- **`optimizeDeps: { exclude: ['maplibre-gl'] }` breaks tile loading.** - It prevents Vite from converting MapLibre's UMD bundle to ESM. The UMD bundle - uses AMD `define()` internally; served raw, the tile worker blob fails silently → - black map, no tiles. The correct setting is `include: ['maplibre-gl']`. - -- **`build.target: 'es2022'` (and `optimizeDeps.esbuildOptions.target`) is required.** - MapLibre's dependencies use ES2022 class field syntax. If esbuild downgrades it, - helpers like `__publicField` aren't available inside the serialised worker blob - scope → tile loading fails. This is a known upstream issue (maplibre-gl-js #6680). - -- **Use static imports, not dynamic `await import('maplibre-gl')`, when possible.** - With `client:only="svelte"` in Astro, SSR never runs for the component so there is - no `window is not defined` risk. Static import lets Vite pre-bundle correctly. - -- **Use `client:only="svelte"` (not `client:load`) for the activity detail page.** - `client:load` does SSR + hydration; complex interactive components with MapLibre - can hit hydration mismatch issues. `client:only` mounts fresh on the client only. - -- **MapLibre v5 requires explicit `center` and `zoom` in the Map constructor.** - v4 silently defaulted to `center: [0,0], zoom: 0`. v5 leaves internal projection - state undefined → `Cannot read properties of undefined (reading 'lng')` crashes - on any operation that touches coordinates (markers, resize, render). Always pass - `center` and `zoom` even if you plan to `fitBounds` later. - -- **MapLibre v5 requires `setLngLat()` on markers before `.addTo(map)`.** - v4 tolerated markers without coordinates. v5 calls `Marker._update()` inside - `addTo()`, which needs valid lngLat → same `'lng'` crash. Set a dummy `[0, 0]` - if the real position arrives later (e.g. hover markers). - -## Observable Plot — known gotchas - -- **Curve names are hyphenated, not camelCase.** - Use `"monotone-x"`, not `"monotoneX"`. Plot uses its own curve name registry - (not raw d3 identifiers). Wrong names throw `unknown curve` at runtime. - -The working `astro.config.mjs` Vite section: -```js -vite: { - optimizeDeps: { - include: ['maplibre-gl'], - esbuildOptions: { target: 'es2022' }, - }, - build: { target: 'es2022' }, -}, -``` - -## Activity sidecar edits — design spec - -Users edit activities via **sidecar markdown files** in the data dir. -No database, no server — consistent with the project's static-files-only philosophy. - -### File naming - -``` -~/bincio_data/ - activities/{id}.json ← immutable extract output - edits/{id}.md ← user edits (sidecar) - edits/images/{id}/ ← uploaded photos - _merged/ ← render-time merge output (gitignored-style) -``` - -### Sidecar format - -```markdown ---- -title: "Epic climb up Monte Grappa" -sport: cycling -hide_stats: [cadence] -highlight: true -private: false -gear: "Trek Domane" ---- - -Rode with friends. Legs felt great after the rest week... -``` - -### Editing UX: drawer in Astro + `bincio edit` write API - -- `bincio edit --data-dir ~/bincio_data` starts a FastAPI server on port 4041 -- Set `PUBLIC_EDIT_URL=http://localhost:4041` in `site/.env` to enable the edit button -- Clicking Edit on any activity detail page opens a slide-in drawer -- Saving writes the sidecar and triggers `merge_all()` automatically -- `bincio render` always runs `merge_all()` before build/serve and symlinks `public/data` → `_merged/` - -### `PUBLIC_EDIT_URL` as feature flag - -- **Unset** → no Edit button, normal static site -- **Set** → edit drawer enabled; lives in `site/.env` (gitignored) - -## Known issues / next steps - -- `bincio render` Python CLI is functional but `--watch` mode not yet implemented -- Activity IDs in older test data may use `+0000` format (pre-fix); re-run extract to get `Z` format -- Some activities appear with both untitled and titled IDs (near-dedup timing race) -- Federation (remote data sources) not yet implemented in site -- Friends pages (`/friends/{handle}/`) not yet implemented -- The `site/.env` file is gitignored — copy from `site/.env.example` - -## What "good" looks like (not yet done) - -- [ ] `bincio render` Python CLI wraps `astro build` properly -- [ ] Friends/federation pages in site -- [ ] Personal records page -- [ ] Activity search / full-text filter in feed -- [ ] GitHub Actions template for auto-publish -- [ ] Karoo/Garmin Connect importers beyond Strava -- [x] `bincio.render.merge` — sidecar parser, `_merged/` output, private filter, highlight sort -- [x] `bincio edit` FastAPI write API (GET/POST activity, image upload/delete, triggers merge) -- [x] `EditDrawer.svelte` — slide-in edit UI in the Astro site -- [x] `PUBLIC_EDIT_URL` feature flag -- [x] Markdown rendering in activity description with image path rewriting -- [x] Photo gallery with lightbox on activity detail page -- [ ] `bincio render --watch` incremental rebuild on sidecar/data changes -- [ ] Highlight badge in activity feed cards diff --git a/publish/extract_config.example.yaml b/publish/extract_config.example.yaml deleted file mode 100644 index a02d6f2..0000000 --- a/publish/extract_config.example.yaml +++ /dev/null @@ -1,31 +0,0 @@ -owner: - handle: yourname - display_name: Your Name - -input: - dirs: - - ~/Activities/gpx - - ~/Activities/fit - # Strava bulk export metadata — provides names, descriptions, gear - # metadata_csv: ~/strava_export/activities.csv - -output: - dir: ~/bincio_data - -default_privacy: public - -sensors: - heart_rate: true - cadence: true - temperature: true - power: true - -track: - simplify: rdp - rdp_epsilon: 0.0001 # ~11m at equator - timeseries_hz: 1 # 1 sample/second max - -classifier: - enabled: false # ML activity type classifier (requires scikit-learn extra) - -incremental: true # skip files whose hash hasn't changed since last run diff --git a/publish/manifest b/publish/manifest deleted file mode 100644 index 9e127b4..0000000 --- a/publish/manifest +++ /dev/null @@ -1,72 +0,0 @@ -# BincioActivity — public release manifest -# One relative path per line. -# If publish/ exists, that sanitized version is used instead of the original. - -.gitignore -.python-version -CHANGELOG.md -CHEATSHEET.md -CLAUDE.md -README.md -SCHEMA.md -pyproject.toml -extract_config.example.yaml -schema/bas-v1.schema.json -bincio/__init__.py -bincio/cli.py -bincio/edit/__init__.py -bincio/edit/cli.py -bincio/edit/server.py -bincio/extract/__init__.py -bincio/extract/cli.py -bincio/extract/config.py -bincio/extract/dedup.py -bincio/extract/metrics.py -bincio/extract/models.py -bincio/extract/parsers/__init__.py -bincio/extract/parsers/base.py -bincio/extract/parsers/factory.py -bincio/extract/parsers/fit.py -bincio/extract/parsers/gpx.py -bincio/extract/parsers/tcx.py -bincio/extract/simplify.py -bincio/extract/sport.py -bincio/extract/strava_csv.py -bincio/extract/timeseries.py -bincio/extract/writer.py -bincio/import_/__init__.py -bincio/import_/cli.py -bincio/import_/strava.py -bincio/render/__init__.py -bincio/render/cli.py -bincio/render/merge.py -publish.sh -publish/CLAUDE.md -publish/extract_config.example.yaml -publish/manifest -site/.env.example -site/astro.config.mjs -site/package.json -site/tailwind.config.mjs -site/tsconfig.json -site/src/components/ActivityCharts.svelte -site/src/components/ActivityDetail.svelte -site/src/components/ActivityFeed.svelte -site/src/components/ActivityMap.svelte -site/src/components/AthleteDrawer.svelte -site/src/components/AthleteView.svelte -site/src/components/EditDrawer.svelte -site/src/components/MmpChart.svelte -site/src/components/RecordsView.svelte -site/src/components/StatsView.svelte -site/src/layouts/Base.astro -site/src/lib/format.ts -site/src/lib/types.ts -site/src/pages/activity/[id].astro -site/src/pages/athlete/index.astro -site/src/pages/index.astro -site/src/pages/stats/index.astro -tests/__init__.py -tests/test_merge.py -tests/test_sport.py -tests/test_writer.py diff --git a/scripts/pull_feedback.sh b/scripts/pull_feedback.sh index c0866fb..346ec8e 100755 --- a/scripts/pull_feedback.sh +++ b/scripts/pull_feedback.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # Pull user feedback from the VPS into ./feedback/ locally. -# Usage: bash scripts/pull_feedback.sh [vps-host] (default: root@95.216.55.151) +# Usage: bash scripts/pull_feedback.sh set -e -VPS=${1:-root@95.216.55.151} +VPS=${1:?Usage: $0 user@host} REMOTE=/var/bincio/data/_feedback LOCAL=$(dirname "$0")/../feedback