import http from "node:http"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT_DIR = path.resolve(__dirname, "../.s3data/imen-media"); if (!fs.existsSync(ROOT_DIR)) { fs.mkdirSync(ROOT_DIR, { recursive: true }); } const MIME_TYPES = { ".webp": "image/webp", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".svg": "image/svg+xml", ".json": "application/json", }; const server = http.createServer(async (req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, PUT, DELETE, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "*"); if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } // Parse path and remove query string const urlObj = new URL(req.url, "http://localhost:9000"); let pathname = decodeURIComponent(urlObj.pathname); // Normalize path by stripping optional leading bucket name "/imen-media" or "/media" if (pathname.startsWith("/imen-media")) { pathname = pathname.slice("/imen-media".length); } else if (pathname.startsWith("/media")) { pathname = pathname.slice("/media".length); } if (pathname === "" || pathname === "/") { res.writeHead(200, { "Content-Type": "application/xml" }); res.end("OK"); return; } const safePath = path.normalize(pathname).replace(/^(\.\.[\/\\])+/, ""); const filePath = path.join(ROOT_DIR, safePath); if (req.method === "HEAD") { if (fs.existsSync(filePath)) { const stat = fs.statSync(filePath); const ext = path.extname(filePath).toLowerCase(); res.writeHead(200, { "Content-Type": MIME_TYPES[ext] || "application/octet-stream", "Content-Length": stat.size, }); res.end(); } else { // Bucket check or file check res.writeHead(200); res.end(); } return; } if (req.method === "GET") { if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { const ext = path.extname(filePath).toLowerCase(); const contentType = MIME_TYPES[ext] || "application/octet-stream"; res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable", }); fs.createReadStream(filePath).pipe(res); } else { res.writeHead(404, { "Content-Type": "text/plain" }); res.end(`File not found: ${pathname}`); } return; } if (req.method === "PUT") { fs.mkdirSync(path.dirname(filePath), { recursive: true }); const writeStream = fs.createWriteStream(filePath); req.pipe(writeStream); writeStream.on("finish", () => { res.writeHead(200, { ETag: '"local-etag"' }); res.end(); }); writeStream.on("error", (err) => { res.writeHead(500, { "Content-Type": "text/plain" }); res.end(err.message); }); return; } if (req.method === "DELETE") { if (fs.existsSync(filePath)) { try { fs.unlinkSync(filePath); } catch {} } res.writeHead(204); res.end(); return; } res.writeHead(405); res.end(); }); const PORT = 9000; server.listen(PORT, "0.0.0.0", () => { console.log(`Media storage server listening on http://0.0.0.0:${PORT} (serving ${ROOT_DIR})`); });