Add repository management functionality with CRUD operations
- Implemented repoService for database interactions including count, list, get, create, update, and delete operations. - Created RepoManager component for managing repositories with a user interface. - Added forms for creating and editing repositories, including validation and error handling. - Integrated API calls for fetching, creating, updating, and deleting repositories. - Enhanced user experience with loading states and action feedback messages.
This commit is contained in:
210
server/index.js
210
server/index.js
@@ -18,6 +18,15 @@ import {
|
||||
listLicensesByUser,
|
||||
touchLicenseHostname
|
||||
} from "./lib/licenseService.js";
|
||||
import {
|
||||
countRepos,
|
||||
createRepo as createRepoRecord,
|
||||
deleteRepo as deleteRepoRecord,
|
||||
findRepoByOwnerRepo,
|
||||
getRepoById,
|
||||
listRepos,
|
||||
updateRepo as updateRepoRecord
|
||||
} from "./lib/repoService.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";
|
||||
@@ -29,6 +38,7 @@ app.use(express.json());
|
||||
|
||||
try {
|
||||
await ensureSchema();
|
||||
await seedLegacyRepos();
|
||||
} catch (error) {
|
||||
console.error("Kon database schema niet initialiseren:", error);
|
||||
process.exit(1);
|
||||
@@ -50,12 +60,18 @@ async function resolveRepoMetaFromRequest(body = {}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const provider = (body.provider || body.repoProvider || "github").toLowerCase();
|
||||
const baseUrl = body.baseUrl || body.repoBaseUrl || (provider === "github" ? "https://github.com" : null);
|
||||
const requestedProvider = (body.provider || body.repoProvider || "").toLowerCase() || null;
|
||||
const requestedBaseUrl = body.baseUrl || body.repoBaseUrl || null;
|
||||
const ownerRepo = `${owner}/${repository}`;
|
||||
|
||||
const existing = await findRepoByOwnerRepo(ownerRepo, requestedProvider || undefined);
|
||||
const provider = existing?.provider || requestedProvider || "github";
|
||||
const baseUrl =
|
||||
existing?.baseUrl || requestedBaseUrl || (provider === "github" ? "https://github.com" : null);
|
||||
|
||||
const repoEntry = normalizeRepoInput(
|
||||
{ repo: `${owner}/${repository}`, provider, baseUrl },
|
||||
{ repo: `${owner}/${repository}`, provider, baseUrl }
|
||||
{ repo: ownerRepo, provider, baseUrl },
|
||||
{ repo: ownerRepo, provider, baseUrl }
|
||||
);
|
||||
|
||||
if (!repoEntry) {
|
||||
@@ -68,9 +84,10 @@ async function resolveRepoMetaFromRequest(body = {}) {
|
||||
const releases = await fetchReleases(repoEntry).catch(() => []);
|
||||
const latestRelease = releases[0];
|
||||
const version = manifest?.version || latestRelease?.tag || latestRelease?.name || info.defaultBranch || null;
|
||||
const pluginName = manifest?.plugin_name || info.name || repository;
|
||||
const pluginName = manifest?.plugin_name || existing?.label || info.name || repository;
|
||||
|
||||
return {
|
||||
repoId: existing?.id || null,
|
||||
repoEntry,
|
||||
pluginName,
|
||||
version
|
||||
@@ -80,6 +97,30 @@ async function resolveRepoMetaFromRequest(body = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
async function seedLegacyRepos() {
|
||||
try {
|
||||
const repoCount = await countRepos();
|
||||
if (repoCount > 0) {
|
||||
return;
|
||||
}
|
||||
const legacy = await readRepos(PATHS.reposFile);
|
||||
if (!Array.isArray(legacy) || legacy.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const entry of legacy) {
|
||||
try {
|
||||
await createRepoRecord(entry);
|
||||
} catch (error) {
|
||||
if (error?.meta !== "DUPLICATE") {
|
||||
console.warn("Kon legacy repo niet importeren:", error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Legacy repos importeren mislukt:", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
app.post("/api/auth/register", async (req, res) => {
|
||||
try {
|
||||
const { username, name, email, password } = req.body || {};
|
||||
@@ -158,34 +199,136 @@ app.post("/api/admin/users", requireAuth, requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
function resolveOwnerRepoFromBody(body = {}) {
|
||||
if (body.ownerRepo) {
|
||||
return String(body.ownerRepo).trim();
|
||||
}
|
||||
const owner = typeof body.owner === "string" ? body.owner.trim() : "";
|
||||
const repo = typeof body.repo === "string" ? body.repo.trim() : "";
|
||||
if (owner && repo) {
|
||||
return `${owner}/${repo}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
app.get("/api/repos", requireAuth, requireAdmin, async (_req, res) => {
|
||||
try {
|
||||
const repos = await listRepos();
|
||||
res.json({ count: repos.length, items: repos });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Kon repos niet laden." });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/repos", requireAuth, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const ownerRepo = resolveOwnerRepoFromBody(req.body);
|
||||
if (!ownerRepo) {
|
||||
return res.status(400).json({ error: "Geef een owner en repo op." });
|
||||
}
|
||||
const provider = (req.body?.provider || "github").toLowerCase();
|
||||
const baseUrl = req.body?.baseUrl || null;
|
||||
const label = req.body?.label || null;
|
||||
const repo = await createRepoRecord({
|
||||
provider,
|
||||
ownerRepo,
|
||||
baseUrl,
|
||||
label
|
||||
});
|
||||
res.status(201).json({ repo });
|
||||
} catch (error) {
|
||||
if (error?.meta === "DUPLICATE") {
|
||||
return res.status(409).json({ error: error.message });
|
||||
}
|
||||
res.status(500).json({ error: "Kon repo niet opslaan." });
|
||||
}
|
||||
});
|
||||
|
||||
app.patch("/api/repos/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const repoId = Number(req.params.id);
|
||||
if (Number.isNaN(repoId)) {
|
||||
return res.status(400).json({ error: "Ongeldige repo id." });
|
||||
}
|
||||
const ownerRepo = resolveOwnerRepoFromBody(req.body);
|
||||
const payload = {
|
||||
provider: req.body?.provider,
|
||||
ownerRepo: ownerRepo || undefined,
|
||||
baseUrl: req.body?.baseUrl,
|
||||
label: req.body?.label
|
||||
};
|
||||
const repo = await updateRepoRecord(repoId, payload);
|
||||
res.json({ repo });
|
||||
} catch (error) {
|
||||
if (error?.meta === "NOT_FOUND") {
|
||||
return res.status(404).json({ error: error.message });
|
||||
}
|
||||
if (error?.meta === "DUPLICATE") {
|
||||
return res.status(409).json({ error: error.message });
|
||||
}
|
||||
res.status(500).json({ error: "Kon repo niet bijwerken." });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete("/api/repos/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const repoId = Number(req.params.id);
|
||||
if (Number.isNaN(repoId)) {
|
||||
return res.status(400).json({ error: "Ongeldige repo id." });
|
||||
}
|
||||
await deleteRepoRecord(repoId);
|
||||
res.status(204).end();
|
||||
} catch (error) {
|
||||
if (error?.meta === "NOT_FOUND") {
|
||||
return res.status(404).json({ error: error.message });
|
||||
}
|
||||
if (error?.meta === "IN_USE") {
|
||||
return res.status(409).json({ error: error.message });
|
||||
}
|
||||
res.status(500).json({ error: "Kon repo niet verwijderen." });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/plugins", async (_req, res) => {
|
||||
try {
|
||||
const repos = await readRepos(PATHS.reposFile);
|
||||
const repos = await listRepos();
|
||||
const results = await Promise.all(
|
||||
repos.map(async (repo) => {
|
||||
const entry = {
|
||||
provider: repo.provider,
|
||||
repo: repo.ownerRepo,
|
||||
baseUrl: repo.baseUrl
|
||||
};
|
||||
try {
|
||||
const info = await fetchRepo(repo);
|
||||
const manifest = await fetchManifest(repo, info.defaultBranch || info.default_branch);
|
||||
return { ...info, manifest };
|
||||
const info = await fetchRepo(entry);
|
||||
const manifest = await fetchManifest(entry, info.defaultBranch || info.default_branch);
|
||||
return {
|
||||
...info,
|
||||
manifest,
|
||||
provider: repo.provider,
|
||||
baseUrl: repo.baseUrl,
|
||||
repoId: repo.id,
|
||||
label: repo.label,
|
||||
ownerRepo: repo.ownerRepo
|
||||
};
|
||||
} catch {
|
||||
const parsed = parseRepoEntry(repo);
|
||||
const parsed = parseRepoEntry(entry);
|
||||
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}`,
|
||||
repoUrl: buildRepoUrl({ provider: repo.provider, baseUrl: repo.baseUrl, repo: repo.ownerRepo }),
|
||||
stars: 0,
|
||||
forks: 0,
|
||||
issues: 0,
|
||||
updatedAt: null,
|
||||
topics: [],
|
||||
manifest: null,
|
||||
provider: parsed.provider,
|
||||
ownerRepo: parsed.ownerRepo,
|
||||
baseUrl: parsed.baseUrl
|
||||
provider: repo.provider,
|
||||
ownerRepo: repo.ownerRepo,
|
||||
baseUrl: repo.baseUrl,
|
||||
repoId: repo.id,
|
||||
label: repo.label
|
||||
};
|
||||
}
|
||||
})
|
||||
@@ -223,9 +366,24 @@ function buildPluginDownloadEndpoint(ownerRepo, { provider, baseUrl, version } =
|
||||
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 baseUrlQuery = req.query.baseUrl || (provider === "github" ? "https://github.com" : "");
|
||||
const entry = provider === "github" ? ownerRepo : { provider, repo: ownerRepo, baseUrl: baseUrlQuery };
|
||||
const repoIdQuery = Number(req.query.repoId);
|
||||
let repoRecord = null;
|
||||
if (!Number.isNaN(repoIdQuery)) {
|
||||
repoRecord = await getRepoById(repoIdQuery);
|
||||
}
|
||||
if (!repoRecord) {
|
||||
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" : "");
|
||||
const entry =
|
||||
repoRecord && repoRecord.ownerRepo
|
||||
? { provider: repoRecord.provider, repo: repoRecord.ownerRepo, baseUrl: repoRecord.baseUrl }
|
||||
: provider === "github"
|
||||
? ownerRepo
|
||||
: { provider, repo: ownerRepo, baseUrl: baseUrlQuery };
|
||||
const normalizedEntry = normalizeRepoInput(entry, { repo: ownerRepo, provider, baseUrl: baseUrlQuery });
|
||||
|
||||
const info = await fetchRepo(entry);
|
||||
@@ -289,6 +447,8 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
|
||||
|
||||
res.json({
|
||||
...info,
|
||||
repoId: repoRecord?.id || null,
|
||||
label: repoRecord?.label || null,
|
||||
version: resolvedVersion,
|
||||
versionSource,
|
||||
manifest,
|
||||
@@ -379,6 +539,10 @@ app.get("/api/licenses", requireAuth, async (req, res) => {
|
||||
app.post("/api/licenses", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const body = req.body || {};
|
||||
const repoId = body.repoId ? Number(body.repoId) : null;
|
||||
if (body.repoId && Number.isNaN(repoId)) {
|
||||
return res.status(400).json({ error: "Ongeldig repo id." });
|
||||
}
|
||||
const repoInput =
|
||||
typeof body.repo === "object"
|
||||
? body.repo
|
||||
@@ -392,7 +556,7 @@ app.post("/api/licenses", requireAuth, async (req, res) => {
|
||||
baseUrl: body.baseUrl
|
||||
}) || null;
|
||||
|
||||
if (!repoEntry) {
|
||||
if (!repoId && !repoEntry) {
|
||||
return res.status(400).json({ error: "Kies een plugin om de licentie aan te koppelen." });
|
||||
}
|
||||
|
||||
@@ -418,7 +582,8 @@ app.post("/api/licenses", requireAuth, async (req, res) => {
|
||||
const payload = await createLicense(ownerUserId, {
|
||||
label: body.label?.trim(),
|
||||
note: body.note?.trim(),
|
||||
repo: repoEntry
|
||||
repo: repoEntry,
|
||||
repoId: repoId || undefined
|
||||
});
|
||||
res.status(201).json(payload);
|
||||
} catch (error) {
|
||||
@@ -443,6 +608,7 @@ app.post("/api/licenses/verify", async (req, res) => {
|
||||
pluginVersion: repoMeta.version,
|
||||
repoFullName: repoMeta.repoEntry.repo,
|
||||
repoUrl: buildRepoUrl(repoMeta.repoEntry),
|
||||
repoId: repoMeta.repoId || null,
|
||||
repo: repoMeta.repoEntry
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import crypto from "crypto";
|
||||
import db from "./db.js";
|
||||
import { fetchManifest, fetchRepo, normalizeRepoInput } from "./pluginService.js";
|
||||
import { getRepoById } from "./repoService.js";
|
||||
|
||||
function toIso(value) {
|
||||
return value ? new Date(value).toISOString() : null;
|
||||
@@ -31,12 +32,29 @@ export async function findLicenseByKey(key) {
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function createLicense(userId, { label, note, repo }) {
|
||||
const repoEntry = normalizeRepoInput(repo);
|
||||
if (!repoEntry) {
|
||||
const error = new Error("Ongeldige plugin referentie.");
|
||||
error.meta = "INVALID_REPO";
|
||||
throw error;
|
||||
export async function createLicense(userId, { label, note, repo, repoId }) {
|
||||
let repoEntry = null;
|
||||
let repoRow = null;
|
||||
|
||||
if (repoId) {
|
||||
repoRow = await getRepoById(repoId);
|
||||
if (!repoRow) {
|
||||
const error = new Error("Onbekende repository.");
|
||||
error.meta = "INVALID_REPO";
|
||||
throw error;
|
||||
}
|
||||
repoEntry = {
|
||||
repo: repoRow.ownerRepo,
|
||||
provider: repoRow.provider,
|
||||
baseUrl: repoRow.baseUrl
|
||||
};
|
||||
} else {
|
||||
repoEntry = normalizeRepoInput(repo);
|
||||
if (!repoEntry) {
|
||||
const error = new Error("Ongeldige plugin referentie.");
|
||||
error.meta = "INVALID_REPO";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let licenseId = null;
|
||||
@@ -47,9 +65,9 @@ export async function createLicense(userId, { label, note, repo }) {
|
||||
await db.query(
|
||||
`INSERT INTO licenses (
|
||||
id, user_id, license_key, label, note,
|
||||
repo_provider, repo_name, repo_base_url,
|
||||
repo_provider, repo_name, repo_base_url, repo_id,
|
||||
created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
[
|
||||
id,
|
||||
userId,
|
||||
@@ -58,7 +76,8 @@ export async function createLicense(userId, { label, note, repo }) {
|
||||
note || null,
|
||||
repoEntry.provider || "github",
|
||||
repoEntry.repo,
|
||||
repoEntry.baseUrl || (repoEntry.provider === "github" ? "https://github.com" : null)
|
||||
repoEntry.baseUrl || (repoEntry.provider === "github" ? "https://github.com" : null),
|
||||
repoRow?.id || null
|
||||
]
|
||||
);
|
||||
licenseId = id;
|
||||
@@ -80,11 +99,24 @@ export async function createLicense(userId, { label, note, repo }) {
|
||||
|
||||
export async function buildLicensePayload(row) {
|
||||
if (!row) return null;
|
||||
const repoEntry = normalizeRepoInput({
|
||||
repo: row.repo_name,
|
||||
provider: row.repo_provider,
|
||||
baseUrl: row.repo_base_url
|
||||
});
|
||||
|
||||
let repoRow = null;
|
||||
if (row.repo_id) {
|
||||
repoRow = await getRepoById(row.repo_id);
|
||||
}
|
||||
|
||||
const repoEntry =
|
||||
repoRow && repoRow.ownerRepo
|
||||
? {
|
||||
repo: repoRow.ownerRepo,
|
||||
provider: repoRow.provider,
|
||||
baseUrl: repoRow.baseUrl
|
||||
}
|
||||
: normalizeRepoInput({
|
||||
repo: row.repo_name,
|
||||
provider: row.repo_provider,
|
||||
baseUrl: row.repo_base_url
|
||||
});
|
||||
const [hostnameRows] = await db.query(
|
||||
`SELECT hostname, normalized, first_seen_at, last_seen_at, hits
|
||||
FROM license_hostnames WHERE license_id = ? ORDER BY first_seen_at ASC`,
|
||||
@@ -112,6 +144,8 @@ export async function buildLicensePayload(row) {
|
||||
primaryHostnameNormalized: row.primary_hostname_normalized,
|
||||
repoFullName: row.repo_name,
|
||||
repoUrl: null,
|
||||
repoId: repoRow?.id || null,
|
||||
repoLabel: repoRow?.label || null,
|
||||
pluginName: row.label,
|
||||
pluginVersion: row.last_used_version || null,
|
||||
lastUsedVersion: row.last_used_version,
|
||||
@@ -134,7 +168,9 @@ export async function buildLicensePayload(row) {
|
||||
primaryHostnameNormalized: row.primary_hostname_normalized,
|
||||
repoFullName: info.fullName,
|
||||
repoUrl: info.repoUrl,
|
||||
pluginName: manifest?.plugin_name || info.name || row.label,
|
||||
repoId: repoRow?.id || null,
|
||||
repoLabel: repoRow?.label || null,
|
||||
pluginName: manifest?.plugin_name || repoRow?.label || info.name || row.label,
|
||||
pluginVersion: manifest?.version || row.last_used_version || null,
|
||||
lastUsedVersion: row.last_used_version,
|
||||
repo: repoEntry,
|
||||
@@ -151,9 +187,14 @@ export async function buildLicensePayload(row) {
|
||||
lastVersionCheckAt: toIso(row.last_version_check_at),
|
||||
primaryHostname: row.primary_hostname,
|
||||
primaryHostnameNormalized: row.primary_hostname_normalized,
|
||||
repoFullName: row.repo_name,
|
||||
repoUrl: repoEntry?.baseUrl ? `${repoEntry.baseUrl.replace(/\/$/, "")}/${row.repo_name}` : null,
|
||||
pluginName: row.label,
|
||||
repoFullName: repoRow?.ownerRepo || row.repo_name,
|
||||
repoUrl:
|
||||
(repoRow?.baseUrl || repoEntry?.baseUrl)
|
||||
? `${(repoRow?.baseUrl || repoEntry.baseUrl).replace(/\/$/, "")}/${repoRow?.ownerRepo || row.repo_name}`
|
||||
: null,
|
||||
repoId: repoRow?.id || null,
|
||||
repoLabel: repoRow?.label || null,
|
||||
pluginName: repoRow?.label || row.label,
|
||||
pluginVersion: row.last_used_version || null,
|
||||
lastUsedVersion: row.last_used_version,
|
||||
repo: repoEntry,
|
||||
|
||||
141
server/lib/repoService.js
Normal file
141
server/lib/repoService.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import db from "./db.js";
|
||||
|
||||
function toIso(value) {
|
||||
return value ? new Date(value).toISOString() : null;
|
||||
}
|
||||
|
||||
function serializeRepo(row) {
|
||||
if (!row) return null;
|
||||
return {
|
||||
id: row.id,
|
||||
provider: row.provider,
|
||||
ownerRepo: row.owner_repo,
|
||||
baseUrl: row.base_url,
|
||||
label: row.label,
|
||||
createdAt: toIso(row.created_at),
|
||||
updatedAt: toIso(row.updated_at)
|
||||
};
|
||||
}
|
||||
|
||||
export async function countRepos() {
|
||||
const [[{ count }]] = await db.query(`SELECT COUNT(*) AS count FROM repos`);
|
||||
return Number(count) || 0;
|
||||
}
|
||||
|
||||
export async function listRepos() {
|
||||
const [rows] = await db.query(`SELECT * FROM repos ORDER BY created_at ASC, id ASC`);
|
||||
return rows.map(serializeRepo);
|
||||
}
|
||||
|
||||
export async function getRepoById(id) {
|
||||
if (!id) return null;
|
||||
const [rows] = await db.query(`SELECT * FROM repos WHERE id = ? LIMIT 1`, [id]);
|
||||
return serializeRepo(rows[0]);
|
||||
}
|
||||
|
||||
export async function findRepoByOwnerRepo(ownerRepo, provider = null) {
|
||||
if (!ownerRepo) return null;
|
||||
if (provider) {
|
||||
const [rows] = await db.query(
|
||||
`SELECT * FROM repos WHERE owner_repo = ? AND provider = ? LIMIT 1`,
|
||||
[ownerRepo, provider.toLowerCase()]
|
||||
);
|
||||
return serializeRepo(rows[0]);
|
||||
}
|
||||
const [rows] = await db.query(`SELECT * FROM repos WHERE owner_repo = ? LIMIT 1`, [ownerRepo]);
|
||||
return serializeRepo(rows[0]);
|
||||
}
|
||||
|
||||
export async function createRepo({ provider = "github", ownerRepo, baseUrl = null, label = null }) {
|
||||
if (!ownerRepo) {
|
||||
const error = new Error("Repo ontbreekt.");
|
||||
error.meta = "INVALID_REPO";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const normalizedProvider = String(provider || "github").toLowerCase();
|
||||
const trimmedOwnerRepo = ownerRepo.trim();
|
||||
const sanitizedBaseUrl = baseUrl ? baseUrl.trim() : null;
|
||||
const safeLabel = label ? label.trim() : null;
|
||||
|
||||
try {
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO repos (provider, owner_repo, base_url, label)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
[normalizedProvider, trimmedOwnerRepo, sanitizedBaseUrl, safeLabel]
|
||||
);
|
||||
return await getRepoById(result.insertId);
|
||||
} catch (error) {
|
||||
if (error?.code === "ER_DUP_ENTRY") {
|
||||
const dup = new Error("Deze repository bestaat al.");
|
||||
dup.meta = "DUPLICATE";
|
||||
throw dup;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateRepo(id, { provider, ownerRepo, baseUrl, label }) {
|
||||
const repo = await getRepoById(id);
|
||||
if (!repo) {
|
||||
const error = new Error("Repo niet gevonden.");
|
||||
error.meta = "NOT_FOUND";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const fields = [];
|
||||
const values = [];
|
||||
|
||||
if (provider) {
|
||||
fields.push("provider = ?");
|
||||
values.push(String(provider).toLowerCase());
|
||||
}
|
||||
if (ownerRepo) {
|
||||
fields.push("owner_repo = ?");
|
||||
values.push(ownerRepo.trim());
|
||||
}
|
||||
if (typeof baseUrl !== "undefined") {
|
||||
fields.push("base_url = ?");
|
||||
values.push(baseUrl ? baseUrl.trim() : null);
|
||||
}
|
||||
if (typeof label !== "undefined") {
|
||||
fields.push("label = ?");
|
||||
values.push(label ? label.trim() : null);
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return repo;
|
||||
}
|
||||
|
||||
try {
|
||||
await db.query(`UPDATE repos SET ${fields.join(", ")}, updated_at = NOW() WHERE id = ?`, [...values, id]);
|
||||
} catch (error) {
|
||||
if (error?.code === "ER_DUP_ENTRY") {
|
||||
const dup = new Error("Deze repository bestaat al.");
|
||||
dup.meta = "DUPLICATE";
|
||||
throw dup;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return await getRepoById(id);
|
||||
}
|
||||
|
||||
export async function deleteRepo(id) {
|
||||
const repo = await getRepoById(id);
|
||||
if (!repo) {
|
||||
const error = new Error("Repo niet gevonden.");
|
||||
error.meta = "NOT_FOUND";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const [[{ count }]] = await db.query(`SELECT COUNT(*) AS count FROM licenses WHERE repo_id = ?`, [id]);
|
||||
if (Number(count) > 0) {
|
||||
const error = new Error("Repo is gekoppeld aan bestaande licenties.");
|
||||
error.meta = "IN_USE";
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.query(`DELETE FROM repos WHERE id = ? LIMIT 1`, [id]);
|
||||
return repo;
|
||||
}
|
||||
@@ -37,6 +37,7 @@ async function createLicensesTable() {
|
||||
repo_provider VARCHAR(32) NOT NULL,
|
||||
repo_name VARCHAR(255) NOT NULL,
|
||||
repo_base_url VARCHAR(255),
|
||||
repo_id INT UNSIGNED NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
last_version_check_at DATETIME NULL,
|
||||
@@ -52,6 +53,21 @@ async function createLicensesTable() {
|
||||
"last_used_version",
|
||||
"last_used_version VARCHAR(64) NULL AFTER last_version_check_at"
|
||||
);
|
||||
|
||||
await ensureColumn(
|
||||
"licenses",
|
||||
"repo_id",
|
||||
"repo_id INT UNSIGNED NULL AFTER repo_base_url"
|
||||
);
|
||||
|
||||
await db
|
||||
.query(
|
||||
`ALTER TABLE licenses
|
||||
ADD CONSTRAINT fk_licenses_repo_id
|
||||
FOREIGN KEY (repo_id) REFERENCES repos(id)
|
||||
ON DELETE SET NULL`
|
||||
)
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async function createLicenseHostnamesTable() {
|
||||
@@ -70,8 +86,24 @@ async function createLicenseHostnamesTable() {
|
||||
`);
|
||||
}
|
||||
|
||||
async function createReposTable() {
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS repos (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
provider VARCHAR(32) NOT NULL DEFAULT 'github',
|
||||
owner_repo VARCHAR(255) NOT NULL,
|
||||
base_url VARCHAR(255),
|
||||
label VARCHAR(255),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_repo_provider (provider, owner_repo)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`);
|
||||
}
|
||||
|
||||
export async function ensureSchema() {
|
||||
await createUsersTable();
|
||||
await createReposTable();
|
||||
await createLicensesTable();
|
||||
await createLicenseHostnamesTable();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user