# Development Guide ## Prerequisites - Node.js 20 or newer - npm 9 or newer - Android Studio or Xcode for native mobile builds - Expo/EAS tooling when building or submitting mobile apps ## Install ```bash npm install cp .env.example .env ``` The root workspace owns dependency installation. Avoid installing separately inside workspaces unless you are intentionally changing that workspace's dependency list. ## Environment | Variable | Default | Purpose | | --- | --- | --- | | `PORT` | `3000` through Next dev defaults | Web server port when the runtime honors it. | | `HAFAS_URL` | `https://fahrplan.oebb.at/bin/mgate.exe` | ÖBB HAFAS endpoint. | | `HAFAS_TIMEOUT_MS` | `10000` | HAFAS request timeout. | | `HAFAS_VER`, `HAFAS_LANG`, `HAFAS_AID`, `HAFAS_CLIENT_*` | ÖBB app-compatible defaults | HAFAS request envelope metadata. | | `NOMINATIM_URL` | `https://nominatim.openstreetmap.org` | Geocoding endpoint. | | `NOMINATIM_USER_AGENT` | `TimeToLeave/2.0` | Required Nominatim user agent. | | `OSRM_URL` | `https://router.project-osrm.org` | Bike and foot routing base URL. | | `WIENER_LINIEN_API_URL` | `https://api.wienerlinien.at/darwin-v2` | Wiener Linien live data base URL. | | `OEBB_GTFS_URL` | ÖBB 2026 GTFS ZIP | Optional HAFAS response enrichment source. | | `CORS_ALLOWED_ORIGINS` | `http://localhost:3000` fallback | Allowed browser origins for `/api/*`. | | `API_RATE_LIMIT_MAX_REQUESTS` | `120` | Per-IP API requests per window. | | `API_RATE_LIMIT_WINDOW_MS` | `60000` | Rate-limit window length. | | `DEPLOYMENT_URL` | local fallback in OAuth code | Public app URL for Google OAuth redirects. | | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI` | none | Enables Google Calendar sync. | | `APP_VERSION` | `0.1.0` | Version returned by `/api/health`. | | `EXPO_PUBLIC_API_BASE_URL` | empty | Mobile backend URL. Required for physical devices unless the empty base URL is intentionally proxied. | ## Commands | Command | Description | | --- | --- | | `npm run dev` | Start the web app with `next dev`. | | `npm run build` | Build the web app. | | `npm run start` | Start the built web app. | | `npm run dev:mobile` | Start the Expo dev server. | | `npm run android` | Run the Expo app on Android from the root script. | | `npm run ios` | Run the Expo app on iOS from the root script. | | `npm run lint` | Run ESLint across web, mobile, core, and api-client. | | `npm run typecheck` | Run TypeScript checks across all workspaces. | | `npm run test` | Run web Vitest tests and mobile Jest tests. | Workspace-specific examples: ```bash npm run test -w apps/web npm run test -w apps/mobile npm run typecheck -w packages/core npm run typecheck -w packages/api-client ``` ## Web Routes | Route | Description | | --- | --- | | `/` | Departure desk with next upcoming event, train/bike mode selector, live journeys, route sections, reminders, and nearby Wiener Linien departures. | | `/calendar` | Calendar import/management with URL, file, Google OAuth, and batch destination editing. | The web add/edit event flow is implemented by `apps/web/src/app/add-event/AddEventModal.tsx`, not a standalone route. ## API Routes | Endpoint | Methods | Notes | | --- | --- | --- | | `/api/health` | `GET` | Returns `{ ok, ts, version }`. | | `/api/hafas` | `GET`, `POST` | GET is a simple trip search. POST validates and forwards `TripSearch` or `LocMatch`. | | `/api/geocode` | `GET` | Requires `name`; optional `countrycodes`. Returns the first result. | | `/api/bike-route` | `GET` | Requires `fromLat`, `fromLng`, `toLat`, `toLng`. | | `/api/walk-route` | `GET` | Same coordinate contract as bike route, using OSRM foot profile. | | `/api/calendar` | `GET` | Requires an allowed remote ICS `url`; optional `days`. | | `/api/calendar/parse` | `POST` | Parses raw ICS text from the request body. | | `/api/calendar/google` | `GET` | Requires Google OAuth token cookie; optional day horizon. | | `/api/auth/google` | `GET` | Starts OAuth. | | `/api/auth/google/callback` | `GET` | Completes OAuth. | | `/api/auth/google/status` | `GET` | Reports configuration and connection state. | | `/api/auth/google/disconnect` | `POST` | Clears OAuth token cookie. | | `/api/wienerlinien/stops` | `GET` | Requires `lat`, `lng`; optional `radius`, capped server-side. | | `/api/wienerlinien/monitor` | `GET` | Accepts repeated `stopIds`, capped server-side. | ## Docker The web app includes Docker support: ```bash cd apps/web docker-compose up --build ``` ## Testing Notes - Web tests use Vitest, jsdom, and Testing Library. - Mobile tests use Jest, jest-expo, and React Native Testing Library. - Route, geocoding, HAFAS, calendar, routing, reminder, and UI component tests are present across the repo. - Run `npm run typecheck` after changing shared types because both apps consume `packages/core`. ## Development Rules - Use `@timetoleave/core` for shared behavior. - Use `@timetoleave/api-client` for client-to-backend calls that are shared by web and mobile. - Keep backend validation close to route handlers, then delegate external service behavior to `apps/web/src/lib`. - Never parse HAFAS date/time values with ad hoc `Date` logic; use `parseHafasTime()` and `hafasDateTime()`. - This repo uses Next.js 16. Read the relevant `node_modules/next/dist/docs/` material before changing Next.js framework code.