Compare commits

..

No commits in common. "6" and "5.0.2" have entirely different histories.
6 ... 5.0.2

24 changed files with 823 additions and 6598 deletions

View file

@ -1,11 +1,12 @@
{ {
"extends": "eslint:recommended", "extends": "eslint:recommended",
"env": { "env": {
"es2022": true, "commonjs": true,
"es6": true,
"node": true "node": true
}, },
"parserOptions": { "parserOptions": {
"sourceType": "module" "ecmaVersion": 2018
}, },
"ignorePatterns": [ "ignorePatterns": [
"node_modules" "node_modules"

13
.github/renovate.json vendored
View file

@ -1,13 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"configMigration": true,
"lockFileMaintenance": {
"enabled": true,
"automerge": true,
"automergeType": "branch",
"ignoreTests": true
}
}

View file

@ -2,11 +2,21 @@ name: build & publish Docker image
on: on:
push: push:
branches: branches:
- 6 - 5
jobs: jobs:
lint-test: lint-test:
name: lint, build & test name: lint & test
uses: './.github/workflows/lint-test.yml' runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: setup Node
uses: actions/setup-node@v1
with:
node-version: 16.x
- run: npm install
- run: npm run lint
- run: npm test
build-and-publish: build-and-publish:
name: build & publish Docker image name: build & publish Docker image
@ -14,32 +24,23 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: check out the repo - name: check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: set up QEMU
uses: docker/setup-qemu-action@v3
- name: configure Docker to use buildx
uses: docker/setup-buildx-action@v3
- name: log in to Docker Hub - name: log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: build Docker image & push to Docker Hub - name: build Docker image & push to Docker Hub
uses: docker/build-push-action@v4 uses: docker/build-push-action@v2
with: with:
context: .
push: true push: true
platforms: linux/amd64,linux/arm64
tags: | tags: |
derhuerst/db-rest:6 derhuerst/db-rest:5
derhuerst/db-rest:latest derhuerst/db-rest:latest
# https://docs.docker.com/build/ci/github-actions/examples/#github-cache
cache-from: type=gha
cache-to: type=gha,mode=max,oci-mediatypes=true,compression=zstd
# this is for the public-transport/infrastructure cluster # this is for the public-transport/infrastructure cluster
- name: log in to GitHub Container Registry - name: log in to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -51,14 +52,7 @@ jobs:
id: datetime id: datetime
run: echo "::set-output name=datetime::$(date -u +'%Y-%m-%dT%H.%M.%SZ')" run: echo "::set-output name=datetime::$(date -u +'%Y-%m-%dT%H.%M.%SZ')"
- name: push Docker image to GitHub Registry - name: push Docker image to GitHub Registry
uses: docker/build-push-action@v4 uses: docker/build-push-action@v2
with: with:
context: .
push: true push: true
platforms: linux/amd64,linux/arm64 tags: ghcr.io/${{github.repository}}:v5_${{steps.hash.outputs.hash}}_${{steps.datetime.outputs.datetime}}
tags: |
ghcr.io/${{github.repository}}:v6
ghcr.io/${{github.repository}}:v6_${{steps.hash.outputs.hash}}_${{steps.datetime.outputs.datetime}}
# https://docs.docker.com/build/ci/github-actions/examples/#github-cache
cache-from: type=gha
cache-to: type=gha,mode=max,oci-mediatypes=true,compression=zstd

View file

@ -1,35 +0,0 @@
name: lint, build & test
on:
push:
branches:
- '*'
pull_request:
branches:
- '*'
workflow_call:
jobs:
lint-test:
name: lint, build & test
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- '16.x'
- '18.x'
- '20.x'
- '22.x'
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run lint
- run: npm run build
- run: npm test

2
.gitignore vendored
View file

@ -5,8 +5,8 @@ Thumbs.db
node_modules node_modules
npm-debug.log npm-debug.log
/package-lock.json
/dump.rdb /dump.rdb
/docs/*.html /docs/*.html
/docs/syntax.css /docs/syntax.css
/docs/api.md

View file

@ -1,10 +1,10 @@
FROM node:18-alpine as builder FROM node:alpine as builder
WORKDIR /app WORKDIR /app
# install dependencies # install dependencies
RUN apk add --update git bash RUN apk add --update git bash
ADD package.json package-lock.json /app ADD package.json /app
RUN npm ci RUN npm install
# build documentation # build documentation
ADD . /app ADD . /app
@ -12,13 +12,13 @@ RUN npm run build
# --- # ---
FROM node:18-alpine FROM node:alpine
LABEL org.opencontainers.image.title="db-rest" LABEL org.opencontainers.image.title="db-rest"
LABEL org.opencontainers.image.description="A clean REST API wrapping around the Deutsche Bahn API." LABEL org.opencontainers.image.description="A clean REST API wrapping around the Deutsche Bahn API."
LABEL org.opencontainers.image.authors="Jannis R <mail@jannisr.de>" LABEL org.opencontainers.image.authors="Jannis R <mail@jannisr.de>"
LABEL org.opencontainers.image.documentation="https://github.com/derhuerst/db-rest/tree/6" LABEL org.opencontainers.image.documentation="https://github.com/derhuerst/db-rest/tree/5"
LABEL org.opencontainers.image.source="https://github.com/derhuerst/db-rest" LABEL org.opencontainers.image.source="https://github.com/derhuerst/db-rest"
LABEL org.opencontainers.image.revision="6" LABEL org.opencontainers.image.revision="5"
LABEL org.opencontainers.image.licenses="ISC" LABEL org.opencontainers.image.licenses="ISC"
WORKDIR /app WORKDIR /app
@ -32,7 +32,7 @@ COPY --from=builder /app/docs ./docs
EXPOSE 3000 EXPOSE 3000
ENV HOSTNAME v6.db.transport.rest ENV HOSTNAME v5.db.transport.rest
ENV PORT 3000 ENV PORT 3000
CMD ["node", "index.js"] CMD ["node", "index.js"]

View file

@ -1,14 +1,16 @@
import {generateApiDocs} from 'hafas-rest-api/tools/generate-docs.js' 'use strict'
import {api} from '../api.js'
const generateApiDocs = require('hafas-rest-api/tools/generate-docs')
const {api} = require('./api')
const HEAD = `\ const HEAD = `\
# \`v6.db.transport.rest\` API documentation # \`v5.db.transport.rest\` API documentation
[\`v6.db.transport.rest\`](https://v6.db.transport.rest/) is a [REST API](https://restfulapi.net). Data is being returned as [JSON](https://www.json.org/). [\`v5.db.transport.rest\`](https://v5.db.transport.rest/) is a [REST API](https://restfulapi.net). Data is being returned as [JSON](https://www.json.org/).
You can just use the API without authentication. There's a [rate limit](https://apisyouwonthate.com/blog/what-is-api-rate-limiting-all-about) of 100 request/minute (burst 200 requests/minute) set up. You can just use the API without authentication. There's a [rate limit](https://apisyouwonthate.com/blog/what-is-api-rate-limiting-all-about) of 100 request/minute (burst 200 requests/minute) set up.
[OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv6.db.transport.rest%2F.well-known%2Fservice-desc%0A) [OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv5.db.transport.rest%2F.well-known%2Fservice-desc%0A)
*Note:* The examples snippets in this documentation uses the \`url-encode\` CLI tool of the [\`url-decode-encode-cli\` package](https://www.npmjs.com/package/url-decode-encode-cli) for [URL-encoding](https://de.wikipedia.org/wiki/URL-Encoding). *Note:* The examples snippets in this documentation uses the \`url-encode\` CLI tool of the [\`url-decode-encode-cli\` package](https://www.npmjs.com/package/url-decode-encode-cli) for [URL-encoding](https://de.wikipedia.org/wiki/URL-Encoding).
` `
@ -30,22 +32,22 @@ const order = [
const descriptions = { const descriptions = {
'/locations': `\ '/locations': `\
Uses [\`hafasClient.locations()\`](https://github.com/public-transport/hafas-client/blob/6/docs/locations.md) to **find stops/stations, POIs and addresses matching \`query\`**. Uses [\`hafasClient.locations()\`](https://github.com/public-transport/hafas-client/blob/5/docs/locations.md) to **find stops/stations, POIs and addresses matching \`query\`**.
`, `,
'/stops/nearby': `\ '/stops/nearby': `\
Uses [\`hafasClient.nearby()\`](https://github.com/public-transport/hafas-client/blob/6/docs/nearby.md) to **find stops/stations close to the given geolocation**. Uses [\`hafasClient.nearby()\`](https://github.com/public-transport/hafas-client/blob/5/docs/nearby.md) to **find stops/stations close to the given geolocation**.
`, `,
'/stops/reachable-from': `\ '/stops/reachable-from': `\
Uses [\`hafasClient.reachableFrom()\`](https://github.com/public-transport/hafas-client/blob/6/docs/reachable-from.md) to **find stops/stations reachable within a certain time from an address**. Uses [\`hafasClient.reachableFrom()\`](https://github.com/public-transport/hafas-client/blob/5/docs/reachable-from.md) to **find stops/stations reachable within a certain time from an address**.
`, `,
'/stops/:id': `\ '/stops/:id': `\
Uses [\`hafasClient.stop()\`](https://github.com/public-transport/hafas-client/blob/6/docs/stop.md) to **find a stop/station by ID**. Uses [\`hafasClient.stop()\`](https://github.com/public-transport/hafas-client/blob/5/docs/stop.md) to **find a stop/station by ID**.
`, `,
'/stops/:id/departures': `\ '/stops/:id/departures': `\
Uses [\`hafasClient.departures()\`](https://github.com/public-transport/hafas-client/blob/6/docs/departures.md) to **get departures at a stop/station**. Uses [\`hafasClient.departures()\`](https://github.com/public-transport/hafas-client/blob/5/docs/departures.md) to **get departures at a stop/station**.
`, `,
'/stops/:id/arrivals': `\ '/stops/:id/arrivals': `\
Works like [\`/stops/:id/departures\`](#get-stopsiddepartures), except that it uses [\`hafasClient.arrivals()\`](https://github.com/public-transport/hafas-client/blob/6/docs/arrivals.md) to **arrivals at a stop/station**. Works like [\`/stops/:id/departures\`](#get-stopsiddepartures), except that it uses [\`hafasClient.arrivals()\`](https://github.com/public-transport/hafas-client/blob/5/docs/arrivals.md) to **arrivals at a stop/station**.
`, `,
'/stations': `\ '/stations': `\
If the \`query\` parameter is used, it will use [\`db-stations-autocomplete@2\`](https://github.com/derhuerst/db-stations-autocomplete/tree/2.2.0) to autocomplete *Deutsche Bahn*-operated stops/stations. Otherwise, it will filter the stops/stations in [\`db-stations@3\`](https://github.com/derhuerst/db-stations/tree/3.0.1). If the \`query\` parameter is used, it will use [\`db-stations-autocomplete@2\`](https://github.com/derhuerst/db-stations-autocomplete/tree/2.2.0) to autocomplete *Deutsche Bahn*-operated stops/stations. Otherwise, it will filter the stops/stations in [\`db-stations@3\`](https://github.com/derhuerst/db-stations/tree/3.0.1).
@ -56,32 +58,32 @@ Instead of receiving a JSON response, you can request [newline-delimited JSON](h
Returns a stop/station from [\`db-stations\`](https://npmjs.com/package/db-stations). Returns a stop/station from [\`db-stations\`](https://npmjs.com/package/db-stations).
`, `,
'/journeys': `\ '/journeys': `\
Uses [\`hafasClient.journeys()\`](https://github.com/public-transport/hafas-client/blob/6/docs/journeys.md) to **find journeys from A (\`from\`) to B (\`to\`)**. Uses [\`hafasClient.journeys()\`](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) to **find journeys from A (\`from\`) to B (\`to\`)**.
\`from\` (A), \`to\` (B), and the optional \`via\` must each have one of these formats: \`from\` (A), \`to\` (B), and the optional \`via\` must each have one of these formats:
- as stop/station ID (e.g. \`from=8010159\` for *Halle (Saale) Hbf*) - as stop/station ID (e.g. \`from=8010159\` for *Halle (Saale) Hbf*)
- as a POI (e.g. \`from.id=991561765&from.latitude=51.48364&from.longitude=11.98084&from.name=Halle+(Saale),+Stadtpark+Halle+(Grünanlagen)\` for *Halle (Saale), Stadtpark Halle (Grünanlagen)*) - as a POI (e.g. \`from.id=991561765&from.latitude=51.48364&from.longitude=11.98084\` for *Halle+(Saale),+Stadtpark+Halle+(Grünanlagen)*)
- as an address (e.g. \`from.latitude=51.25639&from.longitude=7.46685&from.address=Hansestadt+Breckerfeld,+Hansering+3\` for *Hansestadt Breckerfeld, Hansering 3*) - as an address (e.g. \`from.latitude=51.25639&from.longitude=7.46685&from.address=Hansestadt+Breckerfeld,+Hansering+3\` for *Hansestadt Breckerfeld, Hansering 3*)
### Pagination ### Pagination
Given a response, you can also fetch more journeys matching the same criteria. Instead of \`from*\`, \`to*\` & \`departure\`/\`arrival\`, pass \`earlierRef\` from the first response as \`earlierThan\` to get journeys "before", or \`laterRef\` as \`laterThan\` to get journeys "after". Given a response, you can also fetch more journeys matching the same criteria. Instead of \`from*\`, \`to*\` & \`departure\`/\`arrival\`, pass \`earlierRef\` from the first response as \`earlierThan\` to get journeys "before", or \`laterRef\` as \`laterThan\` to get journeys "after".
Check the [\`hafasClient.journeys()\` docs](https://github.com/public-transport/hafas-client/blob/6/docs/journeys.md) for more details. Check the [\`hafasClient.journeys()\` docs](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) for more details.
`, `,
'/journeys/:ref': `\ '/journeys/:ref': `\
Uses [\`hafasClient.refreshJourney()\`](https://github.com/public-transport/hafas-client/blob/6/docs/refresh-journey.md) to **"refresh" a journey, using its \`refreshToken\`**. Uses [\`hafasClient.refreshJourney()\`](https://github.com/public-transport/hafas-client/blob/5/docs/refresh-journey.md) to **"refresh" a journey, using its \`refreshToken\`**.
The journey will be the same (equal \`from\`, \`to\`, \`via\`, date/time & vehicles used), but you can get up-to-date realtime data, like delays & cancellations. The journey will be the same (equal \`from\`, \`to\`, \`via\`, date/time & vehicles used), but you can get up-to-date realtime data, like delays & cancellations.
`, `,
'/trips/:id': `\ '/trips/:id': `\
Uses [\`hafasClient.trip()\`](https://github.com/public-transport/hafas-client/blob/6/docs/trip.md) to **fetch a trip by ID**. Uses [\`hafasClient.trip()\`](https://github.com/public-transport/hafas-client/blob/5/docs/trip.md) to **fetch a trip by ID**.
A trip is a specific vehicle, stopping at a series of stops at specific points in time. Departures, arrivals & journey legs reference trips by their ID. A trip is a specific vehicle, stopping at a series of stops at specific points in time. Departures, arrivals & journey legs reference trips by their ID.
`, `,
'/radar': `\ '/radar': `\
Uses [\`hafasClient.radar()\`](https://github.com/public-transport/hafas-client/blob/6/docs/radar.md) to **find all vehicles currently in an area**, as well as their movements. Uses [\`hafasClient.radar()\`](https://github.com/public-transport/hafas-client/blob/5/docs/radar.md) to **find all vehicles currently in an area**, as well as their movements.
`, `,
} }
@ -90,7 +92,7 @@ const examples = {
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v6.db.transport.rest/locations?query=halle&results=1' -s | jq curl 'https://v5.db.transport.rest/locations?query=halle&results=1' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -118,7 +120,7 @@ curl 'https://v6.db.transport.rest/locations?query=halle&results=1' -s | jq
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v6.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.0015' -s | jq curl 'https://v5.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.0015' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -158,7 +160,7 @@ curl 'https://v6.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.00
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v6.db.transport.rest/stops/reachable-from?latitude=53.553766&longitude=9.977514&address=Hamburg,+Holstenwall+9' -s | jq curl 'https://v5.db.transport.rest/stops/reachable-from?latitude=53.553766&longitude=9.977514&address=Hamburg,+Holstenwall+9' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -198,7 +200,7 @@ curl 'https://v6.db.transport.rest/stops/reachable-from?latitude=53.553766&longi
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v6.db.transport.rest/stops/8010159' -s | jq curl 'https://v5.db.transport.rest/stops/8010159' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -227,7 +229,7 @@ curl 'https://v6.db.transport.rest/stops/8010159' -s | jq
\`\`\`shell \`\`\`shell
# autocomplete using db-stations-autocomplete # autocomplete using db-stations-autocomplete
curl 'https://v6.db.transport.rest/stations?query=dammt' -s | jq curl 'https://v5.db.transport.rest/stations?query=dammt' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -263,7 +265,7 @@ curl 'https://v6.db.transport.rest/stations?query=dammt' -s | jq
\`\`\`shell \`\`\`shell
# filter db-stations by \`hasParking\` property # filter db-stations by \`hasParking\` property
curl 'https://v6.db.transport.rest/stations?hasParking=true' -s | jq curl 'https://v5.db.transport.rest/stations?hasParking=true' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -285,7 +287,7 @@ curl 'https://v6.db.transport.rest/stations?hasParking=true' -s | jq
\`\`\`shell \`\`\`shell
# filter db-stations by \`hasDBLounge\` property, get newline-delimited JSON # filter db-stations by \`hasDBLounge\` property, get newline-delimited JSON
curl 'https://v6.db.transport.rest/stations?hasDBLounge=true' -H 'accept: application/x-ndjson' -s | jq curl 'https://v5.db.transport.rest/stations?hasDBLounge=true' -H 'accept: application/x-ndjson' -s | jq
\`\`\` \`\`\`
`, `,
'/stations/:id': `\ '/stations/:id': `\
@ -293,9 +295,9 @@ curl 'https://v6.db.transport.rest/stations?hasDBLounge=true' -H 'accept: applic
\`\`\`shell \`\`\`shell
# lookup Halle (Saale) Hbf # lookup Halle (Saale) Hbf
curl 'https://v6.db.transport.rest/stations/8010159' -s | jq curl 'https://v5.db.transport.rest/stations/8010159' -s | jq
curl 'https://v6.db.transport.rest/stations/LH' -s | jq # RIL100/DS100 curl 'https://v5.db.transport.rest/stations/LH' -s | jq # RIL100/DS100
curl 'https://v6.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100 curl 'https://v5.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -326,7 +328,7 @@ curl 'https://v6.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100
\`\`\`shell \`\`\`shell
# at Halle (Saale) Hbf, in direction Berlin Südkreuz # at Halle (Saale) Hbf, in direction Berlin Südkreuz
curl 'https://v6.db.transport.rest/stops/8010159/departures?direction=8011113&duration=120' -s | jq curl 'https://v5.db.transport.rest/stops/8010159/departures?direction=8011113&duration=120' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -368,7 +370,7 @@ curl 'https://v6.db.transport.rest/stops/8010159/departures?direction=8011113&du
\`\`\`shell \`\`\`shell
# at Halle (Saale) Hbf, 10 minutes # at Halle (Saale) Hbf, 10 minutes
curl 'https://v6.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq curl 'https://v5.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq
\`\`\` \`\`\`
`, `,
'/journeys': `\ '/journeys': `\
@ -376,9 +378,9 @@ curl 'https://v6.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq
\`\`\`shell \`\`\`shell
# stop/station to POI # stop/station to POI
curl 'https://v6.db.transport.rest/journeys?from=8010159&to.id=991561765&to.latitude=51.483641&to.longitude=11.980841' -s | jq curl 'https://v5.db.transport.rest/journeys?from=8010159&to.id=991561765&to.latitude=51.483641&to.longitude=11.980841' -s | jq
# without buses, with ticket info # without buses, with ticket info
curl 'https://v6.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=true' -s | jq curl 'https://v5.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=true' -s | jq
\`\`\` \`\`\`
`, `,
'/journeys/:ref': `\ '/journeys/:ref': `\
@ -386,11 +388,11 @@ curl 'https://v6.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=tr
\`\`\`shell \`\`\`shell
# get the refreshToken of a journey # get the refreshToken of a journey
journey=$(curl 'https://v6.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]')
refresh_token=$(echo $journey | jq -r '.refreshToken') refresh_token=$(echo $journey | jq -r '.refreshToken')
# refresh the journey # refresh the journey
curl "https://v6.db.transport.rest/journeys/$(echo $refresh_token | url-encode)" -s | jq curl "https://v5.db.transport.rest/journeys/$(echo $refresh_token | url-encode)" -s | jq
\`\`\` \`\`\`
`, `,
'/trips/:id': `\ '/trips/:id': `\
@ -398,12 +400,12 @@ curl "https://v6.db.transport.rest/journeys/$(echo $refresh_token | url-encode)"
\`\`\`shell \`\`\`shell
# get the trip ID of a journey leg # get the trip ID of a journey leg
journey=$(curl 'https://v6.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]')
journey_leg=$(echo $journey | jq -r '.legs[0]') journey_leg=$(echo $journey | jq -r '.legs[0]')
trip_id=$(echo $journey_leg | jq -r '.tripId') trip_id=$(echo $journey_leg | jq -r '.tripId')
# fetch the trip # fetch the trip
curl "https://v6.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq curl "https://v5.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq
\`\`\` \`\`\`
`, `,
'/radar': `\ '/radar': `\
@ -411,47 +413,45 @@ curl "https://v6.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq
\`\`\`shell \`\`\`shell
bbox='north=53.555&west=9.989&south=53.55&east=10.001' bbox='north=53.555&west=9.989&south=53.55&east=10.001'
curl "https://v6.db.transport.rest/radar?$bbox&results=10" -s | jq curl "https://v5.db.transport.rest/radar?$bbox&results=10" -s | jq
\`\`\` \`\`\`
`, `,
} }
const generateMarkdownApiDocs = async function* () { const {
const { listOfRoutes,
listOfRoutes, routes,
routes, tail,
tail, } = generateApiDocs(api.routes)
} = generateApiDocs(api.routes)
const sortedRoutes = Object.entries(routes) const sortedRoutes = Object.entries(routes)
.sort(([routeA], [routeB]) => { .sort(([routeA], [routeB]) => {
const iA = order.indexOf(routeA) const iA = order.indexOf(routeA)
const iB = order.indexOf(routeB) const iB = order.indexOf(routeB)
if (iA >= 0 && iB >= 0) return iA - iB if (iA >= 0 && iB >= 0) return iA - iB
if (iA < 0 && iB >= 0) return 1 if (iA < 0 && iB >= 0) return 1
if (iB < 0 && iA >= 0) return -1 if (iB < 0 && iA >= 0) return -1
return 0 return 0
}) })
yield HEAD const write = process.stdout.write.bind(process.stdout)
yield `\n\n`
yield listOfRoutes write(HEAD)
yield `\n\n` write(`\n\n`)
for (const [route, params] of sortedRoutes) { write(listOfRoutes)
yield `## \`GET ${route}\`\n\n` write(`\n\n`)
yield descriptions[route] || ''
yield `\n### Query Parameters\n` for (const [route, params] of sortedRoutes) {
yield params write(`## \`GET ${route}\`\n\n`)
if (examples[route]) { write(descriptions[route] || '')
yield '\n' + examples[route] write(`
} ### Query Parameters
yield `\n\n` `)
write(params)
if (examples[route]) {
write('\n' + examples[route])
} }
yield tail write(`\n\n`)
} }
write(tail)
export {
generateMarkdownApiDocs,
}

47
api.js
View file

@ -1,36 +1,29 @@
// todo: use import assertions once they're supported by Node.js & ESLint 'use strict'
// https://github.com/tc39/proposal-import-assertions
import {createRequire} from 'node:module'
const require = createRequire(import.meta.url)
import {dirname, join as pathJoin} from 'node:path'
import {fileURLToPath} from 'node:url'
import {createDbHafas as createHafas} from 'db-hafas'
import {createHafasRestApi} from 'hafas-rest-api'
import createHealthCheck from 'hafas-client-health-check'
import Redis from 'ioredis'
import {createCachedHafasClient} from 'cached-hafas-client'
import {createRedisStore} from 'cached-hafas-client/stores/redis.js'
import serveStatic from 'serve-static'
import {parseBoolean, parseInteger} from 'hafas-rest-api/lib/parse.js'
import {loyaltyCardParser} from './lib/loyalty-cards.js'
import {route as stations} from './routes/stations.js'
import {route as station} from './routes/station.js'
const createHafas = require('db-hafas')
const createApi = require('hafas-rest-api')
const createHealthCheck = require('hafas-client-health-check')
const Redis = require('ioredis')
const withCache = require('cached-hafas-client')
const redisStore = require('cached-hafas-client/stores/redis')
const {join: pathJoin} = require('path')
const serveStatic = require('serve-static')
const {parseBoolean} = require('hafas-rest-api/lib/parse')
const pkg = require('./package.json') const pkg = require('./package.json')
const {loyaltyCardParser} = require('./lib/loyalty-cards')
const stations = require('./routes/stations')
const station = require('./routes/station')
const __dirname = dirname(fileURLToPath(import.meta.url))
const docsRoot = pathJoin(__dirname, 'docs') const docsRoot = pathJoin(__dirname, 'docs')
const berlinHbf = '8011160' const berlinHbf = '8011160'
// todo: use process.env.HAFAS_USER_AGENT if defined
let hafas = createHafas(pkg.name) let hafas = createHafas(pkg.name)
let healthCheck = createHealthCheck(hafas, berlinHbf) let healthCheck = createHealthCheck(hafas, berlinHbf)
if (process.env.REDIS_URL) { if (process.env.REDIS_URL) {
const redis = new Redis(process.env.REDIS_URL || null) const redis = new Redis(process.env.REDIS_URL || null)
hafas = createCachedHafasClient(hafas, createRedisStore(redis), { hafas = withCache(hafas, redisStore(redis), {
cachePeriods: { cachePeriods: {
locations: 6 * 60 * 60 * 1000, // 6h locations: 6 * 60 * 60 * 1000, // 6h
}, },
@ -61,12 +54,6 @@ const mapRouteParsers = (route, parsers) => {
default: 'false', default: 'false',
parse: parseBoolean, parse: parseBoolean,
}, },
age: {
description: 'Age of traveller',
type: 'integer',
defaultStr: '*adult*',
parse: parseInteger
},
} }
} }
@ -83,7 +70,7 @@ const config = {
description: pkg.description, description: pkg.description,
homepage: pkg.homepage, homepage: pkg.homepage,
version: pkg.version, version: pkg.version,
docsLink: 'https://github.com/derhuerst/db-rest/blob/6/docs/readme.md', docsLink: 'https://github.com/derhuerst/db-rest/blob/5/docs/readme.md',
openapiSpec: true, openapiSpec: true,
logging: true, logging: true,
aboutPage: false, aboutPage: false,
@ -94,13 +81,13 @@ const config = {
modifyRoutes, modifyRoutes,
} }
const api = await createHafasRestApi(hafas, config, (api) => { const api = createApi(hafas, config, (api) => {
api.use('/', serveStatic(docsRoot, { api.use('/', serveStatic(docsRoot, {
extensions: ['html', 'htm'], extensions: ['html', 'htm'],
})) }))
}) })
export { module.exports = {
hafas, hafas,
config, config,
api, api,

View file

@ -42,7 +42,7 @@
<rect class="box" x="0" y="0" width="100" height="41" /> <rect class="box" x="0" y="0" width="100" height="41" />
<text class="monospace-text" x="26.2" y="13">db-rest</text> <text class="monospace-text" x="26.2" y="13">db-rest</text>
<text class="normal-text small-text" x="4" y="26">deployed at</text> <text class="normal-text small-text" x="4" y="26">deployed at</text>
<text class="monospace-text small-text" x="4" y="36">v6.db.transport.rest</text> <text class="monospace-text small-text" x="4" y="36">v5.db.transport.rest</text>
</g> </g>
<g transform="translate(269, 14)"> <g transform="translate(269, 14)">
<path class="arrow" d="M0,14 L50,14" /> <path class="arrow" d="M0,14 L50,14" />

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1,86 +0,0 @@
#!/usr/bin/env node
// todo: use import assertions once they're supported by Node.js & ESLint
// https://github.com/tc39/proposal-import-assertions
import {createRequire} from 'module'
const require = createRequire(import.meta.url)
import {dirname, join} from 'node:path'
import {pipeline} from 'node:stream/promises'
import {createReadStream, createWriteStream} from 'node:fs'
import {copyFile} from 'node:fs/promises'
import _technicalDocsCli from '@derhuerst/technical-docs-cli'
import {config} from '../api.js'
const {
createMarkdownRenderer,
determineSyntaxStylesheetPath,
} = _technicalDocsCli
import {generateMarkdownApiDocs} from './api-docs.js'
const pkg = require('../package.json')
const BASE_URL = new URL('..', import.meta.url).href
const API_DOCS_DEST = 'docs/api.md'
const DOCS_TO_RENDER = [
['docs/readme.md', 'docs/index.html'],
['docs/getting-started.md', 'docs/getting-started.html'],
['docs/api.md', 'docs/api.html'],
]
const SYNTAX_STYLESHEET_URL = '/syntax.css'
const SYNTAX_STYLESHEET_SRC = determineSyntaxStylesheetPath('github')
const SYNTAX_STYLESHEET_DEST = 'docs/syntax.css'
{
console.info('writing Markdown API docs to ' + API_DOCS_DEST)
const destPath = new URL(API_DOCS_DEST, BASE_URL).pathname
await pipeline(
generateMarkdownApiDocs(),
createWriteStream(destPath),
)
}
const markdownRenderingCfg = {
syntaxStylesheetUrl: SYNTAX_STYLESHEET_URL,
additionalHeadChildren: (h) => {
if (!pkg.goatCounterUrl) return []
return [
// https://72afc0822cce0642af90.goatcounter.com/help/skip-dev#skip-loading-staging-beta-sites-312
h('script', `
if (window.location.host !== ${JSON.stringify(config.hostname)}) {
window.goatcounter = {no_onload: true}
}
`),
// https://72afc0822cce0642af90.goatcounter.com/help/start
h('script', {
src: '//gc.zgo.at/count.js',
async: true,
'data-goatcounter': pkg.goatCounterUrl,
}),
]
},
}
for (const [src, dest] of DOCS_TO_RENDER) {
console.info(`rendering Markdown file ${src} to HTML file ${dest}`)
const srcPath = new URL(src, BASE_URL).pathname
const destPath = new URL(dest, BASE_URL).pathname
// unfortunately, we can't use stream.pipeline right now
// see https://github.com/unifiedjs/unified-stream/issues/1
await new Promise((resolve, reject) => {
createReadStream(srcPath)
.once('error', reject)
.pipe(createMarkdownRenderer(markdownRenderingCfg))
.once('error', reject)
.pipe(createWriteStream(destPath))
.once('error', reject)
.once('finish', resolve)
})
}
{
const srcPath = SYNTAX_STYLESHEET_SRC
const destPath = new URL(SYNTAX_STYLESHEET_DEST, BASE_URL).pathname
console.info(`copying syntax stylesheet from ${srcPath} to ${destPath}`)
await copyFile(srcPath, destPath)
}

620
docs/api.md Normal file
View file

@ -0,0 +1,620 @@
# `v5.db.transport.rest` API documentation
[`v5.db.transport.rest`](https://v5.db.transport.rest/) is a [REST API](https://restfulapi.net). Data is being returned as [JSON](https://www.json.org/).
You can just use the API without authentication. There's a [rate limit](https://apisyouwonthate.com/blog/what-is-api-rate-limiting-all-about) of 100 request/minute (burst 200 requests/minute) set up.
[OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv5.db.transport.rest%2F.well-known%2Fservice-desc%0A)
*Note:* The examples snippets in this documentation uses the `url-encode` CLI tool of the [`url-decode-encode-cli` package](https://www.npmjs.com/package/url-decode-encode-cli) for [URL-encoding](https://de.wikipedia.org/wiki/URL-Encoding).
## Routes
*Note:* These routes only wrap [`hafas-client@5` methods](https://github.com/public-transport/hafas-client/blob/5/docs/readme.md), check their docs for more details.
- [`GET /stops/nearby`](#get-stopsnearby)
- [`GET /stops/reachable-from`](#get-stopsreachable-from)
- [`GET /stops/:id`](#get-stopsid)
- [`GET /stops/:id/departures`](#get-stopsiddepartures)
- [`GET /stops/:id/arrivals`](#get-stopsidarrivals)
- [`GET /journeys`](#get-journeys)
- [`GET /trips/:id`](#get-tripsid)
- [`GET /locations`](#get-locations)
- [`GET /radar`](#get-radar)
- [`GET /journeys/:ref`](#get-journeysref)
- [`GET /stations/:id`](#get-stationsid)
- [`GET /stations`](#get-stations)
- [date/time parameters](#datetime-parameters)
## `GET /locations`
Uses [`hafasClient.locations()`](https://github.com/public-transport/hafas-client/blob/5/docs/locations.md) to **find stops/stations, POIs and addresses matching `query`**.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`query` | **Required.** | string |
`fuzzy` | Find more than exact matches? | boolean | `true`
`results` | How many stations shall be shown? | integer | `10`
`stops` | Show stops/stations? | boolean | `true`
`addresses` | Show points of interest? | boolean | `true`
`poi` | Show addresses? | boolean | `true`
`linesOfStops` | Parse & return lines of each stop/station? | boolean | `false`
`language` | Language of the results. | string | `en`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
curl 'https://v5.db.transport.rest/locations?query=halle&results=1' -s | jq
```
```js
[
{
"type": "stop",
"id": "8010159",
"name": "Halle (Saale) Hbf",
"location": {
"type": "location",
"id": "8010159",
"latitude": 51.477079,
"longitude": 11.98699
},
"products": {
"nationalExpress": true,
"national": true,
// …
}
}
]
```
## `GET /stops/nearby`
Uses [`hafasClient.nearby()`](https://github.com/public-transport/hafas-client/blob/5/docs/nearby.md) to **find stops/stations close to the given geolocation**.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`latitude` | **Required.** | number |
`longitude` | **Required.** | number |
`results` | maximum number of results | integer | `8`
`distance` | maximum walking distance in meters | integer |
`stops` | Return stops/stations? | boolean | `true`
`poi` | Return points of interest? | boolean | `false`
`linesOfStops` | Parse & expose lines at each stop/station? | boolean | `false`
`language` | Language of the results. | string | `en`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
curl 'https://v5.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.0015' -s | jq
```
```js
[
{
"type": "stop",
"id": "694800",
"name": "Böttgerstraße, Hamburg",
"location": {
"type": "location",
"id": "694800",
"latitude": 53.568356,
"longitude": 9.995528
},
"products": { /* … */ },
"distance": 498,
},
// …
{
"type": "stop",
"id": "694802",
"name": "Bahnhof Dammtor, Hamburg",
"location": {
"type": "location",
"id": "694802",
"latitude": 53.561048,
"longitude": 9.990315
},
"products": { /* … */ },
"distance": 1340,
},
// …
]
```
## `GET /stops/reachable-from`
Uses [`hafasClient.reachableFrom()`](https://github.com/public-transport/hafas-client/blob/5/docs/reachable-from.md) to **find stops/stations reachable within a certain time from an address**.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`latitude` | **Required.** | number |
`longitude` | **Required.** | number |
`address` | **Required.** | string |
`when` | Date & time to compute the reachability for. See [date/time parameters](#datetime-parameters). | date+time | *now*
`maxTransfers` | Maximum number of transfers. | integer | `5`
`maxDuration` | Maximum travel duration, in minutes. | integer | *infinite*
`language` | Language of the results. | string | `en`
`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true`
`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true`
`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true`
`regional` | Include Regio (RB)? | boolean | `true`
`suburban` | Include S-Bahn (S)? | boolean | `true`
`bus` | Include Bus (B)? | boolean | `true`
`ferry` | Include Ferry (F)? | boolean | `true`
`subway` | Include U-Bahn (U)? | boolean | `true`
`tram` | Include Tram (T)? | boolean | `true`
`taxi` | Include Group Taxi (Taxi)? | boolean | `true`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
curl 'https://v5.db.transport.rest/stops/reachable-from?latitude=53.553766&longitude=9.977514&address=Hamburg,+Holstenwall+9' -s | jq
```
```js
[
{
"duration": 1,
"stations": [
{
"type": "stop",
"id": "694815",
"name": "Handwerkskammer, Hamburg",
"location": { /* … */ },
"products": { /* … */ },
},
]
},
// …
{
"duration": 5,
"stations": [
{
"type": "stop",
"id": "694807",
"name": "Feldstraße (U), Hamburg",
"location": { /* … */ },
"products": { /* … */ },
// …
},
// …
]
},
// …
]
```
## `GET /stops/:id`
Uses [`hafasClient.stop()`](https://github.com/public-transport/hafas-client/blob/5/docs/stop.md) to **find a stop/station by ID**.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`linesOfStops` | Parse & expose lines at each stop/station? | boolean | `false`
`language` | Language of the results. | string | `en`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
curl 'https://v5.db.transport.rest/stops/8010159' -s | jq
```
```js
{
"type": "stop",
"id": "8010159",
"ids": {
"dhid": "de:15002:8010159",
"MDV": "8010159",
"NASA": "8010159"
},
"name": "Halle (Saale) Hbf",
"location": {
"type": "location",
"id": "8010159",
"latitude": 51.477079,
"longitude": 11.98699
},
"products": { /* … */ },
// …
}
```
## `GET /stops/:id/departures`
Uses [`hafasClient.departures()`](https://github.com/public-transport/hafas-client/blob/5/docs/departures.md) to **get departures at a stop/station**.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`when` | Date & time to get departures for. See [date/time parameters](#datetime-parameters). | date+time | *now*
`direction` | Filter departures by direction. | string |  
`duration` | Show departures for how many minutes? | integer | `10`
`results` | Max. number of departures. | integer | *whatever HAFAS wants
`linesOfStops` | Parse & return lines of each stop/station? | boolean | `false`
`remarks` | Parse & return hints & warnings? | boolean | `true`
`language` | Language of the results. | string | `en`
`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true`
`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true`
`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true`
`regional` | Include Regio (RB)? | boolean | `true`
`suburban` | Include S-Bahn (S)? | boolean | `true`
`bus` | Include Bus (B)? | boolean | `true`
`ferry` | Include Ferry (F)? | boolean | `true`
`subway` | Include U-Bahn (U)? | boolean | `true`
`tram` | Include Tram (T)? | boolean | `true`
`taxi` | Include Group Taxi (Taxi)? | boolean | `true`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
# at Halle (Saale) Hbf, in direction Berlin Südkreuz
curl 'https://v5.db.transport.rest/stops/8010159/departures?direction=8011113&duration=120' -s | jq
```
```js
[
{
"tripId": "1|317591|0|80|1052020",
"direction": "Berlin Hbf (tief)",
"line": {
"type": "line",
"id": "ice-702",
"name": "ICE 702",
"mode": "train",
"product": "nationalExpress",
// …
},
"when": "2020-05-01T21:06:00+02:00",
"plannedWhen": "2020-05-01T21:06:00+02:00",
"delay": 0,
"platform": "8",
"plannedPlatform": "8",
"stop": {
"type": "stop",
"id": "8010159",
"name": "Halle (Saale) Hbf",
"location": { /* … */ },
"products": { /* … */ },
},
"remarks": [],
// …
}
]
```
## `GET /stops/:id/arrivals`
Works like [`/stops/:id/departures`](#get-stopsiddepartures), except that it uses [`hafasClient.arrivals()`](https://github.com/public-transport/hafas-client/blob/5/docs/arrivals.md) to **arrivals at a stop/station**.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`when` | Date & time to get departures for. See [date/time parameters](#datetime-parameters). | date+time | *now*
`direction` | Filter departures by direction. | string |  
`duration` | Show departures for how many minutes? | integer | `10`
`results` | Max. number of departures. | integer | *whatever HAFAS wants*
`linesOfStops` | Parse & return lines of each stop/station? | boolean | `false`
`remarks` | Parse & return hints & warnings? | boolean | `true`
`language` | Language of the results. | string | `en`
`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true`
`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true`
`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true`
`regional` | Include Regio (RB)? | boolean | `true`
`suburban` | Include S-Bahn (S)? | boolean | `true`
`bus` | Include Bus (B)? | boolean | `true`
`ferry` | Include Ferry (F)? | boolean | `true`
`subway` | Include U-Bahn (U)? | boolean | `true`
`tram` | Include Tram (T)? | boolean | `true`
`taxi` | Include Group Taxi (Taxi)? | boolean | `true`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
# at Halle (Saale) Hbf, 10 minutes
curl 'https://v5.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq
```
## `GET /journeys`
Uses [`hafasClient.journeys()`](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) to **find journeys from A (`from`) to B (`to`)**.
`from` (A), `to` (B), and the optional `via` must each have one of these formats:
- as stop/station ID (e.g. `from=8010159` for *Halle (Saale) Hbf*)
- as a POI (e.g. `from.id=991561765&from.latitude=51.48364&from.longitude=11.98084` for *Halle+(Saale),+Stadtpark+Halle+(Grünanlagen)*)
- as an address (e.g. `from.latitude=51.25639&from.longitude=7.46685&from.address=Hansestadt+Breckerfeld,+Hansering+3` for *Hansestadt Breckerfeld, Hansering 3*)
### Pagination
Given a response, you can also fetch more journeys matching the same criteria. Instead of `from*`, `to*` & `departure`/`arrival`, pass `earlierRef` from the first response as `earlierThan` to get journeys "before", or `laterRef` as `laterThan` to get journeys "after".
Check the [`hafasClient.journeys()` docs](https://github.com/public-transport/hafas-client/blob/5/docs/journeys.md) for more details.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`departure` | Compute journeys departing at this date/time. Mutually exclusive with `arrival`. See [date/time parameters](#datetime-parameters). | date+time | *now*
`arrival` | Compute journeys arriving at this date/time. Mutually exclusive with `departure`. See [date/time parameters](#datetime-parameters). | date+time | *now*
`earlierThan` | Compute journeys "before" an `ealierRef`. | string |  
`laterThan` | Compute journeys "after" an `laterRef`. | string |  
`results` | Max. number of journeys. | integer | `3`
`stopovers` | Fetch & parse stopovers on the way? | boolean | `false`
`transfers` | Maximum number of transfers. | integer | *let HAFAS decide*
`transferTime` | Minimum time in minutes for a single transfer. | integer | `0`
`accessibility` | `partial` or `complete`. | string | *not accessible*
`bike` | Compute only bike-friendly journeys? | boolean | `false`
`startWithWalking` | Consider walking to nearby stations at the beginning of a journey? | boolean | `true`
`walkingSpeed` | `slow`, `normal` or `fast`. | string | `normal`
`tickets` | Return information about available tickets? | boolean | `false`
`polylines` | Fetch & parse a shape for each journey leg? | boolean | `false`
`remarks` | Parse & return hints & warnings? | boolean | `true`
`scheduledDays` | Parse & return dates each journey is valid on? | boolean | `false`
`language` | Language of the results. | string | `en`
`loyaltyCard` | Type of loyalty card in use. | string | *none*
`firstClass` | Search for first-class options? | boolean | `false`
`nationalExpress` | Include InterCityExpress (ICE)? | boolean | `true`
`national` | Include InterCity & EuroCity (IC/EC)? | boolean | `true`
`regionalExp` | Include RegionalExpress & InterRegio (RE/IR)? | boolean | `true`
`regional` | Include Regio (RB)? | boolean | `true`
`suburban` | Include S-Bahn (S)? | boolean | `true`
`bus` | Include Bus (B)? | boolean | `true`
`ferry` | Include Ferry (F)? | boolean | `true`
`subway` | Include U-Bahn (U)? | boolean | `true`
`tram` | Include Tram (T)? | boolean | `true`
`taxi` | Include Group Taxi (Taxi)? | boolean | `true`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Examples
```shell
# stop/station to POI
curl 'https://v5.db.transport.rest/journeys?from=8010159&to.id=991561765&to.latitude=51.483641&to.longitude=11.980841' -s | jq
# without buses, with ticket info
curl 'https://v5.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=true' -s | jq
```
## `GET /journeys/:ref`
Uses [`hafasClient.refreshJourney()`](https://github.com/public-transport/hafas-client/blob/5/docs/refresh-journey.md) to **"refresh" a journey, using its `refreshToken`**.
The journey will be the same (equal `from`, `to`, `via`, date/time & vehicles used), but you can get up-to-date realtime data, like delays & cancellations.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`stopovers` | Fetch & parse stopovers on the way? | boolean | `false`
`tickets` | Return information about available tickets? | boolean | `false`
`polylines` | Fetch & parse a shape for each journey leg? | boolean | `false`
`remarks` | Parse & return hints & warnings? | boolean | `true`
`language` | Language of the results. | string | `en`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
# get the refreshToken of a journey
journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]')
refresh_token=$(echo $journey | jq -r '.refreshToken')
# refresh the journey
curl "https://v5.db.transport.rest/journeys/$(echo $refresh_token | url-encode)" -s | jq
```
## `GET /trips/:id`
Uses [`hafasClient.trip()`](https://github.com/public-transport/hafas-client/blob/5/docs/trip.md) to **fetch a trip by ID**.
A trip is a specific vehicle, stopping at a series of stops at specific points in time. Departures, arrivals & journey legs reference trips by their ID.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`lineName` | **Required.** Line name of the part's mode of transport, e.g. `RE7`. | string |
`stopovers` | Fetch & parse stopovers on the way? | boolean | `true`
`remarks` | Parse & return hints & warnings? | boolean | `true`
`polyline` | Fetch & parse the geographic shape of the trip? | boolean | `false`
`language` | Language of the results. | string | `en`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
# get the trip ID of a journey leg
journey=$(curl 'https://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]')
journey_leg=$(echo $journey | jq -r '.legs[0]')
trip_id=$(echo $journey_leg | jq -r '.tripId')
# fetch the trip
curl "https://v5.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq
```
## `GET /stations`
If the `query` parameter is used, it will use [`db-stations-autocomplete@2`](https://github.com/derhuerst/db-stations-autocomplete/tree/2.2.0) to autocomplete *Deutsche Bahn*-operated stops/stations. Otherwise, it will filter the stops/stations in [`db-stations@3`](https://github.com/derhuerst/db-stations/tree/3.0.1).
Instead of receiving a JSON response, you can request [newline-delimited JSON](http://ndjson.org) by sending `Accept: application/x-ndjson`.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`query` | Find stations by name using [`db-stations-autocomplete@2`](https://github.com/derhuerst/db-stations-autocomplete/tree/2.2.0). | string |
`limit` | *If `query` is used:* Return at most `n` stations. | number | `3`
`fuzzy` | *If `query` is used:* Find stations despite typos. | boolean | `false`
`completion` | *If `query` is used:* Autocomplete stations. | boolean | `true`
### Examples
```shell
# autocomplete using db-stations-autocomplete
curl 'https://v5.db.transport.rest/stations?query=dammt' -s | jq
```
```js
{
"8002548": {
"id": "8002548",
"relevance": 0.8572361756428573,
"score": 9.175313823998414,
"weight": 1212,
"type": "station",
"ril100": "ADF",
"name": "Hamburg Dammtor",
"location": {
"type": "location",
"latitude": 53.560751,
"longitude": 9.989566
},
"operator": {
"type": "operator",
"id": "hamburger-verkehrsverbund-gmbh",
"name": "BWVI"
},
"address": {
"city": "Hamburg",
"zipcode": "20354",
"street": "Dag-Hammarskjöld-Platz 15"
},
// …
},
// …
}
```
```shell
# filter db-stations by `hasParking` property
curl 'https://v5.db.transport.rest/stations?hasParking=true' -s | jq
```
```js
{
"8000001": {
"type": "station",
"id": "8000001",
"ril100": "KA",
"name": "Aachen Hbf",
"weight": 653.75,
"location": { /* … */ },
"operator": { /* … */ },
"address": { /* … */ },
// …
},
// …
}
```
```shell
# filter db-stations by `hasDBLounge` property, get newline-delimited JSON
curl 'https://v5.db.transport.rest/stations?hasDBLounge=true' -H 'accept: application/x-ndjson' -s | jq
```
## `GET /stations/:id`
Returns a stop/station from [`db-stations`](https://npmjs.com/package/db-stations).
### Query Parameters
### Example
```shell
# lookup Halle (Saale) Hbf
curl 'https://v5.db.transport.rest/stations/8010159' -s | jq
curl 'https://v5.db.transport.rest/stations/LH' -s | jq # RIL100/DS100
curl 'https://v5.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100
```
```js
{
"type": "station",
"id": "8010159",
"additionalIds": ["8098159"],
"ril100": "LH",
"nr": 2498,
"name": "Halle (Saale) Hbf",
"weight": 815.6,
"location": { /* … */ },
"operator": { /* … */ },
"address": { /* … */ },
"ril100Identifiers": [
{
"rilIdentifier": "LH",
// …
},
// …
],
// …
}
```
## `GET /radar`
Uses [`hafasClient.radar()`](https://github.com/public-transport/hafas-client/blob/5/docs/radar.md) to **find all vehicles currently in an area**, as well as their movements.
### Query Parameters
parameter | description | type | default value
----------|-------------|------|--------------
`north` | **Required.** Northern latitude. | number |
`west` | **Required.** Western longitude. | number |
`south` | **Required.** Southern latitude. | number |
`east` | **Required.** Eastern longitude. | number |
`results` | Max. number of vehicles. | integer | `256`
`duration` | Compute frames for the next `n` seconds. | integer | `30`
`frames` | Number of frames to compute. | integer | `3`
`polylines` | Fetch & parse a geographic shape for the movement of each vehicle? | boolean | `true`
`language` | Language of the results. | string | `en`
`pretty` | Pretty-print JSON responses? | boolean | `true`
### Example
```shell
bbox='north=53.555&west=9.989&south=53.55&east=10.001'
curl "https://v5.db.transport.rest/radar?$bbox&results=10" -s | jq
```
## Date/Time Parameters
Possible formats:
- anything that [`parse-human-relative-time`](https://npmjs.com/package/parse-human-relative-time) can parse (e.g. `tomorrow 2pm`)
- [ISO 8601 date/time string](https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations) (e.g. `2020-04-26T22:43+02:00`)
- [UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) (e.g. `1587933780`)

View file

@ -1,4 +1,4 @@
# Getting Started with `v6.db.transport.rest` # Getting Started with `v5.db.transport.rest`
Let's walk through the **requests that are necessary to implement a typical basic transit app**. Let's walk through the **requests that are necessary to implement a typical basic transit app**.
@ -11,7 +11,7 @@ The following code snippets use [`curl`](https://curl.haxx.se) (a versatile comm
The `/locations?query=…` route allows you to query stops, points of interest (POIs) & addresses. We're only interested in stops though, so we filter using `poi=false&addresses=false`: The `/locations?query=…` route allows you to query stops, points of interest (POIs) & addresses. We're only interested in stops though, so we filter using `poi=false&addresses=false`:
```shell ```shell
curl 'https://v6.db.transport.rest/locations?poi=false&addresses=false&query=südkreuz' -s | jq curl 'https://v5.db.transport.rest/locations?poi=false&addresses=false&query=südkreuz' -s | jq
``` ```
```js ```js
@ -78,7 +78,7 @@ curl 'https://v6.db.transport.rest/locations?poi=false&addresses=false&query=sü
Let's fetch 5 of the next departures at *Berlin Südkreuz* (which has the ID `8011113`): Let's fetch 5 of the next departures at *Berlin Südkreuz* (which has the ID `8011113`):
```shell ```shell
curl 'https://v6.db.transport.rest/stops/8011113/departures?results=5' -s | jq curl 'https://v5.db.transport.rest/stops/8011113/departures?results=5' -s | jq
``` ```
```js ```js
@ -160,7 +160,7 @@ We call a connection from A to B at a specific date & time, made up of secti
Let's fetch 2 journeys from `8011113` (*Berlin Südkreuz*) to `8010159` (*Halle (Saale)Hbf*), departing tomorrow at 2pm (at the time of writing this). Let's fetch 2 journeys from `8011113` (*Berlin Südkreuz*) to `8010159` (*Halle (Saale)Hbf*), departing tomorrow at 2pm (at the time of writing this).
```shell ```shell
curl 'https://v6.db.transport.rest/journeys?from=8011113&to=8010159&departure=tomorrow+2pm&results=2' -s | jq curl 'https://v5.db.transport.rest/journeys?from=8011113&to=8010159&departure=tomorrow+2pm&results=2' -s | jq
``` ```
```js ```js
@ -316,4 +316,4 @@ Note that `departure` includes the `departureDelay`, and `arrival` includes the
### 4. more features ### 4. more features
These are the basics. Check the full [API docs](api.md) for all features or use the [OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv6.db.transport.rest%2F.well-known%2Fservice-desc%0A) or explore the API! These are the basics. Check the full [API docs](api.md) for all features or use the [OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv5.db.transport.rest%2F.well-known%2Fservice-desc%0A) or explore the API!

View file

@ -1,28 +1,24 @@
# `v6.db.transport.rest` documentation # `v5.db.transport.rest` documentation
[`v6.db.transport.rest`](https://v6.db.transport.rest/) is a [REST API](https://restfulapi.net) for the public transportation system in Germany. [`v5.db.transport.rest`](https://v5.db.transport.rest/) is a [REST API](https://restfulapi.net) for the public transportation system in Germany.
[![API status](https://badgen.net/uptime-robot/status/m793274556-25c5e9bbab0297d91cda7134)](https://stats.uptimerobot.com/57wNLs39M/793274556) [![API status](https://badgen.net/uptime-robot/status/m784879516-8a977fa91b975fc3884a9857)](https://stats.uptimerobot.com/57wNLs39M/784879516)
Because it wraps [an API](https://github.com/public-transport/hafas-client/blob/6/readme.md#background) of [Deutsche Bahn](https://de.wikipedia.org/wiki/Deutsche_Bahn), it **includes most of the long-distance and regional traffic, as well as some international trains and local buses**. Essentially, it returns whatever data the [*DB Navigator* app](https://www.bahn.de/p/view/service/mobile/db-navigator.shtml) shows*, **including realtime delays and disruptions**. Because it wraps [an API](https://github.com/public-transport/hafas-client/blob/master/readme.md#background) of [Deutsche Bahn](https://de.wikipedia.org/wiki/Deutsche_Bahn), it **includes most of the long-distance and regional traffic, as well as some international trains and local buses**. Essentially, it returns whatever data the [*DB Navigator* app](https://www.bahn.de/p/view/service/mobile/db-navigator.shtml) shows, **including realtime delays and disruptions**.
*When comparing results from this API to what the DB Navigator app shows there might be occasional differences due to them utilizing different, not 100% identical backends.*
- [Getting Started](getting-started.md) - [Getting Started](getting-started.md)
- [API documentation](api.md) (run `npm run build` to generate) - [API documentation](api.md)
- [OpenAPI playground with API documentation](https://petstore.swagger.io/?url=https%3A%2F%2Fv6.db.transport.rest%2F.well-known%2Fservice-desc%0A) - [OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv5.db.transport.rest%2F.well-known%2Fservice-desc%0A)
## Why use this API? ## Why use this API?
### Realtime Data ### Realtime Data
This API returns realtime data whenever it is upstream. The [API for DB's mobile app](https://github.com/public-transport/hafas-client/blob/6/p/db/readme.md) provides it. This API returns realtime data whenever its upstream, the [API for DB's mobile app](https://github.com/public-transport/hafas-client/blob/33d7d30acf235c54887c6459a15fe581982c6a19/p/db/readme.md), provides it.
*Note: Different endpoints might remove realtime data like delays and cancellations at different times, i.e. after a journey's arrival.*
### No API Key ### No API Key
You can just use the API without authentication. There's a [rate limit](https://apisyouwonthate.com/blog/what-is-api-rate-limiting-all-about) of 100 requests/minute set up. You can just use the API without authentication. There's a [rate limit](https://apisyouwonthate.com/blog/what-is-api-rate-limiting-all-about) of 100 requests/minute (burst 150 requests/minute) set up.
### CORS ### CORS

View file

@ -1,4 +1,6 @@
import {api, config} from './api.js' 'use strict'
const {api, config} = require('./api')
api.listen(config.port, (err) => { api.listen(config.port, (err) => {
const {logger} = api.locals const {logger} = api.locals

View file

@ -1,17 +1,15 @@
import {createRequire} from 'node:module' 'use strict'
const require = createRequire(import.meta.url)
import {statSync} from 'node:fs' const {statSync} = require('fs')
import {readFullStations} from 'db-stations' const {full: readRawStations} = require('db-stations')
// We don't have access to the publish date+time of the npm package, // We don't have access to the publish date+time of the npm package,
// so we use the ctime of db-stations/full.ndjson as an approximation. // so we use the ctime of db-stations/full.ndjson as an approximation.
// Also require() doesn't make much sense in ES Modules.
// todo: this is brittle, find a better way, e.g. a build script // todo: this is brittle, find a better way, e.g. a build script
const timeModified = statSync(require.resolve('db-stations/full.ndjson')).ctime const timeModified = statSync(require.resolve('db-stations/full.ndjson')).ctime
const pStations = new Promise((resolve, reject) => { const pStations = new Promise((resolve, reject) => {
let raw = readFullStations() let raw = readRawStations()
raw.once('error', reject) raw.once('error', reject)
let data = Object.create(null) let data = Object.create(null)
@ -39,6 +37,4 @@ pStations.catch((err) => {
process.exit(1) // todo: is this appropriate? process.exit(1) // todo: is this appropriate?
}) })
export { module.exports = pStations
pStations,
}

View file

@ -1,4 +1,4 @@
import {data as cards} from 'hafas-client/p/db/loyalty-cards.js' const {data: cards} = require('hafas-client/p/db/loyalty-cards')
const typesByName = new Map([ const typesByName = new Map([
// https://github.com/public-transport/hafas-client/blob/68ecd7c5e976dd2f51c5c64a81600e7e181a8996/p/db/loyalty-cards.js#L6-L11 // https://github.com/public-transport/hafas-client/blob/68ecd7c5e976dd2f51c5c64a81600e7e181a8996/p/db/loyalty-cards.js#L6-L11
@ -29,8 +29,8 @@ const loyaltyCardParser = {
parse: parseLoyaltyCard, parse: parseLoyaltyCard,
} }
export { module.exports = {
cards as loyaltyCards, loyaltyCards: cards,
parseLoyaltyCard, parseLoyaltyCard,
loyaltyCardParser, loyaltyCardParser,
} }

View file

@ -1,4 +1,4 @@
Copyright (c) 2024, Jannis R Copyright (c) 2022, Jannis R
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

6216
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,21 +2,10 @@
"private": true, "private": true,
"name": "db-rest", "name": "db-rest",
"description": "A clean REST API wrapping around the Deutsche Bahn API.", "description": "A clean REST API wrapping around the Deutsche Bahn API.",
"version": "6.0.5", "version": "5.0.2",
"type": "module",
"bin": {
"db-rest": "./index.js"
},
"main": "index.js", "main": "index.js",
"files": [
"docs",
"lib",
"routes",
"api.js",
"index.js"
],
"author": "Jannis R <mail@jannisr.de>", "author": "Jannis R <mail@jannisr.de>",
"homepage": "https://github.com/derhuerst/db-rest/tree/6", "homepage": "https://github.com/derhuerst/db-rest/tree/5",
"repository": "derhuerst/db-rest", "repository": "derhuerst/db-rest",
"bugs": "https://github.com/derhuerst/db-rest/issues", "bugs": "https://github.com/derhuerst/db-rest/issues",
"license": "ISC", "license": "ISC",
@ -30,36 +19,36 @@
"db" "db"
], ],
"engines": { "engines": {
"node": ">=18" "node": ">=10"
}, },
"dependencies": { "dependencies": {
"cached-hafas-client": "^5.0.1", "cached-hafas-client": "^4.0.4",
"cli-native": "^1.0.0", "cli-native": "^1.0.0",
"db-hafas": "^6.0.0", "db-hafas": "^5.0.2",
"db-stations": "^5.0.0", "db-stations": "^3.0.0",
"db-stations-autocomplete": "^4.0.0", "db-stations-autocomplete": "^2.2.0",
"etag": "^1.8.1", "etag": "^1.8.1",
"hafas-client-health-check": "^2.1.1", "hafas-client-health-check": "^2.1.1",
"hafas-rest-api": "^5.1.0", "hafas-rest-api": "^3.8.0",
"ioredis": "^5.0.6", "ioredis": "^5.0.6",
"serve-buffer": "^3.0.3", "serve-buffer": "^2.0.0",
"serve-static": "^1.14.1" "serve-static": "^1.14.1"
}, },
"devDependencies": { "devDependencies": {
"@derhuerst/technical-docs-cli": "^1.5.0", "@derhuerst/technical-docs-cli": "^1.1.0",
"axios": "^1.6.1", "axios": "^0.26.1",
"eslint": "^8.12.0", "eslint": "^8.12.0",
"get-port": "^6.1.2", "get-port": "^5.1.1",
"ndjson": "^2.0.0", "ndjson": "^2.0.0",
"pino-pretty": "^9.1.1", "pino-pretty": "^4.0.0",
"tap-min": "^3.0.0", "tap-min": "^2.0.0",
"tape": "^5.5.2" "tape": "^5.5.2"
}, },
"scripts": { "scripts": {
"build": "REDIS_URL='' ./build/index.js", "docs": "node api-docs.js >docs/api.md && build-technical-doc --syntax-stylesheet-url /syntax.css <docs/readme.md >docs/index.html && build-technical-doc --syntax-stylesheet-url /syntax.css <docs/getting-started.md >docs/getting-started.html && build-technical-doc --syntax-stylesheet-url /syntax.css <docs/api.md >docs/api.html && build-technical-doc --syntax-stylesheet github >docs/syntax.css",
"build": "npm run docs",
"start": "node index.js", "start": "node index.js",
"lint": "eslint .", "lint": "eslint .",
"test": "node test/index.js | tap-min" "test": "node test/index.js | tap-min"
}, }
"goatCounterUrl": "https://37462fdee48390778258.goatcounter.com/count"
} }

View file

@ -1,12 +1,12 @@
# db-rest # db-rest
**A clean REST API wrapping around the [Deutsche Bahn HAFAS API](https://github.com/public-transport/db-hafas#db-hafas).** It is deployed at [`v6.db.transport.rest`](https://v6.db.transport.rest/). **A clean REST API wrapping around the [Deutsche Bahn HAFAS API](https://github.com/public-transport/db-hafas#db-hafas).** It is deployed at [`v5.db.transport.rest`](https://v5.db.transport.rest/).
[**API Documentation**](docs/readme.md) [**API Documentation**](docs/readme.md)
![db-rest architecture diagram](architecture.svg) ![db-rest architecture diagram](architecture.svg)
[![API status](https://badgen.net/uptime-robot/status/m793274556-25c5e9bbab0297d91cda7134)](https://stats.uptimerobot.com/57wNLs39M/793274556) [![API status](https://badgen.net/uptime-robot/status/m784879516-8a977fa91b975fc3884a9857)](https://stats.uptimerobot.com/57wNLs39M/784879516)
[![dependency status](https://img.shields.io/david/derhuerst/db-rest.svg)](https://david-dm.org/derhuerst/db-rest) [![dependency status](https://img.shields.io/david/derhuerst/db-rest.svg)](https://david-dm.org/derhuerst/db-rest)
![ISC-licensed](https://img.shields.io/github/license/derhuerst/db-rest.svg) ![ISC-licensed](https://img.shields.io/github/license/derhuerst/db-rest.svg)
[![support me via GitHub Sponsors](https://img.shields.io/badge/support%20me-donate-fa7664.svg)](https://github.com/sponsors/derhuerst) [![support me via GitHub Sponsors](https://img.shields.io/badge/support%20me-donate-fa7664.svg)](https://github.com/sponsors/derhuerst)
@ -15,16 +15,14 @@
## installing & running ## installing & running
### access to Redis `db-rest` expects a [Redis](https://redis.io/) server running on `127.0.0.1:6379` (default port), but you can set the `REDIS_URL` environment variable to change this.
It is recommended that you let `bvg-rest` cache HAFAS responses within a [Redis](https://redis.io/) cache. To use this feature, set `$REDIS_URL` (e.g. to `redis://localhost:6379/1` when running Redis locally).
### via Docker ### via Docker
A Docker image [is available as `docker.io/derhuerst/db-rest:6`](https://hub.docker.com/r/docker.io/derhuerst/db-rest:6). A Docker image [is available as `derhuerst/db-rest:5`](https://hub.docker.com/r/derhuerst/db-rest:5).
```shell ```shell
docker run -d -p 3000:3000 docker.io/derhuerst/db-rest:6 docker run -d -p 3000:3000 derhuerst/db-rest:5
``` ```
*Note:* The Docker image does not contain the Redis server. *Note:* The Docker image does not contain the Redis server.
@ -34,11 +32,8 @@ docker run -d -p 3000:3000 docker.io/derhuerst/db-rest:6
```shell ```shell
git clone https://github.com/derhuerst/db-rest.git git clone https://github.com/derhuerst/db-rest.git
cd db-rest cd db-rest
git checkout 6 git checkout 5
npm install npm install --production
export HOSTNAME='my-vbb-rest-api.example.org'
npm run build
redis-server & redis-server &
npm start npm start
@ -49,7 +44,6 @@ To keep the API running permanently, use tools like [`forever`](https://github.c
## Related Projects ## Related Projects
- [`DB-Adapter-v6`](https://github.com/olech2412/DB-Adapter-v6) A Java API client for `db-rest`.
- [`vbb-rest`](https://github.com/derhuerst/vbb-rest)  A clean REST API wrapping around the VBB API. - [`vbb-rest`](https://github.com/derhuerst/vbb-rest)  A clean REST API wrapping around the VBB API.
- [`bvg-rest`](https://github.com/derhuerst/bvg-rest)  A clean REST API wrapping around the BVG API. - [`bvg-rest`](https://github.com/derhuerst/bvg-rest)  A clean REST API wrapping around the BVG API.
- [`hvv-rest`](https://github.com/derhuerst/hvv-rest)  A clean REST API wrapping around the HVV API. - [`hvv-rest`](https://github.com/derhuerst/hvv-rest)  A clean REST API wrapping around the HVV API.

View file

@ -1,4 +1,6 @@
import {pStations} from '../lib/db-stations.js' 'use strict'
const pStations = require('../lib/db-stations')
const err404 = (msg) => { const err404 = (msg) => {
const err = new Error(msg) const err = new Error(msg)
@ -50,12 +52,9 @@ Returns a stop/station from [\`db-stations@3\`](https://github.com/derhuerst/db-
}, },
}, },
}, },
// todo: non-2xx response
}, },
}, },
}, },
} }
export { module.exports = stationRoute
stationRoute as route,
}

View file

@ -1,10 +1,11 @@
import computeEtag from 'etag' 'use strict'
import serveBuffer from 'serve-buffer'
import {autocomplete} from 'db-stations-autocomplete' const computeEtag = require('etag')
import _cliNative from 'cli-native' const serveBuffer = require('serve-buffer')
const {to: parse} = _cliNative const autocomplete = require('db-stations-autocomplete')
import {createFilter} from 'db-stations/create-filter.js' const parse = require('cli-native').to
import {pStations} from '../lib/db-stations.js' const createFilter = require('db-stations/create-filter')
let pAllStations = require('../lib/db-stations')
const JSON_MIME = 'application/json' const JSON_MIME = 'application/json'
const NDJSON_MIME = 'application/x-ndjson' const NDJSON_MIME = 'application/x-ndjson'
@ -23,7 +24,7 @@ const toNdjsonBuf = (data) => {
return Buffer.concat(chunks, bytes) return Buffer.concat(chunks, bytes)
} }
const pAllStations = pStations.then(({data, timeModified}) => { pAllStations = pAllStations.then(({data, timeModified}) => {
const asJson = Buffer.from(JSON.stringify(data), 'utf8') const asJson = Buffer.from(JSON.stringify(data), 'utf8')
const asNdjson = toNdjsonBuf(data) const asNdjson = toNdjsonBuf(data)
return { return {
@ -165,14 +166,8 @@ Instead of receiving a JSON response, you can request [newline-delimited JSON](h
}, },
// todo: example(s) // todo: example(s)
}, },
'application/x-ndjson': {
schema: {
type: 'string',
},
},
}, },
}, },
// todo: non-2xx response
}, },
}, },
}, },
@ -201,6 +196,4 @@ stationsRoute.queryParameters = {
}, },
} }
export { module.exports = stationsRoute
stationsRoute as route,
}

View file

@ -1,9 +1,10 @@
import tape from 'tape' 'use strict'
import _ndjson from 'ndjson'
const {parse: ndjsonParser} = _ndjson const tape = require('tape')
import {loyaltyCards} from '../lib/loyalty-cards.js' const {parse: ndjsonParser} = require('ndjson')
import {fetchWithTestApi} from './util.js' const {loyaltyCards} = require('../lib/loyalty-cards')
import {pStations as pAllStations} from '../lib/db-stations.js' const {fetchWithTestApi} = require('./util')
const pAllStations = require('../lib/db-stations')
const NO_JOURNEYS = { const NO_JOURNEYS = {
// todo? // todo?
@ -75,11 +76,11 @@ tape.test('/stations works', async (t) => {
} }
}) })
tape.test('/stations?query=frankfurt%20ha works', async (t) => { tape.test('/stations?query=frankf works', async (t) => {
const FRANKFURT_MAIN_HBF = '8000105' const FRANKFURT_MAIN_HBF = '8000105'
{ {
const {headers, data} = await fetchWithTestApi({}, {}, '/stations?query=frankfurt%20ha', { const {headers, data} = await fetchWithTestApi({}, {}, '/stations?query=frankf', {
headers: { headers: {
'accept': 'application/json', 'accept': 'application/json',
}, },
@ -92,7 +93,7 @@ tape.test('/stations?query=frankfurt%20ha works', async (t) => {
} }
{ {
const {headers, data} = await fetchWithTestApi({}, {}, '/stations?query=frankfurt%20ha', { const {headers, data} = await fetchWithTestApi({}, {}, '/stations?query=frankf', {
headers: { headers: {
'accept': 'application/x-ndjson', 'accept': 'application/x-ndjson',
}, },
@ -105,6 +106,6 @@ tape.test('/stations?query=frankfurt%20ha works', async (t) => {
for await (const station of parser) stations.push(station) for await (const station of parser) stations.push(station)
t.ok(stations.find(s => s.id === FRANKFURT_MAIN_HBF)) t.ok(stations.find(s => s.id === FRANKFURT_MAIN_HBF))
t.ok(stations.length > 0) t.ok(Object.keys(stations).length > 0)
} }
}) })

View file

@ -1,9 +1,12 @@
import {createHafasRestApi} from 'hafas-rest-api' 'use strict'
import getPort from 'get-port'
import {createServer} from 'node:http' const createApi = require('hafas-rest-api')
import {promisify} from 'node:util' const getPort = require('get-port')
import axios from 'axios' const {createServer} = require('http')
import {config, hafas as unmockedHafas} from '../api.js' const {promisify} = require('util')
const axios = require('axios')
const pkg = require('../package.json')
const {config, hafas: unmockedHafas} = require('../api')
// adapted from https://github.com/public-transport/hafas-rest-api/blob/60335eacd8332d7f448da875a7498dd97934e360/test/util.js#L40-L77 // adapted from https://github.com/public-transport/hafas-rest-api/blob/60335eacd8332d7f448da875a7498dd97934e360/test/util.js#L40-L77
const createTestApi = async (mocks, cfg) => { const createTestApi = async (mocks, cfg) => {
@ -21,7 +24,7 @@ const createTestApi = async (mocks, cfg) => {
...cfg, ...cfg,
} }
const api = await createHafasRestApi(mockedHafas, cfg, () => {}) const api = createApi(mockedHafas, cfg, () => {})
const server = createServer(api) const server = createServer(api)
const port = await getPort() const port = await getPort()
@ -47,7 +50,7 @@ const fetchWithTestApi = async (mocks, cfg, path, opt = {}) => {
return res return res
} }
export { module.exports = {
createTestApi, createTestApi,
fetchWithTestApi, fetchWithTestApi,
} }