/** * Academy API Client * * API client for the Compliance E-Learning Academy module * Connects to the ai-compliance-sdk backend via Next.js proxy */ import { Course, CourseCategory, CourseCreateRequest, CourseUpdateRequest, Enrollment, EnrollmentStatus, EnrollmentListResponse, EnrollUserRequest, UpdateProgressRequest, Certificate, AcademyStatistics, SubmitQuizRequest, SubmitQuizResponse, GenerateCourseRequest, GenerateCourseResponse, VideoStatus, isEnrollmentOverdue } from './types' // ============================================================================= // CONFIGURATION // ============================================================================= const ACADEMY_API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8093' const API_TIMEOUT = 30000 // 30 seconds // ============================================================================= // HELPER FUNCTIONS // ============================================================================= function getTenantId(): string { if (typeof window !== 'undefined') { return localStorage.getItem('bp_tenant_id') || 'default-tenant' } return 'default-tenant' } function getAuthHeaders(): HeadersInit { const headers: HeadersInit = { 'Content-Type': 'application/json', 'X-Tenant-ID': getTenantId() } if (typeof window !== 'undefined') { const token = localStorage.getItem('authToken') if (token) { headers['Authorization'] = `Bearer ${token}` } const userId = localStorage.getItem('bp_user_id') if (userId) { headers['X-User-ID'] = userId } } return headers } async function fetchWithTimeout( url: string, options: RequestInit = {}, timeout: number = API_TIMEOUT ): Promise { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) try { const response = await fetch(url, { ...options, signal: controller.signal, headers: { ...getAuthHeaders(), ...options.headers } }) if (!response.ok) { const errorBody = await response.text() let errorMessage = `HTTP ${response.status}: ${response.statusText}` try { const errorJson = JSON.parse(errorBody) errorMessage = errorJson.error || errorJson.message || errorMessage } catch { // Keep the HTTP status message } throw new Error(errorMessage) } // Handle empty responses const contentType = response.headers.get('content-type') if (contentType && contentType.includes('application/json')) { return response.json() } return {} as T } finally { clearTimeout(timeoutId) } } // ============================================================================= // COURSE CRUD // ============================================================================= /** * Alle Kurse abrufen */ export async function fetchCourses(): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses` ) } /** * Einzelnen Kurs abrufen */ export async function fetchCourse(id: string): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses/${id}` ) } /** * Neuen Kurs erstellen */ export async function createCourse(request: CourseCreateRequest): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses`, { method: 'POST', body: JSON.stringify(request) } ) } /** * Kurs aktualisieren */ export async function updateCourse(id: string, update: CourseUpdateRequest): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses/${id}`, { method: 'PUT', body: JSON.stringify(update) } ) } /** * Kurs loeschen */ export async function deleteCourse(id: string): Promise { await fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses/${id}`, { method: 'DELETE' } ) } // ============================================================================= // ENROLLMENTS // ============================================================================= /** * Einschreibungen abrufen (optional gefiltert nach Kurs-ID) */ export async function fetchEnrollments(courseId?: string): Promise { const params = new URLSearchParams() if (courseId) { params.set('courseId', courseId) } const queryString = params.toString() const url = `${ACADEMY_API_BASE}/api/v1/academy/enrollments${queryString ? `?${queryString}` : ''}` return fetchWithTimeout(url) } /** * Benutzer in einen Kurs einschreiben */ export async function enrollUser(request: EnrollUserRequest): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/enrollments`, { method: 'POST', body: JSON.stringify(request) } ) } /** * Fortschritt einer Einschreibung aktualisieren */ export async function updateProgress(enrollmentId: string, update: UpdateProgressRequest): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/enrollments/${enrollmentId}/progress`, { method: 'PUT', body: JSON.stringify(update) } ) } /** * Einschreibung als abgeschlossen markieren */ export async function completeEnrollment(enrollmentId: string): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/enrollments/${enrollmentId}/complete`, { method: 'POST' } ) } // ============================================================================= // CERTIFICATES // ============================================================================= /** * Zertifikat abrufen */ export async function fetchCertificate(id: string): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/certificates/${id}` ) } /** * Zertifikat generieren nach erfolgreichem Kursabschluss */ export async function generateCertificate(enrollmentId: string): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/enrollments/${enrollmentId}/certificate`, { method: 'POST' } ) } // ============================================================================= // QUIZ // ============================================================================= /** * Quiz-Antworten einreichen und auswerten */ export async function submitQuiz(lessonId: string, answers: SubmitQuizRequest): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/lessons/${lessonId}/quiz`, { method: 'POST', body: JSON.stringify(answers) } ) } // ============================================================================= // STATISTICS // ============================================================================= /** * Academy-Statistiken abrufen */ export async function fetchAcademyStatistics(): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/statistics` ) } // ============================================================================= // AI GENERATION // ============================================================================= /** * KI-generiert einen kompletten Kurs */ export async function generateCourse(request: GenerateCourseRequest): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses/generate`, { method: 'POST', body: JSON.stringify(request) } ) } /** * Einzelne Lektion neu generieren */ export async function regenerateLesson(lessonId: string): Promise<{ lessonId: string; status: string }> { return fetchWithTimeout<{ lessonId: string; status: string }>( `${ACADEMY_API_BASE}/api/v1/academy/lessons/${lessonId}/regenerate`, { method: 'POST' } ) } // ============================================================================= // VIDEO GENERATION // ============================================================================= /** * Videos fuer alle Lektionen eines Kurses generieren */ export async function generateVideos(courseId: string): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses/${courseId}/generate-videos`, { method: 'POST' } ) } /** * Video-Generierungs-Status abrufen */ export async function getVideoStatus(courseId: string): Promise { return fetchWithTimeout( `${ACADEMY_API_BASE}/api/v1/academy/courses/${courseId}/video-status` ) } // ============================================================================= // CERTIFICATES (Extended) // ============================================================================= /** * Zertifikat als PDF herunterladen */ export async function downloadCertificatePDF(certificateId: string): Promise { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT) try { const response = await fetch( `${ACADEMY_API_BASE}/api/v1/academy/certificates/${certificateId}/pdf`, { signal: controller.signal, headers: getAuthHeaders() } ) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } return response.blob() } finally { clearTimeout(timeoutId) } } // ============================================================================= // SDK PROXY FUNCTION (wraps fetchCourses + fetchAcademyStatistics) // ============================================================================= /** * Kurse und Statistiken laden - mit Fallback auf Mock-Daten */ export async function fetchSDKAcademyList(): Promise<{ courses: Course[] enrollments: Enrollment[] statistics: AcademyStatistics }> { try { const [courses, enrollments, statistics] = await Promise.all([ fetchCourses(), fetchEnrollments(), fetchAcademyStatistics() ]) return { courses, enrollments, statistics } } catch (error) { console.error('Failed to load Academy data from backend, using mock data:', error) // Fallback to mock data const courses = createMockCourses() const enrollments = createMockEnrollments() const statistics = createMockStatistics(courses, enrollments) return { courses, enrollments, statistics } } } // ============================================================================= // MOCK DATA (Fallback / Demo) // ============================================================================= /** * Demo-Kurse mit deutschen Titeln erstellen */ export function createMockCourses(): Course[] { const now = new Date() return [ { id: 'course-001', title: 'DSGVO-Grundlagen fuer Mitarbeiter', description: 'Umfassende Einfuehrung in die Datenschutz-Grundverordnung. Dieses Pflichttraining vermittelt die wichtigsten Grundsaetze des Datenschutzes, Betroffenenrechte und die korrekte Handhabung personenbezogener Daten im Arbeitsalltag.', category: 'dsgvo_basics', durationMinutes: 90, requiredForRoles: ['all'], createdAt: new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000).toISOString(), updatedAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(), lessons: [ { id: 'lesson-001-01', courseId: 'course-001', order: 1, title: 'Was ist die DSGVO?', type: 'text', contentMarkdown: '# Was ist die DSGVO?\n\nDie Datenschutz-Grundverordnung (DSGVO) ist eine Verordnung der Europaeischen Union...', durationMinutes: 15 }, { id: 'lesson-001-02', courseId: 'course-001', order: 2, title: 'Die 7 Grundsaetze der DSGVO', type: 'video', contentMarkdown: 'Videoerklaerung der Grundsaetze: Rechtmaessigkeit, Zweckbindung, Datenminimierung, Richtigkeit, Speicherbegrenzung, Integritaet und Vertraulichkeit, Rechenschaftspflicht.', durationMinutes: 20, videoUrl: '/videos/dsgvo-grundsaetze.mp4' }, { id: 'lesson-001-03', courseId: 'course-001', order: 3, title: 'Betroffenenrechte (Art. 15-21)', type: 'text', contentMarkdown: '# Betroffenenrechte\n\nUebersicht der Betroffenenrechte: Auskunft, Berichtigung, Loeschung, Einschraenkung, Datenuebertragbarkeit, Widerspruch.', durationMinutes: 20 }, { id: 'lesson-001-04', courseId: 'course-001', order: 4, title: 'Personenbezogene Daten im Arbeitsalltag', type: 'video', contentMarkdown: 'Praxisbeispiele fuer den korrekten Umgang mit personenbezogenen Daten am Arbeitsplatz.', durationMinutes: 15, videoUrl: '/videos/dsgvo-praxis.mp4' }, { id: 'lesson-001-05', courseId: 'course-001', order: 5, title: 'Wissenstest: DSGVO-Grundlagen', type: 'quiz', contentMarkdown: 'Testen Sie Ihr Wissen zu den DSGVO-Grundlagen.', durationMinutes: 20 } ] }, { id: 'course-002', title: 'IT-Sicherheit & Cybersecurity Awareness', description: 'Sensibilisierung fuer IT-Sicherheitsrisiken und Best Practices im Umgang mit Phishing, Passwoertern, Social Engineering und sicherer Kommunikation.', category: 'it_security', durationMinutes: 60, requiredForRoles: ['all'], createdAt: new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000).toISOString(), updatedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(), lessons: [ { id: 'lesson-002-01', courseId: 'course-002', order: 1, title: 'Phishing erkennen und vermeiden', type: 'video', contentMarkdown: 'Wie erkennt man Phishing-E-Mails und was tut man im Verdachtsfall?', durationMinutes: 15, videoUrl: '/videos/phishing-awareness.mp4' }, { id: 'lesson-002-02', courseId: 'course-002', order: 2, title: 'Sichere Passwoerter und MFA', type: 'text', contentMarkdown: '# Sichere Passwoerter\n\nRichtlinien fuer starke Passwoerter, Passwort-Manager und Multi-Faktor-Authentifizierung.', durationMinutes: 15 }, { id: 'lesson-002-03', courseId: 'course-002', order: 3, title: 'Social Engineering und Manipulation', type: 'text', contentMarkdown: '# Social Engineering\n\nMethoden von Angreifern zur Manipulation von Mitarbeitern und Schutzmassnahmen.', durationMinutes: 15 }, { id: 'lesson-002-04', courseId: 'course-002', order: 4, title: 'Wissenstest: IT-Sicherheit', type: 'quiz', contentMarkdown: 'Testen Sie Ihr Wissen zur IT-Sicherheit.', durationMinutes: 15 } ] }, { id: 'course-003', title: 'AI Literacy - Sicherer Umgang mit KI', description: 'Grundlagen kuenstlicher Intelligenz, EU AI Act, verantwortungsvoller Einsatz von KI-Werkzeugen und Risiken bei der Nutzung von Large Language Models (LLMs) im Unternehmen.', category: 'ai_literacy', durationMinutes: 75, requiredForRoles: ['admin', 'data_protection_officer'], createdAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(), updatedAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000).toISOString(), lessons: [ { id: 'lesson-003-01', courseId: 'course-003', order: 1, title: 'Was ist Kuenstliche Intelligenz?', type: 'text', contentMarkdown: '# Was ist KI?\n\nGrundlagen von Machine Learning, Deep Learning und Large Language Models in verstaendlicher Sprache.', durationMinutes: 15 }, { id: 'lesson-003-02', courseId: 'course-003', order: 2, title: 'Der EU AI Act - Was bedeutet er fuer uns?', type: 'video', contentMarkdown: 'Ueberblick ueber den EU AI Act, Risikoklassen und Anforderungen fuer Unternehmen.', durationMinutes: 20, videoUrl: '/videos/eu-ai-act.mp4' }, { id: 'lesson-003-03', courseId: 'course-003', order: 3, title: 'KI-Werkzeuge sicher nutzen', type: 'text', contentMarkdown: '# KI-Werkzeuge sicher nutzen\n\nRichtlinien fuer den Einsatz von ChatGPT, Copilot & Co.', durationMinutes: 20 }, { id: 'lesson-003-04', courseId: 'course-003', order: 4, title: 'Wissenstest: AI Literacy', type: 'quiz', contentMarkdown: 'Testen Sie Ihr Wissen zum sicheren Umgang mit KI.', durationMinutes: 20 } ] } ] } /** * Demo-Einschreibungen erstellen */ export function createMockEnrollments(): Enrollment[] { const now = new Date() return [ { id: 'enr-001', courseId: 'course-001', userId: 'user-001', userName: 'Maria Fischer', userEmail: 'maria.fischer@example.de', status: 'in_progress', progress: 40, startedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(), deadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString() }, { id: 'enr-002', courseId: 'course-002', userId: 'user-002', userName: 'Stefan Mueller', userEmail: 'stefan.mueller@example.de', status: 'completed', progress: 100, startedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(), completedAt: new Date(now.getTime() - 12 * 24 * 60 * 60 * 1000).toISOString(), certificateId: 'cert-001', deadline: new Date(now.getTime() + 10 * 24 * 60 * 60 * 1000).toISOString() }, { id: 'enr-003', courseId: 'course-001', userId: 'user-003', userName: 'Laura Schneider', userEmail: 'laura.schneider@example.de', status: 'not_started', progress: 0, startedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(), deadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString() }, { id: 'enr-004', courseId: 'course-003', userId: 'user-004', userName: 'Thomas Wagner', userEmail: 'thomas.wagner@example.de', status: 'expired', progress: 25, startedAt: new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000).toISOString(), deadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString() }, { id: 'enr-005', courseId: 'course-002', userId: 'user-005', userName: 'Julia Becker', userEmail: 'julia.becker@example.de', status: 'in_progress', progress: 50, startedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(), deadline: new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString() } ] } /** * Demo-Statistiken aus Kursen und Einschreibungen berechnen */ export function createMockStatistics(courses?: Course[], enrollments?: Enrollment[]): AcademyStatistics { const c = courses || createMockCourses() const e = enrollments || createMockEnrollments() const completedCount = e.filter(en => en.status === 'completed').length const completionRate = e.length > 0 ? Math.round((completedCount / e.length) * 100) : 0 const overdueCount = e.filter(en => isEnrollmentOverdue(en)).length return { totalCourses: c.length, totalEnrollments: e.length, completionRate, overdueCount, byCategory: { dsgvo_basics: c.filter(co => co.category === 'dsgvo_basics').length, it_security: c.filter(co => co.category === 'it_security').length, ai_literacy: c.filter(co => co.category === 'ai_literacy').length, whistleblower_protection: c.filter(co => co.category === 'whistleblower_protection').length, custom: c.filter(co => co.category === 'custom').length, }, byStatus: { not_started: e.filter(en => en.status === 'not_started').length, in_progress: e.filter(en => en.status === 'in_progress').length, completed: e.filter(en => en.status === 'completed').length, expired: e.filter(en => en.status === 'expired').length, } } }