3 Commits
Author SHA1 Message Date
Jordy Hoogeveen e315039c35 bugfix 2026-09-11 14:05:55 +02:00
Jordy Hoogeveen 0d41c0af0b bugfix 2026-09-10 21:33:44 +02:00
Jordy Hoogeveen 04e70208bd bugfix 2026-09-10 16:51:06 +02:00
7 changed files with 126 additions and 22 deletions
+34 -8
View File
@@ -240,6 +240,7 @@ tag op een Raspberry Pi en op een x86-server werkt.
| Bestand | Wat | Wanneer | | Bestand | Wat | Wanneer |
|---|---|---| |---|---|---|
| `Changelog.md` | kop `## [Niet vrijgegeven]` → `## [x.x.x] - datum` | vóór de build | | `Changelog.md` | kop `## [Niet vrijgegeven]` → `## [x.x.x] - datum` | vóór de build |
| Git-tag | `vx.x.x` op `HEAD`, gepusht naar origin | stap 4 |
| `frontend/package.json` | versie gelijkgetrokken met de root `package.json` | vóór de build | | `frontend/package.json` | versie gelijkgetrokken met de root `package.json` | vóór de build |
| `README.md` | de `versie-x.x.x`-badge bovenaan | vóór de build | | `README.md` | de `versie-x.x.x`-badge bovenaan | vóór de build |
| `package.json` (root) | versie → `x.x.(x+1)-dev` voor de volgende cyclus | **ná** het publiceren | | `package.json` (root) | versie → `x.x.(x+1)-dev` voor de volgende cyclus | **ná** het publiceren |
@@ -332,9 +333,11 @@ De Express-app serveert zowel de API als de gebouwde frontend:
| Verzoek | Wat de app doet | | Verzoek | Wat de app doet |
|---|---| |---|---|
| `/api/...` | REST API (rate limited) | | `/api/...` | REST API (rate limited, `Cache-Control: no-store`) |
| `/assets/...` | statische build uit `STATIC_DIR`, 1 jaar `immutable` gecached | | `/assets/...` | gehashte build-bestanden uit `STATIC_DIR`, 1 jaar `immutable` |
| al het overige | `index.html` — fallback voor React-routing | | `/favicon.svg`, `/icons/...` | vaste namen, dus `max-age=0, must-revalidate` |
| pad mét extensie zonder bestand | echte 404, niet `index.html` |
| al het overige | `index.html` — fallback voor React-routing, `no-store` |
`STATIC_DIR` staat standaard op `/app/public`; daar zet de `Dockerfile` de `STATIC_DIR` staat standaard op `/app/public`; daar zet de `Dockerfile` de
Vite-build neer. Bestaat die map niet (bijv. in dev), dan serveert de app Vite-build neer. Bestaat die map niet (bijv. in dev), dan serveert de app
@@ -380,6 +383,12 @@ De backend slaat alle data op in **PostgreSQL**.
> PostgreSQL 18 gebruikt `PGDATA=/var/lib/postgresql/18/docker`. Het volume is gemount op `/var/lib/postgresql` zodat data bewaard blijft na een container restart. > PostgreSQL 18 gebruikt `PGDATA=/var/lib/postgresql/18/docker`. Het volume is gemount op `/var/lib/postgresql` zodat data bewaard blijft na een container restart.
Daarnaast heeft de app zelf een backup onder **Instellingen**: die schrijft een
ZIP met de data van de *ingelogde* gebruiker (`data.json` + `meta.json`) en leest
zo'n ZIP ook weer in. Handig per gebruiker, maar het is geen servercopie — accounts,
wachtwoordhashes en de data van andere gebruikers zitten er niet in. Voor een
volledige backup gebruik je `pg_dump` hieronder.
**Backup maken:** **Backup maken:**
```bash ```bash
@@ -406,12 +415,25 @@ docker exec -i vibefinance_postgres psql -U vibefinance -d vibefinance < backup-
De eerste geregistreerde gebruiker krijgt automatisch de **Admin**-rol. Verdere accounts kunnen via de app aangemaakt worden (Gebruikersbeheer, Admin vereist) of via de API. De eerste geregistreerde gebruiker krijgt automatisch de **Admin**-rol. Verdere accounts kunnen via de app aangemaakt worden (Gebruikersbeheer, Admin vereist) of via de API.
Rollen: ### Rollen
| Rol | Rechten |
De rolkeuze in Gebruikersbeheer biedt drie waarden, maar de backend dwingt er op
dit moment één af:
| Rol | Wat de code ermee doet |
|---|---| |---|---|
| `Admin` | Alles, inclusief gebruikersbeheer | | `Admin` | Enige rol die wordt gecontroleerd: `requireAdmin` in `middleware/auth.js` bewaakt alle routes onder `/api/users` |
| `Bewerker` | Data lezen en bewerken | | `Bewerker` | Geen eigen rechten — gelijk aan `Viewer` |
| `Viewer` | Alleen lezen | | `Viewer` | Standaardrol voor een nieuw account; mag de eigen data lezen én schrijven |
Dat `Viewer` toch mag schrijven is geen gat in de afscherming: `/api/data` werkt
per gebruiker en gaat altijd over de rij van het eigen token (`req.user.sub`).
Niemand ziet dus data van een ander. Wat ontbreekt is een read-only account —
wil je dat, dan is er een controle op `rol` nodig bij `PUT`/`DELETE /api/data`.
> De "Gebruikers"-knop in de navigatiebalk staat bij iedereen in beeld; bij een
> niet-Admin antwoordt `/api/users` met `403` en blijft de lijst leeg. De
> afscherming zit dus in de API, niet in de navigatie.
--- ---
@@ -473,6 +495,10 @@ Tags:
x.x.x-dev development — specifieke dev versie x.x.x-dev development — specifieke dev versie
``` ```
> Image-tags hebben géén `v`-prefix (`1.0.4`), Git-tags wél (`v1.0.4`). Dat is
> geen inconsistentie maar de conventie aan beide kanten: container-registries
> taggen op het kale nummer, Git-releases met `v`.
Inloggen op de registry: Inloggen op de registry:
```bash ```bash
+47
View File
@@ -2,6 +2,53 @@
## [Niet vrijgegeven] ## [Niet vrijgegeven]
### Gewijzigd
- De documentatie is gelijkgetrokken met de code. `README.md`: de bestandsboom
miste `backend/src/logger.js`, `docker/check-updates.mjs`,
`frontend/public/`, `InstellingenModal.jsx`, `LoginBackground.jsx` en de
losse bestanden in `utils/`, en noemde de update-route `update-check` terwijl
het bestand `routes/update.js` heet. De schermentabel gebruikt nu de labels
zoals ze in `constants/index.js` staan ("Schuld", "Voortgang Schuld & EV") en
vermeldt **Instellingen** — thema plus backup/restore via ZIP, dat nergens in
de README stond
- De tabel "wat de app met een verzoek doet" (in beide bestanden) noemde alleen
`/assets/...` en de SPA-fallback. `server.js` kent vier gevallen: `no-store`
op de API en op HTML, een jaar `immutable` op de gehashte bundels,
`must-revalidate` op bestanden met een vaste naam (`favicon.svg`, `icons/*`)
en een echte 404 op een pad mét extensie waar geen bestand achter zit
- `BEHEER.md` beschreef `Bewerker` als "data lezen en bewerken" en `Viewer` als
"alleen lezen". Zo werkt het niet: `requireAdmin` is de enige rolcontrole in
de code en bewaakt alleen `/api/users`. Een `Viewer` mag daardoor gewoon zijn
eigen data schrijven — geen gat in de afscherming, want `/api/data` gaat altijd
over de rij van het eigen token, maar wél iets anders dan er stond. De sectie
zegt nu wat de code doet en wat er nodig is voor een echt read-only account
- `README.md` en `BEHEER.md` wijzen bij backup naar het juiste niveau: de ZIP
onder Instellingen bevat alleen de data van de ingelogde gebruiker, `pg_dump`
is de volledige servercopie
- `DATABASE_URL` staat in de variabelentabel van `README.md`. Hij wordt door
`docker-compose.yml` samengesteld uit de `POSTGRES_*` waarden, dus zelf zetten
is alleen nodig buiten Compose
## [1.0.4] - 2026-09-10
### Gewijzigd
- Git-tags krijgen weer een `v`-prefix (`v1.0.4`), net als t/m `v1.0.0`. Sinds
1.0.1 was dat `1.0.1`, `1.0.2`, `1.0.3` — en omdat Gitea de tag op de
release-pagina toont, stond daar het kale nummer terwijl de release-naam wél
`v1.0.3` was. De image-tags in de registry houden bewust géén `v`; dat is de
conventie voor container-tags. De update-check verdraagt beide: backend en
frontend strippen een leidende `v` voordat ze versies vergelijken
### Opgelost
- Het omzetten van de changelog-kop slokte de lege regel eronder op, waardoor
`## [x.x.x] - datum` direct tegen `### Toegevoegd` aan kwam te staan. Oorzaak
was `\s*$` in het zoekpatroon: `\s` matcht ook newlines. Het patroon eindigt nu
op `[ \t]*(?=\r?$)` — een lookahead, want dit bestand heeft CRLF en met alleen
`[ \t]*$` matchte de kop helemaal niet meer (de `\r` zit ertussen). Zo blijft
de match binnen één regel én blijft het regeleinde ongemoeid
## [1.0.3] - 2026-09-10
### Toegevoegd ### Toegevoegd
- `release.ps1` zet de changelog-kop `## [Niet vrijgegeven]` nu zelf om naar - `release.ps1` zet de changelog-kop `## [Niet vrijgegeven]` nu zelf om naar
`## [x.x.x] - datum`, net zoals het al deed met `frontend/package.json` en de `## [x.x.x] - datum`, net zoals het al deed met `frontend/package.json` en de
+30 -10
View File
@@ -10,7 +10,7 @@ Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer
![Node.js](https://img.shields.io/badge/Node.js_22-339933?style=flat&logo=node.js&logoColor=white) ![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) ![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) ![Docker](https://img.shields.io/badge/Docker_Compose-2496ED?style=flat&logo=docker&logoColor=white)
![Version](https://img.shields.io/badge/versie-1.0.2-8b5cf6?style=flat) ![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) ![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white)
<img src="docs/screenshot-dashboard.png" alt="VibeFinance dashboard" width="820"> <img src="docs/screenshot-dashboard.png" alt="VibeFinance dashboard" width="820">
@@ -168,14 +168,20 @@ build — anders zou de release met een placeholder als notes online komen.
|---|---| |---|---|
| 📊 **Dashboard** | Overzicht van totaal eigen vermogen, schulden en netto vermogen | | 📊 **Dashboard** | Overzicht van totaal eigen vermogen, schulden en netto vermogen |
| 💰 **Eigen vermogen** | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek | | 💰 **Eigen vermogen** | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek |
| 📉 **Schulden** | Schulden bijhouden per blok met afbetalingsoverzicht | | 📉 **Schuld** | Schulden bijhouden per blok met afbetalingsoverzicht |
| 📈 **Voortgang** | Grafieken en trends van EV en schuld over tijd (apart bij te houden) | | 📈 **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) | | 👥 **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 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 de rechterrand van een kolomkop. De breedtes worden per tabel in de browser
bewaard; dubbelklik op de greep voor de standaardbreedte. 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 ## 🏗️ Architectuur
@@ -193,27 +199,33 @@ vibefinance/
├── release.ps1 / release.sh # Release (test, push, tag, Gitea-release) ├── release.ps1 / release.sh # Release (test, push, tag, Gitea-release)
│ │
├── docker/ ├── docker/
│ └── dev-entrypoint.sh # Start API + Vite samen in de dev-container │ ├── 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/ ├── docs/
│ └── screenshot-dashboard.png # Screenshot bovenaan deze README │ └── screenshot-dashboard.png # Screenshot bovenaan deze README
│ │
├── frontend/ # React / Vite SPA (build → dist) ├── frontend/ # React / Vite SPA (build → dist)
│ ├── vite.config.js # /api proxy naar de API in dezelfde container │ ├── 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/ │ └── src/
│ ├── App.jsx # Root – wiring + idle-logout │ ├── App.jsx # Root – wiring + idle-logout
│ ├── App.css # Reset + losse klassen (o.a. .kolom-grip) │ ├── App.css # Reset + losse klassen (o.a. .kolom-grip)
│ ├── main.jsx │ ├── main.jsx
│ ├── version.js # Draaiende versie (__APP_VERSION__ uit de build) │ ├── version.js # Draaiende versie (__APP_VERSION__ uit de build)
│ ├── constants/ # Kleuren, tabs, begindata │ ├── constants/ # Kleuren, tabs, begindata
│ ├── utils/ # Formatters, helpers │ ├── utils/ # format.js (bedragen/datums), storage.js
│ ├── hooks/ # useTheme, useUpdateCheck, useKolomBreedtes │ ├── hooks/ # useTheme, useUpdateCheck, useKolomBreedtes
│ ├── context/ # AppContext (global state + API sync) │ ├── context/ # AppContext (global state + API sync + backup)
│ ├── components/ │ ├── components/
│ │ ├── ui/ # Primitieve UI (VCard, DonutChart…) │ │ ├── ui/ # Primitieve UI (VCard, DonutChart…)
│ │ ├── LoginPage.jsx │ │ ├── LoginPage.jsx
│ │ ├── LoginBackground.jsx
│ │ ├── NavBar.jsx │ │ ├── NavBar.jsx
│ │ ├── ProfielPopup.jsx │ │ ├── ProfielPopup.jsx
│ │ ├── InstellingenModal.jsx # Thema + backup/restore
│ │ └── GebruikersBeheer.jsx │ │ └── GebruikersBeheer.jsx
│ └── pages/ │ └── pages/
│ ├── DashboardTab.jsx │ ├── DashboardTab.jsx
@@ -226,10 +238,11 @@ vibefinance/
└── src/ └── src/
├── server.js # Entry point: API op /api, SPA op / ├── server.js # Entry point: API op /api, SPA op /
├── db.js # PostgreSQL pool + initDb() schema-migraties ├── db.js # PostgreSQL pool + initDb() schema-migraties
├── logger.js # pino (pino-pretty buiten productie)
├── config/ # Omgevingsvariabelen (incl. STATIC_DIR) ├── config/ # Omgevingsvariabelen (incl. STATIC_DIR)
├── constants/ # Begindata (initialData.js) ├── constants/ # Begindata (initialData.js)
├── middleware/ # auth.js, errorHandler.js ├── middleware/ # auth.js, errorHandler.js
├── routes/ # auth, data, users, health, update-check ├── routes/ # auth, data, users, health, update
└── stores/ # userStore, dataStore └── stores/ # userStore, dataStore
``` ```
@@ -245,9 +258,11 @@ frontend en API.
| Verzoek | Wat de app doet | | Verzoek | Wat de app doet |
|---|---| |---|---|
| `/api/...` | REST API (rate limited) | | `/api/...` | REST API (rate limited, `Cache-Control: no-store`) |
| `/assets/...` | Statische build, 1 jaar `immutable` gecached | | `/assets/...` | Gehashte build-bestanden, 1 jaar `immutable` gecached |
| al het overige | `index.html` — fallback voor React-routing | | `/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 |
--- ---
@@ -295,6 +310,7 @@ Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via
| `JWT_SECRET` | — | **Verplicht** — minimaal 64 tekens | | `JWT_SECRET` | — | **Verplicht** — minimaal 64 tekens |
| `POSTGRES_USER` | `vibefinance` | Database gebruikersnaam | | `POSTGRES_USER` | `vibefinance` | Database gebruikersnaam |
| `POSTGRES_DB` | `vibefinance` | Database naam | | `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 | | `JWT_EXPIRES` | `7d` | Geldigheidsduur token |
| `CORS_ORIGIN` | `http://localhost` | Externe domeinnaam (één origin, dus zelden nodig) | | `CORS_ORIGIN` | `http://localhost` | Externe domeinnaam (één origin, dus zelden nodig) |
| `APP_PORT` | `3400` | Poort op de host (container luistert op 3001) | | `APP_PORT` | `3400` | Poort op de host (container luistert op 3001) |
@@ -309,6 +325,10 @@ Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via
- Wachtwoorden gehasht met bcrypt (cost factor 12) - Wachtwoorden gehasht met bcrypt (cost factor 12)
- JWT authenticatie met instelbare vervaltijd - 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 - Frontend en API op één origin — geen interne proxy, geen CORS-uitzonderingen
- Content-Security-Policy via Helmet, toegespitst op wat de app laadt - Content-Security-Policy via Helmet, toegespitst op wat de app laadt
- Database bereikbaar alleen binnen het interne Docker netwerk - Database bereikbaar alleen binnen het interne Docker netwerk
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "vibefinance-frontend", "name": "vibefinance-frontend",
"version": "1.0.2", "version": "1.0.4",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "vibefinance", "name": "vibefinance",
"version": "1.0.3", "version": "1.0.5",
"description": "VibeFinance — persoonlijk vermogensbeheer", "description": "VibeFinance — persoonlijk vermogensbeheer",
"private": true, "private": true,
"repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance", "repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance",
+13 -2
View File
@@ -91,7 +91,13 @@ if ($appVersion -match "-dev$") {
exit 1 exit 1
} }
$gitTag = "$appVersion" # Git-tag met `v`-prefix, net als de release-naam hieronder en net als de tags
# t/m v1.0.0. Gitea toont de tag op de release-pagina, dus zonder prefix stond
# daar het kale nummer. De image-tags in de registry houden bewust géén `v`:
# dat is de conventie voor container-tags.
# De update-check verdraagt beide: zowel backend/src/routes/update.js als
# useUpdateCheck.js strippen een leidende `v` voordat ze versies vergelijken.
$gitTag = "v$appVersion"
# ── Registry tags (één image voor frontend + backend) ───────────────────── # ── Registry tags (één image voor frontend + backend) ─────────────────────
$appBase = "$registry/$appImage" $appBase = "$registry/$appImage"
@@ -167,7 +173,12 @@ $nieuweKop = "## [$appVersion] - $(Get-Date -Format 'yyyy-MM-dd')"
$changelogTxt = if (Test-Path $changelogPath) { [System.IO.File]::ReadAllText($changelogPath) } else { "" } $changelogTxt = if (Test-Path $changelogPath) { [System.IO.File]::ReadAllText($changelogPath) } else { "" }
$kopPatroon = "(?m)^##\s*\[" + [regex]::Escape($appVersion) + "\]" $kopPatroon = "(?m)^##\s*\[" + [regex]::Escape($appVersion) + "\]"
$onderwegPatroon = "(?m)^##\s*\[\s*Niet vrijgegeven\s*\]\s*$" # Niet `\s*$`: `\s` matcht ook newlines, en dan slokt de match de lege regel ná
# de kop op — die verdween daardoor bij het omzetten. En niet `[ \t]*$` alleen:
# dit bestand heeft CRLF (zie .gitattributes), dus `$` staat achter een `\r` die
# geen space of tab is. De lookahead laat het regeleinde buiten de match, zodat
# de CRLF intact blijft.
$onderwegPatroon = "(?m)^##[ \t]*\[[ \t]*Niet vrijgegeven[ \t]*\][ \t]*(?=\r?$)"
# Eerst alleen in het geheugen omzetten. Schrijven gebeurt na de controle # Eerst alleen in het geheugen omzetten. Schrijven gebeurt na de controle
# hieronder: bij een lege `[Niet vrijgegeven]`-sectie zou de kop anders al # hieronder: bij een lege `[Niet vrijgegeven]`-sectie zou de kop anders al