From c47c311ecd2a4874968e9b3c3acf2871ab2b53a1 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 3 Sep 2025 13:11:03 +0200 Subject: [PATCH 01/12] fixed issue/bug: #7 --- backend/services/database.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/services/database.js b/backend/services/database.js index d284d8a..8848eb9 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -87,11 +87,8 @@ export const getItemsFromDatabase = async (role) => { }; export const getLoansFromDatabase = async () => { - const [result] = await pool.query("SELECT * FROM loans;"); - if (result.length > 0) { - return { success: true, data: result }; - } - return { success: false }; + const [rows] = await pool.query("SELECT * FROM loans;"); + return { success: true, data: rows.length > 0 ? rows : null }; }; export const getUserLoansFromDatabase = async (username) => { From 16e1dca43cd8bec38847153081b7b9545e9b3b95 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 3 Sep 2025 13:12:46 +0200 Subject: [PATCH 02/12] fixed issue/bug: #10 --- admin/src/components/LoanTable.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/src/components/LoanTable.tsx b/admin/src/components/LoanTable.tsx index 43dcb47..a0e1887 100644 --- a/admin/src/components/LoanTable.tsx +++ b/admin/src/components/LoanTable.tsx @@ -78,10 +78,6 @@ const LoanTable: React.FC = () => { return ( <> - - Ausleihen - - {/* Action toolbar */} { {/* End action toolbar */} + + Ausleihen + + {isError && ( Date: Wed, 3 Sep 2025 13:14:30 +0200 Subject: [PATCH 03/12] fixed bug/issue: #11 --- admin/src/components/ItemTable.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/src/components/ItemTable.tsx b/admin/src/components/ItemTable.tsx index a1c9714..acc83fe 100644 --- a/admin/src/components/ItemTable.tsx +++ b/admin/src/components/ItemTable.tsx @@ -24,6 +24,7 @@ import Cookies from "js-cookie"; import { useState, useEffect } from "react"; import { deleteItem } from "@/utils/userActions"; import AddItemForm from "./AddItemForm"; +import { formatDateTime } from "@/utils/userFuncs"; type Items = { id: number; @@ -232,7 +233,7 @@ const ItemTable: React.FC = () => { )} - {item.entry_created_at} + {formatDateTime(item.entry_created_at)} + + + + + ); +}; + +export default ChangePWform; diff --git a/admin/src/components/UserTable.tsx b/admin/src/components/UserTable.tsx index 6d91078..ccd47e6 100644 --- a/admin/src/components/UserTable.tsx +++ b/admin/src/components/UserTable.tsx @@ -18,6 +18,7 @@ import { handleDelete, handleEdit } from "@/utils/userActions"; import MyAlert from "./myChakra/MyAlert"; import AddForm from "./AddForm"; import { formatDateTime } from "@/utils/userFuncs"; +import ChangePWform from "./ChangePWform"; type User = { id: number; @@ -36,6 +37,8 @@ const UserTable: React.FC = () => { const [errorDsc, setErrorDsc] = useState(""); const [reload, setReload] = useState(false); const [addForm, setAddForm] = useState(false); + const [changePWform, setChangePWform] = useState(false); + const [changeUsr, setChangeUsr] = useState(""); const setError = ( status: "error" | "success", @@ -57,6 +60,11 @@ const UserTable: React.FC = () => { ); }; + const handlePasswordChange = (username: string) => { + setChangeUsr(username); + setChangePWform(true); + }; + useEffect(() => { const fetchUsers = async () => { setIsLoading(true); @@ -139,6 +147,16 @@ const UserTable: React.FC = () => { Benutzer + {changePWform && ( + { + setChangePWform(false); + setReload(!reload); + }} + alert={setError} + username={changeUsr} + /> + )} {isError && ( { Benutzername - Passwort + Passwort ändern Rolle @@ -198,12 +216,9 @@ const UserTable: React.FC = () => { /> - - handleInputChange(user.id, "password", e.target.value) - } - value={user.password} - /> + { + try { + const response = await fetch(`http://localhost:8002/api/changePWadmin`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${Cookies.get("token")}`, + }, + body: JSON.stringify({ newPassword, username }), + }); + if (!response.ok) { + throw new Error("Failed to change password"); + } + return { success: true }; + } catch (error) { + console.error("Error changing password:", error); + return { success: false }; + } +}; + export const deleteLoan = async (loanId: number) => { try { const response = await fetch( diff --git a/backend/routes/api.js b/backend/routes/api.js index a4b12c1..6ed2dc6 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -18,6 +18,7 @@ import { getAllItems, deleteItemID, createItem, + changeUserPassword, } from "../services/database.js"; import { authenticate, generateToken } from "../services/tokenService.js"; const router = express.Router(); @@ -276,4 +277,17 @@ router.post("/createItem", authenticate, async (req, res) => { return res.status(500).json({ message: "Failed to create item" }); }); +router.post("/changePWadmin", authenticate, async (req, res) => { + const newPassword = req.body.newPassword; + if (!newPassword) { + return res.status(400).json({ message: "New password is required" }); + } + + const result = await changeUserPassword(req.body.username, newPassword); + if (result.success) { + return res.status(200).json({ message: "Password changed successfully" }); + } + return res.status(500).json({ message: "Failed to change password" }); +}); + export default router; diff --git a/backend/services/database.js b/backend/services/database.js index 8848eb9..d692234 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -327,7 +327,9 @@ export const loginAdmin = async (username, password) => { }; export const getAllUsers = async () => { - const [result] = await pool.query("SELECT * FROM users"); + const [result] = await pool.query( + "SELECT id, username, role, entry_created_at FROM users" + ); if (result.length > 0) return { success: true, data: result }; return { success: false }; }; @@ -382,3 +384,12 @@ export const createItem = async (item_name, can_borrow_role) => { if (result.affectedRows > 0) return { success: true }; return { success: false }; }; + +export const changeUserPassword = async (username, newPassword) => { + const [result] = await pool.query( + "UPDATE users SET password = ? WHERE username = ?", + [newPassword, username] + ); + if (result.affectedRows > 0) return { success: true }; + return { success: false }; +}; From 5a058de2f06c9482ff4dd08788af3a9f525a8830 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 3 Sep 2025 14:16:25 +0200 Subject: [PATCH 08/12] refactor error mgmt for creating new password --- admin/src/components/ChangePWform.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/admin/src/components/ChangePWform.tsx b/admin/src/components/ChangePWform.tsx index 161d671..aeddefe 100644 --- a/admin/src/components/ChangePWform.tsx +++ b/admin/src/components/ChangePWform.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { Button, Card, Field, Input, Stack } from "@chakra-ui/react"; +import { Button, Card, Field, Input, Stack, Alert } from "@chakra-ui/react"; import { changePW } from "@/utils/userActions"; +import { useState } from "react"; type ChangePWformProps = { onClose: () => void; @@ -17,6 +18,14 @@ const ChangePWform: React.FC = ({ alert, username, }) => { + const [showSubAlert, setShowSubAlert] = useState(false); + const [subAlertMessage, setSubAlertMessage] = useState(""); + + const subAlert = (message: string) => { + setSubAlertMessage(message); + setShowSubAlert(true); + }; + return (
@@ -64,7 +73,10 @@ const ChangePWform: React.FC = ({ ) as HTMLInputElement )?.value.trim() || ""; - if (!newPassword || newPassword !== confirmNewPassword) return; + if (!newPassword || newPassword !== confirmNewPassword) { + subAlert("Passwörter stimmen nicht überein!"); + return; + } const res = await changePW(newPassword, username); if (res.success) { @@ -86,6 +98,14 @@ const ChangePWform: React.FC = ({ > Ändern + {showSubAlert && ( + + + + {subAlertMessage} + + + )}
From 13a654561e4a2052c64395d32b8ce0d2c66c6c3e Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 3 Sep 2025 14:25:12 +0200 Subject: [PATCH 09/12] refactor user editing functionality to remove password handling --- admin/src/components/UserTable.tsx | 1 - admin/src/utils/userActions.ts | 7 +++---- backend/routes/api.js | 6 +++--- backend/services/database.js | 6 +++--- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/admin/src/components/UserTable.tsx b/admin/src/components/UserTable.tsx index ccd47e6..708f3ce 100644 --- a/admin/src/components/UserTable.tsx +++ b/admin/src/components/UserTable.tsx @@ -237,7 +237,6 @@ const UserTable: React.FC = () => { user.id, user.username, user.role, - user.password ).then((response) => { if (response.success) { setError( diff --git a/admin/src/utils/userActions.ts b/admin/src/utils/userActions.ts index 1703d3c..5c8b57e 100644 --- a/admin/src/utils/userActions.ts +++ b/admin/src/utils/userActions.ts @@ -24,19 +24,18 @@ export const handleDelete = async (userId: number) => { export const handleEdit = async ( userId: number, username: string, - role: string, - password: string + role: string ) => { try { const response = await fetch( `http://localhost:8002/api/editUser/${userId}`, { - method: "PUT", + method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${Cookies.get("token")}`, }, - body: JSON.stringify({ username, role, password }), + body: JSON.stringify({ username, role }), } ); if (!response.ok) { diff --git a/backend/routes/api.js b/backend/routes/api.js index 6ed2dc6..51efed1 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -224,10 +224,10 @@ router.get("/verifyToken", authenticate, async (req, res) => { res.status(200).json({ message: "Token is valid" }); }); -router.put("/editUser/:id", authenticate, async (req, res) => { +router.post("/editUser/:id", authenticate, async (req, res) => { const userId = req.params.id; - const { username, role, password } = req.body || {}; - const result = await handleEdit(userId, username, role, password); + const { username, role } = req.body || {}; + const result = await handleEdit(userId, username, role); if (result.success) { return res.status(200).json({ message: "User edited successfully" }); } diff --git a/backend/services/database.js b/backend/services/database.js index d692234..967d9db 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -340,10 +340,10 @@ export const deleteUserID = async (userId) => { return { success: false }; }; -export const handleEdit = async (userId, username, role, password) => { +export const handleEdit = async (userId, username, role) => { const [result] = await pool.query( - "UPDATE users SET username = ?, role = ?, password = ? WHERE id = ?", - [username, role, password, userId] + "UPDATE users SET username = ?, role = ? WHERE id = ?", + [username, role, userId] ); if (result.affectedRows > 0) return { success: true }; return { success: false }; From a24b2697b0ac0e1ab79f4526b1885b95acb3aa37 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 3 Sep 2025 14:33:54 +0200 Subject: [PATCH 10/12] enhance loan handling by updating item states on take and return --- backend/services/database.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/backend/services/database.js b/backend/services/database.js index 967d9db..3e02ce9 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -294,24 +294,44 @@ export const createLoanInDatabase = async ( // These functions are only temporary, and will be deleted when the full bin is set up. export const onTake = async (loanId) => { + const [items] = await pool.query( + "SELECT loaned_items_id FROM loans WHERE id = ?", + [loanId] + ); + + const [setItemStates] = await pool.query( + "UPDATE items SET inSafe = 0 WHERE id IN (?)", + [items.map((item) => item.loaned_items_id)] + ); + const [result] = await pool.query( "UPDATE loans SET take_date = NOW() WHERE id = ?", [loanId] ); - if (result.affectedRows > 0) { + if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { return { success: true }; } return { success: false }; }; export const onReturn = async (loanId) => { + const [items] = await pool.query( + "SELECT loaned_items_id FROM loans WHERE id = ?", + [loanId] + ); + + const [setItemStates] = await pool.query( + "UPDATE items SET inSafe = 1 WHERE id IN (?)", + [items.map((item) => item.loaned_items_id)] + ); + const [result] = await pool.query( "UPDATE loans SET returned_date = NOW() WHERE id = ?", [loanId] ); - if (result.affectedRows > 0) { + if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { return { success: true }; } return { success: false }; From 68f13f369c4b20b746694767a3ed229a5bc14f97 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 3 Sep 2025 14:50:35 +0200 Subject: [PATCH 11/12] add password change functionality with frontend integration --- backend/routes/api.js | 16 ++++++++++++++++ backend/services/database.js | 13 +++++++++++++ frontend/src/components/Header.tsx | 24 ++++++++++++++++++++++++ frontend/src/utils/userHandler.ts | 19 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/backend/routes/api.js b/backend/routes/api.js index 51efed1..1863029 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -19,6 +19,7 @@ import { deleteItemID, createItem, changeUserPassword, + changeUserPasswordFRONTEND, } from "../services/database.js"; import { authenticate, generateToken } from "../services/tokenService.js"; const router = express.Router(); @@ -176,6 +177,21 @@ router.post("/createLoan", authenticate, async (req, res) => { } }); +router.post("/changePassword", authenticate, async (req, res) => { + const { oldPassword, newPassword } = req.body || {}; + const username = req.user.username; + const result = await changeUserPasswordFRONTEND( + username, + oldPassword, + newPassword + ); + if (result.success) { + res.status(200).json({ message: "Password changed successfully" }); + } else { + res.status(500).json({ message: "Failed to change password" }); + } +}); + // Admin panel functions router.post("/loginAdmin", async (req, res) => { diff --git a/backend/services/database.js b/backend/services/database.js index 3e02ce9..32c504d 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -413,3 +413,16 @@ export const changeUserPassword = async (username, newPassword) => { if (result.affectedRows > 0) return { success: true }; return { success: false }; }; + +export const changeUserPasswordFRONTEND = async ( + username, + oldPassword, + newPassword +) => { + const [result] = await pool.query( + "UPDATE users SET password = ? WHERE username = ? AND password = ?", + [newPassword, username, oldPassword] + ); + if (result.affectedRows > 0) return { success: true }; + return { success: false }; +}; diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index b04bd4c..8348128 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,10 +1,27 @@ import React from "react"; +import { changePW } from "../utils/userHandler"; +import { myToast } from "../utils/toastify"; type HeaderProps = { onLogout: () => void; }; const Header: React.FC = ({ onLogout }) => { + const passwordForm = () => { + const oldPW = window.prompt("Altes Passwort"); + const newPW = window.prompt("Neues Passwort"); + const repeatNewPW = window.prompt("Neues Passwort wiederholen"); + if (oldPW && newPW && repeatNewPW) { + if (newPW === repeatNewPW) { + changePW(oldPW, newPW); + } else { + myToast("Die neuen Passwörter stimmen nicht überein.", "error"); + } + } else { + myToast("Bitte alle Felder ausfüllen.", "error"); + } + }; + return (
@@ -33,6 +50,13 @@ const Header: React.FC = ({ onLogout }) => { Source Code +
); diff --git a/frontend/src/utils/userHandler.ts b/frontend/src/utils/userHandler.ts index 625803c..d315745 100644 --- a/frontend/src/utils/userHandler.ts +++ b/frontend/src/utils/userHandler.ts @@ -137,3 +137,22 @@ export const onTake = async (loanID: number) => { myToast("Ausleihe erfolgreich ausgeliehen!", "success"); return true; }; + +export const changePW = async (oldPassword: string, newPassword: string) => { + const response = await fetch("http://localhost:8002/api/changePassword", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${Cookies.get("token") || ""}`, + }, + body: JSON.stringify({ oldPassword, newPassword }), + }); + + if (!response.ok) { + myToast("Fehler beim Ändern des Passworts", "error"); + return false; + } + + myToast("Passwort erfolgreich geändert!", "success"); + return true; +}; From c0ae12185a070e3d52c84af8d719b437f529a649 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Wed, 3 Sep 2025 14:52:36 +0200 Subject: [PATCH 12/12] redesgined header --- frontend/src/components/Header.tsx | 53 ++++++++++++++++++------------ 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 8348128..903786c 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -22,9 +22,12 @@ const Header: React.FC = ({ onLogout }) => { } }; + const btn = + "inline-flex items-center h-9 px-3 rounded-md text-sm font-medium border border-slate-300 bg-white text-slate-700 hover:bg-slate-100 active:bg-slate-200 transition focus:outline-none focus:ring-2 focus:ring-slate-400/50"; + return (
-
+

Gegenstand ausleihen @@ -33,30 +36,38 @@ const Header: React.FC = ({ onLogout }) => { Schnell und unkompliziert Equipment reservieren

- - - - - - - - + +
);