Files
moneyfy/README.md
T
Jonas MenzelandClaude Opus 5 d63cc0af65
Images bauen / build (backend) (push) Failing after 58s
Images bauen / build (frontend) (push) Failing after 57s
CI / backend (push) Successful in 2m21s
CI / frontend (push) Canceled after 4m40s
feat: Politur – Demodaten, Kürzel, Fehlerseiten, Kontraste
- Demo-Seed mit realistischem deutschem Haushalt (make seed-demo): drei Konten,
  zehn Firmen samt Logos, zwölf Posten, bestätigte Historie, Buchungen, Budgets
  und Sparziele; alle Termine relativ zum heutigen Monat
- Tastaturkürzel n / r / / / ? samt Übersicht; greifen nur außerhalb von
  Eingabefeldern
- Fehlergrenze für Renderfehler und eigene 404-Seite
- Farbtoken beider Modi gegen WCAG geprüft und nachgezogen: Hilfstext lag hell
  bei 2,56:1, Weiß auf dem Primärknopf bei 3,30:1 – jetzt überall >= 4,5:1
- Abo-Übersicht heißt „Laufende Kosten“, weil sie auch Miete und Sparplan enthält
- build.yml fällt auf GITEA_TOKEN zurück, wenn REGISTRY_TOKEN fehlt, und erklärt
  im Fehlerfall, wie das Secret anzulegen ist
- README mit Screenshot-Abschnitt, Kürzeln und Barrierefreiheit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
2026-09-09 21:55:01 +02:00

