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
+52
View File
@@ -0,0 +1,52 @@
#!/bin/sh
# ---------------------------------------------------------------------------
# Sicherung: Datenbank und Anhänge in ein Archiv schreiben.
#
# Läuft sowohl im Container als auch auf dem Entwicklungsrechner (Git Bash).
#
# sicherung.sh [zielverzeichnis]
#
# Ergebnis: <zielverzeichnis>/lernbar-JJJJMMTT-HHMMSS.tar.gz
# ---------------------------------------------------------------------------
set -eu
DATEN="${LERNBAR_DATEN:-/daten}"
DB="${LERNBAR_DB:-$DATEN/lernbar.sqlite}"
ZIEL="${1:-${LERNBAR_SICHERUNGEN:-$DATEN/sicherungen}}"
if [ ! -f "$DB" ]; then
echo "Keine Datenbank unter $DB gefunden." >&2
exit 1
fi
mkdir -p "$ZIEL"
STEMPEL="$(date +%Y%m%d-%H%M%S)"
ARBEIT="$(mktemp -d)"
trap 'rm -rf "$ARBEIT"' EXIT
# Konsistente Kopie über die Sicherungsschnittstelle von SQLite. Ein blosses
# `cp` wäre bei aktivem WAL-Modus nicht verlässlich.
node -e "
const Database = require('better-sqlite3');
const db = new Database(process.argv[1], { readonly: true });
db.backup(process.argv[2]).then(() => { db.close(); }).catch((f) => { console.error(f); process.exit(1); });
" "$DB" "$ARBEIT/lernbar.sqlite"
mkdir -p "$ARBEIT/anhaenge"
if [ -d "$DATEN/anhaenge" ]; then
cp -R "$DATEN/anhaenge/." "$ARBEIT/anhaenge/" 2>/dev/null || true
fi
cat > "$ARBEIT/sicherung.txt" <<ENDE
Lern-Bar Sicherung
Erstellt am: $(date -Iseconds)
Quelle: $DATEN
Inhalt: lernbar.sqlite (Datenbank), anhaenge/ (hochgeladene Dateien)
Zurückspielen: wiederherstellen.sh <diese-datei.tar.gz>
ENDE
ARCHIV="$ZIEL/lernbar-$STEMPEL.tar.gz"
tar -czf "$ARCHIV" -C "$ARBEIT" lernbar.sqlite anhaenge sicherung.txt
echo "$ARCHIV"
+65
View File
@@ -0,0 +1,65 @@
#!/bin/sh
# ---------------------------------------------------------------------------
# Wiederherstellung aus einem Sicherungsarchiv.
#
# wiederherstellen.sh <archiv.tar.gz> [--ohne-rueckfrage]
#
# Der vorhandene Datenbestand wird vorher zur Seite gelegt
# (daten/verdraengt-<zeitstempel>/), nichts wird endgültig gelöscht.
# Die Anwendung sollte dabei gestoppt sein.
# ---------------------------------------------------------------------------
set -eu
ARCHIV="${1:-}"
BESTAETIGT="${2:-}"
if [ -z "$ARCHIV" ] || [ ! -f "$ARCHIV" ]; then
echo "Aufruf: wiederherstellen.sh <archiv.tar.gz> [--ohne-rueckfrage]" >&2
exit 1
fi
DATEN="${LERNBAR_DATEN:-/daten}"
DB="${LERNBAR_DB:-$DATEN/lernbar.sqlite}"
if [ "$BESTAETIGT" != "--ohne-rueckfrage" ]; then
printf 'Der aktuelle Datenbestand unter %s wird ersetzt. Fortfahren? [j/N] ' "$DATEN"
read -r antwort
case "$antwort" in
j | J | ja | Ja) ;;
*)
echo "Abgebrochen."
exit 1
;;
esac
fi
ARBEIT="$(mktemp -d)"
trap 'rm -rf "$ARBEIT"' EXIT
tar -xzf "$ARCHIV" -C "$ARBEIT"
if [ ! -f "$ARBEIT/lernbar.sqlite" ]; then
echo "Das Archiv enthält keine lernbar.sqlite – falsche Datei?" >&2
exit 1
fi
STEMPEL="$(date +%Y%m%d-%H%M%S)"
VERDRAENGT="$DATEN/verdraengt-$STEMPEL"
mkdir -p "$VERDRAENGT"
for datei in "$DB" "$DB-wal" "$DB-shm"; do
[ -e "$datei" ] && mv "$datei" "$VERDRAENGT/" || true
done
if [ -d "$DATEN/anhaenge" ]; then
mv "$DATEN/anhaenge" "$VERDRAENGT/anhaenge"
fi
mkdir -p "$DATEN"
cp "$ARBEIT/lernbar.sqlite" "$DB"
mkdir -p "$DATEN/anhaenge"
if [ -d "$ARBEIT/anhaenge" ]; then
cp -R "$ARBEIT/anhaenge/." "$DATEN/anhaenge/" 2>/dev/null || true
fi
echo "Wiederhergestellt aus $ARCHIV."
echo "Der vorherige Stand liegt unter $VERDRAENGT und kann gelöscht werden, sobald alles passt."