import { Elysia, status } from "elysia"; import { getAllVideos, getVideoById, deleteVideoById, insertVideo, updateThumbnailKey, updateTier, getVideoByMapAndPlayer, updateVideoPB, updateTranscodedKey, } from "../services/db"; import { uploadObject, deleteObject, getObjectUrl, getStreamUrl, createMultipartUpload, getPresignedUploadPartUrl, completeMultipartUpload, abortMultipartUpload, getVideoStream, } from "../services/rustfs"; import { parseMtvFile } from "../services/mtv-parser"; import { getMapInfo } from "../services/momentum-api"; import type { VideoEntry, PreviousPB } from "../types/video"; const ADMIN_TOKEN = process.env.ADMIN_TOKEN; if (!ADMIN_TOKEN) { console.error( "ADMIN_TOKEN environment variable is required. Set it to secure upload/delete endpoints.", ); process.exit(1); } function sanitizeFileName(name: string): string { return name .replace(/\s+/g, "_") .replace(/[^a-zA-Z0-9_.\-]/g, "") .toLowerCase(); } export const videoRoutes = new Elysia({ prefix: "/api/videos" }) .guard({ beforeHandle: ({ request }) => { const isWrite = request.method !== "GET" && request.method !== "HEAD" && request.method !== "OPTIONS"; if (isWrite) { const auth = request.headers.get("authorization"); const token = auth?.startsWith("Bearer ") ? auth.slice(7) : null; if (!token || token !== ADMIN_TOKEN) return status(401, "Unauthorized"); } }, }) .get("/", () => { const videos = getAllVideos(); return videos.map((v) => ({ ...v, thumbnailUrl: v.thumbnailUrl ? getObjectUrl(v.thumbnailUrl) : undefined, })); }) .post("/upload-url", async ({ request }) => { let formData: FormData; try { formData = await request.formData(); } catch { return status(400, { error: "Failed to parse form data" }); } const mtv = formData.get("mtv") as File | null; const videoFileName = formData.get("videoFileName") as string | null; const videoContentType = (formData.get("videoContentType") as string | null) || "video/webm"; const videoFileSizeStr = formData.get("videoFileSize") as string | null; const runDate = formData.get("runDate") as string | null; if (!mtv || !videoFileName || !videoFileSizeStr) { return status(400, { error: "mtv file, videoFileName, and videoFileSize are required", }); } const videoFileSize = parseInt(videoFileSizeStr, 10); if (isNaN(videoFileSize) || videoFileSize <= 0) { return status(400, { error: "Invalid videoFileSize" }); } const CHUNK_SIZE = 80 * 1024 * 1024; const numParts = Math.ceil(videoFileSize / CHUNK_SIZE); const mtvBuffer = await mtv.arrayBuffer(); const metadata = parseMtvFile(mtvBuffer); if (!metadata) { return status(400, { error: "Invalid .mtv file: could not parse header", }); } const existing = getVideoByMapAndPlayer( metadata.mapName, metadata.steamId, ); if (existing) { if (existing.runTime <= metadata.runTime) { return status(409, { error: `PB not improved. Your current PB on ${metadata.mapName} is ${existing.runTime}, this run is ${metadata.runTime}`, }); } const deletes = [ deleteObject(existing.videoKey), deleteObject(existing.mtvKey), ]; if (existing.thumbnailKey) deletes.push(deleteObject(existing.thumbnailKey)); await Promise.all(deletes); const previousPbs: PreviousPB[] = [ { runTime: existing.runTime, createdAt: existing.createdAt }, ...(existing.previousPbs ?? []), ]; const id = existing.id; const createdAt = runDate && !isNaN(Date.parse(runDate)) ? new Date(runDate).toISOString() : new Date().toISOString(); const sanitizedVideoName = sanitizeFileName(videoFileName); const videoKey = `videos/${id}/${sanitizedVideoName}`; const mtvKey = `videos/${id}/${sanitizeFileName(mtv.name)}`; await uploadObject( mtvKey, Buffer.from(mtvBuffer), "application/octet-stream", ); let tier: number | null | undefined; let mapId: number | null | undefined; let thumbnailKey: string | undefined; const mapInfo = await getMapInfo(metadata.mapName); mapId = mapInfo.mapId ?? undefined; tier = mapInfo.tier; if (mapInfo.thumbnailUrl) { thumbnailKey = `videos/${id}/thumbnail.jpg`; try { const imgRes = await fetch(mapInfo.thumbnailUrl); if (imgRes.ok) { const imgBuf = Buffer.from(await imgRes.arrayBuffer()); await uploadObject(thumbnailKey, imgBuf, "image/jpeg"); } else { thumbnailKey = undefined; } } catch { thumbnailKey = undefined; } } const title = `${metadata.mapName} - ${metadata.playerName}`; const entry: VideoEntry = { id, title, description: "", mapName: metadata.mapName, playerName: metadata.playerName, steamId: metadata.steamId, runTime: metadata.runTime, totalTicks: metadata.totalTicks, tickInterval: metadata.tickInterval, videoKey, mtvKey, thumbnailKey, tier, mapId, jsonStats: metadata.jsonStats, createdAt, previousPbs, }; updateVideoPB(id, entry, previousPbs); const { uploadId } = await createMultipartUpload( videoKey, videoContentType, ); const parts = await Promise.all( Array.from({ length: numParts }, (_, i) => i + 1).map( async (partNumber) => ({ partNumber, url: await getPresignedUploadPartUrl( videoKey, uploadId, partNumber, ), }), ), ); return { ...entry, presignedUrls: { parts, uploadId, }, videoUrl: getStreamUrl(id), mtvUrl: getObjectUrl(mtvKey), thumbnailUrl: thumbnailKey ? getObjectUrl(thumbnailKey) : undefined, }; } const id = crypto.randomUUID(); const createdAt = runDate && !isNaN(Date.parse(runDate)) ? new Date(runDate).toISOString() : new Date().toISOString(); const sanitizedVideoName = sanitizeFileName(videoFileName); const videoKey = `videos/${id}/${sanitizedVideoName}`; const mtvKey = `videos/${id}/${sanitizeFileName(mtv.name)}`; await uploadObject( mtvKey, Buffer.from(mtvBuffer), "application/octet-stream", ); let tier: number | null | undefined; let mapId: number | null | undefined; let thumbnailKey: string | undefined; const mapInfo = await getMapInfo(metadata.mapName); mapId = mapInfo.mapId ?? undefined; tier = mapInfo.tier; if (mapInfo.thumbnailUrl) { thumbnailKey = `videos/${id}/thumbnail.jpg`; try { const imgRes = await fetch(mapInfo.thumbnailUrl); if (imgRes.ok) { const imgBuf = Buffer.from(await imgRes.arrayBuffer()); await uploadObject(thumbnailKey, imgBuf, "image/jpeg"); } else { thumbnailKey = undefined; } } catch { thumbnailKey = undefined; } } const title = `${metadata.mapName} - ${metadata.playerName}`; const entry: VideoEntry = { id, title, description: "", mapName: metadata.mapName, playerName: metadata.playerName, steamId: metadata.steamId, runTime: metadata.runTime, totalTicks: metadata.totalTicks, tickInterval: metadata.tickInterval, videoKey, mtvKey, thumbnailKey, tier, mapId, jsonStats: metadata.jsonStats, createdAt, }; insertVideo(entry); const { uploadId } = await createMultipartUpload( videoKey, videoContentType, ); const parts = await Promise.all( Array.from({ length: numParts }, (_, i) => i + 1).map( async (partNumber) => ({ partNumber, url: await getPresignedUploadPartUrl( videoKey, uploadId, partNumber, ), }), ), ); return { ...entry, presignedUrls: { parts, uploadId, }, videoUrl: getStreamUrl(id), mtvUrl: getObjectUrl(mtvKey), thumbnailUrl: thumbnailKey ? getObjectUrl(thumbnailKey) : undefined, }; }) .post("/:id/complete", async ({ params: { id }, body }) => { const video = getVideoById(id); if (!video) return status(404, { error: "Not found" }); const { parts, uploadId } = body as { parts: { partNumber: number; eTag: string }[]; uploadId: string; }; if (!parts || !Array.isArray(parts) || !uploadId) { return status(400, { error: "parts array and uploadId are required", }); } const s3Parts = parts.map((p) => ({ PartNumber: p.partNumber, ETag: p.eTag, })); await completeMultipartUpload(video.videoKey, uploadId, s3Parts); return { ...video, videoUrl: getStreamUrl(video.id), mtvUrl: getObjectUrl(video.mtvKey), thumbnailUrl: video.thumbnailKey ? getObjectUrl(video.thumbnailKey) : undefined, }; }) .get("/:id/stream", async ({ params: { id }, request, set }) => { const video = getVideoById(id); if (!video) return status(404, { error: "Not found" }); const rangeHeader = request.headers.get("Range"); try { const result = await getVideoStream( video.transcodedKey ?? video.videoKey, rangeHeader, ); const headers = new Headers(); headers.set("Content-Type", result.contentType); headers.set("Accept-Ranges", "bytes"); headers.set("Cache-Control", "public, max-age=86400"); if (result.range) { headers.set( "Content-Range", `bytes ${result.range.start}-${result.range.end}/${result.contentLength}`, ); headers.set( "Content-Length", String(result.range.end - result.range.start + 1), ); return new Response(result.stream, { status: 206, headers, }); } else { headers.set("Content-Length", String(result.contentLength)); return new Response(result.stream, { status: 200, headers, }); } } catch (err: any) { console.error("Stream error:", err); return status(404, { error: "Video not found" }); } }) .get("/:id", ({ params: { id } }) => { const video = getVideoById(id); if (!video) return status(404, { error: "Not found" }); return { ...video, videoUrl: getStreamUrl(video.id), mtvUrl: getObjectUrl(video.mtvKey), thumbnailUrl: video.thumbnailKey ? getObjectUrl(video.thumbnailKey) : undefined, }; }) .post("/:id/refresh-info", async ({ params: { id } }) => { const video = getVideoById(id); if (!video) return status(404, { error: "Not found" }); const mapInfo = await getMapInfo(video.mapName); let thumbnailKey = video.thumbnailKey; if (!thumbnailKey && mapInfo.thumbnailUrl) { thumbnailKey = `videos/${video.id}/thumbnail.jpg`; try { const imgRes = await fetch(mapInfo.thumbnailUrl); if (imgRes.ok) { const imgBuf = Buffer.from(await imgRes.arrayBuffer()); await uploadObject(thumbnailKey, imgBuf, "image/jpeg"); updateThumbnailKey(id, thumbnailKey); } else { thumbnailKey = undefined; } } catch { thumbnailKey = undefined; } } updateTier(id, mapInfo.tier); return { success: true, tier: mapInfo.tier, thumbnailUrl: thumbnailKey ? getObjectUrl(thumbnailKey) : undefined, }; }) .post("/:id/refresh-thumbnail", async ({ params: { id } }) => { const video = getVideoById(id); if (!video) return status(404, { error: "Not found" }); const mapInfo = await getMapInfo(video.mapName); if (!mapInfo.thumbnailUrl) { return status(404, { error: `No thumbnail found on Momentum Mod for ${video.mapName}`, }); } const thumbnailKey = `videos/${video.id}/thumbnail.jpg`; try { const imgRes = await fetch(mapInfo.thumbnailUrl); if (!imgRes.ok) return status(502, { error: "Failed to download thumbnail" }); const imgBuf = Buffer.from(await imgRes.arrayBuffer()); await uploadObject(thumbnailKey, imgBuf, "image/jpeg"); } catch { return status(502, { error: "Failed to download thumbnail" }); } updateThumbnailKey(id, thumbnailKey); updateTier(id, mapInfo.tier); return { success: true, thumbnailUrl: getObjectUrl(thumbnailKey) }; }) .delete("/:id", async ({ params: { id } }) => { const video = getVideoById(id); if (!video) return status(404, { error: "Not found" }); const deletes = [ deleteObject(video.videoKey), deleteObject(video.mtvKey), ]; if (video.thumbnailKey) deletes.push(deleteObject(video.thumbnailKey)); if (video.transcodedKey) deletes.push(deleteObject(video.transcodedKey)); await Promise.all(deletes); deleteVideoById(id); return { success: true }; });