/** * Training Engine API Client * Communicates with the Go backend via Next.js API proxy at /api/sdk/v1/training/* */ import type { ModuleListResponse, AssignmentListResponse, MatrixResponse, AuditLogResponse, EscalationResponse, DeadlineListResponse, TrainingModule, TrainingAssignment, ModuleContent, QuizQuestion, QuizAttempt, QuizSubmitResponse, TrainingStats, TrainingMedia, } from './types' const BASE_URL = '/api/sdk/v1/training' async function apiFetch(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE_URL}${path}`, { ...options, headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': typeof window !== 'undefined' ? (localStorage.getItem('bp-tenant-id') || 'default') : 'default', ...options?.headers, }, }) if (!res.ok) { const error = await res.json().catch(() => ({ error: res.statusText })) throw new Error(error.error || `API Error: ${res.status}`) } return res.json() } // ============================================================================= // MODULES // ============================================================================= export async function getModules(filters?: { regulation_area?: string frequency_type?: string search?: string }): Promise { const params = new URLSearchParams() if (filters?.regulation_area) params.set('regulation_area', filters.regulation_area) if (filters?.frequency_type) params.set('frequency_type', filters.frequency_type) if (filters?.search) params.set('search', filters.search) const qs = params.toString() return apiFetch(`/modules${qs ? `?${qs}` : ''}`) } export async function getModule(id: string): Promise<{ module: TrainingModule content: ModuleContent | null questions: QuizQuestion[] }> { return apiFetch(`/modules/${id}`) } export async function createModule(data: { module_code: string title: string description?: string regulation_area: string frequency_type: string duration_minutes?: number pass_threshold?: number }): Promise { return apiFetch('/modules', { method: 'POST', body: JSON.stringify(data), }) } export async function updateModule(id: string, data: Record): Promise { return apiFetch(`/modules/${id}`, { method: 'PUT', body: JSON.stringify(data), }) } export async function deleteModule(id: string): Promise { return apiFetch(`/modules/${id}`, { method: 'DELETE' }) } // ============================================================================= // MATRIX // ============================================================================= export async function getMatrix(): Promise { return apiFetch('/matrix') } export async function getMatrixForRole(role: string): Promise<{ role: string label: string entries: Array<{ module_id: string; module_code: string; module_title: string; is_mandatory: boolean; priority: number }> total: number }> { return apiFetch(`/matrix/${role}`) } export async function setMatrixEntry(data: { role_code: string module_id: string is_mandatory: boolean priority: number }): Promise { return apiFetch('/matrix', { method: 'POST', body: JSON.stringify(data), }) } export async function deleteMatrixEntry(role: string, moduleId: string): Promise { return apiFetch(`/matrix/${role}/${moduleId}`, { method: 'DELETE' }) } // ============================================================================= // ASSIGNMENTS // ============================================================================= export async function computeAssignments(data: { user_id: string user_name: string user_email: string roles: string[] trigger?: string }): Promise<{ assignments: TrainingAssignment[]; created: number }> { return apiFetch('/assignments/compute', { method: 'POST', body: JSON.stringify(data), }) } export async function getAssignments(filters?: { user_id?: string module_id?: string role?: string status?: string limit?: number offset?: number }): Promise { const params = new URLSearchParams() if (filters?.user_id) params.set('user_id', filters.user_id) if (filters?.module_id) params.set('module_id', filters.module_id) if (filters?.role) params.set('role', filters.role) if (filters?.status) params.set('status', filters.status) if (filters?.limit) params.set('limit', String(filters.limit)) if (filters?.offset) params.set('offset', String(filters.offset)) const qs = params.toString() return apiFetch(`/assignments${qs ? `?${qs}` : ''}`) } export async function getAssignment(id: string): Promise { return apiFetch(`/assignments/${id}`) } export async function startAssignment(id: string): Promise<{ status: string }> { return apiFetch(`/assignments/${id}/start`, { method: 'POST' }) } export async function updateAssignmentProgress(id: string, progress: number): Promise<{ status: string; progress: number }> { return apiFetch(`/assignments/${id}/progress`, { method: 'POST', body: JSON.stringify({ progress }), }) } export async function completeAssignment(id: string): Promise<{ status: string }> { return apiFetch(`/assignments/${id}/complete`, { method: 'POST' }) } export async function updateAssignment(id: string, data: { deadline?: string }): Promise { return apiFetch(`/assignments/${id}`, { method: 'PUT', body: JSON.stringify(data), }) } // ============================================================================= // QUIZ // ============================================================================= export async function getQuiz(moduleId: string): Promise<{ questions: QuizQuestion[]; total: number }> { return apiFetch(`/quiz/${moduleId}`) } export async function submitQuiz(moduleId: string, data: { assignment_id: string answers: Array<{ question_id: string; selected_index: number }> duration_seconds?: number }): Promise { return apiFetch(`/quiz/${moduleId}/submit`, { method: 'POST', body: JSON.stringify(data), }) } export async function getQuizAttempts(assignmentId: string): Promise<{ attempts: QuizAttempt[]; total: number }> { return apiFetch(`/quiz/attempts/${assignmentId}`) } // ============================================================================= // CONTENT GENERATION // ============================================================================= export async function generateContent(moduleId: string, language?: string): Promise { return apiFetch('/content/generate', { method: 'POST', body: JSON.stringify({ module_id: moduleId, language: language || 'de' }), }) } export async function generateQuiz(moduleId: string, count?: number): Promise<{ questions: QuizQuestion[]; total: number }> { return apiFetch('/content/generate-quiz', { method: 'POST', body: JSON.stringify({ module_id: moduleId, count: count || 5 }), }) } export async function getContent(moduleId: string): Promise { return apiFetch(`/content/${moduleId}`) } export async function publishContent(contentId: string): Promise<{ status: string }> { return apiFetch(`/content/${contentId}/publish`, { method: 'POST' }) } // ============================================================================= // DEADLINES & ESCALATION // ============================================================================= export async function getDeadlines(limit?: number): Promise { const qs = limit ? `?limit=${limit}` : '' return apiFetch(`/deadlines${qs}`) } export async function getOverdueDeadlines(): Promise { return apiFetch('/deadlines/overdue') } export async function checkEscalation(): Promise { return apiFetch('/escalation/check', { method: 'POST' }) } // ============================================================================= // AUDIT & STATS // ============================================================================= export async function getAuditLog(filters?: { action?: string entity_type?: string limit?: number offset?: number }): Promise { const params = new URLSearchParams() if (filters?.action) params.set('action', filters.action) if (filters?.entity_type) params.set('entity_type', filters.entity_type) if (filters?.limit) params.set('limit', String(filters.limit)) if (filters?.offset) params.set('offset', String(filters.offset)) const qs = params.toString() return apiFetch(`/audit-log${qs ? `?${qs}` : ''}`) } export async function getStats(): Promise { return apiFetch('/stats') } // ============================================================================= // BULK GENERATION // ============================================================================= export async function generateAllContent(language?: string): Promise<{ generated: number; skipped: number; errors: string[] }> { const qs = language ? `?language=${language}` : '' return apiFetch(`/content/generate-all${qs}`, { method: 'POST' }) } export async function generateAllQuizzes(): Promise<{ generated: number; skipped: number; errors: string[] }> { return apiFetch('/content/generate-all-quiz', { method: 'POST' }) } // ============================================================================= // MEDIA (Audio/Video) // ============================================================================= export async function generateAudio(moduleId: string): Promise { return apiFetch(`/content/${moduleId}/generate-audio`, { method: 'POST' }) } export async function getModuleMedia(moduleId: string): Promise<{ media: TrainingMedia[]; total: number }> { return apiFetch(`/media/module/${moduleId}`) } export async function getMediaURL(mediaId: string): Promise<{ bucket: string; object_key: string; mime_type: string }> { return apiFetch(`/media/${mediaId}/url`) } export async function publishMedia(mediaId: string, publish?: boolean): Promise<{ status: string; is_published: boolean }> { return apiFetch(`/media/${mediaId}/publish`, { method: 'POST', body: JSON.stringify({ publish: publish !== false }), }) } export async function generateVideo(moduleId: string): Promise { return apiFetch(`/content/${moduleId}/generate-video`, { method: 'POST' }) } export async function previewVideoScript(moduleId: string): Promise<{ title: string; sections: Array<{ heading: string; text: string; bullet_points: string[] }> }> { return apiFetch(`/content/${moduleId}/preview-script`, { method: 'POST' }) } // ============================================================================= // TRAINING BLOCKS (Controls → Schulungsmodule) // ============================================================================= import type { TrainingBlockConfig, CanonicalControlSummary, CanonicalControlMeta, BlockPreview, BlockGenerateResult, TrainingBlockControlLink, } from './types' export async function listBlockConfigs(): Promise<{ blocks: TrainingBlockConfig[]; total: number }> { return apiFetch('/blocks') } export async function createBlockConfig(data: { name: string description?: string domain_filter?: string category_filter?: string severity_filter?: string target_audience_filter?: string regulation_area: string module_code_prefix: string frequency_type?: string duration_minutes?: number pass_threshold?: number max_controls_per_module?: number }): Promise { return apiFetch('/blocks', { method: 'POST', body: JSON.stringify(data), }) } export async function getBlockConfig(id: string): Promise { return apiFetch(`/blocks/${id}`) } export async function updateBlockConfig(id: string, data: Record): Promise { return apiFetch(`/blocks/${id}`, { method: 'PUT', body: JSON.stringify(data), }) } export async function deleteBlockConfig(id: string): Promise { return apiFetch(`/blocks/${id}`, { method: 'DELETE' }) } export async function previewBlock(id: string): Promise { return apiFetch(`/blocks/${id}/preview`, { method: 'POST' }) } export async function generateBlock(id: string, data?: { language?: string auto_matrix?: boolean }): Promise { return apiFetch(`/blocks/${id}/generate`, { method: 'POST', body: JSON.stringify(data || { language: 'de', auto_matrix: true }), }) } export async function getBlockControls(id: string): Promise<{ controls: TrainingBlockControlLink[]; total: number }> { return apiFetch(`/blocks/${id}/controls`) } export async function listCanonicalControls(filters?: { domain?: string category?: string severity?: string target_audience?: string }): Promise<{ controls: CanonicalControlSummary[]; total: number }> { const params = new URLSearchParams() if (filters?.domain) params.set('domain', filters.domain) if (filters?.category) params.set('category', filters.category) if (filters?.severity) params.set('severity', filters.severity) if (filters?.target_audience) params.set('target_audience', filters.target_audience) const qs = params.toString() return apiFetch(`/canonical/controls${qs ? `?${qs}` : ''}`) } export async function getCanonicalMeta(): Promise { return apiFetch('/canonical/meta') } // ============================================================================= // CERTIFICATES // ============================================================================= export async function generateCertificate(assignmentId: string): Promise<{ certificate_id: string; assignment: TrainingAssignment }> { return apiFetch(`/certificates/generate/${assignmentId}`, { method: 'POST' }) } export async function listCertificates(): Promise<{ certificates: TrainingAssignment[]; total: number }> { return apiFetch('/certificates') } export async function downloadCertificatePDF(certificateId: string): Promise { const res = await fetch(`${BASE_URL}/certificates/${certificateId}/pdf`, { headers: { 'X-Tenant-ID': typeof window !== 'undefined' ? (localStorage.getItem('bp-tenant-id') || 'default') : 'default', }, }) if (!res.ok) throw new Error(`PDF download failed: ${res.status}`) return res.blob() } export async function verifyCertificate(certificateId: string): Promise<{ valid: boolean; assignment: TrainingAssignment }> { return apiFetch(`/certificates/${certificateId}/verify`) } // ============================================================================= // MEDIA STREAMING // ============================================================================= export function getMediaStreamURL(mediaId: string): string { return `${BASE_URL}/media/${mediaId}/stream` } // ============================================================================= // INTERACTIVE VIDEO // ============================================================================= import type { InteractiveVideoManifest, CheckpointQuizResult, CheckpointProgress, } from './types' export async function generateInteractiveVideo(moduleId: string): Promise { return apiFetch(`/content/${moduleId}/generate-interactive`, { method: 'POST' }) } export async function getInteractiveManifest(moduleId: string, assignmentId?: string): Promise { const qs = assignmentId ? `?assignment_id=${assignmentId}` : '' return apiFetch(`/content/${moduleId}/interactive-manifest${qs}`) } export async function submitCheckpointQuiz(checkpointId: string, assignmentId: string, answers: number[]): Promise { return apiFetch(`/checkpoints/${checkpointId}/submit`, { method: 'POST', body: JSON.stringify({ assignment_id: assignmentId, answers }), }) } export async function getCheckpointProgress(assignmentId: string): Promise<{ progress: CheckpointProgress[]; total: number }> { return apiFetch(`/checkpoints/progress/${assignmentId}`) }