diff --git a/.env.docker.example b/.env.docker.example index 2d366aa..cf35afd 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -1,4 +1,4 @@ -APP_NAME=Mileage Logger +APP_NAME=Trip Tracker # Use production for real deployments. Production mode requires non-default secrets and web login. APP_ENV=production @@ -19,10 +19,10 @@ COMPOSE_PROFILES=local-postgres # Docker Swarm stack deployments cannot build from source. Build/tag these locally for a # single-node Swarm, or set them to registry images available to every Swarm node. -APP_IMAGE=ghcr.io/cosmicc/mileage-logger-app:1.4.4 -NGINX_IMAGE=ghcr.io/cosmicc/mileage-logger-nginx:1.4.4 +APP_IMAGE=ghcr.io/cosmicc/trip-tracker-app:1.5.0 +NGINX_IMAGE=ghcr.io/cosmicc/trip-tracker-nginx:1.5.0 -# Swarm mlapp-container identity. Match these to the owner of HOST_DATA_DIR on shared storage. +# Swarm ttapp-container identity. Match these to the owner of HOST_DATA_DIR on shared storage. APP_UID=1000 APP_GID=100 @@ -38,7 +38,7 @@ WEB_LOGIN_MAX_ATTEMPTS=5 WEB_LOGIN_LOCKOUT_SECONDS=300 # Optional WebAuthn/passkey settings. Leave RP fields blank unless the browser origin needs an override. -PASSKEY_RP_NAME=Mileage Logger +PASSKEY_RP_NAME=Trip Tracker PASSKEY_RP_ID= PASSKEY_ORIGIN= @@ -67,6 +67,8 @@ PUSHOVER_TIMEOUT_SECONDS=10 # App-health notification thresholds. Critical findings render red on Diagnostics. APP_HEALTH_MONITOR_INTERVAL_SECONDS=60 +# Repeat unchanged degraded or unavailable Pushover alerts every hour. +APP_HEALTH_REMINDER_INTERVAL_SECONDS=3600 APP_HEALTH_DB_LATENCY_WARNING_MS=500 APP_HEALTH_DB_LATENCY_CRITICAL_MS=2000 APP_HEALTH_DB_LATENCY_SUSTAINED_SECONDS=15 @@ -78,12 +80,15 @@ APP_HEALTH_STATE_PATH=/data/app-health-state.json WEB_API_KEY=change-web-api-key # Local PostgreSQL container settings. Ignored when COMPOSE_PROFILES is blank for remote DB mode. -POSTGRES_DB=mileage_logger -POSTGRES_USER=mileage +POSTGRES_DB=trip_tracker +POSTGRES_USER=triptracker POSTGRES_PASSWORD=change-postgres-password +# Stable explicit volume name prevents a Compose/Portainer project rename from silently +# selecting an empty database volume. See UPGRADE-1.5.md before changing an existing deployment. +POSTGRES_DATA_VOLUME=trip-tracker-postgres-data # Main app database connection. For remote PostgreSQL, use the remote host and URL-encode passwords. -DATABASE_URL=postgresql+psycopg://mileage:change-postgres-password@postgres:5432/mileage_logger +DATABASE_URL=postgresql+psycopg://triptracker:change-postgres-password@postgres:5432/trip_tracker # SQLAlchemy connection pool settings. Defaults are conservative for local or small remote databases. DATABASE_POOL_SIZE=5 @@ -144,8 +149,8 @@ GAS_SNAPSHOT_RUN_ON_STARTUP=true # Persistent app data and a separate automatic-backup directory. Logs use stdout/stderr. APP_DATA_DIR=/data -HOST_DATA_DIR=/var/lib/mileage-logger -HOST_BACKUP_DIR=/var/lib/mileage-logger/backups +HOST_DATA_DIR=/var/lib/trip-tracker +HOST_BACKUP_DIR=/var/lib/trip-tracker/backups # Maximum uploaded Diagnostics restore file size, in bytes. MAX_BACKUP_RESTORE_BYTES=262144000 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1121a93..9845ec7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,8 +26,8 @@ jobs: CLOUDFLARED_TUNNEL_TOKEN=dummy-token docker compose --env-file .env.docker.example config - name: Check Docker Swarm stack config run: | - export APP_IMAGE=mileage-logger-app:ci - export NGINX_IMAGE=mileage-logger-nginx:ci + export APP_IMAGE=trip-tracker-app:ci + export NGINX_IMAGE=trip-tracker-nginx:ci export SECRET_KEY=dummy-secret-key export WEB_LOGIN_USERNAME=admin export WEB_LOGIN_PASSWORD=dummy-password @@ -36,9 +36,9 @@ jobs: export OWNTRACKS_PASSWORD=dummy-owntracks-password export OWNTRACKS_ENCRYPTION_KEY=dummy-owntracks-encryption-key export CLOUDFLARED_TUNNEL_TOKEN=dummy-token - export DATABASE_URL=postgresql+psycopg://mileage:secret@db.example:5432/mileage_logger + export DATABASE_URL=postgresql+psycopg://triptracker:secret@db.example:5432/trip_tracker docker stack config -c docker-stack.yml - export DATABASE_URL=postgresql+psycopg://mileage:mileage@postgres:5432/mileage_logger + export DATABASE_URL=postgresql+psycopg://triptracker:triptracker@postgres:5432/trip_tracker docker stack config -c docker-stack.yml -c docker-stack.local-postgres.yml - name: Test run: pytest diff --git a/.github/workflows/publish-swarm-images.yml b/.github/workflows/publish-swarm-images.yml index 735b916..c699c4f 100644 --- a/.github/workflows/publish-swarm-images.yml +++ b/.github/workflows/publish-swarm-images.yml @@ -5,7 +5,7 @@ on: branches: - main paths: - - "mileage_logger/**" + - "trip_tracker/**" - "alembic/**" - "alembic.ini" - "scripts/**" @@ -22,7 +22,7 @@ permissions: packages: write concurrency: - group: mileage-logger-images-main + group: trip-tracker-images-main cancel-in-progress: true jobs: @@ -34,13 +34,13 @@ jobs: fail-fast: false matrix: include: - - component: mlapp + - component: ttapp dockerfile: ./Dockerfile - image: ghcr.io/cosmicc/mileage-logger-app + image: ghcr.io/cosmicc/trip-tracker-app - - component: mlnginx + - component: ttnginx dockerfile: ./deploy/nginx/Dockerfile - image: ghcr.io/cosmicc/mileage-logger-nginx + image: ghcr.io/cosmicc/trip-tracker-nginx steps: - name: Check out repository diff --git a/.vscode/SKILL-api-and-web-routes.md b/.vscode/SKILL-api-and-web-routes.md index 75ad91a..a59a909 100644 --- a/.vscode/SKILL-api-and-web-routes.md +++ b/.vscode/SKILL-api-and-web-routes.md @@ -4,9 +4,9 @@ ## Overview -Mileage Logger uses: -- **API routes** (`mileage_logger/api/routes.py`) — JSON responses for OwnTracks integration -- **Web routes** (`mileage_logger/web/routes.py`) — Server-rendered HTML pages via Jinja2 +Trip Tracker uses: +- **API routes** (`trip_tracker/api/routes.py`) — JSON responses for OwnTracks integration +- **Web routes** (`trip_tracker/web/routes.py`) — Server-rendered HTML pages via Jinja2 - **Authentication** — Web login (session-based) + OwnTracks API authentication - **FastAPI** — Modern async web framework with automatic OpenAPI docs @@ -16,14 +16,14 @@ Mileage Logger uses: ### Location -[mileage_logger/api/routes.py](mileage_logger/api/routes.py) +[trip_tracker/api/routes.py](trip_tracker/api/routes.py) ### Basic Pattern ```python from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from mileage_logger.database import get_db +from trip_tracker.database import get_db router = APIRouter() @@ -84,8 +84,8 @@ container health checks. Do not reuse `OWNTRACKS_ENCRYPTION_KEY` as `WEB_API_KEY ### Protecting Endpoints ```python -from mileage_logger.api.deps import verify_owntracks_auth -from mileage_logger.services.owntracks import process_owntracks_payload +from trip_tracker.api.deps import verify_owntracks_auth +from trip_tracker.services.owntracks import process_owntracks_payload @router.post("/owntracks") async def owntracks_http(request: Request) -> JSONResponse: @@ -126,13 +126,13 @@ of app JSON errors. Database-outage web handling is different from normal HTTP errors. The app-level limp-mode middleware renders `web/templates/limp_mode.html` for browser paths when PostgreSQL is unreachable -and returns HTTP 200 with `X-Mileage-Logger-Limp-Mode: true` so nginx does not replace it with the +and returns HTTP 200 with `X-Trip-Tracker-Limp-Mode: true` so nginx does not replace it with the generic 503 page. Non-OwnTracks API paths return 503 JSON during limp mode. Full-page Dashboard and Work Trips requests preflight PostgreSQL reachability and render the limp-mode page instead of their normal loading shells during an outage. JavaScript content fetches must receive only `web/templates/_limp_mode_panel.html` so the fetched HTML can replace the shell without nesting another `layout.html` top bar; shell JavaScript should redirect to `/` if the -content response has `X-Mileage-Logger-Limp-Mode: true` so stale navigation is not left on screen. +content response has `X-Trip-Tracker-Limp-Mode: true` so stale navigation is not left on screen. While `limp_mode_active` is set, the full outage page hides shared app chrome, navigation, icons, and service-worker registration. The page uses the end-user facing `Service Temporarily Unavailable` heading, avoids host/IP/connection-string details and database status cards, and @@ -156,7 +156,7 @@ WEB_API_KEY=separate-web-api-key ### Step 1: Define Request/Response Models -Edit [mileage_logger/schemas.py](mileage_logger/schemas.py): +Edit [trip_tracker/schemas.py](trip_tracker/schemas.py): ```python from pydantic import BaseModel, Field @@ -174,7 +174,7 @@ class MyResponseModel(BaseModel): ### Step 2: Add the Route -Edit [mileage_logger/api/routes.py](mileage_logger/api/routes.py): +Edit [trip_tracker/api/routes.py](trip_tracker/api/routes.py): ```python @router.post("/custom-endpoint") @@ -232,7 +232,7 @@ curl -X POST http://localhost:8000/api/owntracks \ ### Location -[mileage_logger/web/routes.py](mileage_logger/web/routes.py) +[trip_tracker/web/routes.py](trip_tracker/web/routes.py) ### Trips Page Form Boundary @@ -281,7 +281,7 @@ curl -X POST http://localhost:8000/api/owntracks \ - `layout.html` keeps authenticated navigation in the shared top bar. Desktop nav links use one centered blue raised button treatment, with icons shown to the left of text labels. The authenticated header brand uses the cleaned transparent logo asset and shows the current app - version as a small readable line directly under the Mileage Logger title, while installable app + version as a small readable line directly under the Trip Tracker title, while installable app icons use launcher-safe padding from the transparent logo and the favicon stays on the square original logo. When icon assets change, update the static icon cache-busting query in `layout.html` and `manifest.webmanifest`. On mobile, CSS hides the brand/icon and keeps nav links @@ -292,7 +292,7 @@ curl -X POST http://localhost:8000/api/owntracks \ touch icon links. Keep the mobile viewport non-edge-to-edge, and preserve the manifest browser fallback on authenticated app pages so phone system navigation remains visible. The brand icon/text is display-only and not a home link. -- The active app color palette lives in `mileage_logger/web/static/styles.css`. Palette samples are +- The active app color palette lives in `trip_tracker/web/static/styles.css`. Palette samples are saved in `docs/design/color-palettes.svg`; Option A is the current palette. Approved Monthly Work Trips row colors are blue (`#4BA3FF`) for automatic trips, purple (`#A855F7`) for edited trips, and gold (`#E2AD45`) for manual trips. Do not apply a new app-wide palette until the user chooses @@ -323,17 +323,19 @@ curl -X POST http://localhost:8000/api/owntracks \ - Diagnostics hard drive space rows group configured runtime paths as the same drive only when exact used bytes and total bytes both match. Keep this grouping rule aligned with the visible drive-space bars and database summary in `diagnostics.html`. The Diagnostics Application card - also shows the source-controlled app version from `mileage_logger.__version__`. + also shows the source-controlled app version from `trip_tracker.__version__`. - The Diagnostics Data card shows record counts and the lowest/highest queried gas price readings from `gas_price_snapshots.price_per_gallon`. Do not use `MonthlyGasPrice` averages for these high/low values. - Diagnostics degraded/unavailable banners and Pushover app-health notifications must use - `mileage_logger.services.app_health.build_app_health_snapshot()`. Keep monitored signals aligned: + `trip_tracker.services.app_health.build_app_health_snapshot()`. Keep monitored signals aligned: PostgreSQL availability/latency, runtime free disk space, active web-login lockouts, and app-managed Cloudflare IP blocks. Diagnostics shows each current database latency reading, but Pushover must wait `APP_HEALTH_DB_LATENCY_SUSTAINED_SECONDS` and confirm latency is still high. Disk health uses `APP_HEALTH_DISK_WARNING_FREE_MB` and `APP_HEALTH_DISK_CRITICAL_FREE_MB`, not - used-percentage alarm thresholds. + used-percentage alarm thresholds. Preserve immediate issue-change and restoration alerts, and + repeat unchanged degraded or unavailable states every + `APP_HEALTH_REMINDER_INTERVAL_SECONDS` (one hour by default). ### Basic Pattern @@ -359,7 +361,7 @@ def dashboard(request: Request, db: Session = Depends(get_db)) -> TemplateRespon ### Jinja2 Template Filters -Custom filters are registered in [web/routes.py](mileage_logger/web/routes.py): +Custom filters are registered in [web/routes.py](trip_tracker/web/routes.py): ```python def _format_local_datetime(value, fmt: str = "%Y-%m-%d %I:%M:%S %p") -> str: @@ -376,7 +378,7 @@ templates.env.filters["local_datetime"] = _format_local_datetime ### Step 1: Create Jinja2 Template -Create file: `mileage_logger/web/templates/my_page.html` +Create file: `trip_tracker/web/templates/my_page.html` ```html {% extends "layout.html" %} @@ -395,7 +397,7 @@ Create file: `mileage_logger/web/templates/my_page.html` ### Step 2: Add the Route -Edit [mileage_logger/web/routes.py](mileage_logger/web/routes.py): +Edit [trip_tracker/web/routes.py](trip_tracker/web/routes.py): ```python @router.get("/my-page", response_class=HTMLResponse) @@ -418,7 +420,7 @@ def my_page( ### Step 3: Add Navigation Link -Edit `mileage_logger/web/templates/layout.html`: +Edit `trip_tracker/web/templates/layout.html`: ```html