Wegwichtel/docs/REST-API.md
Florian Zumpe 0def9b23db
All checks were successful
Sonarqube Scanner / Build and analyze (push) Successful in 1m6s
included serverside mime detection
2026-06-17 11:42:43 +02:00

27 KiB
Raw Permalink Blame History

Wegwichtel REST-API

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

Basisadressen

Lokaler Standard:

http://127.0.0.1:47145

API-Basis:

http://127.0.0.1:47145/api

Bei einer Nginx-Installation bleibt der Pfad gleich, beispielsweise:

https://wegwichtel.example.org/api

Formate

  • 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 application/json, application/x-www-form-urlencoded oder multipart/form-data.
  • GPX-Dateien werden ausschließlich als multipart/form-data übertragen.
  • Bild- und Audioendpunkte akzeptieren wahlweise multipart/form-data oder ein JSON-Objekt mit Base64-kodierten Dateidaten.
  • Der Server ermittelt den tatsächlichen MIME-Typ aus dem Dateiinhalt. Dateiname, Dateiendung, Multipart-Content-Type, JSON-Felder und Data-URL-Präfixe werden nicht als Typnachweis verwendet.
  • Für JSON wird Content-Type: application/json empfohlen. Content-Type: text/json wird aus Kompatibilitätsgründen ebenfalls akzeptiert.
  • Ein leeres JSON-Objekt ({}) enthält keine Datei und kann deshalb keinen Bild- oder Audio-Upload ausführen.
  • Pro Medien-Request wird genau eine Bild- beziehungsweise Audiodatei verarbeitet.
  • Base64 vergrößert die Requestgröße um ungefähr ein Drittel und benötigt beim Verarbeiten zusätzlichen Arbeitsspeicher. Für große Dateien ist multipart/form-data vorzuziehen.
  • Zeitstempel werden als SQLite- oder ISO-8601-Text ausgegeben.
  • Die Node.js-Anwendung besitzt keine eigene Authentifizierung. Ein vorgeschalteter Webserver kann Basic Auth erzwingen; die mitgelieferten Python-Werkzeuge erkennen 401 Unauthorized, fragen Zugangsdaten ab und wiederholen den Request.

Uploadgrenzen und Dateitypen

Das Dateilimit pro Datei wird durch MAX_UPLOAD_MB festgelegt und beträgt standardmäßig 50 MB.

Unterstützte Dateitypen:

Ressource Dateiendungen beziehungsweise MIME-Typen
GPX GPX-XML mit <gpx>-Wurzelelement
Bilder JPEG, PNG, WebP
Audio MP3, MP4/M4A, AAC, Ogg, WAV, WebM

Die Uploadfelder heißen:

Ressource Multipart-Feld beziehungsweise JSON-Eigenschaft
GPX gpx
einzelnes Bild picture
einzelne Audiodatei audio

Bei einem JSON-Medienupload ist picture beziehungsweise audio ein Objekt:

Eigenschaft Typ Pflicht Beschreibung
filename Text nein ursprünglicher Dateiname für Protokollierung; die Endung wird nicht zur Typbestimmung verwendet
base64 Text ja, sofern dataUrl fehlt reiner Base64-Inhalt ohne Präfix
dataUrl Text alternativ zu base64 vollständige Base64-Data-URL; der dort genannte Medientyp wird ignoriert

base64 und dataUrl sind Alternativen. Der Server erkennt Format und Speicherendung ausschließlich anhand der dekodierten Bytes. Ein vom Client mitgesendetes contentType- oder mimeType-Feld wird ignoriert und ist nicht erforderlich.

Inhaltsbasierte Dateityperkennung

Binäre Bild- und Audiodateien werden mit file-type anhand ihrer Magic Bytes analysiert. Für Audio-/Video-Container wird zusätzlich @file-type/av verwendet, damit beispielsweise M4A und WebM möglichst zuverlässig als Audio oder Video unterschieden werden. GPX ist ein textbasiertes XML-Format und wird deshalb separat als UTF-8 gelesen und anhand des <gpx>-Wurzelelements validiert; anschließend übernimmt der vorhandene GPX-Parser die fachliche Prüfung.

