import express from 'express'; import path from 'path'; import { fileURLToPath } from 'url'; import cors from 'cors'; const app = express(); const PORT = process.env.PORT || 3004; // Karena kita menggunakan ES module, __dirname perlu didefinisikan secara manual const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // URL yang akan di-proxy const GOOGLE_APPS_SCRIPT_URL = 'https://script.google.com/macros/s/AKfycbybbQyKyVPSVPXOqOjaOFMKetuYfyweGcjResfLi-B-8scqaoyXeSsmsqNTXeTLe7_Wvg/exec'; const STUDENT_DATA_URL = 'https://docs.google.com/spreadsheets/d/18nVeas2TG5SbkTUsc1mQWmPP_0xu3fek-UfTQdjjvUc/gviz/tq?tqx=out:csv&sheet=DATABASE'; const PERMIT_DATA_URL = 'https://docs.google.com/spreadsheets/d/18nVeas2TG5SbkTUsc1mQWmPP_0xu3fek-UfTQdjjvUc/gviz/tq?tqx=out:csv&sheet=REKAP IZIN'; // === CACHING SYSTEM === const cache = { students: { data: null, timestamp: 0 }, permits: { data: null, timestamp: 0 } }; // Cache TTL (Time To Live) in milliseconds const STUDENT_CACHE_TTL = 5 * 60 * 1000; // 5 menit - data siswa jarang berubah const PERMIT_CACHE_TTL = 30 * 1000; // 30 detik - data izin lebih sering berubah const isCacheValid = (cacheEntry, ttl) => { return cacheEntry.data && (Date.now() - cacheEntry.timestamp) < ttl; }; // === END CACHING SYSTEM === app.use(cors()); app.use(express.json()); // Proxy untuk data CSV siswa (dengan cache) app.get('/api/students', async (req, res) => { try { // Check cache first if (isCacheValid(cache.students, STUDENT_CACHE_TTL)) { console.log('📦 Serving students from cache'); return res.type('text/csv').send(cache.students.data); } console.log('🌐 Fetching students from Google Sheets...'); const response = await fetch(STUDENT_DATA_URL); if (!response.ok) throw new Error(`Network response was not ok: ${response.statusText}`); const csvText = await response.text(); // Update cache cache.students = { data: csvText, timestamp: Date.now() }; console.log('✅ Students cached successfully'); res.type('text/csv').send(csvText); } catch (error) { console.error("Error proxying student data:", error); res.status(500).json({ error: "Failed to fetch student data." }); } }); // Proxy untuk data CSV izin (dengan cache) app.get('/api/permits-csv', async (req, res) => { try { // Check cache first (shorter TTL for permits) if (isCacheValid(cache.permits, PERMIT_CACHE_TTL)) { console.log('📦 Serving permits from cache'); return res.type('text/csv').send(cache.permits.data); } console.log('🌐 Fetching permits from Google Sheets...'); const url = `${PERMIT_DATA_URL}&t=${new Date().getTime()}`; const response = await fetch(url); if (!response.ok) throw new Error(`Network response was not ok: ${response.statusText}`); const csvText = await response.text(); // Update cache cache.permits = { data: csvText, timestamp: Date.now() }; console.log('✅ Permits cached successfully'); res.type('text/csv').send(csvText); } catch (error) { console.error("Error proxying permit data:", error); res.status(500).json({ error: "Failed to fetch permit data." }); } }); // Proxy untuk semua aksi Google Apps Script (submit, update, delete) app.post('/api/action', async (req, res) => { try { const response = await fetch(GOOGLE_APPS_SCRIPT_URL, { method: 'POST', redirect: 'follow', body: JSON.stringify(req.body), headers: { "Content-Type": "text/plain;charset=utf-8" }, }); if (!response.ok) { const errorBody = await response.text(); console.error('Apps Script Proxy Error Body:', errorBody); throw new Error(`Apps Script responded with status: ${response.status}`); } const result = await response.json(); // Invalidate permits cache after any action (submit, update, delete) cache.permits = { data: null, timestamp: 0 }; console.log('🗑️ Permits cache invalidated after action'); res.json(result); } catch (error) { console.error("Error proxying to Google Apps Script:", error); res.status(500).json({ status: 'error', message: 'Proxy request to Google Apps Script failed.' }); } }); // Endpoint untuk force refresh cache (opsional, berguna untuk admin) app.post('/api/refresh-cache', (req, res) => { cache.students = { data: null, timestamp: 0 }; cache.permits = { data: null, timestamp: 0 }; console.log('🗑️ All caches cleared'); res.json({ status: 'success', message: 'Cache cleared successfully' }); }); // Tentukan path absolut ke folder 'dist' const distPath = path.resolve(__dirname, 'dist'); // Menyajikan file statis dari direktori build React app.use(express.static(distPath)); // "Catchall" handler: untuk permintaan apa pun yang tidak cocok dengan rute di atas, kirim file index.html React. app.get('*', (req, res) => { res.sendFile(path.resolve(distPath, 'index.html')); }); app.listen(PORT, () => { console.log(`🚀 Server is running on port ${PORT}`); console.log(`📂 Serving static files from ${distPath}`); console.log(`⏱️ Student cache TTL: ${STUDENT_CACHE_TTL / 1000}s, Permit cache TTL: ${PERMIT_CACHE_TTL / 1000}s`); });