Baustein 1: Grundgeruest, Datenbank, Anmeldung, zwei Rollen
Legt das Fundament der Lern-Bar an: - Next.js 15 (App Router), React 19, TypeScript, ESLint, Vitest - SQLite ueber Drizzle hinter einer Repository-Schicht - Eigener Migrationslauf mit Pruefsummen, beim Start angewendet - Argon2id, serverseitige Sitzungen, HttpOnly-Cookie, SameSite=Lax - Rollen Ausbilder und Azubi, Rechteregeln als reine Funktionen - Verwaltung: Azubikonto und Ausbildung anlegen, Passwort, Sperren - Gestaltung Petrol/Bernstein, helle und dunkle Darstellung - Dockerfile, docker-compose, Lebenszeichen, update.sh mit Rueckfall - Sicherung und Wiederherstellung als Shell-Skripte - 29 Tests fuer Rechte, Zeitrechnung und Migrationslauf Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
# Lern-Bar
|
||||
|
||||
Selbst gehostete Anwendung für die betriebliche Ausbildung zum/zur
|
||||
**Fachinformatiker/-in für Systemintegration**. Sie ersetzt den Stapel PDFs:
|
||||
Wochenplan, Handouts, Merkkarten, Praxisaufgaben, Checklisten, Lernzielkontrollen
|
||||
und – der wichtigste Teil – den gesetzlich vorgeschriebenen Ausbildungsnachweis
|
||||
(„Berichtsheft").
|
||||
|
||||
Gebaut für **einen Ausbilder und ein bis drei Auszubildende**. Kein
|
||||
Lernplattform-Baukasten, keine Mandanten, keine Cloud.
|
||||
|
||||
> **Stand:** Baustein 1 von 10 ist fertig (Fundament). Der
|
||||
> [Fahrplan](ROADMAP.md) zeigt, was folgt, das
|
||||
> [Änderungsprotokoll](CHANGELOG.md), was schon drin ist.
|
||||
|
||||
---
|
||||
|
||||
## In fünf Minuten zum laufenden System
|
||||
|
||||
### Voraussetzungen
|
||||
|
||||
- **Betrieb:** Docker mit Compose-Erweiterung (Docker 24 oder neuer)
|
||||
- **Entwicklung:** Node.js 22 oder neuer, Git
|
||||
- Ein Reverse Proxy, der TLS beendet (nginx, Caddy, Traefik) – die Anwendung
|
||||
selbst spricht nur HTTP und lauscht nur auf der Schleife
|
||||
|
||||
### Betrieb mit Docker
|
||||
|
||||
```bash
|
||||
git clone <repository-url> lernbar
|
||||
cd lernbar
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Die Anwendung lauscht danach auf `127.0.0.1:3000`. Erstes Ausbilderkonto
|
||||
anlegen:
|
||||
|
||||
```bash
|
||||
docker compose exec lernbar node -e "1" >/dev/null # Container erreichbar?
|
||||
docker compose exec lernbar sh -c \
|
||||
'node werkzeuge/konto.js --name "Vorname Nachname" --email ausbilder@betrieb.de --rolle ausbilder'
|
||||
```
|
||||
|
||||
> Solange das Konto-Werkzeug im Abbild noch nicht liegt (Baustein 1), geht es
|
||||
> vom Entwicklungsrechner aus:
|
||||
> `npm run konto:anlegen -- --name "…" --email … --rolle ausbilder`
|
||||
|
||||
Danach im Browser anmelden, unter **Verwaltung** das Azubikonto anlegen und
|
||||
das Ausbildungsverhältnis mit Beginndatum einrichten.
|
||||
|
||||
### Entwicklung
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm approve-scripts better-sqlite3 esbuild unrs-resolver # nur beim ersten Mal
|
||||
npm run konto:anlegen -- --name "Vorname Nachname" --email du@betrieb.de --rolle ausbilder
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Ohne `npm approve-scripts` bleiben `better-sqlite3` und `esbuild` unübersetzt –
|
||||
npm blockiert seit Version 11 die Installationsskripte fremder Pakete.
|
||||
|
||||
Die Anwendung läuft dann auf <http://localhost:3000>. Datenbank und Anhänge
|
||||
landen unter `daten/` (nicht im Repository).
|
||||
|
||||
---
|
||||
|
||||
## Beispiel für einen Reverse Proxy
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name lernbar.betrieb.local;
|
||||
|
||||
ssl_certificate /etc/ssl/lernbar.crt;
|
||||
ssl_certificate_key /etc/ssl/lernbar.key;
|
||||
|
||||
client_max_body_size 25m; # Fotos vom Aufbau als Anhang
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Inhalte pflegen
|
||||
|
||||
Die fachlichen Inhalte sind **Dateien im Repository**, keine
|
||||
Datenbankeinträge – Bearbeiten in VS Code, Versionieren mit Git, Sichern mit
|
||||
`git clone`.
|
||||
|
||||
```
|
||||
content/
|
||||
wochen/ 01-arbeitsalltag.yaml …
|
||||
handouts/ N3-subnetting-rechnen.md …
|
||||
aufgaben/ 2.2-notebooks-vergleichen.md …
|
||||
checklisten/ 02-windows-installieren.yaml …
|
||||
lernzielkontrollen/ woche-03.yaml …
|
||||
glossar.yaml
|
||||
rahmenplan.yaml Berufsbildpositionen und Lernfelder
|
||||
```
|
||||
|
||||
Ein Handout ist Markdown mit YAML-Kopf:
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: N3
|
||||
titel: Subnetting rechnen – der Weg in vier Schritten
|
||||
block: netzwerk
|
||||
woche: 3
|
||||
worum: Eine Adresse und eine Maske reichen, um Netz, Broadcast und Hostbereich zu bestimmen.
|
||||
rahmenplan: [B2, A8]
|
||||
lernfeld: LF3
|
||||
merksatz: Blockgröße finden, Vielfache aufschreiben, eigene Adresse einsortieren.
|
||||
karten:
|
||||
- frage: Zu welchem Netz gehört 192.168.10.200 bei der Maske 255.255.255.224?
|
||||
antwort: Blockgröße 32, Netze bei .192 und .224 – die Adresse gehört zu 192.168.10.192/27.
|
||||
---
|
||||
|
||||
## Das Wesentliche
|
||||
…
|
||||
```
|
||||
|
||||
Beim Start werden alle Inhalte eingelesen und geprüft: Jede Kennung muss es
|
||||
geben, jedes Handout gehört zu einer Woche, jede Rahmenplanposition steht in
|
||||
`rahmenplan.yaml`. Passt etwas nicht, startet die Anwendung nicht und nennt
|
||||
Datei und Stelle. Prüfen ohne Start:
|
||||
|
||||
```bash
|
||||
npm run inhalte:pruefen
|
||||
```
|
||||
|
||||
*(Wird mit Baustein 2 scharf geschaltet.)*
|
||||
|
||||
---
|
||||
|
||||
## Datensicherung
|
||||
|
||||
Gesichert werden **Datenbank und Anhänge**. Die Inhalte stecken im Repository
|
||||
und brauchen keine eigene Sicherung.
|
||||
|
||||
### Sicherung ziehen
|
||||
|
||||
```bash
|
||||
# im laufenden Container
|
||||
docker compose exec lernbar /app/werkzeuge/sicherung.sh /daten/sicherungen
|
||||
|
||||
# aus dem Volume herausholen
|
||||
docker compose cp lernbar:/daten/sicherungen ./sicherungen
|
||||
```
|
||||
|
||||
Auf dem Entwicklungsrechner:
|
||||
|
||||
```bash
|
||||
LERNBAR_DATEN=./daten npm run backup -- ./sicherungen
|
||||
```
|
||||
|
||||
Das Archiv heißt `lernbar-JJJJMMTT-HHMMSS.tar.gz` und enthält
|
||||
`lernbar.sqlite`, `anhaenge/` und eine kurze Beschreibung. Die Kopie der
|
||||
Datenbank entsteht über die Sicherungsschnittstelle von SQLite, ist also auch
|
||||
im laufenden Betrieb in sich stimmig.
|
||||
|
||||
### Zurückspielen
|
||||
|
||||
```bash
|
||||
docker compose stop lernbar
|
||||
docker compose cp ./sicherungen/lernbar-20260910-0300.tar.gz lernbar:/tmp/
|
||||
docker compose start lernbar
|
||||
docker compose exec lernbar /app/werkzeuge/wiederherstellen.sh /tmp/lernbar-20260910-0300.tar.gz
|
||||
docker compose restart lernbar
|
||||
```
|
||||
|
||||
Der bisherige Stand wird nicht gelöscht, sondern nach
|
||||
`/daten/verdraengt-<zeitstempel>/` verschoben.
|
||||
|
||||
### Nächtlich per Cronjob
|
||||
|
||||
`/etc/cron.d/lernbar-sicherung`:
|
||||
|
||||
```cron
|
||||
# Jede Nacht um 03:00 sichern, Archive älter als 30 Tage wegräumen.
|
||||
SHELL=/bin/sh
|
||||
PATH=/usr/local/bin:/usr/bin:/bin
|
||||
|
||||
0 3 * * * root cd /opt/lernbar && docker compose exec -T lernbar /app/werkzeuge/sicherung.sh /daten/sicherungen >> /var/log/lernbar-sicherung.log 2>&1
|
||||
30 3 * * * root cd /opt/lernbar && docker compose exec -T lernbar find /daten/sicherungen -name 'lernbar-*.tar.gz' -mtime +30 -delete
|
||||
```
|
||||
|
||||
Liegt das Volume auf einem Pfad, den die Sicherung des Servers ohnehin
|
||||
mitnimmt, ist damit alles erledigt. Sonst zusätzlich mit `docker compose cp`
|
||||
herauskopieren und wegschaffen.
|
||||
|
||||
---
|
||||
|
||||
## Aktualisieren
|
||||
|
||||
Auf dem Linux-Server:
|
||||
|
||||
```bash
|
||||
cd /opt/lernbar
|
||||
./update.sh
|
||||
```
|
||||
|
||||
Das Skript sichert zuerst, holt den Quelltext, baut das Abbild neu, schaltet um
|
||||
und wartet auf das Lebenszeichen. Meldet sich die neue Fassung nicht gesund,
|
||||
schaltet es selbsttätig auf das vorherige Abbild zurück und zeigt die letzten
|
||||
Protokollzeilen.
|
||||
|
||||
| Angabe | Wirkung |
|
||||
|---|---|
|
||||
| *(ohne)* | sichern, holen, bauen, umschalten |
|
||||
| `--ohne-git` | nur neu bauen und neu starten |
|
||||
| `--ohne-sicherung` | Sicherung überspringen (nicht empfohlen) |
|
||||
| `--zweig main` | einen bestimmten Git-Zweig holen |
|
||||
| `--hilfe` | Kurzhilfe |
|
||||
|
||||
---
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
| Name | Vorgabe im Container | Bedeutung |
|
||||
|---|---|---|
|
||||
| `PORT` | `3000` | Lauschport |
|
||||
| `TZ` | `Europe/Berlin` | Zeitzone |
|
||||
| `LERNBAR_DATEN` | `/daten` | Wurzel für Datenbank und Anhänge |
|
||||
| `LERNBAR_DB` | `<daten>/lernbar.sqlite` | abweichender Pfad der Datenbank |
|
||||
| `LERNBAR_INHALTE` | `/app/content` | Wurzel der Inhaltsdateien |
|
||||
| `LERNBAR_MIGRATIONEN` | `/app/migrationen` | Verzeichnis der SQL-Migrationen |
|
||||
|
||||
---
|
||||
|
||||
## Prüfen und Testen
|
||||
|
||||
```bash
|
||||
npm run typecheck # TypeScript
|
||||
npm run lint # ESLint
|
||||
npm run test # Vitest – Logik, die wehtut
|
||||
npm run e2e # Playwright – der Hauptweg von Anfang bis Ende
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Aufbau
|
||||
|
||||
```
|
||||
src/
|
||||
app/ Seiten und Serveraktionen (App Router)
|
||||
komponenten/ wiederverwendete Bausteine der Oberfläche
|
||||
lib/
|
||||
auth/ Passwörter, Sitzungen, Rechteregeln
|
||||
db/ Verbindung, Schema, Migrationen
|
||||
repos/ der gesamte Datenzugriff – nur hier wird SQL gesprochen
|
||||
zeit/ Europe/Berlin, ISO-Kalenderwochen
|
||||
content/ fachliche Inhalte als Markdown und YAML
|
||||
scripts/ Werkzeuge für die Kommandozeile
|
||||
im-container/ Sicherung und Wiederherstellung (Shell, läuft überall)
|
||||
```
|
||||
|
||||
Weiterführend: [Fahrplan](ROADMAP.md) ·
|
||||
[Änderungsprotokoll](CHANGELOG.md) · [Arbeitstagebuch](DEVLOG.md) ·
|
||||
[Entscheidungen](ENTSCHEIDUNGEN.md)
|
||||
Reference in New Issue
Block a user