From eccd0135fcf64e06adcd5c57a8a077ff5e8f7be5 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Tue, 11 Nov 2025 20:46:21 +0100 Subject: [PATCH] added api route. But still with bug: still getting 403 but have valid api key --- backendV2/routes/api/api.database.js | 116 ++++++ backendV2/routes/api/api.route.js | 98 ++++- backendV2/services/authentication.js | 25 +- backendV2/services/database.js | 545 +-------------------------- 4 files changed, 237 insertions(+), 547 deletions(-) diff --git a/backendV2/routes/api/api.database.js b/backendV2/routes/api/api.database.js index e69de29..24d7242 100644 --- a/backendV2/routes/api/api.database.js +++ b/backendV2/routes/api/api.database.js @@ -0,0 +1,116 @@ +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 getItemsFromDatabaseV2 = async () => { + const [rows] = await pool.query("SELECT * FROM items;"); + if (rows.length > 0) { + return { success: true, data: rows }; + } + return { success: false }; +}; + +export const getLoanByCodeV2 = async (loan_code) => { + const [result] = await pool.query( + "SELECT * FROM loans WHERE loan_code = ?;", + [loan_code] + ); + if (result.length > 0) { + return { success: true, data: result[0] }; + } + return { success: false }; +}; + +export const changeInSafeStateV2 = async (itemId) => { + const [result] = await pool.query( + "UPDATE items SET inSafe = NOT inSafe WHERE id = ?", + [itemId] + ); + if (result.affectedRows > 0) { + return { success: true }; + } + return { success: false }; +}; + +export const setReturnDateV2 = async (loanCode) => { + const [items] = await pool.query( + "SELECT loaned_items_id 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 }; + + const itemIds = Array.isArray(items[0].loaned_items_id) + ? items[0].loaned_items_id + : JSON.parse(items[0].loaned_items_id || "[]"); + + const [setItemStates] = await pool.query( + "UPDATE items SET inSafe = 1, currently_borrowing = NULL, last_borrowed_person = (?) WHERE id IN (?)", + [owner[0].username, itemIds] + ); + + const [result] = await pool.query( + "UPDATE loans SET returned_date = NOW() WHERE loan_code = ?", + [loanCode] + ); + + if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { + return { success: true }; + } + return { success: false }; +}; + +export const setTakeDateV2 = async (loanCode) => { + const [items] = await pool.query( + "SELECT loaned_items_id 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 }; + + const itemIds = Array.isArray(items[0].loaned_items_id) + ? items[0].loaned_items_id + : JSON.parse(items[0].loaned_items_id || "[]"); + + const [setItemStates] = await pool.query( + "UPDATE items SET inSafe = 0, currently_borrowing = (?) WHERE id IN (?)", + [owner[0].username, itemIds] + ); + + const [result] = await pool.query( + "UPDATE loans SET take_date = NOW() WHERE loan_code = ?", + [loanCode] + ); + + if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { + return { success: true }; + } + return { success: false }; +}; + +export const getAllLoansV2 = async () => { + const [result] = await pool.query("SELECT * FROM loans;"); + if (result.length > 0) { + return { success: true, data: result }; + } + return { success: false }; +}; diff --git a/backendV2/routes/api/api.route.js b/backendV2/routes/api/api.route.js index 9da7196..7e49aae 100644 --- a/backendV2/routes/api/api.route.js +++ b/backendV2/routes/api/api.route.js @@ -1,5 +1,101 @@ import express from "express"; - +import { authenticate } from "../../services/authentication.js"; const router = express.Router(); +import dotenv from "dotenv"; +dotenv.config(); + +import { + getItemsFromDatabaseV2, + changeInSafeStateV2, + setTakeDateV2, + setReturnDateV2, + getLoanByCodeV2, +} from "./api.database.js"; + +// Route for API to get all items from the database +router.get("/items/:key", authenticate, async (req, res) => { + const result = await getItemsFromDatabaseV2(); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(500).json({ message: "Failed to fetch items" }); + } +}); + +// Route for API to control the safe state of an item +router.post( + "/change-state/:key/:itemId/:state", + authenticate, + async (req, res) => { + const itemId = req.params.itemId; + const state = req.params.state; + + if (state === "1" || state === "0") { + const result = await changeInSafeStateV2(itemId, state); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(500).json({ message: "Failed to update item state" }); + } + } else { + res.status(400).json({ message: "Invalid state value" }); + } + } +); + +// Route for API to get a loan by its code +router.get( + "/get-loan-by-code/:key/:loan_code", + authenticate, + async (req, res) => { + const loan_code = req.params.loan_code; + const result = await getLoanByCodeV2(loan_code); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(404).json({ message: "Loan not found" }); + } + } +); + +// Route for API to set the return date by the loan code +router.post( + "/set-return-date/:key/:loan_code", + authenticate, + async (req, res) => { + const loanCode = req.params.loan_code; + const result = await setReturnDateV2(loanCode); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(500).json({ message: "Failed to set return date" }); + } + } +); + +// Route for API to set the take away date by the loan code +router.post( + "/set-take-date/:key/:loan_code", + authenticate, + async (req, res) => { + const loanCode = req.params.loan_code; + const result = await setTakeDateV2(loanCode); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(500).json({ message: "Failed to set take date" }); + } + } +); + +// Route for API to get ALL items from the database (only for landingpage) +router.get("/all-items", async (req, res) => { + const result = await getItemsFromDatabaseV2(); + if (result.success) { + res.status(200).json(result.data); + } else { + res.status(500).json({ message: "Failed to fetch items" }); + } +}); export default router; diff --git a/backendV2/services/authentication.js b/backendV2/services/authentication.js index da33a9d..1940b81 100644 --- a/backendV2/services/authentication.js +++ b/backendV2/services/authentication.js @@ -1,6 +1,6 @@ import { SignJWT, jwtVerify } from "jose"; import env from "dotenv"; -import { getAllApiKeys } from "./database.js"; +import { verifyAPIKeyDB } from "./database.js"; env.config(); const secretKey = process.env.SECRET_KEY; @@ -36,13 +36,13 @@ export async function authenticateAdmin(req, res, next) { req.user = payload; return next(); } catch { - return res.status(403).json({ message: "Forbidden" }); + return res.status(403).json({ message: "Forbidden 403" }); } } export async function authenticate(req, res, next) { const authHeader = req.headers["authorization"]; - const apiKey = req.params.apiKey; + const apiKey = req.params.key; if (authHeader) { const parts = authHeader.split(" "); @@ -58,14 +58,17 @@ export async function authenticate(req, res, next) { req.user = payload; return next(); } catch { - return res.sendStatus(403); // present token invalid + return res.status(403).json({ message: "Present token invalid" }); // present token invalid } } else if (apiKey) { try { - await verifyAPIKey(apiKey); - return next(); + await verifyAPIKey(apiKey).then((result) => { + if (result.valid) { + return next(); + } + }); } catch { - return res.sendStatus(403); // API Key invalid + return res.status(403).json({ message: "API Key invalid" }); // fix: don't chain after sendStatus } } else { return res.status(401).json({ message: "Unauthorized" }); // no credentials @@ -73,9 +76,11 @@ export async function authenticate(req, res, next) { } async function verifyAPIKey(apiKey) { - const apiKeys = await getAllApiKeys(); - const validKey = apiKeys.find((k) => k.key === apiKey); - if (!validKey) { + const result = await verifyAPIKeyDB(apiKey); + + if (result.valid) { + return { valid: true }; + } else { throw new Error("Invalid API Key"); } } diff --git a/backendV2/services/database.js b/backendV2/services/database.js index 4549509..09af3bd 100644 --- a/backendV2/services/database.js +++ b/backendV2/services/database.js @@ -4,548 +4,21 @@ 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, + host: process.env.DB_HOST_2, + user: process.env.DB_USER_2, + password: process.env.DB_PASSWORD_2, + database: process.env.DB_NAME_2, }) .promise(); -export const loginFunc = async (username, password) => { +export const verifyAPIKeyDB = async (apiKey) => { const [result] = await pool.query( - "SELECT * FROM users WHERE username = ? AND password = ?", - [username, password] - ); - if (result.length > 0) return { success: true, data: result[0] }; - return { success: false }; -}; - -export const getItemsFromDatabaseV2 = async () => { - const [rows] = await pool.query("SELECT * FROM items;"); - if (rows.length > 0) { - return { success: true, data: rows }; - } - return { success: false }; -}; - -export const getLoanByCodeV2 = async (loan_code) => { - const [result] = await pool.query( - "SELECT * FROM loans WHERE loan_code = ?;", - [loan_code] + "SELECT * FROM apiKeys WHERE api_key = ?;", + [apiKey] ); if (result.length > 0) { - return { success: true, data: result[0] }; - } - return { success: false }; -}; - -export const changeInSafeStateV2 = async (itemId) => { - const [result] = await pool.query( - "UPDATE items SET inSafe = NOT inSafe WHERE id = ?", - [itemId] - ); - if (result.affectedRows > 0) { - return { success: true }; - } - return { success: false }; -}; - -export const setReturnDateV2 = async (loanCode) => { - const [items] = await pool.query( - "SELECT loaned_items_id FROM loans WHERE loan_code = ?", - [loanCode] - ); - - 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 [setItemStates] = await pool.query( - "UPDATE items SET inSafe = 1 WHERE id IN (?)", - [itemIds] - ); - - const [result] = await pool.query( - "UPDATE loans SET returned_date = NOW() WHERE loan_code = ?", - [loanCode] - ); - - if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { - return { success: true }; - } - return { success: false }; -}; - -export const setTakeDateV2 = async (loanCode) => { - const [items] = await pool.query( - "SELECT loaned_items_id FROM loans WHERE loan_code = ?", - [loanCode] - ); - - 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 [setItemStates] = await pool.query( - "UPDATE items SET inSafe = 0 WHERE id IN (?)", - [itemIds] - ); - - const [result] = await pool.query( - "UPDATE loans SET take_date = NOW() WHERE loan_code = ?", - [loanCode] - ); - - if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { - return { success: true }; - } - return { success: false }; -}; - -export const getItemsFromDatabase = async (role) => { - const sql = - role == 0 - ? "SELECT * FROM items;" - : "SELECT * FROM items WHERE can_borrow_role >= ?"; - const params = role == 0 ? [] : [role]; - - const [rows] = await pool.query(sql, params); - if (rows.length > 0) { - return { success: true, data: rows }; - } - return { success: false }; -}; - -export const getLoansFromDatabase = async () => { - const [rows] = await pool.query("SELECT * FROM loans;"); - return { success: true, data: rows.length > 0 ? rows : null }; -}; - -export const getUserLoansFromDatabase = async (username) => { - const [result] = await pool.query( - "SELECT * FROM loans WHERE username = ? AND deleted = 0;", - [username] - ); - if (result.length > 0) { - return { success: true, data: result }; - } else if (result.length == 0) { - return { success: true, data: "No loans found for this user" }; + return { valid: true }; } else { - return { success: false }; + return { valid: false }; } }; - -export const deleteLoanFromDatabase = async (loanId) => { - const [result] = await pool.query("DELETE FROM loans WHERE id = ?;", [ - loanId, - ]); - if (result.affectedRows > 0) { - return { success: true }; - } else { - return { success: false }; - } -}; - -export const SETdeleteLoanFromDatabase = async (loanId) => { - const [result] = await pool.query( - "UPDATE loans SET deleted = 1 WHERE id = ?;", - [loanId] - ); - if (result.affectedRows > 0) { - return { success: true }; - } else { - return { success: false }; - } -}; - -export const getBorrowableItemsFromDatabase = async ( - startDate, - endDate, - role = 0 -) => { - // Overlap if: loan.start < end AND effective_end > start - // effective_end is returned_date if set, otherwise end_date - const hasRoleFilter = Number(role) > 0; - - const sql = ` - SELECT i.* - FROM items i - WHERE ${hasRoleFilter ? "i.can_borrow_role >= ? AND " : ""}NOT EXISTS ( - SELECT 1 - FROM loans l - JOIN JSON_TABLE(l.loaned_items_id, '$[*]' COLUMNS (item_id INT PATH '$')) jt - WHERE jt.item_id = i.id - AND l.deleted = 0 - AND l.start_date < ? - AND COALESCE(l.returned_date, l.end_date) > ? - ); - `; - - const params = hasRoleFilter - ? [role, endDate, startDate] - : [endDate, startDate]; - - const [rows] = await pool.query(sql, params); - if (rows.length > 0) { - return { success: true, data: rows }; - } - 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, - endDate, - itemIds -) => { - if (!username) - return { success: false, code: "BAD_REQUEST", message: "Missing username" }; - if (!Array.isArray(itemIds) || itemIds.length === 0) - return { - success: false, - code: "BAD_REQUEST", - message: "No items provided", - }; - if (!startDate || !endDate) - return { success: false, code: "BAD_REQUEST", message: "Missing dates" }; - - const start = new Date(startDate); - const end = new Date(endDate); - if ( - !(start instanceof Date) || - isNaN(start.getTime()) || - !(end instanceof Date) || - isNaN(end.getTime()) || - start >= end - ) { - return { - success: false, - code: "BAD_REQUEST", - message: "Invalid date range", - }; - } - - const conn = await pool.getConnection(); - try { - await conn.beginTransaction(); - - // Ensure all items exist and collect names - const [itemsRows] = await conn.query( - "SELECT id, item_name FROM items WHERE id IN (?)", - [itemIds] - ); - if (!itemsRows || itemsRows.length !== itemIds.length) { - await conn.rollback(); - return { - success: false, - code: "BAD_REQUEST", - message: "One or more items not found", - }; - } - const itemNames = itemIds - .map( - (id) => itemsRows.find((r) => Number(r.id) === Number(id))?.item_name - ) - .filter(Boolean); - - // Check availability (no overlap with existing loans) - const [confRows] = await conn.query( - ` - SELECT COUNT(*) AS conflicts - FROM loans l - JOIN JSON_TABLE(l.loaned_items_id, '$[*]' COLUMNS (item_id INT PATH '$')) jt - ON TRUE - WHERE jt.item_id IN (?) - AND l.deleted = 0 - AND l.start_date < ? - AND COALESCE(l.returned_date, l.end_date) > ? - `, - [itemIds, end, start] - ); - if (confRows?.[0]?.conflicts > 0) { - await conn.rollback(); - return { - success: false, - code: "CONFLICT", - message: "One or more items are not available in the selected period", - }; - } - - // Generate unique loan_code (retry a few times) - let loanCode = null; - for (let i = 0; i < 6; i++) { - const candidate = Math.floor(100000 + Math.random() * 899999); // 6 digits - const [exists] = await conn.query( - "SELECT 1 FROM loans WHERE loan_code = ? LIMIT 1", - [candidate] - ); - if (exists.length === 0) { - loanCode = candidate; - break; - } - } - if (!loanCode) { - await conn.rollback(); - return { - success: false, - code: "SERVER_ERROR", - message: "Failed to generate unique loan code", - }; - } - - // Insert loan - const [insertRes] = await conn.query( - ` - INSERT INTO loans (username, loan_code, start_date, end_date, loaned_items_id, loaned_items_name) - VALUES (?, ?, ?, ?, CAST(? AS JSON), CAST(? AS JSON)) - `, - [ - username, - loanCode, - // Use DATETIME/TIMESTAMP friendly format - new Date(start).toISOString().slice(0, 19).replace("T", " "), - new Date(end).toISOString().slice(0, 19).replace("T", " "), - JSON.stringify(itemIds.map((n) => Number(n))), - JSON.stringify(itemNames), - ] - ); - - await conn.commit(); - return { - success: true, - data: { - id: insertRes.insertId, - loan_code: loanCode, - username, - start_date: start, - end_date: end, - items: itemIds, - item_names: itemNames, - }, - }; - } catch (err) { - await conn.rollback(); - console.error("createLoanInDatabase error:", err); - return { - success: false, - code: "SERVER_ERROR", - message: "Failed to create loan", - }; - } finally { - conn.release(); - } -}; - -// 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] - ); - - 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 [setItemStates] = await pool.query( - "UPDATE items SET inSafe = 0 WHERE id IN (?)", - [itemIds] - ); - - const [result] = await pool.query( - "UPDATE loans SET take_date = NOW() WHERE id = ?", - [loanId] - ); - - 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] - ); - - 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 [setItemStates] = await pool.query( - "UPDATE items SET inSafe = 1 WHERE id IN (?)", - [itemIds] - ); - - const [result] = await pool.query( - "UPDATE loans SET returned_date = NOW() WHERE id = ?", - [loanId] - ); - - if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { - return { success: true }; - } - return { success: false }; -}; -// Temporary functions end here. - -export const loginAdmin = async (username, password) => { - const [result] = await pool.query( - "SELECT * FROM admins WHERE username = ? AND password = ?", - [username, password] - ); - if (result.length > 0) return { success: true, data: result[0] }; - return { success: false }; -}; - -export const getAllUsers = async () => { - 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 }; -}; - -export const deleteUserID = async (userId) => { - const [result] = await pool.query("DELETE FROM users WHERE id = ?", [userId]); - if (result.affectedRows > 0) return { success: true }; - return { success: false }; -}; - -export const handleEdit = async (userId, username, role) => { - const [result] = await pool.query( - "UPDATE users SET username = ?, role = ? WHERE id = ?", - [username, role, userId] - ); - if (result.affectedRows > 0) return { success: true }; - return { success: false }; -}; - -export const createUser = async (username, role, password) => { - const [result] = await pool.query( - "INSERT INTO users (username, role, password) VALUES (?, ?, ?)", - [username, role, password] - ); - if (result.affectedRows > 0) return { success: true }; - return { success: false }; -}; - -export const getAllLoans = async () => { - const [result] = await pool.query("SELECT * FROM loans"); - if (result.length > 0) return { success: true, data: result }; - return { success: false }; -}; - -export const getAllItems = async () => { - const [result] = await pool.query("SELECT * FROM items"); - if (result.length > 0) return { success: true, data: result }; - return { success: false }; -}; - -export const deleteItemID = async (itemId) => { - const [result] = await pool.query("DELETE FROM items WHERE id = ?", [itemId]); - if (result.affectedRows > 0) return { success: true }; - return { success: false }; -}; - -export const createItem = async (item_name, can_borrow_role) => { - const [result] = await pool.query( - "INSERT INTO items (item_name, can_borrow_role) VALUES (?, ?)", - [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 }; -}; - -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 }; -}; - -export const updateItemByID = async (itemId, item_name, can_borrow_role) => { - const [result] = await pool.query( - "UPDATE items SET item_name = ?, can_borrow_role = ? WHERE id = ?", - [item_name, can_borrow_role, itemId] - ); - if (result.affectedRows > 0) return { success: true }; - return { success: false }; -}; - -export const getAllLoansV2 = async () => { - const [rows] = await pool.query( - "SELECT id, username, start_date, end_date, loaned_items_name, returned_date, take_date FROM loans" - ); - if (rows.length > 0) { - return { success: true, data: rows }; - } - return { success: false }; -}; - -export const getAllApiKeys = async () => { - const [rows] = await pool.query("SELECT * FROM apiKeys"); - if (rows.length > 0) { - return { success: true, data: rows }; - } - return { success: false }; -}; - -export const createAPIentry = async (apiKey, user) => { - const [result] = await pool.query( - "INSERT INTO apiKeys (apiKey, user) VALUES (?, ?)", - [apiKey, user] - ); - if (result.affectedRows > 0) return { success: true }; - return { success: false }; -}; - -export const deleteAPKey = async (apiKeyId) => { - const [result] = await pool.query("DELETE FROM apiKeys WHERE id = ?", [ - apiKeyId, - ]); - if (result.affectedRows > 0) return { success: true }; - return { success: false }; -}; - -export const getAPIkey = async () => { - const [rows] = await pool.query("SELECT apiKey FROM apiKeys"); - if (rows.length > 0) { - return { success: true, data: rows }; - } - return { success: false }; -};