Daraus folgen zwei wichtige Regeln:

  • Eine als image/png deklarierte Textdatei wird mit 415 Unsupported Media Type abgewiesen.
  • Eine echte PNG-Datei wird auch dann akzeptiert, wenn sie datei.bin heißt oder im Multipart-Request kein Datei-Content-Type angegeben ist.

Fehlerformat

{
  "error": "HttpError",
  "message": "Strecke nicht gefunden."
}

Typische Statuscodes:

Status Bedeutung
200 Anfrage erfolgreich
201 Ressource wurde angelegt
400 Parameter oder Upload fehlt beziehungsweise ist ungültig
401 vorgeschalteter Webserver verlangt Authentifizierung
404 Route, POI oder Medienressource wurde nicht gefunden
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

Alle IDs sind positive, von SQLite erzeugte Ganzzahlen.

  • :id bezeichnet bei /routes/:id/... die Route.
  • :pictureId bezeichnet einen Datensatz aus poi_images.
  • :poiId bezeichnet den POI und zugleich seine höchstens eine Audioressource.

Beispiele verwenden überwiegend Route 1, POI 7 und Bild 15.

2. Datenmodelle

Route

{
  "id": 1,
  "slug": "schulwald-runde",
  "name": "Schulwald-Runde",
  "description": "Naturkundlicher Rundweg",
  "schoolName": "Beispielschule",
  "status": "active",
  "start": { "lat": 52.5208, "lon": 13.407 },
  "center": { "lat": 52.521, "lon": 13.408 },
  "bounds": {
    "minLat": 52.5208,
    "minLon": 13.407,
    "maxLat": 52.5212,
    "maxLon": 13.409
  },
  "distanceM": 842.6,
  "elevationGainM": 14.2,
  "pointCount": 87,
  "gpxUrl": "/api/routes/1/gpx",
  "createdAt": "2026-06-16 12:00:00",
  "updatedAt": "2026-06-16 12:00:00",
  "deletedAt": null,
  "proximityM": 324.8
}

POI

{
  "id": 7,
  "routeId": 1,
  "title": "Die alte Eiche",
  "description": "Hier wird das Alter der Eiche erklärt.",
  "lat": 52.5208,
  "lon": 13.407,
  "triggerRadiusM": 60,
  "sequence": 2,
  "audioUrl": "/api/routes/1/pois/7/audio",
  "images": [
    {
      "id": 15,
      "caption": "Blick auf die Baumkrone",
      "sequence": 0,
      "url": "/api/routes/1/pois/7/pictures/15"
    }
  ]
}

Bildressource

{
  "id": 15,
  "routeId": 1,
  "poiId": 7,
  "poiTitle": "Die alte Eiche",
  "caption": "Blick auf die Baumkrone",
  "sequence": 0,
  "url": "/api/routes/1/pois/7/pictures/15",
  "createdAt": "2026-06-16 12:30:00"
}

Audioressource

Pro POI kann höchstens eine Audiodatei existieren. Deshalb wird die Audioressource über die poiId adressiert.

{
  "routeId": 1,
  "poiId": 7,
  "poiTitle": "Die alte Eiche",
  "url": "/api/routes/1/pois/7/audio",
  "updatedAt": "2026-06-16 12:35:00"
}

3. Endpunktübersicht

Methode Pfad Zweck
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
DELETE /api/routes/:id Route weich löschen
POST /api/routes/:id/restore Route wiederherstellen
GET /api/routes/:id/pois POIs einer Route auflisten
GET /api/routes/:routeId/pois/:poiId einzelnen POI der Route lesen
POST /api/routes/:id/pois POI-Metadaten anlegen
PUT /api/routes/:routeId/pois/:poiId POI-Metadaten aktualisieren
DELETE /api/routes/:routeId/pois/:poiId POI einschließlich Bildern und Audio löschen
GET /api/routes/:routeId/pois/:poiId/pictures Bilder eines POIs auflisten
GET /api/routes/:routeId/pois/:poiId/pictures/:pictureId Bilddatei ausliefern; optional Metadaten mit ?metadata=true
POST /api/routes/:routeId/pois/:poiId/pictures einzelnes Bild für den POI hochladen
PUT /api/routes/:routeId/pois/:poiId/pictures/:pictureId Bilddatei oder Metadaten aktualisieren
DELETE /api/routes/:routeId/pois/:poiId/pictures/:pictureId einzelnes Bild löschen
GET /api/routes/:routeId/pois/:poiId/audio Audiodatei ausliefern; optional Metadaten mit ?metadata=true
POST /api/routes/:routeId/pois/:poiId/audio Audiodatei für den POI anlegen
PUT /api/routes/:routeId/pois/:poiId/audio Audiodatei des POIs ersetzen
DELETE /api/routes/:routeId/pois/:poiId/audio Audiodatei des POIs löschen

