💜 VibeFinance
Persoonlijk vermogensdashboard
Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer
🤖 Gebouwd met Claude.ai
Deze applicatie is volledig ontworpen en ontwikkeld met behulp van Claude.ai — de AI-assistent van Anthropic. Van architectuur tot gebruikersinterface: alles is stap voor stap tot stand gekomen in samenwerking met Claude.
⚡ Quick Start
Vereisten: Docker Engine 24+ en Docker Compose v2+
De app draait in 2 containers: app (Express API + gebouwde frontend) en
postgres (database).
.env — kopieer .env.example en vul in:
POSTGRES_USER=vibefinance
POSTGRES_PASSWORD=kies_een_sterk_wachtwoord
JWT_SECRET=minimaal_64_willekeurige_tekens
APP_PORT=3400
# Genereer een veilige JWT_SECRET
openssl rand -hex 64
cp .env.example .env # en vul de waarden in
# Het app-image staat in een privé-registry, dus eerst inloggen:
docker login vibehoogie.duckdns.org # wachtwoord = Gitea-token met read:package
docker compose pull && docker compose up -d
Navigeer naar http://<server-ip>:3400 en registreer je account. De eerste geregistreerde gebruiker krijgt automatisch de Admin-rol.
De container luistert intern op poort 3001 en serveert daar zowel de frontend
(op /) als de API (op /api). APP_PORT bepaalt de poort op de host.
Development (hot reload)
.\dev.ps1
Eén dev-container draait de API (node --watch) én de Vite dev-server samen:
| Adres | Wat |
|---|---|
| http://localhost | de app in de browser (Vite, hot reload) |
| http://localhost:3001/api/health | de API rechtstreeks |
Vite proxyt /api naar de API in dezelfde container, dus je hebt geen CORS-
uitzonderingen nodig. Handige acties:
.\dev.ps1 up -Target app # alleen de app herbouwen en herstarten
.\dev.ps1 logs # logs van de app volgen
.\dev.ps1 down # stoppen én volumes wissen (dev-database weg)
.\dev.ps1 test # testsuite draaien
.\dev.ps1 uplaat de dev-database staan; alleen.\dev.ps1 downwist de volumes.
Testen
.\test.ps1 # dependency-check + backend-tests + build + smoketest
.\test.ps1 -Unit # dependency-check + backend-tests
.\test.ps1 -NoSmoke # zonder de stack te starten
.\test.ps1 -Strict # faal als een dependency achterloopt
.\test.ps1 -SkipDeps # dependency-check overslaan (bijv. offline)
Alles loopt in Docker — Node op de host is niet nodig. De backend-tests draaien
zonder database via het test-target in de Dockerfile.
Stap 1 vergelijkt elke dependency in frontend/ en backend/package.json met
de laatste versie in het npm-register, zodat je bij elke testronde ziet of er
updates open staan. Pakketten die bewust achterblijven staan met reden in
docker/update-exceptions.json en laten de
check niet falen:
frontend/package.json (9 dependencies)
[vast ] eslint ^9.39.5 → 10.9.1 eslint-plugin-react ondersteunt nog geen ESLint 10
actueel op de vastgezette versies na
backend/package.json (15 dependencies)
alles actueel
Productie release
# token: staat in .env.release, of eenmalig in de shell
$env:GITEA_TOKEN = "<token met write:repository>"
.\release.ps1
Draait de tests, bouwt het app-image voor amd64 + arm64 (Raspberry Pi),
pusht naar de registry, zet de Git-tag en publiceert de Gitea-release.
🗂️ Functionaliteit
| Scherm | Wat het doet |
|---|---|
| 📊 Dashboard | Overzicht van totaal eigen vermogen, schulden en netto vermogen |
| 💰 Eigen vermogen | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek |
| 📉 Schulden | Schulden bijhouden per blok met afbetalingsoverzicht |
| 📈 Voortgang | Grafieken en trends van EV en schuld over tijd (apart bij te houden) |
| 👥 Gebruikers | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) |
🏗️ Architectuur
vibefinance/
├── Dockerfile # Multi-stage: frontend build → app-image
├── .dockerignore
├── docker-compose.yml # Productie: app + postgres
├── docker-compose.dev.yml # Dev: app (API + Vite) + postgres
├── .env.example # Kopieer naar .env en vul in
├── package.json # Versienummer — single source of truth
├── dev.ps1 / dev.sh # Dev script (bouwen, starten, logs, pushen)
├── test.ps1 / test.sh # Testsuite (unit + build + smoketest)
├── release.ps1 / release.sh # Release (test, push, tag, Gitea-release)
│
├── docker/
│ └── dev-entrypoint.sh # Start API + Vite samen in de dev-container
│
├── docs/
│ └── screenshot-dashboard.png # Screenshot bovenaan deze README
│
├── frontend/ # React / Vite SPA (build → dist)
│ ├── vite.config.js # /api proxy naar de API in dezelfde container
│ └── src/
│ ├── App.jsx # Root – wiring + idle-logout
│ ├── main.jsx
│ ├── version.js # Draaiende versie (__APP_VERSION__ uit de build)
│ ├── constants/ # Kleuren, tabs, begindata
│ ├── utils/ # Formatters, helpers
│ ├── hooks/ # useTheme, useUpdateCheck
│ ├── context/ # AppContext (global state + API sync)
│ ├── components/
│ │ ├── ui/ # Primitieve UI (VCard, DonutChart…)
│ │ ├── LoginPage.jsx
│ │ ├── NavBar.jsx
│ │ ├── ProfielPopup.jsx
│ │ └── GebruikersBeheer.jsx
│ └── pages/
│ ├── DashboardTab.jsx
│ ├── EigenVermogenTab.jsx
│ ├── SchuldTab.jsx
│ └── VoortgangTab.jsx
│
└── backend/ # Express REST API + statische frontend
├── test/ # node --test integratietests
└── src/
├── server.js # Entry point: API op /api, SPA op /
├── db.js # PostgreSQL pool + initDb() schema-migraties
├── config/ # Omgevingsvariabelen (incl. STATIC_DIR)
├── constants/ # Begindata (initialData.js)
├── middleware/ # auth.js, errorHandler.js
├── routes/ # auth, data, users, health, update-check
└── stores/ # userStore, dataStore
Stack: React 19 · Vite 8 · Node.js 22 · Express 5 · PostgreSQL 18 · JWT · bcrypt · Docker Compose v2
Waarom 2 containers
De frontend werd eerder door een eigen nginx-container geserveerd. Voor self-hosting op een Raspberry Pi is dat een container te veel: de Express-app serveert de statische build nu zelf, dus er is geen aparte webserver en geen interne proxy meer nodig. Dat betekent ook: één origin, dus geen CORS tussen frontend en API.
| Verzoek | Wat de app doet |
|---|---|
/api/... |
REST API (rate limited) |
/assets/... |
Statische build, 1 jaar immutable gecached |
| al het overige | index.html — fallback voor React-routing |
🔌 API
Beveiligde endpoints vereisen Authorization: Bearer <token>. Rate limiting: 20 req/15 min op auth-routes, 200 req/15 min op de rest van /api (statische bestanden zijn uitgezonderd).
| Route | Methoden | Omschrijving |
|---|---|---|
/api/auth/register |
POST | Account aanmaken (eerste user = Admin) |
/api/auth/login |
POST | Inloggen, JWT token terug |
/api/auth/me |
GET, PATCH | Profiel ophalen en bijwerken |
/api/auth/me/password |
POST | Wachtwoord wijzigen |
/api/data |
GET, PUT, DELETE | Financiële data per gebruiker |
/api/users |
GET, POST, PATCH, DELETE | Gebruikersbeheer (Admin only) |
/api/health |
GET | Status + uptime |
/api/update-check |
GET | Laatste release (voor de update-melding) |
🗄️ Database
| Tabel | Omschrijving |
|---|---|
users |
Accounts met bcrypt hash, rol, actief-vlag en aanmaakdatum |
user_data |
Financiële data per gebruiker (JSON) |
| Omgeving | Volume | Mount pad |
|---|---|---|
| Productie | vibefinance_pgdata |
/var/lib/postgresql |
| Development | postgres_dev_data |
/var/lib/postgresql |
PostgreSQL 18 gebruikt
PGDATA=/var/lib/postgresql/18/docker— het volume is daarom gemount op/var/lib/postgresql(niet/data).
Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via
initDb() in backend/src/db.js.
⚙️ Omgevingsvariabelen
| Variabele | Standaard | Omschrijving |
|---|---|---|
POSTGRES_PASSWORD |
— | Verplicht |
JWT_SECRET |
— | Verplicht — minimaal 64 tekens |
POSTGRES_USER |
vibefinance |
Database gebruikersnaam |
POSTGRES_DB |
vibefinance |
Database naam |
JWT_EXPIRES |
7d |
Geldigheidsduur token |
CORS_ORIGIN |
http://localhost |
Externe domeinnaam (één origin, dus zelden nodig) |
APP_PORT |
3400 |
Poort op de host (container luistert op 3001) |
PORT |
3001 |
Poort binnen de container |
STATIC_DIR |
/app/public |
Map met de frontend-build |
UPDATE_CHECK_URL |
Gitea releases-API | Bron update-melding (leeg = uit) |
UPDATE_CHECK_TOKEN |
— | Nodig zolang de Gitea-repo privé is; scope read:repository |
🔐 Beveiliging
- Wachtwoorden gehasht met bcrypt (cost factor 12)
- JWT authenticatie met instelbare vervaltijd
- Frontend en API op één origin — geen interne proxy, geen CORS-uitzonderingen
- Content-Security-Policy via Helmet, toegespitst op wat de app laadt
- Database bereikbaar alleen binnen het interne Docker netwerk
- App-container draait als non-root
- Rate limiting op
/api(statische bestanden uitgezonderd)
🧪 Testen
| Laag | Wat | Commando |
|---|---|---|
| Dependencies | Staan package.json's op de laatste versies |
.\test.ps1 -Unit |
| Backend | API-routes, SPA-fallback, cache- en security-headers | .\test.ps1 -Unit |
| Build | Vite-build + productie-image | .\test.ps1 -NoSmoke |
| Integratie | Smoketest op de echte app + postgres stack | .\test.ps1 |
🛠️ Beheer
Zie BEHEER.md voor commando's voor lokale ontwikkeling, releases, logs en
database-toegang — en voor de sectie Tokens: welk Gitea-token op welke
machine hoort, met welke scopes, en wat een 401, 403 of 404 van Gitea betekent.
Privé gebruik · gebouwd met Claude.ai