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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3c67381de9
commit
bfec3a25f3
@@ -27,13 +27,16 @@ Deze applicatie is volledig ontworpen en ontwikkeld met behulp van [Claude.ai](h
|
||||
|
||||
> 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
|
||||
FRONTEND_PORT=3300
|
||||
APP_PORT=3400
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -42,10 +45,14 @@ 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>:3300` en registreer je account. De **eerste** geregistreerde gebruiker krijgt automatisch de Admin-rol.
|
||||
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)
|
||||
|
||||
@@ -53,9 +60,45 @@ Navigeer naar `http://<server-ip>:3300` en registreer je account. De **eerste**
|
||||
.\dev.ps1
|
||||
```
|
||||
|
||||
Frontend bereikbaar op **http://localhost** (poort 80 → Vite dev server op 5173).
|
||||
Eén dev-container draait de API (`node --watch`) én de Vite dev-server samen:
|
||||
|
||||
> **Let op:** `.\dev.ps1 up` en `.\dev.ps1 down` wissen de development-database (volumes). Gebruik voor een gewone herstart `docker compose -f docker-compose.dev.yml down` gevolgd door `docker compose -f docker-compose.dev.yml up -d --build`.
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -75,22 +118,28 @@ Frontend bereikbaar op **http://localhost** (poort 80 → Vite dev server op 517
|
||||
|
||||
```
|
||||
vibefinance/
|
||||
├── docker-compose.yml # Productie orchestratie (pre-built images)
|
||||
├── docker-compose.dev.yml # Standalone dev stack (hot reload)
|
||||
├── 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 script (bouwen, starten, pushen)
|
||||
├── 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)
|
||||
│
|
||||
├── frontend/ # React / Vite SPA
|
||||
│ ├── Dockerfile # Multi-stage: dev → build → nginx
|
||||
│ ├── nginx-spa.conf # SPA fallback + /api proxy naar backend
|
||||
│ ├── vite.config.js
|
||||
├── 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
|
||||
│ ├── hooks/ # useTheme, useUpdateCheck
|
||||
│ ├── context/ # AppContext (global state + API sync)
|
||||
│ ├── components/
|
||||
│ │ ├── ui/ # Primitieve UI (VCard, DonutChart…)
|
||||
@@ -104,25 +153,39 @@ vibefinance/
|
||||
│ ├── SchuldTab.jsx
|
||||
│ └── VoortgangTab.jsx
|
||||
│
|
||||
└── backend/ # Express REST API
|
||||
├── Dockerfile # Multi-stage: dev → production (non-root)
|
||||
└── backend/ # Express REST API + statische frontend
|
||||
├── test/ # node --test integratietests
|
||||
└── src/
|
||||
├── server.js # Entry point
|
||||
├── db.js # PostgreSQL connectie + schema migraties
|
||||
├── config/ # Omgevingsvariabelen
|
||||
├── 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
|
||||
├── 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 globaal.
|
||||
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 |
|
||||
|---|---|---|
|
||||
@@ -133,6 +196,7 @@ Beveiligde endpoints vereisen `Authorization: Bearer <token>`. Rate limiting: 20
|
||||
| `/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) |
|
||||
|
||||
---
|
||||
|
||||
@@ -150,6 +214,9 @@ Beveiligde endpoints vereisen `Authorization: Bearer <token>`. Rate limiting: 20
|
||||
|
||||
> 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
|
||||
@@ -161,8 +228,12 @@ Beveiligde endpoints vereisen `Authorization: Bearer <token>`. Rate limiting: 20
|
||||
| `POSTGRES_USER` | `vibefinance` | Database gebruikersnaam |
|
||||
| `POSTGRES_DB` | `vibefinance` | Database naam |
|
||||
| `JWT_EXPIRES` | `7d` | Geldigheidsduur token |
|
||||
| `CORS_ORIGIN` | — | Externe domeinnaam |
|
||||
| `FRONTEND_PORT` | `3300` | Externe poort frontend |
|
||||
| `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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -170,11 +241,23 @@ Beveiligde endpoints vereisen `Authorization: Bearer <token>`. Rate limiting: 20
|
||||
|
||||
- Wachtwoorden gehasht met bcrypt (cost factor 12)
|
||||
- JWT authenticatie met instelbare vervaltijd
|
||||
- Backend uitsluitend bereikbaar via nginx proxy (`/api/` in `nginx-spa.conf`)
|
||||
- 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
|
||||
- Containers draaien als non-root
|
||||
- Helmet security headers
|
||||
- Rate limiting op alle endpoints
|
||||
- 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`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user