BTC Map August Recap

September 1, 2026  |  BTC Map  ·  Projects

Note: this only reflects my personal work this month. Visit the BTC Map Blog for consolidated monthly reports.

My previous monthly recap

Thanks

Thanks to Nathanael for testing the new geofences and admin interface in Switzerland.

API

The API absorbed the bulk of my time again this month. The big theme was access control, since we need to decentralize admin work and let locals maintain their own data. A new area_manager role and a per-user geofence that constrains both area and event managers to a region they are actually responsible for.

Geofences

I added a geofence column to the user table that holds a comma-separated list of area ids. When non-empty, its behavior is role-dependent:

  • event_manager callers are rejected if the event’s area_id or (lat, lon) falls outside every fenced area’s GeoJSON.
  • area_manager callers can only mutate areas with full id match regardless of GeoJSON, so they can’t touch nested areas, and add_area call is rejected outright (admins and roots still create areas on request).
  • admin and root are exempt, and an empty geofence means unrestricted.

A new set_user_geofence RPC sets or clears the whitelist.

Area Manager

The new Role::AreaManager unlocks add_area, get_area, set_area_tag, set_area_image, remove_area_tag, remove_area and search.

Place Reports

I added a new fire-and-forget report_place RPC that lets trusted external sources record typed reports (verified, refused_sats, out_of_business, …) against an existing BTC Map place. It comes with a new place_report table (place_id, origin_id, extra_fields JSON, ticket_url (populated by a future background job, not user-facing), and the usual soft-delete columns).

I also added the public GET /v4/place-submissions endpoint, which exposes open, non-revoked rows from place_submission with an optional source= filter.

RPC Refactor

Most area RPCs were moved from the flat src/rpc/ into dedicated src/rpc/area/ and src/rpc/event/ submodules. It is mostly churn, but it makes the directory tree match the docs tree.

I also renamed generate_bboxes to generate_area_bboxes for consistency, and generate_element_icons now iterates every element in the database via select_updated_since(UNIX_EPOCH, ...) instead of looping over an id range.

Docs

The area RPC docs were split from a single area-methods.md into per-method files under docs/rpc/area/, mirroring the structure already used by docs/rpc/event/. Each file follows the event pattern: Description, Params, Result Format, Allowed Roles, and Examples. I also added an event RPC API index page and refreshed the main index pages.

Auth & Logging

Handlers (and the rest::auth Auth extractor) now write the resolved user id into the request extensions via AuthenticatedUser. The Log middleware reads it back so the request table records who is calling each endpoint instead of always leaving user_id NULL.

I also replaced the raw Query returned no rows error that surfaced when calling an RPC with an unknown or revoked bearer token. It’s now a friendly Invalid bearer token JSON-RPC error, with focused tests for both cases.

Electrum

A new ping_electrum_servers RPC probes every configured electrum server in parallel with the standard server.ping JSON-RPC request and reports per-server success/failure.

Each server has a 5-second timeout. The outer call never fails because of a single unhealthy server — the results just carry ok + latency_ms or ok=false + error. Allowed for admin and root roles.

Area BBox Sync

add_area and set_area_tag (when applied with geo_json) used to leave the bbox_* columns stale until someone manually ran generate_bboxes. Both paths now derive the bbox from the stored GeoJSON via the existing BboxGenerator and update the columns in the same transaction. set_area_tag also validates geo_json before persisting it.

Maintenance

  • Pinned the build target CPU
  • Fixed a pile of clippy lints
  • Counted only active area_elements in generate_reports (it used to include soft-deleted ones)
  • Filtered soft-deleted area_elements from area-scoped feeds in new_places_for_area and get_comments, with a regression test
  • Fixed started_at being overwritten in the generate_reports response
  • Cleaned up several README files

CLI

The CLI picked up wrappers for most of the new API surface:

  • place report calls the new report_place RPC (the old import module was renamed to place to match)
  • electrum-server ping wraps ping_electrum_servers with an --include-deleted flag matching get_electrum_servers
  • area add wraps add_area
  • area generate-bboxes wraps the renamed generate_area_bboxes
  • element generate-icons was renamed from generate-element-icons to match the new no-argument RPC signature
  • area generate-element-mapping was renamed from generate-areas-elements-mapping

Dashboard

I hid bots and other non-user traffic from the unique IPs pie chart on the dashboard. They were swamping the slice for actual BTC Map users.

Event Admin

Event editing is now fully wired up. The marker popup got a Rename action that swaps the event name for an inline editor (Enter to save, Esc to cancel) and calls update_event via RPC, and an Edit button that opens the full event form.

I also fixed a shadowed event variable that was preventing update_event from firing, and surfaced the real error message from the API’s error.data envelope (e.g. Location (51.5, -0.1) is outside your geofence) instead of the generic Server error.

Android

Most of my Android work this month was around build infrastructure rather than features.

Build Infrastructure

  • Bumped AGP to 9.3.2 and Gradle wrapper to 9.5.0
  • Added release signing config (previously the signing key lived in CI; it now lives in app/build.gradle.kts and the CI workflow drops its signing block)
  • Added a new beta build type, with a matching app deploy command in the devtools script

Dev Tools

I added a devtools helper script at the repo root that wraps the common emulator and app commands: emulator start/stop, app install/run/uninstall/deploy. The script is documented in AGENTS.md.

OpenStreetMap Attribution

Added a small “© OpenStreetMap contributors” attribution overlay to the map fragment, with a settings toggle to show or hide it.

Bug Fixes & Polish

  • Fixed a ConcurrentModificationException in ViewportCache by snapshotting items before serializing GeoJson when onCameraIdle fires while a previous loadInBounds coroutine is still iterating
  • Updated the Material Symbols font to the 2026-08-28 snapshot, switched glyphs to the outlined variant, and added a handful of new icons (18_up_rating, destruction, dresser, favorite, info, water_drop)

Ansible

I moved the API backups to a new dedicated volume.

Website

I added Skate Spots as a new BTC Map app sponsor. Nothing else.

Blog

I migrated one of my old historical BTC Map posts from this site into the official BTC Map Blog, so all of our consolidated reporting now lives in one place.

Future Plans

A few things on my mind:

  • Cut a new Android release.
  • Keep working on RPC documentation.
  • Continue the RPC submodule migration.
  • Add a dashboard widget for the place-report data once we have enough volume to make it interesting.