Compare commits

...

52 commits
5.0.2 ... 6

Author SHA1 Message Date
renovate[bot]
f0901ed56c
chore(deps): lock file maintenance
Some checks failed
lint, build & test / lint, build & test (push) Has been cancelled
2024-09-23 02:05:06 +00:00
renovate[bot]
cf63dff36b
chore(deps): lock file maintenance 2024-09-16 01:38:20 +00:00
renovate[bot]
be8aee3fbf
chore(deps): lock file maintenance 2024-09-09 01:07:28 +00:00
renovate[bot]
58c18827ca
chore(deps): lock file maintenance 2024-09-02 00:14:38 +00:00
renovate[bot]
5fffdb88d2
chore(deps): lock file maintenance 2024-08-26 01:25:54 +00:00
renovate[bot]
4517d41e2b chore(deps): update dependency axios to v1.7.4 [security] 2024-08-14 01:34:29 +02:00
renovate[bot]
0d92cb7ed0 chore(deps): update actions/checkout action to v4 2024-08-08 16:11:32 +02:00
renovate[bot]
3e5dc8e733 chore(deps): update docker/login-action action to v3 2024-08-08 16:09:57 +02:00
renovate[bot]
4be921d5dd chore(deps): update docker/setup-buildx-action action to v3 2024-08-08 16:08:57 +02:00
renovate[bot]
399713695e chore(deps): lock file maintenance 2024-08-08 14:02:07 +00:00
Jannis R
66ca7d32e5
CI: lint & test on PRs too, with Node 18-22 💚 2024-08-08 16:01:45 +02:00
Jannis R
76358235ab
readme: use "docker.io" in image names; 6.0.5 2024-05-29 17:23:47 +02:00
Jannis R
6a94c19dc8
minor-upgrade deps & dev deps 2024-05-29 17:22:42 +02:00
dependabot[bot]
291006c81e Bump express from 4.18.2 to 4.19.2
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-06 14:31:57 +02:00
Johannes
3941e5f4ea
readme: explain differences in realtime data between DB app and db-rest 📝
Co-Authored-By: Jannis R <mail@jannisr.de>
2024-03-19 13:41:27 +01:00
dependabot[bot]
e8b040e601 Bump follow-redirects from 1.15.5 to 1.15.6
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.5 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-17 16:06:48 +01:00
Jannis R
d53ff22d53
readme: mention $REDIS_URL 📝 2024-01-26 17:38:20 +01:00
Jannis R
a1050c9d90
minor-upgrade deps & dev deps; 6.0.4 2024-01-25 14:51:00 +01:00
Jannis R
40ae288388
minor tweaks 📝 2024-01-25 14:49:59 +01:00
Jannis R
9793b471f2
CI: publish with "floating" Docker image tag too 💚 2024-01-25 14:49:41 +01:00
Jannis R
8de11ee2cd
API docs: minor fix 📝 2024-01-25 14:48:16 +01:00
Jannis R
a294db5cd4
add files and bin to package.json
Co-Authored-By: Marie Ramlow <me@nycode.dev>
2023-11-24 16:19:55 +01:00
Jannis R
1eff75e491
axios@1
closes #36
2023-11-11 14:13:43 +01:00
Jannis R
f0c0cc698b
renovate bot config: temporarily remove schedule 💚
https://github.com/derhuerst/db-rest/pull/34#issuecomment-1801918396
2023-11-08 17:07:12 +01:00
Jannis R
c087d49f16
6.0.3 2023-11-07 19:08:51 +01:00
Jannis R
8482862f76
/journeys docs: fix POI example 📝
fixes https://github.com/public-transport/hafas-client/issues/299
2023-11-07 18:56:32 +01:00
Jannis R
3fe3d4ac7b
tap-min@3 2023-11-07 18:54:43 +01:00
Jannis R
5b4a97a93c
minor-upgrade deps & dev deps 2023-11-07 18:53:49 +01:00
Jannis R
d1be6cb7af
package-lock.json: update db-rest version field 2023-11-07 18:52:32 +01:00
Jannis R
2339f1752a
CI: build & publish arm64 binaries 💚
v5 equivalent: 76c5885
2023-09-19 11:01:14 +02:00
Jannis R
11aa62d829
6.0.2 2023-09-06 16:48:26 +02:00
Jannis R
c34f218373
check in package-lock.json, adapt Dockerfile & CI config 💚 2023-09-06 14:41:51 +02:00
Marie
e038ce37de
configure renovate bot
#34 squashed
2023-09-06 14:38:00 +02:00
Jannis R
30a56869ce
docs: fix status page link/badge, other tweaks 📝; 6.0.1
db-rest equivalent of fb08b213ed
2023-05-08 14:58:13 +02:00
Jannis R
303544277b
CI: upgrade actions 💚
db-rest equivalent of 9a67cc00ad
2023-04-20 00:34:11 +02:00
Jannis R
a5688cabe6
CI: cache Docker layers 💚
db-rest equivalent of c8a06f0c6d
2023-04-20 00:33:19 +02:00
Jannis R
63964f91f3
docs: embed GoatCounter visits counter
db-rest equivalent of 5b1ade80ed
2023-04-20 00:17:51 +02:00
Jannis R
9879365e9b
use JS build script
db-rest equivalent of 605085229e
2023-04-20 00:14:23 +02:00
Jannis R
ba256397d0
CI: fix Docker build & push 💚 2023-03-26 19:31:18 +02:00
Jannis R
1047946a23
CI: minor tweaks 💚 2023-03-26 00:00:01 +01:00
Jannis R
5658c84c54
CI: build before testing 💚 2023-03-26 00:00:01 +01:00
jannes.t13@gmail.com
448294ba5c add travellers age parameter
Co-Authored-By: Jannis R <mail@jannisr.de>
2023-03-25 23:59:24 +01:00
Jannis R
779e67886b
adapt tests to latest data 2022-12-28 14:22:28 +01:00
Jannis R
cea4439e11
fix readme 📝; 6.0.0 2022-12-28 14:22:28 +01:00
Jannis R
44b7d6f3ce
serve-buffer@3, axios@1.1, get-port@6, pino-pretty@9 2022-12-28 14:22:28 +01:00
Jannis R
2ef8bbe6df
db-stations@5, db-stations-autocomplete@4 2022-12-28 14:22:28 +01:00
Jannis R
04543f0146
db-hafas@6, hafas-rest-api@5, cached-hafas-client@5 💥📝
closes #22
2022-12-28 14:22:22 +01:00
Jannis R
82a8a97e0f
gitignore /docs/api.md 2022-12-21 14:48:06 +01:00
Jannis R
6f2e5f134b
convert to ESM 💥 2022-12-21 14:47:58 +01:00
Jannis R
fe469cd658
Node 18+ 💥💚 2022-12-21 14:47:58 +01:00
Jannis R
4b333ea9be
prepare for v6 📝💚 2022-12-21 14:47:58 +01:00
Jannis R
9e6cc8ed6b
/stations: document application/x-ndjson representation; put todos 2022-12-19 13:07:03 +01:00
24 changed files with 6598 additions and 823 deletions

