cdn / index.js
FarelDeveloper's picture
Update index.js
9d9e25b verified
Raw
History Blame Contribute Delete
25.1 kB
const express = require('express');
const multer = require('multer');
const { Storage, File } = require('megajs');
const fs = require('fs');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 7860;
// Setup penyimpanan sementara untuk upload transit
const upload = multer({ dest: '/tmp/' });
app.use(express.json());
let megaStorage;
// Variabel tracker statis sederhana (akan ter-reset jika space restart/tidur)
let totalFiles = 0;
let totalBytes = 0;
let totalDownloads = 0;
// Fungsi format ukuran biner
function formatBytes(bytes, decimals = 2) {
if (!bytes) return '0 B';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
async function connectStorage() {
return new Promise((resolve, reject) => {
const email = process.env.MEGA_EMAIL;
const password = process.env.MEGA_PASSWORD;
if (!email || !password) {
console.error("ERROR: Variabel lingkungan kredensial belum diatur!");
return reject("Kredensial kosong");
}
const storage = new Storage({
email: email,
password: password,
keepalive: true
}, (err) => {
if (err) return reject(err);
console.log('✓ Sistem backend CDN siap dan terhubung!');
resolve(storage);
});
});
}
connectStorage().then(storage => {
megaStorage = storage;
}).catch(err => {
console.error('⚠️ Gagal menginisialisasi storage:', err);
});
// Endpoint 1: Frontend Dashboard Utama (Tema Putih Bersih Premium)
app.get('/', (req, res) => {
const hostUrl = `${req.protocol}://${req.get('host')}`;
res.send(`
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CDN FAREL - Secure CDN Gateway</title>
<style>
:root {
--bg-main: #f8fafc;
--bg-card: #ffffff;
--text-main: #0f172a;
--text-muted: #64748b;
--border-color: #e2e8f0;
--accent: #ff4757;
--accent-hover: #ff6b81;
--indigo: #4f46e5;
--success: #10b981;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; }
body { background: var(--bg-main); color: var(--text-main); padding: 40px 20px; line-height: 1.5; }
.container { max-width: 1000px; margin: 0 auto; }
/* Header */
header { text-align: center; margin-bottom: 40px; }
header h1 { font-size: 2.2rem; font-weight: 800; tracking-spacing: -0.05em; color: var(--text-main); }
header h1 span { color: var(--accent); }
header p.tagline { font-size: 1.1rem; color: var(--text-muted); font-weight: 500; margin-top: 5px; }
header p.desc { max-width: 600px; margin: 10px auto 0; color: var(--text-muted); font-size: 0.95rem; }
/* Grid Statistik */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; margin-bottom: 40px; }
.stat-card { background: var(--bg-card); padding: 24px; border-radius: 12px; border: 1px solid var(--border-color); box-shadow: 0 1px 3px rgba(0,0,0,0.02); }
.stat-card h3 { font-size: 2rem; font-weight: 700; color: var(--text-main); margin-bottom: 4px; }
.stat-card p.label { font-size: 0.9rem; font-weight: 600; color: var(--text-main); }
.stat-card p.sub { font-size: 0.8rem; color: var(--text-muted); }
/* Card Utama */
.card { background: var(--bg-card); padding: 40px; border-radius: 16px; border: 1px solid var(--border-color); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -1px rgba(0,0,0,0.03); margin-bottom: 40px; }
.card-title { font-size: 1.4rem; font-weight: 700; margin-bottom: 6px; }
.card-subtitle { font-size: 0.95rem; color: var(--text-muted); margin-bottom: 25px; }
/* Dropzone */
.upload-box { border: 2px dashed #cbd5e1; padding: 50px 20px; text-align: center; border-radius: 12px; cursor: pointer; transition: all 0.2s ease; background: #f8fafc; }
.upload-box:hover { border-color: var(--accent); background: #fff5f5; }
.upload-box p.primary-text { font-size: 1.05rem; font-weight: 600; color: var(--text-main); margin-bottom: 4px; }
.upload-box p.secondary-text { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 15px; }
.upload-box p.info-text { font-size: 0.85rem; color: var(--text-muted); line-height: 1.4; background: #fff; padding: 8px 12px; border-radius: 6px; display: inline-block; border: 1px solid var(--border-color); }
input[type="file"] { display: none; }
button.btn-upload { background: var(--accent); color: white; border: none; padding: 14px 30px; border-radius: 8px; font-weight: 700; cursor: pointer; width: 100%; margin-top: 20px; font-size: 16px; transition: background 0.2s; }
button.btn-upload:hover { background: var(--accent-hover); }
/* Hasil Upload */
.result { margin-top: 30px; display: none; background: #f0fdf4; padding: 25px; border-radius: 10px; border: 1px solid #bbf7d0; }
.result h3 { margin-bottom: 12px; color: #166534; font-size: 1.15rem; display: flex; align-items: center; gap: 8px; }
.result p { font-size: 0.95rem; margin-bottom: 10px; color: var(--text-main); }
.result-link-box { background: #ffffff; padding: 12px; border-radius: 6px; border: 1px solid #dcfce7; display: flex; align-items: center; justify-content: space-between; margin-top: 10px; gap: 10px; }
.result-link-box a { color: var(--indigo); word-break: break-all; font-weight: 600; text-decoration: none; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; }
.result-link-box a:hover { text-decoration: underline; }
.btn-copy-link { background: var(--text-main); color: #fff; border: none; padding: 6px 12px; border-radius: 4px; font-size: 0.8rem; font-weight: 600; cursor: pointer; white-space: nowrap; }
.status { text-align: center; margin-top: 15px; color: var(--indigo); font-weight: 600; font-size: 0.95rem; display: none; }
/* Fitur & Manfaat Kelebihan */
.section-title { font-size: 1.6rem; font-weight: 800; text-align: center; margin-bottom: 6px; }
.section-subtitle { text-align: center; color: var(--text-muted); font-size: 0.95rem; margin-bottom: 35px; }
.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 25px; margin-bottom: 5px; }
.feature-item { background: var(--bg-card); padding: 30px; border-radius: 12px; border: 1px solid var(--border-color); }
.feature-item h4 { font-size: 1.1rem; font-weight: 700; margin-bottom: 8px; color: var(--text-main); }
.feature-item p { font-size: 0.9rem; color: var(--text-muted); line-height: 1.5; }
/* Dokumentasi API Section */
.api-card { background: var(--bg-card); border-radius: 14px; border: 1px solid var(--border-color); margin-top: 40px; overflow: hidden; }
.api-header { padding: 25px; border-bottom: 1px solid var(--border-color); background: #fafafa; }
.api-badge-row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.badge-method { background: var(--success); color: white; padding: 4px 10px; border-radius: 4px; font-weight: 700; font-size: 0.8rem; }
.badge-type { background: #e2e8f0; color: var(--text-main); padding: 4px 10px; border-radius: 4px; font-weight: 600; font-size: 0.8rem; }
.api-endpoint { font-family: monospace; font-size: 1.1rem; font-weight: 700; color: var(--text-main); }
.api-desc { padding: 0 25px 20px; font-size: 0.9rem; color: var(--text-muted); border-bottom: 1px solid var(--border-color); background: #fafafa; }
/* Code tabs */
.api-body { padding: 25px; background: #1e293b; color: #f8fafc; }
.tab-header { display: flex; gap: 8px; margin-bottom: 15px; border-bottom: 1px solid #334155; padding-bottom: 10px; }
.tab-btn { background: transparent; color: #94a3b8; border: none; padding: 6px 14px; font-size: 0.85rem; font-weight: 600; cursor: pointer; border-radius: 4px; }
.tab-btn.active { background: #334155; color: #fff; }
.code-container { position: relative; }
pre { font-family: 'Courier New', Courier, monospace; font-size: 0.85rem; overflow-x: auto; white-space: pre-wrap; word-break: break-all; color: #e2e8f0; line-height: 1.5; }
.btn-copy-code { position: absolute; top: 0; right: 0; background: #334155; color: #f8fafc; border: none; padding: 5px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer; font-weight: 600; }
.btn-copy-code:hover { background: #475569; }
.cors-text { font-size: 0.75rem; font-weight: 700; color: #38bdf8; letter-spacing: 0.05em; margin-top: 15px; display: inline-block; }
/* Footer */
footer { border-top: 1px solid var(--border-color); margin-top: 60px; padding-top: 30px; color: var(--text-muted); font-size: 0.85rem; }
.footer-top { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 20px; margin-bottom: 20px; }
.footer-brand h5 { font-size: 1.1rem; font-weight: 800; color: var(--text-main); margin-bottom: 4px; }
.footer-meta { display: flex; gap: 15px; font-weight: 600; color: var(--text-main); font-size: 0.85rem; }
.footer-bottom { display: flex; justify-content: space-between; align-items: center; border-top: 1px solid var(--border-color); padding-top: 20px; color: var(--text-muted); font-size: 0.8rem; }
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<header>
<h1>CDN <span>FAREL</span></h1>
<p class="tagline">Secure CDN Gateway</p>
<p class="desc">Unggah Berkas, Dapatkan Direct Link</p>
<p class="desc" style="font-size: 0.85rem; margin-top: 5px; max-width: 500px;">Sistem direct link CDN mandiri berkecepatan tinggi dengan enkripsi awan ganda yang aman dan global.</p>
</header>
<!-- Grid Statistik -->
<div class="stats-grid">
<div class="stat-card">
<h3 id="statFiles">${totalFiles}</h3>
<p class="label">Total File Aktif</p>
<p class="sub">Seluruh file yang tersimpan di CDN</p>
</div>
<div class="stat-card">
<h3 id="statBytes">${formatBytes(totalBytes)}</h3>
<p class="label">Kapasitas Terpakai</p>
<p class="sub">Total kapasitas cloud terpakai</p>
</div>
<div class="stat-card">
<h3 id="statDownloads">${totalDownloads}</h3>
<p class="label">Total Pengunduhan</p>
<p class="sub">File dikonsumsi via link CDN</p>
</div>
<div class="stat-card">
<h3>1</h3>
<p class="label">Node Storage Cluster</p>
<p class="sub">Penyimpanan cloud node terenkripsi aktif</p>
</div>
</div>
<!-- Card Upload Dropzone -->
<div class="card">
<h2 class="card-title">Unggah Berkas ke CDN Farel</h2>
<p class="card-subtitle">Dapatkan direct link, embedding, dan direct download berkecepatan tinggi instan dengan mengunggah berkas ke gateway kami.</p>
<form id="uploadForm">
<div class="upload-box" onclick="document.getElementById('fileInput').click()" id="dropzone">
<p class="primary-text" id="boxText">Seret & taruh berkas di sini, atau pilih manual</p>
<p class="secondary-text">Semua format file didukung (Maks 100 MB)</p>
<p class="info-text">Pilih atau seret berkas ke dropzone untuk mempersiapkan pratinjau dan mengaktifkan tautan CDN.</p>
<input type="file" id="fileInput" name="file" required onchange="updateBoxText()">
</div>
<button type="submit" class="btn-upload">UNGGAH KE CDN</button>
</form>
<div class="status" id="statusText">Sedang memproses berkas... Harap tunggu...</div>
<!-- Box Hasil Output -->
<div class="result" id="resultBox">
<h3>🎉 Berkas Berhasil Diproses!</h3>
<p><strong>Nama Berkas:</strong> <span id="resName"></span></p>
<p style="font-weight: 600; margin-top: 15px; font-size: 0.9rem; color: #15803d;">Link CDN Anda:</p>
<div class="result-link-box">
<a id="resCdn" href="#" target="_blank"></a>
<button class="btn-copy-link" onclick="copyLinkOutput()">Copy Link</button>
</div>
</div>
</div>
<!-- Section Kelebihan -->
<h2 class="section-title">Manfaat & Kelebihan CDN FAREL</h2>
<p class="section-subtitle">Infrastruktur andal yang dioptimalkan untuk performa pengiriman aset digital terbaik.</p>
<div class="features-grid">
<div class="feature-item">
<h4>Direct Streaming Link</h4>
<p>Dapatkan link bersih langsung tanpa halaman pengalihan, iklan, atau timer. Sempurna untuk disematkan langsung pada HTML, Markdown, stylesheet, atau tag media web.</p>
</div>
<div class="feature-item">
<h4>Bebas Hambatan CORS</h4>
<p>Mendukung kebijakan penuh Cross-Origin Resource Sharing (CORS). Integrasikan aset seperti gambar kustom, font kustom, atau file JSON dari domain manapun secara mulus.</p>
</div>
<div class="feature-item">
<h4>Penyimpanan MEGA Aman</h4>
<p>Semua aset diunggah langsung dan disimpan ke dalam penyimpanan awan MEGA terenkripsi yang aman untuk menjamin ketersediaan data Anda tetap utuh, aman, dan dapat diakses terus.</p>
</div>
<div class="feature-item">
<h4>MIME Type yang Tepat</h4>
<p>Deteksi tipe konten otomatis secara cerdas. Server menyajikan berkas dengan header <code>Content-Type</code> yang akurat agar dapat di-render atau diputar langsung di browser.</p>
</div>
<div class="feature-item">
<h4>Kapasitas Besar 100MB</h4>
<p>Kirim berkas media berukuran besar seperti video klip, track musik berkualitas tinggi, dokumen PDF tebal, hingga kompresi zip tanpa takut batasan ukuran yang kecil.</p>
</div>
<div class="feature-item">
<h4>Arsip Selamanya (Persistent)</h4>
<p>Berkas tidak memiliki batas kedaluwarsa waktu. File Anda tetap tersimpan utuh di gateway awan tanpa kekhawatiran akan terhapus otomatis setelah beberapa hari.</p>
</div>
</div>
<!-- Section API Dokumentasi -->
<div class="api-card">
<div class="api-header">
<div class="api-badge-row">
<span class="badge-method">POST</span>
<span class="badge-type">multipart/form-data</span>
</div>
<div class="api-endpoint">/api/upload</div>
</div>
<div class="api-desc">
Gunakan endpoint pengunggahan file dari CDN FAREL untuk mengotomatiskan proses upload file langsung dari aplikasi, skrip backend, website, atau bot Anda. Kirim berkas Anda pada parameter body bernama <code>file</code>. Respon akan mengembalikan URL streaming langsung secara instan.
</div>
<div class="api-body">
<div class="tab-header">
<button class="tab-btn active" onclick="switchTab('curl')">cURL</button>
<button class="tab-btn" onclick="switchTab('js')">javascript</button>
<button class="tab-btn" onclick="switchTab('py')">python</button>
</div>
<div class="code-container">
<button class="btn-copy-code" onclick="copyCodeSnippet()">Copy Snippet</button>
<pre id="codeBlock">curl -X POST "${hostUrl}/api/upload" \\\n -F "file=@/path/to/your/file.png"</pre>
</div>
<span class="cors-text">CORS ENABLED</span>
</div>
</div>
<!-- Footer -->
<footer>
<div class="footer-top">
<div class="footer-brand">
<h5>CDN FAREL</h5>
<p>Penyimpanan awan performa tinggi dan gerbang distribusi direct link aman.</p>
</div>
<div class="footer-meta">
<span>Node Kluster Utama Aktif</span>
<span>|</span>
<span>SSL Terenkripsi</span>
<span>|</span>
<span>CORS Terbuka</span>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2026 CDN FAREL. Hak Cipta Dilindungi.</p>
<p>v1.4.0 Production &bull; SLA 99.9%</p>
</div>
</footer>
</div>
<script>
const hostBase = "${hostUrl}";
let currentTab = 'curl';
// Snippets generator dinamis sesuai host saat ini
const snippets = {
curl: \`curl -X POST "\${hostBase}/api/upload" \\\n -F "file=@/path/to/your/file.png"\`,
js: \`const data = new FormData();\ndata.append('file', fileInput.files[0]);\n\nfetch('\${hostBase}/api/upload', {\n method: 'POST',\n body: data\n})\n.then(res => res.json())\n.then(json => console.log(json.cdnLink));\`,
py: \`import requests\n\nurl = "\${hostBase}/api/upload"\nfiles = {'file': open('file.png', 'rb')}\nr = requests.post(url, files=files)\nprint(r.json()['cdnLink'])\`
};
function switchTab(lang) {
currentTab = lang;
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
document.getElementById('codeBlock').innerText = snippets[lang];
}
function copyCodeSnippet() {
navigator.clipboard.writeText(snippets[currentTab]);
alert('Snippet kode berhasil disalin!');
}
function copyLinkOutput() {
const linkText = document.getElementById('resCdn').innerText;
navigator.clipboard.writeText(linkText);
alert('Tautan CDN berhasil disalin!');
}
function updateBoxText() {
const input = document.getElementById('fileInput');
if(input.files.length > 0) {
document.getElementById('boxText').innerText = "Berkas terpilih: " + input.files[0].name;
}
}
// Setup Drag and Drop
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
['dragenter', 'dragover'].forEach(eventName => {
dropzone.addEventListener(eventName, (e) => { e.preventDefault(); dropzone.style.borderColor = 'var(--accent)'; }, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, (e) => { e.preventDefault(); dropzone.style.borderColor = '#cbd5e1'; }, false);
});
dropzone.addEventListener('drop', (e) => {
fileInput.files = e.dataTransfer.files;
updateBoxText();
});
// Submit form lewat AJAX
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
if (fileInput.files.length === 0) return;
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const statusText = document.getElementById('statusText');
const resultBox = document.getElementById('resultBox');
statusText.style.display = "block";
resultBox.style.display = "none";
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
statusText.style.display = "none";
document.getElementById('resName').innerText = data.fileName;
document.getElementById('resCdn').innerText = data.cdnLink;
document.getElementById('resCdn').href = data.cdnLink;
resultBox.style.display = "block";
// Update UI statistik secara real-time di halaman
if(data.stats) {
document.getElementById('statFiles').innerText = data.stats.files;
document.getElementById('statBytes').innerText = data.stats.formattedBytes;
}
} else {
statusText.innerText = "Gagal memproses: " + data.error;
}
} catch (err) {
statusText.innerText = "Terjadi kesalahan sistem saat memproses berkas.";
console.error(err);
}
});
</script>
</body>
</html>
`);
});
// Endpoint 2: API Proses Upload File (Update data stat secara real-time)
app.post('/api/upload', upload.single('file'), async (req, res) => {
if (!megaStorage) return res.status(503).json({ error: 'Storage backend belum siap' });
if (!req.file) return res.status(400).json({ error: 'Tidak ada file yang dikirim' });
try {
const filePath = req.file.path;
const fileName = req.file.originalname;
const fileSize = req.file.size;
const fileStream = fs.createReadStream(filePath);
// Upload berkas ke cloud storage MEGA
const megaFile = await megaStorage.upload({
name: fileName,
size: fileSize
}, fileStream).complete;
// Dapatkan tautan cloud publik internal
const downloadLink = await megaFile.link();
// Hapus file transit di lokal (/tmp/)
fs.unlinkSync(filePath);
// Ubah '#' menjadi '-' agar aman dilewati via URL Express Gateway
const fileId = downloadLink.split('/file/')[1].replace('#', '-');
// Tambah counter statistik internal server
totalFiles += 1;
totalBytes += fileSize;
res.json({
success: true,
fileName: fileName,
cdnLink: `${req.protocol}://${req.get('host')}/cdn/${fileId}`,
stats: {
files: totalFiles,
formattedBytes: formatBytes(totalBytes)
}
});
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Gagal memproses penyimpanan: ' + error.message });
}
});
// Endpoint 3: Proxy CDN Gateway (Streaming Data Langsung + Dukungan CORS)
app.get('/cdn/:fileIdAndKey', async (req, res) => {
try {
const fullId = req.params.fileIdAndKey;
// Kembalikan karakter '-' menjadi '#' untuk menyusun URL asli MEGA
const correctedId = fullId.replace('-', '#');
const targetUrl = `https://mega.nz/file/${correctedId}`;
const file = File.fromURL(targetUrl);
await file.loadAttributes();
// Dukungan Penuh Kebijakan CORS
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
// Atur header disposisi agar browser langsung memutar/merender inline aset
res.setHeader('Content-Disposition', `inline; filename="${file.name}"`);
// Tambah hit counter unduhan
totalDownloads += 1;
// Alirkan data secara streaming waktu nyata ke pengguna luar
const stream = file.download();
stream.pipe(res);
} catch (error) {
console.error(error);
res.status(404).send('Berkas tidak ditemukan atau tautan CDN salah.');
}
});
app.listen(port, () => {
console.log(`Server CDN Farel berjalan aktif pada port ${port}`);
});