236 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# moneyfy
Selbst gehostete Web-Anwendung zur Planung monatlicher Kosten und Einkünfte.
Single-User, deutschsprachige Oberfläche, ausgelegt für den Betrieb im Homelab
hinter einem Reverse Proxy.
> **Version 0.1.0.** Siehe [CHANGELOG.md](CHANGELOG.md).
## Screenshots
Die Bilder entstehen aus dem Demo-Datensatz (`make seed-demo`), damit sie ohne
echte Kontostände auskommen. Ablegen unter `docs/screenshots/`.
| | |
|---|---|
| ![Dashboard](docs/screenshots/dashboard.png)<br>**Dashboard** verfügbar nach Fixkosten, Kategorienring, Zwölf-Monats-Vorschau | ![Cashflow-Kalender](docs/screenshots/calendar.png)<br>**Cashflow-Kalender** Fälligkeiten je Tag mit Firmenlogo, darunter der Kontostandsverlauf |
| ![Wiederkehrende Posten](docs/screenshots/recurrences.png)<br>**Wiederkehrend** Abos, Verträge und Raten mit Klartext-Rhythmus | ![RRULE-Editor](docs/screenshots/rrule-editor.png)<br>**RRULE-Editor** geführte Auswahl, Expertenmodus und Live-Vorschau der nächsten Termine |
| ![Firmen](docs/screenshots/merchants.png)<br>**Firmen** Kachelgrid mit Logo, Markenfarbe und Jahreskosten | ![Auswertungen](docs/screenshots/reports.png)<br>**Auswertungen** laufende Kosten, Jahresvergleich und Export |
## Funktionsumfang
- Wiederkehrende Zahlungen und Einkünfte über vollständige RFC-5545-RRULEs,
inklusive Werktagsverschiebung nach NRW-Feiertagen.
- Preishistorie je Vertrag, sodass vergangene Monate betragstreu bleiben.
- Verträge mit Mindestlaufzeit, Kündigungsfrist und automatischer Verlängerung.
- Ratenzahlungen mit Restschuld- und Restratenberechnung.
- Rücklagenbildung für nicht-monatliche Posten.
- Firmenlogos und Markenfarben, lokal zwischengespeichert (siehe unten).
- Auswertungen: Monatsübersicht, Cashflow-Kalender, 12-Monats-Forecast,
Kategorien, Abo-Übersicht, Jahresvergleich, Budgets, Sparziele.
- Benachrichtigungen per SMTP und Apprise.
## Technischer Stack
| Bereich | Technologie |
|---|---|
| Backend | Python 3.12, FastAPI, SQLAlchemy 2 (async), Alembic, Pydantic v2 |
| Datenbank | PostgreSQL 17 |
| Scheduler | APScheduler (in-process) |
| Frontend | React 18, TypeScript, Vite, TailwindCSS |
| Charts | Recharts |
| Tests | pytest + httpx, vitest + testing-library |
## Schnellstart (Betrieb)
Voraussetzungen: Docker mit Compose-Plugin und ein externes Netz `proxy`.
```bash
mkdir -p /opt/moneyfy && cd /opt/moneyfy
curl -fsSLO https://git.menzel.center/menzeljonas/moneyfy/raw/branch/main/docker-compose.yml
curl -fsSLO https://git.menzel.center/menzeljonas/moneyfy/raw/branch/main/.env.example
cp .env.example .env
# Mindestens setzen: POSTGRES_PASSWORD, SECRET_KEY, MONEYFY_ADMIN_PASSWORD
$EDITOR .env
docker login git.menzel.center
docker compose up -d
```
Das Backend wartet auf die Datenbank, spielt die Migrationen ein und legt beim
allerersten Start den Kategoriebaum an. Danach läuft die Oberfläche auf
`http://127.0.0.1:8087`. Die vollständige Einrichtung samt Reverse Proxy und
Sicherung steht in [docs/deployment.md](docs/deployment.md).
## Schnellstart (lokale Entwicklung)
Voraussetzungen: Python 3.12, [uv](https://docs.astral.sh/uv/), Node 20+,
eine erreichbare PostgreSQL-17-Instanz.
```bash
git clone https://git.menzel.center/menzeljonas/moneyfy.git
cd moneyfy
cp .env.example backend/.env # DATABASE_URL und SECRET_KEY anpassen
make install # Backend-Abhängigkeiten in backend/.venv
make migrate # Schema anlegen
make seed # Kategoriebaum und Standardregeln
make dev # http://localhost:8000/api/docs
```
In einem zweiten Terminal das Frontend, es leitet `/api` an das Backend weiter:
```bash
make fe-install
make fe-dev # http://localhost:5173
```
Für einen gefüllten Stand:
```bash
make seed-demo # realistischer Beispielhaushalt, Termine relativ zu heute
make seed-demo-reset # setzt Konten, Firmen, Posten und Buchungen vorher zurück
```
Der Demo-Seed legt drei Konten an, zehn Firmen samt Logos, zwölf wiederkehrende
Posten (Miete, Strom, Netflix, Spotify, jährliche Kfz-Versicherung, Handyvertrag
mit Mindestlaufzeit, Kredit über 36 Raten, Gehalt), bestätigt die vergangenen
Fälligkeiten, ergänzt Buchungen, Budgets und Sparziele.
Nützliche Ziele: `make check` (alle Prüfungen), `make test`, `make lint`,
`make format`, `make fe-lint`, `make fe-test`, `make fe-build`.
## Bedienung
| Kürzel | Wirkung |
|---|---|
| `n` | Neue Buchung |
| `r` | Neuer wiederkehrender Posten |
| `/` | Suchfeld der aktuellen Seite |
| `?` | Übersicht der Kürzel |
| `Esc` | Dialog schließen |
Kürzel greifen nur, solange kein Eingabefeld den Fokus hat.
## Barrierefreiheit
Die Farbtoken sind gegen die WCAG-Kontraste geprüft in beiden Modi erreicht
jeder Text mindestens 4,5:1, jedes Bedienelement 3:1. Zwei Werte weichen
deshalb bewusst von den naheliegenden Tailwind-Stufen ab: `slate-400` als
Hilfstext käme auf hellem Grund nur auf 2,56:1, und Weiß auf `green-600` nur
auf 3,30:1.
Die Serienfarben der Diagramme sind zusätzlich auf Farbfehlsichtigkeit geprüft
(siehe [Diagramme](#diagramme)). Jeder Zustand Budget-Ampel, Versandstatus,
Vertragswarnung trägt neben der Farbe ein Symbol und einen Wortlaut.
## Konfiguration
Alle Einstellungen kommen aus Umgebungsvariablen; `.env.example` enthält die
vollständige, kommentierte Liste. Die wichtigsten:
| Variable | Standard | Bedeutung |
|---|---|---|
| `DATABASE_URL` | | PostgreSQL-DSN, `postgresql://` wird auf asyncpg umgestellt |
| `SECRET_KEY` | | Signaturschlüssel für JWTs, mind. 32 Zeichen (`openssl rand -hex 32`) |
| `MONEYFY_ADMIN_USER` / `MONEYFY_ADMIN_PASSWORD` | `admin` / | Beim Erststart angelegter Benutzer |
| `TIMEZONE` | `Europe/Berlin` | Zeitzone der gesamten Anwendung |
| `HOLIDAY_REGION` | `DE-NW` | Feiertagsregion für Werktagsverschiebungen |
| `LOGO_STORAGE_DIR` | `/data/logos` | Verzeichnis des Logo-Caches |
| `LOGODEV_API_KEY` / `BRANDFETCH_API_KEY` | leer | Optionale Logo-Provider, ohne Schlüssel übersprungen |
| `SMTP_*` | leer | Mailversand für Benachrichtigungen |
| `APPRISE_URLS` | leer | Komma-separierte Apprise-Ziele |
| `COOKIE_SECURE` | `true` | Hinter HTTPS `true`, für lokales HTTP `false` |
| `SCHEDULER_ENABLED` | `true` | Täglicher Benachrichtigungslauf um 07:00 |
## Benachrichtigungen
Ein Job läuft täglich um 07:00 Uhr (`Europe/Berlin`) im Anwendungsprozess ohne
Redis, ohne Celery. Er prüft vier Anlässe: bald fällige Posten, Kündigungsfristen
30, 14 und 7 Tage vor dem Termin, überschrittene Budgets und Vertragsverlängerungen
im kommenden Monat.
Der Duplikatsschutz hängt am **Zieltag des Ereignisses**, nicht am Versandtag:
Eine Fälligkeit am 15.03. wird genau einmal gemeldet, unabhängig davon, an
welchem Tag des Vorlaufs der Job läuft. Ein fehlgeschlagener Versand gilt als
offen und wird beim nächsten Lauf erneut versucht.
Als Kanäle stehen SMTP (HTML-Mail mit eingebetteten Firmenlogos) und Apprise zur
Verfügung. Über *Einstellungen → Benachrichtigungen* lassen sich Regeln pflegen,
eine Testnachricht verschicken und das Versandprotokoll einsehen.
## Diagramme
Die Serienfarben stammen aus einer Palette, die gegen die hellen und dunklen
Flächen der Anwendung auf Kontrast und Farbfehlsichtigkeit geprüft wurde; ihre
Reihenfolge ist Teil dieser Absicherung. Grün und Rot bleiben den Vorzeichen im
Text vorbehalten als Serienpaar wären sie bei Deuteranopie nicht zu
unterscheiden. Jedes Diagramm mit mehr als einer Serie führt eine Legende, und
zu den Balkendiagrammen lässt sich die Wertetabelle aufklappen.
Einnahmen, Ausgaben und der kumulierte Kontostand stehen bewusst in zwei
getrennten Diagrammen untereinander: eine gemeinsame Größenachse würde bei so
verschiedenen Größenordnungen einen Zusammenhang vortäuschen.
## Logos und Markenfarben
Jede Firma bekommt ein Bild. Die Provider-Kette arbeitet der Reihe nach:
1. **simple-icons** rund 3.500 Marken, im Repository unter
`backend/app/assets/simple_icons.json.gz` mitgeliefert. Kein Schlüssel, kein
Netzzugriff. Neu erzeugen mit `make vendor-icons`.
2. **logo.dev** nur mit `LOGODEV_API_KEY`.
3. **Brandfetch** nur mit `BRANDFETCH_API_KEY` und hinterlegter Domain.
4. **Favicon** über den Google-Dienst, wenn eine Domain bekannt oder aus dem
Namen ableitbar ist.
5. **Generierter Avatar** Buchstaben-Monogramm mit fester Farbe aus dem
Namens-Hash. Schlägt nie fehl.
Findet die Kette einen eindeutigen Treffer im lokalen Katalog, unterbleiben die
Anfragen nach außen vollständig die Firmenliste verlässt den Server nicht. Im
Auswahldialog (`POST /api/merchants/{id}/logo/search`) läuft dagegen bewusst die
ganze Kette, damit Alternativen zur Wahl stehen.
Ausgewählte Logos landen unter `LOGO_STORAGE_DIR`, benannt nach ihrem
SHA-256-Hash, und werden nur über `GET /api/logos/{id}` ausgeliefert. Beim
Seitenaufruf entsteht dadurch kein Zugriff auf fremde Dienste. Ein Upload oder
eine bewusste Auswahl setzt den Status auf `manual` und wird von der Automatik
nie überschrieben.
## Projektstruktur
```
backend/ FastAPI-Anwendung, Modelle, Services, Alembic-Migrationen, Tests
frontend/ React-Oberfläche (Vite)
docs/ Betriebs- und Runner-Dokumentation
.gitea/ Gitea-Actions-Workflows
```
Alternativ lässt sich alles aus dem Quellcode bauen:
```bash
cp docker-compose.override.yml.example docker-compose.override.yml
docker compose up -d --build
```
Die Override-Datei baut beide Images lokal, bindet den Quellcode ein und öffnet
die Ports für Backend (8000) und Vite (5173).
## Bauen und Ausrollen
Zwei Gitea-Workflows unter `.gitea/workflows/`:
- **`ci.yml`** läuft bei jedem Push und Pull Request: `ruff`, `pytest`,
`tsc --noEmit`, `eslint`, `vitest` und der Bundle-Bau.
- **`build.yml`** läuft auf `main` und bei Tags `v*` und lädt beide Images für
`linux/amd64` in die Gitea-Registry. Push auf `main` erzeugt `:main` und
`:sha-<kurz>`, ein Tag `v1.2.3` zusätzlich `:1.2.3`, `:1.2` und `:latest`.
Ausgerollt wird **nicht** automatisch. Die Einrichtung des Runners beschreibt
[docs/runner.md](docs/runner.md), das Ausrollen [docs/deployment.md](docs/deployment.md).
## Lizenz
[MIT](LICENSE)