CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | /**
* REST API routes for repository management.
*/
import { Hono } from "hono";
import { eq, and } from "drizzle-orm";
import { db } from "../db";
import { users, repositories } from "../db/schema";
import { initBareRepo, repoExists } from "../git/repository";
const api = new Hono().basePath("/api");
// Create repository
api.post("/repos", async (c) => {
const body = await c.req.json<{
name: string;
owner: string;
description?: string;
isPrivate?: boolean;
}>();
if (!body.name || !body.owner) {
return c.json({ error: "name and owner are required" }, 400);
}
// Validate repo name
if (!/^[a-zA-Z0-9._-]+$/.test(body.name)) {
return c.json({ error: "Invalid repository name" }, 400);
}
// Find owner
const [owner] = await db
.select()
.from(users)
.where(eq(users.username, body.owner));
if (!owner) {
return c.json({ error: "Owner not found" }, 404);
}
// Check duplicate
if (await repoExists(body.owner, body.name)) {
return c.json({ error: "Repository already exists" }, 409);
}
// Init bare repo on disk
const diskPath = await initBareRepo(body.owner, body.name);
// Insert into DB
const [repo] = await db
.insert(repositories)
.values({
name: body.name,
ownerId: owner.id,
description: body.description || null,
isPrivate: body.isPrivate || false,
diskPath,
})
.returning();
return c.json(repo, 201);
});
// List user's repositories
api.get("/users/:username/repos", async (c) => {
const { username } = c.req.param();
const [owner] = await db
.select()
.from(users)
.where(eq(users.username, username));
if (!owner) return c.json({ error: "User not found" }, 404);
const repos = await db
.select()
.from(repositories)
.where(eq(repositories.ownerId, owner.id));
return c.json(repos);
});
// Get single repository
api.get("/repos/:owner/:name", async (c) => {
const { owner: ownerName, name } = c.req.param();
const [owner] = await db
.select()
.from(users)
.where(eq(users.username, ownerName));
if (!owner) return c.json({ error: "Not found" }, 404);
const [repo] = await db
.select()
.from(repositories)
.where(
and(eq(repositories.ownerId, owner.id), eq(repositories.name, name))
);
if (!repo) return c.json({ error: "Not found" }, 404);
return c.json(repo);
});
// Quick-setup: create user + repo in one call (dev convenience)
api.post("/setup", async (c) => {
const body = await c.req.json<{
username: string;
email: string;
repoName: string;
description?: string;
}>();
// Upsert user
let [user] = await db
.select()
.from(users)
.where(eq(users.username, body.username));
if (!user) {
[user] = await db
.insert(users)
.values({
username: body.username,
email: body.email,
passwordHash: "placeholder", // TODO: real auth
})
.returning();
}
// Create repo if not exists
if (!(await repoExists(body.username, body.repoName))) {
const diskPath = await initBareRepo(body.username, body.repoName);
await db.insert(repositories).values({
name: body.repoName,
ownerId: user.id,
description: body.description || null,
diskPath,
});
}
return c.json({ user: user.username, repo: body.repoName, status: "ready" });
});
export default api;
|