feat: enhance repository handling to support multiple providers and add Gitea integration
This commit is contained in:
1
server.log
Normal file
1
server.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Server draait op http://:::3001
|
||||||
162
server/index.js
162
server/index.js
@@ -15,6 +15,21 @@ const reposFile = path.join(__dirname, "repos.json");
|
|||||||
|
|
||||||
const cache = new Map();
|
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() {
|
async function readRepos() {
|
||||||
const content = await fs.readFile(reposFile, "utf-8");
|
const content = await fs.readFile(reposFile, "utf-8");
|
||||||
const parsed = JSON.parse(content);
|
const parsed = JSON.parse(content);
|
||||||
@@ -35,29 +50,63 @@ function setCached(key, value) {
|
|||||||
cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
|
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);
|
const cached = getCached(cacheKey);
|
||||||
if (cached) return cached;
|
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, {
|
const response = await fetch(url, { headers });
|
||||||
headers: {
|
if (!response.ok) {
|
||||||
Accept: "application/vnd.github+json",
|
throw new Error(`${opts.provider || "git"} request failed for ${url}`);
|
||||||
"User-Agent": "siti-plugin-repo"
|
}
|
||||||
|
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);
|
setCached(cacheKey, data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRepo(ownerRepo) {
|
async function fetchRepo(entry) {
|
||||||
const cached = getCached(`repo:${ownerRepo}`);
|
const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry);
|
||||||
|
const cacheKey = `repo:${provider}:${ownerRepo}`;
|
||||||
|
const cached = getCached(cacheKey);
|
||||||
if (cached) return cached;
|
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 = {
|
const mapped = {
|
||||||
fullName: data.full_name,
|
fullName: data.full_name,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@@ -70,33 +119,54 @@ async function fetchRepo(ownerRepo) {
|
|||||||
updatedAt: data.updated_at,
|
updatedAt: data.updated_at,
|
||||||
topics: data.topics || []
|
topics: data.topics || []
|
||||||
};
|
};
|
||||||
setCached(`repo:${ownerRepo}`, mapped);
|
setCached(cacheKey, mapped);
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchManifest(ownerRepo, defaultBranch) {
|
async function fetchManifest(entry, defaultBranch) {
|
||||||
const cached = getCached(`manifest:${ownerRepo}`);
|
const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry);
|
||||||
|
const cacheKey = `manifest:${provider}:${ownerRepo}`;
|
||||||
|
const cached = getCached(cacheKey);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
const branches = [defaultBranch, "main", "master"].filter(Boolean);
|
const branches = [defaultBranch, "main", "master"].filter(Boolean);
|
||||||
|
const [owner, repo] = ownerRepo.split("/");
|
||||||
for (const branch of branches) {
|
for (const branch of branches) {
|
||||||
const url = `https://raw.githubusercontent.com/${ownerRepo}/${branch}/manifest.json`;
|
let url;
|
||||||
const response = await fetch(url, {
|
if (provider === "gitea") {
|
||||||
headers: { "User-Agent": "siti-plugin-repo" }
|
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) {
|
if (response.ok) {
|
||||||
const manifest = await response.json();
|
const manifest = await response.json().catch(() => null);
|
||||||
setCached(`manifest:${ownerRepo}`, manifest);
|
setCached(cacheKey, manifest);
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
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(
|
const data = await fetchJson(
|
||||||
`https://api.github.com/repos/${ownerRepo}/releases?per_page=5`,
|
`https://api.github.com/repos/${ownerRepo}/releases?per_page=5`,
|
||||||
`releases:${ownerRepo}`
|
`releases:github:${ownerRepo}`,
|
||||||
|
{ provider: "github" }
|
||||||
);
|
);
|
||||||
return Array.isArray(data)
|
return Array.isArray(data)
|
||||||
? data.map((release) => ({
|
? 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(
|
const data = await fetchJson(
|
||||||
`https://api.github.com/repos/${ownerRepo}/commits?per_page=5`,
|
`https://api.github.com/repos/${ownerRepo}/commits?per_page=5`,
|
||||||
`commits:${ownerRepo}`
|
`commits:github:${ownerRepo}`,
|
||||||
|
{ provider: "github" }
|
||||||
);
|
);
|
||||||
return Array.isArray(data)
|
return Array.isArray(data)
|
||||||
? data.map((commit) => ({
|
? data.map((commit) => ({
|
||||||
@@ -131,14 +217,15 @@ app.get("/api/plugins", async (_req, res) => {
|
|||||||
repos.map(async (repo) => {
|
repos.map(async (repo) => {
|
||||||
try {
|
try {
|
||||||
const info = await fetchRepo(repo);
|
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 };
|
return { ...info, manifest };
|
||||||
} catch {
|
} catch {
|
||||||
|
const parsed = parseRepoEntry(repo);
|
||||||
return {
|
return {
|
||||||
fullName: repo,
|
fullName: parsed.ownerRepo,
|
||||||
name: repo.split("/")[1] || repo,
|
name: parsed.ownerRepo.split("/")[1] || parsed.ownerRepo,
|
||||||
description: "Kon gegevens niet ophalen.",
|
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,
|
stars: 0,
|
||||||
forks: 0,
|
forks: 0,
|
||||||
issues: 0,
|
issues: 0,
|
||||||
@@ -163,11 +250,15 @@ app.get("/api/plugins", async (_req, res) => {
|
|||||||
app.get("/api/plugins/:owner/:repo", async (req, res) => {
|
app.get("/api/plugins/:owner/:repo", async (req, res) => {
|
||||||
const ownerRepo = `${req.params.owner}/${req.params.repo}`;
|
const ownerRepo = `${req.params.owner}/${req.params.repo}`;
|
||||||
try {
|
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([
|
const [manifest, releases, commits] = await Promise.all([
|
||||||
fetchManifest(ownerRepo, info.defaultBranch).catch(() => null),
|
fetchManifest(entry, info.defaultBranch).catch(() => null),
|
||||||
fetchReleases(ownerRepo).catch(() => []),
|
fetchReleases(entry).catch(() => []),
|
||||||
fetchCommits(ownerRepo).catch(() => [])
|
fetchCommits(entry).catch(() => [])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -186,6 +277,7 @@ app.get("*", (_req, res) => {
|
|||||||
res.sendFile(path.join(distDir, "index.html"));
|
res.sendFile(path.join(distDir, "index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
const HOST = process.env.HOST || "::";
|
||||||
console.log(`Server draait op http://localhost:${PORT}`);
|
app.listen(PORT, HOST, () => {
|
||||||
|
console.log(`Server draait op http://${HOST}:${PORT}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
[
|
[
|
||||||
"SitiWeb/siti-ai-product-content-generator",
|
{
|
||||||
"SitiWeb/siti-stock-plugin",
|
"provider": "gitea",
|
||||||
"SitiWeb/SitiWeb-SelectCourier"
|
"repo": "roberto/siti-ai-product-content-generator",
|
||||||
|
"baseUrl": "https://git.robert.ooo"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
Reference in New Issue
Block a user