import { COOKIENAME, fileRouter } from "../../"; import { authCheck } from "../../lib/Auth"; import { minioClient, CDN_BUCKET, getPublicUrl } from "../../lib/CDN"; import crypto from "crypto"; export = new fileRouter.Path("/").http( "POST", "/api/account/cdn/upload", (http) => http.onRequest(async (ctr) => { const cookie = ctr.cookies.get(COOKIENAME) || null; const apiHeader = (ctr.headers && ctr.headers.get) ? (ctr.headers.get("api-authentication") as string) || null : null; const auth = await authCheck(cookie, apiHeader); if (!auth.state) { return ctr .status(ctr.$status.UNAUTHORIZED) .print({ error: auth.message }); } const [data, error] = await ctr.bindBody((z) => z.object({ images: z.array(z.object({ image: z.string().min(1), // base64 filename: z.string().min(1).max(200), contentType: z.string().min(1).max(100), })).min(1), }) ); if (!data) return ctr.status(ctr.$status.BAD_REQUEST).print({ error: error.toString() }); try { const urls: string[] = []; const ALLOWED_EXTENSIONS = new Set(["jpg", "jpeg", "png", "gif", "webp", "pdf"]); for (const item of data.images) { const buffer = Buffer.from(item.image, 'base64'); const rawExtension = item.filename.split('.').pop() || ""; const extension = rawExtension.toLowerCase(); if (!ALLOWED_EXTENSIONS.has(extension)) { return ctr .status(ctr.$status.BAD_REQUEST) .print({ error: "Unsupported file extension" }); } const objectName = `${auth.userId}/${crypto.randomUUID()}.${extension}`; await minioClient.putObject(CDN_BUCKET, objectName, buffer, buffer.length, { 'Content-Type': item.contentType, }); urls.push(getPublicUrl(objectName)); } return ctr.print({ success: true, urls: urls }); } catch (error: unknown) { console.error(error); const details = error instanceof Error ? error.message : "Unknown error during image upload"; return ctr.status(ctr.$status.INTERNAL_SERVER_ERROR).print({ error: "Failed to upload images to CDN", errorCode: "CDN_UPLOAD_FAILED", details }); } }) ).http( "GET", "/api/account/cdn/list", (http) => http.onRequest(async (ctr) => { const cookie = ctr.cookies.get(COOKIENAME) || null; const apiHeader = (ctr.headers && ctr.headers.get) ? (ctr.headers.get("api-authentication") as string) || null : null; const auth = await authCheck(cookie, apiHeader); if (!auth.state) { return ctr .status(ctr.$status.UNAUTHORIZED) .print({ error: auth.message }); } try { const objects: any[] = []; const stream = minioClient.listObjectsV2(CDN_BUCKET, `${auth.userId}/`, true); for await (const obj of stream) { objects.push({ name: obj.name, url: getPublicUrl(obj.name), size: obj.size, lastModified: obj.lastModified }); } return ctr.print({ success: true, images: objects }); } catch (e) { console.error(e); return ctr.status(ctr.$status.INTERNAL_SERVER_ERROR).print({ error: "Failed to list images from CDN" }); } }) ).http( "DELETE", "/api/account/cdn/delete", (http) => http.onRequest(async (ctr) => { const cookie = ctr.cookies.get(COOKIENAME) || null; const apiHeader = (ctr.headers && ctr.headers.get) ? (ctr.headers.get("api-authentication") as string) || null : null; const auth = await authCheck(cookie, apiHeader); if (!auth.state) { return ctr .status(ctr.$status.UNAUTHORIZED) .print({ error: auth.message }); } const [data, error] = await ctr.bindBody((z) => z.object({ urls: z.array(z.string().url()).min(1), }) ); if (!data) return ctr.status(ctr.$status.BAD_REQUEST).print({ error: error.toString() }); try { const objectsToDelete: string[] = []; for (const url of data.urls) { const urlObj = new URL(url); const pathParts = urlObj.pathname.split('/').filter(Boolean); // Expected: [bucket, userId, filename] if (pathParts.length < 3) continue; const bucket = pathParts[0]; const userId = pathParts[1]; const filename = pathParts.slice(2).join('/'); const objectName = `${userId}/${filename}`; if (bucket === CDN_BUCKET && userId === auth.userId) { objectsToDelete.push(objectName); } } if (objectsToDelete.length === 0) { return ctr.status(ctr.$status.BAD_REQUEST).print({ error: "No valid objects to delete" }); } await minioClient.removeObjects(CDN_BUCKET, objectsToDelete); return ctr.print({ success: true, message: `${objectsToDelete.length} image(s) deleted successfully` }); } catch (e) { console.error(e); return ctr.status(ctr.$status.INTERNAL_SERVER_ERROR).print({ error: "Failed to delete images" }); } }) );