diff --git a/DEPLOY.md b/DEPLOY.md index 1a1ae55..bfacd37 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -22,9 +22,18 @@ erboven (meestal een SSH-/sleutelprobleem). ./scripts/deploy.sh ``` -Dit haalt de laatste code op (`git reset --hard origin/`), herbouwt de -containers (incl. migraties via de entrypoint) en controleert of er een nieuwe -frontend-bundel staat. +Dit controleert eerst of Docker en `.env` aanwezig zijn, haalt dan de laatste +code op (`git reset --hard origin/`), herbouwt de containers (incl. +migraties via de entrypoint) en controleert daarna of de frontend-bundel er +staat, of de backend antwoordt en of er een actief account is om mee in te +loggen. + +Staan er **lokale wijzigingen** in de map op de server, dan stopt het script: +`git reset --hard` zou die weggooien. Zet ze eerst veilig, of forceer bewust: + +```bash +./scripts/deploy.sh --force +``` Nieuwe **modules** verschijnen daarna pas in de app nadat je ze aanzet via **⚙ → Modulebeheer**. @@ -45,7 +54,7 @@ EMAIL_HOST_USER=postvak@jouwschool.nl EMAIL_HOST_PASSWORD=... EMAIL_USE_TLS=1 DEFAULT_FROM_EMAIL=Roosterwijs -FRONTEND_BASE_URL=http://rooster-test.example.nl:8080 +FRONTEND_BASE_URL=https://rooster.example.nl ``` `FRONTEND_BASE_URL` bepaalt de basis van de resetlink in de e-mail (zonder @@ -76,8 +85,8 @@ Je hoeft op de server **niets** te installeren behalve Docker zelf (met de ``` 2. Haal de code op: ```bash - git clone /Roostersoftware.git - cd Roostersoftware + git clone /roosterwijs.git + cd roosterwijs ``` 3. Maak het `.env`-bestand aan vanuit het voorbeeld en vul echte waarden in: ```bash @@ -88,7 +97,8 @@ Je hoeft op de server **niets** te installeren behalve Docker zelf (met de - `DJANGO_SECRET_KEY` — genereer met `python3 -c "import secrets; print(secrets.token_urlsafe(50))"` - `DJANGO_ALLOWED_HOSTS` — de hostnaam/IP van de test-server - - `DJANGO_CSRF_TRUSTED_ORIGINS` — `http://:8080` (of je echte URL) + - `DJANGO_CSRF_TRUSTED_ORIGINS` — exact de origin die de browser gebruikt, + dus `https://` achter TLS (of `http://:8080` zonder TLS) - `POSTGRES_PASSWORD` — een sterk wachtwoord `.env` staat in `.gitignore` en komt dus **niet** in git — dat hoort zo. @@ -116,7 +126,10 @@ pas hem daar aan als die poort bezet is.) docker compose exec backend python manage.py createsuperuser ``` -Daarna kun je inloggen op `http://:8080/admin/`. +Dit account heb je **hoe dan ook** nodig: de hele API staat op +`IsAuthenticated`, dus zonder gebruiker kom je ook de app zelf niet in — niet +alleen `/admin/`. Zonder dit commando wordt er nergens automatisch een account +aangemaakt; `entrypoint.sh` draait alleen migraties en `collectstatic`. ## Updaten na nieuwe code @@ -164,8 +177,11 @@ zonder tussenkomst van Django). als je een eigen nginx/Apache ervoor hebt. 2. **`DJANGO_CSRF_TRUSTED_ORIGINS` moet de https-origin zijn**, exact zoals de browser hem gebruikt (bv. `https://rooster.example.nl`, zonder poort op 443). - Staat hier nog een `http://`-adres, dan geeft elke POST — inclusief het - inloggen — een CSRF-fout (HTTP 403). + Dit geldt voor de **Django-admin** en andere gewone Django-formulieren: staat + hier een `http://`-adres, dan weigert `/admin/` je aanmelding met een + CSRF-fout (HTTP 403). Het inloggen in de app zelf loopt hier níet op stuk — + DRF-views zijn `csrf_exempt` en `SessionAuthentication` controleert CSRF pas + als er al een sessie is. 3. **`DJANGO_ALLOWED_HOSTS` moet de publieke hostnaam bevatten**, anders volgt een 400 Bad Request. diff --git a/README.md b/README.md index b256890..38ffae1 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ Fase 3 (afwezigheid, uitzonderingen, effectief weekrooster) en Fase 4 Het Rooster-scherm is een **weekweergave met drag-and-drop** (muis én touch) en een **touch-vriendelijk** ontwerp; vrije dagen, afwezigheid en uitzonderingen -worden automatisch verwerkt. Het Rooster heeft tabs **Weekrooster / Conflicten / -Schooljaar / Instellingen** (tijdsloten, activiteiten en locaties beheer je daar, -en het schooljaar + de kalender voer je in onder de Schooljaar-tab, zodat het menu -rustiger is). De zijbalk groepeert items onder inklapbare submenu's (Personen, +worden automatisch verwerkt. De Roostermaker heeft tabs **Week / Genereren / +Instellingen**: onder Instellingen beheer je schooljaar + kalender, tijdvakken, +vakken, lokalen, locaties en vormgeving; onder Genereren zitten de Vak-wizard en +het Auto-rooster. Conflicten verschijnen als melding boven het weekrooster, niet +als aparte tab. De zijbalk groepeert items onder inklapbare submenu's (Personen, Groepen) en heeft onderaan een subtiel **Instellingen**-blok met Gebruikers en Modulebeheer. @@ -22,9 +23,10 @@ Er zijn twee rooster-schermen: **Rooster** (alleen-lezen weergave, om te bekijke en af te drukken) en **Roostermaker** (de aanpasser met drag-and-drop, tabs en conflictdetectie). In de Roostermaker open je een blok met een **klik** om het te bewerken (vak, locatie, doelgroep, begeleiders, opmerking) of te **kopiëren** naar -andere tijden/dagen. De tab **Indeling** verdeelt een hoofdgroep (of leerplein) -kolomsgewijs in subgroepen, met per subgroep de vakken en de verantwoordelijke(n), -en je kunt er direct subgroepen toevoegen. Beide schermen hebben een **scope-filter** (alles / per klas / +andere tijden/dagen. Het aparte scherm **Groepsindeling** (zijbalk → Organisatie) +verdeelt een hoofdgroep of leerplein kolomsgewijs in subgroepen, met per subgroep +de vakken en de verantwoordelijke(n), en je kunt er direct subgroepen toevoegen. +Beide roosterschermen hebben een **scope-filter** (alles / per klas / per leerplein / per leerling / per personeel); de module **Printen/Exporteren** voegt — als die aanstaat — een subtiele **Afdrukken**-knop toe die het (gefilterde) weekrooster printvriendelijk afdrukt of als PDF bewaart. @@ -52,11 +54,14 @@ geboortedatum vast. - **Backend** (`backend/`) — Django + Django REST Framework. - `plugins/` — het plugin-framework: een moduleregister, opslag van de aan/uit-status per module en een beheer-API. - - `core/` — de kern (altijd actief). **Fase 1**: modellen Persoon, Groep en - Subgroep met een volledige CRUD-API. Roosterblokken en kalender volgen in - fase 2. - - `modules/printing/` en `modules/accounts/` — twee voorbeeldmodules die - zich bij opstarten aanmelden bij het register. + - `core/` — de kern (altijd actief): personen, groepen, subgroepen, + functies, schooljaar + kalender, tijdvakken, activiteiten, locaties, + lokalen, roosterblokken en conflictdetectie, elk met een CRUD-API. + - `modules/` — twaalf modules die zich bij opstarten aanmelden bij het + register: `printing`, `accounts`, `leerplein`, `stage`, `uren`, + `blokwizard`, `personenwizard`, `autorooster`, `huisstijl`, + `importexport`, `vervanging` en `portal`. De volledige lijst staat in + `MODULE_APPS` in `backend/config/settings.py`. - **Frontend** (`frontend/`) — React (Vite). Zijbalk met Roosterwijs-logo die alleen menu's toont van ingeschakelde modules, beheerschermen voor **Personen**, **Groepen** en **Subgroepen**, en een scherm **Modulebeheer**. @@ -74,18 +79,34 @@ geboortedatum vast. Een nieuwe module toevoegen = nieuwe app maken, één regel in `MODULE_APPS` (in `backend/config/settings.py`), klaar. -## Starten +## Starten (lokaal ontwikkelen) + +> Voor een test- of productieserver gelden **andere** stappen: zie `DEPLOY.md`. +> Daar draait alles in Docker en komt de configuratie uit `.env`. ### Backend (Django, poort 8000) ```bash cd backend +python3 -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r requirements.txt + +# Zonder DJANGO_DEBUG=1 weigert Django te starten: buiten debug is +# DJANGO_SECRET_KEY verplicht. Met debug aan valt hij terug op een dev-sleutel. +export DJANGO_DEBUG=1 # Windows: $env:DJANGO_DEBUG="1" + python manage.py migrate -python manage.py createsuperuser # optioneel, voor /admin/ +python manage.py createsuperuser # VERPLICHT, zie hieronder python manage.py runserver ``` +**`createsuperuser` is geen optionele stap.** De hele API staat op +`IsAuthenticated` en de frontend toont eerst een inlogscherm; zonder account kom +je nergens binnen — ook niet in de app zelf, niet alleen in `/admin/`. + +Zonder `POSTGRES_DB` in de omgeving gebruikt de backend automatisch SQLite in +`backend/db.sqlite3`. `DEPLOY.md` zet PostgreSQL in via `.env`. + API-eindpunten: - `GET /api/modules/` — alle modules met status. - `POST /api/modules//state/` — body `{"enabled": true|false}`. @@ -111,6 +132,9 @@ API-eindpunten: ### Frontend (React, poort 5173) +Vereist **Node 20.19+ of 22.12+** — Vite 8 weigert oudere versies. +Controleer met `node -v`. + ```bash cd frontend npm install diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 988e764..8bee224 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,10 @@ # Frontend in twee fases: -# 1) Node bouwt de React-app tot statische bestanden. +# 1) Node bouwt de React-app tot statische bestanden (Node 22: Vite 8 eist +# ^20.19 of >=22.12, en de CI bouwt ook op 22 — zelfde versie overal). # 2) Nginx serveert die bestanden en proxyt /api en /admin naar de backend. # --- Fase 1: build -------------------------------------------------------- -FROM node:20-alpine AS build +FROM node:22-alpine AS build WORKDIR /app COPY package*.json ./ # npm ci (reproduceerbaar) zodra er een package-lock.json in git staat; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d85f209..663a237 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "roosterwijs-frontend", - "version": "0.2.2-beta", + "version": "0.2.3-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roosterwijs-frontend", - "version": "0.2.2-beta", + "version": "0.2.3-beta", "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/frontend/package.json b/frontend/package.json index 3b11bb9..51abb99 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "roosterwijs-frontend", "private": true, - "version": "0.2.2-beta", + "version": "0.2.3-beta", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/modern.css b/frontend/src/modern.css index e826f65..48ff542 100644 --- a/frontend/src/modern.css +++ b/frontend/src/modern.css @@ -35,7 +35,7 @@ button,a,input,select{transition:border-color .15s,background-color .15s,color . .cog-btn{width:100%;min-height:38px;justify-content:flex-start;font-size:15px;padding:0} .cog-btn::after{content:"Beheer en instellingen";margin-left:8px;font-size:13px} .cog-dropdown{position:static;min-width:0;margin:4px 0 0;box-shadow:none} -.topbar::after{content:"v0.2.02 beta";order:4;margin:10px 8px 0;color:#98a2b3;font-size:10px} +.topbar::after{content:"v0.2.03 beta";order:4;margin:10px 8px 0;color:#98a2b3;font-size:10px} .content{margin-left:var(--sidebar);width:calc(100% - var(--sidebar));max-width:1680px;padding:32px 38px 48px} .content-wide{padding:24px 28px 42px} h1{font-size:clamp(24px,2.4vw,30px);line-height:1.2;letter-spacing:-.025em;font-weight:750} diff --git a/scripts/deploy.sh b/scripts/deploy.sh old mode 100644 new mode 100755 index ed524c1..cbb3a95 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,19 +1,102 @@ #!/usr/bin/env bash # Op de test-server: haal de laatste code op en herbouw de containers. -# Gebruik: ./scripts/deploy.sh +# +# Gebruik: ./scripts/deploy.sh [--force] +# --force ook doorgaan als er lokale wijzigingen zijn +# (let op: die worden dan overschreven) set -euo pipefail cd "$(dirname "$0")/.." -branch="$(git rev-parse --abbrev-ref HEAD)" -echo "Branch: $branch" -git fetch origin -# Forceer exact gelijk aan de remote (voorkomt 'already up to date'-verwarring). -git reset --hard "origin/$branch" -echo "Nu op commit:" -git --no-pager log --oneline -1 +force=0 +for arg in "${@:-}"; do + case "$arg" in + "") ;; + --force) force=1 ;; + *) echo "Onbekende optie: $arg" >&2; exit 2 ;; + esac +done +# --- Vooraf controleren --------------------------------------------------- +if ! command -v docker >/dev/null 2>&1; then + echo "FOUT: docker is niet geïnstalleerd op deze server." >&2 + exit 1 +fi +if ! docker compose version >/dev/null 2>&1; then + echo "FOUT: 'docker compose' werkt niet (compose-plugin ontbreekt)." >&2 + exit 1 +fi + +# Zonder .env start de stack met lege variabelen: compose vult dan overal +# lege strings in, de backend ziet geen POSTGRES_DB (valt terug op SQlite in +# de container) en weigert te starten omdat DJANGO_SECRET_KEY ontbreekt. +if [ ! -f .env ]; then + echo "FOUT: .env ontbreekt in $(pwd)." >&2 + echo " Maak hem aan met: cp .env.example .env && nano .env" >&2 + exit 1 +fi + +# 'git reset --hard' hieronder gooit lokale aanpassingen weg — bijvoorbeeld een +# met de hand aangepaste nginx.conf. Daarom eerst waarschuwen. +if [ -n "$(git status --porcelain)" ]; then + echo "LET OP: er staan lokale wijzigingen in deze map:" + git --no-pager status --short | sed 's/^/ /' + if [ "$force" -ne 1 ]; then + echo + echo "Die gaan verloren bij het bijwerken. Zet ze eerst veilig, of draai:" >&2 + echo " ./scripts/deploy.sh --force" >&2 + exit 1 + fi + echo "--force opgegeven: deze wijzigingen worden overschreven." +fi + +# --- Code bijwerken ------------------------------------------------------- +branch="$(git rev-parse --abbrev-ref HEAD)" +oud="$(git rev-parse --short HEAD)" +echo "Branch: $branch (nu op $oud)" + +git fetch origin +git reset --hard "origin/$branch" +nieuw="$(git rev-parse --short HEAD)" + +if [ "$oud" = "$nieuw" ]; then + echo "Geen nieuwe commits; containers worden wel opnieuw gebouwd." +else + echo "Bijgewerkt: $oud -> $nieuw" + git --no-pager log --oneline "$oud..$nieuw" | sed 's/^/ /' +fi + +# --- Bouwen en starten ---------------------------------------------------- docker compose up -d --build -echo "Controle: nieuwe frontend-bundel aanwezig?" -docker compose exec -T nginx sh -c 'ls /usr/share/nginx/html/assets/*.js >/dev/null 2>&1 && echo " frontend OK" || echo " GEEN frontend-bundel gevonden"' +# --- Controleren ---------------------------------------------------------- +echo +echo "Controle: frontend-bundel aanwezig?" +if docker compose exec -T nginx sh -c 'ls /usr/share/nginx/html/assets/*.js >/dev/null 2>&1'; then + echo " frontend OK" +else + echo " GEEN frontend-bundel gevonden" +fi + +echo "Controle: reageert de backend?" +docker compose exec -T backend python -c " +import urllib.request, urllib.error +try: + r = urllib.request.urlopen('http://127.0.0.1:8000/api/info/', timeout=5) + print(' backend OK (HTTP', r.status, ')') +except urllib.error.HTTPError as e: + print(' backend OK (HTTP', e.code, '- reageert)') +except Exception as e: + print(' GEEN antwoord van de backend:', e) +" || echo " Kon de backend niet bereiken — zie 'docker compose logs backend'." + +echo "Controle: is er een gebruiker om mee in te loggen?" +docker compose exec -T backend python manage.py shell -c " +from django.contrib.auth import get_user_model +n = get_user_model().objects.filter(is_active=True).count() +print(' actieve gebruikers:', n) +if n == 0: + print(' LET OP: geen actief account. Maak er een met:') + print(' docker compose exec backend python manage.py createsuperuser') +" || echo " Kon de gebruikers niet tellen — zie 'docker compose logs backend'." + echo "Klaar."