const express = require("express"); const multer = require("multer"); const path = require("path"); const fs = require("fs"); const cors = require("cors"); const crypto = require("crypto"); const { exec } = require("child_process"); const app = express(); /* ================= CONFIG ================= */ app.use(cors()); app.use(express.json()); const UPLOADS = path.join(__dirname, "uploads"); const SHARES = path.join(__dirname, "shares.json"); if (!fs.existsSync(UPLOADS)) fs.mkdirSync(UPLOADS); if (!fs.existsSync(SHARES)) fs.writeFileSync(SHARES, "{}"); /* ================= HELPERS ================= */ const safe = v => v.replace(/[^a-zA-Z0-9@._/-]/g, "").replace(/\.\./g, ""); const readShares = () => JSON.parse(fs.readFileSync(SHARES)); const writeShares = d => fs.writeFileSync(SHARES, JSON.stringify(d, null, 2)); const token = () => Math.random().toString(36).slice(2) + Date.now(); // Helper to calculate directory size and generate tree const getDirInfo = (dirPath) => { let size = 0; const children = fs.readdirSync(dirPath).map(name => { const fullPath = path.join(dirPath, name); const stats = fs.statSync(fullPath); if (stats.isDirectory()) { const subDir = getDirInfo(fullPath); size += subDir.size; return { name, isFolder: true, children: subDir.children, size: subDir.size }; } size += stats.size; return { name, isFolder: false, size: stats.size }; }); return { children, size }; }; /* ================= PREVIEW STORE (Persistent Logic) ================= */ // We now use a hash-based approach so the URL is deterministic for the file const getPersistentToken = (email, filePath) => { return crypto.createHash('sha256').update(`${email}-${filePath}`).digest('hex').slice(0, 16); }; /* ================= FILE EDITOR ================= */ app.post("/get-file-content", (req, res) => { const { email, path: filePath } = req.body; if (!email || !filePath) return res.status(400).json({ error: "Missing email or path" }); const fullPath = path.join(UPLOADS, safe(email), safe(filePath)); if (!fs.existsSync(fullPath)) return res.status(404).json({ error: "File not found" }); res.json({ content: fs.readFileSync(fullPath, "utf8") }); }); app.post("/save-file-content", (req, res) => { const { email, path: filePath, content } = req.body; if (!email || !filePath || content === undefined) return res.status(400).json({ error: "Missing data" }); const fullPath = path.join(UPLOADS, safe(email), safe(filePath)); fs.writeFileSync(fullPath, content, "utf8"); res.json({ ok: true }); }); // DETERMINISTIC PREVIEW URL app.post("/preview-html", (req, res) => { const { email, path: filePath } = req.body; if (!email || !filePath) return res.status(400).json({ error: "Missing email or path" }); const t = getPersistentToken(email, filePath); res.json({ url: `https://adamyakhairwal2011-vasuki-cloud.hf.space/preview/${email}/${filePath}` }); }); // SERVE PREVIEW (Directly from Disk for Persistence) app.get("/preview/:email/*", (req, res) => { const email = safe(req.params.email); const filePath = safe(req.params[0]); const fullPath = path.join(UPLOADS, email, filePath); if (!fs.existsSync(fullPath)) return res.status(404).send("File not found"); const content = fs.readFileSync(fullPath, "utf8"); const baseUrl = `https://adamyakhairwal2011-vasuki-cloud.hf.space/download/${email}/`; res.send(` ${content} `); }); /* ================= PYTHON COMPILATION ================= */ app.post("/run-python", (req, res) => { const { code } = req.body; if (!code) return res.status(400).json({ error: "No code provided" }); const fileName = `exec_${Date.now()}.py`; const filePath = path.join(__dirname, fileName); fs.writeFileSync(filePath, code); // Execute with a 5-second timeout for safety exec(`python3 ${filePath}`, { timeout: 5000 }, (error, stdout, stderr) => { // Cleanup if (fs.existsSync(filePath)) fs.unlinkSync(filePath); if (error && error.killed) { return res.json({ output: "", error: "Execution timed out (5s limit)" }); } res.json({ output: stdout, error: stderr }); }); }); /* ================= ADMIN OPS ================= */ // CEO Insight: System Health and Storage Tree app.get("/admin/system-overview", (req, res) => { try { const info = getDirInfo(UPLOADS); res.json({ status: "online", totalSize: `${(info.size / (1024 * 1024)).toFixed(2)} MB`, tree: info.children }); } catch (err) { res.status(500).json({ error: err.message }); } }); /* ================= FILE OPS ================= */ app.post("/create-file", (req, res) => { const { email, folder = "", name, extension } = req.body; if (!email || !name || !extension) return res.status(400).json({ error: "Missing data" }); const fileName = `${safe(name)}.${safe(extension)}`; const fullPath = path.join(UPLOADS, safe(email), safe(folder), fileName); if (fs.existsSync(fullPath)) return res.status(409).json({ error: "File already exists" }); fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, "", "utf8"); res.json({ ok: true, file: fileName }); }); app.post("/create-folder", (req, res) => { const dir = path.join( UPLOADS, safe(req.body.email), safe(req.body.folder || ""), safe(req.body.name) ); fs.mkdirSync(dir, { recursive: true }); res.json({ ok: true }); }); app.post("/list-files", (req, res) => { const dir = path.join( UPLOADS, safe(req.body.email), safe(req.body.folder || "") ); if (!fs.existsSync(dir)) return res.json({ files: [] }); const files = fs.readdirSync(dir).map(n => { const s = fs.statSync(path.join(dir, n)); return { name: n, isFolder: s.isDirectory(), size: s.size }; }); res.json({ files }); }); const storage = multer.diskStorage({ destination: (req, file, cb) => { const dir = path.join(UPLOADS, safe(req.body.email), safe(req.body.folder || "")); fs.mkdirSync(dir, { recursive: true }); cb(null, dir); }, filename: (req, file, cb) => cb(null, file.originalname.trim()) }); const upload = multer({ storage }); app.post("/upload", upload.array("files", 20), (req, res) => res.json({ ok: true }) ); app.post("/delete-file", (req, res) => { const p = path.join(UPLOADS, safe(req.body.email), safe(req.body.path)); if (!fs.existsSync(p)) return res.status(404).json({ error: "Not found" }); fs.lstatSync(p).isDirectory() ? fs.rmSync(p, { recursive: true, force: true }) : fs.unlinkSync(p); res.json({ ok: true }); }); app.post("/rename-file", (req, res) => { const oldP = path.join(UPLOADS, safe(req.body.email), safe(req.body.oldPath)); const newP = path.join(path.dirname(oldP), safe(req.body.newName)); fs.renameSync(oldP, newP); res.json({ ok: true }); }); app.get("/download/:email/*", (req, res) => { const p = path.join(UPLOADS, safe(req.params.email), safe(req.params[0])); res.download(p); }); /* ================= SHARING ================= */ app.post("/share", (req, res) => { const { email, files, permission } = req.body; if (!email || !files?.length) return res.status(400).json({ error: "Invalid request" }); const db = readShares(); const t = token(); db[t] = { email, files: files.map(safe), permission, expires: Date.now() + 86400000 }; writeShares(db); res.json({ url: `https://vasuki.cloud/share.html?token=${t}` }); }); app.get("/shared-info/:token", (req, res) => { const db = readShares(); res.json(db[req.params.token] || {}); }); app.get("/shared-file/:token/*", (req, res) => { const db = readShares(); const s = db[req.params.token]; if (!s || Date.now() > s.expires) return res.sendStatus(404); const rel = safe(req.params[0]); if (!s.files.includes(rel)) return res.sendStatus(403); const p = path.join(UPLOADS, safe(s.email), rel); if (!fs.existsSync(p)) return res.sendStatus(404); res.sendFile(p); }); app.get('/', (req, res) => { res.status(200).send('Vasuki Backend is Online'); }); /* ================= START ================= */ const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Vasuki Backend running on port ${PORT}`); });