All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 45s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 20s
Use consistent :moduleId param name for content routes and :mediaId for media routes. Add param adapters for handlers that expect different names. Fix frontend media API paths to match backend route structure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
312 lines
10 KiB
TypeScript
312 lines
10 KiB
TypeScript
/**
|
|
* 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<T>(path: string, options?: RequestInit): Promise<T> {
|
|
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<ModuleListResponse> {
|
|
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<ModuleListResponse>(`/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<TrainingModule> {
|
|
return apiFetch<TrainingModule>('/modules', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
})
|
|
}
|
|
|
|
export async function updateModule(id: string, data: Record<string, unknown>): Promise<TrainingModule> {
|
|
return apiFetch<TrainingModule>(`/modules/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data),
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// MATRIX
|
|
// =============================================================================
|
|
|
|
export async function getMatrix(): Promise<MatrixResponse> {
|
|
return apiFetch<MatrixResponse>('/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<unknown> {
|
|
return apiFetch('/matrix', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
})
|
|
}
|
|
|
|
export async function deleteMatrixEntry(role: string, moduleId: string): Promise<unknown> {
|
|
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<AssignmentListResponse> {
|
|
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<AssignmentListResponse>(`/assignments${qs ? `?${qs}` : ''}`)
|
|
}
|
|
|
|
export async function getAssignment(id: string): Promise<TrainingAssignment> {
|
|
return apiFetch<TrainingAssignment>(`/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' })
|
|
}
|
|
|
|
// =============================================================================
|
|
// 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<QuizSubmitResponse> {
|
|
return apiFetch<QuizSubmitResponse>(`/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<ModuleContent> {
|
|
return apiFetch<ModuleContent>('/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<ModuleContent> {
|
|
return apiFetch<ModuleContent>(`/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<DeadlineListResponse> {
|
|
const qs = limit ? `?limit=${limit}` : ''
|
|
return apiFetch<DeadlineListResponse>(`/deadlines${qs}`)
|
|
}
|
|
|
|
export async function getOverdueDeadlines(): Promise<DeadlineListResponse> {
|
|
return apiFetch<DeadlineListResponse>('/deadlines/overdue')
|
|
}
|
|
|
|
export async function checkEscalation(): Promise<EscalationResponse> {
|
|
return apiFetch<EscalationResponse>('/escalation/check', { method: 'POST' })
|
|
}
|
|
|
|
// =============================================================================
|
|
// AUDIT & STATS
|
|
// =============================================================================
|
|
|
|
export async function getAuditLog(filters?: {
|
|
action?: string
|
|
entity_type?: string
|
|
limit?: number
|
|
offset?: number
|
|
}): Promise<AuditLogResponse> {
|
|
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<AuditLogResponse>(`/audit-log${qs ? `?${qs}` : ''}`)
|
|
}
|
|
|
|
export async function getStats(): Promise<TrainingStats> {
|
|
return apiFetch<TrainingStats>('/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<TrainingMedia> {
|
|
return apiFetch<TrainingMedia>(`/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<TrainingMedia> {
|
|
return apiFetch<TrainingMedia>(`/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' })
|
|
}
|