A Flask-based web application for managing areas in the BTC Map ecosystem. Provides an administrative interface for creating, editing, and validating geographical areas with metadata.
- Python 3.11+
- uv package manager
pip install uvuv venv
source .venv/bin/activate
uv syncpython3 main.pyOr via run script: ./run.sh
Production (gunicorn):
gunicorn app:app- Interactive Maps: Leaflet-based map visualization with GeoJSON editing
- Real-time Validation: Client and server-side validation for area data
- RPC Integration: JSON-RPC communication with BTC Map API
- Nostr Authentication: NIP-98 HTTP Auth via NIP-07 browser extensions
- Session Management: Flask-Login with server-side filesystem sessions
- Area Linting: Automated linting rules for area data quality
├── app.py # Flask application (routes, validation, RPC)
├── auth.py # Nostr authentication blueprint
├── main.py # Application entry point
├── linting.py # Area linting system and cache
├── models.py # User model for Flask-Login
├── nostr_utils.py # NIP-01 & NIP-98 event verification
├── user_store.py # Encrypted JSON user storage
├── pyproject.toml # Dependencies (uv)
├── static/
│ ├── css/
│ └── js/
├── templates/
│ ├── base.html
│ ├── login.html
│ ├── select_area.html
│ ├── show_area.html
│ ├── add_area.html
│ ├── profile.html
│ ├── error.html
│ ├── maintenance.html
│ └── components/
├── flask_session/ # Server-side session storage (auto-generated)
└── users.json # Encrypted user data (auto-generated)
- Base URL:
https://api.btcmap.org - Protocol: JSON-RPC 2.0
- Auth: Per-user RPC token via Bearer header
- Timeout: 20 seconds per request
| Method | Description |
|---|---|
search |
Search areas by query |
get_area |
Retrieve area details |
add_area |
Create new area |
set_area_tag |
Update/add area tag |
remove_area_tag |
Remove area tag |
remove_area |
Delete area |
Uses NIP-98 HTTP Auth with Nostr browser extensions (Alby, nos2x, etc.):
- User signs a kind 27235 event via
window.nostr.signEvent() - Server validates event (signature, timestamp within 60s, URL/method binding)
- User's RPC token is stored encrypted at rest with Fernet
GET /login- Login pagePOST /auth/nostr/login- NIP-98 Nostr loginPOST /auth/btcmap/login- Login with BTC Map credentialsGET /auth/logout- Logout
| Field | Type | Notes |
|---|---|---|
name |
text | Non-empty |
type |
select | Must be "community" |
url_alias |
text | URL-friendly identifier |
continent |
select | africa, asia, europe, north-america, oceania, south-america |
icon:square |
text | Icon identifier |
population |
number | Non-negative integer |
population:date |
date | YYYY-MM-DD format |
geo_json |
GeoJSON | Valid Polygon/MultiPolygon |
Contact fields (contact:website, contact:email, contact:phone, etc.), area_km2 (auto-calculated), organization, language, description, tips:lightning_address.
Validation functions in app.py return (is_valid, error_message). GeoJSON validation uses Shapely for geometry checking and geojson-rewind for correct orientation. Client-side validation in static/js/validation.js mirrors server logic.
LintRule/LintResultdataclasses for rule definitions and issue reportingLintCachefor global area linting- Auto-fix functions in
FIX_ACTIONSdict - Add new rules: define in
LINT_RULES, create check function, add tolint_area()check list
- Python:
snake_casefor functions/variables,PascalCasefor classes, type hints where beneficial - Flask: Clear endpoint names,
app.loggerfor logging, proper HTTP status codes - JS: Vanilla JS, follow existing patterns in
static/js/ - Templates: Jinja2 with Bootstrap 5 classes, Leaflet.js maps via CDN
Core: flask, flask-session, flask-login, requests, geojson-rewind, shapely, pyproj, cryptography, nostr-sdk