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
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
graphql.ts3.1 KB · 93 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
 * Block G2 — GraphQL HTTP endpoint.
 *
 *   POST /api/graphql   — execute { query } against the schema in `lib/graphql`
 *   GET  /api/graphql   — minimal in-browser "GraphiQL-lite" explorer
 *
 * Auth: softAuth only — the schema is queries-only and every resolver enforces
 * visibility (only public repos surface for logged-out viewers). Writes live on
 * the REST + /api endpoints.
 */

import { Hono } from "hono";
import { softAuth } from "../middleware/auth";
import type { AuthEnv } from "../middleware/auth";
import { execute } from "../lib/graphql";

const graphql = new Hono<AuthEnv>();
graphql.use("*", softAuth);

graphql.post("/api/graphql", async (c) => {
  let body: { query?: string } = {};
  try {
    body = await c.req.json();
  } catch {
    return c.json({ errors: [{ message: "Invalid JSON body" }] }, 400);
  }
  const q = String(body.query || "");
  if (!q.trim()) {
    return c.json({ errors: [{ message: "query is required" }] }, 400);
  }
  const user = c.get("user") || null;
  const result = await execute(q, { user: user ? { id: user.id, username: user.username } : null });
  return c.json(result);
});

graphql.get("/api/graphql", (c) => {
  const sample = `query {
  viewer { id username email }
  search(q: "ai", limit: 5) { id name ownerUsername }
  rateLimit { remaining reset }
}`;
  const html = `<!doctype html>
<html><head><meta charset="UTF-8" /><title>Gluecron GraphQL</title>
<style>
  body { background:#0d1117; color:#e6edf3; font-family:system-ui,sans-serif; margin:0; padding:20px; }
  h1 { font-size:18px; margin:0 0 12px; }
  .layout { display:grid; grid-template-columns:1fr 1fr; gap:12px; height:85vh; }
  textarea, pre {
    background:#161b22; color:#e6edf3; border:1px solid #30363d; border-radius:6px;
    padding:12px; font-family:monospace; font-size:13px; width:100%; height:100%;
    box-sizing:border-box; resize:none;
  }
  pre { overflow:auto; white-space:pre-wrap; }
  button {
    background:#238636; color:#fff; border:0; border-radius:6px;
    padding:8px 16px; font-weight:600; cursor:pointer; margin-bottom:8px;
  }
  a { color:#58a6ff; }
</style>
</head><body>
<h1>gluecron · GraphQL <a href="/">home</a></h1>
<button onclick="run()">Run (Ctrl+Enter)</button>
<div class="layout">
  <textarea id="q" spellcheck="false">${sample.replace(/</g, "&lt;")}</textarea>
  <pre id="r">{ "hint": "Click Run" }</pre>
</div>
<script>
async function run(){
  const q = document.getElementById('q').value;
  document.getElementById('r').textContent = 'Loading…';
  try {
    const r = await fetch('/api/graphql', {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify({ query: q }),
      credentials:'include'
    });
    const j = await r.json();
    document.getElementById('r').textContent = JSON.stringify(j, null, 2);
  } catch (e) {
    document.getElementById('r').textContent = String(e);
  }
}
document.getElementById('q').addEventListener('keydown', (e) => {
  if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); run(); }
});
</script>
</body></html>`;
  c.header("content-type", "text/html; charset=utf-8");
  return c.body(html);
});

export default graphql;