From 0964109c4bce40416cc9f4b153a2405ec24e1c9b Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Sun, 26 Apr 2026 14:40:53 +0200 Subject: [PATCH] added new feature: service config; Currently implemented in: loanMgmt and userMgmt (only Backend) Co-authored-by: Copilot --- backendV2/routes/app/loanMgmt.route.js | 314 ++++++++++++++----------- backendV2/routes/app/userMgmt.route.js | 79 ++++--- backendV2/schemeV2.sql | 13 +- backendV2/services/functions.js | 42 ++++ 4 files changed, 279 insertions(+), 169 deletions(-) create mode 100644 backendV2/services/functions.js diff --git a/backendV2/routes/app/loanMgmt.route.js b/backendV2/routes/app/loanMgmt.route.js index 2a57f60..eb4f203 100644 --- a/backendV2/routes/app/loanMgmt.route.js +++ b/backendV2/routes/app/loanMgmt.route.js @@ -1,5 +1,9 @@ import express from "express"; import { authenticate, generateToken } from "../../services/authentication.js"; +import { + checkIfServiceIsActive, + checkIfServiceIsActive2, +} from "../../services/functions.js"; const router = express.Router(); import dotenv from "dotenv"; dotenv.config(); @@ -18,106 +22,129 @@ import { } from "./database/loansMgmt.database.js"; import { sendMailLoan } from "./services/mailer.js"; -router.post("/createLoan", authenticate, async (req, res) => { - try { - const { items, startDate, endDate, note } = req.body || {}; +router.post( + "/createLoan", + checkIfServiceIsActive("Loan Service"), + authenticate, + async (req, res) => { + try { + const { items, startDate, endDate, note } = req.body || {}; - if (!Array.isArray(items) || items.length === 0) { - return res.status(400).json({ message: "Items array is required" }); - } + if (!Array.isArray(items) || items.length === 0) { + return res.status(400).json({ message: "Items array is required" }); + } - // If dates are not provided, default to now .. +7 days - const start = - startDate ?? new Date().toISOString().slice(0, 19).replace("T", " "); - const end = - endDate ?? - new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) - .toISOString() - .slice(0, 19) - .replace("T", " "); + // If dates are not provided, default to now .. +7 days + const start = + startDate ?? new Date().toISOString().slice(0, 19).replace("T", " "); + const end = + endDate ?? + new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) + .toISOString() + .slice(0, 19) + .replace("T", " "); - // Coerce item IDs to numbers and filter invalids - const itemIds = items - .map((v) => Number(v)) - .filter((n) => Number.isFinite(n)); + // Coerce item IDs to numbers and filter invalids + const itemIds = items + .map((v) => Number(v)) + .filter((n) => Number.isFinite(n)); - if (itemIds.length === 0) { - return res.status(400).json({ message: "No valid item IDs provided" }); - } + if (itemIds.length === 0) { + return res.status(400).json({ message: "No valid item IDs provided" }); + } - const result = await createLoanInDatabase( - req.user.username, - start, - end, - note, - itemIds, - ); - - if (result.success) { - const mailInfo = await getLoanInfoWithID(result.data.id); - console.log(mailInfo); - sendMailLoan( - mailInfo.data.username, - mailInfo.data.loaned_items_name, - mailInfo.data.start_date, - mailInfo.data.end_date, - mailInfo.data.created_at, - mailInfo.data.note, + const result = await createLoanInDatabase( + req.user.username, + start, + end, + note, + itemIds, ); - return res.status(201).json({ - message: "Loan created successfully", - loanId: result.data.id, - loanCode: result.data.loan_code, - }); + + if (result.success) { + if (await checkIfServiceIsActive2("Loan Mailer")) { + const mailInfo = await getLoanInfoWithID(result.data.id); + console.log(mailInfo); + sendMailLoan( + mailInfo.data.username, + mailInfo.data.loaned_items_name, + mailInfo.data.start_date, + mailInfo.data.end_date, + mailInfo.data.created_at, + mailInfo.data.note, + ); + } + + return res.status(201).json({ + message: "Loan created successfully", + loanId: result.data.id, + loanCode: result.data.loan_code, + }); + } + + if (result.code === "CONFLICT") { + return res + .status(409) + .json({ message: "Items not available in the selected period" }); + } + + if (result.code === "BAD_REQUEST") { + return res.status(400).json({ message: result.message }); + } + + return res.status(500).json({ message: "Failed to create loan" }); + } catch (err) { + console.error("createLoan error:", err); + return res.status(500).json({ message: "Failed to create loan" }); } + }, +); - if (result.code === "CONFLICT") { - return res - .status(409) - .json({ message: "Items not available in the selected period" }); +router.get( + "/loans", + checkIfServiceIsActive("Loan Service"), + authenticate, + async (req, res) => { + const result = await getLoansFromDatabase(req.user.username); + if (result.success) { + res.status(200).json(result.data); + } else if (result.status) { + res.status(200).json([]); + } else { + res.status(500).json({ message: "Failed to fetch loans" }); } + }, +); - if (result.code === "BAD_REQUEST") { - return res.status(400).json({ message: result.message }); +router.post( + "/set-return-date/:loan_code", + checkIfServiceIsActive("Loan Service"), + authenticate, + async (req, res) => { + const loanCode = req.params.loan_code; + const result = await setReturnDate(loanCode); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(500).json({ message: "Failed to set return date" }); } + }, +); - return res.status(500).json({ message: "Failed to create loan" }); - } catch (err) { - console.error("createLoan error:", err); - return res.status(500).json({ message: "Failed to create loan" }); - } -}); - -router.get("/loans", authenticate, async (req, res) => { - const result = await getLoansFromDatabase(req.user.username); - if (result.success) { - res.status(200).json(result.data); - } else if (result.status) { - res.status(200).json([]); - } else { - res.status(500).json({ message: "Failed to fetch loans" }); - } -}); - -router.post("/set-return-date/:loan_code", authenticate, async (req, res) => { - const loanCode = req.params.loan_code; - const result = await setReturnDate(loanCode); - if (result.success) { - res.status(200).json({ data: result.data }); - } else { - res.status(500).json({ message: "Failed to set return date" }); - } -}); - -router.post("/set-take-date/:loan_code", authenticate, async (req, res) => { - const loanCode = req.params.loan_code; - const result = await setTakeDate(loanCode); - if (result.success) { - res.status(200).json({ data: result.data }); - } else { - res.status(500).json({ message: "Failed to set take date" }); - } -}); +router.post( + "/set-take-date/:loan_code", + checkIfServiceIsActive("Loan Service"), + authenticate, + async (req, res) => { + const loanCode = req.params.loan_code; + const result = await setTakeDate(loanCode); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(500).json({ message: "Failed to set take date" }); + } + }, +); router.get("/all-items", authenticate, async (req, res) => { const result = await getItems(); @@ -128,56 +155,71 @@ router.get("/all-items", authenticate, async (req, res) => { } }); -router.delete("/delete-loan/:id", authenticate, async (req, res) => { - const loanId = req.params.id; - const result = await SETdeleteLoanFromDatabase(loanId); - if (result.success) { - res.status(200).json({ message: "Loan deleted successfully" }); - } else { - if (result.code === "LOAN_NOT_FOUND") { - res.status(404).json({ message: "Loan not found" }); +router.delete( + "/delete-loan/:id", + checkIfServiceIsActive("Loan Service"), + authenticate, + async (req, res) => { + const loanId = req.params.id; + const result = await SETdeleteLoanFromDatabase(loanId); + if (result.success) { + res.status(200).json({ message: "Loan deleted successfully" }); + } else { + if (result.code === "LOAN_NOT_FOUND") { + res.status(404).json({ message: "Loan not found" }); + } + + if (result.code === "LOAN_NOT_RETURNED") { + res.status(507).json({ + message: "Cannot delete loan that has not been returned", + }); + } + + res.status(500).json({ message: "Failed to delete loan" }); + } + }, +); + +router.get( + "/all-loans", + checkIfServiceIsActive("Loan Service"), + authenticate, + async (req, res) => { + const result = await getALLLoans(); + if (result.success) { + res.status(200).json(result.data); + } else { + res.status(500).json({ message: "Failed to fetch loans" }); + } + }, +); + +router.post( + "/borrowable-items", + checkIfServiceIsActive("Loan Service"), + authenticate, + async (req, res) => { + const { startDate, endDate } = req.body || {}; + if (!startDate || !endDate) { + return res + .status(400) + .json({ message: "startDate and endDate are required" }); } - if (result.code === "LOAN_NOT_RETURNED") { - res.status(507).json({ - message: "Cannot delete loan that has not been returned", - }); + const result = await getBorrowableItemsFromDatabase( + startDate, + endDate, + req.user.role, + ); + if (result.success) { + // return the array directly for consistency with /items + return res.status(200).json(result.data); + } else { + return res + .status(500) + .json({ message: "Failed to fetch borrowable items" }); } - - res.status(500).json({ message: "Failed to delete loan" }); - } -}); - -router.get("/all-loans", authenticate, async (req, res) => { - const result = await getALLLoans(); - if (result.success) { - res.status(200).json(result.data); - } else { - res.status(500).json({ message: "Failed to fetch loans" }); - } -}); - -router.post("/borrowable-items", authenticate, async (req, res) => { - const { startDate, endDate } = req.body || {}; - if (!startDate || !endDate) { - return res - .status(400) - .json({ message: "startDate and endDate are required" }); - } - - const result = await getBorrowableItemsFromDatabase( - startDate, - endDate, - req.user.role, - ); - if (result.success) { - // return the array directly for consistency with /items - return res.status(200).json(result.data); - } else { - return res - .status(500) - .json({ message: "Failed to fetch borrowable items" }); - } -}); + }, +); export default router; diff --git a/backendV2/routes/app/userMgmt.route.js b/backendV2/routes/app/userMgmt.route.js index 0f63862..d91b6b3 100644 --- a/backendV2/routes/app/userMgmt.route.js +++ b/backendV2/routes/app/userMgmt.route.js @@ -1,5 +1,6 @@ import express from "express"; import { authenticate, generateToken } from "../../services/authentication.js"; +import { checkIfServiceIsActive } from "../../services/functions.js"; const router = express.Router(); import dotenv from "dotenv"; dotenv.config(); @@ -8,41 +9,55 @@ dotenv.config(); import { loginFunc, changePassword } from "./database/userMgmt.database.js"; import { sendMail } from "./services/mailer_v2.js"; -router.post("/login", async (req, res) => { - const result = await loginFunc(req.body.username, req.body.password); - if (result.success) { - const token = await generateToken({ - username: result.data.username, - is_admin: result.data.is_admin, - first_name: result.data.first_name, - last_name: result.data.last_name, - role: result.data.role, - }); - res.status(200).json({ message: "Login successful", token }); - } else { - res.status(401).json({ message: "Invalid credentials" }); - } -}); +router.post( + "/login", + checkIfServiceIsActive("User Frontend"), + async (req, res) => { + const result = await loginFunc(req.body.username, req.body.password); + if (result.success) { + const token = await generateToken({ + username: result.data.username, + is_admin: result.data.is_admin, + first_name: result.data.first_name, + last_name: result.data.last_name, + role: result.data.role, + }); + res.status(200).json({ message: "Login successful", token }); + } else { + res.status(401).json({ message: "Invalid credentials" }); + } + }, +); -router.post("/change-password", authenticate, async (req, res) => { - const oldPassword = req.body.oldPassword; - const newPassword = req.body.newPassword; - const username = req.user.username; - const result = await changePassword(username, oldPassword, newPassword); - if (result.success) { - res.status(200).json({ message: "Password changed successfully" }); - } else { - res.status(500).json({ message: "Failed to change password" }); - } -}); +router.post( + "/change-password", + checkIfServiceIsActive("User Frontend"), + authenticate, + async (req, res) => { + const oldPassword = req.body.oldPassword; + const newPassword = req.body.newPassword; + const username = req.user.username; + const result = await changePassword(username, oldPassword, newPassword); + if (result.success) { + res.status(200).json({ message: "Password changed successfully" }); + } else { + res.status(500).json({ message: "Failed to change password" }); + } + }, +); -router.post("/contact", authenticate, async (req, res) => { - const message = req.body.message; - const username = req.user.username; +router.post( + "/contact", + checkIfServiceIsActive("Contact Form Service"), + authenticate, + async (req, res) => { + const message = req.body.message; + const username = req.user.username; - sendMail(username, message); + sendMail(username, message); - res.status(200).json({ message: "Contact message sent successfully" }); -}); + res.status(200).json({ message: "Contact message sent successfully" }); + }, +); export default router; diff --git a/backendV2/schemeV2.sql b/backendV2/schemeV2.sql index 95ff6ed..699e404 100644 --- a/backendV2/schemeV2.sql +++ b/backendV2/schemeV2.sql @@ -54,4 +54,15 @@ CREATE TABLE apiKeys ( entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), CHECK (api_key REGEXP '^[0-9]{8}$') -) ENGINE=InnoDB; \ No newline at end of file +) ENGINE=InnoDB; + +CREATE TABLE functions ( + id INT NOT NULL AUTO_INCREMENT, + function_name VARCHAR(500) NOT NULL UNIQUE, + active BOOLEAN NOT NULL DEFAULT true, + entry_updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +) ENGINE=InnoDB; + +INSERT INTO functions (function_name) VALUES ("Loan Mailer"), ("Loan Service"), ("Contact Form Service"), ("User Frontend"), ("API") \ No newline at end of file diff --git a/backendV2/services/functions.js b/backendV2/services/functions.js new file mode 100644 index 0000000..eea290c --- /dev/null +++ b/backendV2/services/functions.js @@ -0,0 +1,42 @@ +import mysql from "mysql2"; +import dotenv from "dotenv"; +dotenv.config(); + +const pool = mysql + .createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + }) + .promise(); + +export function checkIfServiceIsActive(service) { + return async (req, res, next) => { + const [result] = await pool.query( + "SELECT * FROM functions WHERE function_name = ? AND active = 1;", + [service], + ); + + if (result.length > 0) { + return next(); + } + + return res + .status(503) + .json({ message: `-${service}- is currently unavailable.` }); + }; +} + +export async function checkIfServiceIsActive2(service) { + const [result] = await pool.query( + "SELECT * FROM functions WHERE function_name = ? AND active = 1;", + [service], + ); + + if (result.length > 0) { + return true; + } + + return false; +}