const API_URL = import.meta.env.VITE_API_URL || '/api/v1'; const SESSION_KEY = 'compor_hub_session'; export type ApiRole = 'student' | 'instructor' | 'admin'; export interface ApiUser { id: string; email: string; name: string; role: ApiRole; } export interface ManagedUser { id: string; email: string; name: string; role: ApiRole; isActive: boolean; createdAt: string; } export interface ManagedUserDetail extends ManagedUser { completedLessons: number; lastLearningAt: string | null; } export interface PlatformAnalytics { totalUsers: number; activeUsers: number; publishedCourses: number; completedLessons: number; comments: number; } export interface InstructorAnalytics { courses: number; lessons: number; learners: number; completedLessons: number; comments: number; } export interface AuditEntry { id: string; action: string; subjectType: string; subjectId: string | null; metadata: Record; createdAt: string; actorName: string; } export interface BunnyVideo { id: string; title: string; status: 'processing' | 'ready' | 'failed'; providerStatus: number; encodeProgress: number; durationSeconds: number | null; } export interface LessonPlayback { provider: string; kind: 'embed' | 'video'; embedUrl?: string; source?: string; expiresAt?: string; } interface Session { token: string; user: ApiUser; } export class ApiError extends Error { constructor(message: string, public status: number) { super(message); } } export function getSession(): Session | null { const value = localStorage.getItem(SESSION_KEY); if (!value) return null; try { return JSON.parse(value) as Session; } catch { localStorage.removeItem(SESSION_KEY); return null; } } export function saveSession(session: Session) { localStorage.setItem(SESSION_KEY, JSON.stringify(session)); } export function clearSession() { localStorage.removeItem(SESSION_KEY); } export async function apiRequest(path: string, options: RequestInit = {}): Promise { const session = getSession(); const headers = new Headers(options.headers); if (options.body) headers.set('Content-Type', 'application/json'); if (session) headers.set('Authorization', `Bearer ${session.token}`); const response = await fetch(`${API_URL}${path}`, { ...options, headers }); if (response.status === 204) return undefined as T; const body = await response.json().catch(() => ({})); if (!response.ok) { throw new ApiError(body.error || 'The request could not be completed', response.status); } return body as T; } async function uploadRequest(path: string, file: File): Promise { const session = getSession(); const headers = new Headers({ 'Content-Type': file.type || 'application/octet-stream' }); if (session) headers.set('Authorization', `Bearer ${session.token}`); const response = await fetch(`${API_URL}${path}`, { method: 'PUT', headers, body: file }); const body = await response.json().catch(() => ({})); if (!response.ok) throw new ApiError(body.error || 'The video upload could not be completed', response.status); return body as T; } export const authApi = { async login(email: string, password: string) { return apiRequest('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }); }, async register(name: string, email: string, password: string) { return apiRequest('/auth/register', { method: 'POST', body: JSON.stringify({ name, email, password }) }); }, async me() { return apiRequest<{ user: ApiUser }>('/auth/me'); }, async acceptInvitation(token: string, name: string, password: string) { return apiRequest('/auth/accept-invitation', { method: 'POST', body: JSON.stringify({ token, name, password }) }); }, async resetPassword(token: string, password: string) { return apiRequest('/auth/reset-password', { method: 'POST', body: JSON.stringify({ token, password }) }); }, }; export const adminApi = { async dashboard() { return apiRequest<{ data: PlatformAnalytics }>('/admin/dashboard'); }, async listUsers() { return apiRequest<{ data: ManagedUser[] }>('/admin/users'); }, async updateUser(userId: string, update: Partial>) { return apiRequest<{ data: ManagedUser }>(`/admin/users/${userId}`, { method: 'PATCH', body: JSON.stringify(update), }); }, async invite(email: string, role: 'student' | 'instructor') { return apiRequest<{ data: { inviteUrl: string } }>('/admin/invitations', { method: 'POST', body: JSON.stringify({ email, role }) }); }, async passwordReset(userId: string) { return apiRequest<{ data: { resetUrl: string } }>(`/admin/users/${userId}/password-reset`, { method: 'POST' }); }, async userDetail(userId: string) { return apiRequest<{ data: ManagedUserDetail }>(`/admin/users/${userId}`); }, async auditLog() { return apiRequest<{ data: AuditEntry[] }>('/admin/audit-log'); }, async categories() { return apiRequest<{ data: Array<{ id: string; name: string; position: number; isActive: boolean }> }>('/admin/categories'); }, async createCategory(name: string) { return apiRequest<{ data: { id: string; name: string; position: number; isActive: boolean } }>('/admin/categories', { method: 'POST', body: JSON.stringify({ name }) }); }, async updateCategory(categoryId: string, update: { name: string; isActive?: boolean; position?: number }) { return apiRequest<{ data: { id: string; name: string; position: number; isActive: boolean } }>(`/admin/categories/${categoryId}`, { method: 'PATCH', body: JSON.stringify(update) }); }, async homeConfiguration() { return apiRequest<{ data: { featuredCourseId: string | null; courseOrder: string[]; defaultCoverImageUrl: string | null; courses: Array<{ id: string; title: string; status: string }> } }>('/admin/home-configuration'); }, async updateHomeConfiguration(input: { featuredCourseId: string | null; courseOrder: string[]; defaultCoverImageUrl: string | null }) { return apiRequest<{ data: typeof input }>('/admin/home-configuration', { method: 'PUT', body: JSON.stringify(input) }); }, }; export const instructorApi = { async analytics() { return apiRequest<{ data: InstructorAnalytics }>('/manage/courses/analytics'); }, async bunnyConfiguration() { return apiRequest<{ data: { configured: boolean; maxUploadBytes: number } }>('/manage/media/bunny/config'); }, async createBunnyVideo(title: string) { return apiRequest<{ data: BunnyVideo }>('/manage/media/bunny/videos', { method: 'POST', body: JSON.stringify({ title }) }); }, async uploadBunnyVideo(videoId: string, file: File) { return uploadRequest<{ data: BunnyVideo }>(`/manage/media/bunny/videos/${videoId}/upload`, file); }, async bunnyVideoStatus(videoId: string) { return apiRequest<{ data: BunnyVideo }>(`/manage/media/bunny/videos/${videoId}`); }, }; export const courseApi = { async categories() { return apiRequest<{ data: string[] }>('/courses/categories'); }, async playback(courseId: string, lessonId: string) { return apiRequest<{ data: LessonPlayback }>(`/courses/${courseId}/lessons/${lessonId}/playback`); }, };