View file

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

13
.github/renovate.json vendored Normal file
View file

@ -0,0 +1,13 @@
{
"$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,21 +2,11 @@ name: build & publish Docker image
on: on:
push: push:
branches: branches:
- 5 - 6
jobs: jobs:
lint-test: lint-test:
name: lint & test name: lint, build & test
runs-on: ubuntu-latest uses: './.github/workflows/lint-test.yml'
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
@ -24,23 +14,32 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: check out the repo - name: check out the repo
uses: actions/checkout@v2 uses: actions/checkout@v4
- 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@v1 uses: docker/login-action@v3
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@v2 uses: docker/build-push-action@v4
with: with:
context: .
push: true push: true
platforms: linux/amd64,linux/arm64
tags: | tags: |
derhuerst/db-rest:5 derhuerst/db-rest:6
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@v1 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -52,7 +51,14 @@ 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@v2 uses: docker/build-push-action@v4
with: with:
context: .
push: true push: true
tags: ghcr.io/${{github.repository}}:v5_${{steps.hash.outputs.hash}}_${{steps.datetime.outputs.datetime}} platforms: linux/amd64,linux/arm64
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

35
.github/workflows/lint-test.yml vendored Normal file
View file

@ -0,0 +1,35 @@
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:alpine as builder FROM node:18-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 /app ADD package.json package-lock.json /app
RUN npm install RUN npm ci
# build documentation # build documentation
ADD . /app ADD . /app
@ -12,13 +12,13 @@ RUN npm run build
# --- # ---
FROM node:alpine FROM node:18-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/5" LABEL org.opencontainers.image.documentation="https://github.com/derhuerst/db-rest/tree/6"
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="5" LABEL org.opencontainers.image.revision="6"
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 v5.db.transport.rest ENV HOSTNAME v6.db.transport.rest
ENV PORT 3000 ENV PORT 3000
CMD ["node", "index.js"] CMD ["node", "index.js"]

47
api.js
View file

@ -1,29 +1,36 @@
'use strict' // todo: use import assertions once they're supported by Node.js & ESLint
// 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 = withCache(hafas, redisStore(redis), { hafas = createCachedHafasClient(hafas, createRedisStore(redis), {
cachePeriods: { cachePeriods: {
locations: 6 * 60 * 60 * 1000, // 6h locations: 6 * 60 * 60 * 1000, // 6h
}, },
@ -54,6 +61,12 @@ const mapRouteParsers = (route, parsers) => {
default: 'false', default: 'false',
parse: parseBoolean, parse: parseBoolean,
}, },
age: {
description: 'Age of traveller',
type: 'integer',
defaultStr: '*adult*',
parse: parseInteger
},
} }
} }
@ -70,7 +83,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/5/docs/readme.md', docsLink: 'https://github.com/derhuerst/db-rest/blob/6/docs/readme.md',
openapiSpec: true, openapiSpec: true,
logging: true, logging: true,
aboutPage: false, aboutPage: false,
@ -81,13 +94,13 @@ const config = {
modifyRoutes, modifyRoutes,
} }
const api = createApi(hafas, config, (api) => { const api = await createHafasRestApi(hafas, config, (api) => {
api.use('/', serveStatic(docsRoot, { api.use('/', serveStatic(docsRoot, {
extensions: ['html', 'htm'], extensions: ['html', 'htm'],
})) }))
}) })
module.exports = { export {
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">v5.db.transport.rest</text> <text class="monospace-text small-text" x="4" y="36">v6.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,16 +1,14 @@
'use strict' import {generateApiDocs} from 'hafas-rest-api/tools/generate-docs.js'
import {api} from '../api.js'
const generateApiDocs = require('hafas-rest-api/tools/generate-docs')
const {api} = require('./api')
const HEAD = `\ const HEAD = `\
# \`v5.db.transport.rest\` API documentation # \`v6.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/). [\`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/).
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%2Fv5.db.transport.rest%2F.well-known%2Fservice-desc%0A) [OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv6.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).
` `
@ -32,22 +30,22 @@ const order = [
const descriptions = { const descriptions = {
'/locations': `\ '/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\`**. Uses [\`hafasClient.locations()\`](https://github.com/public-transport/hafas-client/blob/6/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/5/docs/nearby.md) to **find stops/stations close to the given geolocation**. Uses [\`hafasClient.nearby()\`](https://github.com/public-transport/hafas-client/blob/6/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/5/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/6/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/5/docs/stop.md) to **find a stop/station by ID**. Uses [\`hafasClient.stop()\`](https://github.com/public-transport/hafas-client/blob/6/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/5/docs/departures.md) to **get departures at a stop/station**. Uses [\`hafasClient.departures()\`](https://github.com/public-transport/hafas-client/blob/6/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/5/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/6/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).
@ -58,32 +56,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/5/docs/journeys.md) to **find journeys from A (\`from\`) to B (\`to\`)**. Uses [\`hafasClient.journeys()\`](https://github.com/public-transport/hafas-client/blob/6/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\` for *Halle+(Saale),+Stadtpark+Halle+(Grünanlagen)*) - 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 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/5/docs/journeys.md) for more details. Check the [\`hafasClient.journeys()\` docs](https://github.com/public-transport/hafas-client/blob/6/docs/journeys.md) for more details.
`, `,
'/journeys/:ref': `\ '/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\`**. Uses [\`hafasClient.refreshJourney()\`](https://github.com/public-transport/hafas-client/blob/6/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/5/docs/trip.md) to **fetch a trip by ID**. Uses [\`hafasClient.trip()\`](https://github.com/public-transport/hafas-client/blob/6/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/5/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/6/docs/radar.md) to **find all vehicles currently in an area**, as well as their movements.
`, `,
} }
@ -92,7 +90,7 @@ const examples = {
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v5.db.transport.rest/locations?query=halle&results=1' -s | jq curl 'https://v6.db.transport.rest/locations?query=halle&results=1' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -120,7 +118,7 @@ curl 'https://v5.db.transport.rest/locations?query=halle&results=1' -s | jq
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v5.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.0015' -s | jq curl 'https://v6.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.0015' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -160,7 +158,7 @@ curl 'https://v5.db.transport.rest/stops/nearby?latitude=53.5711&longitude=10.00
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v5.db.transport.rest/stops/reachable-from?latitude=53.553766&longitude=9.977514&address=Hamburg,+Holstenwall+9' -s | jq curl 'https://v6.db.transport.rest/stops/reachable-from?latitude=53.553766&longitude=9.977514&address=Hamburg,+Holstenwall+9' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -200,7 +198,7 @@ curl 'https://v5.db.transport.rest/stops/reachable-from?latitude=53.553766&longi
### Example ### Example
\`\`\`shell \`\`\`shell
curl 'https://v5.db.transport.rest/stops/8010159' -s | jq curl 'https://v6.db.transport.rest/stops/8010159' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -229,7 +227,7 @@ curl 'https://v5.db.transport.rest/stops/8010159' -s | jq
\`\`\`shell \`\`\`shell
# autocomplete using db-stations-autocomplete # autocomplete using db-stations-autocomplete
curl 'https://v5.db.transport.rest/stations?query=dammt' -s | jq curl 'https://v6.db.transport.rest/stations?query=dammt' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -265,7 +263,7 @@ curl 'https://v5.db.transport.rest/stations?query=dammt' -s | jq
\`\`\`shell \`\`\`shell
# filter db-stations by \`hasParking\` property # filter db-stations by \`hasParking\` property
curl 'https://v5.db.transport.rest/stations?hasParking=true' -s | jq curl 'https://v6.db.transport.rest/stations?hasParking=true' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -287,7 +285,7 @@ curl 'https://v5.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://v5.db.transport.rest/stations?hasDBLounge=true' -H 'accept: application/x-ndjson' -s | jq curl 'https://v6.db.transport.rest/stations?hasDBLounge=true' -H 'accept: application/x-ndjson' -s | jq
\`\`\` \`\`\`
`, `,
'/stations/:id': `\ '/stations/:id': `\
@ -295,9 +293,9 @@ curl 'https://v5.db.transport.rest/stations?hasDBLounge=true' -H 'accept: applic
\`\`\`shell \`\`\`shell
# lookup Halle (Saale) Hbf # lookup Halle (Saale) Hbf
curl 'https://v5.db.transport.rest/stations/8010159' -s | jq curl 'https://v6.db.transport.rest/stations/8010159' -s | jq
curl 'https://v5.db.transport.rest/stations/LH' -s | jq # RIL100/DS100 curl 'https://v6.db.transport.rest/stations/LH' -s | jq # RIL100/DS100
curl 'https://v5.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100 curl 'https://v6.db.transport.rest/stations/LHG' -s | jq # RIL100/DS100
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -328,7 +326,7 @@ curl 'https://v5.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://v5.db.transport.rest/stops/8010159/departures?direction=8011113&duration=120' -s | jq curl 'https://v6.db.transport.rest/stops/8010159/departures?direction=8011113&duration=120' -s | jq
\`\`\` \`\`\`
\`\`\`js \`\`\`js
@ -370,7 +368,7 @@ curl 'https://v5.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://v5.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq curl 'https://v6.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq
\`\`\` \`\`\`
`, `,
'/journeys': `\ '/journeys': `\
@ -378,9 +376,9 @@ curl 'https://v5.db.transport.rest/stops/8010159/arrivals?duration=10' -s | jq
\`\`\`shell \`\`\`shell
# stop/station to POI # 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 curl 'https://v6.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://v5.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=true' -s | jq curl 'https://v6.db.transport.rest/journeys?from=…&to=…&bus=false&tickets=true' -s | jq
\`\`\` \`\`\`
`, `,
'/journeys/:ref': `\ '/journeys/:ref': `\
@ -388,11 +386,11 @@ curl 'https://v5.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://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') journey=$(curl 'https://v6.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://v5.db.transport.rest/journeys/$(echo $refresh_token | url-encode)" -s | jq curl "https://v6.db.transport.rest/journeys/$(echo $refresh_token | url-encode)" -s | jq
\`\`\` \`\`\`
`, `,
'/trips/:id': `\ '/trips/:id': `\
@ -400,12 +398,12 @@ curl "https://v5.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://v5.db.transport.rest/journeys?from=…&to=…&results=1' -s | jq '.journeys[0]') journey=$(curl 'https://v6.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://v5.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq curl "https://v6.db.transport.rest/trips/$(echo $trip_id | url-encode)" -s | jq
\`\`\` \`\`\`
`, `,
'/radar': `\ '/radar': `\
@ -413,45 +411,47 @@ curl "https://v5.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://v5.db.transport.rest/radar?$bbox&results=10" -s | jq curl "https://v6.db.transport.rest/radar?$bbox&results=10" -s | jq
\`\`\` \`\`\`
`, `,
} }
const { const generateMarkdownApiDocs = async function* () {
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
}) })
const write = process.stdout.write.bind(process.stdout) yield HEAD
yield `\n\n`
write(HEAD) yield listOfRoutes
write(`\n\n`) yield `\n\n`
write(listOfRoutes) for (const [route, params] of sortedRoutes) {
write(`\n\n`) yield `## \`GET ${route}\`\n\n`
yield descriptions[route] || ''
for (const [route, params] of sortedRoutes) { yield `\n### Query Parameters\n`
write(`## \`GET ${route}\`\n\n`) yield params
write(descriptions[route] || '')
write(`
### Query Parameters
`)
write(params)
if (examples[route]) { if (examples[route]) {
write('\n' + examples[route]) yield '\n' + examples[route]
} }
write(`\n\n`) yield `\n\n`
}
yield tail
}
export {
generateMarkdownApiDocs,
} }
write(tail)

86
build/index.js Executable file
View file

@ -0,0 +1,86 @@
#!/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)
}