4. Systemzustand

GET /api/health

Parameter: keine.

curl http://127.0.0.1:47145/api/health
{
  "ok": true,
  "service": "wegwichtel",
  "socket": "127.0.0.1:47145",
  "sqliteVersion": "3.46.1",
  "timestamp": "2026-06-16T12:00:00.000Z"
}

5. Routen lesen

GET /api/routes

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

Parameter Typ Pflicht Standard Beschreibung
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:

curl http://127.0.0.1:47145/api/routes

Mit allen Query-Parametern:

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'

GET /api/routes/:id

Liefert Route, GPX-Punkte und POIs einschließlich Medien-URLs.

Pfadparameter

Parameter Typ Pflicht Beschreibung
id Ganzzahl ja Route

Query-Parameter

Parameter Typ Pflicht Standard Beschreibung
includeDeleted Boolean-Text nein false mit true kann eine gelöschte Route gelesen werden
curl http://127.0.0.1:47145/api/routes/1
curl 'http://127.0.0.1:47145/api/routes/1?includeDeleted=true'

GET /api/routes/:id/gpx

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.

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; der Dateiname ist unerheblich
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
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'

Erfolg: 201 Created und Location: /api/routes/<id>.

PUT /api/routes/:id

Aktualisiert Metadaten. Eine optionale GPX-Datei ersetzt alle bisherigen Trackpunkte.

Feld Typ Pflicht Verhalten ohne Feld
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
curl -X PUT http://127.0.0.1:47145/api/routes/1 \
  -F 'name=Schulwald-Runde 2026' \
  -F 'description=Überarbeitete Strecke' \
  -F 'schoolName=Beispielschule' \
  -F 'gpx=@route-neu.gpx'

POST /api/routes/:id/append

Hängt alle Trackpunkte einer GPX-Datei an die Route an und berechnet Streckenwerte neu.

Feld Typ Pflicht Beschreibung
gpx Datei ja anzuhängende GPX-Datei
curl -X POST http://127.0.0.1:47145/api/routes/1/append \
  -F 'gpx=@verlaengerung.gpx'

7. POIs lesen und bearbeiten

GET /api/routes/:id/pois

Liefert alle POIs einer aktiven Route nach sequence und id.

curl http://127.0.0.1:47145/api/routes/1/pois

GET /api/routes/:routeId/pois/:poiId

Liefert einen einzelnen POI einschließlich seiner aktuellen Bild- und Audio-URLs. Route und POI werden gemeinsam geprüft; der POI muss zur angegebenen aktiven Route gehören.

Pfadparameter Typ Pflicht Beschreibung
routeId Ganzzahl ja ID der aktiven Route
poiId Ganzzahl ja ID des POIs innerhalb dieser Route
curl http://127.0.0.1:47145/api/routes/1/pois/7

Eine unbekannte Route, ein unbekannter POI oder eine falsche Route-POI-Kombination liefert 404 Not Found.

POST /api/routes/:id/pois

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 Stationsname
description Text nein leer Beschreibung
lat Dezimalzahl ja Breitengrad
lon Dezimalzahl ja Längengrad
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:

curl -X POST http://127.0.0.1:47145/api/routes/1/pois \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Die alte Eiche",
    "description": "Hier wird das Alter der Eiche erklärt.",
    "lat": 52.5208,
    "lon": 13.4070,
    "triggerRadiusM": 60,
    "sequence": 2
  }'

Erfolg: 201 Created und Location: /api/routes/<routeId>/pois/<poiId>.

PUT /api/routes/:routeId/pois/:poiId

Aktualisiert ausschließlich POI-Metadaten. Nicht übergebene Werte bleiben erhalten. Route und POI werden gemeinsam validiert; ein POI kann über diesen Endpunkt keiner anderen Route zugeordnet werden.

