No description
Find a file
Ramon e7bb76b76d
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m9s
feat: klassenmanagement - niveaugroep + niveau per leerling per vakgebied (v0.4.34-beta)
- Grootste openstaande roadmap-punt. Nieuwe tabel pupil_levels
  (db/019_pupil_levels.sql, zelfde class_id/pupil_id-vorm als
  assignments) legt per vakgebied (taal/rekenen/world) een niveaugroep
  vast (groep 1-8, gebaseerd op de leerjaren van de basisschool waaraan
  de leerdoelen gekoppeld zijn) + niveau (1-3, fijnmazige verfijning
  BINNEN die groep). Klas-standaard met individuele leerling-overrides,
  net zo laagdrempelig instelbaar als de standaard - het speciaal-
  onderwijs-geval waar dat vanaf het begin de norm is.
- Server: GET/PUT/DELETE /levels/class/:id en /levels/pupil/:id
  (src/api.js) hergebruiken exact het assignments-patroon (upsert via
  partial-unique-index, pupil-eerst-dan-klas-resolutie), plus
  GET /my/level voor de leerling-kant. Zelfde permissie
  (assignments.manage) en sameSchool/teacherOnly/pupilAccessible-
  guards als de bestaande toewijzingsroutes.
- Nieuwe admin-tab "Klassenmanagement": klas + vakgebied kiezen, een
  klas-standaard instellen, en per leerling de effectieve groep/niveau
  zien (overerft van de klas tenzij een eigen afwijking is ingesteld)
  met bewerk- en terug-naar-standaard-knoppen.
- ECHTE inhoudskoppeling, niet alleen een standaardwaarde: rekenen is
  het eerste vakgebied waarvan de SLO/TULE-leerlijn direct in
  getalgrenzen te vertalen is (groep 3 t/m 20, groep 4 t/m 100, groep 5
  t/m 1000, groep 6 t/m 10.000). Nieuwe SLO_MATH_RANGES/mathRangeFor()
  in data.js; math.js's gen() gebruikt dit voor de daadwerkelijke
  getallen zodra een leerling een groep heeft (S.yearGroup), met het
  bestaande niveau 1-3 als verfijning daarbinnen. Zonder groep blijft
  het oude vaste 10/20/100-gedrag exact bestaan.
- pupil.js geeft groep/niveau door aan taal/rekenen-widgets bij het
  mounten: niveau is een standaardwaarde (een al opgeslagen bord-niveau
  blijft leidend), groep wordt voor rekenen-widgets altijd meegegeven
  (er is geen "oud" groep-concept om te respecteren). Terzijde ook de
  stille .catch() op de nieuwe async renderPupilWidgets() opgelost.
- Taal/wereldoriëntatie krijgen deze ronde nog geen eigen
  inhoudskoppeling (vraagt een eigen contentbron per groep, nog niet
  uitgezocht) - eerlijk benoemd als vervolgstap, geen loze belofte.
- Tests: test/pupil-levels.test.js (10 tests) dekt de server-routes
  (mocked-pool-patroon) én - via dezelfde vm-techniek als elders in
  test/widgets.test.js voor MONEY_LEVELS - een ECHTE (niet regex-)test
  van mathRangeFor die bevestigt dat elke groep de juiste getalgrens
  oplevert en niveau nooit boven de groep-grens uitkomt. Twee bugs
  gevonden en gefixt tijdens het schrijven van deze tests: null-yearGroup
  werd per ongeluk als groep 1 behandeld i.p.v. "geen groep", en de
  testrol had geen klas-eigenaarschap waardoor 403 in plaats van 200
  terugkwam. Volledige testsuite: 128/128.
- Live geverifieerd in de sandbox: klas-standaard + leerling-override
  instellen in de nieuwe admin-tab, en drie scenario's in de
  leerling-omgeving die bevestigen dat de daadwerkelijk gegenereerde
  sommen binnen de juiste groep-grens blijven (geen klassenmanagement →
  ongewijzigd oud gedrag; groep zonder eigen bord-niveau → volledige
  groep-grens; groep mét een al opgeslagen bord-niveau → dat niveau
  blijft leidend, geschaald naar de juiste groep-grens).
