import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import path from "node:path";
import { createRepoSchema } from "@pijulab/shared";
import { createRepo, findRepoByOwnerUsername, findUserByUsername } from "../db/queries.js";
import { sessionMiddleware, requireAuth } from "../middleware/auth.js";
import { PijulRepo } from "../pijul/index.js";
import { env } from "../env.js";
function repoPath(ownerUsername: string, repoName: string): string {
return path.join(env.PIJULAB_REPOS_DIR, ownerUsername, repoName);
}
export const reposRouter = new Hono()
.use(sessionMiddleware)
.post("/", requireAuth, zValidator("json", createRepoSchema), async (c) => {
const user = c.get("user")!;
const { name, description, visibility } = c.req.valid("json");
const existing = await findRepoByOwnerUsername(user.username, name);
if (existing) return c.json({ message: "Repository already exists" }, 409);
const absPath = repoPath(user.username, name);
await PijulRepo.init(absPath);
const repoId = await createRepo({
ownerId: user.id,
name,
...(description !== undefined ? { description } : {}),
visibility,
});
return c.json({ id: repoId, name, description: description ?? null, visibility }, 201);
})
.get("/:owner/:name", async (c) => {
const { owner, name } = c.req.param();
const repo = await findRepoByOwnerUsername(owner, name);
if (!repo) return c.json({ message: "Not found" }, 404);
const currentUser = c.get("user");
if (repo.visibility === "private" && currentUser?.id !== repo.owner_id) {
return c.json({ message: "Not found" }, 404);
}
return c.json({
id: repo.id,
name: repo.name,
description: repo.description,
visibility: repo.visibility,
defaultChannel: repo.default_channel,
owner: { id: repo.owner_id, username: repo.owner_username, displayName: repo.owner_display_name },
createdAt: repo.created_at,
updatedAt: repo.updated_at,
});
})
.get("/:owner/:name/channels", async (c) => {
const { owner, name } = c.req.param();
const repo = await findRepoByOwnerUsername(owner, name);
if (!repo) return c.json({ message: "Not found" }, 404);
const pijul = PijulRepo.open(repoPath(owner, name));
const channels = await pijul.listChannels();
return c.json(channels);
})
.get("/:owner/:name/tree", async (c) => {
const { owner, name } = c.req.param();
const channel = c.req.query("channel") ?? "main";
const subpath = c.req.query("path") ?? "";
const repo = await findRepoByOwnerUsername(owner, name);
if (!repo) return c.json({ message: "Not found" }, 404);
const pijul = PijulRepo.open(repoPath(owner, name));
const tree = await pijul.listTree(channel, subpath);
return c.json(tree);
})
.get("/:owner/:name/file", async (c) => {
const { owner, name } = c.req.param();
const channel = c.req.query("channel") ?? "main";
const filePath = c.req.query("path") ?? "";
if (!filePath) return c.json({ message: "path is required" }, 400);
const repo = await findRepoByOwnerUsername(owner, name);
if (!repo) return c.json({ message: "Not found" }, 404);
const pijul = PijulRepo.open(repoPath(owner, name));
try {
const content = await pijul.readFile(channel, filePath);
return new Response(content, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
} catch {
return c.json({ message: "File not found" }, 404);
}
})
.get("/:owner/:name/log", async (c) => {
const { owner, name } = c.req.param();
const channel = c.req.query("channel") ?? "main";
const limit = Math.min(Number(c.req.query("limit") ?? 50), 200);
const repo = await findRepoByOwnerUsername(owner, name);
if (!repo) return c.json({ message: "Not found" }, 404);
const pijul = PijulRepo.open(repoPath(owner, name));
const changes = await pijul.log(channel, limit);
return c.json(changes);
})
.get("/:owner/:name/change/:hash", async (c) => {
const { owner, name, hash } = c.req.param();
const repo = await findRepoByOwnerUsername(owner, name);
if (!repo) return c.json({ message: "Not found" }, 404);
const pijul = PijulRepo.open(repoPath(owner, name));
const change = await pijul.getChange(hash);
if (!change) return c.json({ message: "Change not found" }, 404);
return c.json(change);
});