diff --git a/apps/web/app/api/media/[mediaId]/route.ts b/apps/web/app/api/media/[mediaId]/route.ts new file mode 100644 index 000000000..9f7a2f970 --- /dev/null +++ b/apps/web/app/api/media/[mediaId]/route.ts @@ -0,0 +1,60 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getMedia } from "@/services/medialit"; +import { Constants } from "@courselit/common-models"; + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ mediaId: string }> }, +) { + const mediaId = (await params).mediaId; + + if (!mediaId) { + return new NextResponse("Missing mediaId parameter", { status: 400 }); + } + + try { + const media = await getMedia(mediaId); + + if (!media) { + return new NextResponse("Media not found", { status: 404 }); + } + + if (media.access !== Constants.MediaAccessType.PUBLIC) { + return new NextResponse("Media not found", { status: 404 }); + } + + if (!media.file) { + return new NextResponse("Media file URL not available", { + status: 404, + }); + } + + const response = await fetch(media.file); + + if (!response.ok) { + throw new Error(`Failed to fetch file: ${response.statusText}`); + } + + const headers = new Headers(); + headers.set( + "Content-Type", + response.headers.get("Content-Type") || + media.mimeType || + "application/octet-stream", + ); + + const fileName = media.originalFileName || "file"; + headers.set( + "Content-Disposition", + `attachment; filename="${fileName}"`, + ); + + return new NextResponse(response.body, { + status: 200, + headers, + }); + } catch (error) { + console.error("Error downloading file:", error); + return new NextResponse("Error downloading file", { status: 500 }); + } +} diff --git a/apps/web/components/community/index.tsx b/apps/web/components/community/index.tsx index 1fd20d633..bea629cc5 100644 --- a/apps/web/components/community/index.tsx +++ b/apps/web/components/community/index.tsx @@ -25,6 +25,7 @@ import { FlagTriangleRight, Maximize2, ArrowLeft, + Download, } from "lucide-react"; import { Dialog, @@ -103,7 +104,8 @@ export function CommunityForum({ () => categories.filter((x) => x !== "All"), [categories], ); - const [fullscreenImage, setFullscreenImage] = useState(null); + const [fullscreenMedia, setFullscreenMedia] = + useState(null); const [page, setPage] = useState(1); const [totalPosts, setTotalPosts] = useState(0); const [postToDelete, setPostToDelete] = useState( @@ -650,7 +652,7 @@ export function CommunityForum({ type="button" onClick={(e) => { e.stopPropagation(); - setFullscreenImage(media.media!.file!); + setFullscreenMedia(media); }} className="absolute top-2 right-2 rounded-md bg-black/60 text-white p-1.5 opacity-0 group-hover:opacity-100 transition-opacity hover:bg-black/80" aria-label="View full screen" @@ -733,10 +735,40 @@ export function CommunityForum({ if (options && options.renderActualFile) { // embed pdf return ( - +
e.preventDefault()} + > +
+ +
+ {media.media?.mediaId && ( + e.stopPropagation()} + className="rounded-md bg-black/60 text-white p-1.5 hover:bg-black/80" + aria-label="Download" + > + + + )} + +
+
); } return ( @@ -1376,7 +1408,7 @@ export function CommunityForum({ onOpenChange={(open) => { if (!open) { setOpenPostId(null); - setFullscreenImage(null); + setFullscreenMedia(null); } }} > @@ -1387,13 +1419,13 @@ export function CommunityForum({ Post' content - {openPost && fullscreenImage ? ( -
+ {openPost && fullscreenMedia ? ( +
- Full size preview + {fullscreenMedia.type === "pdf" ? ( +
+