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