Merge branch 'dev_v1-admin' into debian12_v1-admin

This commit is contained in:
2025-09-27 22:55:22 +02:00
9 changed files with 506 additions and 49 deletions

View File

@@ -22,6 +22,9 @@ import {
changeUserPasswordFRONTEND,
changeInSafeStateV2,
updateItemByID,
getAllApiKeys,
createAPIentry,
deleteAPKey,
} from "../services/database.js";
import { authenticate, generateToken } from "../services/tokenService.js";
const router = express.Router();
@@ -330,4 +333,66 @@ router.put("/changeSafeState/:itemId", authenticate, async (req, res) => {
return res.status(500).json({ message: "Failed to update item safe state" });
});
router.get("/apiKeys", authenticate, async (req, res) => {
const result = await getAllApiKeys();
if (result.success) {
return res.status(200).json(result.data);
}
return res.status(500).json({ message: "Failed to fetch API keys" });
});
router.delete("/deleteAPKey/:id", authenticate, async (req, res) => {
const apiKeyId = req.params.id;
const result = await deleteAPKey(apiKeyId);
if (result.success) {
return res.status(200).json({ message: "API key deleted successfully" });
}
return res.status(500).json({ message: "Failed to delete API key" });
});
router.post("/createAPIentry", authenticate, async (req, res) => {
const apiKey = req.body.apiKey;
const user = req.body.user;
if (!apiKey || !user) {
return res.status(400).json({ message: "API key and user are required" });
}
// Ensure apiKey is a number
const apiKeyNum = Number(apiKey);
if (!Number.isFinite(apiKeyNum)) {
return res.status(400).json({ message: "API key must be a number" });
}
const result = await createAPIentry(apiKeyNum, user);
if (result.success) {
return res.status(201).json({ message: "API key created successfully" });
}
if (result.code === "DUPLICATE") {
return res.status(409).json({ message: "API key already exists" });
}
return res.status(500).json({ message: "Failed to create API key" });
});
router.get("/apiKeys/validate/:key", async (req, res) => {
try {
const rawKey = req.params.key;
const result = await getAllApiKeys();
if (!result.success || !Array.isArray(result.data)) {
return res.status(500).json({ valid: false });
}
const isValid = result.data.some((entry) => {
const val = String(
entry?.key ?? entry?.apiKey ?? entry?.api_key ?? entry
);
return val === String(rawKey);
});
return res.status(200).json({ valid: isValid });
} catch (err) {
console.error("validate api key error:", err);
return res.status(500).json({ valid: false });
}
});
export default router;

View File

@@ -7,30 +7,64 @@ import {
setTakeDateV2,
getLoanByCodeV2,
getAllLoansV2,
getAPIkey,
} from "../services/database.js";
dotenv.config();
const router = express.Router();
// Route for API to get ALL items from the database
router.get("/items/:key", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
const result = await getItemsFromDatabaseV2();
if (result.success) {
res.status(200).json({ data: result.data });
} else {
res.status(500).json({ message: "Failed to fetch items" });
async function validateAPIKey(apiKey) {
try {
if (!apiKey) return false;
const result = await getAPIkey();
if (!result?.success || !Array.isArray(result.data)) return false;
return result.data.some((row) => String(row.apiKey) === String(apiKey));
} catch (err) {
console.error("validateAPIKey error:", err);
return false;
}
}
// Add a guard that returns Access Denied instead of hanging
const apiKeyGuard = async (req, res, next) => {
try {
const key = req.params.key;
if (!key) {
return res
.status(401)
.json({ message: "Access denied: missing API key" });
}
const ok = await validateAPIKey(key);
if (!ok) {
return res
.status(401)
.json({ message: "Access denied: invalid API key" });
}
next();
} catch (e) {
console.error("apiKeyGuard error:", e);
res.status(500).json({ message: "Internal server error" });
}
};
// Route for API to get ALL items from the database
router.get("/items/:key", apiKeyGuard, async (req, res) => {
const result = await getItemsFromDatabaseV2();
if (result.success) {
res.status(200).json({ data: result.data });
} else {
res.status(403).json({ message: "Access denied" });
res.status(500).json({ message: "Failed to fetch items" });
}
});
// Route for API to control the position of an item
router.post("/controlInSafe/:key/:itemId/:state", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
router.post(
"/controlInSafe/:key/:itemId/:state",
apiKeyGuard,
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) {
@@ -41,59 +75,44 @@ router.post("/controlInSafe/:key/:itemId/:state", async (req, res) => {
} else {
res.status(400).json({ message: "Invalid state value" });
}
} else {
res.status(403).json({ message: "Access denied" });
}
});
);
// Route for API to get a loan by its code
router.get("/getLoanByCode/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
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" });
}
router.get("/getLoanByCode/:key/:loan_code", apiKeyGuard, 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("/setReturnDate/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
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" });
}
router.post("/setReturnDate/:key/:loan_code", apiKeyGuard, 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(403).json({ message: "Access denied" });
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("/setTakeDate/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) {
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" });
}
router.post("/setTakeDate/:key/:loan_code", apiKeyGuard, 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(403).json({ message: "Access denied" });
res.status(500).json({ message: "Failed to set take date" });
}
});
// Route for API to get ALL loans from the database without sensitive info
router.get("/allLoans", async (req, res) => {
router.get("/allLoans/:key", apiKeyGuard, async (req, res) => {
const result = await getAllLoansV2();
if (result.success) {
return res.status(200).json(result.data);
@@ -101,8 +120,8 @@ router.get("/allLoans", async (req, res) => {
return res.status(500).json({ message: "Failed to fetch loans" });
});
// Route for API to get ALL items form the database without key
router.get("/allItems", async (req, res) => {
// Route for API to get ALL items form the database
router.get("/allItems/:key", apiKeyGuard, async (req, res) => {
const result = await getItemsFromDatabaseV2();
if (result.success) {
res.status(200).json(result.data);

View File

@@ -58,6 +58,15 @@ CREATE TABLE `lockers` (
UNIQUE KEY `locker_number` (`locker_number`)
);
CREATE TABLE `apiKeys` (
`id` int NOT NULL AUTO_INCREMENT,
`apiKey` int NOT NULL UNIQUE,
`user` VARCHAR(255) NOT NULL,
`entry_created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `apiKey` (`apiKey`)
);
INSERT INTO `items` (`item_name`, `can_borrow_role`, `inSafe`) VALUES
('DJI 1er Mikro', 4, 1),
('DJI 2er Mikro 1', 4, 1),

View File

@@ -458,3 +458,36 @@ export const getAllLoansV2 = async () => {
}
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 };
};