Pfadparameter Typ Pflicht Beschreibung
routeId Ganzzahl ja ID der aktiven Route
poiId Ganzzahl ja ID des POIs innerhalb dieser Route
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
curl -X PUT http://127.0.0.1:47145/api/routes/1/pois/7 \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Die sehr alte Eiche",
    "description": "Aktualisierte Beschreibung",
    "lat": 52.5209,
    "lon": 13.4071,
    "triggerRadiusM": 45,
    "sequence": 3
  }'

DELETE /api/routes/:routeId/pois/:poiId

Löscht den POI-Datensatz sowie alle zugehörigen Bilddatensätze, Bilddateien und die optionale Audiodatei. Dieser Vorgang ist im Gegensatz zum Soft Delete einer vollständigen Route nicht wiederherstellbar.

curl -X DELETE http://127.0.0.1:47145/api/routes/1/pois/7
{
  "id": 7,
  "routeId": 1,
  "deleted": true
}

8. Bilder eines POIs einzeln verwalten

Die Route und der POI sind Bestandteil jedes Bildpfades. Dadurch ist die Zuordnung eindeutig und beim Upload muss keine zusätzliche poiId übergeben werden.

Gemeinsame Pfadparameter

Parameter Typ Pflicht Beschreibung
routeId positive Ganzzahl ja ID der aktiven Route
poiId positive Ganzzahl ja ID eines POIs, der zu dieser Route gehört
pictureId positive Ganzzahl nur bei Einzelressourcen ID des Bildes, das zu diesem POI gehört

GET /api/routes/:routeId/pois/:poiId/pictures

Liefert alle Bilder des angegebenen POIs in Diashow-Reihenfolge.

curl http://127.0.0.1:47145/api/routes/1/pois/7/pictures
{
  "pictures": [
    {
      "id": 15,
      "routeId": 1,
      "poiId": 7,
      "poiTitle": "Die alte Eiche",
      "caption": "Blick auf die Baumkrone",
      "sequence": 0,
      "url": "/api/routes/1/pois/7/pictures/15",
      "createdAt": "2026-06-16 12:30:00"
    }
  ]
}

GET /api/routes/:routeId/pois/:poiId/pictures/:pictureId

Liefert standardmäßig die Bilddatei direkt aus. Genau dieser Pfad wird im Feld url der Bildressource und unter pois[].images[].url ausgegeben.

Bild speichern:

curl http://127.0.0.1:47145/api/routes/1/pois/7/pictures/15 \
  --output bild-15.jpg

Metadaten statt Dateidaten abrufen:

curl --get http://127.0.0.1:47145/api/routes/1/pois/7/pictures/15 \
  --data-urlencode 'metadata=true'
Query-Parameter Typ Pflicht Standard Beschreibung
metadata Boolean nein false bei true JSON-Metadaten statt der Bilddatei liefern

Die API antwortet mit 404, wenn Route, POI oder Bild nicht zusammengehören.

POST /api/routes/:routeId/pois/:poiId/pictures

Lädt genau ein Bild für den im Pfad angegebenen POI hoch. Zulässig sind zwei Übertragungsformen.

Variante A: multipart/form-data

Feld Typ Pflicht Standard Beschreibung
picture Datei ja JPEG-, PNG- oder WebP-Datei; der Typ wird aus dem Inhalt erkannt
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
curl -X POST http://127.0.0.1:47145/api/routes/1/pois/7/pictures \
  -F 'caption=Blick auf die Baumkrone' \
  -F 'sequence=0' \
  -F 'picture=@eiche.jpg'

Variante B: JSON mit Base64

Empfohlener Content-Type: application/json. Der Server akzeptiert zusätzlich text/json.

{
  "caption": "Blick auf die Baumkrone",
  "sequence": 0,
  "picture": {
    "filename": "eiche.jpg",
    "base64": "/9j/4AAQSkZJRgABAQ..."
  }
}

Beispiel mit text/json und einer separat erzeugten Payload-Datei:

base64 < eiche.jpg | tr -d '\n' > eiche.jpg.b64

