Merge branch 'dev_demo' into host_demo
This commit is contained in:
@@ -17,7 +17,10 @@ export const getAllLoans = async () => {
|
||||
};
|
||||
|
||||
export const deleteLoanById = async (loanId) => {
|
||||
const [result] = await pool.query("DELETE FROM loans WHERE id = ?", [loanId]);
|
||||
const [result] = await pool.query(
|
||||
"UPDATE loans SET deleted = true, deleted_admin = true WHERE id = ?",
|
||||
[loanId],
|
||||
);
|
||||
if (result.affectedRows > 0) return { success: true };
|
||||
return { success: false };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
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 const getAllFunctions = async () => {
|
||||
const [rows] = await pool.query("SELECT * FROM functions");
|
||||
return { success: true, data: rows };
|
||||
};
|
||||
|
||||
export const updateFunctionStatus = async (functionName, active) => {
|
||||
const [result] = await pool.query(
|
||||
"UPDATE functions SET active = ? WHERE function_name = ?",
|
||||
[active, functionName],
|
||||
);
|
||||
if (result.affectedRows > 0) return { success: true };
|
||||
return { success: false };
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import express from "express";
|
||||
import { authenticateAdmin } from "../../services/authentication.js";
|
||||
const router = express.Router();
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// database funcs import
|
||||
import {
|
||||
getAllFunctions,
|
||||
updateFunctionStatus,
|
||||
} from "./database/serverConfMgmt.database.js";
|
||||
|
||||
// Route to get all functions and their statuses
|
||||
router.get("/all", async (req, res) => {
|
||||
try {
|
||||
const result = await getAllFunctions();
|
||||
if (result.success) {
|
||||
res.status(200).json({ data: result.data });
|
||||
} else {
|
||||
res.status(500).json({ message: "Failed to fetch functions" });
|
||||
}
|
||||
} catch (error) {
|
||||
res
|
||||
.status(500)
|
||||
.json({ message: "An error occurred", error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to update the status of a function
|
||||
router.post("/update", async (req, res) => {
|
||||
const functionName = req.query.functionName;
|
||||
let active = req.query.active;
|
||||
|
||||
if (active === "false") {
|
||||
active = 0;
|
||||
} else if (active === "true") {
|
||||
active = 1;
|
||||
} else {
|
||||
res.status(406).json({ message: "Got unexpected format" });
|
||||
}
|
||||
|
||||
const result = await updateFunctionStatus(functionName, active);
|
||||
if (result.success) {
|
||||
res.status(200).json({ message: "Function status updated successfully" });
|
||||
} else {
|
||||
res.status(500).json({ message: "Failed to update function status" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,9 +1,12 @@
|
||||
import express from "express";
|
||||
import { authenticate } from "../../services/authentication.js";
|
||||
import { checkIfServiceIsActive } from "../../services/functions.js";
|
||||
const router = express.Router();
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const loan_service = "Loan Service";
|
||||
|
||||
import {
|
||||
getItemsFromDatabaseV2,
|
||||
changeInSafeStateV2,
|
||||
@@ -39,6 +42,7 @@ router.post("/change-state/:key/:itemId", authenticate, async (req, res) => {
|
||||
router.get(
|
||||
"/get-loan-by-code/:key/:loan_code",
|
||||
authenticate,
|
||||
checkIfServiceIsActive(loan_service),
|
||||
async (req, res) => {
|
||||
const loan_code = req.params.loan_code;
|
||||
const result = await getLoanByCodeV2(loan_code);
|
||||
@@ -54,6 +58,7 @@ router.get(
|
||||
router.post(
|
||||
"/set-return-date/:key/:loan_code",
|
||||
authenticate,
|
||||
checkIfServiceIsActive(loan_service),
|
||||
async (req, res) => {
|
||||
const loanCode = req.params.loan_code;
|
||||
const result = await setReturnDateV2(loanCode);
|
||||
@@ -69,6 +74,7 @@ router.post(
|
||||
router.post(
|
||||
"/set-take-date/:key/:loan_code",
|
||||
authenticate,
|
||||
checkIfServiceIsActive(loan_service),
|
||||
async (req, res) => {
|
||||
const loanCode = req.params.loan_code;
|
||||
const result = await setTakeDateV2(loanCode);
|
||||
|
||||
@@ -234,6 +234,23 @@ export const getBorrowableItemsFromDatabase = async (
|
||||
};
|
||||
|
||||
export const SETdeleteLoanFromDatabase = async (loanId) => {
|
||||
const [checkIfdatesReturned] = await pool.query(
|
||||
"SELECT take_date, returned_date FROM loans WHERE id = ? AND deleted = 0",
|
||||
[loanId],
|
||||
);
|
||||
|
||||
if (checkIfdatesReturned.length === 0) {
|
||||
return { success: false, code: "LOAN_NOT_FOUND" };
|
||||
}
|
||||
|
||||
const { take_date, returned_date } = checkIfdatesReturned[0];
|
||||
const bothNull = take_date === null && returned_date === null;
|
||||
const bothSet = take_date !== null && returned_date !== null;
|
||||
|
||||
if (!(bothNull || bothSet)) {
|
||||
return { success: false, code: "LOAN_NOT_RETURNED" };
|
||||
}
|
||||
|
||||
const [result] = await pool.query(
|
||||
"UPDATE loans SET deleted = 1 WHERE id = ?;",
|
||||
[loanId],
|
||||
|
||||
@@ -14,7 +14,7 @@ const pool = mysql
|
||||
export const loginFunc = async (username, password) => {
|
||||
const [result] = await pool.query(
|
||||
"SELECT * FROM users WHERE username = ? AND password = ?",
|
||||
[username, password]
|
||||
[username, password],
|
||||
);
|
||||
if (result.length > 0) return { success: true, data: result[0] };
|
||||
return { success: false };
|
||||
@@ -40,7 +40,7 @@ export const changePassword = async (username, oldPassword, newPassword) => {
|
||||
// get user current password
|
||||
const [user] = await pool.query(
|
||||
"SELECT * FROM users WHERE username = ? AND password = ?",
|
||||
[username, oldPassword]
|
||||
[username, oldPassword],
|
||||
);
|
||||
if (user.length === 0) return { success: false };
|
||||
|
||||
@@ -48,8 +48,16 @@ export const changePassword = async (username, oldPassword, newPassword) => {
|
||||
|
||||
const [result] = await pool.query(
|
||||
"UPDATE users SET password = ? WHERE username = ?",
|
||||
[newPassword, username]
|
||||
[newPassword, username],
|
||||
);
|
||||
if (result.affectedRows > 0) return { success: true };
|
||||
return { success: false };
|
||||
};
|
||||
|
||||
export const getDeactivatedServices = async () => {
|
||||
const [rows] = await pool.query("SELECT function_name FROM functions WHERE active = 0;");
|
||||
if (rows.length > 0) {
|
||||
return { success: true, data: rows };
|
||||
}
|
||||
return { success: false };
|
||||
};
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import express from "express";
|
||||
import { authenticate, generateToken } from "../../services/authentication.js";
|
||||
const router = express.Router();
|
||||
import {
|
||||
checkIfServiceIsActive,
|
||||
checkIfServiceIsActive2,
|
||||
} from "../../services/functions.js";
|
||||
|
||||
// mailer imports
|
||||
import { sendMail } from "../../services/mailer/send.js";
|
||||
import { loanMail } from "../../services/mailer/templates/loan_created.js";
|
||||
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
const router = express.Router();
|
||||
|
||||
const loan_service = "Loan Service";
|
||||
const loan_mailer_service = "Loan Mailer";
|
||||
|
||||
// database funcs import
|
||||
import {
|
||||
@@ -16,108 +28,135 @@ import {
|
||||
setReturnDate,
|
||||
setTakeDate,
|
||||
} 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_service)) {
|
||||
const mailInfo = await getLoanInfoWithID(result.data.id);
|
||||
console.log(mailInfo);
|
||||
const { html, text } = loanMail(
|
||||
req.user.first_name + " " + req.user.last_name,
|
||||
mailInfo.data.loaned_items_name,
|
||||
mailInfo.data.start_date,
|
||||
mailInfo.data.end_date,
|
||||
mailInfo.data.created_at,
|
||||
mailInfo.data.note,
|
||||
);
|
||||
await sendMail({
|
||||
to: process.env.MAIL_SENDEES,
|
||||
subject: "Neue Ausleihe erstellt!",
|
||||
html,
|
||||
text,
|
||||
});
|
||||
}
|
||||
|
||||
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,46 +167,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 {
|
||||
res.status(500).json({ message: "Failed to delete loan" });
|
||||
}
|
||||
});
|
||||
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" });
|
||||
}
|
||||
|
||||
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" });
|
||||
}
|
||||
});
|
||||
if (result.code === "LOAN_NOT_RETURNED") {
|
||||
res.status(507).json({
|
||||
message: "Cannot delete loan that has not been returned",
|
||||
});
|
||||
}
|
||||
|
||||
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" });
|
||||
}
|
||||
res.status(500).json({ message: "Failed to delete loan" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
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" });
|
||||
}
|
||||
});
|
||||
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" });
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
import nodemailer from "nodemailer";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
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";
|
||||
};
|
||||
|
||||
function buildLoanEmail({
|
||||
user,
|
||||
items,
|
||||
startDate,
|
||||
endDate,
|
||||
createdDate,
|
||||
note,
|
||||
}) {
|
||||
const brand = process.env.MAIL_BRAND_COLOR || "#0ea5e9";
|
||||
const itemsList =
|
||||
Array.isArray(items) && items.length
|
||||
? `<ul style="margin:4px 0 0 18px; padding:0;">${items
|
||||
.map(
|
||||
(i) =>
|
||||
`<li style="margin:2px 0; color:#111827; line-height:1.3;">${i}</li>`,
|
||||
)
|
||||
.join("")}</ul>`
|
||||
: "<span style='color:#111827;'>N/A</span>";
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="color-scheme" content="light">
|
||||
<meta name="supported-color-schemes" content="light">
|
||||
<meta name="x-apple-disable-message-reformatting">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
:root { color-scheme: light; supported-color-schemes: light; }
|
||||
body { margin:0; padding:0; }
|
||||
/* Mobile stacking */
|
||||
@media (max-width:480px) {
|
||||
.outer { width:100% !important; }
|
||||
.pad-sm { padding:16px !important; }
|
||||
.w-label { width:120px !important; }
|
||||
}
|
||||
/* Dark-mode override safety */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body, table, td, p, a, h1, h2, h3 { background:#ffffff !important; color:#111827 !important; }
|
||||
.brand-header { background:${brand} !important; color:#ffffff !important; }
|
||||
a { color:${brand} !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body bgcolor="#ffffff" style="background:#ffffff; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif; color:#111827; -webkit-text-size-adjust:100%;">
|
||||
<!-- Preheader (hidden) -->
|
||||
<div style="display:none; max-height:0; overflow:hidden; opacity:0; mso-hide:all;">
|
||||
Neue Ausleihe erstellt – Übersicht der Buchung.
|
||||
</div>
|
||||
<div role="article" aria-roledescription="email" lang="de" style="padding:24px; background:#f2f4f7;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" class="outer" style="max-width:600px; margin:0 auto; background:#ffffff; border:1px solid #e5e7eb; border-radius:14px; overflow:hidden;">
|
||||
<tr>
|
||||
<td class="brand-header" style="padding:22px 26px; background:${brand}; color:#ffffff;">
|
||||
<h1 style="margin:0; font-size:18px; line-height:1.35; font-weight:600;">Neue Ausleihe erstellt</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pad-sm" style="padding:24px 26px; color:#111827;">
|
||||
<p style="margin:0 0 14px 0; line-height:1.4;">Es wurde eine neue Ausleihe angelegt. Hier sind die Details:</p>
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="border-collapse:collapse; font-size:14px; line-height:1.3; background:#fcfcfd; border:1px solid #e5e7eb; border-radius:10px; overflow:hidden;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="w-label" style="padding:10px 14px; color:#6b7280; width:170px; border-bottom:1px solid #ececec;">Benutzer</td>
|
||||
<td style="padding:10px 14px; font-weight:600; border-bottom:1px solid #ececec; color:#111827;">${
|
||||
user || "N/A"
|
||||
}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 14px; color:#6b7280; vertical-align:top; border-bottom:1px solid #ececec;">Ausgeliehene Gegenstände</td>
|
||||
<td style="padding:10px 14px; font-weight:600; border-bottom:1px solid #ececec; color:#111827;">${itemsList}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 14px; color:#6b7280; border-bottom:1px solid #ececec;">Startdatum</td>
|
||||
<td style="padding:10px 14px; font-weight:600; border-bottom:1px solid #ececec; color:#111827;">${formatDateTime(
|
||||
startDate,
|
||||
)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 14px; color:#6b7280; border-bottom:1px solid #ececec;">Enddatum</td>
|
||||
<td style="padding:10px 14px; font-weight:600; border-bottom:1px solid #ececec; color:#111827;">${formatDateTime(
|
||||
endDate,
|
||||
)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 14px; color:#6b7280;">Erstellt am</td>
|
||||
<td style="padding:10px 14px; font-weight:600; color:#111827;">${formatDateTime(
|
||||
createdDate,
|
||||
)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 14px; color:#6b7280; vertical-align:top;">Notiz</td>
|
||||
<td style="padding:10px 14px; font-weight:600; color:#111827;">${
|
||||
note || "Keine Notiz"
|
||||
}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="margin:22px 0 0 0; font-size:14px;">
|
||||
<a href="https://admin.insta.the1s.de/api" style="display:inline-block; background:${brand}; color:#ffffff; text-decoration:none; padding:10px 16px; border-radius:6px; font-weight:600; font-size:14px;" target="_blank" rel="noopener noreferrer">
|
||||
Übersicht öffnen
|
||||
</a>
|
||||
</p>
|
||||
<p style="margin:18px 0 0 0; font-size:12px; color:#6b7280; line-height:1.4;">
|
||||
Diese E-Mail wurde automatisch vom Ausleihsystem gesendet. Bitte nicht antworten.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function buildLoanEmailText({
|
||||
user,
|
||||
items,
|
||||
startDate,
|
||||
endDate,
|
||||
createdDate,
|
||||
note,
|
||||
}) {
|
||||
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)}`,
|
||||
`Notiz: ${note || "Keine Notiz"}`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function sendMailLoan(
|
||||
user,
|
||||
items,
|
||||
startDate,
|
||||
endDate,
|
||||
createdDate,
|
||||
note,
|
||||
) {
|
||||
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" <noreply@mcs-medien.de>',
|
||||
to: process.env.MAIL_SENDEES,
|
||||
subject: "Eine neue Ausleihe wurde erstellt!",
|
||||
text: buildLoanEmailText({
|
||||
user,
|
||||
items,
|
||||
startDate,
|
||||
endDate,
|
||||
createdDate,
|
||||
note,
|
||||
}),
|
||||
html: buildLoanEmail({
|
||||
user,
|
||||
items,
|
||||
startDate,
|
||||
endDate,
|
||||
createdDate,
|
||||
note,
|
||||
}),
|
||||
});
|
||||
|
||||
console.log("Loan message sent:", info.messageId);
|
||||
})();
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import nodemailer from "nodemailer";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
export function sendMail(username, message) {
|
||||
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 mailText = `Neue Kontaktanfrage im Ausleihsystem.\n\nBenutzername: ${username}\n\nNachricht:\n${message}`;
|
||||
|
||||
const mailHtml = `<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Neue Nachricht im Ausleihsystem</title>
|
||||
</head>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.5; color: #222;">
|
||||
<h2>Neue Nachricht im Ausleihsystem</h2>
|
||||
<p><strong>Benutzername:</strong> ${username}</p>
|
||||
<p><strong>Nachricht:</strong></p>
|
||||
<p style="white-space: pre-line;">${message}</p>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const info = await transporter.sendMail({
|
||||
from: '"Ausleihsystem" <noreply@mcs-medien.de>',
|
||||
to: process.env.MAIL_SENDEES_CONTACT,
|
||||
subject: "Sie haben eine neue Nachricht!",
|
||||
text: mailText,
|
||||
html: mailHtml,
|
||||
});
|
||||
|
||||
console.log("Contact message sent: %s", info.messageId);
|
||||
})();
|
||||
}
|
||||
@@ -1,48 +1,99 @@
|
||||
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();
|
||||
|
||||
const user_frontend_service = "User Frontend";
|
||||
const contact_form_service = "Contact Form Service";
|
||||
|
||||
// database funcs import
|
||||
import { loginFunc, changePassword } from "./database/userMgmt.database.js";
|
||||
import { sendMail } from "./services/mailer_v2.js";
|
||||
import {
|
||||
loginFunc,
|
||||
changePassword,
|
||||
getDeactivatedServices,
|
||||
} from "./database/userMgmt.database.js";
|
||||
|
||||
router.post("/login", async (req, res) => {
|
||||
const result = await loginFunc(req.body.username, req.body.password);
|
||||
// mailer imports
|
||||
import { sendMail } from "../../services/mailer/send.js";
|
||||
import { contactMail } from "../../services/mailer/templates/contact.js";
|
||||
|
||||
router.post(
|
||||
"/login",
|
||||
checkIfServiceIsActive(user_frontend_service),
|
||||
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",
|
||||
checkIfServiceIsActive(user_frontend_service),
|
||||
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",
|
||||
checkIfServiceIsActive(contact_form_service),
|
||||
authenticate,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const message = req.body?.message;
|
||||
const username = req.user?.first_name + " " + req.user?.last_name;
|
||||
|
||||
if (!username || !message) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: "Username and message are required" });
|
||||
}
|
||||
|
||||
const { html, text } = contactMail({ username, message });
|
||||
await sendMail({
|
||||
to: process.env.MAIL_SENDEES_CONTACT,
|
||||
subject: "Neue Nachricht!",
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
res.status(200).json({ message: "Contact message sent successfully" });
|
||||
} catch (error) {
|
||||
console.error("Failed to send contact mail:", error);
|
||||
res.status(500).json({ message: "Failed to send contact message" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.get("/deactivated-services", authenticate, async (req, res) => {
|
||||
const result = await getDeactivatedServices();
|
||||
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 });
|
||||
res.status(200).json(result.data);
|
||||
} else {
|
||||
res.status(401).json({ message: "Invalid credentials" });
|
||||
res.status(500).json({ message: "Failed to fetch deactivated services" });
|
||||
}
|
||||
});
|
||||
|
||||
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("/contact", authenticate, async (req, res) => {
|
||||
const message = req.body.message;
|
||||
const username = req.user.username;
|
||||
|
||||
sendMail(username, message);
|
||||
|
||||
res.status(200).json({ message: "Contact message sent successfully" });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user