Erscheinungsbild
Technische Dokumentation
1. Projektstruktur
Der BKT-Manager ist als npm-Workspaces-Monorepo mit den drei Workspaces common, backend und frontend aufgebaut. Die Abhängigkeiten werden einmalig im Root-Verzeichnis installiert (npm ci). Befehle für einen einzelnen Workspace werden im Root-Verzeichnis mit -w <workspace> ausgeführt, z. B. npm test -w backend.
1.1 common
Das common-Paket enthält den gemeinsamen Vertrag zwischen Frontend und Backend: DTOs, Enums, Konstanten, Statistikfunktionen und weitere Hilfsfunktionen. Alle öffentlichen Typen und Funktionen werden über die Datei src/index.ts exportiert und in Frontend und Backend als @kt/common importiert.
Das Paket wird in zwei Varianten gebaut: als CommonJS-Modul für das Backend (tsconfig.backend.json) und als ES-Modul für das Frontend (tsconfig.frontend.json). Frontend und Backend verwenden den gebauten Code. Nach Änderungen im common-Paket muss es daher neu gebaut werden:
bash
npm run build -w common1.2 backend
Das Backend ist eine NestJS-REST-API, die nach fachlichen Modulen unter backend/src/modules/ gegliedert ist, z. B.:
| Modul | Aufgabe |
|---|---|
auth | Authentifizierung und Guards |
tests | Testtypen, Testvarianten, Tests (Messzeitpunkte), Subtests, Feedback-Gruppen |
classes | Klassen und Klassenergebnisse |
registration-management, registration-data | Klassenanmeldungen |
testcenter | Kommunikation mit dem Testcenter |
results, autocoder | Import, Kodierung und Auswertung von Antworten |
queue | Hintergrundprozesse (BullMQ) |
materials, manuals | Förderhinweise und Manuale |
impex | Import und Export von Testtypen |
Jedes Modul besteht in der Regel aus einem Controller (Endpunkte), einem oder mehreren Services (Fachlogik und Datenbankzugriff) und den zugehörigen Unit-Tests (*.spec.ts), die neben dem Quellcode liegen.
Die Synchronisation mit dem Testcenter und die Verarbeitung von Antwortdaten werden als Hintergrundprozesse über BullMQ mit Redis ausgeführt. Schlägt ein Job fehl, wird der Fehler geloggt und erneut geworfen, damit BullMQ den Job erneut ausführen kann.
Aufrufe externer Dienste (Testcenter, KT-Schulportal, Kodierungs-Microservice) erfolgen ausschließlich im Backend über den HTTP-Client von NestJS (@nestjs/axios).
1.3 frontend
Das Frontend ist eine Vue-3-Anwendung mit Vuetify, die mit Vite gebaut wird. Es besteht aus mehreren Einstiegspunkten (frontend/vite.config.ts):
| Einstiegspunkt | Beschreibung |
|---|---|
registration | Web-Komponente für die Klassenanmeldung |
admin | Web-Komponente für die Administration |
registration.html, admin.html | Eigenständige Seiten für Klassenanmeldung und Administration |
codecards.html | Codelisten |
feedback.html | Rückmeldungen |
login.html | Dem Testcenter vorgeschaltete Loginseite für Schüler*innen |
Der Anwendungszustand wird mit Pinia verwaltet (frontend/src/stores/). Alle HTTP-Anfragen an das Backend laufen über den zentralen Service in frontend/src/backend.ts.
1.4 Tests
| Bereich | Befehl |
|---|---|
common | npm test -w common |
| Backend (Unit) | npm test -w backend |
| Backend (Integration) | docker compose exec backend npm run test:e2e |
| Frontend | npm test -w frontend |
Die Integrationstests des Backends (backend/test/*.e2e-spec.ts) greifen auf eine echte Datenbank zu und werden daher im Docker-Container ausgeführt.
1.5 Datenbankmigrationen
Änderungen am Datenbankschema erfolgen ausschließlich über TypeORM-Migrationen in backend/src/database/migrations/. Die Migrationsbefehle arbeiten mit dem gebauten Code, daher muss das Backend vorher gebaut werden:
bash
npm run build -w backend
npm run migration:generate -w backend --name=MeineMigration # Migration aus Entity-Änderungen erzeugen
npm run migration:create -w backend --name=MeineMigration # Leere Migration erzeugen
npm run migration:run -w backend # Migrationen ausführen
npm run migration:revert -w backend # Letzte Migration rückgängig machen2. Authentifizierung
Die Authentifizierung im BKT-System erfolgt über mehrere Stufen und nutzt sowohl das externe KT-System (kompetenztest.de) als auch eigene JWT Tokens für die Session-Verwaltung.
Erläuterung des Authentifizierungsprozesses im Frontend und Backend:
- Frontend Login-Prozess
- Es gibt drei Arten von Logins: SCHOOL, ADMIN und ADMIN_SCHOOL
- SCHOOL: Zugriff einer Schule auf die Klassenanmeldung und Rückmeldungen
- ADMIN: Zugriff auf die Administration
- ADMIN_SCHOOL: Zugriff von Schuladministrationen auf die Klassenanmeldung einer bestimmten Schule
- Die Authentifizierung beginnt mit einem KT-Token (kompetenztest.de Token)
- Backend Auth Service
typescript
// Datei: backend/src/modules/auth/auth.service.ts
@Injectable()
export class AuthService {
async authenticate(ktToken: string, type: AuthType): Promise<TokenResponseDto> {
// 1. Verifiziere KT-Token
let authResponse: KtAuthResponse;
try {
authResponse = await this.verifyToken(ktToken, type);
} catch (err: any) {
// Fehlerbehandlung für ungültige Tokens
if (err instanceof AxiosError) {
if (err.response) {
const message = err.response.data?.status_message;
throw new UnauthorizedException(message);
}
// ... weitere Fehlerbehandlung
}
}
// 2. Prüfe Auth Response (altes und neues Antwortformat des KT-Systems)
if (
('status' in authResponse && authResponse.status === false) ||
('success' in authResponse && authResponse.success === false)
) {
throw new UnauthorizedException(/* ... */);
}
// 3. Ermittle Benutzer- bzw. Schul-ID aus der Antwort
let sub: string;
if (type === AuthType.Admin && 'user_id' in authResponse) {
sub = authResponse.user_id;
} else if (type === AuthType.Admin && 'data' in authResponse) {
sub = authResponse.data.user_id;
} else if ('id' in authResponse) {
sub = authResponse.id;
} else if ('data' in authResponse) {
sub = authResponse.data.id;
}
// 4. Erstelle JWT Token
const payload = {
sub,
type: type,
ktToken: ktToken,
isAdmin: type === AuthType.Admin || type === AuthType.AdminSchool,
};
// 5. Sende JWT Token zurück
return {
access_token: await this.jwtService.signAsync(payload),
};
}
private async verifyToken(ktToken: string, type: AuthType) {
// Token-Header basierend auf Type
// SCHOOL: X-API-KEY-SCHOOL, ADMIN: X-API-KEY-USER, ADMIN_SCHOOL: X-API-KEY
const tokenHeaderName = this.getTokenHeaderName(type);
const headers = {};
headers[tokenHeaderName] = ktToken;
// Optional: Hostname Header
if (this.ktAuthHostname) {
headers['Host'] = this.ktAuthHostname;
}
// Verifiziere Token gegen KT Auth System
const response = await lastValueFrom(
this.httpService.get<KtAuthResponse>(this.ktAuthUrl, {
headers: headers,
})
);
return response.data;
}
}- Authentifizierungsfluss:
- Frontend sendet KT-Token an
/auth/token - Backend verifiziert Token gegen KT-Auth-System
- Bei erfolgreicher Verifizierung wird JWT Token generiert
- JWT Token wird ans Frontend zurückgesendet
- Frontend speichert JWT Token und nutzt ihn für weitere Requests
- JWT Verwendung:
- Jeder API Request vom Frontend enthält den JWT Token im Authorization Header
- Backend validiert JWT Token mit AuthGuard
- Token enthält Informationen über:
- Benutzer ID bzw. Schul-ID (sub)
- Token Typ (SCHOOL, ADMIN oder ADMIN_SCHOOL)
- Ob es sich um einen administrativen Zugriff handelt (isAdmin)
- Original KT-Token
- Guards im Backend:
Die Guards befinden sich in backend/src/modules/auth/guards/:
AuthGuard: Zugriff für authentifizierte Schulen und Administrator*innenAdminGuard: Zugriff nur für Administrator*innenSchoolGuard: Zugriff nur für Schulen
Bei Routen für Schulen wird die Schul-ID aus dem authentifizierten Request ermittelt. Administrator*innen können bei einigen Routen die Schul-ID über den Query-Parameter schoolId angeben.
typescript
@Controller('registrations')
@UseGuards(AuthGuard) // Beispiel für geschützte Route
@ApiBearerAuth()
export class RegistrationController {
// ... Controller Logik
}- Frontend API Service:
typescript
// Datei: frontend/src/backend.ts (vereinfacht)
this.instance = axios.create({ baseURL });
// Automatisches Hinzufügen des JWT Tokens
this.instance.interceptors.request.use(async (config) => {
const token = getStoredToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});Der Prozess stellt sicher, dass:
- Nur authentifizierte Benutzer Zugriff haben
- Verschiedene Benutzertypen (SCHOOL/ADMIN/ADMIN_SCHOOL) unterschiedliche Rechte haben
- Die Kommunikation mit dem KT-System sicher ist
3. Datenbankschema

