Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

api.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.

api.tsBlame201 lines · 1 contributor
fc1817aClaude1/**
2 * REST API routes for repository management.
3 */
4
5import { Hono } from "hono";
6import { eq, and } from "drizzle-orm";
7import { db } from "../db";
8import { users, repositories } from "../db/schema";
9import { initBareRepo, repoExists } from "../git/repository";
06d5ffeClaude10import { hashPassword } from "../lib/auth";
fc1817aClaude11
12const api = new Hono().basePath("/api");
13
14// Create repository
15api.post("/repos", async (c) => {
63807e5Claude16 let body: {
fc1817aClaude17 name: string;
18 owner: string;
19 description?: string;
20 isPrivate?: boolean;
63807e5Claude21 };
22 try {
23 body = await c.req.json();
24 } catch {
25 return c.json({ error: "Invalid JSON body" }, 400);
26 }
fc1817aClaude27
28 if (!body.name || !body.owner) {
29 return c.json({ error: "name and owner are required" }, 400);
30 }
31
32 // Validate repo name
33 if (!/^[a-zA-Z0-9._-]+$/.test(body.name)) {
34 return c.json({ error: "Invalid repository name" }, 400);
35 }
36
63807e5Claude37 try {
38 // Find owner
39 const [owner] = await db
40 .select()
41 .from(users)
42 .where(eq(users.username, body.owner));
fc1817aClaude43
63807e5Claude44 if (!owner) {
45 return c.json({ error: "Owner not found" }, 404);
46 }
fc1817aClaude47
63807e5Claude48 // Check duplicate
49 if (await repoExists(body.owner, body.name)) {
50 return c.json({ error: "Repository already exists" }, 409);
51 }
fc1817aClaude52
63807e5Claude53 // Init bare repo on disk
54 const diskPath = await initBareRepo(body.owner, body.name);
55
56 // Insert into DB
57 const [repo] = await db
58 .insert(repositories)
59 .values({
60 name: body.name,
61 ownerId: owner.id,
62 description: body.description || null,
63 isPrivate: body.isPrivate || false,
64 diskPath,
65 })
66 .returning();
3ef4c9dClaude67
63807e5Claude68 // Green-ecosystem bootstrap: settings, protection, labels, welcome issue
69 if (repo) {
70 const { bootstrapRepository } = await import("../lib/repo-bootstrap");
71 await bootstrapRepository({
72 repositoryId: repo.id,
73 ownerUserId: owner.id,
74 });
75 }
76
77 return c.json(repo, 201);
78 } catch (err) {
79 console.error("[api] POST /repos:", err);
80 return c.json({ error: "Service unavailable" }, 503);
81 }
fc1817aClaude82});
83
84// List user's repositories
85api.get("/users/:username/repos", async (c) => {
86 const { username } = c.req.param();
63807e5Claude87 try {
88 const [owner] = await db
89 .select()
90 .from(users)
91 .where(eq(users.username, username));
92 if (!owner) return c.json({ error: "User not found" }, 404);
93
94 const repos = await db
95 .select()
96 .from(repositories)
97 .where(eq(repositories.ownerId, owner.id));
98
99 return c.json(repos);
100 } catch (err) {
101 console.error("[api] /users/:username/repos:", err);
102 return c.json({ error: "Service unavailable" }, 503);
103 }
fc1817aClaude104});
105
106// Get single repository
107api.get("/repos/:owner/:name", async (c) => {
108 const { owner: ownerName, name } = c.req.param();
63807e5Claude109 try {
110 const [owner] = await db
111 .select()
112 .from(users)
113 .where(eq(users.username, ownerName));
114 if (!owner) return c.json({ error: "Not found" }, 404);
fc1817aClaude115
63807e5Claude116 const [repo] = await db
117 .select()
118 .from(repositories)
119 .where(
120 and(eq(repositories.ownerId, owner.id), eq(repositories.name, name))
121 );
122 if (!repo) return c.json({ error: "Not found" }, 404);
123
124 return c.json(repo);
125 } catch (err) {
126 console.error("[api] /repos/:owner/:name:", err);
127 return c.json({ error: "Service unavailable" }, 503);
128 }
fc1817aClaude129});
130
131// Quick-setup: create user + repo in one call (dev convenience)
132api.post("/setup", async (c) => {
63807e5Claude133 let body: {
fc1817aClaude134 username: string;
135 email: string;
136 repoName: string;
137 description?: string;
63807e5Claude138 };
139 try {
140 body = await c.req.json();
141 } catch {
142 return c.json({ error: "Invalid JSON body" }, 400);
143 }
144 if (!body.username || !body.email || !body.repoName) {
145 return c.json(
146 { error: "username, email, and repoName are required" },
147 400
148 );
fc1817aClaude149 }
150
63807e5Claude151 try {
152 // Upsert user
153 let [user] = await db
154 .select()
155 .from(users)
156 .where(eq(users.username, body.username));
157
158 if (!user) {
159 [user] = await db
160 .insert(users)
161 .values({
162 username: body.username,
163 email: body.email,
164 passwordHash: await hashPassword("changeme"),
165 })
166 .returning();
167 }
168
169 // Create repo if not exists
170 if (!(await repoExists(body.username, body.repoName))) {
171 const diskPath = await initBareRepo(body.username, body.repoName);
172 const [repo] = await db
173 .insert(repositories)
174 .values({
175 name: body.repoName,
176 ownerId: user.id,
177 description: body.description || null,
178 diskPath,
179 })
180 .returning();
181 if (repo) {
182 const { bootstrapRepository } = await import("../lib/repo-bootstrap");
183 await bootstrapRepository({
184 repositoryId: repo.id,
185 ownerUserId: user.id,
186 });
187 }
3ef4c9dClaude188 }
fc1817aClaude189
63807e5Claude190 return c.json({
191 user: user.username,
192 repo: body.repoName,
193 status: "ready",
194 });
195 } catch (err) {
196 console.error("[api] POST /setup:", err);
197 return c.json({ error: "Service unavailable" }, 503);
198 }
fc1817aClaude199});
200
201export default api;