From 21b85acff584e95d5d46b108173d379cf7e7d7a1 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Fri, 25 Sep 2026 14:46:31 -0700 Subject: [PATCH] Refactor: Move SurrealDB init to /_setup endpoint Co-Authored-By: Claude Opus 5.5 --- README.md | 2 ++ functions/[[path]].js | 44 ++++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 266c944..c7e5989 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,8 @@ Using atomic database transactions over HTTP to track per-IP/per-day search freq **Note:** This implementation is tested and verified for **SurrealDB v2.3.10**. +After deploying, visit `https:///_setup` once. It creates the `direct_img` namespace, `rate_limit` database, `rate` table and an `updated_at` index (used by the cleanup query). It's idempotent, so visiting again is harmless, and it returns `{"ok":true}` on success. + --- ## Stack diff --git a/functions/[[path]].js b/functions/[[path]].js index 64499ac..31afc9e 100644 --- a/functions/[[path]].js +++ b/functions/[[path]].js @@ -12,6 +12,8 @@ export async function onRequest(context) { return env.ASSETS.fetch(request); } + if (path === "_setup") return setupSurreal(env); + const rawQueryPart = url.pathname.slice(1).replace(/\/+$/, ""); if (rawQueryPart.includes(".") || rawQueryPart.includes("/")) { return env.ASSETS.fetch(new Request(new URL("/bad.webp", url.origin))); @@ -49,22 +51,6 @@ export async function onRequest(context) { "surreal-db": "rate_limit", }; - try { - const initSql = `DEFINE NAMESPACE IF NOT EXISTS direct_img; USE NS direct_img; DEFINE DATABASE IF NOT EXISTS rate_limit;`; - await fetch(`${env.SURREAL_URL}/sql`, { - method: "POST", - headers: { ...surrealHeaders, "surreal-ns": "direct_img", "surreal-db": "rate_limit" }, - body: initSql - }); - } catch (err) { - context.waitUntil(notify(env, { - title: "SurrealDB Init Error", - message: `Failed to init NS/DB: ${err.message}`, - tags: "warning", - priority: 4 - })); - } - const sql = `UPSERT rate:\`${rateId}\` SET count = IF count IS NONE THEN 1 ELSE count + 1 END, updated_at = time::now() RETURN count;`; try { @@ -191,7 +177,31 @@ export async function onRequest(context) { return new Response(imgBuffer, { headers: imageHeaders(finalContentType, TTL_SECONDS * 1000) }); } -async function notify(env, { title, message, tags, priority }) { +async function setupSurreal(env) { + if (!env.SURREAL_URL || !env.SURREAL_USER || !env.SURREAL_PASS) return jsonResponse(500, { error: "SurrealDB env vars missing" }); + const sql = `DEFINE NAMESPACE IF NOT EXISTS direct_img; +USE NS direct_img; +DEFINE DATABASE IF NOT EXISTS rate_limit; +USE DB rate_limit; +DEFINE TABLE IF NOT EXISTS rate SCHEMALESS; +DEFINE INDEX IF NOT EXISTS rate_updated_at ON rate FIELDS updated_at;`; + try { + const res = await fetch(`${env.SURREAL_URL}/sql`, { + method: "POST", + headers: { "Accept": "application/json", "Authorization": `Basic ${btoa(`${env.SURREAL_USER}:${env.SURREAL_PASS}`)}` }, + body: sql + }); + const text = await res.text(); + let body; + try { body = JSON.parse(text); } catch { body = text.slice(0, 1000); } + const ok = res.ok && Array.isArray(body) && body.every(r => r.status === "OK"); + return jsonResponse(ok ? 200 : 502, { ok, results: body }); + } catch (err) { + return jsonResponse(502, { ok: false, error: err.message }); + } +} + +async function notify(env,{ title, message, tags, priority }) { if (!env.NTFY_URL) return; const endpoint = env.NTFY_URL.startsWith("http") ? env.NTFY_URL : `https://${env.NTFY_URL}`; try {