{"id":42606760,"url":"https://github.com/abrechen2/travstats","last_synced_at":"2026-05-09T21:03:00.596Z","repository":{"id":329405559,"uuid":"1100657628","full_name":"Abrechen2/TravStats","owner":"Abrechen2","description":"Self-hosted flight tracker with interactive 2D/3D maps, 58 achievements, boarding-pass scanner, email/PDF import, and optional local LLM parsing — AGPL-3.0-or-later.","archived":false,"fork":false,"pushed_at":"2026-04-27T19:32:08.000Z","size":69066,"stargazers_count":15,"open_issues_count":10,"forks_count":0,"subscribers_count":0,"default_branch":"Main","last_synced_at":"2026-04-27T20:19:01.243Z","etag":null,"topics":["aviation","deck-gl","docker","flight-tracker","nodejs","postgis","react","self-hosted","typescript","unraid"],"latest_commit_sha":null,"homepage":"https://github.com/Abrechen2/TravStats","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Abrechen2.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-20T15:21:08.000Z","updated_at":"2026-04-27T19:10:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"09eaa4d0-141f-405b-a69f-b5bd93322a04","html_url":"https://github.com/Abrechen2/TravStats","commit_stats":null,"previous_names":["abrechen2/travstats"],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/Abrechen2/TravStats","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Abrechen2%2FTravStats","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Abrechen2%2FTravStats/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Abrechen2%2FTravStats/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Abrechen2%2FTravStats/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Abrechen2","download_url":"https://codeload.github.com/Abrechen2/TravStats/tar.gz/refs/heads/Main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Abrechen2%2FTravStats/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32553690,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-02T22:28:24.418Z","status":"ssl_error","status_checked_at":"2026-05-02T22:28:14.225Z","response_time":132,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aviation","deck-gl","docker","flight-tracker","nodejs","postgis","react","self-hosted","typescript","unraid"],"created_at":"2026-01-29T02:05:31.638Z","updated_at":"2026-05-03T00:01:33.576Z","avatar_url":"https://github.com/Abrechen2.png","language":"TypeScript","funding_links":["https://www.paypal.com/donate?hosted_button_id=HW9MPYVURCT42"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"docs/images/logo.svg\" alt=\"TravStats\" width=\"140\" /\u003e\n\n# TravStats\n\n**Self-hosted travel logbook for small households and groups (1–10 users).**\nIt's a logbook, not a live tracker — you record trips manually, scan a boarding pass, or import a confirmation email, and TravStats turns them into history, stats and maps.\n\n[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)\n[![Release](https://img.shields.io/github/v/release/Abrechen2/TravStats?include_prereleases\u0026sort=semver)](https://github.com/Abrechen2/TravStats/releases)\n[![GHCR](https://img.shields.io/badge/container-ghcr.io-181717?logo=github)](https://github.com/Abrechen2/TravStats/pkgs/container/travstats)\n[![Docker Hub](https://img.shields.io/docker/pulls/abrechen2/travstats?logo=docker\u0026label=Docker%20Hub)](https://hub.docker.com/r/abrechen2/travstats)\n[![CI](https://github.com/Abrechen2/TravStats/actions/workflows/ci.yml/badge.svg)](https://github.com/Abrechen2/TravStats/actions/workflows/ci.yml)\n\n\u003c/div\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/map-2d.png\" alt=\"2D route map centred on Munich with 120 flights radiating across Europe\" width=\"85%\" /\u003e\n\u003c/p\u003e\n\n## Why TravStats\n\nLog every flight you take (cruises landing in v2), visualise your routes\non interactive 2D and 3D maps, collect 101 achievements, and import flights\nfrom boarding passes (QR / PDF417 / OCR), confirmation emails, or\nExcel/CSV — all on your own server, no cloud, no telemetry.\n\nIt's a logbook, not a live tracker — you record trips manually, scan a\nboarding pass, or import a confirmation email, and TravStats turns them\ninto history, stats and maps. Flight data is optionally enriched from\nAirLabs / Aviationstack / OpenSky, but everything you record lives in\nyour own PostgreSQL. No accounts on someone else's servers, no analytics,\nno ads.\n\n- 🗺️ **Six map modes** — Routes, Heatmap, Hexagon, 3D columns, animated Trips, 3D Globe\n- 📊 **Year-over-year statistics** across flights, distance, seats, classes, routes\n- 🏆 **101 Battlefield-style achievements** across five categories\n- 🎫 **Boarding-pass scanner** — QR / barcode / OCR\n- 📧 **Email import** — plain text, HTML, Outlook `.msg`, `.eml`, with optional local LLM parsing via Ollama\n- 📑 **Excel/CSV round-trip import** — export, edit in Excel, re-import; rows with an `id` update existing flights\n- 🤖 **Public REST API + OpenAPI 3.0 / Swagger UI** — Personal Access Tokens with `read` / `write` / `admin` scopes for AI agents and automation\n- 💾 **Automated backups** with retention + optional WebDAV sync\n- 🔐 **Invite-only by default** — toggle public registration anytime from the admin UI; JWT in HttpOnly cookies, 18 rate limiters on sensitive endpoints\n- 🌐 **German + English UI** with browser-locale auto-detection, i18n-ready\n\n---\n\n## Screenshots\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"50%\"\u003e\u003cimg src=\"docs/images/certificate.png\" alt=\"Vintage passport-style flight certificate\" /\u003e\u003c/td\u003e\n    \u003ctd width=\"50%\"\u003e\u003cimg src=\"docs/images/achievements.png\" alt=\"Achievements gallery with 101 unlockables\" /\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eDownloadable PNG certificate with your totals\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003e101 Battlefield-style achievements\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n---\n\n## Quick start\n\nThe only thing you set in a file is a database password. Everything else —\ninstance name, user cap, API keys, Ollama model, backup schedule, WebDAV —\nis captured by the first-run setup wizard in the browser.\n\n### Option A — Stack (bundled Postgres, recommended)\n\n```bash\n# 1. Grab the compose file\ncurl -O https://raw.githubusercontent.com/Abrechen2/TravStats/Main/docker-compose.prod.yml\n\n# 2. Set one variable\necho \"DB_PASSWORD=$(openssl rand -base64 32)\" \u003e .env\n\n# 3. Start\ndocker compose -f docker-compose.prod.yml up -d\n\n# 4. Open the setup wizard\nopen http://localhost:3000/setup\n```\n\nThat's the complete install. The container seeds the airport database on\nfirst boot (~30 s), auto-generates a JWT secret, and the setup wizard\npersists instance settings to the database.\n\n### Option B — External Postgres\n\nIf you already run a Postgres server (homelab, managed DB), skip the\nbundled `db` service by providing `DATABASE_URL` and removing the `db` and\n`depends_on` blocks from the compose file:\n\n```bash\nDATABASE_URL=postgresql://user:pass@postgres.lan:5432/travstats\n```\n\n### Unraid\n\nCommunity Apps templates live in a dedicated repo —\n[Abrechen2/docker-templates](https://github.com/Abrechen2/docker-templates).\nInstall `travstats-db` from there first, then `TravStats`, set the\n`Database URL` password, open `/setup`. Walk-through with screenshots in\n[`docs/unraid/README.md`](docs/unraid/README.md).\n\n\u003e **Optional local LLM parsing:** install the\n\u003e [Ollama Community App](https://unraid.net/community/apps?q=ollama)\n\u003e (or run Ollama anywhere else on your network), pull `gemma3:12b` (~7.5 GB),\n\u003e then point TravStats at it from **Admin → Parser**. Multi-flight\n\u003e confirmation mails and unknown airline templates are then handled locally —\n\u003e nothing leaves your network.\n\n### Image tags\n\nBoth registries (GHCR and Docker Hub) carry the same digests for these\nmoving tags. Pick the one your platform defaults to.\n\n| Tag | Points to | Use for |\n|---|---|---|\n| `:latest`, `:stable` | Latest stable release (currently `1.2.1`) | Normal production. Auto-updates to the next promoted release. |\n| `:X.Y.Z` (e.g. `:1.2.1`) | Pinned immutable release | Reproducible installs, audit, regulated environments. |\n| `:rc-latest` | Newest Release Candidate (currently `1.3.0-rc.7`) | Beta testers — receive every fresh RC via `docker compose pull`. May include breaking schema changes across major bumps; an in-place backup is taken automatically on first start of a new major. |\n\nSpecific RC tags (`:1.3.0-rc.1`, `:2.0.0-beta.8`) and dev builds live on\nGHCR only — Docker Hub only mirrors the moving tags above plus pinned\nfinal releases.\n\n---\n\n## Configuration\n\nAlmost nothing to configure via environment variables. The setup wizard\ncaptures everything instance-level (name, public URL, user cap,\nregistration mode) and the admin UI handles API keys, Ollama, backup\nschedule and WebDAV sync.\n\n| Env variable | When to set it | Default |\n|---|---|---|\n| `DB_PASSWORD` | Always — shared secret between app and bundled Postgres | **required** |\n| `APP_PORT` | Different host port | `3000` |\n| `DATABASE_URL` | External Postgres instead of the bundled service | *(derived from `DB_PASSWORD`)* |\n| `COOKIE_SECURE` | Reverse proxy doesn't send `X-Forwarded-Proto` | *(auto-detected)* |\n| `CORS_ORIGIN` | Frontend lives on a different hostname than the API | *(same-origin only)* |\n| `TZ` | Non-UTC container clock (not recommended) | `UTC` |\n\nSee [`.env.prod.example`](.env.prod.example) for the annotated list.\n\n### Runtime-configurable from the admin UI\n\n- Instance name, public URL, user cap, registration mode\n- **AirLabs / OpenSky / Aviationstack** API keys (encrypted at rest)\n- **Ollama** endpoint + model (default `gemma3:12b`)\n- Backup schedule and retention\n- WebDAV off-site backup sync (Nextcloud, HiDrive, …)\n- SMTP for invitation and password-reset emails\n- Logging level and retention\n\nTravStats works without any API key; manual flight entry and boarding-pass\nscanning cover the full feature set.\n\n---\n\n## What's in a release\n\nSee [CHANGELOG.md](CHANGELOG.md) for the full history and\n[ROADMAP.md](ROADMAP.md) for where things are heading (cruises module,\nCO₂ tracking, trip planner, PWA).\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for the hardening summary, audit history, and\nverification commands. TL;DR: JWT in HttpOnly cookies, 18 distinct rate\nlimiters, Zod validation on every endpoint, Prisma-parameterised queries,\nHelmet CSP, invite-only by default.\n\nReport vulnerabilities via\n[GitHub Security Advisories](https://github.com/Abrechen2/TravStats/security/advisories/new)\n— **please do not open a public issue**.\n\n## API for external tools\n\nTravStats ships an authenticated REST API for AI agents, automation\nscripts and integrations. Every flight, trip, airport and stat the\nweb UI shows is reachable programmatically.\n\n**1. Mint a Personal Access Token**: in the app, go to **Settings →\nAPI Tokens**, give it a label and a scope (`read`, `write`, `admin`),\nand copy the `ts_pat_…` value. The plaintext is shown exactly once —\nonly the bcrypt hash is persisted.\n\n**2. Browse the spec**: open `https://\u003cyour-host\u003e/api/v1/docs`\n(Swagger UI) or fetch `/api/v1/openapi.json` for the raw OpenAPI 3.0\ndocument. The spec is auto-generated from the same Zod schemas the\nbackend uses for validation, so it never drifts.\n\n**3. Call it**: every endpoint accepts the token via the standard\n`Authorization` header.\n\n```bash\nTOKEN=\"ts_pat_…\"\n\n# List your flights\ncurl -H \"Authorization: Bearer $TOKEN\" \\\n  https://travstats.example.com/api/v1/flights\n\n# Create a flight (write or admin scope required)\ncurl -H \"Authorization: Bearer $TOKEN\" \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\"departure\":{\"iata\":\"FRA\",\"lat\":50.0379,\"lon\":8.5622},\n          \"arrival\":{\"iata\":\"JFK\",\"lat\":40.6413,\"lon\":-73.7781},\n          \"departureTime\":\"2026-05-01T08:00:00.000Z\",\n          \"arrivalTime\":\"2026-05-01T17:00:00.000Z\",\n          \"flightNumber\":\"LH400\"}' \\\n  https://travstats.example.com/api/v1/flights\n```\n\nPass `?merge=true` on `POST /flights` to enrich an existing matching\nflight (boarding-pass re-import, email confirmation upgrade) instead\nof creating a duplicate. Read-only tokens are blocked from mutating\nendpoints with 403; admin endpoints additionally require an\n`admin`-scoped token even if the owning user is an admin.\n\nToken requests get their own per-token rate-limit bucket so an\naggressive script can't lock the user out of the web UI.\n\n## Contributing\n\nBug reports, feature ideas and pull requests are welcome. The\n[Report Bug](https://github.com/Abrechen2/TravStats/issues/new/choose) button\nin the app copies an anonymised diagnostic bundle for you.\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide.\n\n## Development\n\n```bash\nnpm run install:all     # install backend + frontend\nnpm run dev             # backend :8000 + frontend :3000\nnpm run typecheck       # tsc --noEmit on both\nnpm run test:frontend   # Vitest (backend tests need Postgres)\n```\n\nDeep-dive developer reference: [CLAUDE.md](CLAUDE.md).\n\n---\n\n## License\n\nCopyright © 2026 Dennis Wittke · **AGPL-3.0-or-later**\n\nYou may use, modify and redistribute TravStats, but if you run it as a web\nservice (even modified) you must make the complete source code of your\nmodifications available under the same licence. See [LICENSE](LICENSE).\n\n## Support the project\n\nTravStats is a solo side project. If it's useful to you,\n[a small donation via PayPal](https://www.paypal.com/donate?hosted_button_id=HW9MPYVURCT42)\nkeeps the lights on for AirLabs quota top-ups. ❤️\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**[⭐ Star on GitHub](https://github.com/Abrechen2/TravStats)** ·\n[Releases](https://github.com/Abrechen2/TravStats/releases) ·\n[Roadmap](ROADMAP.md) ·\n[Issues](https://github.com/Abrechen2/TravStats/issues)\n\n\u003csub\u003eMade with ❤️ and a bit of AI for flight enthusiasts.\u003c/sub\u003e\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabrechen2%2Ftravstats","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabrechen2%2Ftravstats","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabrechen2%2Ftravstats/lists"}