- public/js/data.js bevatte al niet-gerelateerde, nog niet gecommitte
  wijzigingen van vóór deze sessie (extra taalthema's/woorden) - alleen
  de SLO_MATH_RANGES/mathRangeFor-hunk is hier meegenomen (git add -p),
  de rest blijft bewust ongemoeid staan, net als test/language-library.test.js.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 15:31:26 +02:00
.forgejo/workflows v0.3.05-beta: rol nginx-config atomisch uit 2026-07-14 13:23:15 +02:00
db feat: klassenmanagement - niveaugroep + niveau per leerling per vakgebied (v0.4.34-beta) 2026-07-19 15:31:26 +02:00
deploy perf: gzip-vangnet in nginx voor geproxyde responses (v0.3.82-beta) 2026-07-16 19:02:53 +02:00
public feat: klassenmanagement - niveaugroep + niveau per leerling per vakgebied (v0.4.34-beta) 2026-07-19 15:31:26 +02:00
src feat: klassenmanagement - niveaugroep + niveau per leerling per vakgebied (v0.4.34-beta) 2026-07-19 15:31:26 +02:00
test feat: klassenmanagement - niveaugroep + niveau per leerling per vakgebied (v0.4.34-beta) 2026-07-19 15:31:26 +02:00
.dockerignore Initiële opzet: Fastify + static, Docker, Postgres, Forgejo Actions 2026-07-07 22:41:05 +02:00
.env.example v0.2.00: schoolsysteem met rollen (super/admin/groepsleiding/leerling), koppelcodes, server-API en beheerpaneel 2026-07-09 22:26:01 +02:00
.gitignore feat: voeg afbeeldingscatalogus en opslagquota toe 2026-07-15 23:45:17 +02:00
AGENTS.md v0.2.05-beta: bronwaarde widgets als string bewaren + projectafspraken toegevoegd 2026-07-13 08:04:50 +02:00
CLAUDE.md v0.2.05-beta: bronwaarde widgets als string bewaren + projectafspraken toegevoegd 2026-07-13 08:04:50 +02:00
compose.yaml feat: voeg afbeeldingscatalogus en opslagquota toe 2026-07-15 23:45:17 +02:00
Dockerfile feat: voeg afbeeldingscatalogus en opslagquota toe 2026-07-15 23:45:17 +02:00
package-lock.json perf: compressie, cache-headers, cache-busting en parallel ladende scripts (v0.3.81-beta) 2026-07-16 19:01:25 +02:00
package.json perf: compressie, cache-headers, cache-busting en parallel ladende scripts (v0.3.81-beta) 2026-07-16 19:01:25 +02:00
README.md v0.3.06-beta: herstel bereikbaarheid via Pangolin 2026-07-14 13:26:15 +02:00
VERSION feat: klassenmanagement - niveaugroep + niveau per leerling per vakgebied (v0.4.34-beta) 2026-07-19 15:31:26 +02:00

Teach

Digibord-webapp (Fastify + static frontend) met PostgreSQL, gecontaineriseerd en via Forgejo Actions automatisch uitgerold: dev → test-VM, release → productie-VM.

Structuur

teach/
├─ public/index.html          # de digibord-app (voorheen teach.html)
├─ src/server.js              # Fastify: serveert de app + /api + DB-pool
├─ db/001_init.sql            # initieel Postgres-schema
├─ Dockerfile                 # productie-image (non-root, healthcheck)
├─ compose.yaml              # lokaal draaien (app + postgres)
├─ deploy/compose.deploy.yaml # test/prod: pullt image uit de registry
├─ .forgejo/workflows/
│  ├─ dev.yaml               # push naar dev → build + deploy test
│  └─ release.yaml           # release → build + deploy prod
└─ .env.example

Lokaal draaien

cp .env.example .env      # pas POSTGRES_PASSWORD en DATABASE_URL aan
docker compose up --build

App op http://localhost:3000, healthcheck op /healthz, DB-check op /readyz.

Architectuurkeuzes

  • Container i.p.v. losse static site: omdat er een database bijkomt, is een backend nodig (browser praat niet rechtstreeks met Postgres). De Fastify-app serveert de HTML én biedt de /api, en praat met de DB.
  • Forgejo container registry: CI bouwt de image één keer en pusht die; beide VM's pullen exact dezelfde geteste image. Geen build op de productie-VM.
  • Pangolin/Traefik verzorgt de publieke HTTPS-ingang. De meegeleverde nginx vormt de interne proxylaag en luistert standaard op poort 8081, zodat de Pangolin/Traefik-route deze via het VM-/containernetwerk kan bereiken. De Fastify-app vertrouwt precies twee proxy-hops.

Eenmalige setup

1. Repo aanmaken in Forgejo

Maak een leeg repo (bv. ramon/teach) en push (zie onderaan).

2. Registry / Actions variabelen en secrets

Onder Settings → Actions → Variables van het repo (of org):

Variable Waarde Uitleg
REGISTRY 10.0.20.22:3000 Host van je Forgejo = registry

De image heet dan 10.0.20.22:3000/bes-r/teach.

Onder Settings → Actions → Secrets:

Secret Uitleg
REGISTRY_USER Forgejo-gebruiker met package-write rechten
REGISTRY_TOKEN Token/wachtwoord voor die gebruiker (scope: packages)
TEST_HOST IP/hostname van de test-VM
TEST_USER SSH-gebruiker op de test-VM
TEST_SSH_KEY Private SSH-key (deploy key) voor de test-VM
TEST_DEPLOY_PATH Pad op de test-VM met compose.deploy.yaml + .env + db/
PROD_HOST IP/hostname van de productie-VM
PROD_USER SSH-gebruiker op de productie-VM
PROD_SSH_KEY Private SSH-key voor de productie-VM
PROD_DEPLOY_PATH Pad op de productie-VM met de deploy-bestanden

De runner moet de docker-CLI kunnen gebruiken (host-socket of docker-in-docker) en actions/checkout + appleboy/ssh-action kunnen ophalen. Pas runs-on in de workflows aan naar het label van jouw runner als dat niet ubuntu-latest is.

2b. HTTP-registry toestaan (insecure-registries)

Je Forgejo draait op http://10.0.20.22:3000 (platte HTTP). Docker weigert HTTP-registries tenzij je ze expliciet toestaat. Doe dit op drie plekken: de runner-host (die pusht) en beide VM's (die pullen). Op elke Docker-host:

# /etc/docker/daemon.json
{
  "insecure-registries": ["10.0.20.22:3000"]
}
sudo systemctl restart docker

Zet je Forgejo later achter HTTPS met een echt domein, dan kan deze stap weg en gebruik je dat domein als REGISTRY.

3. Deploy-map op elke VM

Op zowel de test- als de productie-VM, in het pad dat je bij *_DEPLOY_PATH opgeeft:

mkdir -p teach && cd teach
# kopieer deze twee uit de repo:
#   deploy/compose.deploy.yaml  -> compose.deploy.yaml
#   db/                         -> db/
# maak een .env aan:
cat > .env <<'EOF'
IMAGE=10.0.20.22:3000/bes-r/teach:dev   # prod: laat CI dit op :vX.Y.Z zetten
POSTGRES_DB=teach
POSTGRES_USER=teach
POSTGRES_PASSWORD=<sterk-wachtwoord>
DATABASE_URL=postgres://teach:<sterk-wachtwoord>@db:5432/teach
APP_PORT=3000
WEB_BIND_IP=0.0.0.0
WEB_PORT=8081
TRUST_PROXY_HOPS=2
SUPER_USER=beheerder
SUPER_PASS=<uniek-sterk-eerste-wachtwoord>
EOF

CI werkt bij elke deploy de IMAGE=-regel bij, pullt en herstart.

4. Pangolin/Traefik publiceren

Publiceer http://<interne-vm-ip>:8081 via Pangolin/Traefik en laat daar TLS beëindigen. De interne nginx behoudt X-Forwarded-Proto: https, waarna de app Secure/HttpOnly/SameSite-cookies, HSTS en overige securityheaders gebruikt.

  • Beperk poort 8081 met de hostfirewall tot het Pangolin/Traefik- of tunnelnetwerk.
  • Pas TRUST_PROXY_HOPS alleen aan wanneer de proxyketen werkelijk verandert.
  • Bij een aparte Traefik-container kan een gedeeld intern Docker-netwerk nodig zijn; zet WEB_BIND_IP niet ruimer dan noodzakelijk.
  • SUPER_PASS is alleen nodig bij een lege database, wordt nooit gelogd en moet via de beveiligde deploy-omgeving worden aangeleverd.
  • De overgang naar v0.3.03-beta trekt bestaande browsersessies bewust in.

Workflow / branching

  • Werk op feature-branches, merge naar dev → automatisch naar test.
  • Tevreden? Maak een release (tag vX.Y.Z) → automatisch naar productie.

Push naar Forgejo (eerste keer)

git remote add origin http://10.0.20.22:3000/bes-r/teach.git
git push -u origin main
git push -u origin dev

Volgende stap: data uit localStorage naar de database

De frontend bewaart borden/gebruikers nu nog in localStorage. Om echt een gedeelde database te gebruiken, bouwen we /api-endpoints (boards, folders, users) in src/server.js en laten we de frontend die aanroepen i.p.v. localStorage. Het schema in db/001_init.sql is daarvoor het startpunt.