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

@@ -1,4 +1,4 @@
import { Attachment, AttachmentType, Comment, Course, Lesson } from '../types';
import { Attachment, AttachmentType, Comment, Course, Lesson, LessonMedia } from '../types';
import { apiRequest } from './api';
export const initDB = async (): Promise<void> => undefined;
@@ -27,7 +27,15 @@ type ApiCourse = {
description: string;
durationSeconds: number | null;
accessLevel: 'public' | 'enrolled';
media: Array<{ provider: string; playbackUrl: string | null; embedUrl: string | null }>;
media: Array<{
id: string;
provider: string;
externalId: string;
playbackUrl: string | null;
embedUrl: string | null;
status: 'processing' | 'ready' | 'failed';
durationSeconds: number | null;
}>;
assets: ApiAsset[];
}>;
assets: ApiAsset[];
@@ -64,16 +72,28 @@ const toCourse = (course: ApiCourse): Course => ({
progress: course.progress,
lastActivity: course.lastActivity || undefined,
duration: `${course.lessons.length} Aulas`,
lessons: course.lessons.map((lesson): Lesson => ({
id: lesson.id,
title: lesson.title,
description: lesson.description,
duration: toDuration(lesson.durationSeconds),
isFree: lesson.accessLevel === 'public',
videoUrl: lesson.media[0]?.playbackUrl || lesson.media[0]?.embedUrl || '',
mediaProvider: lesson.media[0]?.provider,
attachments: lesson.assets.map(toAttachment),
})),
lessons: course.lessons.map((lesson): Lesson => {
const media: LessonMedia[] = lesson.media.map((item) => ({
id: item.id,
provider: item.provider,
externalId: item.externalId,
playbackUrl: item.playbackUrl,
embedUrl: item.embedUrl,
status: item.status,
durationSeconds: item.durationSeconds,
}));
return {
id: lesson.id,
title: lesson.title,
description: lesson.description,
duration: toDuration(lesson.durationSeconds),
isFree: lesson.accessLevel === 'public',
videoUrl: media[0]?.playbackUrl || media[0]?.embedUrl || '',
mediaProvider: media[0]?.provider,
media,
attachments: lesson.assets.map(toAttachment),
};
}),
attachments: course.assets.map(toAttachment),
});
@@ -112,9 +132,18 @@ const toCoursePayload = (course: Course) => ({
description: lesson.description || '',
durationSeconds: toSeconds(lesson.duration),
accessLevel: lesson.isFree ? 'public' as const : 'enrolled' as const,
media: lesson.videoUrl && !lesson.videoUrl.startsWith('blob:')
? [{ provider: lesson.mediaProvider || 'external', externalId: lesson.videoUrl, playbackUrl: lesson.videoUrl, status: 'ready' as const }]
: [],
media: lesson.media?.length
? lesson.media.map((media) => ({
provider: media.provider,
externalId: media.externalId,
playbackUrl: media.playbackUrl || null,
embedUrl: media.embedUrl || null,
status: media.status,
durationSeconds: media.durationSeconds || null,
}))
: lesson.videoUrl && !lesson.videoUrl.startsWith('blob:')
? [{ provider: lesson.mediaProvider || 'external', externalId: lesson.videoUrl, playbackUrl: lesson.videoUrl, status: 'ready' as const }]
: [],
assets: (lesson.attachments || []).filter((attachment) => attachment.url && attachment.url !== '#').map(toAssetPayload),
})),
});