CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
graphql.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| eae38d1 | 1 | /** |
| 2 | * Block G2 — GraphQL HTTP endpoint. | |
| 3 | * | |
| 4 | * POST /api/graphql — execute { query } against the schema in `lib/graphql` | |
| 5 | * GET /api/graphql — minimal in-browser "GraphiQL-lite" explorer | |
| 6 | * | |
| 7 | * Auth: softAuth only — the schema is queries-only and every resolver enforces | |
| 8 | * visibility (only public repos surface for logged-out viewers). Writes live on | |
| 9 | * the REST + /api endpoints. | |
| 10 | */ | |
| 11 | ||
| 12 | import { Hono } from "hono"; | |
| 13 | import { softAuth } from "../middleware/auth"; | |
| 14 | import type { AuthEnv } from "../middleware/auth"; | |
| 15 | import { execute } from "../lib/graphql"; | |
| 16 | ||
| 17 | const graphql = new Hono<AuthEnv>(); | |
| 18 | graphql.use("*", softAuth); | |
| 19 | ||
| 20 | graphql.post("/api/graphql", async (c) => { | |
| 21 | let body: { query?: string } = {}; | |
| 22 | try { | |
| 23 | body = await c.req.json(); | |
| 24 | } catch { | |
| 25 | return c.json({ errors: [{ message: "Invalid JSON body" }] }, 400); | |
| 26 | } | |
| 27 | const q = String(body.query || ""); | |
| 28 | if (!q.trim()) { | |
| 29 | return c.json({ errors: [{ message: "query is required" }] }, 400); | |
| 30 | } | |
| 31 | const user = c.get("user") || null; | |
| 32 | const result = await execute(q, { user: user ? { id: user.id, username: user.username } : null }); | |
| 33 | return c.json(result); | |
| 34 | }); | |
| 35 | ||
| 36 | graphql.get("/api/graphql", (c) => { | |
| 37 | const sample = `query { | |
| 38 | viewer { id username email } | |
| 39 | search(q: "ai", limit: 5) { id name ownerUsername } | |
| 40 | rateLimit { remaining reset } | |
| 41 | }`; | |
| 42 | const html = `<!doctype html> | |
| 43 | <html><head><meta charset="UTF-8" /><title>Gluecron GraphQL</title> | |
| 44 | <style> | |
| 45 | body { background:#0d1117; color:#e6edf3; font-family:system-ui,sans-serif; margin:0; padding:20px; } | |
| 46 | h1 { font-size:18px; margin:0 0 12px; } | |
| 47 | .layout { display:grid; grid-template-columns:1fr 1fr; gap:12px; height:85vh; } | |
| 48 | textarea, pre { | |
| 49 | background:#161b22; color:#e6edf3; border:1px solid #30363d; border-radius:6px; | |
| 50 | padding:12px; font-family:monospace; font-size:13px; width:100%; height:100%; | |
| 51 | box-sizing:border-box; resize:none; | |
| 52 | } | |
| 53 | pre { overflow:auto; white-space:pre-wrap; } | |
| 54 | button { | |
| 55 | background:#238636; color:#fff; border:0; border-radius:6px; | |
| 56 | padding:8px 16px; font-weight:600; cursor:pointer; margin-bottom:8px; | |
| 57 | } | |
| 58 | a { color:#58a6ff; } | |
| 59 | </style> | |
| 60 | </head><body> | |
| 61 | <h1>gluecron · GraphQL <a href="/">home</a></h1> | |
| 62 | <button onclick="run()">Run (Ctrl+Enter)</button> | |
| 63 | <div class="layout"> | |
| 64 | <textarea id="q" spellcheck="false">${sample.replace(/</g, "<")}</textarea> | |
| 65 | <pre id="r">{ "hint": "Click Run" }</pre> | |
| 66 | </div> | |
| 67 | <script> | |
| 68 | async function run(){ | |
| 69 | const q = document.getElementById('q').value; | |
| 70 | document.getElementById('r').textContent = 'Loading…'; | |
| 71 | try { | |
| 72 | const r = await fetch('/api/graphql', { | |
| 73 | method:'POST', | |
| 74 | headers:{'content-type':'application/json'}, | |
| 75 | body: JSON.stringify({ query: q }), | |
| 76 | credentials:'include' | |
| 77 | }); | |
| 78 | const j = await r.json(); | |
| 79 | document.getElementById('r').textContent = JSON.stringify(j, null, 2); | |
| 80 | } catch (e) { | |
| 81 | document.getElementById('r').textContent = String(e); | |
| 82 | } | |
| 83 | } | |
| 84 | document.getElementById('q').addEventListener('keydown', (e) => { | |
| 85 | if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); run(); } | |
| 86 | }); | |
| 87 | </script> | |
| 88 | </body></html>`; | |
| 89 | c.header("content-type", "text/html; charset=utf-8"); | |
| 90 | return c.body(html); | |
| 91 | }); | |
| 92 | ||
| 93 | export default graphql; |