No description
  • Python 83.3%
  • HTML 9%
  • CSS 5.5%
  • Dockerfile 2.2%
Find a file
2026-08-25 13:40:32 +02:00
app Add RootPathPrefixMiddleware to handle requests with or without ROOT_PATH prefix 2026-08-25 13:40:32 +02:00
quadlet Add support for serving app under a sub-path and update documentation 2026-08-25 13:35:58 +02:00
tests first commit 2026-08-25 13:29:12 +02:00
tools first commit 2026-08-25 13:29:12 +02:00
.containerignore first commit 2026-08-25 13:29:12 +02:00
.env.example Add support for serving app under a sub-path and update documentation 2026-08-25 13:35:58 +02:00
.gitignore first commit 2026-08-25 13:29:12 +02:00
_probe2.py Add support for serving app under a sub-path and update documentation 2026-08-25 13:35:58 +02:00
Containerfile first commit 2026-08-25 13:29:12 +02:00
README.md Add RootPathPrefixMiddleware to handle requests with or without ROOT_PATH prefix 2026-08-25 13:40:32 +02:00
requirements.txt first commit 2026-08-25 13:29:12 +02:00

Brandflyg statistik

Web app that shows brandflyg statistics per pilot/spanare for a selected year (defaults to the current year), based on data from the myWebLog API v4.

Per person it shows number of missions (total / as pilot / as spanare) and flight time (total / as pilot / as spanare).

Run locally

Put your token in a .env file in the repo root (it is git-ignored, see .env.example):

MWL_API_TOKEN=<your token>

Then:

py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe -m uvicorn app.main:app --reload --port 8080

The app reads .env on startup, so no environment variables need to be set manually. A real environment variable always wins over the value in .env.

Open http://localhost:8080/ . JSON is available at /api/stats?year=2026, health at /healthz.

Configuration

All settings are environment variables, see .env.example. Only MWL_API_TOKEN is required.

Variable Default Purpose
MWL_API_TOKEN Bearer token for the myWebLog API
MWL_API_BASE_URL https://api.myweblog.se/main/v4 API base URL
BRANDFLYG_FLIGHT_TYPE_IDS type_of_flight.id values counted as brandflyg (takes precedence)
BRANDFLYG_KEYWORDS brand Fallback keyword match on type_of_flight.name
PILOT_ROLES pilot,pic users[].role values counted as pilot
SPANARE_ROLES scout,spanare,observer users[].role values counted as spanare
FLIGHT_TIME_SOURCES airborne,block,tach,hobbs summary.total_{source}_minutes used as flight time, first match wins
YEARS_BACK 10 Number of years offered in the year selector
CACHE_TTL_SECONDS 300 In-memory cache lifetime for API responses
ROOT_PATH Sub-path the app is served under, e.g. /subfolder/app

The mapping is verified against live data: a flight log entry carries type_of_flight, departure.takeoff_datetime, summary.total_airborne_minutes and a users array where each member has a role (Pilot, Scout, Instructor). Entries whose flight type name contains "brand" (BRANDFLYG O1, FFK-BRANDBEVAKNING) are counted; instructors are ignored. To inspect the live data yourself (also uses .env):

.\.venv\Scripts\python.exe -m tools.probe_api 2026

Build the container

podman build -t brandflyg-statistik:latest -f Containerfile .

Run with Podman Quadlet (rootless systemd user unit)

mkdir -p ~/.config/containers/systemd ~/.config/brandflyg-statistik
cp quadlet/brandflyg-statistik.container ~/.config/containers/systemd/
cp .env.example ~/.config/brandflyg-statistik/app.env   # then fill in MWL_API_TOKEN
chmod 600 ~/.config/brandflyg-statistik/app.env

systemctl --user daemon-reload
systemctl --user start brandflyg-statistik.service
systemctl --user status brandflyg-statistik.service
journalctl --user -u brandflyg-statistik.service -f

For a system-wide unit put the file in /etc/containers/systemd/, point EnvironmentFile at an absolute path such as /etc/brandflyg-statistik/app.env and use systemctl without --user. To keep the service running after logout: loginctl enable-linger $USER.

The app listens on port 8080 in the container; change the host port via PublishPort in the Quadlet file. Put a TLS-terminating reverse proxy in front of it if exposed outside the host.

Hosting under a sub-path

Set ROOT_PATH to serve the app from e.g. https://example.com/subfolder/app/:

ROOT_PATH=/subfolder/app

All generated links (year form, static files, JSON link) then include the prefix. Both proxy styles work: one that strips the prefix before forwarding, and one that forwards the full path — requests are accepted with or without the prefix. /healthz therefore also stays reachable without the prefix, so the container health check keeps working.

nginx example:

location /subfolder/app/ {
    proxy_pass http://127.0.0.1:8080/subfolder/app/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Tests

.\.venv\Scripts\python.exe -m pytest -q