From f0c0032726fc6bbab3aa4a862d527df26f56b21e Mon Sep 17 00:00:00 2001 From: Jabir Khan <98869091+captain-jack-sparrow909@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:19:35 +0400 Subject: [PATCH] GET support --- api-gateway/README.md | 2 +- api-gateway/src/routes/health.ts | 120 ++++++++++++++++--------------- docs/DEPLOYMENT.md | 4 +- 3 files changed, 65 insertions(+), 61 deletions(-) diff --git a/api-gateway/README.md b/api-gateway/README.md index 9a64f0e..20f5322 100644 --- a/api-gateway/README.md +++ b/api-gateway/README.md @@ -10,7 +10,7 @@ Deploy separately (Render). Not part of the Next.js app. |--------|------|------|-------------| | GET | `/health`, `/v1/health` | — | Liveness | | GET | `/ready` | — | Database, Redis, and execution-mode readiness | -| POST | `/v1/keepalive` | Cron bearer secret | Wake Render and atomically toggle a dedicated Supabase maintenance row | +| GET/POST | `/v1/keepalive` | Cron bearer secret | Wake Render and atomically toggle a dedicated Supabase maintenance row | | GET | `/v1/me` | Bearer | Profile + plan + usage (syncs Clerk → Supabase) | | GET | `/v1/usage` | Bearer | Monthly usage snapshot | | POST | `/v1/usage` | Bearer | Record usage (enforces limits) | diff --git a/api-gateway/src/routes/health.ts b/api-gateway/src/routes/health.ts index ca3d62a..512eea9 100644 --- a/api-gateway/src/routes/health.ts +++ b/api-gateway/src/routes/health.ts @@ -18,69 +18,73 @@ export const healthRoutes: FastifyPluginAsync = async (app) => { ts: new Date().toISOString(), })); - app.post("/v1/keepalive", async (request, reply) => { - reply - .header("Cache-Control", "no-store, max-age=0") - .header("Pragma", "no-cache") - .header("Expires", "0"); + app.route({ + method: ["GET", "POST"], + url: "/v1/keepalive", + handler: async (request, reply) => { + reply + .header("Cache-Control", "no-store, max-age=0") + .header("Pragma", "no-cache") + .header("Expires", "0"); - if (!env.KEEPALIVE_CRON_SECRET) { - request.log.error("keepalive endpoint called without KEEPALIVE_CRON_SECRET"); - return reply.status(503).send({ - ok: false, - error: "Keepalive endpoint is not configured", - requestId: request.id, - }); - } + if (!env.KEEPALIVE_CRON_SECRET) { + request.log.error("keepalive endpoint called without KEEPALIVE_CRON_SECRET"); + return reply.status(503).send({ + ok: false, + error: "Keepalive endpoint is not configured", + requestId: request.id, + }); + } - if ( - !hasValidKeepaliveAuthorization( - request.headers.authorization, - env.KEEPALIVE_CRON_SECRET, - ) - ) { - return reply.status(401).send({ - ok: false, - error: "Unauthorized", - requestId: request.id, - }); - } + if ( + !hasValidKeepaliveAuthorization( + request.headers.authorization, + env.KEEPALIVE_CRON_SECRET, + ) + ) { + return reply.status(401).send({ + ok: false, + error: "Unauthorized", + requestId: request.id, + }); + } - const startedAt = Date.now(); - const { data, error } = await getSupabase().rpc("toggle_keepalive_pulse"); - if (error) { - request.log.error( - { err: error, requestId: request.id }, - "database keepalive failed", - ); - return reply.status(503).send({ - ok: false, - error: "Database keepalive failed", - requestId: request.id, - }); - } + const startedAt = Date.now(); + const { data, error } = await getSupabase().rpc("toggle_keepalive_pulse"); + if (error) { + request.log.error( + { err: error, requestId: request.id }, + "database keepalive failed", + ); + return reply.status(503).send({ + ok: false, + error: "Database keepalive failed", + requestId: request.id, + }); + } - const result = Array.isArray(data) ? data[0] : data; - const action = result?.action; - if (action !== "inserted" && action !== "deleted") { - request.log.error( - { result, requestId: request.id }, - "database keepalive returned an invalid result", - ); - return reply.status(503).send({ - ok: false, - error: "Database keepalive returned an invalid result", - requestId: request.id, - }); - } + const result = Array.isArray(data) ? data[0] : data; + const action = result?.action; + if (action !== "inserted" && action !== "deleted") { + request.log.error( + { result, requestId: request.id }, + "database keepalive returned an invalid result", + ); + return reply.status(503).send({ + ok: false, + error: "Database keepalive returned an invalid result", + requestId: request.id, + }); + } - return reply.send({ - ok: true, - service: "api-gateway", - database: { action }, - latencyMs: Date.now() - startedAt, - ts: new Date().toISOString(), - }); + return reply.send({ + ok: true, + service: "api-gateway", + database: { action }, + latencyMs: Date.now() - startedAt, + ts: new Date().toISOString(), + }); + }, }); app.get("/ready", async (_request, reply) => { diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 9936833..08708a5 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -105,7 +105,7 @@ cron-job.org job with these settings: |-------|-------| | URL | `https://api.rontgenai.dev/v1/keepalive` | | Schedule | Every 13 minutes (`*/13 * * * *`) | -| Method | `POST` | +| Method | `GET` | | Header | `Authorization: Bearer ` | Use the same secret in cron-job.org and the Render web service, then redeploy @@ -116,7 +116,7 @@ between `database.action: "inserted"` and `database.action: "deleted"`: # Generate once, then save this value in both Render and cron-job.org. openssl rand -hex 32 -curl --fail --request POST \ +curl --fail \ --header "Authorization: Bearer $KEEPALIVE_CRON_SECRET" \ https://api.rontgenai.dev/v1/keepalive ```