Files
time_to_leave/docs/ARCHITECTURE.md
T
2026-05-18 15:01:53 +02:00

4.6 KiB

TimeToLeave Architecture

TimeToLeave is an npm workspaces monorepo. The web app is both the browser UI and the backend proxy for external services; the mobile app calls that backend through the shared API client; shared domain logic lives in packages/core.

Monorepo Structure

TimeToLeave/
├── apps/
│   ├── web/        # Next.js 16 App Router UI and backend proxy
│   └── mobile/     # Expo 54 / React Native 0.81 app
├── packages/
│   ├── api-client/ # Shared client for /api/* backend routes
│   └── core/       # Shared types, defaults, parsing, scoring, formatting
├── docs/           # Documentation
└── package.json    # Workspace scripts

Components

Web App: apps/web

  • Next.js 16 App Router with React 19 and Tailwind CSS 4.
  • User-facing routes are / and /calendar.
  • Route handlers under apps/web/src/app/api/**/route.ts proxy HAFAS, Nominatim, OSRM, Google Calendar, remote ICS, and Wiener Linien calls.
  • apps/web/src/proxy.ts applies strict CORS and per-IP rate limiting to /api/*.
  • Client state is kept in React context and persisted to localStorage through useEventsStore and useReminderSettings.

Mobile App: apps/mobile

  • Expo 54 / React Native 0.81 with React Navigation 7.
  • Screens cover event list, add/edit event, event detail, settings, and calendar import.
  • Uses native APIs through expo-calendar, expo-location, expo-notifications, and AsyncStorage.
  • Calls the web backend through @timetoleave/api-client; device builds should set EXPO_PUBLIC_API_BASE_URL.

Core Package: packages/core

  • Owns the shared TypeScript model: events, calendar events, stations, journeys, routes, reminder settings, geocoding, and Wiener Linien types.
  • Provides Vienna-aware HAFAS time conversion through parseHafasTime() and hafasDateTime().
  • Parses HAFAS journey responses, ranks journeys, formats dates/durations/distances, and computes countdown/status labels.
  • Exports default origin constants for the Mödling fallback origin.

API Client Package: packages/api-client

  • Wraps the web backend routes from browser and mobile code.
  • Supports base URL failover by accepting either one base URL or an array of base URLs.
  • Builds HAFAS TripSearch and LocMatch requests, parses journey responses with @timetoleave/core, and includes an arrive-by fallback search window.

Data Flow

  1. Events enter through manual entry, remote ICS URL import, local ICS file parsing, Google Calendar on web, or native device calendars on mobile.
  2. Events are normalized to Event or CalendarEvent objects and stored locally in the client.
  3. The app resolves an origin station from saved settings, geolocation, or the shared default origin.
  4. The event destination is geocoded with Nominatim.
  5. HAFAS LocMatch resolves the nearest destination station.
  6. HAFAS TripSearch fetches live journeys, with HAFAS time conversion handled by packages/core.
  7. OSRM provides optional bike and final-walk routes.
  8. Wiener Linien endpoints provide nearby stops and live departures around the destination.
  9. Countdown and leave-by calculations combine event time, selected transport mode, journey arrival, final walk duration, bike route duration, and reminder buffers.

External Integrations

Service Used for Access path
ÖBB HAFAS Station lookup and journey search /api/hafas
ÖBB GTFS ZIP Optional train metadata enrichment apps/web/src/lib/oebb-gtfs.ts
Nominatim Destination geocoding /api/geocode
OSRM Bike and foot routes /api/bike-route, /api/walk-route
Wiener Linien Darwin Nearby stops and live monitor data /api/wienerlinien/*
Google Calendar Web OAuth calendar import /api/auth/google/*, /api/calendar/google
Remote ICS providers Calendar URL import /api/calendar

Security and Operational Constraints

  • Calendar URL imports validate provider domains, block private/reserved hosts, reject redirects, validate content type, and cap response size.
  • HAFAS POST bodies are size-limited and only TripSearch and LocMatch are allowed.
  • Coordinate APIs validate ranges and reject unrealistically distant route requests.
  • API routes are rate-limited per client IP.
  • CORS is allow-list based through CORS_ALLOWED_ORIGINS.
  • Google OAuth tokens are stored in HTTP-only cookies.
  • HAFAS time values must be treated as Europe/Vienna local strings.
  • This project uses Next.js 16. Before changing framework behavior, routing, route handlers, or proxy/middleware code, read the relevant guide in node_modules/next/dist/docs/.