From c07c29c60c88c0479689a7a674d78ee598af9240 Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #78)" Date: Sat, 11 Apr 2026 18:00:44 +0000 Subject: [PATCH] feat: Add servers-api Cloudflare Worker to git (recovered from dashboard) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker proxies Pterodactyl client API for website server list. Live status + player counts, CORS-locked, 60s cache. Was only in Cloudflare Dashboard — known gap now closed. Recovered via Cloudflare MCP connector audit. Chronicler #78 | firefrost-services --- services/servers-api/README.md | 32 +++++++++++ services/servers-api/index.js | 99 ++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 services/servers-api/README.md create mode 100644 services/servers-api/index.js diff --git a/services/servers-api/README.md b/services/servers-api/README.md new file mode 100644 index 0000000..775abeb --- /dev/null +++ b/services/servers-api/README.md @@ -0,0 +1,32 @@ +# servers-api — Cloudflare Worker + +Proxies Pterodactyl client API for the firefrostgaming.com website server list. + +## What It Does + +- Fetches all servers from Pterodactyl Panel client API +- Gets live resource stats (running/offline, player count) +- Returns JSON for the website to display +- CORS-locked to firefrostgaming.com and Pages preview URL +- 60-second cache + +## Environment Variables (set in Cloudflare Dashboard) + +- `PANEL_URL` — Pterodactyl Panel URL (https://panel.firefrostgaming.com) +- `CLIENT_API_KEY` — Pterodactyl client API key + +## Deployment + +Deployed via Cloudflare Dashboard (Wrangler not configured). +Worker name: `servers-api` +Created: April 3, 2026 + +## History + +- Originally created directly in Cloudflare Dashboard +- Never committed to git (known gap) +- Recovered and committed by Chronicler #78 on April 11, 2026 via Cloudflare MCP connector + +--- + +**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️ diff --git a/services/servers-api/index.js b/services/servers-api/index.js new file mode 100644 index 0000000..4d857e2 --- /dev/null +++ b/services/servers-api/index.js @@ -0,0 +1,99 @@ +/** + * servers-api — Cloudflare Worker + * + * Proxies Pterodactyl client API for the firefrostgaming.com website. + * Returns live server status + player counts. + * + * Deployed: April 3, 2026 + * Location: Cloudflare Workers (servers-api) + * Env vars: PANEL_URL, CLIENT_API_KEY + * CORS: firefrostgaming.com, firefrost-website.pages.dev + * Cache: 60 seconds + * + * NOT in any git repo until now — recovered by Chronicler #78 via Cloudflare MCP. + */ + +export default { + async fetch(request, env) { + // Determine allowed origin + const origin = request.headers.get('Origin'); + const allowedOrigins = [ + 'https://firefrostgaming.com', + 'https://firefrost-website.pages.dev' + ]; + const allowedOrigin = allowedOrigins.includes(origin) ? origin : 'https://firefrostgaming.com'; + + // Handle CORS preflight + if (request.method === 'OPTIONS') { + return new Response(null, { + headers: { + 'Access-Control-Allow-Origin': allowedOrigin, + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type' + } + }); + } + + const PANEL_URL = env.PANEL_URL; + const API_KEY = env.CLIENT_API_KEY; + + try { + // Fetch server list + const listResponse = await fetch(`${PANEL_URL}/api/client`, { + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Accept': 'application/json' + } + }); + + const listData = await listResponse.json(); + + if (!listData.data) throw new Error("Failed to fetch server list"); + + // Fetch live stats for all servers + const serverPromises = listData.data.map(async (server) => { + const id = server.attributes.identifier; + + const statsResponse = await fetch(`${PANEL_URL}/api/client/servers/${id}/resources`, { + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Accept': 'application/json' + } + }); + + const stats = await statsResponse.json(); + const isRunning = stats.attributes?.current_state === 'running'; + + return { + id: id, + name: server.attributes.name, + status: isRunning ? 'Online' : 'Offline', + players: isRunning ? (stats.attributes?.resources?.players || 0) : 0, + description: server.attributes.description + }; + }); + + const finalServers = await Promise.all(serverPromises); + + return new Response(JSON.stringify({ servers: finalServers }), { + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': allowedOrigin, + 'Cache-Control': 'public, s-maxage=60, max-age=60' + } + }); + + } catch (error) { + return new Response(JSON.stringify({ + error: "Servers temporarily unreachable", + servers: [] + }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': allowedOrigin + } + }); + } + } +}