View file

@ -1,620 +0,0 @@
# `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 `v5.db.transport.rest` # Getting Started with `v6.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://v5.db.transport.rest/locations?poi=false&addresses=false&query=südkreuz' -s | jq curl 'https://v6.db.transport.rest/locations?poi=false&addresses=false&query=südkreuz' -s | jq
``` ```
```js ```js
@ -78,7 +78,7 @@ curl 'https://v5.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://v5.db.transport.rest/stops/8011113/departures?results=5' -s | jq curl 'https://v6.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://v5.db.transport.rest/journeys?from=8011113&to=8010159&departure=tomorrow+2pm&results=2' -s | jq curl 'https://v6.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%2Fv5.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%2Fv6.db.transport.rest%2F.well-known%2Fservice-desc%0A) or explore the API!

View file

@ -1,24 +1,28 @@
# `v5.db.transport.rest` documentation # `v6.db.transport.rest` documentation
[`v5.db.transport.rest`](https://v5.db.transport.rest/) is a [REST API](https://restfulapi.net) for the public transportation system in Germany. [`v6.db.transport.rest`](https://v6.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/m784879516-8a977fa91b975fc3884a9857)](https://stats.uptimerobot.com/57wNLs39M/784879516) [![API status](https://badgen.net/uptime-robot/status/m793274556-25c5e9bbab0297d91cda7134)](https://stats.uptimerobot.com/57wNLs39M/793274556)
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**. 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**.
*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) - [API documentation](api.md) (run `npm run build` to generate)
- [OpenAPI playground](https://petstore.swagger.io/?url=https%3A%2F%2Fv5.db.transport.rest%2F.well-known%2Fservice-desc%0A) - [OpenAPI playground with API documentation](https://petstore.swagger.io/?url=https%3A%2F%2Fv6.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 its upstream, the [API for DB's mobile app](https://github.com/public-transport/hafas-client/blob/33d7d30acf235c54887c6459a15fe581982c6a19/p/db/readme.md), provides it. 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.
*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 (burst 150 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 set up.
### CORS ### CORS

View file

@ -1,6 +1,4 @@
'use strict' import {api, config} from './api.js'
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,15 +1,17 @@
'use strict' import {createRequire} from 'node:module'
const require = createRequire(import.meta.url)
const {statSync} = require('fs') import {statSync} from 'node:fs'
const {full: readRawStations} = require('db-stations') import {readFullStations} from '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 = readRawStations() let raw = readFullStations()
raw.once('error', reject) raw.once('error', reject)
let data = Object.create(null) let data = Object.create(null)
@ -37,4 +39,6 @@ pStations.catch((err) => {
process.exit(1) // todo: is this appropriate? process.exit(1) // todo: is this appropriate?
}) })
module.exports = pStations export {
pStations,
}

