Merge branch 'dev' into debian12

This commit is contained in:
2026-02-20 16:30:34 +01:00
16 changed files with 271 additions and 499 deletions

View File

@@ -22,7 +22,7 @@ export const getItemsFromDatabaseV2 = async () => {
export const getLoanByCodeV2 = async (loan_code) => {
const [result] = await pool.query(
"SELECT username, returned_date, take_date, lockers FROM loans WHERE loan_code = ?;",
[loan_code]
[loan_code],
);
if (result.length > 0) {
return { success: true, data: result[0] };
@@ -33,7 +33,7 @@ export const getLoanByCodeV2 = async (loan_code) => {
export const changeInSafeStateV2 = async (itemId) => {
const [result] = await pool.query(
"UPDATE items SET in_safe = NOT in_safe WHERE id = ?",
[itemId]
[itemId],
);
if (result.affectedRows > 0) {
return { success: true };
@@ -42,50 +42,62 @@ export const changeInSafeStateV2 = async (itemId) => {
};
export const setReturnDateV2 = async (loanCode) => {
const [items] = await pool.query(
"SELECT loaned_items_id FROM loans WHERE loan_code = ?",
[loanCode]
);
try {
const [items] = await pool.query(
"SELECT loaned_items_id, username FROM loans WHERE loan_code = ?",
[loanCode],
);
const [owner] = await pool.query(
"SELECT username FROM loans WHERE loan_code = ?",
[loanCode]
);
if (items.length === 0)
return { success: false, message: "No items found for loan" };
if (items.length === 0) return { success: false };
const itemIds = Array.isArray(items[0].loaned_items_id)
? items[0].loaned_items_id
: JSON.parse(items[0].loaned_items_id || "[]");
const itemIds = Array.isArray(items[0].loaned_items_id)
? items[0].loaned_items_id
: JSON.parse(items[0].loaned_items_id || "[]");
const [result] = await pool.query(
"UPDATE loans SET returned_date = NOW() WHERE loan_code = ? AND returned_date IS NULL",
[loanCode],
);
const [setItemStates] = await pool.query(
"UPDATE items SET in_safe = 1, currently_borrowing = NULL, last_borrowed_person = (?) WHERE id IN (?)",
[owner[0].username, itemIds]
);
if (result.affectedRows === 0) return { success: false };
const [result] = await pool.query(
"UPDATE loans SET returned_date = NOW() WHERE loan_code = ?",
[loanCode]
);
if (itemIds.length > 0) {
await pool.query(
"UPDATE items SET in_safe = 1, currently_borrowing = NULL, last_borrowed_person = ? WHERE id IN (?)",
[items[0].username, itemIds],
);
}
if (result.affectedRows > 0 && setItemStates.affectedRows > 0) {
return { success: true };
return { success: true, data: { returned: true } };
} catch (error) {
console.error("setReturnDateV2 error:", error);
return { success: false, message: "Failed to set return date" };
}
return { success: false };
};
export const setTakeDateV2 = async (loanCode) => {
const [isTaken] = await pool.query(
"SELECT take_date FROM loans WHERE loan_code = ?",
[loanCode],
);
if (isTaken.length === 0 || isTaken[0].take_date !== null) {
return { success: false, message: "Loan not found or already taken" };
}
const [items] = await pool.query(
"SELECT loaned_items_id FROM loans WHERE loan_code = ?",
[loanCode]
[loanCode],
);
const [owner] = await pool.query(
"SELECT username FROM loans WHERE loan_code = ?",
[loanCode]
[loanCode],
);
if (items.length === 0) return { success: false };
if (items.length === 0)
return { success: false, message: "No items found for loan" };
const itemIds = Array.isArray(items[0].loaned_items_id)
? items[0].loaned_items_id
@@ -93,18 +105,18 @@ export const setTakeDateV2 = async (loanCode) => {
const [setItemStates] = await pool.query(
"UPDATE items SET in_safe = 0, currently_borrowing = (?) WHERE id IN (?)",
[owner[0].username, itemIds]
[owner[0].username, itemIds],
);
const [result] = await pool.query(
"UPDATE loans SET take_date = NOW() WHERE loan_code = ?",
[loanCode]
"UPDATE loans SET take_date = NOW() WHERE loan_code = ? AND take_date IS NULL",
[loanCode],
);
if (result.affectedRows > 0 && setItemStates.affectedRows > 0) {
return { success: true };
}
return { success: false };
return { message: "Failed to set take date", success: false };
};
export const getAllLoansV2 = async () => {
@@ -118,12 +130,12 @@ export const getAllLoansV2 = async () => {
export const openDoor = async (doorKey) => {
const [result] = await pool.query(
"SELECT safe_nr, id FROM items WHERE door_key = ?;",
[doorKey]
[doorKey],
);
if (result.length > 0) {
const [changeItemSate] = await pool.query(
"UPDATE items SET in_safe = NOT in_safe WHERE id = ?",
[result[0].id]
[result[0].id],
);
if (changeItemSate.affectedRows > 0) {
return { success: true, data: result[0] };

View File

@@ -47,7 +47,7 @@ router.get(
} else {
res.status(404).json({ message: "Loan not found" });
}
}
},
);
// Route for API to set the return date by the loan code
@@ -58,11 +58,11 @@ router.post(
const loanCode = req.params.loan_code;
const result = await setReturnDateV2(loanCode);
if (result.success) {
res.status(200).json({ data: result.data });
res.status(200).json({});
} else {
res.status(500).json({ message: "Failed to set return date" });
}
}
},
);
// Route for API to set the take away date by the loan code
@@ -73,11 +73,11 @@ router.post(
const loanCode = req.params.loan_code;
const result = await setTakeDateV2(loanCode);
if (result.success) {
res.status(200).json({ data: result.data });
res.status(200).json({});
} else {
res.status(500).json({ message: "Failed to set take date" });
res.status(500).json({ message: result.message });
}
}
},
);
// Route for API to open a door

View File

@@ -62,6 +62,7 @@ router.post("/createLoan", authenticate, async (req, res) => {
mailInfo.data.start_date,
mailInfo.data.end_date,
mailInfo.data.created_at,
mailInfo.data.note,
);
return res.status(201).json({
message: "Loan created successfully",

View File

@@ -34,7 +34,14 @@ const formatDateTime = (value) => {
return "N/A";
};
function buildLoanEmail({ user, items, startDate, endDate, createdDate }) {
function buildLoanEmail({
user,
items,
startDate,
endDate,
createdDate,
note,
}) {
const brand = process.env.MAIL_BRAND_COLOR || "#0ea5e9";
const itemsList =
Array.isArray(items) && items.length
@@ -116,6 +123,12 @@ function buildLoanEmail({ user, items, startDate, endDate, createdDate }) {
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;">
@@ -134,7 +147,14 @@ function buildLoanEmail({ user, items, startDate, endDate, createdDate }) {
</html>`;
}
function buildLoanEmailText({ user, items, startDate, endDate, createdDate }) {
function buildLoanEmailText({
user,
items,
startDate,
endDate,
createdDate,
note,
}) {
const itemsText =
Array.isArray(items) && items.length ? items.join(", ") : "N/A";
return [
@@ -145,10 +165,18 @@ function buildLoanEmailText({ user, items, startDate, endDate, createdDate }) {
`Start: ${formatDateTime(startDate)}`,
`Ende: ${formatDateTime(endDate)}`,
`Erstellt am: ${formatDateTime(createdDate)}`,
`Notiz: ${note || "Keine Notiz"}`,
].join("\n");
}
export function sendMailLoan(user, items, startDate, endDate, createdDate) {
export function sendMailLoan(
user,
items,
startDate,
endDate,
createdDate,
note,
) {
const transporter = nodemailer.createTransport({
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
@@ -170,8 +198,16 @@ export function sendMailLoan(user, items, startDate, endDate, createdDate) {
startDate,
endDate,
createdDate,
note,
}),
html: buildLoanEmail({
user,
items,
startDate,
endDate,
createdDate,
note,
}),
html: buildLoanEmail({ user, items, startDate, endDate, createdDate }),
});
console.log("Loan message sent:", info.messageId);

View File

@@ -1,120 +0,0 @@
USE borrow_system_new;
-- Reset tables (no FKs defined, so order is safe)
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE loans;
TRUNCATE TABLE apiKeys;
TRUNCATE TABLE items;
TRUNCATE TABLE users;
SET FOREIGN_KEY_CHECKS = 1;
-- Users (roles 16, plain-text passwords; is_admin is BOOL)
INSERT INTO users (username, password, email, first_name, last_name, role, is_admin) VALUES
('admin', 'adminpass', 'admin@example.com', 'System', 'Admin', 6, TRUE),
('alice', 'alice123', 'alice@example.com', 'Alice', 'Andersen',1, FALSE),
('bob', 'bob12345', 'bob@example.com', 'Bob', 'Berg', 2, FALSE),
('carol', 'carol123', 'carol@example.com', 'Carol', 'Christensen', 3, FALSE),
('dave', 'dave123', 'dave@example.com', 'Dave', 'Dahl', 4, FALSE),
('erin', 'erin123', 'erin@example.com', 'Erin', 'Enevoldsen', 5, FALSE),
('frank', 'frank123', 'frank@example.com', 'Frank', 'Fisher', 2, FALSE),
('grace', 'grace123', 'grace@example.com', 'Grace', 'Gundersen',1, FALSE),
('heidi', 'heidi123', 'heidi@example.com', 'Heidi', 'Hansen', 4, FALSE),
('tech', 'techpass', 'tech@example.com', 'Tech', 'User', 5, TRUE);
-- Items (safe_nr is two digits or NULL; matches CHECK and UNIQUE constraint)
INSERT INTO items (item_name, can_borrow_role, in_safe, safe_nr, last_borrowed_person, currently_borrowing) VALUES
('Laptop A', 2, FALSE, NULL, 'grace', 'bob'),
('Laptop B', 2, TRUE, '01', NULL, NULL),
('Camera Canon', 3, TRUE, '02', 'erin', NULL),
('Microphone Rode', 1, TRUE, '03', 'grace', NULL),
('Tripod Manfrotto', 1, TRUE, '04', 'frank', NULL),
('Oscilloscope Tek', 4, TRUE, '05', NULL, NULL),
('VR Headset', 3, FALSE, NULL, 'heidi', 'carol'),
('Keycard Programmer', 6, TRUE, '06', 'admin', NULL);
-- Loans (JSON strings, 6-digit numeric loan_code per CHECK)
-- Assumes the items above have ids 1..8 in insert order
INSERT INTO loans (
username,
lockers,
loan_code,
start_date,
end_date,
take_date,
returned_date,
loaned_items_id,
loaned_items_name,
deleted,
note
) VALUES
-- Active loan: bob has Laptop A (item id 1, locker "01")
('bob',
'["01"]',
'123456',
'2025-11-15 09:00:00',
'2025-11-22 17:00:00',
'2025-11-15 09:15:00',
NULL,
'[1]',
'["Laptop A"]',
FALSE,
'Active loan - Laptop A'
),
-- Returned loan: frank had Tripod Manfrotto (item id 5, locker "04")
('frank',
'["04"]',
'234567',
'2025-10-01 10:00:00',
'2025-10-07 16:00:00',
'2025-10-01 10:05:00',
'2025-10-05 15:30:00',
'[5]',
'["Tripod Manfrotto"]',
FALSE,
'Completed loan'
),
-- Future reservation: dave will take Oscilloscope Tek (item id 6, locker "05")
('dave',
'["05"]',
'345678',
'2025-12-10 09:00:00',
'2025-12-12 17:00:00',
NULL,
NULL,
'[6]',
'["Oscilloscope Tek"]',
FALSE,
'Reserved'
),
-- Active loan: carol has VR Headset (item id 7, locker "02")
('carol',
'["02"]',
'456789',
'2025-11-10 13:00:00',
'2025-11-20 12:00:00',
'2025-11-10 13:10:00',
NULL,
'[7]',
'["VR Headset"]',
FALSE,
'Active loan - VR Headset'
),
-- Soft-deleted historic loan: grace had Microphone + Tripod (item ids 4,5; lockers "03","04")
('grace',
'["03","04"]',
'567890',
'2025-09-01 09:00:00',
'2025-09-03 17:00:00',
'2025-09-01 09:10:00',
'2025-09-03 16:45:00',
'[4,5]',
'["Microphone Rode","Tripod Manfrotto"]',
TRUE,
'Canceled/soft-deleted record'
);
-- API keys (8-digit numeric keys per CHECK)
INSERT INTO apiKeys (api_key, entry_name, last_used_at) VALUES
('12345678', 'CI token', '2025-11-15 08:00:00'),
('87654321', 'Local dev', NULL),
('00000001', 'Monitoring', '2025-11-10 12:30:00');