From a8aad77aa961c3422d0483de03971fce2ea8a3c8 Mon Sep 17 00:00:00 2001 From: Roberto Guagliardo Date: Sun, 1 Feb 2026 00:27:40 +0000 Subject: [PATCH] feat: enhance repository handling to support multiple providers and add Gitea integration --- server.log | 1 + server/index.js | 162 ++++++++++++++++++++++++++++++++++++---------- server/repos.json | 8 ++- 3 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 server.log diff --git a/server.log b/server.log new file mode 100644 index 0000000..04e850b --- /dev/null +++ b/server.log @@ -0,0 +1 @@ +Server draait op http://:::3001 diff --git a/server/index.js b/server/index.js index 7a8128a..fcb313c 100644 --- a/server/index.js +++ b/server/index.js @@ -15,6 +15,21 @@ const reposFile = path.join(__dirname, "repos.json"); const cache = new Map(); +function parseRepoEntry(entry) { + // support legacy string entries and object entries + if (typeof entry === "string") { + return { + provider: "github", + ownerRepo: entry, + baseUrl: "https://github.com" + }; + } + const provider = (entry.provider || "github").toLowerCase(); + const ownerRepo = entry.repo || entry.ownerRepo || ""; + const baseUrl = entry.baseUrl || (provider === "github" ? "https://github.com" : entry.baseUrl || ""); + return { provider, ownerRepo, baseUrl }; +} + async function readRepos() { const content = await fs.readFile(reposFile, "utf-8"); const parsed = JSON.parse(content); @@ -35,29 +50,63 @@ function setCached(key, value) { cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS }); } -async function fetchJson(url, cacheKey) { +async function fetchJson(url, cacheKey, opts = {}) { const cached = getCached(cacheKey); if (cached) return cached; + const headers = { + "User-Agent": "siti-plugin-repo", + Accept: "application/json" + }; + // prefer GitHub API accept header when talking to github.com/api + if (!opts.provider || opts.provider === "github") { + headers.Accept = "application/vnd.github+json"; + } - const response = await fetch(url, { - headers: { - Accept: "application/vnd.github+json", - "User-Agent": "siti-plugin-repo" + const response = await fetch(url, { headers }); + if (!response.ok) { + throw new Error(`${opts.provider || "git"} request failed for ${url}`); + } + const data = await response.json().catch(async () => { + // attempt to read text for non-json responses + const text = await response.text(); + try { + return JSON.parse(text); + } catch { + return null; } }); - if (!response.ok) { - throw new Error(`GitHub request failed for ${url}`); - } - const data = await response.json(); setCached(cacheKey, data); return data; } -async function fetchRepo(ownerRepo) { - const cached = getCached(`repo:${ownerRepo}`); +async function fetchRepo(entry) { + const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); + const cacheKey = `repo:${provider}:${ownerRepo}`; + const cached = getCached(cacheKey); if (cached) return cached; - const data = await fetchJson(`https://api.github.com/repos/${ownerRepo}`, `repo-raw:${ownerRepo}`); + let data; + if (provider === "gitea") { + const url = `${baseUrl.replace(/\/$/, "")}/api/v1/repos/${ownerRepo}`; + data = await fetchJson(url, `repo-raw:${provider}:${ownerRepo}`, { provider }); + const mapped = { + fullName: data.full_name || `${ownerRepo}`, + name: data.name || ownerRepo.split("/")[1] || ownerRepo, + description: data.description || null, + repoUrl: `${baseUrl.replace(/\/$/, "")}/${ownerRepo}`, + defaultBranch: data.default_branch || data.default_branch || "main", + stars: data.stargazers_count || data.watchers || 0, + forks: data.forks_count || data.forks || 0, + issues: data.open_issues_count || 0, + updatedAt: data.updated_at || data.updated || null, + topics: data.topics || [] + }; + setCached(cacheKey, mapped); + return mapped; + } + + // default: github + data = await fetchJson(`https://api.github.com/repos/${ownerRepo}`, `repo-raw:github:${ownerRepo}`, { provider: "github" }); const mapped = { fullName: data.full_name, name: data.name, @@ -70,33 +119,54 @@ async function fetchRepo(ownerRepo) { updatedAt: data.updated_at, topics: data.topics || [] }; - setCached(`repo:${ownerRepo}`, mapped); + setCached(cacheKey, mapped); return mapped; } -async function fetchManifest(ownerRepo, defaultBranch) { - const cached = getCached(`manifest:${ownerRepo}`); +async function fetchManifest(entry, defaultBranch) { + const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); + const cacheKey = `manifest:${provider}:${ownerRepo}`; + const cached = getCached(cacheKey); if (cached) return cached; const branches = [defaultBranch, "main", "master"].filter(Boolean); + const [owner, repo] = ownerRepo.split("/"); for (const branch of branches) { - const url = `https://raw.githubusercontent.com/${ownerRepo}/${branch}/manifest.json`; - const response = await fetch(url, { - headers: { "User-Agent": "siti-plugin-repo" } - }); + let url; + if (provider === "gitea") { + url = `${baseUrl.replace(/\/$/, "")}/repos/${owner}/${repo}/raw/${branch}/manifest.json`; + } else { + url = `https://raw.githubusercontent.com/${ownerRepo}/${branch}/manifest.json`; + } + const response = await fetch(url, { headers: { "User-Agent": "siti-plugin-repo" } }); if (response.ok) { - const manifest = await response.json(); - setCached(`manifest:${ownerRepo}`, manifest); + const manifest = await response.json().catch(() => null); + setCached(cacheKey, manifest); return manifest; } } return null; } -async function fetchReleases(ownerRepo) { +async function fetchReleases(entry) { + const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); + if (provider === "gitea") { + const url = `${baseUrl.replace(/\/$/, "")}/api/v1/repos/${ownerRepo}/releases?limit=5`; + const data = await fetchJson(url, `releases:${provider}:${ownerRepo}`, { provider }); + return Array.isArray(data) + ? data.map((release) => ({ + tag: release.tag_name || release.name, + name: release.name || release.tag_name, + url: release.html_url || `${baseUrl.replace(/\/$/, "")}/${ownerRepo}/releases`, + publishedAt: release.published_at || release.created_at + })) + : []; + } + const data = await fetchJson( `https://api.github.com/repos/${ownerRepo}/releases?per_page=5`, - `releases:${ownerRepo}` + `releases:github:${ownerRepo}`, + { provider: "github" } ); return Array.isArray(data) ? data.map((release) => ({ @@ -108,10 +178,26 @@ async function fetchReleases(ownerRepo) { : []; } -async function fetchCommits(ownerRepo) { +async function fetchCommits(entry) { + const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); + if (provider === "gitea") { + const url = `${baseUrl.replace(/\/$/, "")}/api/v1/repos/${ownerRepo}/commits?limit=5`; + const data = await fetchJson(url, `commits:${provider}:${ownerRepo}`, { provider }); + return Array.isArray(data) + ? data.map((commit) => ({ + sha: commit.sha, + message: commit.commit?.message || commit.message, + author: commit.commit?.author?.name || commit.author?.name, + date: commit.commit?.author?.date || commit.author?.date, + url: commit.html_url || `${baseUrl.replace(/\/$/, "")}/${ownerRepo}/commit/${commit.sha}` + })) + : []; + } + const data = await fetchJson( `https://api.github.com/repos/${ownerRepo}/commits?per_page=5`, - `commits:${ownerRepo}` + `commits:github:${ownerRepo}`, + { provider: "github" } ); return Array.isArray(data) ? data.map((commit) => ({ @@ -131,14 +217,15 @@ app.get("/api/plugins", async (_req, res) => { repos.map(async (repo) => { try { const info = await fetchRepo(repo); - const manifest = await fetchManifest(repo, info.defaultBranch); + const manifest = await fetchManifest(repo, info.defaultBranch || info.default_branch); return { ...info, manifest }; } catch { + const parsed = parseRepoEntry(repo); return { - fullName: repo, - name: repo.split("/")[1] || repo, + fullName: parsed.ownerRepo, + name: parsed.ownerRepo.split("/")[1] || parsed.ownerRepo, description: "Kon gegevens niet ophalen.", - repoUrl: `https://github.com/${repo}`, + repoUrl: parsed.provider === "gitea" ? `${parsed.baseUrl.replace(/\/$/, "")}/${parsed.ownerRepo}` : `https://github.com/${parsed.ownerRepo}`, stars: 0, forks: 0, issues: 0, @@ -163,11 +250,15 @@ app.get("/api/plugins", async (_req, res) => { app.get("/api/plugins/:owner/:repo", async (req, res) => { const ownerRepo = `${req.params.owner}/${req.params.repo}`; try { - const info = await fetchRepo(ownerRepo); + const provider = (req.query.provider || "github").toLowerCase(); + const baseUrl = req.query.baseUrl || (provider === "github" ? "https://github.com" : ""); + const entry = provider === "github" ? ownerRepo : { provider, repo: ownerRepo, baseUrl }; + + const info = await fetchRepo(entry); const [manifest, releases, commits] = await Promise.all([ - fetchManifest(ownerRepo, info.defaultBranch).catch(() => null), - fetchReleases(ownerRepo).catch(() => []), - fetchCommits(ownerRepo).catch(() => []) + fetchManifest(entry, info.defaultBranch).catch(() => null), + fetchReleases(entry).catch(() => []), + fetchCommits(entry).catch(() => []) ]); res.json({ @@ -186,6 +277,7 @@ app.get("*", (_req, res) => { res.sendFile(path.join(distDir, "index.html")); }); -app.listen(PORT, () => { - console.log(`Server draait op http://localhost:${PORT}`); +const HOST = process.env.HOST || "::"; +app.listen(PORT, HOST, () => { + console.log(`Server draait op http://${HOST}:${PORT}`); }); diff --git a/server/repos.json b/server/repos.json index a3b0b72..776445f 100644 --- a/server/repos.json +++ b/server/repos.json @@ -1,5 +1,7 @@ [ - "SitiWeb/siti-ai-product-content-generator", - "SitiWeb/siti-stock-plugin", - "SitiWeb/SitiWeb-SelectCourier" + { + "provider": "gitea", + "repo": "roberto/siti-ai-product-content-generator", + "baseUrl": "https://git.robert.ooo" + } ] \ No newline at end of file