Files
siti-plugin-repo/server/index.js
Roberto Guagliardo 81d1d0910c feat: add react-router-dom for routing and implement plugin detail view
- Updated package.json to include react-router-dom dependency.
- Refactored App component to use React Router for navigation.
- Created PluginDetail component to display detailed information about a selected plugin.
- Added fetch functions for releases and commits in the server code.
- Enhanced UI with new styles for dark mode and improved layout.
- Implemented caching for API responses to optimize performance.
2026-01-31 19:48:44 +00:00

192 lines
5.7 KiB
JavaScript

import express from "express";
import path from "path";
import { fileURLToPath } from "url";
import fs from "fs/promises";
const app = express();
const PORT = process.env.PORT || 3001;
const CACHE_TTL_MS = Number(process.env.CACHE_TTL_MS || 10 * 60 * 1000);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const rootDir = path.resolve(__dirname, "..");
const distDir = path.join(rootDir, "dist");
const reposFile = path.join(__dirname, "repos.json");
const cache = new Map();
async function readRepos() {
const content = await fs.readFile(reposFile, "utf-8");
const parsed = JSON.parse(content);
return Array.isArray(parsed) ? parsed : [];
}
function getCached(key) {
const entry = cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
cache.delete(key);
return null;
}
return entry.value;
}
function setCached(key, value) {
cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
}
async function fetchJson(url, cacheKey) {
const cached = getCached(cacheKey);
if (cached) return cached;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github+json",
"User-Agent": "siti-plugin-repo"
}
});
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}`);
if (cached) return cached;
const data = await fetchJson(`https://api.github.com/repos/${ownerRepo}`, `repo-raw:${ownerRepo}`);
const mapped = {
fullName: data.full_name,
name: data.name,
description: data.description,
repoUrl: data.html_url,
defaultBranch: data.default_branch,
stars: data.stargazers_count,
forks: data.forks_count,
issues: data.open_issues_count,
updatedAt: data.updated_at,
topics: data.topics || []
};
setCached(`repo:${ownerRepo}`, mapped);
return mapped;
}
async function fetchManifest(ownerRepo, defaultBranch) {
const cached = getCached(`manifest:${ownerRepo}`);
if (cached) return cached;
const branches = [defaultBranch, "main", "master"].filter(Boolean);
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" }
});
if (response.ok) {
const manifest = await response.json();
setCached(`manifest:${ownerRepo}`, manifest);
return manifest;
}
}
return null;
}
async function fetchReleases(ownerRepo) {
const data = await fetchJson(
`https://api.github.com/repos/${ownerRepo}/releases?per_page=5`,
`releases:${ownerRepo}`
);
return Array.isArray(data)
? data.map((release) => ({
tag: release.tag_name,
name: release.name || release.tag_name,
url: release.html_url,
publishedAt: release.published_at
}))
: [];
}
async function fetchCommits(ownerRepo) {
const data = await fetchJson(
`https://api.github.com/repos/${ownerRepo}/commits?per_page=5`,
`commits:${ownerRepo}`
);
return Array.isArray(data)
? data.map((commit) => ({
sha: commit.sha,
message: commit.commit?.message,
author: commit.commit?.author?.name,
date: commit.commit?.author?.date,
url: commit.html_url
}))
: [];
}
app.get("/api/plugins", async (_req, res) => {
try {
const repos = await readRepos();
const results = await Promise.all(
repos.map(async (repo) => {
try {
const info = await fetchRepo(repo);
const manifest = await fetchManifest(repo, info.defaultBranch);
return { ...info, manifest };
} catch {
return {
fullName: repo,
name: repo.split("/")[1] || repo,
description: "Kon gegevens niet ophalen.",
repoUrl: `https://github.com/${repo}`,
stars: 0,
forks: 0,
issues: 0,
updatedAt: null,
topics: [],
manifest: null
};
}
})
);
res.json({
count: results.length,
updatedAt: new Date().toISOString(),
items: results
});
} catch (error) {
res.status(500).json({ error: "Kon plugins niet laden." });
}
});
app.get("/api/plugins/:owner/:repo", async (req, res) => {
const ownerRepo = `${req.params.owner}/${req.params.repo}`;
try {
const info = await fetchRepo(ownerRepo);
const [manifest, releases, commits] = await Promise.all([
fetchManifest(ownerRepo, info.defaultBranch).catch(() => null),
fetchReleases(ownerRepo).catch(() => []),
fetchCommits(ownerRepo).catch(() => [])
]);
res.json({
...info,
manifest,
releases,
commits
});
} catch (error) {
res.status(500).json({ error: "Kon plugin details niet laden." });
}
});
app.use(express.static(distDir));
app.get("*", (_req, res) => {
res.sendFile(path.join(distDir, "index.html"));
});
app.listen(PORT, () => {
console.log(`Server draait op http://localhost:${PORT}`);
});