Reworked pause and stop buttons and documentation
This commit is contained in:
parent
4529749baa
commit
40f230864b
45
README.md
45
README.md
@ -68,44 +68,9 @@ public/vendor/jquery-ui/images/*.png
|
||||
|
||||
Die Anwendung verwendet aus jQuery UI insbesondere die Widgets **Button** und **Controlgroup**. Das offizielle vollständige jQuery-UI-Bundle bleibt lokal verfügbar, damit weitere aktuelle Widgets ohne erneuten CDN-Bezug ergänzt werden können. Die frühere jQuery-Mobile-Seitensteuerung wurde durch eine eigene, History-API-basierte Navigation ersetzt.
|
||||
|
||||
## REST-API
|
||||
## API-Dokumentation
|
||||
|
||||
| Methode | Pfad | Zweck |
|
||||
|---|---|---|
|
||||
| GET | `/api/health` | Server- und SQLite-Selbsttest |
|
||||
| GET | `/api/routes?lat=…&lon=…&radiusKm=25` | aktive, optional nahe Routen |
|
||||
| GET | `/api/routes/:id` | Route, Trackpunkte, POIs und Medien-URLs |
|
||||
| GET | `/api/routes/:id/pois` | POIs einer Route |
|
||||
| GET | `/api/pois/:id` | einzelner POI samt Bild- und Audio-URLs |
|
||||
| POST | `/api/routes` | neue Route; `multipart/form-data`, Feld `gpx` |
|
||||
| PUT | `/api/routes/:id` | Metadaten und optional GPX vollständig ersetzen |
|
||||
| POST | `/api/routes/:id/append` | zusätzliche GPX-Trackpunkte anhängen |
|
||||
| POST | `/api/routes/:id/pois` | POI anlegen; Felder `audio` und `images` |
|
||||
| PUT | `/api/pois/:id` | POI ändern und weitere Medien ergänzen |
|
||||
| DELETE | `/api/routes/:id` | Route samt Medien in den Papierkorb verschieben |
|
||||
| POST | `/api/routes/:id/restore` | Route aus dem Papierkorb wiederherstellen |
|
||||
|
||||
### Route anlegen
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:47145/api/routes \
|
||||
-F 'name=Schulwald-Runde' \
|
||||
-F 'schoolName=Beispielschule' \
|
||||
-F 'description=Naturkundlicher Rundweg' \
|
||||
-F 'gpx=@examples/sample-route.gpx;type=application/gpx+xml'
|
||||
```
|
||||
|
||||
### POI mit Bildern und Audio anlegen
|
||||
|
||||
```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 'audio=@ansage.mp3;type=audio/mpeg' \
|
||||
-F 'images=@eiche-1.jpg;type=image/jpeg' \
|
||||
-F 'images=@eiche-2.jpg;type=image/jpeg'
|
||||
```
|
||||
Die vollständige REST-API ist einschließlich aller Pfad-, Query-, Formular- und Datei-Parameter sowie ausführlicher `curl`-Beispiele in [`docs/REST-API.md`](docs/REST-API.md) dokumentiert. Die README beschreibt bewusst nur Installation, Architektur und Bedienverhalten.
|
||||
|
||||
## Dateistruktur
|
||||
|
||||
@ -171,8 +136,10 @@ Beim Start der Route wird der aktuellen Position nächstgelegene GPX-Trackpunkt
|
||||
Die Routenansicht besitzt drei nebeneinanderliegende, semantische Schaltflächen mit lokalen SVG-Symbolen:
|
||||
|
||||
- **Route starten beziehungsweise fortsetzen:** aktiviert `navigator.geolocation.watchPosition()` und die Geräteausrichtung.
|
||||
- **Route pausieren:** beendet den aktiven GPS-Watcher und den Kompass-Listener, behält aber Routenfortschritt und bereits ausgelöste POIs bei.
|
||||
- **Route beenden:** beendet alle Sensor-Listener, setzt den Fortschritt zurück und leert die Liste bereits ausgelöster POIs.
|
||||
- **Route pausieren:** beendet den aktiven GPS-Watcher und den Kompass-Listener, pausiert eine laufende Audioansage und behält Routenfortschritt, Audioquelle, Abspielposition und bereits ausgelöste POIs bei.
|
||||
- **Route beenden:** beendet alle Sensor-Listener, hält die Audioansage an, entlädt ihre Quelldatei, setzt den Fortschritt zurück und kehrt zur Routenauswahl zurück.
|
||||
|
||||
Die aktuell geöffnete beziehungsweise automatisch ausgelöste Station wird in der Stationsliste sichtbar hervorgehoben und mit `aria-current="step"` semantisch gekennzeichnet.
|
||||
|
||||
Während die Route läuft, werden Positionsänderungen fortlaufend verarbeitet. Eine Browser-Webanwendung ist jedoch kein nativer Hintergrunddienst: Betriebssystem und Browser können die Aktualisierung bei gesperrtem Bildschirm, Energiesparmodus oder im Hintergrund drosseln beziehungsweise anhalten.
|
||||
|
||||
|
||||
636
docs/REST-API.md
Normal file
636
docs/REST-API.md
Normal file
@ -0,0 +1,636 @@
|
||||
# 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 |
|
||||
@ -219,6 +219,19 @@ button,
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
#poi-list button[aria-current="step"] {
|
||||
border-color: var(--primary);
|
||||
background: #e7f1e9;
|
||||
box-shadow: inset .35rem 0 0 var(--primary), 0 .12rem .35rem rgba(0, 0, 0, .08);
|
||||
}
|
||||
|
||||
#poi-list button[aria-current="step"] > strong::after {
|
||||
content: " · aktuelle Station";
|
||||
color: var(--primary-dark);
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.route-list > li > p {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
|
||||
@ -118,7 +118,7 @@
|
||||
</div>
|
||||
|
||||
<p id="poi-description"></p>
|
||||
<audio id="poi-audio" controls preload="metadata"></audio>
|
||||
<audio id="poi-audio" controls preload="metadata" hidden></audio>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
routeState: 'idle',
|
||||
activePage: null,
|
||||
routeProgressIndex: null,
|
||||
activePoiId: null,
|
||||
deviceHeading: null
|
||||
};
|
||||
|
||||
@ -169,21 +170,36 @@
|
||||
$('#stop-route').prop('disabled', state.routeState === 'idle');
|
||||
}
|
||||
|
||||
function endRoute({ announce = true } = {}) {
|
||||
function setActivePoi(poiId) {
|
||||
state.activePoiId = poiId == null ? null : Number(poiId);
|
||||
$('#poi-list [data-poi-id]').removeAttr('aria-current');
|
||||
if (state.activePoiId != null) {
|
||||
$(`#poi-list [data-poi-id="${state.activePoiId}"]`).attr('aria-current', 'step');
|
||||
}
|
||||
}
|
||||
|
||||
function endRoute({ announce = true, returnToRoutes = false } = {}) {
|
||||
const wasActive = state.routeState !== 'idle';
|
||||
state.routeState = 'idle';
|
||||
state.routeProgressIndex = null;
|
||||
state.triggered.clear();
|
||||
stopRouteSensors();
|
||||
ns.AudioPlayer.unload();
|
||||
setActivePoi(null);
|
||||
hideNavigation();
|
||||
|
||||
if (announce && wasActive) {
|
||||
if (announce && wasActive && !returnToRoutes) {
|
||||
$('#tracking-status').prop('hidden', false).text('Route beendet.');
|
||||
} else {
|
||||
$('#tracking-status').prop('hidden', true).text('');
|
||||
}
|
||||
|
||||
updateRouteControls();
|
||||
|
||||
if (returnToRoutes) {
|
||||
state.route = null;
|
||||
navigate('#routes-page', { replace: true });
|
||||
}
|
||||
}
|
||||
|
||||
function pauseRoute() {
|
||||
@ -191,6 +207,7 @@
|
||||
|
||||
state.routeState = 'paused';
|
||||
stopRouteSensors();
|
||||
ns.AudioPlayer.pause();
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
.text('Route pausiert. Die Standortverfolgung ist angehalten.');
|
||||
@ -204,6 +221,7 @@
|
||||
state.route = route;
|
||||
state.triggered.clear();
|
||||
state.routeProgressIndex = null;
|
||||
setActivePoi(null);
|
||||
$('#route-title').text(route.name);
|
||||
$('#route-description').text(route.description || '');
|
||||
$('#route-distance').text(ns.Distance.format(route.distanceM));
|
||||
@ -231,6 +249,7 @@
|
||||
}
|
||||
|
||||
function showPoi(poi, automatic) {
|
||||
setActivePoi(poi.id);
|
||||
$('#poi-title').text(poi.title);
|
||||
$('#poi-description').text(poi.description || '');
|
||||
ns.Slideshow.show(poi.images);
|
||||
@ -425,7 +444,7 @@
|
||||
});
|
||||
|
||||
activate('#stop-route', function () {
|
||||
endRoute();
|
||||
endRoute({ returnToRoutes: true });
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', event => {
|
||||
|
||||
@ -1,11 +1,39 @@
|
||||
(function (ns, $) {
|
||||
'use strict';
|
||||
|
||||
function element() {
|
||||
return $('#poi-audio')[0];
|
||||
}
|
||||
|
||||
function unload() {
|
||||
const audio = element();
|
||||
audio.pause();
|
||||
audio.removeAttribute('src');
|
||||
audio.load();
|
||||
$(audio).prop('hidden', true);
|
||||
}
|
||||
|
||||
ns.AudioPlayer = {
|
||||
load(url) {
|
||||
const audio = $('#poi-audio');
|
||||
audio[0].pause(); audio.attr('src', url || '').toggle(Boolean(url));
|
||||
if (url) audio[0].load();
|
||||
unload();
|
||||
if (!url) return;
|
||||
|
||||
const audio = element();
|
||||
audio.src = url;
|
||||
$(audio).prop('hidden', false);
|
||||
audio.load();
|
||||
},
|
||||
play() { const audio = $('#poi-audio')[0]; if (audio.src) return audio.play(); }
|
||||
|
||||
play() {
|
||||
const audio = element();
|
||||
if (audio.getAttribute('src')) return audio.play();
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
pause() {
|
||||
element().pause();
|
||||
},
|
||||
|
||||
unload
|
||||
};
|
||||
}(window.Wegwichtel, window.jQuery));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user