120 lines
3.4 KiB
JavaScript
120 lines
3.4 KiB
JavaScript
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("<BucketResponse>OK</BucketResponse>");
|
||
|
|
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})`);
|
||
|
|
});
|