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

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.tsBlame141 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";
10
11const api = new Hono().basePath("/api");
12
13// Create repository
14api.post("/repos", async (c) => {
15 const body = await c.req.json<{
16 name: string;
17 owner: string;
18 description?: string;
19 isPrivate?: boolean;
20 }>();
21
22 if (!body.name || !body.owner) {
23 return c.json({ error: "name and owner are required" }, 400);
24 }
25
26 // Validate repo name
27 if (!/^[a-zA-Z0-9._-]+$/.test(body.name)) {
28 return c.json({ error: "Invalid repository name" }, 400);
29 }
30
31 // Find owner
32 const [owner] = await db
33 .select()
34 .from(users)
35 .where(eq(users.username, body.owner));
36
37 if (!owner) {
38 return c.json({ error: "Owner not found" }, 404);
39 }
40
41 // Check duplicate
42 if (await repoExists(body.owner, body.name)) {
43 return c.json({ error: "Repository already exists" }, 409);
44 }
45
46 // Init bare repo on disk
47 const diskPath = await initBareRepo(body.owner, body.name);
48
49 // Insert into DB
50 const [repo] = await db
51 .insert(repositories)
52 .values({
53 name: body.name,
54 ownerId: owner.id,
55 description: body.description || null,
56 isPrivate: body.isPrivate || false,
57 diskPath,
58 })
59 .returning();
60
61 return c.json(repo, 201);
62});
63
64// List user's repositories
65api.get("/users/:username/repos", async (c) => {
66 const { username } = c.req.param();
67 const [owner] = await db
68 .select()
69 .from(users)
70 .where(eq(users.username, username));
71 if (!owner) return c.json({ error: "User not found" }, 404);
72
73 const repos = await db
74 .select()
75 .from(repositories)
76 .where(eq(repositories.ownerId, owner.id));
77
78 return c.json(repos);
79});
80
81// Get single repository
82api.get("/repos/:owner/:name", async (c) => {
83 const { owner: ownerName, name } = c.req.param();
84 const [owner] = await db
85 .select()
86 .from(users)
87 .where(eq(users.username, ownerName));
88 if (!owner) return c.json({ error: "Not found" }, 404);
89
90 const [repo] = await db
91 .select()
92 .from(repositories)
93 .where(
94 and(eq(repositories.ownerId, owner.id), eq(repositories.name, name))
95 );
96 if (!repo) return c.json({ error: "Not found" }, 404);
97
98 return c.json(repo);
99});
100
101// Quick-setup: create user + repo in one call (dev convenience)
102api.post("/setup", async (c) => {
103 const body = await c.req.json<{
104 username: string;
105 email: string;
106 repoName: string;
107 description?: string;
108 }>();
109
110 // Upsert user
111 let [user] = await db
112 .select()
113 .from(users)
114 .where(eq(users.username, body.username));
115
116 if (!user) {
117 [user] = await db
118 .insert(users)
119 .values({
120 username: body.username,
121 email: body.email,
122 passwordHash: "placeholder", // TODO: real auth
123 })
124 .returning();
125 }
126
127 // Create repo if not exists
128 if (!(await repoExists(body.username, body.repoName))) {
129 const diskPath = await initBareRepo(body.username, body.repoName);
130 await db.insert(repositories).values({
131 name: body.repoName,
132 ownerId: user.id,
133 description: body.description || null,
134 diskPath,
135 });
136 }
137
138 return c.json({ user: user.username, repo: body.repoName, status: "ready" });
139});
140
141export default api;