- Kanäle SMTP (HTML-Mail mit Logos als CID-Anhang) und Apprise, beide blockierenden Bibliotheken laufen in einem Thread - Vier Anlässe: Fälligkeiten im Vorlauf, Kündigungsfristen in drei Stufen, überschrittene Budgets je Monat, Vertragsverlängerungen im Folgemonat - APScheduler im Anwendungsprozess, täglich 07:00 Europe/Berlin, räumt zugleich abgelaufene Sitzungen auf - Duplikatsschutz über den Zieltag statt den Versandtag; fehlgeschlagener Versand wird beim nächsten Lauf erneut versucht - Endpunkte für Regeln, Protokoll, Testversand und sofortigen Lauf - Einstellungsseite mit Einrichtungsstand, Regelpflege und Protokoll - 25 neue Backend-Tests (260 gesamt), 9 neue Frontend-Tests (74 gesamt) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
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.
Status: in Entwicklung. Siehe CHANGELOG.md.
Screenshots
Platzhalter – werden ergänzt, sobald die Oberfläche steht.
| Dashboard | Cashflow-Kalender | Firmen |
|---|---|---|
| folgt | folgt | folgt |
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 (lokale Entwicklung)
Voraussetzungen: Python 3.12, uv, Node 20+, eine erreichbare PostgreSQL-17-Instanz.
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:
make fe-install
make fe-dev # http://localhost:5173
Nützliche Ziele: make check (alle Prüfungen), make test, make lint,
make format, make fe-lint, make fe-test, make fe-build.
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:
- simple-icons – rund 3.500 Marken, im Repository unter
backend/app/assets/simple_icons.json.gzmitgeliefert. Kein Schlüssel, kein Netzzugriff. Neu erzeugen mitmake vendor-icons. - logo.dev – nur mit
LOGODEV_API_KEY. - Brandfetch – nur mit
BRANDFETCH_API_KEYund hinterlegter Domain. - Favicon – über den Google-Dienst, wenn eine Domain bekannt oder aus dem Namen ableitbar ist.
- 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