jq -n \
  --arg caption 'Blick auf die Baumkrone' \
  --argjson sequence 0 \
  --rawfile data eiche.jpg.b64 \
  '{
    caption: $caption,
    sequence: $sequence,
    picture: {
      filename: "eiche.jpg",
      base64: $data
    }
  }' > picture.json

curl -X POST http://127.0.0.1:47145/api/routes/1/pois/7/pictures \
  -H 'Content-Type: text/json' \
  --data-binary @picture.json

Alternativ kann eine Data-URL übertragen werden:

{
  "caption": "Blick auf die Baumkrone",
  "picture": {
    "filename": "eiche.png",
    "dataUrl": "data:image/png;base64,iVBORw0KGgoAAA..."
  }
}

Ein Request mit -d '{}' schlägt mit 400 Bad Request fehl, weil weder eine Multipart-Datei noch ein JSON-Dateiobjekt enthalten ist.

Erfolg: 201 Created und Location: /api/routes/1/pois/7/pictures/<pictureId>.

PUT /api/routes/:routeId/pois/:poiId/pictures/:pictureId

Aktualisiert Metadaten und kann optional die Datei ersetzen. Nicht übergebene Metadaten bleiben erhalten. Ein Bild kann über diesen Endpunkt nicht einem anderen POI zugeordnet werden; dafür muss es beim bisherigen POI gelöscht und beim Ziel-POI neu angelegt werden.

Feld Typ Pflicht Beschreibung
picture Multipart-Datei oder JSON-Dateiobjekt nein ersetzt die bisherige Bilddatei
caption Text nein neue Bildbeschreibung; leerer Text entfernt die Beschreibung
sequence nichtnegative Ganzzahl nein neue Position in der Diashow

Nur Metadaten per JSON ändern:

curl -X PUT http://127.0.0.1:47145/api/routes/1/pois/7/pictures/15 \
  -H 'Content-Type: application/json' \
  -d '{
    "caption": "Nahaufnahme der Eichenblätter",
    "sequence": 1
  }'

Datei und Metadaten per Multipart ersetzen:

curl -X PUT http://127.0.0.1:47145/api/routes/1/pois/7/pictures/15 \
  -F 'caption=Neue Aufnahme der Eiche' \
  -F 'sequence=2' \
  -F 'picture=@eiche-neu.webp'

Datei und Metadaten per JSON ersetzen:

{
  "caption": "Neue Aufnahme der Eiche",
  "sequence": 2,
  "picture": {
    "filename": "eiche-neu.webp",
    "base64": "UklGRiQAAABXRUJQVlA4..."
  }
}
curl -X PUT http://127.0.0.1:47145/api/routes/1/pois/7/pictures/15 \
  -H 'Content-Type: application/json' \
  --data-binary @picture-update.json

Wird eine Datei ersetzt, entfernt der Server die bisherige Datei nach erfolgreicher Datenbankaktualisierung.

DELETE /api/routes/:routeId/pois/:poiId/pictures/:pictureId

Entfernt Bilddatensatz und Datei.

curl -X DELETE http://127.0.0.1:47145/api/routes/1/pois/7/pictures/15
{
  "id": 15,
  "routeId": 1,
  "poiId": 7,
  "deleted": true
}

9. Audiodatei eines POIs verwalten

Pro POI ist höchstens eine Audiodatei vorgesehen. Daher ist /audio selbst die Einzelressource; eine zusätzliche Audio-ID ist nicht erforderlich.

Gemeinsame Pfadparameter

Parameter Typ Pflicht Beschreibung
routeId positive Ganzzahl ja ID der aktiven Route
poiId positive Ganzzahl ja ID eines POIs, der zu dieser Route gehört

GET /api/routes/:routeId/pois/:poiId/audio

Liefert standardmäßig die Audiodatei des POIs direkt aus. Genau dieser Pfad wird in audioUrl und im Feld url der Audioressource ausgegeben. Der Endpunkt unterstützt HTTP-Range-Anfragen, damit Browser innerhalb der Audiodatei springen können.

Audiodatei speichern:

curl http://127.0.0.1:47145/api/routes/1/pois/7/audio \
  --output ansage-7.mp3

Metadaten statt Dateidaten abrufen:

curl --get http://127.0.0.1:47145/api/routes/1/pois/7/audio \
  --data-urlencode 'metadata=true'
