Add authenticated finance REST API

This commit is contained in:
kai
2026-09-09 09:47:56 +02:00
parent afc6f74f36
commit ad452045be
13 changed files with 760 additions and 44 deletions
+109
View File
@@ -135,3 +135,112 @@ Die lokale Datenbank wird **nicht mit Git übertragen**. Für die Erstübernahme
Nur für das private LAN, ohne Benutzerverwaltung. Validierung, SQL-Parameterbindung, Jinja-Autoescaping und Prüfung fremder Browser-Formularursprünge sind enthalten. Chart.js wird fest versioniert vom CDN geladen; ohne Internet funktionieren Buchungen, Kennzahlen und Tabellen weiter. Die Diagramme benötigen Zugang zum CDN.
Implementierungsreferenzen: [FastAPI Templates](https://fastapi.tiangolo.com/advanced/templates/) und [Chart.js Integration](https://www.chartjs.org/docs/latest/getting-started/integration.html).
## REST-API aktivieren
Die API läuft im bestehenden FastAPI-Prozess unter **`/api/v1`** und verwendet dieselben Services und dieselbe SQLite-Datenbank wie die Weboberfläche. Es gibt keine neue Datenhaltung und keine zusätzlichen Laufzeit-Abhängigkeiten.
1. Ein langes zufälliges Token erzeugen:
```bash
python -c 'import secrets; print(secrets.token_urlsafe(32))'
```
2. Im Projektverzeichnis eine `.env` anlegen bzw. die vorhandene Datei ergänzen:
```dotenv
FINANCE_API_TOKEN=<langes-zufälliges-token>
```
`.env.example` enthält nur den Platzhalter `change-me`. Diesen durch das erzeugte Token ersetzen; `change-me` aktiviert die API ausdrücklich nicht. `.env` bleibt in `.gitignore` und `.dockerignore`. Ein echtes Token gehört niemals ins Repository.
3. Den Container nach einer Tokenänderung neu erstellen:
```bash
docker compose up -d --no-build --force-recreate
```
Compose liest `.env` automatisch und reicht `FINANCE_API_TOKEN` weiter. Bei direktem Uvicorn-Start muss die Variable in der Prozessumgebung gesetzt sein. Ohne Token, bei leerem Token oder beim Beispiel-Platzhalter antwortet die API mit **503**. Weboberfläche und `/health` funktionieren weiterhin. Ein fehlender/falscher Bearer-Header bei aktivierter API führt zu **401** mit `WWW-Authenticate: Bearer`. Der Vergleich erfolgt mit `secrets.compare_digest`. Tokens werden nicht protokolliert. CORS bleibt deaktiviert.
Für die folgenden curl-Beispiele muss `FINANCE_API_TOKEN` auch in der aufrufenden Shell gesetzt sein. Im eigenen Projektverzeichnis kann die selbst angelegte `.env` geladen werden:
```bash
set -a
. ./.env
set +a
curl \
-H "Authorization: Bearer $FINANCE_API_TOKEN" \
http://pinguAurora:8081/api/v1/assets
```
### Endpunkte
Alle folgenden Pfade beginnen mit `/api/v1` und erfordern den Bearer-Header, auch `/meta`.
| Methode | Pfad | Verhalten |
| --- | --- | --- |
| GET | `/assets` | Alle Positionen, optional `active=true/false` |
| GET | `/assets/{id}` | Einzelne Position, sonst 404 |
| POST | `/assets` | Position erstellen, 201; normalisierte Duplikate 409 |
| PATCH | `/assets/{id}` | Nur übergebene Felder ändern |
| DELETE | `/assets/{id}` | Position deaktivieren (`active=false`), 204; Historie bleibt erhalten |
| GET | `/income` | Historie, neueste zuerst; Filter und Pagination |
| GET | `/income/{id}` | Einzelne Buchung, sonst 404 |
| POST | `/income` | Zahlung erstellen und zurückgeben, 201 |
| PATCH | `/income/{id}` | Teiländerung, aktualisierte Zahlung zurückgeben |
| DELETE | `/income/{id}` | Buchung löschen, 204 ohne Inhalt |
| GET | `/stats/summary` | Dieselben acht Kennzahlen wie im Dashboard |
| GET | `/stats/monthly` | Monatswerte je Jahr, optional `year` |
| GET | `/stats/by-asset` | Betrag und Anteil je Position, alle Jahre |
| GET | `/stats/by-category` | Betrag und Anteil für alle vier Kategorien |
| GET | `/meta` | Name, API-Version und Datenbankstatus ohne Systemdetails |
`GET /income` unterstützt `year`, `month`, `asset_id`, `category`, `received`, `expected`, `limit` (Standard 100, maximal 1000) und `offset` (Standard 0). Sortierung: Datum absteigend, bei gleichem Datum ID absteigend. Listen werden als JSON-Arrays geliefert. `asset` in Buchungen und Anteilen ist der normalisierte Positionsname; `asset_id` identifiziert die Position eindeutig.
Geldbeträge werden im JSON **ausschließlich als Strings in Euro** mit zwei Nachkommastellen ausgegeben, z. B. `"0.04"`. Auch Eingaben müssen Dezimalstrings sein (`"0.04"` oder `"0,04"`); JSON-Fließkommazahlen werden abgelehnt. Intern bleibt es bei Decimal und Integer-Cent. Prozentwerte sind ebenfalls Strings mit zwei Nachkommastellen; bei Nenner 0 ist der Wert `null`. Ein Anteil wird relativ zur tatsächlichen Gesamtsumme berechnet, inklusive negativer Korrekturen.
`/stats/monthly` liefert ein Array aus `{year, months: [{month, amount}], total}` mit zwölf Monaten pro Jahr. Ein explizit angefragtes Jahr ohne Buchungen liefert Nullbeträge. Alle Statistik-Endpunkte zählen ausschließlich `received=true`; `expected=true, received=false` erhöht keine tatsächliche Einnahme. Der Jahresvergleich bleibt aktuelles Jahr gegen gesamtes Vorjahr. `/stats/by-category` trennt `dividend` und `distribution`; das Dashboard-Diagramm fasst sie zur Anzeige zusammen.
Unbekannte IDs liefern 404, doppelte Positionsnamen 409 und ungültige Felder/Referenzen 422. `PATCH` lässt fehlende Felder unverändert; `note: null` bzw. `ticker: null` leert optionale Felder. Andere Felder dürfen nicht `null` sein. Historische Buchungen deaktivierter Positionen bleiben lesbar und bearbeitbar; neue Buchungen für inaktive Positionen werden abgelehnt. Reaktivierung erfolgt über `PATCH /assets/{id}` mit `{"active":true}`. Datenbank-Locks führen nach der bestehenden Wartezeit zu 503 mit Wiederholungshinweis; interne Fehler zu 500 ohne Stacktrace oder interne Details.
### Zahlung per API erstellen
Zuerst die passende `asset_id` über `/assets` ermitteln; die ID im folgenden Beispiel ersetzen:
```bash
curl -X POST \
-H "Authorization: Bearer $FINANCE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"date":"2026-09-02",
"asset_id":1,
"category":"dividend",
"amount":"0.04",
"note":"Test",
"expected":false,
"received":true
}' \
http://pinguAurora:8081/api/v1/income
```
POST legt jeweils eine echte neue Zahlung an; die Dublettenerkennung des Excel-Imports gilt nicht für manuelle/API-Buchungen.
Swagger: **http://pinguAurora:8081/docs**. Unter **Authorize** nur das Token eingeben; Swagger ergänzt `Bearer`. OpenAPI: `/openapi.json`. Die Tags heißen Assets, Income und Stats. Die Dokumentation ist lesbar, die dokumentierten API-Aufrufe erfordern Authentifizierung.
### API-Deployment auf pinguAurora
Einmalig `.env` mit einem echten Token auf dem Raspberry anlegen. Dann weiterhin der bestehende Weg ohne Buildx:
```bash
git pull
DOCKER_BUILDKIT=0 docker build \
-t finance-dashboard-finance-dashboard:latest \
.
docker compose up -d --no-build
curl --fail http://127.0.0.1:8081/health
```
`./deploy.sh` bleibt unverändert nutzbar. Die vorhandene `/data/finance.db` wird weiterverwendet und weder gelöscht noch überschrieben. API-Tests setzen ein zufälliges Testtoken in der Testumgebung und verwenden ausschließlich temporäre Datenbanken.
Technische Referenzen: [FastAPI HTTPBearer](https://fastapi.tiangolo.com/reference/security/) und [Pydantic Serialization](https://docs.pydantic.dev/latest/concepts/serialization/).