remove /media endpoint

This commit is contained in:
Florian Zumpe
2026-06-17 00:10:46 +02:00
parent c98f304ffa
commit 833b5ad3a4
12 changed files with 443 additions and 365 deletions
+309 -226
View File
@@ -1,6 +1,6 @@
# Wegwichtel REST-API
Diese Datei dokumentiert die vollständige HTTP-Schnittstelle des Wegwichtel-Servers. Die API-Basis lautet `/api`. Bild- und Audiodateien werden ebenfalls ausschließlich über API-Endpunkte ausgeliefert; die Clientanwendung verwendet dafür keine direkten Speicherpfade.
Diese Datei dokumentiert die vollständige HTTP-Schnittstelle des Wegwichtel-Servers. Sämtliche JSON-Ressourcen und aktiven GPX-, Bild- und Audiodateien werden unter `/api` bereitgestellt. Ein separates öffentliches `/media`-URL-Schema wird nicht verwendet.
## 1. Grundlagen
@@ -18,7 +18,7 @@ API-Basis:
http://127.0.0.1:47145/api
```
Bei vorgeschaltetem Nginx bleibt der Pfad erhalten, zum Beispiel:
Bei einer Nginx-Installation bleibt der Pfad gleich, beispielsweise:
```text
https://wegwichtel.example.org/api
@@ -26,23 +26,33 @@ https://wegwichtel.example.org/api
### Formate
- JSON-Lesezugriffe liefern `application/json`.
- Bild- und Audio-GETs liefern den jeweiligen binären Medientyp.
- Schreibzugriffe ohne Datei akzeptieren JSON, URL-encoded Formulare oder `multipart/form-data`.
- Datei-Uploads verwenden `multipart/form-data`.
- Pro Medienrequest wird genau eine Datei verarbeitet.
- Zeitstempel werden als SQLite- beziehungsweise ISO-8601-Text ausgegeben.
- Die derzeitige API besitzt noch keine Authentifizierung. Schreibzugriffe dürfen nicht ungeschützt öffentlich erreichbar sein.
- Listen- und Metadatenzugriffe liefern JSON.
- Die Einzelendpunkte für GPX, Bilder und Audio liefern die jeweilige Datei direkt aus. Mit `?metadata=true` liefern die Bild- und Audioendpunkte stattdessen JSON-Metadaten.
- Schreibzugriffe ohne Dateien akzeptieren JSON, `application/x-www-form-urlencoded` oder `multipart/form-data`.
- Schreibzugriffe mit Dateien verwenden `multipart/form-data`.
- Pro Medien-Request wird genau eine Bild- beziehungsweise Audiodatei verarbeitet.
- Zeitstempel werden als SQLite- oder ISO-8601-Text ausgegeben.
- Die aktuelle API besitzt keine Authentifizierung. Schreibzugriffe dürfen nicht ungeschützt öffentlich erreichbar sein.
### Uploadgrenzen und unterstützte Dateitypen
### Uploadgrenzen und Dateitypen
Das Dateilimit pro Upload wird mit `MAX_UPLOAD_MB` festgelegt und beträgt standardmäßig `50 MB`.
Das Dateilimit pro Datei wird durch `MAX_UPLOAD_MB` festgelegt und beträgt standardmäßig `50 MB`.
| Ressource | Uploadfeld | Dateitypen |
|---|---|---|
| GPX | `gpx` | `.gpx`, `application/gpx+xml`, XML |
| Bild | `picture` | JPEG, PNG, WebP |
| Audio | `audio` | MP3, MP4/M4A, AAC, Ogg, WAV, WebM |
Unterstützte Dateitypen:
| Ressource | Dateiendungen beziehungsweise MIME-Typen |
|---|---|
| GPX | `.gpx`, `application/gpx+xml`, `application/xml`, `text/xml` |
| Bilder | JPEG, PNG, WebP |
| Audio | MP3, MP4/M4A, AAC, Ogg, WAV, WebM |
Die Uploadfelder heißen:
| Ressource | Feldname |
|---|---|
| GPX | `gpx` |
| einzelnes Bild | `picture` |
| einzelne Audiodatei | `audio` |
### Fehlerformat
@@ -59,23 +69,22 @@ Typische Statuscodes:
|---:|---|
| `200` | Anfrage erfolgreich |
| `201` | Ressource wurde angelegt |
| `400` | Parameter oder Datei fehlt beziehungsweise ist ungültig |
| `400` | Parameter oder Upload fehlt beziehungsweise ist ungültig |
| `404` | Route, POI oder Medienressource wurde nicht gefunden |
| `409` | Ressource existiert bereits oder widerspricht dem aktuellen Zustand |
| `409` | Ressource existiert bereits oder Dateisystemzustand verhindert die Operation |
| `413` | Datei überschreitet `MAX_UPLOAD_MB` |
| `415` | Dateityp wird nicht unterstützt |
| `500` | Interner Serverfehler |
### IDs und Medienadressierung
### IDs
Alle IDs sind positive, von SQLite erzeugte Ganzzahlen.
- `:id` bezeichnet bei `/routes/:id/...` die Route.
- `:pictureId` bezeichnet ein einzelnes Bild aus `poi_images`.
- `poiId` bezeichnet einen POI innerhalb einer Route.
- Ein POI kann mehrere Bilder, aber höchstens eine Audiodatei besitzen.
- Bilder erhalten deshalb eine eigene Pfad-ID.
- Audio verwendet keinen zusätzlichen Pfadabschnitt; der POI wird über `poiId` angegeben.
- `:pictureId` bezeichnet einen Datensatz aus `poi_images`.
- `:poiId` bezeichnet den POI und zugleich seine höchstens eine Audioressource.
Beispiele verwenden Route `1`, POI `7` und Bild `15`.
Beispiele verwenden überwiegend Route `1`, POI `7` und Bild `15`.
## 2. Datenmodelle
@@ -100,10 +109,11 @@ Beispiele verwenden Route `1`, POI `7` und Bild `15`.
"distanceM": 842.6,
"elevationGainM": 14.2,
"pointCount": 87,
"gpxUrl": "/media/routes/1/route.gpx",
"gpxUrl": "/api/routes/1/gpx",
"createdAt": "2026-06-16 12:00:00",
"updatedAt": "2026-06-16 12:00:00",
"deletedAt": null
"deletedAt": null,
"proximityM": 324.8
}
```
@@ -119,7 +129,7 @@ Beispiele verwenden Route `1`, POI `7` und Bild `15`.
"lon": 13.407,
"triggerRadiusM": 60,
"sequence": 2,
"audioUrl": "/api/routes/1/audio?poiId=7",
"audioUrl": "/api/routes/1/audio/7",
"images": [
{
"id": 15,
@@ -131,7 +141,7 @@ Beispiele verwenden Route `1`, POI `7` und Bild `15`.
}
```
### Bildmetadaten
### Bildressource
```json
{
@@ -146,14 +156,16 @@ Beispiele verwenden Route `1`, POI `7` und Bild `15`.
}
```
### Audiometadaten
### Audioressource
Pro POI kann höchstens eine Audiodatei existieren. Deshalb wird die Audioressource über die `poiId` adressiert.
```json
{
"routeId": 1,
"poiId": 7,
"poiTitle": "Die alte Eiche",
"url": "/api/routes/1/audio?poiId=7",
"url": "/api/routes/1/audio/7",
"updatedAt": "2026-06-16 12:35:00"
}
```
@@ -165,6 +177,7 @@ Beispiele verwenden Route `1`, POI `7` und Bild `15`.
| `GET` | `/api/health` | Server- und SQLite-Zustand |
| `GET` | `/api/routes` | Routen auflisten |
| `GET` | `/api/routes/:id` | Route mit Punkten und POIs lesen |
| `GET` | `/api/routes/:id/gpx` | GPX-Datei der Route ausliefern |
| `POST` | `/api/routes` | Route anlegen |
| `PUT` | `/api/routes/:id` | Route aktualisieren |
| `POST` | `/api/routes/:id/append` | GPX-Punkte anhängen |
@@ -172,21 +185,22 @@ Beispiele verwenden Route `1`, POI `7` und Bild `15`.
| `POST` | `/api/routes/:id/restore` | Route wiederherstellen |
| `GET` | `/api/routes/:id/pois` | POIs einer Route auflisten |
| `GET` | `/api/pois/:id` | einzelnen POI lesen |
| `POST` | `/api/routes/:id/pois` | POI anlegen |
| `PUT` | `/api/pois/:id` | POI aktualisieren |
| `GET` | `/api/routes/:id/pictures` | Bildmetadaten auflisten |
| `GET` | `/api/routes/:id/pictures/:pictureId` | Bilddatei ausliefern |
| `POST` | `/api/routes/:id/pictures` | Bild anlegen |
| `POST` | `/api/routes/:id/pois` | POI-Metadaten anlegen |
| `PUT` | `/api/pois/:id` | POI-Metadaten aktualisieren |
| `GET` | `/api/routes/:id/pictures` | Bilder einer Route auflisten |
| `GET` | `/api/routes/:id/pictures/:pictureId` | Bilddatei ausliefern; optional Metadaten mit `?metadata=true` |
| `POST` | `/api/routes/:id/pictures` | einzelnes Bild hochladen |
| `PUT` | `/api/routes/:id/pictures/:pictureId` | Bilddatei oder Metadaten aktualisieren |
| `DELETE` | `/api/routes/:id/pictures/:pictureId` | Bild löschen |
| `GET` | `/api/routes/:id/audio` | Audio auflisten oder für einen POI ausliefern |
| `POST` | `/api/routes/:id/audio` | Audio für einen POI anlegen |
| `PUT` | `/api/routes/:id/audio` | Audio eines POIs ersetzen |
| `DELETE` | `/api/routes/:id/audio` | Audio eines POIs löschen |
| `DELETE` | `/api/routes/:id/pictures/:pictureId` | einzelnes Bild löschen |
| `GET` | `/api/routes/:id/audio` | Audiodateien einer Route auflisten |
| `GET` | `/api/routes/:id/audio/:poiId` | Audiodatei ausliefern; optional Metadaten mit `?metadata=true` |
| `POST` | `/api/routes/:id/audio` | Audiodatei für einen POI anlegen |
| `PUT` | `/api/routes/:id/audio/:poiId` | Audiodatei eines POIs ersetzen |
| `DELETE` | `/api/routes/:id/audio/:poiId` | Audiodatei eines POIs löschen |
## 4. Systemzustand
### GET /api/health
### `GET /api/health`
Parameter: keine.
@@ -194,8 +208,6 @@ Parameter: keine.
curl http://127.0.0.1:47145/api/health
```
Beispielantwort:
```json
{
"ok": true,
@@ -206,20 +218,20 @@ Beispielantwort:
}
```
## 5. Routen
## 5. Routen lesen
### GET /api/routes
### `GET /api/routes`
Listet aktive Routen auf. Werden `lat` und `lon` gemeinsam angegeben, berechnet der Server die Entfernung zum Routenstart, filtert mit `radiusKm` und sortiert nach Entfernung.
Ohne Positionsparameter werden alle aktiven Routen alphabetisch geliefert. Mit `lat` und `lon` wird die Entfernung zum Routenstart berechnet, anhand `radiusKm` gefiltert und nach Entfernung sortiert.
Query-Parameter:
#### Query-Parameter
| Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---|---|
| `lat` | Dezimalzahl | nein | – | Breitengrad des Ausgangspunkts |
| `lon` | Dezimalzahl | nein | – | Längengrad des Ausgangspunkts |
| `radiusKm` | Dezimalzahl | nein | `DEFAULT_ROUTE_RADIUS_KM` | maximaler Abstand zum Routenstart |
| `includeDeleted` | `true`/`false` | nein | `false` | gelöschte Routen einschließen |
|---|---|---:|---:|---|
| `lat` | Dezimalzahl | nein | – | Breitengrad; nur zusammen mit `lon` wirksam |
| `lon` | Dezimalzahl | nein | – | Längengrad; nur zusammen mit `lat` wirksam |
| `radiusKm` | Dezimalzahl | nein | `DEFAULT_ROUTE_RADIUS_KM`, standardmäßig `25` | maximaler Abstand zum Routenstart |
| `includeDeleted` | Boolean-Text | nein | `false` | der exakte Wert `true` schließt gelöschte Routen ein |
Alle aktiven Routen:
@@ -237,128 +249,139 @@ curl --get http://127.0.0.1:47145/api/routes \
--data-urlencode 'includeDeleted=true'
```
### GET /api/routes/:id
### `GET /api/routes/:id`
Pfadparameter:
Liefert Route, GPX-Punkte und POIs einschließlich Medien-URLs.
#### Pfadparameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `id` | positive Ganzzahl | ja | Route |
| `id` | Ganzzahl | ja | Route |
Query-Parameter:
#### Query-Parameter
| Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---|---|
| `includeDeleted` | `true`/`false` | nein | `false` | auch eine weich gelöschte Route lesen |
|---|---|---:|---:|---|
| `includeDeleted` | Boolean-Text | nein | `false` | mit `true` kann eine gelöschte Route gelesen werden |
```bash
curl http://127.0.0.1:47145/api/routes/1
```
Die Antwort enthält `points`, `pois`, `images[].url` und `audioUrl`. Die Medien-URLs zeigen auf die API-Endpunkte.
```bash
curl 'http://127.0.0.1:47145/api/routes/1?includeDeleted=true'
```
### POST /api/routes
### `GET /api/routes/:id/gpx`
Content-Type: `multipart/form-data`.
Liefert die aktive GPX-Datei der Route direkt mit `Content-Type: application/gpx+xml` aus. Der in einer Routenressource enthaltene Wert `gpxUrl` verweist auf diesen Endpunkt.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `gpx` | Datei | ja | GPX-Datei |
| `name` | Text | nein, wenn GPX einen Namen enthält | Anzeigename |
| `slug` | Text | nein | gewünschter URL-tauglicher Bezeichner |
| `description` | Text | nein | Beschreibung |
| `schoolName` | Text | nein | Schule oder Projekt |
```bash
curl http://127.0.0.1:47145/api/routes/1/gpx \
--output schulwald-runde.gpx
```
Parameter außer der Routen-ID sind nicht vorgesehen. Gelöschte oder unbekannte Routen antworten mit `404`.
## 6. Route anlegen und bearbeiten
### `POST /api/routes`
Legt eine Route aus einer GPX-Datei an.
Content-Type: `multipart/form-data`
| Feld | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---|---|
| `gpx` | Datei | ja | – | GPX-Datei mit mindestens einem Trackpunkt |
| `name` | Text | bedingt | Name aus GPX | erforderlich, wenn GPX keinen Namen enthält |
| `slug` | Text | nein | aus `name` | interne URL-freundliche Kennung |
| `description` | Text | nein | leer | Routenbeschreibung |
| `schoolName` | Text | nein | leer | Schule oder Einrichtung |
```bash
curl -X POST http://127.0.0.1:47145/api/routes \
-F 'name=Schulwald-Runde' \
-F 'slug=schulwald-runde' \
-F 'description=Naturkundlicher Rundweg' \
-F 'slug=schulwald-runde-klasse-7a' \
-F 'description=Naturkundlicher Rundweg der Klasse 7a' \
-F 'schoolName=Beispielschule' \
-F 'gpx=@route.gpx;type=application/gpx+xml'
-F 'gpx=@examples/sample-route.gpx;type=application/gpx+xml'
```
### PUT /api/routes/:id
Erfolg: `201 Created` und `Location: /api/routes/<id>`.
Aktualisiert Routendaten. Eine neue GPX-Datei ersetzt die gespeicherten Trackpunkte.
### `PUT /api/routes/:id`
| Feld | Typ | Pflicht | Beschreibung |
Aktualisiert Metadaten. Eine optionale GPX-Datei ersetzt alle bisherigen Trackpunkte.
| Feld | Typ | Pflicht | Verhalten ohne Feld |
|---|---|---:|---|
| `name` | Text | nein | neuer Name |
| `description` | Text | nein | neue Beschreibung |
| `schoolName` | Text | nein | neue Schule |
| `gpx` | Datei | nein | vollständige Ersatzstrecke |
| `gpx` | Datei | nein | bisherige GPX-Punkte bleiben erhalten |
| `name` | Text | nein | bisheriger Wert bleibt |
| `description` | Text | nein | bisheriger Wert bleibt |
| `schoolName` | Text | nein | bisheriger Wert bleibt |
```bash
curl -X PUT http://127.0.0.1:47145/api/routes/1 \
-F 'name=Schulwald-Runde 2026' \
-F 'description=Überarbeitete Route' \
-F 'description=Überarbeitete Strecke' \
-F 'schoolName=Beispielschule' \
-F 'gpx=@route-neu.gpx;type=application/gpx+xml'
```
### POST /api/routes/:id/append
### `POST /api/routes/:id/append`
Hängt GPX-Punkte an eine Route an.
Hängt alle Trackpunkte einer GPX-Datei an die Route an und berechnet Streckenwerte neu.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `gpx` | Datei | ja | GPX-Datei mit zusätzlichen Punkten |
| `gpx` | Datei | ja | anzuhängende GPX-Datei |
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/append \
-F 'gpx=@erweiterung.gpx;type=application/gpx+xml'
-F 'gpx=@verlaengerung.gpx;type=application/gpx+xml'
```
### DELETE /api/routes/:id
## 7. POIs lesen und bearbeiten
Markiert die Route als gelöscht und verschiebt GPX, Bilder und Audio in den Papierkorb.
### `GET /api/routes/:id/pois`
```bash
curl -X DELETE http://127.0.0.1:47145/api/routes/1
```
### POST /api/routes/:id/restore
Stellt eine weich gelöschte Route einschließlich ihrer Dateien wieder her.
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/restore
```
## 6. POIs
### GET /api/routes/:id/pois
Liefert alle POIs einer aktiven Route nach `sequence` und `id`.
```bash
curl http://127.0.0.1:47145/api/routes/1/pois
```
### GET /api/pois/:id
### `GET /api/pois/:id`
Liefert einen einzelnen POI einschließlich seiner aktuellen Bild- und Audio-URLs.
```bash
curl http://127.0.0.1:47145/api/pois/7
```
### POST /api/routes/:id/pois
### `POST /api/routes/:id/pois`
Akzeptiert JSON, URL-encoded Formulare oder Multipart ohne Mediendateien.
Legt ausschließlich die POI-Metadaten an. Bilder und Audio werden anschließend über die gesonderten Medienendpunkte hochgeladen.
| Feld | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---|---|
| `title` | Text | nein | `Unbenannter POI` | Titel |
|---|---|---:|---:|---|
| `title` | Text | nein | `Unbenannter POI` | Stationsname |
| `description` | Text | nein | leer | Beschreibung |
| `lat` | Dezimalzahl | ja | – | Breitengrad |
| `lon` | Dezimalzahl | ja | – | Längengrad |
| `triggerRadiusM` | Dezimalzahl | nein | `DEFAULT_POI_TRIGGER_METERS` | Aktivierungsradius in Metern |
| `sequence` | Ganzzahl ≥ 0 | nein | `0` | Reihenfolge |
| `triggerRadiusM` | Dezimalzahl | nein | `DEFAULT_POI_TRIGGER_METERS`, standardmäßig `80` | Aktivierungsradius in Metern |
| `sequence` | Ganzzahl | nein | `0` | Reihenfolge in der Stationsliste |
JSON-Beispiel mit allen Parametern:
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/pois \
-H 'Content-Type: application/json' \
-d '{
"title": "Die alte Eiche",
"description": "Informationen zur Baumart",
"description": "Hier wird das Alter der Eiche erklärt.",
"lat": 52.5208,
"lon": 13.4070,
"triggerRadiusM": 60,
@@ -366,9 +389,20 @@ curl -X POST http://127.0.0.1:47145/api/routes/1/pois \
}'
```
### PUT /api/pois/:id
Erfolg: `201 Created` und `Location: /api/pois/<id>`.
Alle Felder sind optional; nicht angegebene Werte bleiben erhalten.
### `PUT /api/pois/:id`
Aktualisiert ausschließlich POI-Metadaten. Nicht übergebene Werte bleiben erhalten.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `title` | Text | nein | neuer Stationsname |
| `description` | Text | nein | neue Beschreibung |
| `lat` | Dezimalzahl | nein | neuer Breitengrad |
| `lon` | Dezimalzahl | nein | neuer Längengrad |
| `triggerRadiusM` | Dezimalzahl | nein | neuer Aktivierungsradius |
| `sequence` | nichtnegative Ganzzahl | nein | neue Reihenfolge |
```bash
curl -X PUT http://127.0.0.1:47145/api/pois/7 \
@@ -379,23 +413,21 @@ curl -X PUT http://127.0.0.1:47145/api/pois/7 \
"lat": 52.5209,
"lon": 13.4071,
"triggerRadiusM": 45,
"sequence": 1
"sequence": 3
}'
```
## 7. Bilder
## 8. Bilder einzeln verwalten
Ein POI kann mehrere Bilder besitzen. Die Collection wird über die Route adressiert; jedes Bild hat zusätzlich eine `pictureId`.
### `GET /api/routes/:id/pictures`
### GET /api/routes/:id/pictures
Liefert alle Bilder der Route. Optional kann auf einen POI eingeschränkt werden.
Liefert Bildmetadaten als JSON.
Query-Parameter:
#### Query-Parameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `poiId` | positive Ganzzahl | nein | auf Bilder eines POIs einschränken |
| `poiId` | positive Ganzzahl | nein | liefert nur Bilder dieses POIs |
Alle Bilder der Route:
@@ -403,15 +435,13 @@ Alle Bilder der Route:
curl http://127.0.0.1:47145/api/routes/1/pictures
```
Nur Bilder von POI 7:
Nur Bilder des POIs `7`:
```bash
curl --get http://127.0.0.1:47145/api/routes/1/pictures \
--data-urlencode 'poiId=7'
```
Beispielantwort:
```json
{
"pictures": [
@@ -429,27 +459,40 @@ Beispielantwort:
}
```
### GET /api/routes/:id/pictures/:pictureId
### `GET /api/routes/:id/pictures/:pictureId`
Liefert die Bilddatei binär mit dem passenden `Content-Type`, beispielsweise `image/jpeg`, `image/png` oder `image/webp`.
Liefert standardmäßig die Bilddatei direkt aus. Genau dieser Pfad wird im Feld `url` der Bildressource und unter `pois[].images[].url` ausgegeben.
Bild speichern:
```bash
curl http://127.0.0.1:47145/api/routes/1/pictures/15 \
--output bild-15.jpg
```
Bildmetadaten werden über `GET /api/routes/:id/pictures` gelesen.
Metadaten statt Dateidaten abrufen:
### POST /api/routes/:id/pictures
```bash
curl --get http://127.0.0.1:47145/api/routes/1/pictures/15 \
--data-urlencode 'metadata=true'
```
Content-Type: `multipart/form-data`.
| Query-Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---:|---|
| `metadata` | Boolean | nein | `false` | bei `true` JSON-Metadaten statt der Bilddatei liefern |
### `POST /api/routes/:id/pictures`
Lädt genau ein Bild hoch und ordnet es einem POI derselben Route zu.
Content-Type: `multipart/form-data`
| Feld | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---|---|
| `picture` | Datei | ja | – | Bilddatei |
| `poiId` | positive Ganzzahl | ja | – | zugehöriger POI |
| `caption` | Text | nein | leer | sichtbare Bildbeschreibung und Alternativtext |
| `sequence` | Ganzzahl ≥ 0 | nein | nächste freie Position | Reihenfolge in der Diashow |
|---|---|---:|---:|---|
| `picture` | Datei | ja | – | JPEG-, PNG- oder WebP-Datei |
| `poiId` | positive Ganzzahl | ja | – | Ziel-POI derselben Route |
| `caption` | Text | nein | leer | sichtbare Bildbeschreibung und Grundlage für den Alternativtext |
| `sequence` | nichtnegative Ganzzahl | nein | nächster freier Wert des POIs | Reihenfolge in der Diashow |
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/pictures \
@@ -459,20 +502,20 @@ curl -X POST http://127.0.0.1:47145/api/routes/1/pictures \
-F 'picture=@eiche.jpg;type=image/jpeg'
```
Die Antwort enthält Metadaten. Der `Location`-Header zeigt auf `/api/routes/1/pictures/:pictureId`.
Erfolg: `201 Created` und `Location: /api/routes/1/pictures/<pictureId>`.
### PUT /api/routes/:id/pictures/:pictureId
### `PUT /api/routes/:id/pictures/:pictureId`
Aktualisiert Metadaten und optional die Datei. Der Request kann JSON ohne Datei oder Multipart mit Datei verwenden.
Aktualisiert Metadaten und kann optional die Datei ersetzen. Nicht übergebene Metadaten bleiben erhalten.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `picture` | Datei | nein | Ersatzdatei |
| `poiId` | positive Ganzzahl | nein | Bild einem anderen POI derselben Route zuordnen |
| `caption` | Text | nein | Bildbeschreibung |
| `sequence` | Ganzzahl ≥ 0 | nein | Reihenfolge |
| `picture` | Datei | nein | ersetzt die bisherige Bilddatei |
| `poiId` | positive Ganzzahl | nein | ordnet das Bild einem anderen POI derselben Route zu |
| `caption` | Text | nein | neue Bildbeschreibung; leerer Text entfernt die Beschreibung |
| `sequence` | nichtnegative Ganzzahl | nein | neue Position in der Diashow |
Nur Metadaten ändern:
Nur Metadaten per JSON ändern:
```bash
curl -X PUT http://127.0.0.1:47145/api/routes/1/pictures/15 \
@@ -484,37 +527,57 @@ curl -X PUT http://127.0.0.1:47145/api/routes/1/pictures/15 \
}'
```
Datei und Metadaten ersetzen:
Datei und alle Metadaten ersetzen:
```bash
curl -X PUT http://127.0.0.1:47145/api/routes/1/pictures/15 \
-F 'poiId=7' \
-F 'caption=Neue Bildbeschreibung' \
-F 'sequence=1' \
-F 'caption=Neue Aufnahme der Eiche' \
-F 'sequence=2' \
-F 'picture=@eiche-neu.webp;type=image/webp'
```
### DELETE /api/routes/:id/pictures/:pictureId
Wird eine Datei ersetzt, entfernt der Server die bisherige Datei nach erfolgreicher Datenbankaktualisierung.
Löscht den Datenbankeintrag und die einzelne Bilddatei.
### `DELETE /api/routes/:id/pictures/:pictureId`
Entfernt Bilddatensatz und Datei.
```bash
curl -X DELETE http://127.0.0.1:47145/api/routes/1/pictures/15
```
## 8. Audio
```json
{
"id": 15,
"routeId": 1,
"poiId": 7,
"deleted": true
}
```
Pro POI existiert höchstens eine Audiodatei. Deshalb gibt es keine zusätzliche Audio-ID und keinen Pfad wie `/audio/:poiId`. Alle Operationen verwenden `/api/routes/:id/audio`; der konkrete POI wird mit `poiId` angegeben.
## 9. Audiodateien einzeln verwalten
### GET /api/routes/:id/audio
Pro POI ist höchstens eine Audiodatei vorgesehen. Deshalb bildet die `poiId` den Schlüssel der Audioressource.
Ohne `poiId` liefert der Endpunkt alle Audiometadaten der Route als JSON:
### `GET /api/routes/:id/audio`
Liefert alle vorhandenen Audiodateien der Route. POIs ohne Audio werden nicht ausgegeben.
#### Query-Parameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `poiId` | positive Ganzzahl | nein | schränkt die Liste auf einen POI ein |
```bash
curl http://127.0.0.1:47145/api/routes/1/audio
```
Beispielantwort:
```bash
curl --get http://127.0.0.1:47145/api/routes/1/audio \
--data-urlencode 'poiId=7'
```
```json
{
@@ -523,35 +586,47 @@ Beispielantwort:
"routeId": 1,
"poiId": 7,
"poiTitle": "Die alte Eiche",
"url": "/api/routes/1/audio?poiId=7",
"url": "/api/routes/1/audio/7",
"updatedAt": "2026-06-16 12:35:00"
}
]
}
```
Mit `poiId` liefert derselbe Endpunkt die Audiodatei binär:
### `GET /api/routes/:id/audio/:poiId`
| Query-Parameter | Typ | Pflicht für Dateiausgabe | Beschreibung |
|---|---|---:|---|
| `poiId` | positive Ganzzahl | ja | POI, dessen Audio geliefert wird |
Liefert standardmäßig die Audiodatei des POIs direkt aus. Genau dieser Pfad wird in `audioUrl` und im Feld `url` einer Audioressource ausgegeben. Der Endpunkt unterstützt HTTP-Range-Anfragen, damit Browser innerhalb einer Audiodatei springen können.
Audiodatei speichern:
```bash
curl --get http://127.0.0.1:47145/api/routes/1/audio \
--data-urlencode 'poiId=7' \
--output ansage.mp3
curl http://127.0.0.1:47145/api/routes/1/audio/7 \
--output ansage-7.mp3
```
Der Response-`Content-Type` entspricht dem gespeicherten Format, beispielsweise `audio/mpeg` oder `audio/ogg`.
Metadaten statt Dateidaten abrufen:
### POST /api/routes/:id/audio
```bash
curl --get http://127.0.0.1:47145/api/routes/1/audio/7 \
--data-urlencode 'metadata=true'
```
Legt die Audiodatei für einen POI an. Besteht bereits eine Datei, antwortet der Server mit `409`; zum Ersetzen wird `PUT` verwendet.
| Query-Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---:|---:|---|
| `metadata` | Boolean | nein | `false` | bei `true` JSON-Metadaten statt der Audiodatei liefern |
Antwortet mit `404`, wenn der POI keine Audiodatei besitzt.
### `POST /api/routes/:id/audio`
Legt die Audiodatei eines POIs an.
Content-Type: `multipart/form-data`
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `audio` | Datei | ja | Audiodatei |
| `poiId` | positive Ganzzahl | ja | zugehöriger POI |
| `audio` | Datei | ja | MP3-, MP4/M4A-, AAC-, Ogg-, WAV- oder WebM-Datei |
| `poiId` | positive Ganzzahl | ja | POI derselben Route |
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/audio \
@@ -559,82 +634,90 @@ curl -X POST http://127.0.0.1:47145/api/routes/1/audio \
-F 'audio=@ansage.mp3;type=audio/mpeg'
```
Der `Location`-Header lautet beispielsweise:
Erfolg: `201 Created` und `Location: /api/routes/1/audio/7`.
```text
/api/routes/1/audio?poiId=7
```
Existiert bereits eine Audiodatei, antwortet der Server mit `409`. Zum Ersetzen ist `PUT` zu verwenden.
### PUT /api/routes/:id/audio
### `PUT /api/routes/:id/audio/:poiId`
Ersetzt eine vorhandene Audiodatei. `poiId` darf als Query-Parameter oder als Multipart-Feld angegeben werden.
Ersetzt die vorhandene Audiodatei des POIs.
Query-Variante:
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---:|---|
| `audio` | Datei | ja | neue Audiodatei |
```bash
curl -X PUT 'http://127.0.0.1:47145/api/routes/1/audio?poiId=7' \
curl -X PUT http://127.0.0.1:47145/api/routes/1/audio/7 \
-F 'audio=@ansage-neu.ogg;type=audio/ogg'
```
Multipart-Variante:
Der Server entfernt die bisherige Datei nach erfolgreicher Aktualisierung. Besitzt der POI noch keine Audiodatei, antwortet der Server mit `404`; zum erstmaligen Anlegen ist `POST` zu verwenden.
### `DELETE /api/routes/:id/audio/:poiId`
Entfernt die Audiodatei und setzt `audioUrl` des POIs auf `null`.
```bash
curl -X PUT http://127.0.0.1:47145/api/routes/1/audio \
-F 'poiId=7' \
-F 'audio=@ansage-neu.ogg;type=audio/ogg'
curl -X DELETE http://127.0.0.1:47145/api/routes/1/audio/7
```
| Parameter | Ort | Typ | Pflicht | Beschreibung |
|---|---|---|---:|---|
| `poiId` | Query oder Formular | positive Ganzzahl | ja | POI |
| `audio` | Formular | Datei | ja | Ersatzdatei |
### DELETE /api/routes/:id/audio
Löscht die Audiodatei des angegebenen POIs und setzt `audioUrl` anschließend auf `null`.
Empfohlen als Query-Parameter:
```bash
curl -X DELETE 'http://127.0.0.1:47145/api/routes/1/audio?poiId=7'
```
Alternativ als JSON-Body:
```bash
curl -X DELETE http://127.0.0.1:47145/api/routes/1/audio \
-H 'Content-Type: application/json' \
-d '{ "poiId": 7 }'
```
## 9. Verwendung durch die Clientanwendung
Die Clientanwendung konstruiert Medienadressen ausschließlich über das API-Modul:
```javascript
Wegwichtel.Api.pictureUrl(routeId, pictureId);
// /api/routes/1/pictures/15
Wegwichtel.Api.audioUrl(routeId, poiId);
// /api/routes/1/audio?poiId=7
```
Die Diashow setzt die API-Bildadresse als `src` des Bildes. Der HTML5-Audioplayer setzt die API-Audioadresse als `src` und lädt sie mit `preload="auto"`. Das Erreichen eines POIs startet keine automatische Wiedergabe.
## 10. Nginx-Hinweis
Nginx sollte `/api/` vollständig an den Node.js-Prozess weiterleiten. Da Bilder und Audio über `/api` ausgeliefert werden, muss der Proxy binäre Responses unverändert durchreichen:
```nginx
location ^~ /api/ {
proxy_pass http://127.0.0.1:47145;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
```json
{
"routeId": 1,
"poiId": 7,
"deleted": true
}
```
Für Uploads muss außerdem `client_max_body_size` mindestens so groß wie `MAX_UPLOAD_MB` gewählt werden.
## 10. Route weich löschen und wiederherstellen
### `DELETE /api/routes/:id`
Markiert die Route als gelöscht und verschiebt das vollständige Streckenverzeichnis mit GPX, Bildern und Audio nach `storage/trash/routes/`. Die Datenbankeinträge bleiben erhalten und ihre Pfade werden auf den Papierkorb umgeschrieben.
```bash
curl -X DELETE http://127.0.0.1:47145/api/routes/1
```
```json
{
"route": {
"id": 1,
"status": "deleted",
"gpxUrl": null
},
"softDeleted": true
}
```
### `POST /api/routes/:id/restore`
Verschiebt eine gelöschte Route zurück in den aktiven Speicher und schreibt alle GPX-, Bild- und Audiopfade zurück.
Parameter: keine.
```bash
curl -X POST http://127.0.0.1:47145/api/routes/1/restore
```
## 11. Dateien über REST ausliefern
Die API veröffentlicht keine internen Speicherpfade und keine `/media/...`-Adressen. Alle in JSON ausgegebenen Datei-URLs verweisen auf stabile REST-Endpunkte:
```text
GET /api/routes/1/gpx
GET /api/routes/1/pictures/15
GET /api/routes/1/audio/7
```
Die Zuordnung lautet:
| JSON-Feld | Datei-Endpunkt |
|---|---|
| `route.gpxUrl` | `/api/routes/:id/gpx` |
| `poi.audioUrl` | `/api/routes/:id/audio/:poiId` |
| `poi.images[].url` | `/api/routes/:id/pictures/:pictureId` |
| `picture.url` | `/api/routes/:id/pictures/:pictureId` |
| `audio.url` | `/api/routes/:id/audio/:poiId` |
Die Dateinamen und relativen Pfade unter `storage/` bleiben ausschließlich interne Implementierungsdetails. Dateien gelöschter Routen liegen im Papierkorb und sind über keinen Datei-Endpunkt erreichbar.