Add arc42 architecture documentation in doc/architecture.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
554
doc/architecture.md
Normal file
554
doc/architecture.md
Normal file
@@ -0,0 +1,554 @@
|
||||
# Pamietnik — Architekturdokumentation (arc42)
|
||||
|
||||
## 1. Einführung und Ziele
|
||||
|
||||
**Pamietnik** (Codename RALPH) ist ein persönliches Lebens- und Reisejournal bestehend aus drei Komponenten: einer Android-App, einer Web-App und einem Go-Backend-Server.
|
||||
|
||||
### Fachliches Zielbild
|
||||
|
||||
- Android-App loggt GPS-Standortdaten im Hintergrund, cached offline, lädt bevorzugt per HTTP zum Server hoch
|
||||
- Manuelles Hinzufügen von Punkten (ohne GPS) mit optionaler Notiz und Hashtags
|
||||
- Server erkennt aus Logdaten „Stops" (längere Aufenthalte) und leitet Vorschläge ab
|
||||
- Standortinformationen (Reverse-Geocoding) über kostenlose API (Nominatim/OSM)
|
||||
- Web-Ansicht nach Login: Tagesübersicht und Tagesdetail mit Karte, Stops, Vorschlägen
|
||||
- Datei-Export aus der App (Android SAF)
|
||||
- Nutzung erfordert Account und Login
|
||||
|
||||
### Qualitätsziele
|
||||
|
||||
| Priorität | Ziel | Maßnahme |
|
||||
|-----------|------|----------|
|
||||
| 1 | Datensicherheit | Argon2id Passwort-Hashing, Session-Auth, HTTPS |
|
||||
| 2 | Offline-Fähigkeit | Room + WorkManager auf Android, kein RAM-only Queue |
|
||||
| 3 | Idempotenz | `event_id` (UUID) als Unique Key pro Device |
|
||||
| 4 | Erweiterbarkeit | Geocoding-Provider über Config austauschbar |
|
||||
| 5 | Datenschutz | Background-Location Disclosure (Google Play) |
|
||||
|
||||
### Stakeholder
|
||||
|
||||
| Rolle | Erwartung |
|
||||
|-------|-----------|
|
||||
| Mobiler Nutzer | Automatisches Tracking, Offline-Betrieb, Export |
|
||||
| Web-Nutzer | Login, Tagesübersicht mit Karte, Stops und Vorschläge |
|
||||
|
||||
---
|
||||
|
||||
## 2. Randbedingungen
|
||||
|
||||
| Art | Bedingung |
|
||||
|-----|-----------|
|
||||
| Technisch | Android: Kotlin + Jetpack Compose (DEC-CLIENT-01) |
|
||||
| Technisch | Backend: Go, PostgreSQL (DEC-DB-01) |
|
||||
| Technisch | Webapp: Vanilla TypeScript, keine Frameworks |
|
||||
| Technisch | Server-Schnittstelle: HTTP/REST, kein MQTT (DEC-API-01) |
|
||||
| Technisch | Karten: OpenStreetMap + MapLibre, konfigurierbare Tile-Quelle (DEC-MAP-01) |
|
||||
| Technisch | API-Spec: OpenAPI 3.1 als `openapi.yaml` (DEC-OPENAPI-01) |
|
||||
| Organisatorisch | Keine kommerziell lizenzierten Bibliotheken oder Schriften |
|
||||
| Organisatorisch | Geocoding über kostenlose API, Policy-konform (DEC-GEO-01) |
|
||||
| Organisatorisch | Alle Anforderungen in Markdown; Diagramme in Mermaid (DEC-DOC-01) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Kontextsicht
|
||||
|
||||
### Fachlicher Kontext
|
||||
|
||||
| Kommunikationspartner | Interaktion |
|
||||
|-----------------------|-------------|
|
||||
| Benutzer mobil | Start/Stop Trip, Status, Export, manuelle Punkte, Hashtags |
|
||||
| Benutzer web | Login, Tagesübersicht, Tagesdetail, Stops, Vorschläge, Tag-Filter |
|
||||
| Android OS / Location Services | Liefert GPS-Standortupdates |
|
||||
| Webserver | Nimmt Trackpoints entgegen, berechnet Stops, stellt Website bereit |
|
||||
| Geocoding-Dienst (Nominatim) | Liefert Adress-/Place-Infos zu Koordinaten |
|
||||
| Dateisystem via SAF | Export-Ziel für Logs/Trips |
|
||||
|
||||
### Technischer Kontext
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UserM[Nutzer mobil]
|
||||
UserW[Nutzer Web]
|
||||
App[Android App\nKotlin + Compose]
|
||||
OS[Android Location Services]
|
||||
Webapp[Webapp\nTS + nginx :9050]
|
||||
API[Go Backend\nREST + Web UI]
|
||||
DB[(PostgreSQL)]
|
||||
GEO[Nominatim\nReverse-Geocoding]
|
||||
SAF[Android SAF\nDatei-Export]
|
||||
|
||||
UserM -->|Start/Stop\nManuelle Punkte\nHashtags| App
|
||||
OS -->|GPS Updates| App
|
||||
App -->|HTTPS REST JSON\nPOST /v1/trackpoints:batch| API
|
||||
UserW -->|HTTPS Browser| Webapp
|
||||
Webapp -->|/v1/* proxy_pass| API
|
||||
API --> DB
|
||||
API -->|HTTPS cached| GEO
|
||||
App -->|ACTION_CREATE_DOCUMENT| SAF
|
||||
```
|
||||
|
||||
| Schnittstelle | Protokoll | Auth |
|
||||
|---------------|-----------|------|
|
||||
| Android → Backend | HTTPS REST JSON | Session Cookie (TBD: API-Key/JWT) |
|
||||
| Browser → Backend | HTTPS | Session Cookie |
|
||||
| Backend → Nominatim | HTTPS | — (Rate-Limit, gecached) |
|
||||
| App → Dateisystem | Android SAF | OS-Permission |
|
||||
|
||||
---
|
||||
|
||||
## 4. Lösungsstrategie
|
||||
|
||||
| Entscheidung | ID | Begründung |
|
||||
|---|---|---|
|
||||
| Android: Kotlin + Jetpack Compose | DEC-CLIENT-01 | Android „Kotlin-first", moderne UI |
|
||||
| Go Single Binary als Backend | — | Geringe Ressourcen, einfaches Deployment |
|
||||
| Distroless Docker Image | — | Minimale Angriffsfläche |
|
||||
| Schema via `go:embed` + `CREATE TABLE IF NOT EXISTS` | DEC-SCHEMA-01 | Keine Migrations-Infrastruktur, idempotenter Start |
|
||||
| Room (SQLite) auf Android | DEC-DB-01 | Offline-First Persistenz |
|
||||
| PostgreSQL im Backend | DEC-DB-01 | JSONB, UUID-Funktionen, zuverlässig |
|
||||
| Session Cookie statt JWT (Web) | DEC-AUTH-01 | Serverseitige Invalidierung, einfacheres Threat Model |
|
||||
| Argon2id für Passwort-Hashing | REQ-AUTH-01 | GPU-resistent, State-of-the-art |
|
||||
| MapLibre + OpenStreetMap | DEC-MAP-01 | Open Source, keine Lizenzkosten, konfigurierbare Tiles |
|
||||
| Nominatim als Geocoding-Provider | DEC-GEO-01 | Kostenlos, OSM-basiert, Provider austauschbar |
|
||||
| Vanilla Web Components | — | Kein Framework-Overhead, langlebig, keine Lizenzkonflikte |
|
||||
| OpenAPI 3.1 als API-Spec | DEC-OPENAPI-01 | Maschinenlesbar, Validierbar in CI |
|
||||
|
||||
---
|
||||
|
||||
## 5. Bausteinsicht
|
||||
|
||||
### Ebene 1 — Systeme
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Mobile[Android App — app/]
|
||||
UI[Compose UI\nScreens + ViewModels]
|
||||
Domain[Domain / Use-Cases]
|
||||
LocalDB[(Room SQLite\nLocal DB + Queue)]
|
||||
Worker[WorkManager\nUpload Worker]
|
||||
Service[Foreground Service\nGPS Tracking]
|
||||
Export[Export SAF]
|
||||
end
|
||||
|
||||
subgraph Server[Go Backend — backend/]
|
||||
Router[chi Router\n+ Middleware]
|
||||
IngestAPI[Ingest API\nPOST /v1/trackpoints]
|
||||
QueryAPI[Query API\nGET /v1/*]
|
||||
WebUI[Web UI\nGo Templates SSR]
|
||||
Auth[Auth Store\nArgon2id + Sessions]
|
||||
Stores[DB Stores\nTrackpoints / Stops\nSuggestions / Journal]
|
||||
StopEngine[Stop Detection\n+ Suggestions]
|
||||
GeoAdapter[Geocoding Adapter\n+ Cache + RateLimit]
|
||||
DB[(PostgreSQL)]
|
||||
end
|
||||
|
||||
subgraph Web[Webapp — webapp/]
|
||||
SPA[Vanilla TS SPA\nWeb Components]
|
||||
Map[TrackMap\nMapLibre GL]
|
||||
Nginx[nginx\nProxy + Static Files]
|
||||
end
|
||||
|
||||
Mobile -->|HTTPS Batch| IngestAPI
|
||||
SPA -->|HTTPS /v1/*| Nginx
|
||||
Nginx -->|proxy_pass| Router
|
||||
Router --> IngestAPI
|
||||
Router --> QueryAPI
|
||||
Router --> WebUI
|
||||
Router --> Auth
|
||||
IngestAPI --> Stores
|
||||
QueryAPI --> Stores
|
||||
WebUI --> Stores
|
||||
Auth --> DB
|
||||
Stores --> DB
|
||||
StopEngine --> GeoAdapter
|
||||
GeoAdapter -->|HTTPS| GEO[Nominatim]
|
||||
```
|
||||
|
||||
### Ebene 2 — Backend-Pakete
|
||||
|
||||
```
|
||||
backend/
|
||||
├── cmd/
|
||||
│ ├── server/ Einstiegspunkt: Pool → InitSchema → Router → HTTP Server
|
||||
│ └── createuser/ CLI: User mit Argon2id-Hash anlegen
|
||||
└── internal/
|
||||
├── domain/ Shared Types: Trackpoint, Stop, Suggestion, Session, User
|
||||
├── db/
|
||||
│ ├── schema.sql Eingebettetes Schema (go:embed, IF NOT EXISTS)
|
||||
│ ├── db.go NewPool + InitSchema
|
||||
│ ├── trackpoints.go UpsertBatch, ListByDate, ListDays, EnsureDevice
|
||||
│ ├── stops.go ListByDate
|
||||
│ ├── suggestions.go ListByDate
|
||||
│ └── journal.go CRUD Journal Entries + Images
|
||||
├── auth/
|
||||
│ └── auth.go HashPassword, VerifyPassword, Login, GetSession, Logout
|
||||
└── api/
|
||||
├── router.go chi Routing, Middleware-Gruppen
|
||||
├── middleware.go RequireAuth (Session Cookie → Context)
|
||||
├── ingest.go HandleSingleTrackpoint, HandleBatchTrackpoints
|
||||
├── query.go HandleListDays, HandleListTrackpoints, Stops, Suggestions
|
||||
├── webui.go Server-side rendered Web UI (Go Templates)
|
||||
├── journal.go Journal Entry Endpoints
|
||||
└── response.go writeJSON, writeError helpers
|
||||
```
|
||||
|
||||
### Ebene 2 — Android-Pakete
|
||||
|
||||
```
|
||||
app/app/src/main/kotlin/de/jacek/reisejournal/
|
||||
├── domain/ Trackpoint Domain Model
|
||||
├── data/ Room Entities, DAOs, lokale DB
|
||||
├── service/ Background Location Foreground Service
|
||||
├── worker/ WorkManager Upload Worker
|
||||
└── ui/
|
||||
├── home/ HomeScreen (Compose) + HomeViewModel
|
||||
├── navigation/ NavGraph
|
||||
└── theme/ Compose Theme
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Laufzeitsicht
|
||||
|
||||
### R1 — Trip starten und Trackpoints senden
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Android App
|
||||
participant Room as Room DB
|
||||
participant Worker as WorkManager
|
||||
participant API as Go Backend
|
||||
participant DB as PostgreSQL
|
||||
|
||||
App->>Room: Trackpoint persistieren (source=gps, pending)
|
||||
Worker->>Room: Pending Batch laden (NetworkConnected)
|
||||
Worker->>API: POST /v1/trackpoints:batch + Cookie
|
||||
API->>DB: EnsureDevice (INSERT ... ON CONFLICT DO NOTHING)
|
||||
API->>DB: UpsertBatch (ON CONFLICT device+event_id DO NOTHING)
|
||||
API-->>Worker: {accepted_ids, rejected: []}
|
||||
Worker->>Room: Accepted als "sent" markieren
|
||||
```
|
||||
|
||||
### R2 — Offline → Online Retry (REQ-SYNC-02, REQ-SYNC-04)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Worker as WorkManager
|
||||
participant Room as Room DB
|
||||
participant API as Go Backend
|
||||
|
||||
Worker->>API: POST /v1/trackpoints:batch
|
||||
API--xWorker: Netzwerkfehler / Timeout
|
||||
Worker->>Room: Pending belassen, retryCount++
|
||||
Note over Worker: Exponential Backoff\nConstraint: NetworkConnected
|
||||
Worker->>API: POST /v1/trackpoints:batch (gleiche event_ids)
|
||||
API-->>Worker: {accepted_ids} (Duplikate via ON CONFLICT ignoriert)
|
||||
Worker->>Room: Accepted als "sent" markieren
|
||||
```
|
||||
|
||||
### R3 — Web Login und Tagesansicht (REQ-WEB-01, REQ-WEB-02)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant B as Browser
|
||||
participant Nginx as nginx :9050
|
||||
participant API as Go Backend
|
||||
participant DB as PostgreSQL
|
||||
|
||||
B->>Nginx: POST /login (username, password)
|
||||
Nginx->>API: proxy_pass
|
||||
API->>DB: User laden, Argon2id verify
|
||||
API-->>B: Set-Cookie: session=..., 303 /days
|
||||
B->>Nginx: GET /v1/days?from=&to= + Cookie
|
||||
Nginx->>API: proxy_pass
|
||||
API->>DB: trackpoints JOIN devices WHERE user_id = $1
|
||||
API-->>B: [{date, count, first_ts, last_ts}]
|
||||
B->>Nginx: GET /v1/trackpoints?date=YYYY-MM-DD + Cookie
|
||||
API->>DB: trackpoints WHERE date = $2 AND user_id = $1
|
||||
API-->>B: [Trackpoint...]
|
||||
```
|
||||
|
||||
### R4 — Manuellen Punkt hinzufügen (REQ-MAN-01 bis REQ-MAN-05)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Nutzer
|
||||
participant VM as ViewModel
|
||||
participant Room as Room DB
|
||||
participant Worker as WorkManager
|
||||
participant API as Go Backend
|
||||
|
||||
U->>VM: Eingabe lat/lon/timestamp/note/tags
|
||||
VM-->>U: Validierung as-the-user-types
|
||||
VM->>Room: Trackpoint (source=manual, tags=[...], pending)
|
||||
Worker->>Room: Fetch pending
|
||||
Worker->>API: POST /v1/trackpoints:batch
|
||||
API-->>Worker: Ack
|
||||
Worker->>Room: Mark sent
|
||||
```
|
||||
|
||||
### R5 — Stop-Erkennung und Geocoding (REQ-SUG-01, REQ-GEO-01)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant API as Go Backend
|
||||
participant SE as Stop Engine
|
||||
participant GEO as Geocoding Adapter
|
||||
participant NOM as Nominatim
|
||||
participant DB as PostgreSQL
|
||||
|
||||
API->>SE: Trigger nach Batch-Ingest (async)
|
||||
SE->>DB: Letzte Trackpoints lesen
|
||||
SE->>SE: Stop erkennen (minDuration, radiusMeters)
|
||||
SE->>GEO: Reverse-Geocode Stop-Koordinate
|
||||
alt Cache Hit
|
||||
GEO-->>SE: Place Info (gecached)
|
||||
else Cache Miss
|
||||
GEO->>NOM: HTTPS Reverse-Geocode
|
||||
NOM-->>GEO: Place Info
|
||||
GEO-->>SE: Place Info (jetzt gecached)
|
||||
end
|
||||
SE->>DB: Stop + Suggestion speichern
|
||||
```
|
||||
|
||||
### R6 — Schema-Initialisierung beim Server-Start
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant S as cmd/server
|
||||
participant DB as PostgreSQL
|
||||
|
||||
S->>DB: pgxpool.New + Ping
|
||||
S->>DB: schema.sql ausführen (go:embed)\nCREATE TABLE IF NOT EXISTS ...
|
||||
Note over DB: Idempotent — bestehende\nTabellen bleiben erhalten
|
||||
S->>S: Stores + Router aufbauen
|
||||
S->>S: HTTP Server starten :8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Verteilungssicht
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Phone[Android Device]
|
||||
Browser[Web Browser]
|
||||
|
||||
subgraph Docker[Docker Compose]
|
||||
Nginx[webapp\nnginx:alpine\n:9050]
|
||||
API[api\ndistroless\n:8080 intern]
|
||||
PG[postgres\npostgres:16-alpine\ninternal]
|
||||
end
|
||||
|
||||
GEO[Nominatim\nexternal HTTPS]
|
||||
|
||||
Phone -->|HTTPS| Nginx
|
||||
Browser -->|HTTPS| Nginx
|
||||
Nginx -->|/v1/* /login /logout\nproxy_pass| API
|
||||
Nginx -->|/ static SPA| Nginx
|
||||
API --> PG
|
||||
API -->|cached Rate-Limited| GEO
|
||||
```
|
||||
|
||||
**Docker Compose Services:**
|
||||
|
||||
| Service | Image | Port extern | Funktion |
|
||||
|---------|-------|-------------|----------|
|
||||
| `postgres` | postgres:16-alpine | — (intern) | Datenhaltung |
|
||||
| `api` | golang:1.25 → distroless | — (intern) | REST API + Web UI |
|
||||
| `webapp` | node:22 → nginx:alpine | **9050** | SPA + API-Proxy |
|
||||
|
||||
**Start:**
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
Schema wird beim API-Start automatisch initialisiert (keine separate Migration nötig).
|
||||
|
||||
---
|
||||
|
||||
## 8. Querschnittskonzepte
|
||||
|
||||
### Authentifizierung & Sessions (REQ-AUTH-01, REQ-AUTH-02, DEC-AUTH-01)
|
||||
|
||||
- Passwörter: Argon2id (`$argon2id$<saltHex>$<hashHex>`)
|
||||
- Sessions in PostgreSQL (`sessions`-Tabelle): sofortige Invalidierung bei Logout
|
||||
- Cookie: `HttpOnly`, `SameSite=Lax`, 24h Lebensdauer
|
||||
- Android Upload-Auth: aktuell Session Cookie — **TBD: API-Key oder JWT** (DEC-AUTH-01)
|
||||
|
||||
### Idempotenz (REQ-SYNC-04)
|
||||
|
||||
- Jeder Trackpoint trägt eine client-generierte `event_id` (UUID empfohlen)
|
||||
- Unique Constraint `(device_id, event_id)` in PostgreSQL
|
||||
- `ON CONFLICT DO NOTHING` → doppelter Upload = 200 OK, kein Fehler
|
||||
- Batch-Response enthält `accepted_ids` und `rejected[]` für Queue-Steuerung
|
||||
|
||||
### Offline-First (REQ-SYNC-01 bis REQ-SYNC-03)
|
||||
|
||||
- Jeder Punkt wird zuerst in Room (SQLite) persistiert
|
||||
- Upload via WorkManager: `NetworkConnected`-Constraint, Exponential Backoff
|
||||
- `source`-Feld unterscheidet `"gps"` und `"manual"`
|
||||
|
||||
### Device-Registrierung
|
||||
|
||||
- `EnsureDevice()` beim ersten Ingest: `INSERT ... ON CONFLICT DO NOTHING`
|
||||
- Verknüpft `device_id` mit authentifiziertem `user_id`
|
||||
- Query-Endpoints filtern über `devices.user_id`
|
||||
|
||||
### Hashtags (REQ-TAG-01 bis REQ-TAG-06)
|
||||
|
||||
- Feld `tags: string[]` im Trackpoint-Schema
|
||||
- Eingabe-Vorschläge aus vorhandenen Tags des Nutzers
|
||||
- Server: `GET /v1/tags` (Häufigkeit), `GET /v1/trackpoints?tag=...`
|
||||
- Webapp: Tag-Filter in Tagesdetail, Tag-Übersicht
|
||||
|
||||
### Geocoding (REQ-GEO-01 bis REQ-GEO-03)
|
||||
|
||||
- Nur ereignisbasiert (pro Stop), niemals bulk/periodisch
|
||||
- Cache vor jedem Provider-Call
|
||||
- Provider über Config austauschbar (kein Hardcode)
|
||||
- Nominatim: User-Agent korrekt setzen (Policy-Pflicht)
|
||||
|
||||
### Karten (DEC-MAP-01, REQ-MAP-01, REQ-MAP-02)
|
||||
|
||||
- Android: MapLibre SDK
|
||||
- Webapp: MapLibre GL JS (open source, lizenzfrei)
|
||||
- Tile-Quelle konfigurierbar (`tileUrl`-Property auf `<track-map>`)
|
||||
|
||||
---
|
||||
|
||||
## 9. Produktanforderungen (aus README)
|
||||
|
||||
### 3.1 Background-Logging
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-LOC-01 | App MUSS Standort im Vordergrund und Hintergrund erfassen |
|
||||
| REQ-LOC-02 | COARSE (optional FINE) im Manifest + Runtime; App funktioniert mit „approximate" |
|
||||
| REQ-LOC-03 | Background-Location korrekt angefordert |
|
||||
| REQ-LOC-04 | `foregroundServiceType="location"` wenn Foreground Service |
|
||||
|
||||
### 3.2 Offline-First
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-SYNC-01 | Trackpoints zuerst lokal persistieren (kein RAM-Queue) |
|
||||
| REQ-SYNC-02 | Upload bevorzugt; bei Fehler cachen und automatisch nachladen |
|
||||
| REQ-SYNC-03 | WorkManager mit `NetworkConnected` + Retry/Backoff |
|
||||
| REQ-SYNC-04 | Keine Duplikate bei Retries; Idempotenz via `event_id` |
|
||||
|
||||
### 3.3 Manuelle Punkte
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-MAN-01 | Manueller Punkt: lat, lon, timestamp (Pflicht); Name/Notiz (optional) |
|
||||
| REQ-MAN-02 | Lokal in Room speichern |
|
||||
| REQ-MAN-03 | In Upload-Flow integriert (inkl. `event_id`) |
|
||||
| REQ-MAN-04 | Validierung as-the-user-types; verständliche Fehlermeldungen |
|
||||
| REQ-MAN-05 | Vorschlag „aktueller Zeitpunkt / aktuelle Position" (editierbar) |
|
||||
|
||||
### 3.4 Export
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-EXP-01 | Export in Datei möglich |
|
||||
| REQ-EXP-02 | Via SAF `ACTION_CREATE_DOCUMENT`; Nutzer wählt Speicherort |
|
||||
|
||||
### 3.5 Server Website
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-WEB-01 | Website nur nach Login zugänglich |
|
||||
| REQ-WEB-02 | Pro Tag Punkte anzeigen (Liste + Detailansicht) |
|
||||
| REQ-WEB-03 | Daten nutzerspezifisch (ein Nutzer sieht nur eigene Daten) |
|
||||
| REQ-WEB-04 | Kartenansicht (Punkte/Stops pro Tag) |
|
||||
|
||||
### 3.6 Security / Auth
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-AUTH-01 | Passwörter NICHT im Klartext; Argon2id |
|
||||
| REQ-AUTH-02 | Session Cookie; serverseitig verwaltbar (Logout/Expire) |
|
||||
| REQ-AUTH-03 | Auth auch für Web-Query-Endpoints |
|
||||
|
||||
### 3.7 Stops / Vorschläge
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-SUG-01 | Stop-Erkennung (Mindestdauer, Radius konfigurierbar) |
|
||||
| REQ-SUG-02 | Vorschläge aus Stops ableiten |
|
||||
| REQ-SUG-03 | Pro Nutzer/Device/Trip zuordenbar; in Website sichtbar |
|
||||
|
||||
### 3.8 Geocoding
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-GEO-01 | Kostenloser Dienst (Nominatim); Caching + Rate-Limit |
|
||||
| REQ-GEO-02 | Provider austauschbar via Config (ohne App-Update) |
|
||||
| REQ-GEO-03 | Kein Bulk/periodisches Geocoding; ereignisbasiert (pro Stop) |
|
||||
|
||||
### 3.9 Karten
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-MAP-01 | OpenStreetMap-Basis; MapLibre als Renderer |
|
||||
| REQ-MAP-02 | Tile-Quelle konfigurierbar |
|
||||
|
||||
### 3.10 OpenAPI
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-OPENAPI-01 | API als OpenAPI 3.1 dokumentiert |
|
||||
| REQ-OPENAPI-02 | `openapi.yaml` im Repository versioniert |
|
||||
| REQ-OPENAPI-03 | Alle Endpoints, Schemas, Fehler, Security-Schemes |
|
||||
| REQ-OPENAPI-04 | CookieAuth via `type: apiKey, in: cookie` |
|
||||
|
||||
### 3.11 Google Play / Datenschutz
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-PRIV-01 | Background-Location Disclosure im UI; korrekte Deklaration |
|
||||
|
||||
### 3.13 Hashtags
|
||||
|
||||
| ID | Anforderung |
|
||||
|----|------------|
|
||||
| REQ-TAG-01 | Trackpoints mit Hashtags versehen (`#restaurant`, ...) |
|
||||
| REQ-TAG-02 | Tags lokal in Room + beim Upload übertragen (`tags: string[]`) |
|
||||
| REQ-TAG-03 | Server persistiert Tags, abfragbar pro Trackpoint |
|
||||
| REQ-TAG-04 | Webapp: Filter nach Hashtag |
|
||||
| REQ-TAG-05 | Eingabe-Vorschläge aus vorhandenen Tags des Nutzers |
|
||||
| REQ-TAG-06 | Webapp: Tag-Übersicht mit Häufigkeit |
|
||||
|
||||
---
|
||||
|
||||
## 10. Architekturentscheidungen
|
||||
|
||||
| ID | Entscheidung | Alternativen | Status |
|
||||
|----|-------------|--------------|--------|
|
||||
| DEC-CLIENT-01 | Android (Kotlin + Compose) | iOS, Flutter | Entschieden |
|
||||
| DEC-LOC-01 | Background Location Logging | Nur Vordergrund | Entschieden |
|
||||
| DEC-API-01 | HTTP/REST + JSON | MQTT, gRPC, Protobuf | Entschieden (Protobuf offen) |
|
||||
| DEC-DB-01 | Room (Android) + PostgreSQL (Server) | SQLite Server, MySQL | Entschieden |
|
||||
| DEC-WEB-01 | Website mit Login (SSR + SPA) | nur API | Entschieden |
|
||||
| DEC-AUTH-01 | Session Cookie für Web; JWT optional | JWT only | Session entschieden |
|
||||
| DEC-MAP-01 | MapLibre + OSM, konfigurierbare Tiles | Google Maps | Entschieden |
|
||||
| DEC-GEO-01 | Nominatim, Provider austauschbar | Google Places API | Entschieden |
|
||||
| DEC-OPENAPI-01 | OpenAPI 3.1 YAML | kein Spec | Entschieden |
|
||||
| DEC-SCHEMA-01 | `go:embed` schema.sql, IF NOT EXISTS | golang-migrate | Entschieden |
|
||||
|
||||
---
|
||||
|
||||
## 11. Risiken und offene Punkte
|
||||
|
||||
| Risiko | Schwere | Status |
|
||||
|--------|---------|--------|
|
||||
| Android Upload Auth: Session Cookie suboptimal für Mobile | Mittel | **TBD** — API-Key oder JWT (T050) |
|
||||
| `timestamp`-Format: epochMillis vs RFC3339 | Niedrig | **Offen** |
|
||||
| Payload: JSON vs Protobuf | Niedrig | **Offen** |
|
||||
| Batch-Limits (max Items, max Bytes) | Niedrig | **Offen** |
|
||||
| Kein Rate-Limiting auf Ingest-Endpoints | Mittel | Backlog (T029) |
|
||||
| Stop Detection nicht implementiert | Mittel | Backlog (T061) |
|
||||
| Geocoding Adapter + Cache nicht implementiert | Mittel | Backlog (T063) |
|
||||
| Kein HTTPS/TLS im nginx (nur HTTP intern) | Hoch | Für Produktion: TLS-Termination via Reverse Proxy nötig |
|
||||
| Retention Policy (wie lange Trackpoints gespeichert) | Niedrig | **Offen** |
|
||||
| Stop Detection Parameter (Mindestdauer, Radius) | Niedrig | **Offen** |
|
||||
| Geocoding Provider: Nominatim public vs self-hosted | Niedrig | **Offen** |
|
||||
Reference in New Issue
Block a user