From af4f713395470bc744a3d05b450378e6aab31e32 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Mon, 27 Apr 2026 05:58:39 -0700 Subject: [PATCH] integartion of refresh URL --- src/api/files.api.ts | 15 +++++++++++ .../content-management/CourseDetailPage.tsx | 26 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/api/files.api.ts b/src/api/files.api.ts index 377b509..ec6a59d 100644 --- a/src/api/files.api.ts +++ b/src/api/files.api.ts @@ -25,6 +25,16 @@ export interface ResolveFileUrlResponse { success?: boolean } +export interface RefreshFileUrlResponse { + message: string + data?: { + object_key?: string + url?: string + expires_in?: number + } + success?: boolean +} + export interface UploadMediaOptions { title?: string description?: string @@ -86,3 +96,8 @@ export const resolveFileUrl = (key: string) => params: { key }, }) +export const refreshFileUrl = (reference: string) => + http.post("/files/refresh-url", { + reference, + }) + diff --git a/src/pages/content-management/CourseDetailPage.tsx b/src/pages/content-management/CourseDetailPage.tsx index ab6da09..143fcd6 100644 --- a/src/pages/content-management/CourseDetailPage.tsx +++ b/src/pages/content-management/CourseDetailPage.tsx @@ -31,7 +31,7 @@ import { getTopLevelCourseModules, updateTopLevelCourseModule, } from "../../api/courses.api"; -import { resolveFileUrl } from "../../api/files.api"; +import { refreshFileUrl, resolveFileUrl } from "../../api/files.api"; import type { ProgramCourseListItem, TopLevelCourseModuleItem, @@ -52,6 +52,17 @@ function isLikelyImageUrl(src: string): boolean { ); } +function isSignedMinioUrl(src: string): boolean { + const value = src.trim(); + if (!value.startsWith("http://") && !value.startsWith("https://")) return false; + try { + const url = new URL(value); + return url.searchParams.has("X-Amz-Signature"); + } catch { + return false; + } +} + /** Default purple gradient with optional cover image; gradient stays if URL missing or image errors. */ function ModuleCardTopMedia({ iconSrc }: { iconSrc: string }) { const [coverFailed, setCoverFailed] = useState(false); @@ -201,10 +212,17 @@ export function CourseDetailPage() { const refreshed = await Promise.all( list.map(async (module) => { const icon = module.icon?.trim() ?? ""; - // If backend already returns a full MinIO/S3 URL (including presigned), - // use it directly. Only resolve raw object keys via /files/url. - if (!icon || isLikelyImageUrl(icon)) return module; + if (!icon) return module; try { + if (isSignedMinioUrl(icon)) { + const refreshedRes = await refreshFileUrl(icon); + const refreshedUrl = refreshedRes.data?.data?.url?.trim(); + if (refreshedUrl) { + return { ...module, icon: refreshedUrl }; + } + return module; + } + if (isLikelyImageUrl(icon)) return module; const resolved = await resolveFileUrl(icon); const freshUrl = resolved.data?.data?.url?.trim(); if (!freshUrl) return module;