From 1d5b807fc7f2a80f024c5d571913755b8d1a2bb3 Mon Sep 17 00:00:00 2001 From: Raditya_Indra_Putranto Date: Thu, 4 Jun 2026 18:53:02 +0700 Subject: [PATCH] fix: proxy dashboard through Netlify function --- netlify.toml | 9 +++++ netlify/functions/dashboard.js | 68 ++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 netlify/functions/dashboard.js diff --git a/netlify.toml b/netlify.toml index 0cab1ff..2a8fe6c 100644 --- a/netlify.toml +++ b/netlify.toml @@ -4,3 +4,12 @@ [build.environment] NODE_VERSION = "22" + +[functions] + directory = "netlify/functions" + node_bundler = "esbuild" + +[[redirects]] + from = "/api/dashboard" + to = "/.netlify/functions/dashboard" + status = 200 diff --git a/netlify/functions/dashboard.js b/netlify/functions/dashboard.js new file mode 100644 index 0000000..c9fad0d --- /dev/null +++ b/netlify/functions/dashboard.js @@ -0,0 +1,68 @@ +const DEFAULT_DASHBOARD_URL = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian'; +const CACHE_TTL_MS = 5 * 60 * 1000; + +let dashboardCache = { + html: '', + timestamp: 0, +}; + +function getDashboardUrl() { + return process.env.DASHBOARD_TARGET_URL || DEFAULT_DASHBOARD_URL; +} + +function response(statusCode, body, extraHeaders = {}) { + return { + statusCode, + headers: { + 'Content-Type': 'text/html; charset=utf-8', + 'Cache-Control': 'public, max-age=60, stale-while-revalidate=300', + ...extraHeaders, + }, + body, + }; +} + +exports.handler = async () => { + const now = Date.now(); + + if (dashboardCache.html && now - dashboardCache.timestamp < CACHE_TTL_MS) { + return response(200, dashboardCache.html, { 'X-Cache': 'HIT' }); + } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 12000); + + try { + const dashboardResponse = await fetch(getDashboardUrl(), { + signal: controller.signal, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36', + }, + }); + + if (!dashboardResponse.ok) { + throw new Error(`HTTP_ERROR_${dashboardResponse.status}`); + } + + const html = await dashboardResponse.text(); + dashboardCache = { html, timestamp: Date.now() }; + + return response(200, html, { 'X-Cache': 'MISS' }); + } catch (error) { + if (dashboardCache.html) { + return response(200, dashboardCache.html, { 'X-Cache': 'STALE' }); + } + + return { + statusCode: 502, + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + }, + body: error && error.name === 'AbortError' + ? 'DASHBOARD_FETCH_TIMEOUT' + : 'DASHBOARD_FETCH_FAILED', + }; + } finally { + clearTimeout(timeoutId); + } +};