From ba437e3c5a3ca26d9c23bf065a0b1b98919d6d79 Mon Sep 17 00:00:00 2001 From: M1 Date: Wed, 18 Mar 2026 09:30:13 +0400 Subject: [PATCH] feat: block web UI routes on api.pingql.com, serve JSON root --- apps/web/src/index.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/web/src/index.ts b/apps/web/src/index.ts index 405f133..5caf04d 100644 --- a/apps/web/src/index.ts +++ b/apps/web/src/index.ts @@ -9,11 +9,36 @@ import { migrate } from "./db"; await migrate(); +// Web-only paths that shouldn't be accessible via api.pingql.com +const WEB_ONLY_PATHS = ["/", "/docs", "/privacy", "/tos", "/dashboard"]; + const app = new Elysia() .use(cors({ origin: process.env.CORS_ORIGINS?.split(",") ?? ["https://pingql.com", "https://api.pingql.com"], credentials: true, })) + // Host-based routing: api.pingql.com gets JSON-only responses + .onBeforeHandle(({ request, set }) => { + const host = new URL(request.url).hostname; + if (host === "api.pingql.com") { + const path = new URL(request.url).pathname; + if (path === "/") { + set.headers["content-type"] = "application/json"; + return new Response(JSON.stringify({ + name: "PingQL API", + version: "1", + docs: "https://pingql.com/docs", + }), { status: 200, headers: { "content-type": "application/json" } }); + } + const isWebOnly = WEB_ONLY_PATHS.some(p => p !== "/" && path.startsWith(p)); + if (isWebOnly) { + return new Response(JSON.stringify({ error: "Not found" }), { + status: 404, + headers: { "content-type": "application/json" }, + }); + } + } + }) .use(dashboard) .use(account) .use(monitors)