Files
VibeFinance/README.md
T
Jordy HoogeveenandClaude Opus 5 bfec3a25f3 Migratie naar 2-container opzet (app + database)
De Express-app serveert nu zelf de gebouwde Vite-frontend op / met een
SPA-fallback naar index.html; de aparte nginx-container is vervallen.
Bedoeld voor self-hosting op een Raspberry Pi: één image, één origin,
geen interne proxy en geen CORS tussen frontend en API.

Docker
- Nieuwe multi-stage Dockerfile in de root met targets test, development
  en production; frontend/Dockerfile, frontend/nginx-spa.conf en
  backend/Dockerfile verwijderd
- docker-compose.yml: alleen app + postgres, app publiceert APP_PORT
  (standaard 3400) naar containerpoort 3001
- docker-compose.dev.yml: dev-container draait API (node --watch) en de
  Vite dev-server samen via docker/dev-entrypoint.sh, hot reload intact
- .dockerignore, .env.example, .gitignore en .gitattributes toegevoegd

Backend
- express.static + SPA-fallback op STATIC_DIR (default /app/public), met
  immutable caching voor gehashte assets en no-store voor index.html
- Rate limiting alleen nog op /api — statische bestanden liepen anders na
  200 requests tegen de limiet aan
- Helmet CSP toegespitst op wat de frontend laadt (Google Fonts, inline
  styles, base64-avatars); upgrade-insecure-requests uit voor intern http
- Schema-init verplaatst naar initDb(), niet meer bij import van db.js

Tests en CI/CD
- backend/test/app.test.js: 11 tests (node --test) voor API-routes,
  SPA-fallback, cache- en security-headers, zonder database
- test.ps1 / test.sh: backend-tests, productiebuild en smoketest op de
  echte app + postgres stack
- azure-pipelines.yml: Test -> Build (incl. smoketest) -> Publish
  (multi-arch amd64 + arm64, alleen op main/master of een tag)

Scripts en documentatie
- dev.ps1/dev.sh met targets app en postgres, acties logs en test;
  dev.ps1 up wist de dev-database niet langer (alleen dev.ps1 down)
- release.ps1/release.sh bouwen één image voor amd64 + arm64 en draaien
  de testsuite vóór het publiceren
- README.md en BEHEER.md bijgewerkt voor de nieuwe structuur

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 11:51:29 +02:00

275 lines
9.9 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.
<div align="center">
# 💜 VibeFinance
**Persoonlijk vermogensdashboard**
Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer
![React](https://img.shields.io/badge/React_18-20232A?style=flat&logo=react&logoColor=61DAFB)
![Node.js](https://img.shields.io/badge/Node.js_22-339933?style=flat&logo=node.js&logoColor=white)
![PostgreSQL](https://img.shields.io/badge/PostgreSQL_18-4169E1?style=flat&logo=postgresql&logoColor=white)
![Docker](https://img.shields.io/badge/Docker_Compose-2496ED?style=flat&logo=docker&logoColor=white)
![Version](https://img.shields.io/badge/versie-0.1.4-8b5cf6?style=flat)
![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white)
</div>
---
## 🤖 Gebouwd met Claude.ai
Deze applicatie is volledig ontworpen en ontwikkeld met behulp van [Claude.ai](https://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:
```env
POSTGRES_USER=vibefinance
POSTGRES_PASSWORD=kies_een_sterk_wachtwoord
JWT_SECRET=minimaal_64_willekeurige_tekens
APP_PORT=3400
```
```bash
# Genereer een veilige JWT_SECRET
openssl rand -hex 64
```
```bash
cp .env.example .env # en vul de waarden in
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)
```powershell
.\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:
```powershell
.\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 up` laat de dev-database staan; alleen `.\dev.ps1 down` wist de volumes.
### Testen
```powershell
.\test.ps1 # backend-tests + productiebuild + smoketest
.\test.ps1 -Unit # alleen de backend-tests
.\test.ps1 -NoSmoke # tests + build, zonder de stack te starten
```
Alles loopt in Docker — Node op de host is niet nodig. De backend-tests draaien
zonder database via het `test`-target in de `Dockerfile`.
### Productie release
```powershell
$env:GITEA_TOKEN = "<token met release-rechten>"
.\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
├── azure-pipelines.yml # CI/CD: test → build+smoketest → publish
├── .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
│
├── 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
│ ├── 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 18 · Vite · Node.js 22 · Express 4 · 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` | — | Alleen voor een niet-publieke repo |
---
## 🔐 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 |
|---|---|---|
| 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` |
In CI draaien dezelfde stappen via `azure-pipelines.yml`.
---
## 🛠️ Beheer
Zie `BEHEER.md` voor commando's voor lokale ontwikkeling, releases, logs en database-toegang.
---
<div align="center">
Privé gebruik · gebouwd met [Claude.ai](https://claude.ai)
</div>