integartion of refresh URL

This commit is contained in:
Yared Yemane 2026-04-27 05:58:39 -07:00
parent 472e71d1a2
commit af4f713395
2 changed files with 37 additions and 4 deletions

View File

@ -25,6 +25,16 @@ export interface ResolveFileUrlResponse {
success?: boolean success?: boolean
} }
export interface RefreshFileUrlResponse {
message: string
data?: {
object_key?: string
url?: string
expires_in?: number
}
success?: boolean
}
export interface UploadMediaOptions { export interface UploadMediaOptions {
title?: string title?: string
description?: string description?: string
@ -86,3 +96,8 @@ export const resolveFileUrl = (key: string) =>
params: { key }, params: { key },
}) })
export const refreshFileUrl = (reference: string) =>
http.post<RefreshFileUrlResponse>("/files/refresh-url", {
reference,
})

View File

@ -31,7 +31,7 @@ import {
getTopLevelCourseModules, getTopLevelCourseModules,
updateTopLevelCourseModule, updateTopLevelCourseModule,
} from "../../api/courses.api"; } from "../../api/courses.api";
import { resolveFileUrl } from "../../api/files.api"; import { refreshFileUrl, resolveFileUrl } from "../../api/files.api";
import type { import type {
ProgramCourseListItem, ProgramCourseListItem,
TopLevelCourseModuleItem, 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. */ /** Default purple gradient with optional cover image; gradient stays if URL missing or image errors. */
function ModuleCardTopMedia({ iconSrc }: { iconSrc: string }) { function ModuleCardTopMedia({ iconSrc }: { iconSrc: string }) {
const [coverFailed, setCoverFailed] = useState(false); const [coverFailed, setCoverFailed] = useState(false);
@ -201,10 +212,17 @@ export function CourseDetailPage() {
const refreshed = await Promise.all( const refreshed = await Promise.all(
list.map(async (module) => { list.map(async (module) => {
const icon = module.icon?.trim() ?? ""; const icon = module.icon?.trim() ?? "";
// If backend already returns a full MinIO/S3 URL (including presigned), if (!icon) return module;
// use it directly. Only resolve raw object keys via /files/url.
if (!icon || isLikelyImageUrl(icon)) return module;
try { 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 resolved = await resolveFileUrl(icon);
const freshUrl = resolved.data?.data?.url?.trim(); const freshUrl = resolved.data?.data?.url?.trim();
if (!freshUrl) return module; if (!freshUrl) return module;