# πŸ’œ VibeFinance **Persoonlijk vermogensdashboard** Eigen vermogen Β· Schulden Β· Voortgang Β· Gebruikersbeheer ![React](https://img.shields.io/badge/React_19-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-1.0.1-8b5cf6?style=flat) ![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white) VibeFinance dashboard
--- ## πŸ€– 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 ``` De `Vibe`-organisatie in Gitea is publiek, dus een pull werkt anoniem β€” geen `docker login` nodig. Krijg je toch `401 Unauthorized` of "pull access denied", dan staat de organisatie weer op privΓ©: ```bash docker login vibehoogie.duckdns.org # wachtwoord = Gitea-token met read:package ``` Navigeer naar `http://: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 # 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`](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 ```powershell # token: staat in .env.release, of eenmalig in de shell # één Gitea-token met write:package (push) Γ©n write:repository (release) $env:GITEA_TOKEN = "" .\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) | De kolommen in de tabellen op Eigen vermogen en Voortgang zijn versleepbaar: pak de rechterrand van een kolomkop. De breedtes worden per tabel in de browser bewaard; dubbelklik op de greep voor de standaardbreedte. --- ## πŸ—οΈ 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 β”‚ β”œβ”€β”€ App.css # Reset + losse klassen (o.a. .kolom-grip) β”‚ β”œβ”€β”€ main.jsx β”‚ β”œβ”€β”€ version.js # Draaiende versie (__APP_VERSION__ uit de build) β”‚ β”œβ”€β”€ constants/ # Kleuren, tabs, begindata β”‚ β”œβ”€β”€ utils/ # Formatters, helpers β”‚ β”œβ”€β”€ hooks/ # useTheme, useUpdateCheck, useKolomBreedtes β”‚ β”œβ”€β”€ 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 `. 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` | β€” | Optioneel: de Gitea-repo is publiek, dus de check werkt anoniem. Alleen nodig als de organisatie weer privΓ© wordt; 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**: waarom één Gitea-token op het werkstation genoeg is, welke scopes het nodig heeft, en wat een 401, 403 of 404 van Gitea betekent. ---
PrivΓ© gebruik Β· gebouwd met [Claude.ai](https://claude.ai)