294 lines
10 KiB
JavaScript
294 lines
10 KiB
JavaScript
import express from "express";
|
|
import path from "path";
|
|
import {
|
|
fetchCommits,
|
|
fetchManifest,
|
|
fetchRepo,
|
|
fetchReleases,
|
|
normalizeRepoInput,
|
|
parseRepoEntry,
|
|
readRepos
|
|
} from "./lib/pluginService.js";
|
|
import {
|
|
buildLicensePayload,
|
|
createLicense,
|
|
findLicenseByKey,
|
|
getLicenseById,
|
|
listLicensesByUser,
|
|
touchLicenseHostname
|
|
} from "./lib/licenseService.js";
|
|
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";
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
try {
|
|
await ensureSchema();
|
|
} catch (error) {
|
|
console.error("Kon database schema niet initialiseren:", error);
|
|
process.exit(1);
|
|
}
|
|
|
|
app.post("/api/auth/register", async (req, res) => {
|
|
try {
|
|
const { username, name, email, password } = req.body || {};
|
|
if (!username || !name || !email || !password) {
|
|
return res.status(400).json({ error: "Vul gebruikersnaam, naam, e-mail en wachtwoord in." });
|
|
}
|
|
if (password.length < 8) {
|
|
return res.status(400).json({ error: "Wachtwoord moet minimaal 8 karakters zijn." });
|
|
}
|
|
const { user, token } = await registerUser({
|
|
username: String(username).trim(),
|
|
name: String(name).trim(),
|
|
email: String(email).trim().toLowerCase(),
|
|
password: String(password)
|
|
});
|
|
res.status(201).json({ token, user });
|
|
} catch (error) {
|
|
if (error?.code === "ER_DUP_ENTRY") {
|
|
const field = error.meta === "EMAIL" ? "e-mailadres" : "gebruikersnaam";
|
|
return res.status(409).json({ error: `Dit ${field} is al in gebruik.` });
|
|
}
|
|
res.status(500).json({ error: "Registratie mislukt." });
|
|
}
|
|
});
|
|
|
|
app.post("/api/auth/login", async (req, res) => {
|
|
try {
|
|
const { identifier, password } = req.body || {};
|
|
if (!identifier || !password) {
|
|
return res.status(400).json({ error: "Vul gebruikersnaam/e-mail en wachtwoord in." });
|
|
}
|
|
const { user, token } = await authenticateUser(String(identifier).trim(), String(password));
|
|
res.json({ token, user });
|
|
} catch (error) {
|
|
const message = error?.meta === "INVALID_CREDENTIALS" ? "Onjuiste inloggegevens." : "Login mislukt.";
|
|
res.status(401).json({ error: message });
|
|
}
|
|
});
|
|
|
|
app.get("/api/auth/me", requireAuth, (req, res) => {
|
|
res.json({ user: req.user });
|
|
});
|
|
|
|
app.get("/api/admin/users", requireAuth, requireAdmin, async (_req, res) => {
|
|
try {
|
|
const users = await listUsers();
|
|
res.json({ count: users.length, items: users });
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Kon gebruikers niet laden." });
|
|
}
|
|
});
|
|
|
|
app.post("/api/admin/users", requireAuth, requireAdmin, async (req, res) => {
|
|
try {
|
|
const { username, name, email, password, isAdmin } = req.body || {};
|
|
if (!username || !name || !email || !password) {
|
|
return res.status(400).json({ error: "Alle velden zijn verplicht." });
|
|
}
|
|
if (String(password).length < 8) {
|
|
return res.status(400).json({ error: "Wachtwoord moet minimaal 8 karakters zijn." });
|
|
}
|
|
const user = await adminCreateUser({
|
|
username: String(username).trim(),
|
|
name: String(name).trim(),
|
|
email: String(email).trim().toLowerCase(),
|
|
password: String(password),
|
|
isAdmin: Boolean(isAdmin)
|
|
});
|
|
res.status(201).json({ user });
|
|
} catch (error) {
|
|
if (error?.code === "ER_DUP_ENTRY") {
|
|
const field = error.meta === "EMAIL" ? "e-mailadres" : "gebruikersnaam";
|
|
return res.status(409).json({ error: `Dit ${field} is al in gebruik.` });
|
|
}
|
|
res.status(500).json({ error: "Gebruiker aanmaken mislukt." });
|
|
}
|
|
});
|
|
|
|
app.get("/api/plugins", async (_req, res) => {
|
|
try {
|
|
const repos = await readRepos(PATHS.reposFile);
|
|
const results = await Promise.all(
|
|
repos.map(async (repo) => {
|
|
try {
|
|
const info = await fetchRepo(repo);
|
|
const manifest = await fetchManifest(repo, info.defaultBranch || info.default_branch);
|
|
return { ...info, manifest };
|
|
} catch {
|
|
const parsed = parseRepoEntry(repo);
|
|
return {
|
|
fullName: parsed.ownerRepo,
|
|
name: parsed.ownerRepo.split("/")[1] || parsed.ownerRepo,
|
|
description: "Kon gegevens niet ophalen.",
|
|
repoUrl:
|
|
parsed.provider === "gitea"
|
|
? `${parsed.baseUrl.replace(/\/$/, "")}/${parsed.ownerRepo}`
|
|
: `https://github.com/${parsed.ownerRepo}`,
|
|
stars: 0,
|
|
forks: 0,
|
|
issues: 0,
|
|
updatedAt: null,
|
|
topics: [],
|
|
manifest: null,
|
|
provider: parsed.provider,
|
|
ownerRepo: parsed.ownerRepo,
|
|
baseUrl: parsed.baseUrl
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
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 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(entry, info.defaultBranch).catch(() => null),
|
|
fetchReleases(entry).catch(() => []),
|
|
fetchCommits(entry).catch(() => [])
|
|
]);
|
|
|
|
res.json({
|
|
...info,
|
|
manifest,
|
|
releases,
|
|
commits
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Kon plugin details niet laden." });
|
|
}
|
|
});
|
|
|
|
app.get("/api/licenses", requireAuth, async (req, res) => {
|
|
try {
|
|
let targetUserId = req.user.id;
|
|
if (req.user.isAdmin && req.query.userId) {
|
|
const parsed = Number(req.query.userId);
|
|
if (Number.isNaN(parsed)) {
|
|
return res.status(400).json({ error: "Ongeldige userId." });
|
|
}
|
|
const targetUser = await getUserById(parsed);
|
|
if (!targetUser) {
|
|
return res.status(404).json({ error: "Gebruiker niet gevonden." });
|
|
}
|
|
targetUserId = parsed;
|
|
}
|
|
const payload = await listLicensesByUser(targetUserId);
|
|
res.json({
|
|
count: payload.length,
|
|
updatedAt: new Date().toISOString(),
|
|
items: payload
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Kon licenties niet laden." });
|
|
}
|
|
});
|
|
|
|
app.post("/api/licenses", requireAuth, async (req, res) => {
|
|
try {
|
|
const body = req.body || {};
|
|
const repoInput =
|
|
typeof body.repo === "object"
|
|
? body.repo
|
|
: typeof body.plugin === "object"
|
|
? body.plugin
|
|
: body.repo || body.ownerRepo || body.fullName || body.plugin;
|
|
const repoEntry =
|
|
normalizeRepoInput(repoInput, {
|
|
repo: body.ownerRepo || body.fullName || body.repo,
|
|
provider: body.provider,
|
|
baseUrl: body.baseUrl
|
|
}) || null;
|
|
|
|
if (!repoEntry) {
|
|
return res.status(400).json({ error: "Kies een plugin om de licentie aan te koppelen." });
|
|
}
|
|
|
|
try {
|
|
await fetchRepo(repoEntry);
|
|
} catch (error) {
|
|
return res.status(400).json({ error: "Kon plugin gegevens niet ophalen." });
|
|
}
|
|
|
|
let ownerUserId = req.user.id;
|
|
if (req.user.isAdmin && body.userId) {
|
|
const parsed = Number(body.userId);
|
|
if (Number.isNaN(parsed)) {
|
|
return res.status(400).json({ error: "Ongeldige gebruiker." });
|
|
}
|
|
const target = await getUserById(parsed);
|
|
if (!target) {
|
|
return res.status(404).json({ error: "Gebruiker niet gevonden." });
|
|
}
|
|
ownerUserId = parsed;
|
|
}
|
|
|
|
const payload = await createLicense(ownerUserId, {
|
|
label: body.label?.trim(),
|
|
note: body.note?.trim(),
|
|
repo: repoEntry
|
|
});
|
|
res.status(201).json(payload);
|
|
} catch (error) {
|
|
res.status(500).json({ error: "Kon licentie niet aanmaken." });
|
|
}
|
|
});
|
|
|
|
app.post("/api/licenses/verify", async (req, res) => {
|
|
try {
|
|
const { key, hostname } = req.body || {};
|
|
if (!key || !hostname) {
|
|
return res.status(400).json({ valid: false, error: "Licentiecode en hostname zijn verplicht." });
|
|
}
|
|
const license = await findLicenseByKey(String(key).trim());
|
|
if (!license) {
|
|
return res.status(404).json({ valid: false, error: "Licentie niet gevonden." });
|
|
}
|
|
|
|
const result = await touchLicenseHostname(license, hostname);
|
|
if (!result.ok) {
|
|
const status = result.conflict ? 403 : 400;
|
|
return res.status(status).json({ valid: false, error: result.error, boundHostname: license.primary_hostname });
|
|
}
|
|
|
|
const freshLicense = await getLicenseById(license.id);
|
|
const payload = await buildLicensePayload(freshLicense);
|
|
res.json({
|
|
valid: true,
|
|
hostname: payload.primaryHostname,
|
|
boundNow: !!result.boundNow,
|
|
license: payload
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ valid: false, error: "Validatie mislukt." });
|
|
}
|
|
});
|
|
|
|
app.use(express.static(PATHS.distDir));
|
|
app.get("*", (_req, res) => {
|
|
res.sendFile(path.join(PATHS.distDir, "index.html"));
|
|
});
|
|
|
|
app.listen(PORT, HOST, () => {
|
|
console.log(`Server draait op http://${HOST}:${PORT}`);
|
|
});
|