# πŸ’œ 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.4-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). **1. De bestanden op de server zetten.** `docker compose` leest `docker-compose.yml` uit de map waarin je het commando draait, dus dat bestand moet er eerst staan. De organisatie in Gitea is publiek, dus dit kan anoniem: ```bash git clone https://vibehoogie.duckdns.org/Vibe/VibeFinance.git cd VibeFinance ``` Wil je de repo niet op je server, dan zijn deze twee bestanden genoeg: ```bash mkdir -p vibefinance && cd vibefinance base=https://vibehoogie.duckdns.org/Vibe/VibeFinance/raw/branch/main curl -fsSLO $base/docker-compose.yml curl -fsSL $base/.env.example -o .env.example ``` **2. `.env` aanmaken** β€” 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 ``` **3. Starten:** ```bash cp .env.example .env # en vul de waarden in docker compose pull && docker compose up -d ``` Twee foutmeldingen en wat ze betekenen: - **`no configuration file provided: not found`** β€” je staat in een map zonder `docker-compose.yml`. Controleer met `ls docker-compose.yml`, of geef het bestand expliciet mee: `docker compose -f /pad/naar/docker-compose.yml up -d`. - **`401 Unauthorized`** of **"pull access denied"** β€” de organisatie staat niet meer op publiek. Dan alsnog inloggen: ```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. Het script regelt ook het bijwerken eromheen: de changelog-kop `## [Niet vrijgegeven]` wordt `## [x.x.x] - datum`, de versie in `frontend/package.json` en de badge hierboven gaan mee, en nΓ‘ het publiceren zet het de root `package.json` op `x.x.(x+1)-dev` voor de volgende cyclus. Beschrijft de changelog niets over deze versie, dan stopt het script vΓ³Γ³r de build β€” anders zou de release met een placeholder als notes online komen. --- ## πŸ—‚οΈ 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 | | πŸ“‰ **Schuld** | Schulden bijhouden per blok met afbetalingsoverzicht | | πŸ“ˆ **Voortgang Schuld & EV** | Grafieken en trends van EV en schuld over tijd (apart bij te houden) | | πŸ‘₯ **Gebruikers** | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) | | βš™οΈ **Instellingen** | Licht/donker thema, backup downloaden en backup terugzetten | 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. Backup en restore lopen via **Instellingen** in de navigatiebalk: "Backup maken" downloadt een ZIP (`data.json` + `meta.json`) met je eigen financiΓ«le data, "Backup terugzetten" leest zo'n ZIP weer in en schrijft hem via `/api/data` weg. Dat is de gebruikersvariant; een volledige database-dump staat in `BEHEER.md`. --- ## πŸ—οΈ 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 β”‚ β”œβ”€β”€ check-updates.mjs # Dependency-check tegen het npm-register β”‚ └── update-exceptions.json # Pakketten die bewust achterblijven, met reden β”‚ β”œβ”€β”€ 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 β”‚ β”œβ”€β”€ index.html # Vite-entry, verwijst naar /favicon.svg β”‚ β”œβ”€β”€ public/ # Ongebundeld: favicon.svg + icons/ β”‚ └── 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/ # format.js (bedragen/datums), storage.js β”‚ β”œβ”€β”€ hooks/ # useTheme, useUpdateCheck, useKolomBreedtes β”‚ β”œβ”€β”€ context/ # AppContext (global state + API sync + backup) β”‚ β”œβ”€β”€ components/ β”‚ β”‚ β”œβ”€β”€ ui/ # Primitieve UI (VCard, DonutChart…) β”‚ β”‚ β”œβ”€β”€ LoginPage.jsx β”‚ β”‚ β”œβ”€β”€ LoginBackground.jsx β”‚ β”‚ β”œβ”€β”€ NavBar.jsx β”‚ β”‚ β”œβ”€β”€ ProfielPopup.jsx β”‚ β”‚ β”œβ”€β”€ InstellingenModal.jsx # Thema + backup/restore β”‚ β”‚ └── 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 β”œβ”€β”€ logger.js # pino (pino-pretty buiten productie) β”œβ”€β”€ config/ # Omgevingsvariabelen (incl. STATIC_DIR) β”œβ”€β”€ constants/ # Begindata (initialData.js) β”œβ”€β”€ middleware/ # auth.js, errorHandler.js β”œβ”€β”€ routes/ # auth, data, users, health, update └── 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, `Cache-Control: no-store`) | | `/assets/...` | Gehashte build-bestanden, 1 jaar `immutable` gecached | | `/favicon.svg`, `/icons/...` | Vaste namen, dus `max-age=0, must-revalidate` | | pad mΓ©t extensie, geen bestand | echte 404 (geen HTML waar een icoon wordt verwacht) | | al het overige | `index.html` β€” fallback voor React-routing, niet gecached | --- ## πŸ”Œ 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 | | `DATABASE_URL` | β€” | Connectiestring; `docker-compose.yml` stelt hem samen uit de drie `POSTGRES_*` waarden, dus zelf zetten hoeft alleen buiten Compose | | `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 - Data is per gebruiker: `/api/data` leest en schrijft alleen de rij van de ingelogde gebruiker (`sub` uit het token) - Van de rollen wordt alleen **Admin** afgedwongen, op `/api/users` β€” zie de sectie Rollen in `BEHEER.md` - 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)