diff --git a/admin/tsconfig.app.json b/admin/tsconfig.app.json
index 8ad072c..c433433 100644
--- a/admin/tsconfig.app.json
+++ b/admin/tsconfig.app.json
@@ -29,7 +29,8 @@
"@/*": ["./src/*"]
},
- "forceConsistentCasingInFileNames": true
+ "forceConsistentCasingInFileNames": true,
+ "ignoreDeprecations": "6.0"
},
"include": ["src"]
}
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 4453be8..5ea46a4 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -14,7 +14,8 @@
"ejs": "^3.1.10",
"express": "^5.1.0",
"jose": "^6.0.12",
- "mysql2": "^3.14.3"
+ "mysql2": "^3.14.3",
+ "nodemailer": "^7.0.6"
}
},
"node_modules/accepts": {
@@ -713,6 +714,15 @@
"node": ">= 0.6"
}
},
+ "node_modules/nodemailer": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz",
+ "integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==",
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
diff --git a/backend/package.json b/backend/package.json
index 3614208..2215eb9 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -16,6 +16,7 @@
"ejs": "^3.1.10",
"express": "^5.1.0",
"jose": "^6.0.12",
- "mysql2": "^3.14.3"
+ "mysql2": "^3.14.3",
+ "nodemailer": "^7.0.6"
}
}
diff --git a/backend/routes/api.js b/backend/routes/api.js
index 05c6536..103eeef 100644
--- a/backend/routes/api.js
+++ b/backend/routes/api.js
@@ -25,9 +25,159 @@ import {
getAllApiKeys,
createAPIentry,
deleteAPKey,
+ getLoanInfoWithID,
} from "../services/database.js";
import { authenticate, generateToken } from "../services/tokenService.js";
const router = express.Router();
+import nodemailer from "nodemailer";
+import dotenv from "dotenv";
+dotenv.config();
+
+// Nice HTML + text templates for the loan email
+function buildLoanEmail({ user, items, startDate, endDate, createdDate }) {
+ const brand = process.env.MAIL_BRAND_COLOR || "#0ea5e9";
+ const itemsList =
+ Array.isArray(items) && items.length
+ ? `
${items
+ .map((i) => `- ${i}
`)
+ .join("")}
`
+ : "N/A";
+
+ return `
+
+
+
+
+
+
+
+
+
+
+
+ Neue Ausleihe erstellt
+ |
+
+
+
+ Es wurde eine neue Ausleihe angelegt. Hier sind die Details:
+
+
+ Benutzer |
+ ${
+ user || "N/A"
+ } |
+
+
+ Ausgeliehene Gegenstände |
+ ${itemsList} |
+
+
+ Startdatum |
+ ${formatDateTime(
+ startDate
+ )} |
+
+
+ Enddatum |
+ ${formatDateTime(
+ endDate
+ )} |
+
+
+ Erstellt am |
+ ${formatDateTime(
+ createdDate
+ )} |
+
+
+ Diese E-Mail wurde automatisch vom Ausleihsystem gesendet. Bitte nicht antworten.
+ |
+
+
+
+
+`;
+}
+
+function buildLoanEmailText({ user, items, startDate, endDate, createdDate }) {
+ const itemsText =
+ Array.isArray(items) && items.length ? items.join(", ") : "N/A";
+ return [
+ "Neue Ausleihe erstellt",
+ "",
+ `Benutzer: ${user || "N/A"}`,
+ `Gegenstände: ${itemsText}`,
+ `Start: ${formatDateTime(startDate)}`,
+ `Ende: ${formatDateTime(endDate)}`,
+ `Erstellt am: ${formatDateTime(createdDate)}`,
+ ].join("\n");
+}
+
+function sendMailLoan(user, items, startDate, endDate, createdDate) {
+ const transporter = nodemailer.createTransport({
+ host: process.env.MAIL_HOST,
+ port: process.env.MAIL_PORT,
+ secure: true,
+ auth: {
+ user: process.env.MAIL_USER,
+ pass: process.env.MAIL_PASSWORD,
+ },
+ });
+
+ (async () => {
+ const info = await transporter.sendMail({
+ from: '"Ausleihsystem" ',
+ to: process.env.MAIL_SENDEES,
+ subject: "Eine neue Ausleihe wurde erstellt!",
+ text: buildLoanEmailText({
+ user,
+ items,
+ startDate,
+ endDate,
+ createdDate,
+ }),
+ html: buildLoanEmail({ user, items, startDate, endDate, createdDate }),
+ });
+
+ console.log("Message sent:", info.messageId);
+ })();
+ console.log("sendMailLoan called");
+}
+
+// ...existing code...
+const formatDateTime = (value) => {
+ if (value == null) return "N/A";
+
+ const toOut = (d) => {
+ if (!(d instanceof Date) || isNaN(d.getTime())) return "N/A";
+ const dd = String(d.getDate()).padStart(2, "0");
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
+ const yyyy = d.getFullYear();
+ const hh = String(d.getHours()).padStart(2, "0");
+ const mi = String(d.getMinutes()).padStart(2, "0");
+ return `${dd}.${mm}.${yyyy} ${hh}:${mi} Uhr`;
+ };
+
+ if (value instanceof Date) return toOut(value);
+ if (typeof value === "number") return toOut(new Date(value));
+
+ const s = String(value).trim();
+
+ // Direct pattern: "YYYY-MM-DD[ T]HH:mm[:ss]"
+ const m = s.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::\d{2})?/);
+ if (m) {
+ const [, y, M, d, h, min] = m;
+ return `${d}.${M}.${y} ${h}:${min} Uhr`;
+ }
+
+ // ISO or other parseable formats
+ const dObj = new Date(s);
+ if (!isNaN(dObj.getTime())) return toOut(dObj);
+
+ return "N/A";
+};
+// ...existing code...
router.post("/login", async (req, res) => {
const result = await loginFunc(req.body.username, req.body.password);
@@ -158,6 +308,15 @@ router.post("/createLoan", authenticate, async (req, res) => {
);
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
+ );
return res.status(201).json({
message: "Loan created successfully",
loanId: result.data.id,
diff --git a/backend/services/database.js b/backend/services/database.js
index dacc1d1..91d0247 100644
--- a/backend/services/database.js
+++ b/backend/services/database.js
@@ -182,6 +182,16 @@ export const getBorrowableItemsFromDatabase = async (
return { success: false };
};
+export const getLoanInfoWithID = async (loanId) => {
+ const [rows] = await pool.query("SELECT * FROM loans WHERE id = ?;", [
+ loanId,
+ ]);
+ if (rows.length > 0) {
+ return { success: true, data: rows[0] };
+ }
+ return { success: false };
+};
+
export const createLoanInDatabase = async (
username,
startDate,