diff --git a/apps/api/src/courses/course.service.ts b/apps/api/src/courses/course.service.ts index a68b9972..c8edce99 100644 --- a/apps/api/src/courses/course.service.ts +++ b/apps/api/src/courses/course.service.ts @@ -447,7 +447,7 @@ export class CourseService { .select({ id: courses.id, title: courses.title, - thumbnailUrl: sql`COALESCE(${courses.thumbnailS3Key}, '')`, + thumbnailS3Key: sql`COALESCE(${courses.thumbnailS3Key}, '')`, category: categories.title, categoryId: categories.id, description: sql`${courses.description}`, @@ -489,7 +489,7 @@ export class CourseService { return await this.fileService.getFileUrl(url); }; - const thumbnailUrl = await getImageUrl(course.thumbnailUrl); + const thumbnailS3SingedUrl = await getImageUrl(course.thumbnailS3Key); const updatedCourseLessonList = await Promise.all( courseChapterList?.map(async (chapter) => { @@ -507,7 +507,7 @@ export class CourseService { return { ...course, - thumbnailUrl, + thumbnailS3SingedUrl, chapters: updatedCourseLessonList ?? [], }; } @@ -517,7 +517,7 @@ export class CourseService { .select({ id: courses.id, title: courses.title, - thumbnailUrl: sql`${courses.thumbnailS3Key}`, + thumbnailS3Key: sql`${courses.thumbnailS3Key}`, category: categories.title, categoryId: categories.id, description: sql`${courses.description}`, @@ -556,11 +556,11 @@ export class CourseService { return await this.fileService.getFileUrl(url); }; - const thumbnailUrl = await getImageUrl(course.thumbnailUrl); + const thumbnailS3SingedUrl = await getImageUrl(course.thumbnailS3Key); return { ...course, - thumbnailUrl, + thumbnailS3SingedUrl, chapters: courseChapterList, }; } diff --git a/apps/api/src/courses/schemas/showCourseCommon.schema.ts b/apps/api/src/courses/schemas/showCourseCommon.schema.ts index 3c0ca82c..c27c8fba 100644 --- a/apps/api/src/courses/schemas/showCourseCommon.schema.ts +++ b/apps/api/src/courses/schemas/showCourseCommon.schema.ts @@ -20,6 +20,8 @@ export const commonShowCourseSchema = Type.Object({ isScorm: Type.Optional(Type.Boolean()), priceInCents: Type.Number(), thumbnailUrl: Type.Optional(Type.String()), + thumbnailS3Key: Type.Optional(Type.String()), + thumbnailS3SingedUrl: Type.Optional(Type.String()), title: Type.String(), }); diff --git a/apps/api/src/seed/seed-helpers.ts b/apps/api/src/seed/seed-helpers.ts index 71fb1f25..94a7044a 100644 --- a/apps/api/src/seed/seed-helpers.ts +++ b/apps/api/src/seed/seed-helpers.ts @@ -95,8 +95,8 @@ export async function createNiceCourses( lessonData.type === LESSON_TYPES.presentation ? "pptx" : lessonData.type === LESSON_TYPES.video - ? "mp4" - : null, + ? "mp4" + : null, chapterId: chapter.id, createdAt: createdAt, updatedAt: createdAt, diff --git a/apps/api/src/swagger/api-schema.json b/apps/api/src/swagger/api-schema.json index fe492725..b24c36be 100644 --- a/apps/api/src/swagger/api-schema.json +++ b/apps/api/src/swagger/api-schema.json @@ -3711,6 +3711,12 @@ "thumbnailUrl": { "type": "string" }, + "thumbnailS3Key": { + "type": "string" + }, + "thumbnailS3SingedUrl": { + "type": "string" + }, "title": { "type": "string" } @@ -3857,6 +3863,12 @@ "thumbnailUrl": { "type": "string" }, + "thumbnailS3Key": { + "type": "string" + }, + "thumbnailS3SingedUrl": { + "type": "string" + }, "title": { "type": "string" } @@ -4003,6 +4015,12 @@ "thumbnailUrl": { "type": "string" }, + "thumbnailS3Key": { + "type": "string" + }, + "thumbnailS3SingedUrl": { + "type": "string" + }, "title": { "type": "string" } diff --git a/apps/web/app/api/generated-api.ts b/apps/web/app/api/generated-api.ts index b4f9d4a9..409f1d53 100644 --- a/apps/web/app/api/generated-api.ts +++ b/apps/web/app/api/generated-api.ts @@ -467,6 +467,8 @@ export interface GetCourseResponse { isScorm?: boolean; priceInCents: number; thumbnailUrl?: string; + thumbnailS3Key?: string; + thumbnailS3SingedUrl?: string; title: string; }; } @@ -506,6 +508,8 @@ export interface GetCourseByIdResponse { isScorm?: boolean; priceInCents: number; thumbnailUrl?: string; + thumbnailS3Key?: string; + thumbnailS3SingedUrl?: string; title: string; }; } @@ -545,6 +549,8 @@ export interface GetBetaCourseByIdResponse { isScorm?: boolean; priceInCents: number; thumbnailUrl?: string; + thumbnailS3Key?: string; + thumbnailS3SingedUrl?: string; title: string; }; } @@ -1687,8 +1693,6 @@ export class API extends HttpClient { const { form, onSubmit } = useCourseSettingsForm({ title, description, categoryId, - imageUrl, + thumbnailS3Key, courseId: courseId || "", }); const { data: categories } = useCategoriesSuspense(); const [isUploading, setIsUploading] = useState(false); const { mutateAsync: uploadFile } = useUploadFile(); const isFormValid = form.formState.isValid; + const [displayThumbnailUrl, setDisplayThumbnailUrl] = useState( + thumbnailS3SingedUrl || undefined, + ); const watchedTitle = form.watch("title"); const watchedDescription = form.watch("description"); - const watchedImageUrl = form.watch("imageUrl"); const watchedCategoryId = form.getValues("categoryId"); const categoryName = useMemo(() => { @@ -61,7 +65,8 @@ const CourseSettings = ({ setIsUploading(true); try { const result = await uploadFile({ file, resource: "course" }); - form.setValue("imageUrl", result.fileUrl, { shouldValidate: true }); + form.setValue("thumbnailS3Key", result.fileKey, { shouldValidate: true }); + setDisplayThumbnailUrl(result.fileUrl); } catch (error) { console.error("Error uploading image:", error); } finally { @@ -71,6 +76,11 @@ const CourseSettings = ({ [form, uploadFile], ); + const removeThumbnail = () => { + form.setValue("thumbnailS3Key", ""); + setDisplayThumbnailUrl(undefined); + }; + return (
@@ -121,16 +131,16 @@ const CourseSettings = ({ /> ( - + {isUploading &&

Uploading image...

} @@ -139,11 +149,8 @@ const CourseSettings = ({ )} />
- {watchedImageUrl && ( - @@ -160,7 +167,7 @@ const CourseSettings = ({
{ const { mutateAsync: updateCourse } = useUpdateCourse(); - const { mutateAsync: deleteOldFile } = useDeleteFile(); const form = useForm({ resolver: zodResolver(courseSettingsFormSchema), defaultValues: { title: title || "", description: description || "", categoryId: categoryId || "", - imageUrl: imageUrl || "", + thumbnailS3Key: thumbnailS3Key || "", }, }); - const newImageUrl = form.getValues("imageUrl"); - const oldImageUrl = imageUrl ?? ""; - - const oldImageExist = Boolean(oldImageUrl); - const newImageExist = Boolean(newImageUrl); - const imageUrlChanged = oldImageExist && newImageExist && newImageUrl !== oldImageUrl; - const onSubmit = async (data: UpdateCourseBody) => { updateCourse({ data: { ...data }, courseId, }).then(() => { queryClient.invalidateQueries(courseQueryOptions(courseId)); - if (imageUrlChanged) { - deleteOldFile(oldImageUrl); - } }); }; diff --git a/apps/web/app/modules/Admin/EditCourse/CourseSettings/validators/courseSettingsFormSchema.ts b/apps/web/app/modules/Admin/EditCourse/CourseSettings/validators/courseSettingsFormSchema.ts index dc305dfa..e373c37a 100644 --- a/apps/web/app/modules/Admin/EditCourse/CourseSettings/validators/courseSettingsFormSchema.ts +++ b/apps/web/app/modules/Admin/EditCourse/CourseSettings/validators/courseSettingsFormSchema.ts @@ -4,7 +4,7 @@ export const courseSettingsFormSchema = z.object({ title: z.string().min(2, "Title must be at least 2 characters."), description: z.string().min(2, "Description must be at least 2 characters."), categoryId: z.string().min(1, "Category is required"), - imageUrl: z.union([z.string().url("Invalid image URL"), z.string().length(0)]).optional(), + thumbnailS3Key: z.string().optional(), }); export type CourseSettingsFormValues = z.infer; diff --git a/apps/web/app/modules/Admin/EditCourse/EditCourse.tsx b/apps/web/app/modules/Admin/EditCourse/EditCourse.tsx index 3a92ebfa..3e6c54db 100644 --- a/apps/web/app/modules/Admin/EditCourse/EditCourse.tsx +++ b/apps/web/app/modules/Admin/EditCourse/EditCourse.tsx @@ -77,7 +77,8 @@ const EditCourse = () => { title={course?.title} description={course?.description} categoryId={course?.categoryId} - imageUrl={course?.thumbnailUrl} + thumbnailS3SingedUrl={course?.thumbnailS3SingedUrl} + thumbnailS3Key={course?.thumbnailS3Key} />