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:
Florian Wilde
2026-09-10 00:52:44 +02:00
co-authored by Claude Opus 5
commit e856349cd4
51 changed files with 11883 additions and 0 deletions
Executable
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Lern-Bar aktualisieren – für den Linux-Server, auf dem der Container läuft.
#
# ./update.sh Quelltext holen, neu bauen, neu starten
# ./update.sh --ohne-git nur neu bauen und neu starten
# ./update.sh --ohne-sicherung Sicherung überspringen (nicht empfohlen)
# ./update.sh --zweig main einen bestimmten Git-Zweig holen
#
# Ablauf: Sicherung → Quelltext holen → Abbild bauen → umschalten →
# Lebenszeichen prüfen. Klappt das Lebenszeichen nicht, wird automatisch auf
# das vorherige Abbild zurückgeschaltet.
# ---------------------------------------------------------------------------
set -euo pipefail
cd "$(dirname "$(readlink -f "$0")")"
DIENST="lernbar"
ABBILD="lernbar:lokal"
ZWEIG=""
MIT_GIT=1
MIT_SICHERUNG=1
WARTEZEIT=90
while [ $# -gt 0 ]; do
case "$1" in
--ohne-git) MIT_GIT=0 ;;
--ohne-sicherung) MIT_SICHERUNG=0 ;;
--zweig)
ZWEIG="${2:-}"
shift
;;
--hilfe | -h)
sed -n '2,18p' "$0"
exit 0
;;
*)
echo "Unbekannte Angabe: $1 (--hilfe zeigt die Möglichkeiten)" >&2
exit 1
;;
esac
shift
done
# --- Docker-Compose-Aufruf ermitteln ---------------------------------------
if docker compose version >/dev/null 2>&1; then
COMPOSE="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE="docker-compose"
else
echo "Weder 'docker compose' noch 'docker-compose' gefunden." >&2
exit 1
fi
meldung() { printf '\n\033[1;36m▸ %s\033[0m\n' "$1"; }
warnung() { printf '\033[1;33m! %s\033[0m\n' "$1"; }
fehler() { printf '\033[1;31m✗ %s\033[0m\n' "$1" >&2; }
fertig() { printf '\033[1;32m✓ %s\033[0m\n' "$1"; }
# --- 1. Sicherung ----------------------------------------------------------
if [ "$MIT_SICHERUNG" -eq 1 ]; then
meldung "Sicherung anlegen"
if $COMPOSE ps --status running --services 2>/dev/null | grep -qx "$DIENST"; then
ARCHIV="$($COMPOSE exec -T "$DIENST" /app/werkzeuge/sicherung.sh /daten/sicherungen | tail -1)"
fertig "Sicherung liegt im Volume unter ${ARCHIV}"
else
warnung "Container läuft nicht – Sicherung übersprungen."
fi
else
warnung "Sicherung auf Wunsch übersprungen."
fi
# --- 2. Quelltext holen ----------------------------------------------------
VORHER_STAND=""
if [ "$MIT_GIT" -eq 1 ]; then
if [ -d .git ]; then
meldung "Quelltext holen"
VORHER_STAND="$(git rev-parse HEAD)"
if [ -n "$(git status --porcelain)" ]; then
fehler "Im Arbeitsverzeichnis liegen Änderungen. Bitte erst aufräumen (git status)."
exit 1
fi
git fetch --prune origin
if [ -n "$ZWEIG" ]; then
git checkout "$ZWEIG"
fi
AKTUELLER_ZWEIG="$(git rev-parse --abbrev-ref HEAD)"
git merge --ff-only "origin/$AKTUELLER_ZWEIG"
fertig "Stand: $(git rev-parse --short HEAD) – $(git log -1 --pretty=%s)"
else
warnung "Kein Git-Repository – Quelltext bleibt, wie er ist."
fi
fi
# --- 3. Vorheriges Abbild sichern ------------------------------------------
ALTE_ID="$(docker image inspect "$ABBILD" --format '{{.Id}}' 2>/dev/null || true)"
if [ -n "$ALTE_ID" ]; then
docker tag "$ALTE_ID" "lernbar:vorher"
fi
# --- 4. Bauen --------------------------------------------------------------
meldung "Abbild bauen"
if ! $COMPOSE build --pull "$DIENST"; then
fehler "Der Bau ist fehlgeschlagen. Es läuft weiterhin die alte Fassung."
exit 1
fi
# --- 5. Umschalten ---------------------------------------------------------
meldung "Neu starten"
$COMPOSE up -d "$DIENST"
# --- 6. Lebenszeichen ------------------------------------------------------
meldung "Auf das Lebenszeichen warten (bis zu ${WARTEZEIT}s)"
GESUND=0
for _ in $(seq 1 "$WARTEZEIT"); do
ZUSTAND="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}kein-test{{end}}' "$DIENST" 2>/dev/null || echo unbekannt)"
case "$ZUSTAND" in
healthy | kein-test)
GESUND=1
break
;;
unhealthy)
break
;;
esac
sleep 1
done
if [ "$GESUND" -ne 1 ]; then
fehler "Die neue Fassung meldet sich nicht gesund. Ich schalte zurück."
docker logs --tail 60 "$DIENST" || true
if [ -n "$ALTE_ID" ]; then
docker tag "lernbar:vorher" "$ABBILD"
$COMPOSE up -d --no-build "$DIENST"
warnung "Zurückgeschaltet auf das vorherige Abbild."
fi
if [ -n "$VORHER_STAND" ]; then
warnung "Quelltext steht auf einem neueren Stand. Zurück mit: git reset --hard $VORHER_STAND"
fi
exit 1
fi
# --- 7. Aufräumen ----------------------------------------------------------
meldung "Aufräumen"
docker image prune -f --filter "label=org.opencontainers.image.title=lernbar" >/dev/null 2>&1 || true
docker image rm "lernbar:vorher" >/dev/null 2>&1 || true
fertig "Lern-Bar läuft in der neuen Fassung."
$COMPOSE ps "$DIENST"