Add Docker support with multi-stage build and standalone output
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
coverage/
|
||||
|
||||
# env files
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# IDE / editors
|
||||
.idea/
|
||||
.vscode/
|
||||
.zed/
|
||||
.claude/
|
||||
|
||||
# git
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# docker
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.dockerignore
|
||||
|
||||
# docs
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
CHECKLIST.md
|
||||
REWRITE_PLAN.md
|
||||
|
||||
# tests
|
||||
vitest.config.ts
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# ---- Stage 1: Install dependencies ----
|
||||
FROM node:20-alpine AS deps
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# ---- Stage 2: Build the app ----
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ---- Stage 3: Production image ----
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy built artifacts
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
|
||||
# Use standalone output if available, otherwise copy .next
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
|
||||
# Ensure correct permissions
|
||||
RUN chown -R nextjs:nodejs /app
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -1,21 +1,150 @@
|
||||
# TimeToLeave - Next.js Rewrite
|
||||
# TimeToLeave
|
||||
|
||||
This is a rewrite of the TimeToLeave application using Next.js App Router with TypeScript.
|
||||
> Know when to leave the house so you're never late for your next meeting.
|
||||
|
||||
## Development
|
||||
TimeToLeave is a web app that tells you **when to leave** for upcoming events based on real-time Austrian train (ÖBB) schedules — and optionally compares train journeys with bicycle routes.
|
||||
|
||||
## Features
|
||||
|
||||
- **Real-time train journeys** — Query the ÖBB HAFAS API for live departure times, delays, and platform information.
|
||||
- **Calendar import** — Import events from any ICS calendar (CalDAV, Google Calendar, etc.).
|
||||
- **Bicycle routing** — Compare train journeys with bicycle routes powered by OSRM, including distance, duration, and turn-by-turn directions.
|
||||
- **Train vs bicycle comparison** — See both travel modes side by side for each event, so you can decide what works best.
|
||||
- **Calendar month view** — Browse events in a month grid, see event indicators on dates, and drill into train/bike details per day.
|
||||
- **Geolocation support** — Use your browser's current location as the origin station.
|
||||
- **Countdown & status indicators** — Visual countdown timers and live status badges so you know exactly when to go.
|
||||
|
||||
## Architecture
|
||||
|
||||
Built with **Next.js App Router** (v16), **React 19**, **TypeScript**, and **Tailwind CSS v4**.
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ # Next.js App Router pages & layout
|
||||
│ ├── api/ # Server-side API routes
|
||||
│ │ ├── hafas/ # ÖBB train journey search
|
||||
│ │ ├── calendar/ # ICS calendar parsing
|
||||
│ │ ├── geocode/ # Nominatim geocoding
|
||||
│ │ ├── bike-route/ # OSRM bicycle routing
|
||||
│ │ └── health/ # Server health check
|
||||
│ ├── ui/ # Shared UI components
|
||||
│ ├── page.tsx # Home page
|
||||
│ └── layout.tsx # Root layout
|
||||
├── hooks/ # Custom React hooks
|
||||
│ ├── useJourneys.ts # Fetch train journeys per event
|
||||
│ ├── useBikeRoute.ts # Fetch bicycle routes
|
||||
│ ├── useCalendar.ts # Calendar import & parsing
|
||||
│ ├── useEventsStore.tsx # Events state management
|
||||
│ ├── useGeolocation.ts # Browser geolocation
|
||||
│ ├── useOriginStation.ts # Origin station selection
|
||||
│ ├── useClock.ts # Live clock for countdowns
|
||||
│ └── useServerHealth.ts # API health monitoring
|
||||
├── lib/ # Shared utilities & clients
|
||||
│ ├── hafas-client.ts # ÖBB HAFAS API client
|
||||
│ ├── geocoding-client.ts # Nominatim geocoding client
|
||||
│ ├── bike-routing-client.ts# OSRM bicycle routing client
|
||||
│ ├── calendar-utils.ts # ICS parsing utilities
|
||||
│ ├── countdown-utils.ts # Countdown time calculations
|
||||
│ ├── formatting.ts # Date/time formatting helpers
|
||||
│ └── constants.ts # App-wide constants
|
||||
├── types/ # TypeScript type definitions
|
||||
└── test/ # Test utilities
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20+
|
||||
- npm (or yarn / pnpm)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Building
|
||||
The app will be available at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
### Build & Start
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Testing
|
||||
### Docker Deployment
|
||||
|
||||
Build and run with Docker Compose:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
The app will be available on port `3000` by default. Override the port or any other variable by creating a `.env` file at the project root:
|
||||
|
||||
```env
|
||||
PORT=8080
|
||||
HAFAS_URL=https://fahrplan.oebb.at/bin/mgate.exe
|
||||
NOMINATIM_URL=https://nominatim.openstreetmap.org
|
||||
NOMINATIM_USER_AGENT=TimeToLeave/2.0
|
||||
OSRM_URL=https://router.project-osrm.org
|
||||
```
|
||||
|
||||
The `Dockerfile` uses a 3-stage build (deps → builder → runner) with `output: "standalone"`, producing a small final image that runs as a non-root user. Stop and remove the container with `docker compose down`.
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
npm test # Run tests once
|
||||
npm run test:watch # Run tests in watch mode
|
||||
```
|
||||
|
||||
### Linting & Type Checking
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables can be configured (defaults are provided for development):
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HAFAS_URL` | ÖBB HAFAS API endpoint | `https://fahrplan.oebb.at/bin/mgate.exe` |
|
||||
| `NOMINATIM_URL` | Nominatim geocoding endpoint | `https://nominatim.openstreetmap.org` |
|
||||
| `NOMINATIM_USER_AGENT` | User-Agent for Nominatim requests | `TimeToLeave/2.0` |
|
||||
| `OSRM_URL` | OSRM routing endpoint | `https://router.project-osrm.org` |
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Category | Technology |
|
||||
|----------|-----------|
|
||||
| Framework | [Next.js 16](https://nextjs.org/) (App Router) |
|
||||
| UI | [React 19](https://react.dev/) + [Tailwind CSS v4](https://tailwindcss.com/) |
|
||||
| Language | [TypeScript 5](https://www.typescriptlang.org/) |
|
||||
| Train Data | [ÖBB HAFAS API](https://fahrplan.oebb.at/) |
|
||||
| Geocoding | [Nominatim](https://nominatim.openstreetmap.org/) |
|
||||
| Bicycle Routing | [OSRM](https://project-osrm.org/) |
|
||||
| Calendar | [node-ical](https://www.npmjs.com/package/node-ical) |
|
||||
| Dates | [date-fns](https://date-fns.org/) |
|
||||
| Testing | [Vitest](https://vitest.dev/) + [React Testing Library](https://testing-library.com/) |
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Add events** — Manually enter events or import from an ICS calendar URL.
|
||||
2. **Pick an origin** — Use your current location or search for a departure station.
|
||||
3. **Get journey info** — The app queries ÖBB for real-time train schedules and optionally calculates bicycle routes via OSRM.
|
||||
4. **Decide when to leave** — Each event shows a countdown, the best available train, and (if enabled) a bicycle alternative with estimated travel time.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: time-to-leave
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${PORT:-3000}:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HAFAS_URL=${HAFAS_URL:-https://fahrplan.oebb.at/bin/mgate.exe}
|
||||
- NOMINATIM_URL=${NOMINATIM_URL:-https://nominatim.openstreetmap.org}
|
||||
- NOMINATIM_USER_AGENT=${NOMINATIM_USER_AGENT:-TimeToLeave/2.0}
|
||||
- OSRM_URL=${OSRM_URL:-https://router.project-osrm.org}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
Reference in New Issue
Block a user