Baustein 2: Inhalte einlesen und pruefen, Handouts, Suche, Glossar

Die fachlichen Inhalte liegen als Dateien unter content/ und werden beim
Start eingelesen, geprueft und in den Speicher geladen.

- Inhaltslader mit Zod-Schemata und Querverweispruefung
- Fehlermeldungen nennen Datei und Zeile, gesammelt statt beim ersten
  Fehler geworfen; die Anwendung startet bei Fehlern nicht
- npm run inhalte:pruefen prueft ohne Start
- Handout-Ansicht mit festem Aufbau, Uebersicht nach Themenbloecken
- Volltextsuche mit Gewichtung und Trefferausschnitt
- Gelesen-Markierung je Handout
- Glossar mit Suche und automatischer Verlinkung in den Handouts
- Beispielinhalte: 4 Wochen, 12 Handouts, 24 Merkkarten, 4 Aufgaben,
  4 Checklisten, 2 Lernzielkontrollen, 24 Glossareintraege
- rahmenplan.yaml aus FIAusbV 2020 und KMK-Rahmenlehrplan, mit Fundstellen
- gray-matter entfernt, YAML-Kopf wird selbst herausgeloest
- npm run screenshots nimmt die Bilder fuer die Anleitung auf
- 39 neue Tests, insgesamt 68

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-10 07:56:38 +02:00
co-authored by Claude Opus 5
parent e856349cd4
commit 1af5613e9b
70 changed files with 4779 additions and 124 deletions
+43
View File
@@ -0,0 +1,43 @@
import { and, eq } from 'drizzle-orm';
import { datenbank, schema } from '../db';
import { jetzt } from '../zeit';
/** Fortschritt beim Lesen der Handouts. */
export function gelesenSet(benutzerId: string): Set<string> {
const zeilen = datenbank()
.select({ handoutId: schema.handoutGelesen.handoutId })
.from(schema.handoutGelesen)
.where(eq(schema.handoutGelesen.benutzerId, benutzerId))
.all();
return new Set(zeilen.map((z) => z.handoutId));
}
export function gelesenAm(benutzerId: string, handoutId: string): string | null {
const zeile = datenbank()
.select({ gelesenAm: schema.handoutGelesen.gelesenAm })
.from(schema.handoutGelesen)
.where(and(eq(schema.handoutGelesen.benutzerId, benutzerId), eq(schema.handoutGelesen.handoutId, handoutId)))
.get();
return zeile?.gelesenAm ?? null;
}
export function alsGelesenMerken(benutzerId: string, handoutId: string): void {
datenbank()
.insert(schema.handoutGelesen)
.values({ benutzerId, handoutId, gelesenAm: jetzt() })
.onConflictDoNothing()
.run();
}
export function alsUngelesenMerken(benutzerId: string, handoutId: string): void {
datenbank()
.delete(schema.handoutGelesen)
.where(and(eq(schema.handoutGelesen.benutzerId, benutzerId), eq(schema.handoutGelesen.handoutId, handoutId)))
.run();
}
/** Anzahl gelesener Handouts – für die Fortschrittsanzeige. */
export function anzahlGelesen(benutzerId: string): number {
return gelesenSet(benutzerId).size;
}