Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-02-01 02:28:31 +00:00
parent 7b0ca40c4f
commit 73025c84c5
10 changed files with 413 additions and 87 deletions

View File

@@ -19,8 +19,8 @@ import {
} from "./lib/licenseService.js";
import { HOST, PATHS, PORT } from "./lib/config.js";
import { ensureSchema } from "./lib/schema.js";
import { authenticateUser, registerUser } from "./lib/userService.js";
import { requireAuth } from "./middleware/auth.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());
@@ -75,6 +75,41 @@ 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);
@@ -145,7 +180,19 @@ app.get("/api/plugins/:owner/:repo", async (req, res) => {
app.get("/api/licenses", requireAuth, async (req, res) => {
try {
const payload = await listLicensesByUser(req.user.id);
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(),
@@ -182,7 +229,20 @@ app.post("/api/licenses", requireAuth, async (req, res) => {
return res.status(400).json({ error: "Kon plugin gegevens niet ophalen." });
}
const payload = await createLicense(req.user.id, {
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

View File

@@ -7,10 +7,16 @@ async function createUsersTable() {
username VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(120) NOT NULL,
email VARCHAR(120) NOT NULL UNIQUE,
is_admin TINYINT(1) NOT NULL DEFAULT 0,
password_hash VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
const [columns] = await db.query(`SHOW COLUMNS FROM users LIKE 'is_admin'`);
if (columns.length === 0) {
await db.query(`ALTER TABLE users ADD COLUMN is_admin TINYINT(1) NOT NULL DEFAULT 0 AFTER email`);
}
}
async function createLicensesTable() {

View File

@@ -10,6 +10,7 @@ function serializeUser(row) {
username: row.username,
name: row.name,
email: row.email,
isAdmin: Boolean(row.is_admin),
createdAt: row.created_at ? new Date(row.created_at).toISOString() : null
};
}
@@ -20,10 +21,12 @@ function signToken(userId) {
export async function registerUser({ username, name, email, password }) {
const passwordHash = await bcrypt.hash(password, 10);
const [[{ count }]] = await db.query("SELECT COUNT(*) AS count FROM users");
const isAdmin = count === 0 ? 1 : 0;
try {
const [result] = await db.query(
`INSERT INTO users (username, name, email, password_hash) VALUES (?, ?, ?, ?)`,
[username, name, email, passwordHash]
`INSERT INTO users (username, name, email, password_hash, is_admin) VALUES (?, ?, ?, ?, ?)`,
[username, name, email, passwordHash, isAdmin]
);
const user = await getUserById(result.insertId);
const token = signToken(user.id);
@@ -64,6 +67,37 @@ export async function authenticateUser(identifier, password) {
}
export async function getUserById(id) {
const [rows] = await db.query(`SELECT id, username, name, email, created_at FROM users WHERE id = ? LIMIT 1`, [id]);
const [rows] = await db.query(
`SELECT id, username, name, email, is_admin, created_at FROM users WHERE id = ? LIMIT 1`,
[id]
);
return serializeUser(rows[0]);
}
export async function listUsers() {
const [rows] = await db.query(
`SELECT id, username, name, email, is_admin, created_at FROM users ORDER BY created_at ASC`
);
return rows.map(serializeUser);
}
export async function adminCreateUser({ username, name, email, password, isAdmin = false }) {
const passwordHash = await bcrypt.hash(password, 10);
try {
const [result] = await db.query(
`INSERT INTO users (username, name, email, password_hash, is_admin) VALUES (?, ?, ?, ?, ?)`,
[username, name, email, passwordHash, isAdmin ? 1 : 0]
);
return await getUserById(result.insertId);
} catch (error) {
if (error?.code === "ER_DUP_ENTRY") {
const message = error.sqlMessage || "Duplicate";
if (message.includes("username")) {
error.meta = "USERNAME";
} else if (message.includes("email")) {
error.meta = "EMAIL";
}
}
throw error;
}
}

View File

@@ -21,3 +21,13 @@ export async function requireAuth(req, res, next) {
return res.status(401).json({ error: "Ongeldige of verlopen token." });
}
}
export function requireAdmin(req, res, next) {
if (!req.user) {
return res.status(401).json({ error: "Inloggen vereist." });
}
if (!req.user.isAdmin) {
return res.status(403).json({ error: "Administratorrechten vereist." });
}
next();
}