feat: add Bunny Storage course cover uploads
All checks were successful
CI / Validate frontend and API (push) Successful in 44s
CI / Build and publish Docker images (push) Successful in 22s

This commit is contained in:
Cauê Faleiros
2026-09-04 13:39:30 -03:00
parent 3498d47182
commit e15a44e733
11 changed files with 195 additions and 14 deletions

View File

@@ -84,6 +84,16 @@ async function uploadRequest<T>(path: string, file: File): Promise<T> {
return body as T;
}
async function uploadImageRequest<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: 'POST', headers, body: file });
const body = await response.json().catch(() => ({}));
if (!response.ok) throw new ApiError(body.error || 'The cover image could not be uploaded', 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 }) });
@@ -140,6 +150,8 @@ export const instructorApi = {
async bunnyVideoStatus(videoId: string) {
return apiRequest<{ data: BunnyVideo }>(`/manage/media/bunny/videos/${videoId}`);
},
async coverConfiguration() { return apiRequest<{ data: { configured: boolean; maxUploadBytes: number } }>('/manage/media/covers/config'); },
async uploadCover(file: File) { return uploadImageRequest<{ data: { key: string; coverImageUrl: string } }>('/manage/media/covers', file); },
};
export const courseApi = {

View File

@@ -123,7 +123,7 @@ const toCoursePayload = (course: Course) => ({
title: course.title,
description: course.description,
category: course.category,
coverImageUrl: course.thumbnail && !course.thumbnail.startsWith('blob:') ? course.thumbnail : null,
coverImageUrl: course.thumbnail && /^https?:\/\//.test(course.thumbnail) ? course.thumbnail : null,
status: course.status || 'published',
assets: (course.attachments || []).filter((attachment) => attachment.url && attachment.url !== '#').map(toAssetPayload),
lessons: course.lessons.map((lesson) => ({