Query-Parameter Typ Pflicht Standard Beschreibung
metadata Boolean nein false bei true JSON-Metadaten statt der Audiodatei liefern

Antwortet mit 404, wenn Route und POI nicht zusammengehören oder der POI keine Audiodatei besitzt.

POST /api/routes/:routeId/pois/:poiId/audio

Legt die Audiodatei des im Pfad angegebenen POIs an. Zulässig sind multipart/form-data und JSON mit Base64.

Variante A: multipart/form-data

Feld Typ Pflicht Beschreibung
audio Datei ja MP3-, MP4/M4A-, AAC-, Ogg-, WAV- oder WebM-Datei; der Typ wird aus dem Inhalt erkannt
curl -X POST http://127.0.0.1:47145/api/routes/1/pois/7/audio \
  -F 'audio=@ansage.mp3'

Variante B: JSON mit Base64

{
  "audio": {
    "filename": "ansage.mp3",
    "base64": "SUQzBAAAAAAAI1RTU0UAAA..."
  }
}
base64 < ansage.mp3 | tr -d '\n' > ansage.mp3.b64

jq -n --rawfile data ansage.mp3.b64 \
  '{
    audio: {
      filename: "ansage.mp3",
      base64: $data
    }
  }' > audio.json

curl -X POST http://127.0.0.1:47145/api/routes/1/pois/7/audio \
  -H 'Content-Type: application/json' \
  --data-binary @audio.json

Auch hier wird Content-Type: text/json akzeptiert. Ein leeres {} enthält keine Audiodatei und liefert 400 Bad Request.

Erfolg: 201 Created und Location: /api/routes/1/pois/7/audio.

Existiert bereits eine Audiodatei, antwortet der Server mit 409. Zum Ersetzen ist PUT zu verwenden.

PUT /api/routes/:routeId/pois/:poiId/audio

Ersetzt die vorhandene Audiodatei des POIs. Die Datei kann als Multipart-Upload oder als JSON-Dateiobjekt übertragen werden.

Feld Typ Pflicht Beschreibung
audio Multipart-Datei oder JSON-Dateiobjekt ja neue Audiodatei

Multipart-Beispiel:

curl -X PUT http://127.0.0.1:47145/api/routes/1/pois/7/audio \
  -F 'audio=@ansage-neu.ogg'

JSON-Beispiel:

{
  "audio": {
    "filename": "ansage-neu.ogg",
    "base64": "T2dnUwACAAAAAAAAAAB..."
  }
}
curl -X PUT http://127.0.0.1:47145/api/routes/1/pois/7/audio \
  -H 'Content-Type: text/json' \
  --data-binary @audio-update.json

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/:routeId/pois/:poiId/audio

Entfernt die Audiodatei und setzt audioUrl des POIs auf null.

curl -X DELETE http://127.0.0.1:47145/api/routes/1/pois/7/audio
{
  "routeId": 1,
  "poiId": 7,
  "deleted": true
}

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.

curl -X DELETE http://127.0.0.1:47145/api/routes/1
{
  "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.

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:

GET /api/routes/1/gpx
GET /api/routes/1/pois/7/pictures/15
GET /api/routes/1/pois/7/audio

Die Zuordnung lautet:

JSON-Feld Datei-Endpunkt
route.gpxUrl /api/routes/:id/gpx
poi.audioUrl /api/routes/:routeId/pois/:poiId/audio
poi.images[].url /api/routes/:routeId/pois/:poiId/pictures/:pictureId
picture.url /api/routes/:routeId/pois/:poiId/pictures/:pictureId
audio.url /api/routes/:routeId/pois/:poiId/audio

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.

12. Python-Werkzeuge

Unter tools/python/ liegen interaktive Skripte für Anlegen, Ändern, Erweitern, Wiederherstellen und Löschen von Routen, POIs, Bildern und Audio. Sie verwenden ausschließlich die Python-Standardbibliothek. Fehlende Parameter werden abgefragt. Antwortet ein vorgeschalteter Webserver mit HTTP 401, fragt die gemeinsame Request-Schicht Benutzername und Passwort ab und wiederholt den ursprünglichen Request.

Details und Aufrufbeispiele stehen in tools/python/README.md.