Dev Guide¶
Prerequisites¶
- Docker + Docker Compose
- macOS terminal (
zsh) for local workflow - Optional: Python virtualenv for docs (
mkdocs)
Initial setup¶
- Create env file:
cp .env.example .env
-
Update at minimum:
-
POSTGRES_PASSWORD SECRET_KEY(64+ random chars)CORS_ORIGINSACCESS_TOKEN_EXPIRE_MINUTESREFRESH_TOKEN_EXPIRE_DAYSREFRESH_TOKEN_COOKIE_SECURE(truein production behind HTTPS)VITE_API_URL(keep as/apiin Docker dev)VITE_PROXY_TARGET(default:http://backend:8000)
Run locally (Docker)¶
docker compose up --build
or:
make up
Endpoints:
- Frontend:
http://localhost:5173 - Backend OpenAPI docs:
http://localhost:8000/docs - Health check:
http://localhost:8000/healthz
Development loop¶
- Backend uses
uvicorn --reloadin compose dev service. - Frontend uses Vite dev server with hot reload and
/apiproxy to backend. - DB and upload storage are persisted in docker volumes / local folders.
Phone/LAN testing¶
- Access the app from another device with
http://<host-lan-ip>:<FRONTEND_PORT>. - Keep
VITE_API_URL=/apifor this flow. If set tohttp://localhost:..., phones/tablets will try to call their own localhost and stall on status loading. - Geolocation caveat: most mobile browsers require HTTPS for GPS access. Over plain HTTP LAN URLs,
Locatecan fail immediately and showNo location could be established.
Bootstrap flow¶
- Open frontend.
- Create first user in "Create first admin" screen.
- That user is stored as admin and signed in immediately.
Login and registration flow¶
- If users already exist, the logged-out screen shows the same top app bar styling as the signed-in app (without user menu actions).
- An auth mode bar appears directly below the top bar with
Sign inandRegister. Registeris open by default (no moderation gate) and creates a normal non-admin account.Sign infirst submits username/password only; if MFA is enabled for that account, an OTP modal appears as a second step.- The auth card is positioned in the lower half of the viewport with intentional extra bottom space reserved for a future banner.
- Username input is normalized to lowercase and validated for
a-z,0-9,.,_,-(3-64 chars). - Password policy for create/reset/change: 8-128 chars with at least one uppercase, lowercase, and digit.
Session and refresh-token flow¶
- Auth endpoints (
bootstrap,register,login) return access token JSON and set an HttpOnly refresh cookie. - Frontend stores access token for API bearer auth and includes cookies (
credentials: include) for refresh calls. - On cold start without access token, frontend attempts
/api/auth/refreshto restore session. - On API
401responses with a bearer token, frontend attempts one refresh and retries the original request once. - Backend rotates refresh token on every successful refresh and stores only a hashed token in DB.
- Logout revokes only the current refresh token and clears cookie.
- Password changes (self-service or admin reset) revoke all refresh tokens for that user.
- Manual logout in the UI disables same-session auto-refresh recovery to avoid mobile race conditions where slow logout responses could re-authenticate immediately.
Parking capture flow¶
Locatebutton requests geolocation and attempts reverse-lookup location naming.- On success, the UI shows place name and coordinates.
- On successful save with coordinates, the current place label is persisted as
location_labelfor later history/latest display. - Coordinate-style location labels are rejected by the API; coordinates must be paired with a physical address label.
- Stored labels are normalized in street-first order (
Street 12, ZIP City, Province, Country). - On failure, the UI shows
No location could be established; save is still allowed when note and/or photos are provided. - Photos are handled via 3 capture slots; each slot is an empty action button or an image thumbnail that can be retaken/removed.
- Camera capture prefers an in-app camera modal (
getUserMedia) to avoid external camera-app round trips on memory-constrained mobile browsers; gallery/file-picker fallback remains available. - Any parking photo thumbnail can be tapped/clicked to open a full-size preview modal.
Multi-user behavior¶
- Admin can create new users in the UI (
settings>admin>add users). - Admin can manage existing users in
settings>admin>edit users: Editopens a modal to reset password and/or change admin roleDeleteremoves the selected user- self account is visible but does not expose edit/delete actions
- Non-admin users only see/delete their own parking records.
- Admin can filter history scope by user or all users.
UI navigation¶
- The authenticated app uses 3 fixed bottom tabs:
home: parking start capture + active parking sessionhistory: record history with expandable details and deletesettings: profile and admin- Top app bar:
- app brand on the left
- account icon menu on the right with signed-in user and sign-out action
- Home-tab mobile layout includes horizontal overflow guards to prevent off-screen content and shifted bottom navigation.
- Background image asset:
- file:
frontend/public/images/parking-background-option-3.jpg - source:
https://www.pexels.com/photo/a-modern-car-in-an-underground-garage-16304132/ - license:
https://www.pexels.com/license/ homestate flow:- default shows
Parked?capture form (Locate, note, up to 3 photos) - after
Remember This, app switches toYou are parkedwith start timestamp + running duration - active panel shows optional location map, note, thumbnails, and
End parkingconfirmation (yes/no) - active session is sticky per user across app close/reopen via local browser persistence
historyrecord cards:- collapsed view shows time/date, saved location text, and quick actions
More infoexpands details with OpenStreetMap embed preview, aMore detailsnote section, thumbnails, and aTake me theresectionDeleteremoves the recordsettings>profileincludes:- change password
- theme switcher (system/light/dark)
- accent color selector (preset button/select highlight palettes, applied live and tuned for light/dark)
- MFA setup/verify/disable
settings>adminis only visible to admin users and containsedit users.settings>adminlayout:Add userssection for username/password/admin toggle creationEdit userssection for list + edit/delete actions (non-self users only)
MFA behavior¶
- Any signed-in user can start MFA setup.
- App returns TOTP secret + provisioning URI, and the UI renders a QR code from that URI for app scan.
- User verifies one OTP code to enable MFA.
- If enabled, login requires
otp_code.
Documentation workflow¶
When changing code:
- Update relevant pages in
docs/. - Keep
docs/api-reference.mdin sync with route changes. - Keep architecture and decision pages current.
README screenshot refresh workflow¶
When refreshing README screenshots (docs/assets/screenshots/home.png, history.png, settings.png), keep these capture defaults so the README layout stays consistent:
- Output format/size: PNG at
1170x1992for each image. - Visual scale: apply
1.5xpage zoom during capture so card text and controls remain readable in the 3-up README gallery. - Theme: dark mode (
fmr_theme_mode=dark) with the defaultevergreenaccent. - Environment: run against the local Docker stack (
docker compose up -d --build) on the frontend URL (http://127.0.0.1:${FRONTEND_PORT}; current default in this repo is15173). - Data setup: use a dedicated demo account (currently
readme_demo) with at least 3 history rows: - at least 2 rows with stored geocoded
location_label+ coordinates - at least 1 row without location (note/photo fallback state)
- Required capture states:
home: defaultParked?cardhistory: first record expanded viaMore info, with OpenStreetMap embed visiblesettings:Profilesection visible including theme + accent controls- Capture hygiene:
- dismiss transient toasts before each screenshot
- avoid camera/mfa modal overlays
- commit only the screenshot assets (no temporary local capture scripts)
One-time backfill for old address labels¶
If older records have missing/coordinate-style location_label values, run:
docker compose run --rm --no-deps backend python -m app.scripts.backfill_location_labels
This keeps coordinates intact and writes resolved physical addresses where lookup succeeds.
Validation and tests policy¶
- Any code change must be validated before completing work.
- Every functional change must include or update automated tests.
- If a test command cannot be executed in the current environment, report that gap explicitly.
Run backend tests:
make test
make test runs the suite in Docker (backend service image) to avoid host dependency drift.
If you already installed backend Python dependencies locally, you can run:
make test-local
Run frontend tests:
make test-frontend
Run both:
make test-all
Run dependency security scans:
make security-scan
make security-scan runs:
- npm audit for frontend dependencies
- pip-audit for backend dependencies (inside backend container)
- pip-audit for docs dependencies (ephemeral Python container)
Serve docs locally¶
pip install -r docs/requirements.txt
mkdocs serve
or:
make docs-serve
Open http://127.0.0.1:8001.
Production deployment notes¶
- Use
docker-compose.prod.yml. docker-compose.prod.ymlmaps frontend host port fromFRONTEND_PORT(fallback18080).- Production frontend nginx proxies
/api/*to backend service, so no separate public backend port is required. - Set
APP_VERSIONto the release tag before deploy (for exampleAPP_VERSION=v0.1-beta.1) so PWA cache/version updates propagate to installed mobile clients. - Place frontend/backend behind an SSL reverse proxy (proxy config not included here).
- Restrict CORS to real public domain(s).
- Use strong credentials and persisted volumes for db/uploads.