feat: integrate Bunny Stream course videos
All checks were successful
CI / Validate frontend and API (push) Successful in 1m50s
CI / Build and publish Docker images (push) Successful in 18s

This commit is contained in:
Cauê Faleiros
2026-09-02 10:09:36 -03:00
parent 3d4c72e85c
commit 2c137529bc
15 changed files with 607 additions and 40 deletions

View File

@@ -25,6 +25,8 @@ export interface ManagedUserDetail extends ManagedUser {
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<string, unknown>; 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;
@@ -72,6 +74,16 @@ export async function apiRequest<T>(path: string, options: RequestInit = {}): Pr
return body as T;
}
async function uploadRequest<T>(path: string, file: File): Promise<T> {
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<Session>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) });
@@ -113,4 +125,20 @@ export const adminApi = {
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 playback(courseId: string, lessonId: string) {
return apiRequest<{ data: LessonPlayback }>(`/courses/${courseId}/lessons/${lessonId}/playback`);
},
};