View file

@ -1,4 +1,4 @@
const {data: cards} = require('hafas-client/p/db/loyalty-cards') import {data as cards} from 'hafas-client/p/db/loyalty-cards.js'
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,
} }
module.exports = { export {
loyaltyCards: cards, cards as loyaltyCards,
parseLoyaltyCard, parseLoyaltyCard,
loyaltyCardParser, loyaltyCardParser,
} }

View file

@ -1,4 +1,4 @@
Copyright (c) 2022, Jannis R Copyright (c) 2024, 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 Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,21 @@
"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": "5.0.2", "version": "6.0.5",
"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/5", "homepage": "https://github.com/derhuerst/db-rest/tree/6",
"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",
@ -19,36 +30,36 @@
"db" "db"
], ],
"engines": { "engines": {
"node": ">=10" "node": ">=18"
}, },
"dependencies": { "dependencies": {
"cached-hafas-client": "^4.0.4", "cached-hafas-client": "^5.0.1",
"cli-native": "^1.0.0", "cli-native": "^1.0.0",
"db-hafas": "^5.0.2", "db-hafas": "^6.0.0",
"db-stations": "^3.0.0", "db-stations": "^5.0.0",
"db-stations-autocomplete": "^2.2.0", "db-stations-autocomplete": "^4.0.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": "^3.8.0", "hafas-rest-api": "^5.1.0",
"ioredis": "^5.0.6", "ioredis": "^5.0.6",
"serve-buffer": "^2.0.0", "serve-buffer": "^3.0.3",
"serve-static": "^1.14.1" "serve-static": "^1.14.1"
}, },
"devDependencies": { "devDependencies": {
"@derhuerst/technical-docs-cli": "^1.1.0", "@derhuerst/technical-docs-cli": "^1.5.0",
"axios": "^0.26.1", "axios": "^1.6.1",
"eslint": "^8.12.0", "eslint": "^8.12.0",
"get-port": "^5.1.1", "get-port": "^6.1.2",
"ndjson": "^2.0.0", "ndjson": "^2.0.0",
"pino-pretty": "^4.0.0", "pino-pretty": "^9.1.1",
"tap-min": "^2.0.0", "tap-min": "^3.0.0",
"tape": "^5.5.2" "tape": "^5.5.2"
}, },
"scripts": { "scripts": {
"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": "REDIS_URL='' ./build/index.js",
"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 [`v5.db.transport.rest`](https://v5.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 [`v6.db.transport.rest`](https://v6.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/m784879516-8a977fa91b975fc3884a9857)](https://stats.uptimerobot.com/57wNLs39M/784879516) [![API status](https://badgen.net/uptime-robot/status/m793274556-25c5e9bbab0297d91cda7134)](https://stats.uptimerobot.com/57wNLs39M/793274556)
[![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,14 +15,16 @@
## installing & running ## installing & running
`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. ### access to Redis
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 `derhuerst/db-rest:5`](https://hub.docker.com/r/derhuerst/db-rest:5). A Docker image [is available as `docker.io/derhuerst/db-rest:6`](https://hub.docker.com/r/docker.io/derhuerst/db-rest:6).
```shell ```shell
docker run -d -p 3000:3000 derhuerst/db-rest:5 docker run -d -p 3000:3000 docker.io/derhuerst/db-rest:6
``` ```
*Note:* The Docker image does not contain the Redis server. *Note:* The Docker image does not contain the Redis server.
@ -32,8 +34,11 @@ docker run -d -p 3000:3000 derhuerst/db-rest:5
```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 5 git checkout 6
npm install --production npm install
export HOSTNAME='my-vbb-rest-api.example.org'
npm run build
redis-server & redis-server &
npm start npm start
@ -44,6 +49,7 @@ 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,6 +1,4 @@
'use strict' import {pStations} from '../lib/db-stations.js'
const pStations = require('../lib/db-stations')
const err404 = (msg) => { const err404 = (msg) => {
const err = new Error(msg) const err = new Error(msg)
@ -52,9 +50,12 @@ Returns a stop/station from [\`db-stations@3\`](https://github.com/derhuerst/db-
}, },
}, },
}, },
// todo: non-2xx response
}, },
}, },
}, },
} }
module.exports = stationRoute export {
stationRoute as route,
}

View file

@ -1,11 +1,10 @@
'use strict' import computeEtag from 'etag'
import serveBuffer from 'serve-buffer'
const computeEtag = require('etag') import {autocomplete} from 'db-stations-autocomplete'
const serveBuffer = require('serve-buffer') import _cliNative from 'cli-native'
const autocomplete = require('db-stations-autocomplete') const {to: parse} = _cliNative
const parse = require('cli-native').to import {createFilter} from 'db-stations/create-filter.js'
const createFilter = require('db-stations/create-filter') import {pStations} from '../lib/db-stations.js'
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'
@ -24,7 +23,7 @@ const toNdjsonBuf = (data) => {
return Buffer.concat(chunks, bytes) return Buffer.concat(chunks, bytes)
} }
pAllStations = pAllStations.then(({data, timeModified}) => { const pAllStations = pStations.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 {
@ -166,10 +165,16 @@ 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
},
},
}, },
} }
@ -196,4 +201,6 @@ stationsRoute.queryParameters = {
}, },
} }
module.exports = stationsRoute export {
stationsRoute as route,
}

View file

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

View file

@ -1,12 +1,9 @@
'use strict' import {createHafasRestApi} from 'hafas-rest-api'
import getPort from 'get-port'
const createApi = require('hafas-rest-api') import {createServer} from 'node:http'
const getPort = require('get-port') import {promisify} from 'node:util'
const {createServer} = require('http') import axios from 'axios'
const {promisify} = require('util') import {config, hafas as unmockedHafas} from '../api.js'
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) => {
@ -24,7 +21,7 @@ const createTestApi = async (mocks, cfg) => {
...cfg, ...cfg,
} }
const api = createApi(mockedHafas, cfg, () => {}) const api = await createHafasRestApi(mockedHafas, cfg, () => {})
const server = createServer(api) const server = createServer(api)
const port = await getPort() const port = await getPort()
@ -50,7 +47,7 @@ const fetchWithTestApi = async (mocks, cfg, path, opt = {}) => {
return res return res
} }
module.exports = { export {
createTestApi, createTestApi,
fetchWithTestApi, fetchWithTestApi,
} }