Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-02-01 15:43:35 +00:00
parent 3cf7e76d21
commit d306267d58
16 changed files with 286 additions and 42 deletions

View File

@@ -1,3 +1,4 @@
import "dotenv/config";
import express from "express";
import path from "path";
import { Readable } from "node:stream";
@@ -328,7 +329,8 @@ app.get("/api/plugins", async (_req, res) => {
ownerRepo: repo.ownerRepo,
baseUrl: repo.baseUrl,
repoId: repo.id,
label: repo.label
label: repo.label,
isPrivate: false
};
}
})
@@ -375,23 +377,77 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
repoRecord = await findRepoByOwnerRepo(ownerRepo);
}
const provider = (req.query.provider || repoRecord?.provider || "github").toLowerCase();
const baseUrlQuery =
req.query.baseUrl || repoRecord?.baseUrl || (provider === "github" ? "https://github.com" : "");
let provider = (req.query.provider || repoRecord?.provider || "github").toLowerCase();
if (repoRecord?.provider) {
provider = repoRecord.provider;
}
const baseUrlFallback = provider === "github" ? "https://github.com" : "";
let baseUrlQuery = req.query.baseUrl || repoRecord?.baseUrl || baseUrlFallback;
if (repoRecord?.baseUrl) {
baseUrlQuery = repoRecord.baseUrl;
}
const entry =
repoRecord && repoRecord.ownerRepo
? { provider: repoRecord.provider, repo: repoRecord.ownerRepo, baseUrl: repoRecord.baseUrl }
? {
provider: repoRecord.provider,
repo: repoRecord.ownerRepo,
baseUrl: repoRecord.baseUrl || baseUrlQuery || undefined
}
: provider === "github"
? ownerRepo
: { provider, repo: ownerRepo, baseUrl: baseUrlQuery };
const normalizedEntry = normalizeRepoInput(entry, { repo: ownerRepo, provider, baseUrl: baseUrlQuery });
const normalizedEntry =
normalizeRepoInput(entry, { repo: ownerRepo, provider, baseUrl: baseUrlQuery }) ||
null;
const info = await fetchRepo(entry);
const [manifest, releases, commits] = await Promise.all([
fetchManifest(entry, info.defaultBranch).catch(() => null),
fetchReleases(entry).catch(() => []),
fetchCommits(entry).catch(() => [])
]);
if (!normalizedEntry) {
return res.status(400).json({ error: "Ongeldige repository." });
}
let info;
let manifest = null;
let releases = [];
let commits = [];
try {
info = await fetchRepo(normalizedEntry);
[manifest, releases, commits] = await Promise.all([
fetchManifest(normalizedEntry, info.defaultBranch).catch(() => null),
fetchReleases(normalizedEntry).catch(() => []),
fetchCommits(normalizedEntry).catch(() => [])
]);
} catch (fetchError) {
console.warn("Kon plugin info niet ophalen:", fetchError.message);
const parsed = parseRepoEntry(normalizedEntry);
const fallbackRepo = {
provider: repoRecord?.provider || parsed.provider,
baseUrl: repoRecord?.baseUrl || parsed.baseUrl,
repo: repoRecord?.ownerRepo || parsed.ownerRepo
};
const repoUrl = buildRepoUrl(fallbackRepo);
const name = repoRecord?.label || parsed.ownerRepo.split("/")[1] || parsed.ownerRepo;
return res.json({
fullName: fallbackRepo.repo,
name,
description: repoRecord?.label ? `Plugin: ${repoRecord.label}` : "Kon gegevens niet ophalen.",
repoUrl,
provider: fallbackRepo.provider,
ownerRepo: fallbackRepo.repo,
baseUrl: fallbackRepo.baseUrl,
repoId: repoRecord?.id || null,
label: repoRecord?.label || null,
manifest: null,
releases: [],
commits: [],
download: null,
version: null,
versionSource: null,
isPrivate: false
});
}
const isPrivate = Boolean(info?.isPrivate);
const allowDownloads = normalizedEntry && !isPrivate;
const effectiveBaseUrl =
normalizedEntry?.baseUrl ||
@@ -400,7 +456,7 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
(provider === "github" ? "https://github.com" : "");
const releasesWithDownload =
normalizedEntry && releases.length > 0
allowDownloads && releases.length > 0
? releases.map((release) => {
const tagOrName = release.tag || release.name;
if (!tagOrName) {
@@ -420,7 +476,7 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
: releases;
let downloadMeta = null;
if (normalizedEntry) {
if (allowDownloads) {
const latestRelease = releasesWithDownload[0];
const sourceType = latestRelease ? "release" : "branch";
const version = latestRelease?.tag || latestRelease?.name || info.defaultBranch || "main";
@@ -454,6 +510,7 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
manifest,
releases: releasesWithDownload,
download: downloadMeta,
isPrivate,
commits
});
} catch (error) {
@@ -502,6 +559,9 @@ app.get("/api/plugins/:owner/:repo/download", async (req, res) => {
});
stream.pipe(res);
} catch (error) {
if (error?.meta === "PRIVATE_REPO") {
return res.status(403).json({ error: "Downloads voor private plugins zijn uitgeschakeld." });
}
console.error("Plugin download endpoint error:", error);
if (!res.headersSent) {
res.status(500).json({ error: "Download mislukt." });
@@ -543,20 +603,34 @@ app.post("/api/licenses", requireAuth, async (req, res) => {
if (body.repoId && Number.isNaN(repoId)) {
return res.status(400).json({ error: "Ongeldig repo id." });
}
let repoRecord = null;
if (repoId) {
repoRecord = await getRepoById(repoId);
if (!repoRecord) {
return res.status(404).json({ error: "Plugin niet gevonden." });
}
}
const repoInput =
typeof body.repo === "object"
? body.repo
: typeof body.plugin === "object"
? body.plugin
: body.repo || body.ownerRepo || body.fullName || body.plugin;
const repoFromRecord = repoRecord
? {
repo: repoRecord.ownerRepo,
provider: repoRecord.provider,
baseUrl: repoRecord.baseUrl
}
: null;
const repoEntry =
normalizeRepoInput(repoInput, {
repo: body.ownerRepo || body.fullName || body.repo,
provider: body.provider,
baseUrl: body.baseUrl
}) || null;
repo: body.ownerRepo || body.fullName || body.repo || repoFromRecord?.repo,
provider: body.provider || repoFromRecord?.provider,
baseUrl: body.baseUrl || repoFromRecord?.baseUrl
}) || repoFromRecord;
if (!repoId && !repoEntry) {
if (!repoEntry) {
return res.status(400).json({ error: "Kies een plugin om de licentie aan te koppelen." });
}
@@ -677,6 +751,12 @@ async function handleLicenseDownload(res, { key, hostname, version = "latest" })
});
stream.pipe(res);
} catch (error) {
if (error?.meta === "PRIVATE_REPO") {
if (!res.headersSent) {
return res.status(403).json({ error: "Downloads voor private plugins zijn uitgeschakeld." });
}
return res.end();
}
console.error("Download endpoint error:", error);
if (!res.headersSent) {
res.status(500).json({ error: "Download mislukt." });

View File

@@ -27,3 +27,63 @@ export const DB_CONFIG = {
export const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production";
export const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "7d";
function normalizeHostKey(value) {
if (!value) return null;
try {
const url = new URL(value);
return url.host.toLowerCase();
} catch {
return value.replace(/^[a-z]+:\/\//i, "").replace(/\/.*$/, "").trim().toLowerCase() || null;
}
}
function parseGiteaTokenMap(rawValue) {
if (!rawValue) return {};
const map = {};
try {
const parsed = typeof rawValue === "string" ? JSON.parse(rawValue) : rawValue;
if (parsed && typeof parsed === "object") {
for (const [key, token] of Object.entries(parsed)) {
const host = normalizeHostKey(key);
if (host && typeof token === "string" && token.trim()) {
map[host] = token.trim();
}
}
return map;
}
} catch {
// Fall through to comma-separated parsing.
}
const segments = String(rawValue)
.split(",")
.map((segment) => segment.trim())
.filter(Boolean);
for (const segment of segments) {
const [key, token] = segment.split("=").map((part) => part.trim());
const host = normalizeHostKey(key);
if (host && token) {
map[host] = token;
}
}
return map;
}
const DEFAULT_GITEA_TOKEN = process.env.GITEA_TOKEN?.trim() || null;
const GITEA_TOKENS = parseGiteaTokenMap(process.env.GITEA_TOKENS);
function getEnvTokenForHost(host) {
if (!host) return null;
const envKey = `GITEA_TOKEN_${host.replace(/[^A-Z0-9]/gi, "_").toUpperCase()}`;
return process.env[envKey]?.trim() || null;
}
export function getGiteaToken(baseUrl) {
const host = normalizeHostKey(baseUrl);
return (
(host && (GITEA_TOKENS[host] || getEnvTokenForHost(host))) ||
DEFAULT_GITEA_TOKEN ||
null
);
}

View File

@@ -22,6 +22,11 @@ export async function resolveRepoDownload(repoEntry, requestedVersion = "latest"
}
const repoInfo = await fetchRepo(normalizedEntry);
if (repoInfo.isPrivate) {
const error = new Error("Downloads niet beschikbaar voor private repositories.");
error.meta = "PRIVATE_REPO";
throw error;
}
const releases = await fetchReleases(normalizedEntry).catch(() => []);
let targetVersion = requestedVersion || "latest";

View File

@@ -1,5 +1,6 @@
import { readJsonFile } from "./storage.js";
import { getCached, setCached } from "./cache.js";
import { getGiteaToken } from "./config.js";
export function parseRepoEntry(entry) {
if (typeof entry === "string") {
@@ -51,6 +52,12 @@ async function fetchJson(url, cacheKey, opts = {}) {
if (!opts.provider || opts.provider === "github") {
headers.Accept = "application/vnd.github+json";
}
if (opts.provider === "gitea") {
const token = opts.token || getGiteaToken(opts.baseUrl || url);
if (token) {
headers.Authorization = `token ${token}`;
}
}
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`${opts.provider || "git"} request failed for ${url}`);
@@ -76,7 +83,7 @@ export async function fetchRepo(entry) {
let data;
if (provider === "gitea") {
const url = `${baseUrl.replace(/\/$/, "")}/api/v1/repos/${ownerRepo}`;
data = await fetchJson(url, `repo-raw:${provider}:${ownerRepo}`, { provider });
data = await fetchJson(url, `repo-raw:${provider}:${ownerRepo}`, { provider, baseUrl });
const mapped = {
fullName: data.full_name || `${ownerRepo}`,
name: data.name || ownerRepo.split("/")[1] || ownerRepo,
@@ -90,13 +97,16 @@ export async function fetchRepo(entry) {
topics: data.topics || [],
provider,
ownerRepo,
baseUrl
baseUrl,
isPrivate: Boolean(data.private)
};
setCached(cacheKey, mapped);
return mapped;
}
data = await fetchJson(`https://api.github.com/repos/${ownerRepo}`, `repo-raw:github:${ownerRepo}`, { provider: "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,
@@ -110,7 +120,8 @@ export async function fetchRepo(entry) {
topics: data.topics || [],
provider,
ownerRepo,
baseUrl: "https://github.com"
baseUrl: "https://github.com",
isPrivate: Boolean(data.private)
};
setCached(cacheKey, mapped);
return mapped;
@@ -131,7 +142,14 @@ export async function fetchManifest(entry, defaultBranch) {
} else {
url = `https://raw.githubusercontent.com/${ownerRepo}/${branch}/manifest.json`;
}
const response = await fetch(url, { headers: { "User-Agent": "siti-plugin-repo" } });
const headers = { "User-Agent": "siti-plugin-repo" };
if (provider === "gitea") {
const token = getGiteaToken(baseUrl);
if (token) {
headers.Authorization = `token ${token}`;
}
}
const response = await fetch(url, { headers });
if (response.ok) {
const manifest = await response.json().catch(() => null);
setCached(cacheKey, manifest);
@@ -145,7 +163,7 @@ export 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 });
const data = await fetchJson(url, `releases:${provider}:${ownerRepo}`, { provider, baseUrl });
return Array.isArray(data)
? data.map((release) => ({
tag: release.tag_name || release.name,
@@ -175,7 +193,7 @@ export 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 });
const data = await fetchJson(url, `commits:${provider}:${ownerRepo}`, { provider, baseUrl });
return Array.isArray(data)
? data.map((commit) => ({
sha: commit.sha,