feat: add download functionality for licenses and enhance plugin detail view
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import express from "express";
|
||||
import path from "path";
|
||||
import { Readable } from "node:stream";
|
||||
import {
|
||||
fetchCommits,
|
||||
fetchManifest,
|
||||
@@ -21,6 +22,7 @@ import { HOST, PATHS, PORT } from "./lib/config.js";
|
||||
import { ensureSchema } from "./lib/schema.js";
|
||||
import { authenticateUser, registerUser, adminCreateUser, listUsers, getUserById } from "./lib/userService.js";
|
||||
import { requireAuth, requireAdmin } from "./middleware/auth.js";
|
||||
import { getDownloadSourceForLicense, resolveRepoDownload, buildDownloadUrl } from "./lib/downloadService.js";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
@@ -159,6 +161,7 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
|
||||
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 normalizedEntry = normalizeRepoInput(entry, { repo: ownerRepo, provider, baseUrl });
|
||||
|
||||
const info = await fetchRepo(entry);
|
||||
const [manifest, releases, commits] = await Promise.all([
|
||||
@@ -167,10 +170,36 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
|
||||
fetchCommits(entry).catch(() => [])
|
||||
]);
|
||||
|
||||
const releasesWithDownload =
|
||||
normalizedEntry && releases.length > 0
|
||||
? releases.map((release) => {
|
||||
const tagOrName = release.tag || release.name;
|
||||
return {
|
||||
...release,
|
||||
downloadUrl: tagOrName ? buildDownloadUrl(normalizedEntry, tagOrName, "release") : null
|
||||
};
|
||||
})
|
||||
: releases;
|
||||
|
||||
let downloadMeta = null;
|
||||
if (normalizedEntry) {
|
||||
const latestRelease = releasesWithDownload[0];
|
||||
const sourceType = latestRelease ? "release" : "branch";
|
||||
const version = latestRelease?.tag || latestRelease?.name || info.defaultBranch || "main";
|
||||
if (version) {
|
||||
downloadMeta = {
|
||||
url: buildDownloadUrl(normalizedEntry, version, sourceType),
|
||||
version,
|
||||
sourceType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
...info,
|
||||
manifest,
|
||||
releases,
|
||||
releases: releasesWithDownload,
|
||||
download: downloadMeta,
|
||||
commits
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -283,6 +312,51 @@ app.post("/api/licenses/verify", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/licenses/download", async (req, res) => {
|
||||
try {
|
||||
const { key, hostname, version = "latest" } = req.body || {};
|
||||
if (!key || !hostname) {
|
||||
return res.status(400).json({ error: "Licentiecode en hostname zijn verplicht." });
|
||||
}
|
||||
const license = await findLicenseByKey(String(key).trim());
|
||||
if (!license) {
|
||||
return res.status(404).json({ error: "Licentie niet gevonden." });
|
||||
}
|
||||
const result = await touchLicenseHostname(license, hostname);
|
||||
if (!result.ok) {
|
||||
const status = result.conflict ? 403 : 400;
|
||||
return res.status(status).json({ error: result.error, boundHostname: license.primary_hostname });
|
||||
}
|
||||
const source = await getDownloadSourceForLicense(license, version);
|
||||
const remoteResponse = await fetch(source.url);
|
||||
if (!remoteResponse.ok || !remoteResponse.body) {
|
||||
return res.status(502).json({ error: "Kon plugin versie niet ophalen." });
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", remoteResponse.headers.get("content-type") || "application/zip");
|
||||
const length = remoteResponse.headers.get("content-length");
|
||||
if (length) {
|
||||
res.setHeader("Content-Length", length);
|
||||
}
|
||||
res.setHeader("Content-Disposition", `attachment; filename=\"${source.filename}\"`);
|
||||
res.setHeader("X-Plugin-Version", source.version);
|
||||
|
||||
const stream = Readable.fromWeb(remoteResponse.body);
|
||||
stream.on("error", (err) => {
|
||||
console.error("Download stream error", err);
|
||||
res.destroy(err);
|
||||
});
|
||||
stream.pipe(res);
|
||||
} catch (error) {
|
||||
console.error("Download endpoint error:", error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: "Download mislukt." });
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.use(express.static(PATHS.distDir));
|
||||
app.get("*", (_req, res) => {
|
||||
res.sendFile(path.join(PATHS.distDir, "index.html"));
|
||||
|
||||
Reference in New Issue
Block a user