# πŸ’œ 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.11-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 # deps + backend-tests + build + imagescan + smoketest .\test.ps1 -Unit # dependency-check + backend-tests .\test.ps1 -NoSmoke # zonder de stack te starten (imagescan draait wΓ©l) .\test.ps1 -Strict # faal op achterlopende deps; scan ook op MEDIUM/LOW .\test.ps1 -SkipDeps # dependency-check overslaan (bijv. offline) .\test.ps1 -SkipScan # imagescan 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 ``` Stap 4 scant het gebouwde image met [Trivy](https://trivy.dev) en blokkeert bij CRITICAL of HIGH waarvoor een fix bestaat. Dat is iets anders dan stap 1: die kijkt naar de `package.json`'s, deze naar wat er daadwerkelijk in het image zit β€” de Alpine-pakketten en alles wat het base-image meelevert. Dat onderscheid is niet theoretisch. Bij de scan van 1.0.10 kwamen alle 39 bevindingen daarvandaan en geen enkele uit de applicatie zelf: | Bron | Aantal | Waar het vandaan kwam | |---|---|---| | `libssl3` + `libcrypto3` | 20 | Alpine in het node-image liep achter op 3.5.8-r0 | | npm's eigen dependencies | 19 | `tar`, `pacote`, `sigstore`, `brace-expansion`, `picomatch`, `ip-address` | Een dependency-check had er nul van gezien. Het productie-image doet daarom aan het eind van de build een `apk upgrade` en gooit npm, npx, corepack en yarn eruit: de container start met `node src/server.js` en gebruikt nooit een pakketbeheerder. Dat bracht de scan op nul, en houdt hem daar β€” npm's dependencies zijn een terugkerende bron die nu simpelweg niet meer in het image zit. `release.ps1` draait dezelfde scan vΓ³Γ³r het pushen, dus een image met openstaande CVE's komt de registry niet in. ### 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** | KPI's over inleg, vermogen, winst en ROI, de voortgang naar je doel, en de grafiek met vermogen tegen inleg | | πŸ’° **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** | Kleurthema, licht/donker, backup downloaden, backup terugzetten en export naar HTML | De grafiek "Ontwikkeling van Eigen Vermogen" op het dashboard tekent twee lijnen op één as: je netto waarde per voortgangsmoment, en daaronder de cumulatief geΓ―nvesteerde fiat. Het verschil tussen de twee is je rendement; de tooltip zet vermogen, inleg en resultaat per datum onder elkaar. Het laatste punt van de inleglijn is per definitie gelijk aan de KPI "Totaal geΓ―nvesteerde fiat" β€” beide rekenen het oudste voortgangsmoment als startinleg en tellen daar de stortingen per rij bij op. De x-as noemt maanden (`sep '26`), niet losse datums: de grafiek rekent uit hoeveel labels er naast elkaar passen en toont er zoveel, terugtellend vanaf het laatste punt zodat dat er altijd bij staat. Bij een langere reeks dunt hij vanzelf verder uit. De exacte dag lees je in de tooltip. De vermogenslijn is een Catmull-Rom spline; de inleglijn krijgt bewust rechte segmenten, omdat een spline tussen twee punten iets buiten hun bereik uitschiet en dat bij een reeks die nooit terugloopt een daling zou tekenen die er niet is. Beide lijnen delen de tekenhulp in [`frontend/src/utils/grafiek.js`](frontend/src/utils/grafiek.js). Onder **Instellingen** kies je een van de negen kleurthema's. Thema en licht/donker staan los van elkaar: het thema levert het accent, de schakelaar de ondergrond. De paletten staan in [`frontend/src/constants/themas.js`](frontend/src/constants/themas.js); `useTheme(themaId, darkMode)` leidt daar randen, invoervelden en schaduwen uit af. Groen, rood en goud veranderen bewust nooit mee β€” die betekenen winst, verlies en inleg, en betekenis hoort niet van een kleurkeuze af te hangen. Thema en modus worden per browser bewaard. 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`. Daarnaast staat er **"Exporteren naar HTML"**: één zelfdragend bestand met al je gegevens, bedoeld als laatste vangnet. Draait de app niet meer, is de database weg en ligt er geen backup, dan opent dit bestand nog steeds β€” in elke browser, op een computer zonder internet. Er zit niets van buiten in: de opmaak staat inline, er worden geen lettertypen, afbeeldingen of scripts geladen. Je ziet een samenvatting met de KPI's, eigen vermogen en schulden per categorie met subtotalen, de initiΓ«le schuld met wat er sinds de start is afgelost, de voortgangstabel en de historie. Alles wat je ooit hebt ingevoerd komt mee, ook wat de app zelf niet meer toont: stortingen op een inmiddels verwijderde categorie krijgen een eigen kolom (`Verwijderd (c9)`). De voortgangstabel is bij zes categorieΓ«n twaalf kolommen breed en scrollt horizontaal in plaats van kolommen af te kappen; de datumkolom blijft daarbij staan. Op print vervalt de scrollbalk en wordt de tabel kleiner gezet, zodat alle kolommen op papier alsnog passen. Het bestand is tegelijk een backup: onderin staat de complete ruwe data in een `