Wegwichtel/docs/REST-API.md
2026-06-16 17:01:53 +02:00

637 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Wegwichtel REST-API
Diese Datei dokumentiert die vollständige HTTP-Schnittstelle des aktuellen Wegwichtel-Servers. Die API wird unter `/api` bereitgestellt. Aktive GPX-, Bild- und Audiodateien werden zusätzlich unter `/media` ausgeliefert.
## 1. Grundlagen
### Basisadresse
Lokaler Standard:
```text
http://127.0.0.1:47145
```
API-Basis:
```text
http://127.0.0.1:47145/api
```
Bei einer Nginx-Installation bleibt der Pfad gleich, beispielsweise:
```text
https://wegwichtel.example.org/api
```
### Formate
- Lesezugriffe liefern JSON.
- Routen- und POI-Schreibzugriffe mit Dateien verwenden `multipart/form-data`.
- Fehler werden als JSON ausgegeben.
- Zeitstempel stammen aus SQLite beziehungsweise JavaScript und werden als Text oder ISO-8601-Zeitstempel ausgegeben.
- Die aktuelle API besitzt noch keine Authentifizierung. Schreibzugriffe dürfen deshalb nicht ungeschützt öffentlich erreichbar sein.
### Allgemeines Fehlerformat
```json
{
"error": "HttpError",
"message": "Strecke nicht gefunden."
}
```
Bei Detailinformationen kann zusätzlich `details` enthalten sein:
```json
{
"error": "HttpError",
"message": "Die GPX-Datei ist kein gültiges XML.",
"details": "Parsermeldung"
}
```
Typische Statuscodes:
| Status | Bedeutung |
|---:|---|
| `200` | Anfrage erfolgreich |
| `201` | Ressource wurde angelegt |
| `400` | Parameter oder Upload unvollständig beziehungsweise ungültig |
| `404` | Route, POI oder Datei wurde nicht gefunden |
| `409` | Dateisystemzustand verhindert Löschen oder Wiederherstellen |
| `413` | Eine hochgeladene Datei überschreitet das konfigurierte Limit |
| `415` | Dateityp wird nicht unterstützt |
| `500` | Interner Serverfehler |
### IDs
Die Pfadparameter `:id` sind positive, von SQLite erzeugte Ganzzahlen. Beispiele verwenden überwiegend Route `1` und POI `7`.
### Uploadgrenzen und Dateitypen
Das Dateilimit pro Datei wird durch `MAX_UPLOAD_MB` festgelegt und beträgt standardmäßig `50 MB`. Multer akzeptiert höchstens 25 Dateien pro Request.
Erlaubte Uploadtypen:
- GPX/XML: `.gpx`, `application/gpx+xml`, `application/xml`, `text/xml`
- Bilder: JPEG, PNG, WebP
- Audio: MP3, MP4/M4A, AAC, Ogg, WAV, WebM
Beim Anlegen oder Aktualisieren eines POIs gelten zusätzlich:
- höchstens eine Datei im Feld `audio`
- höchstens 20 Dateien im Feld `images`
## 2. Datenmodelle
### Kurzfassung einer Route
```json
{
"id": 1,
"slug": "schulwald-runde",
"name": "Schulwald-Runde",
"description": "Naturkundlicher Rundweg",
"schoolName": "Beispielschule",
"status": "active",
"start": { "lat": 52.5208, "lon": 13.4070 },
"center": { "lat": 52.5210, "lon": 13.4080 },
"bounds": {
"minLat": 52.5208,
"minLon": 13.4070,
"maxLat": 52.5212,
"maxLon": 13.4090
},
"distanceM": 842.6,
"elevationGainM": 14.2,
"pointCount": 87,
"gpxUrl": "/media/routes/1/route.gpx",
"createdAt": "2026-06-16 12:00:00",
"updatedAt": "2026-06-16 12:00:00",
"deletedAt": null,
"proximityM": 324.8
}
```
`proximityM` ist `null`, wenn beim Listenaufruf keine gültige Position übergeben wurde. Bei gelöschten Routen ist `gpxUrl` `null`, weil Dateien im Papierkorb nicht öffentlich ausgeliefert werden.
### Vollständige Route
`GET /api/routes/:id` ergänzt die Kurzfassung um `points` und `pois`:
```json
{
"id": 1,
"name": "Schulwald-Runde",
"points": [
{
"sequence": 0,
"lat": 52.5208,
"lon": 13.4070,
"elevation": 71.4,
"recordedAt": "2026-06-16T09:00:00Z"
}
],
"pois": []
}
```
### POI
```json
{
"id": 7,
"routeId": 1,
"title": "Die alte Eiche",
"description": "Hier wird das Alter der Eiche erklärt.",
"lat": 52.5208,
"lon": 13.4070,
"triggerRadiusM": 60,
"sequence": 2,
"audioUrl": "/media/routes/1/audio/uuid.mp3",
"images": [
{
"id": 15,
"caption": "",
"sequence": 0,
"url": "/media/routes/1/images/uuid.jpg"
}
]
}
```
## 3. Systemzustand
### `GET /api/health`
Prüft den Node.js-Prozess und eine SQLite-Abfrage.
Parameter: keine.
```bash
curl http://127.0.0.1:47145/api/health
```
Beispielantwort:
```json
{
"ok": true,
"service": "wegwichtel",
"socket": "127.0.0.1:47145",
"sqliteVersion": "3.46.1",
"timestamp": "2026-06-16T12:00:00.000Z"
}
```
## 4. Routen lesen
### `GET /api/routes`
Liefert standardmäßig alle aktiven Routen alphabetisch. Werden `lat` und `lon` gemeinsam übergeben, berechnet der Server die Entfernung zum Startpunkt, filtert anhand `radiusKm` und sortiert nach Entfernung.
#### Query-Parameter
| Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---:|---|
| `lat` | Dezimalzahl | nein | | Breitengrad der aktuellen Position; nur zusammen mit `lon` wirksam |
| `lon` | Dezimalzahl | nein | | Längengrad der aktuellen Position; nur zusammen mit `lat` wirksam |
| `radiusKm` | Dezimalzahl | nein | `DEFAULT_ROUTE_RADIUS_KM`, standardmäßig `25` | maximaler Abstand zum Routenstart; nur bei gültigem `lat` und `lon` wirksam |
| `includeDeleted` | Boolean-Text | nein | `false` | nur der exakte Wert `true` schließt gelöschte Routen ein |
Alle aktiven Routen:
```bash
curl http://127.0.0.1:47145/api/routes
```
Alle Parameter in einem Beispiel:
```bash
curl --get http://127.0.0.1:47145/api/routes \
--data-urlencode 'lat=52.5208' \
--data-urlencode 'lon=13.4070' \
--data-urlencode 'radiusKm=12.5' \
--data-urlencode 'includeDeleted=true'
```
Antwort:
```json
{
"routes": [
{
"id": 1,
"name": "Schulwald-Runde",
"status": "active",
"proximityM": 324.8
}
]
}
```
Hinweise:
- Eine ungültige Zahl wird intern wie ein nicht gesetzter Wert behandelt.
- Der Positionsfilter wird nur aktiv, wenn sowohl `lat` als auch `lon` gültige Zahlen sind.
- `radiusKm` wird derzeit nicht auf einen Mindest- oder Höchstwert begrenzt.
### `GET /api/routes/:id`
Liefert eine Route mit sämtlichen GPX-Punkten und POIs.
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID der Route |
#### Query-Parameter
| Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---:|---|
| `includeDeleted` | Boolean-Text | nein | `false` | mit `true` kann auch eine als gelöscht markierte Route gelesen werden |
Aktive Route:
```bash
curl http://127.0.0.1:47145/api/routes/1
```
Gelöschte Route ausdrücklich einschließen:
```bash
curl 'http://127.0.0.1:47145/api/routes/1?includeDeleted=true'
```
Bei einer gelöschten Route sind Medien-URLs `null`, weil der Papierkorb nicht unter `/media` veröffentlicht wird.
### `GET /api/routes/:id/pois`
Liefert alle POIs einer aktiven Route in der Reihenfolge `sequence`, anschließend `id`.
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID der aktiven Route |
Weitere Parameter: keine.
```bash
curl http://127.0.0.1:47145/api/routes/1/pois
```
Antwort:
```json
{
"pois": [
{
"id": 7,
"routeId": 1,
"title": "Die alte Eiche",
"sequence": 2,
"audioUrl": "/media/routes/1/audio/uuid.mp3",
"images": []
}
]
}
```
## 5. POIs lesen
### `GET /api/pois/:id`
Liefert einen einzelnen POI einschließlich Bild- und Audio-URLs. Der POI muss zu einer aktiven Route gehören.
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID des POIs |
Weitere Parameter: keine.
```bash
curl http://127.0.0.1:47145/api/pois/7
```
## 6. Route anlegen
### `POST /api/routes`
Legt eine neue Route aus einer GPX-Datei an. Trackpunkte, Streckenlänge, Höhengewinn, Startpunkt, Mittelpunkt und Grenzen werden aus der Datei berechnet.
Content-Type: `multipart/form-data`
#### Formular- und Dateiparameter
| Feld | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---|---|
| `gpx` | Datei | ja | | GPX-Datei mit mindestens einem verwertbaren `trkpt` |
| `name` | Text | bedingt | GPX-Track- oder Metadatenname | Routenname; erforderlich, falls die GPX-Datei keinen Namen enthält |
| `slug` | Text | nein | aus `name` abgeleitet | URL-freundliche interne Kennung; Kollisionen erhalten automatisch `-2`, `-3` usw. |
| `description` | Text | nein | leer | Beschreibung der Route |
| `schoolName` | Text | nein | leer | Name der Schule oder Einrichtung |
Beispiel mit allen Parametern:
```bash
curl -X POST http://127.0.0.1:47145/api/routes \
-F 'name=Schulwald-Runde' \
-F 'slug=schulwald-runde-klasse-7a' \
-F 'description=Naturkundlicher Rundweg der Klasse 7a' \
-F 'schoolName=Beispielschule' \
-F 'gpx=@examples/sample-route.gpx;type=application/gpx+xml'
```
Erfolg:
- Status `201 Created`
- Header `Location: /api/routes/<id>`
- Body: vollständige neu angelegte Route
## 7. Route vollständig aktualisieren
### `PUT /api/routes/:id`
Ändert Metadaten einer aktiven Route. Eine optionale GPX-Datei ersetzt alle bisherigen Trackpunkte und die Datei `route.gpx`.
Content-Type: `multipart/form-data`
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID der aktiven Route |
#### Formular- und Dateiparameter
| Feld | Typ | Pflicht | Verhalten bei Auslassung | Beschreibung |
|---|---|---:|---|---|
| `name` | Text | nein | alter Wert bleibt | neuer Routenname |
| `description` | Text | nein | alter Wert bleibt | neue Beschreibung; leere Zeichenfolge löscht den Inhalt |
| `schoolName` | Text | nein | alter Wert bleibt | neuer Schulname; leere Zeichenfolge löscht den Inhalt |
| `gpx` | Datei | nein | Track bleibt unverändert | ersetzt GPX-Datei, Trackpunkte und berechnete Kennzahlen vollständig |
`slug` kann über diesen Endpunkt derzeit nicht geändert werden.
Beispiel mit allen Parametern:
```bash
curl -X PUT http://127.0.0.1:47145/api/routes/1 \
-F 'name=Schulwald-Runde überarbeitet' \
-F 'description=Neue Wegführung ab dem Schulhof' \
-F 'schoolName=Beispielschule Nord' \
-F 'gpx=@examples/sample-route.gpx;type=application/gpx+xml'
```
Nur die Beschreibung ändern:
```bash
curl -X PUT http://127.0.0.1:47145/api/routes/1 \
-F 'description=Nur dieser Wert wird geändert.'
```
Antwort: vollständige aktualisierte Route.
## 8. GPX-Punkte an eine Route anhängen
### `POST /api/routes/:id/append`
Hängt sämtliche verwertbaren Trackpunkte einer GPX-Datei an eine aktive Route an und berechnet die Streckenkennzahlen neu.
Content-Type: `multipart/form-data`
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID der aktiven Route |
#### Dateiparameter
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `gpx` | Datei | ja | GPX-Datei mit den anzuhängenden Trackpunkten |
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/append \
-F 'gpx=@weiterer-abschnitt.gpx;type=application/gpx+xml'
```
Hinweis: Die Punkte werden in SQLite ergänzt. Die gespeicherte Originaldatei `route.gpx` wird durch diesen Endpunkt derzeit nicht zu einer konsolidierten GPX-Datei erweitert.
## 9. POI anlegen
### `POST /api/routes/:id/pois`
Legt einen POI für eine aktive Route an und speichert optional eine Audioansage und mehrere Bilder.
Content-Type: `multipart/form-data`
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID der aktiven Route |
#### Formular- und Dateiparameter
| Feld | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---|---|
| `title` | Text | nein | `Unbenannter POI` | Titel der Station |
| `description` | Text | nein | leer | Beschreibung der Station |
| `lat` | Dezimalzahl | ja | | Breitengrad des POIs |
| `lon` | Dezimalzahl | ja | | Längengrad des POIs |
| `triggerRadiusM` | Dezimalzahl | nein | `DEFAULT_POI_TRIGGER_METERS`, standardmäßig `80` | Entfernung in Metern, ab der die Station automatisch ausgelöst wird |
| `sequence` | Ganzzahl | nein | `0` | Sortierreihenfolge innerhalb der Route |
| `audio` | Datei | nein | keine | eine Audioansage |
| `images` | Datei, wiederholbar | nein | keine | bis zu 20 Bilder; jedes Bild wird als eigenes Feld `images` gesendet |
Beispiel mit allen Parametern:
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/pois \
-F 'title=Die alte Eiche' \
-F 'description=Hier wird das Alter der Eiche erklärt.' \
-F 'lat=52.5208' \
-F 'lon=13.4070' \
-F 'triggerRadiusM=60' \
-F 'sequence=2' \
-F 'audio=@ansage.mp3;type=audio/mpeg' \
-F 'images=@eiche-1.jpg;type=image/jpeg' \
-F 'images=@eiche-2.webp;type=image/webp'
```
Erfolg:
- Status `201 Created`
- Header `Location: /api/pois/<id>`
- Body: neu angelegter POI
Bildunterschriften können in der aktuellen API noch nicht per Parameter gesetzt werden und bleiben leer.
## 10. POI aktualisieren und Medien ergänzen
### `PUT /api/pois/:id`
Ändert einen POI einer aktiven Route. Eine neue Audiodatei ersetzt den in der Datenbank referenzierten Audiopfad. Neue Bilder werden an die vorhandene Bilderliste angehängt.
Content-Type: `multipart/form-data`
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID des POIs |
#### Formular- und Dateiparameter
| Feld | Typ | Pflicht | Verhalten bei Auslassung | Beschreibung |
|---|---|---:|---|---|
| `title` | Text | nein | alter Wert bleibt | neuer Titel |
| `description` | Text | nein | alter Wert bleibt | neue Beschreibung |
| `lat` | Dezimalzahl | nein | alter Wert bleibt | neuer Breitengrad |
| `lon` | Dezimalzahl | nein | alter Wert bleibt | neuer Längengrad |
| `triggerRadiusM` | Dezimalzahl | nein | alter Wert bleibt | neuer automatischer Auslöseradius in Metern |
| `sequence` | Ganzzahl | nein | alter Wert bleibt | neue Sortierreihenfolge |
| `audio` | Datei | nein | alte Referenz bleibt | neue Audioansage; maximal eine Datei |
| `images` | Datei, wiederholbar | nein | Bilder bleiben unverändert | bis zu 20 zusätzliche Bilder |
Beispiel mit allen Parametern:
```bash
curl -X PUT http://127.0.0.1:47145/api/pois/7 \
-F 'title=Die sehr alte Eiche' \
-F 'description=Überarbeitete Ansage und neue Fotos.' \
-F 'lat=52.5209' \
-F 'lon=13.4072' \
-F 'triggerRadiusM=45' \
-F 'sequence=3' \
-F 'audio=@ansage-neu.ogg;type=audio/ogg' \
-F 'images=@eiche-3.png;type=image/png' \
-F 'images=@eiche-4.jpg;type=image/jpeg'
```
Wichtige aktuelle Einschränkungen:
- Einzelne Bilder können noch nicht per API gelöscht, umsortiert oder beschriftet werden.
- Neu hochgeladene Bilder werden hinter vorhandenen Bildern einsortiert.
- Beim Ersetzen der Audio-Referenz wird die vorherige Audiodatei derzeit nicht automatisch aus dem aktiven Verzeichnis entfernt.
## 11. Route zum Löschen markieren
### `DELETE /api/routes/:id`
Markiert eine aktive Route als gelöscht und verschiebt ihr gesamtes Verzeichnis einschließlich GPX, Bildern und Audio nach `storage/trash/routes`. Die Datenbankzeilen bleiben erhalten; alle gespeicherten Pfade werden auf den Papierkorb umgeschrieben.
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID der aktiven Route |
Weitere Parameter: keine.
```bash
curl -X DELETE http://127.0.0.1:47145/api/routes/1
```
Antwort:
```json
{
"route": {
"id": 1,
"status": "deleted",
"gpxUrl": null,
"deletedAt": "2026-06-16 12:30:00"
},
"softDeleted": true
}
```
Es werden keine Dateien endgültig entfernt.
## 12. Route wiederherstellen
### `POST /api/routes/:id/restore`
Stellt eine als gelöscht markierte Route wieder her, verschiebt ihr Verzeichnis nach `storage/active/routes/<id>` zurück und korrigiert alle GPX-, Bild- und Audiopfade.
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | Ganzzahl | ja | ID der gelöschten Route |
Weitere Parameter: keine. Der Request besitzt keinen Body.
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/restore
```
Antwort:
```json
{
"route": {
"id": 1,
"status": "active",
"gpxUrl": "/media/routes/1/route.gpx",
"deletedAt": null
},
"restored": true
}
```
## 13. Medien abrufen
Aktive Dateien werden nicht unter `/api`, sondern statisch unter `/media` ausgeliefert. Die benötigten URLs stehen in `gpxUrl`, `audioUrl` und `images[].url`.
Parameter: keine zusätzlichen Query- oder Formularparameter. Der komplette Pfad stammt aus der jeweiligen API-Antwort.
GPX-Datei:
```bash
curl --output route.gpx \
http://127.0.0.1:47145/media/routes/1/route.gpx
```
Audio:
```bash
curl --output ansage.mp3 \
http://127.0.0.1:47145/media/routes/1/audio/DATEINAME.mp3
```
Bild:
```bash
curl --output station.jpg \
http://127.0.0.1:47145/media/routes/1/images/DATEINAME.jpg
```
Nur `storage/active` wird veröffentlicht. Dateien gelöschter Routen unter `storage/trash` sind nicht über HTTP erreichbar.
## 14. Endpunktübersicht
| Methode | Pfad | Zweck |
|---|---|---|
| `GET` | `/api/health` | Server und SQLite prüfen |
| `GET` | `/api/routes` | Routen auflisten und optional räumlich filtern |
| `GET` | `/api/routes/:id` | vollständige Route lesen |
| `GET` | `/api/routes/:id/pois` | POIs einer Route lesen |
| `GET` | `/api/pois/:id` | einzelnen POI lesen |
| `POST` | `/api/routes` | Route aus GPX anlegen |
| `PUT` | `/api/routes/:id` | Route aktualisieren oder GPX ersetzen |
| `POST` | `/api/routes/:id/append` | GPX-Punkte anhängen |
| `POST` | `/api/routes/:id/pois` | POI und Medien anlegen |
| `PUT` | `/api/pois/:id` | POI aktualisieren und Medien ergänzen |
| `DELETE` | `/api/routes/:id` | Route in den Papierkorb verschieben |
| `POST` | `/api/routes/:id/restore` | Route wiederherstellen |
| `GET` | `/media/routes/...` | aktive GPX-, Bild- und Audiodateien abrufen |