Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
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.

graphql.tsBlame93 lines · 1 contributor
eae38d1Claude1/**
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
12import { Hono } from "hono";
13import { softAuth } from "../middleware/auth";
14import type { AuthEnv } from "../middleware/auth";
15import { execute } from "../lib/graphql";
16
17const graphql = new Hono<AuthEnv>();
18graphql.use("*", softAuth);
19
20graphql.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
36graphql.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, "&lt;")}</textarea>
65 <pre id="r">{ "hint": "Click Run" }</pre>
66</div>
67<script>
68async 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}
84document.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
93export default graphql;