some basic statistics and invite tree, plus watch new data

This commit is contained in:
Davide Scaini
2026-04-10 13:21:31 +02:00
parent 6b2d31a44a
commit 053da10ab9
9 changed files with 655 additions and 5 deletions
+135
View File
@@ -1,5 +1,140 @@
# Changelog
## [Unreleased] — 2026-04-10
### New feature — Per-instance user limit
Operators can now cap the maximum number of registered users on an instance.
- **`bincio/serve/db.py`**
- New `settings` table (key/value, upsert-safe via `ON CONFLICT DO UPDATE`).
- `count_users(db)` — returns total number of rows in `users`.
- `get_setting(db, key)` / `set_setting(db, key, value)` — generic persistent settings store.
- **`bincio/serve/server.py`** — `POST /api/register` now reads the `max_users` setting; if
set to N > 0 and the current user count is already ≥ N, registration is rejected with
HTTP 403 and a clear message. Imports `count_users` and `get_setting`.
- **`bincio/serve/init_cmd.py`** — new `--max-users N` flag (default 0 = unlimited). Saves
the value to the `settings` table via `set_setting`. Printed in the init summary.
- **`bincio/serve/cli.py`** — new `--max-users N` flag on `bincio serve`. Writes to the DB
on startup (lets operators change the limit without re-running `bincio init`). Startup
banner now shows `Users: max N` or `Users: unlimited`.
---
### New feature — Original file storage option (upload & Strava sync)
Users can now choose whether to keep their source files on the server after processing.
Keeping originals allows reprocessing if the pipeline improves; discarding them is the
privacy-conscious choice. Previously, uploaded files were always deleted after processing.
- **`bincio/serve/db.py`** — `store_originals` is stored as a settings key. `bincio init`
writes `store_originals=true` on first run.
- **`bincio/serve/server.py`** — `POST /api/upload` accepts a new `store_original: bool`
form field. On success, if true, the staged file is moved to `{user_dir}/originals/`
instead of being deleted. `GET /api/me` now includes `store_originals_default: bool`
(read from the instance setting) so the frontend can pre-populate the checkbox.
`POST /api/strava/sync` checks the `store_originals` instance setting; if true, creates
`{user_dir}/originals/strava/` and passes it as `originals_dir` to `run_strava_sync`.
- **`bincio/edit/server.py`** — `POST /api/upload` gains the same `store_original` form
field with identical behaviour (originals stored in `{data_dir}/originals/`).
- **`bincio/edit/ops.py`** — `run_strava_sync` gains an `originals_dir: Optional[Path]`
parameter, passed through to `ingest.strava_sync`.
- **`bincio/extract/ingest.py`** — `strava_sync` gains `originals_dir: Optional[Path]`.
When set, saves `{"meta": …, "streams": …}` as JSON to
`originals_dir/{activity_id}.json` before processing each activity. This preserves the
raw Strava API response for future reprocessing without needing another API call.
- **`bincio/serve/init_cmd.py`** — sets `store_originals=true` in the settings table on
first init (skipped if the key already exists, so re-running init doesn't override
an operator's choice).
- **`site/src/layouts/Base.astro`** — upload modal file view gains a "Keep original file on
server" checkbox. Defaults to unchecked; pre-checked after login if the instance setting
is `true` (read from `store_originals_default` in the `/api/me` response). The checkbox
value is sent as the `store_original` form field.
- **`bincio/serve/server.py`** and **`bincio/edit/server.py`** — `Form` added to the
FastAPI imports (was missing, causing a startup `NameError`).
---
### New feature — About page (multilingual)
New static `/about/` page explaining the project, with a Ko-fi donation button, data
storage disclaimer, and early-software caveats. Available in four languages.
- **`site/src/pages/about/index.astro`** — English
- **`site/src/pages/about/it/index.astro`** — Italian
- **`site/src/pages/about/es/index.astro`** — Spanish
- **`site/src/pages/about/ca/index.astro`** — Catalan
All four pages share the same structure:
- Language switcher (EN / IT / ES / CA) in the top-right corner.
- Ko-fi donation button (`https://ko-fi.com/brutsalvadi`) at the top.
- **Community stats section** — fetches `GET /api/stats` on load; shown only in
multi-user mode (silently hidden in single-user mode where the endpoint doesn't exist).
Displays total member count and an indented invitation tree: each row shows display name,
`@handle`, membership duration (days / months), and either "founder" or "invited by @X".
UI labels are fully translated per language.
- Sections: What is this · Your data on this server · Early-stage software · Disclaimer ·
Open source.
- All pages use `public={true}` so they bypass the instance auth wall.
"About" link added to the main nav bar (visible when not on a public page).
The upload modal's "Keep original file" checkbox links to `/about/` for context.
---
### New feature — Community stats API
- **`bincio/serve/db.py`** — `get_member_tree(db)` joins `users` with `invites` (on
`used_by`) to reconstruct the invitation graph. Returns a list ordered oldest-first with
`handle`, `display_name`, `created_at`, and `invited_by` (inviter handle or `None` for
the founder/admin).
- **`bincio/serve/server.py`** — new public `GET /api/stats` endpoint (no auth required).
Returns `user_count` and a `members` array where each entry includes `handle`,
`display_name`, `member_since` (Unix timestamp), `member_for_days`, and `invited_by`.
---
### Fix — `bincio dev` now watches data directory for live re-merge
Previously, editing a sidecar or running `bincio extract` while `bincio dev` was running
required a manual restart to pick up changes. Now a background watcher thread re-merges
automatically.
- **`bincio/dev.py`** — new `_watch_data(data)` function, started as a daemon thread
alongside `bincio serve`. Uses `watchfiles` (already bundled with `uvicorn[standard]`,
no new dependency) for OS-level file event watching — no polling.
- Watches every `{user_dir}/edits/` and `{user_dir}/activities/` directory.
- On any change, identifies which users were affected and calls `merge_all(user_dir)`
for each.
- Skips churn files written by merge itself (`.timeseries.json`, `.geojson`,
`index.json`) to avoid re-triggering.
- Prints `↺ {handle}: merged` on each successful re-merge; warns on failure.
- Astro dev picks up the result automatically since `public/data` is a symlink into
the live data directory.
---
### Tests
- **`tests/test_server_imports.py`** (new) — smoke tests that import `bincio.serve.server`
and `bincio.edit.server` at module level, catching `NameError`, missing imports, and
syntax errors before they reach the runtime. Also asserts that key routes (`/api/me`,
`/api/upload`, `/api/strava/sync`, `/api/register`, `/api/activity/{activity_id}`) are
registered on each app.
---
## [Unreleased] — 2026-04-06
### New feature — Strava sync from UI