Architecture¶
Overview¶
find-my-ride is a 3-tier app:
- Frontend: React + TypeScript + Vite PWA (
frontend/) - Backend API: FastAPI + SQLAlchemy (
backend/) - Database: PostgreSQL (
dbservice in Docker Compose)
Uploads are stored on filesystem volume data/uploads and referenced from database records.
Runtime topology¶
Development (docker-compose.yml):
frontendon:5173backendon:8000dbon:5432- Frontend API path uses same-origin
/apiand Vite dev proxy forwards to backend (VITE_PROXY_TARGET)
Production-style (docker-compose.prod.yml):
frontendstatic app on:${FRONTEND_PORT}(container80; default host fallback18080)backendinternal APIdbinternal PostgreSQL- Frontend nginx proxies
/api/*to backend (http://backend:8000) so the browser keeps same-origin API calls
Backend architecture¶
Main modules:
app/core/: config, database session, security utilities, dependenciesapp/models/: SQLAlchemy models (User,ParkingRecord,Photo,RefreshToken)app/schemas/: Pydantic request/response modelsapp/api/routes/: route handlers forsystem,auth,users,parkingapp/services/: geocoding, MFA, refresh-token lifecycle, and file storage helpers
Data model:
users: account identity, password hash, admin flag, MFA status/secretparking_records: owner, optional lat/lng pair, persisted location label text (street-first normalized), note, parked time, timestampsphotos: per-record metadata and storage pathrefresh_tokens: hashed refresh token values, owner, expiry, and revocation timestamp
Frontend architecture¶
Single-page React app with feature sections:
- Bootstrap admin / login / open self-register (no moderation)
- Login flow is two-phase for MFA users: username/password first, then OTP in a modal when challenged
- MFA setup shows a locally generated QR code above the TOTP secret, using the backend
otpauth_url - Bottom-tab navigation:
home: start parking + active parking session (sticky until ended)history: record history + details + deletesettings: profile + admin (admin-only)- Top app bar with account menu for signed-in identity and sign-out
- Logged-out auth layout mirrors the top app bar style and places auth mode buttons (
Sign in/Register) directly below it - Auth form panel is anchored toward the lower half of the viewport with reserved bottom spacing for a future banner slot
- Subtle parked-car background image (locally bundled from selected royalty-free source) with theme-aware overlays to preserve readability
- Park-now action with
Locate(geolocation) + reverse place lookup + optional note + up to 3 photos - Park-now photo intake uses 3 camera-first capture slots with thumbnail preview/retake/remove controls
- Camera capture is handled in-app with
getUserMedia(when available) to avoid mobile browser restarts during external camera handoff; gallery/file-picker fallback remains available - File-picker selection is synchronized on
focus/visibilitychangefor fallback browser flows - Park-now location state is explicit (
readywith resolved place text orNo location could be established) - On non-secure contexts (common on HTTP LAN URLs),
Locatetypically resolves toNo location could be established - Park-now start accepts either a valid location pair or note/photo evidence, allowing garage use when GPS is unavailable
- Park-now requires a resolved physical
location_labelwhen coordinates are saved (coordinate-style labels are rejected) - Home active state (
You are parked) shows start timestamp, running duration, optional location map, notes, and thumbnails until user confirmsEnd parking - When active parking has coordinates, home also shows a
Take me theresection withGoogle MapsandOpenStreetMaplinks;Actionssection always containsEnd parking - Parking thumbnails (home capture, active parking, history details) open a full-size preview modal on tap/click
- Active parking session is persisted per user in browser storage so app restarts restore the in-progress state
- Active parking can emit browser notifications with parked duration when notification permission is granted
- History cards use expandable (
More info/Close) details with OpenStreetMap embed preview, saved location text, aMore detailsnote section, photo thumbnails, and route/actions sections - Date/time rendering in cards is day-first (
dd-mm-yyyy) with browser-local time - History cards provide quick delete action from collapsed state
- Mobile home layout applies horizontal overflow guards to prevent off-screen content and bottom-tab drift
- Profile controls: change password, MFA setup/verify/disable, theme mode switch, and accent color preset selection (theme-aware tones, stored client-side)
- Admin user management and history scope filtering (
Admin>Add users+Edit userslist with edit/delete actions and role/password modal)
PWA support:
- Static web manifest at
frontend/public/manifest.webmanifest - Local service worker at
frontend/public/sw.js, registered insrc/main.tsx - Service worker explicitly avoids caching
/api/*responses to prevent sensitive auth/data caching - Service worker cache is versioned by app release tag via
VITE_APP_VERSION/APP_VERSIONand rotates cache namespace on deploy - Navigation requests use network-first caching so online clients can fetch the latest app shell after a release
Security model¶
- Passwords hashed with bcrypt (
passlib) - Username validation and normalization at API boundary (lowercased, restricted character set)
- Password policy enforced at API boundary for create/reset/change operations: 8-128 chars with uppercase/lowercase/digit
- Note/location text sanitization rejects control characters and normalizes whitespace
- Short-lived JWT bearer access tokens with expiry
- Refresh tokens are random 64-byte URL-safe secrets, stored hashed (
sha256) in DB - Refresh token cookie is HttpOnly and scoped to
/api/auth; backend rotates refresh token on every refresh - Refresh token replay/expired/revoked checks revoke active refresh sessions for that user before rejecting
- Refresh tokens are revoked on logout, self password change, and admin password reset
- Manual logout sets a client guard that suppresses same-session silent refresh recovery, avoiding mobile race conditions after sign-out
- Frontend retries once on
401by refreshing and replaying the original API request - Route-level auth dependency (
get_current_user) + admin guard (get_admin_user) - Owner-based row access checks for parking records/photos
- Upload constraints:
- Max photos per record (
MAX_PHOTOS_PER_RECORD, default 3) - Allowed MIME types: jpeg/jpg/pjpeg/png/webp/heic/heic-sequence/heif/heif-sequence/avif
- Max photo size (
MAX_PHOTO_SIZE_MB, default 8 MB) - Basic security headers middleware enabled in backend
- CORS allowlist controlled by
CORS_ORIGINSwith credentials enabled for cookie-based refresh flow
Limitations in current baseline¶
- Access tokens are still persisted in browser
localStoragebetween reloads (refresh token stays HttpOnly cookie). - MFA secret is stored in DB without envelope encryption.
- DB schema migrations are currently startup
create_all(no Alembic migration history yet).
These are acceptable for initial scaffold but should be hardened before full production rollout.