83 lines
4.6 KiB
Markdown
83 lines
4.6 KiB
Markdown
# 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
|
|
|
|
```text
|
|
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/`.
|