feat: add GitHub service for PAT validation, forking, and PR management

This commit is contained in:
plenarius
2026-04-20 21:54:09 -04:00
parent 6e3abb0b07
commit f54816a622
6 changed files with 442 additions and 7 deletions
+116
View File
@@ -0,0 +1,116 @@
import { Router } from "express";
import { readConfig, writeConfig } from "../services/workspace.service.js";
import { REPOS } from "../config/repos.js";
import {
validatePat,
forkRepo,
listUserForks,
createPullRequest,
listPullRequests,
} from "../services/github.service.js";
const router = Router();
async function getPat(): Promise<string> {
const config = await readConfig();
const pat = config.githubPat;
if (!pat) throw new Error("GitHub PAT not configured");
return pat;
}
router.post("/validate-pat", async (req, res) => {
try {
const { pat } = req.body;
if (!pat) {
res.status(400).json({ error: "PAT is required" });
return;
}
const result = await validatePat(pat);
const config = await readConfig();
await writeConfig({ ...config, githubPat: pat });
res.json(result);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Invalid PAT";
res.status(401).json({ error: message });
}
});
router.post("/fork", async (req, res) => {
try {
const pat = await getPat();
const { repo: repoName } = req.body;
const repoDef = REPOS.find((r) => r.name === repoName);
if (!repoDef) {
res.status(400).json({ error: `Unknown repo: ${repoName}` });
return;
}
const [owner, repo] = repoDef.upstream.split("/");
const result = await forkRepo(pat, owner, repo);
res.json(result);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Fork failed";
res.status(500).json({ error: message });
}
});
router.get("/forks", async (_req, res) => {
try {
const pat = await getPat();
const forks = await listUserForks(pat);
res.json(forks);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Failed to list forks";
res.status(500).json({ error: message });
}
});
router.post("/pr", async (req, res) => {
try {
const pat = await getPat();
const { repo: repoName, title, body, headBranch } = req.body;
const repoDef = REPOS.find((r) => r.name === repoName);
if (!repoDef) {
res.status(400).json({ error: `Unknown repo: ${repoName}` });
return;
}
const { login } = await import("../services/github.service.js").then((m) =>
m.validatePat(pat),
);
const result = await createPullRequest(pat, {
upstream: repoDef.upstream,
repo: repoName,
title,
body,
head: `${login}:${headBranch}`,
base: repoDef.branch,
});
res.json(result);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "PR creation failed";
res.status(500).json({ error: message });
}
});
router.get("/prs/:repo", async (req, res) => {
try {
const pat = await getPat();
const repoName = req.params.repo;
const repoDef = REPOS.find((r) => r.name === repoName);
if (!repoDef) {
res.status(400).json({ error: `Unknown repo: ${repoName}` });
return;
}
const [owner, repo] = repoDef.upstream.split("/");
const prs = await listPullRequests(pat, owner, repo);
res.json(prs);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Failed to list PRs";
res.status(500).json({ error: message });
}
});
export default router;