added new feature: service config; Currently implemented in: loanMgmt and userMgmt (only Backend)
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { authenticate, generateToken } from "../../services/authentication.js";
|
import { authenticate, generateToken } from "../../services/authentication.js";
|
||||||
|
import {
|
||||||
|
checkIfServiceIsActive,
|
||||||
|
checkIfServiceIsActive2,
|
||||||
|
} from "../../services/functions.js";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
@@ -18,106 +22,129 @@ import {
|
|||||||
} from "./database/loansMgmt.database.js";
|
} from "./database/loansMgmt.database.js";
|
||||||
import { sendMailLoan } from "./services/mailer.js";
|
import { sendMailLoan } from "./services/mailer.js";
|
||||||
|
|
||||||
router.post("/createLoan", authenticate, async (req, res) => {
|
router.post(
|
||||||
try {
|
"/createLoan",
|
||||||
const { items, startDate, endDate, note } = req.body || {};
|
checkIfServiceIsActive("Loan Service"),
|
||||||
|
authenticate,
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { items, startDate, endDate, note } = req.body || {};
|
||||||
|
|
||||||
if (!Array.isArray(items) || items.length === 0) {
|
if (!Array.isArray(items) || items.length === 0) {
|
||||||
return res.status(400).json({ message: "Items array is required" });
|
return res.status(400).json({ message: "Items array is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If dates are not provided, default to now .. +7 days
|
// If dates are not provided, default to now .. +7 days
|
||||||
const start =
|
const start =
|
||||||
startDate ?? new Date().toISOString().slice(0, 19).replace("T", " ");
|
startDate ?? new Date().toISOString().slice(0, 19).replace("T", " ");
|
||||||
const end =
|
const end =
|
||||||
endDate ??
|
endDate ??
|
||||||
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
||||||
.toISOString()
|
.toISOString()
|
||||||
.slice(0, 19)
|
.slice(0, 19)
|
||||||
.replace("T", " ");
|
.replace("T", " ");
|
||||||
|
|
||||||
// Coerce item IDs to numbers and filter invalids
|
// Coerce item IDs to numbers and filter invalids
|
||||||
const itemIds = items
|
const itemIds = items
|
||||||
.map((v) => Number(v))
|
.map((v) => Number(v))
|
||||||
.filter((n) => Number.isFinite(n));
|
.filter((n) => Number.isFinite(n));
|
||||||
|
|
||||||
if (itemIds.length === 0) {
|
if (itemIds.length === 0) {
|
||||||
return res.status(400).json({ message: "No valid item IDs provided" });
|
return res.status(400).json({ message: "No valid item IDs provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await createLoanInDatabase(
|
const result = await createLoanInDatabase(
|
||||||
req.user.username,
|
req.user.username,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
note,
|
note,
|
||||||
itemIds,
|
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,
|
|
||||||
);
|
);
|
||||||
return res.status(201).json({
|
|
||||||
message: "Loan created successfully",
|
if (result.success) {
|
||||||
loanId: result.data.id,
|
if (await checkIfServiceIsActive2("Loan Mailer")) {
|
||||||
loanCode: result.data.loan_code,
|
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") {
|
router.get(
|
||||||
return res
|
"/loans",
|
||||||
.status(409)
|
checkIfServiceIsActive("Loan Service"),
|
||||||
.json({ message: "Items not available in the selected period" });
|
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") {
|
router.post(
|
||||||
return res.status(400).json({ message: result.message });
|
"/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" });
|
router.post(
|
||||||
} catch (err) {
|
"/set-take-date/:loan_code",
|
||||||
console.error("createLoan error:", err);
|
checkIfServiceIsActive("Loan Service"),
|
||||||
return res.status(500).json({ message: "Failed to create loan" });
|
authenticate,
|
||||||
}
|
async (req, res) => {
|
||||||
});
|
const loanCode = req.params.loan_code;
|
||||||
|
const result = await setTakeDate(loanCode);
|
||||||
router.get("/loans", authenticate, async (req, res) => {
|
if (result.success) {
|
||||||
const result = await getLoansFromDatabase(req.user.username);
|
res.status(200).json({ data: result.data });
|
||||||
if (result.success) {
|
} else {
|
||||||
res.status(200).json(result.data);
|
res.status(500).json({ message: "Failed to set take date" });
|
||||||
} 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.get("/all-items", authenticate, async (req, res) => {
|
router.get("/all-items", authenticate, async (req, res) => {
|
||||||
const result = await getItems();
|
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) => {
|
router.delete(
|
||||||
const loanId = req.params.id;
|
"/delete-loan/:id",
|
||||||
const result = await SETdeleteLoanFromDatabase(loanId);
|
checkIfServiceIsActive("Loan Service"),
|
||||||
if (result.success) {
|
authenticate,
|
||||||
res.status(200).json({ message: "Loan deleted successfully" });
|
async (req, res) => {
|
||||||
} else {
|
const loanId = req.params.id;
|
||||||
if (result.code === "LOAN_NOT_FOUND") {
|
const result = await SETdeleteLoanFromDatabase(loanId);
|
||||||
res.status(404).json({ message: "Loan not found" });
|
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") {
|
const result = await getBorrowableItemsFromDatabase(
|
||||||
res.status(507).json({
|
startDate,
|
||||||
message: "Cannot delete loan that has not been returned",
|
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;
|
export default router;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { authenticate, generateToken } from "../../services/authentication.js";
|
import { authenticate, generateToken } from "../../services/authentication.js";
|
||||||
|
import { checkIfServiceIsActive } from "../../services/functions.js";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
@@ -8,41 +9,55 @@ dotenv.config();
|
|||||||
import { loginFunc, changePassword } from "./database/userMgmt.database.js";
|
import { loginFunc, changePassword } from "./database/userMgmt.database.js";
|
||||||
import { sendMail } from "./services/mailer_v2.js";
|
import { sendMail } from "./services/mailer_v2.js";
|
||||||
|
|
||||||
router.post("/login", async (req, res) => {
|
router.post(
|
||||||
const result = await loginFunc(req.body.username, req.body.password);
|
"/login",
|
||||||
if (result.success) {
|
checkIfServiceIsActive("User Frontend"),
|
||||||
const token = await generateToken({
|
async (req, res) => {
|
||||||
username: result.data.username,
|
const result = await loginFunc(req.body.username, req.body.password);
|
||||||
is_admin: result.data.is_admin,
|
if (result.success) {
|
||||||
first_name: result.data.first_name,
|
const token = await generateToken({
|
||||||
last_name: result.data.last_name,
|
username: result.data.username,
|
||||||
role: result.data.role,
|
is_admin: result.data.is_admin,
|
||||||
});
|
first_name: result.data.first_name,
|
||||||
res.status(200).json({ message: "Login successful", token });
|
last_name: result.data.last_name,
|
||||||
} else {
|
role: result.data.role,
|
||||||
res.status(401).json({ message: "Invalid credentials" });
|
});
|
||||||
}
|
res.status(200).json({ message: "Login successful", token });
|
||||||
});
|
} else {
|
||||||
|
res.status(401).json({ message: "Invalid credentials" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post("/change-password", authenticate, async (req, res) => {
|
router.post(
|
||||||
const oldPassword = req.body.oldPassword;
|
"/change-password",
|
||||||
const newPassword = req.body.newPassword;
|
checkIfServiceIsActive("User Frontend"),
|
||||||
const username = req.user.username;
|
authenticate,
|
||||||
const result = await changePassword(username, oldPassword, newPassword);
|
async (req, res) => {
|
||||||
if (result.success) {
|
const oldPassword = req.body.oldPassword;
|
||||||
res.status(200).json({ message: "Password changed successfully" });
|
const newPassword = req.body.newPassword;
|
||||||
} else {
|
const username = req.user.username;
|
||||||
res.status(500).json({ message: "Failed to change password" });
|
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) => {
|
router.post(
|
||||||
const message = req.body.message;
|
"/contact",
|
||||||
const username = req.user.username;
|
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;
|
export default router;
|
||||||
|
|||||||
+12
-1
@@ -54,4 +54,15 @@ CREATE TABLE apiKeys (
|
|||||||
entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CHECK (api_key REGEXP '^[0-9]{8}$')
|
CHECK (api_key REGEXP '^[0-9]{8}$')
|
||||||
) ENGINE=InnoDB;
|
) 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")
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user