NOT WORKING - Implement API key management features: add API key creation and deletion, update API routes, and refactor related components. - NOT WORKING

This commit is contained in:
2025-09-27 17:33:59 +02:00
parent b9d637665c
commit f83f321876
6 changed files with 299 additions and 65 deletions

View File

@@ -11,17 +11,11 @@ import {
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { Tooltip } from "@/components/ui/tooltip"; import { Tooltip } from "@/components/ui/tooltip";
import MyAlert from "./myChakra/MyAlert"; import MyAlert from "./myChakra/MyAlert";
import { import { Trash2, RefreshCcwDot, CirclePlus } from "lucide-react";
Trash2,
RefreshCcwDot,
CirclePlus,
} from "lucide-react";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { import { deleteAPKey } from "@/utils/userActions";
deleteItem, import AddAPIKey from "./AddAPIKey";
} from "@/utils/userActions";
import AddItemForm from "./AddItemForm";
import { formatDateTime } from "@/utils/userFuncs"; import { formatDateTime } from "@/utils/userFuncs";
type Items = { type Items = {
@@ -57,7 +51,7 @@ const APIKeyTable: React.FC = () => {
const fetchData = async () => { const fetchData = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
const response = await fetch("http://localhost:8002/api/keys", { const response = await fetch("http://localhost:8002/api/apiKeys", {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${Cookies.get("token")}`, Authorization: `Bearer ${Cookies.get("token")}`,
@@ -118,7 +112,7 @@ const APIKeyTable: React.FC = () => {
}} }}
> >
<CirclePlus size={18} style={{ marginRight: 6 }} /> <CirclePlus size={18} style={{ marginRight: 6 }} />
Neuen Gegenstand hinzufügen Neuen API Key hinzufügen
</Button> </Button>
</Tooltip> </Tooltip>
</HStack> </HStack>
@@ -141,7 +135,7 @@ const APIKeyTable: React.FC = () => {
</VStack> </VStack>
)} )}
{addAPIForm && ( {addAPIForm && (
<AddItemForm <AddAPIKey
onClose={() => { onClose={() => {
setAddAPIForm(false); setAddAPIForm(false);
setReload(!reload); setReload(!reload);
@@ -175,14 +169,12 @@ const APIKeyTable: React.FC = () => {
<Table.Row key={apiKey.id}> <Table.Row key={apiKey.id}>
<Table.Cell>{apiKey.id}</Table.Cell> <Table.Cell>{apiKey.id}</Table.Cell>
<Table.Cell>{apiKey.apiKey}</Table.Cell> <Table.Cell>{apiKey.apiKey}</Table.Cell>
<Table.Cell> <Table.Cell>{apiKey.user}</Table.Cell>
<Table.Cell>{apiKey.user}</Table.Cell>
</Table.Cell>
<Table.Cell>{formatDateTime(apiKey.entry_created_at)}</Table.Cell> <Table.Cell>{formatDateTime(apiKey.entry_created_at)}</Table.Cell>
<Table.Cell> <Table.Cell>
<Button <Button
onClick={() => onClick={() =>
deleteItem(apiKey.id).then((response) => { deleteAPKey(apiKey.id).then((response) => {
if (response.success) { if (response.success) {
setItems(items.filter((i) => i.id !== apiKey.id)); setItems(items.filter((i) => i.id !== apiKey.id));
setError( setError(

View File

@@ -0,0 +1,73 @@
import React from "react";
import { Button, Card, Field, Input, Stack } from "@chakra-ui/react";
import { createAPIentry } from "@/utils/userActions";
type AddAPIKeyProps = {
onClose: () => void;
alert: (
status: "success" | "error",
message: string,
description: string
) => void;
};
const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
<Card.Root maxW="sm">
<Card.Header>
<Card.Title>Neuen API Key erstellen</Card.Title>
<Card.Description>
Füllen Sie das folgende Formular aus, um einen API Key zu erstellen.
</Card.Description>
</Card.Header>
<Card.Body>
<Stack gap="4" w="full">
<Field.Root>
<Field.Label>API key</Field.Label>
<Input type="number" id="apiKey" />
</Field.Root>
<Field.Root>
<Field.Label>Benutzer</Field.Label>
<Input id="user" type="text" />
</Field.Root>
</Stack>
</Card.Body>
<Card.Footer justifyContent="flex-end">
<Button variant="outline" onClick={onClose}>
Abbrechen
</Button>
<Button
variant="solid"
onClick={async () => {
const apiKey =
(
document.getElementById("apiKey") as HTMLInputElement
)?.value.trim() || "";
const user =
(
document.getElementById("user") as HTMLInputElement
)?.value.trim() || "";
if (!apiKey || !user) return;
const res = await createAPIentry(apiKey, user);
if (res.success) {
alert(
"success",
"API Key erstellt",
"Der API Key wurde erfolgreich erstellt."
);
onClose();
}
}}
>
Erstellen
</Button>
</Card.Footer>
</Card.Root>
</div>
);
};
export default AddAPIKey;

View File

@@ -201,3 +201,44 @@ export const changeSafeState = async (itemId: number) => {
return { success: false }; return { success: false };
} }
}; };
export const createAPIentry = async (apiKey: string, user: string) => {
try {
const response = await fetch(`http://localhost:8002/api/createAPIentry`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ apiKey, user }),
});
if (!response.ok) {
throw new Error("Failed to create API entry");
}
return { success: true };
} catch (error) {
console.error("Error creating API entry:", error);
return { success: false };
}
};
export const deleteAPKey = async (apiKeyId: number) => {
try {
const response = await fetch(
`http://localhost:8002/api/deleteAPKey/${apiKeyId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
}
);
if (!response.ok) {
throw new Error("Failed to delete API key");
}
return { success: true };
} catch (error) {
console.error("Error deleting API key:", error);
return { success: false };
}
};

View File

@@ -22,6 +22,9 @@ import {
changeUserPasswordFRONTEND, changeUserPasswordFRONTEND,
changeInSafeStateV2, changeInSafeStateV2,
updateItemByID, updateItemByID,
getAllApiKeys,
createAPIentry,
deleteAPKey,
} from "../services/database.js"; } from "../services/database.js";
import { authenticate, generateToken } from "../services/tokenService.js"; import { authenticate, generateToken } from "../services/tokenService.js";
const router = express.Router(); const router = express.Router();
@@ -330,4 +333,77 @@ router.put("/changeSafeState/:itemId", authenticate, async (req, res) => {
return res.status(500).json({ message: "Failed to update item safe state" }); 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.get("/apiKeys/apiV2/:id", authenticate, async (req, res) => {
if (req.params.id !== process.env.ADMIN_ID) {
return res.status(403).json({ message: "Access denied" });
}
const result = await getAPIkey();
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; export default router;

View File

@@ -7,93 +7,110 @@ import {
setTakeDateV2, setTakeDateV2,
getLoanByCodeV2, getLoanByCodeV2,
getAllLoansV2, getAllLoansV2,
getAPIkey,
} from "../services/database.js"; } from "../services/database.js";
dotenv.config(); dotenv.config();
const router = express.Router(); const router = express.Router();
async function validateAPIKey(apiKey) {
try {
const result = await getAPIkey();
if (!result.success || !Array.isArray(result.data)) return false;
return result.data.some((row) => {
const val = String(row?.apiKey ?? row?.key ?? row?.api_key);
return val === String(apiKey);
});
} catch (err) {
console.error("validateAPIKey error:", err);
return false;
}
}
async function ensureValidApiKey(req, res) {
const isValid = await validateAPIKey(req.params.key);
if (!isValid) {
res.status(403).json({ message: "Access denied" });
return false;
}
return true;
}
// Route for API to get ALL items from the database // Route for API to get ALL items from the database
router.get("/items/:key", async (req, res) => { router.get("/items/:key", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) { if (!(await ensureValidApiKey(req, res))) return;
const result = await getItemsFromDatabaseV2();
if (result.success) { const result = await getItemsFromDatabaseV2();
res.status(200).json({ data: result.data }); if (result.success) {
} else { res.status(200).json({ data: result.data });
res.status(500).json({ message: "Failed to fetch items" });
}
} else { } 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 // Route for API to control the position of an item
router.post("/controlInSafe/:key/:itemId/:state", async (req, res) => { router.post("/controlInSafe/:key/:itemId/:state", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) { if (!(await ensureValidApiKey(req, res))) return;
const itemId = req.params.itemId;
const state = req.params.state; const itemId = req.params.itemId;
if (state === "1" || state === "0") { const state = req.params.state;
const result = await changeInSafeStateV2(itemId, state);
if (result.success) { if (state === "1" || state === "0") {
res.status(200).json({ data: result.data }); const result = await changeInSafeStateV2(itemId, state);
} else { if (result.success) {
res.status(500).json({ message: "Failed to update item state" }); res.status(200).json({ data: result.data });
}
} else { } else {
res.status(400).json({ message: "Invalid state value" }); res.status(500).json({ message: "Failed to update item state" });
} }
} else { } else {
res.status(403).json({ message: "Access denied" }); res.status(400).json({ message: "Invalid state value" });
} }
}); });
// Route for API to get a loan by its code // Route for API to get a loan by its code
router.get("/getLoanByCode/:key/:loan_code", async (req, res) => { router.get("/getLoanByCode/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) { if (!(await ensureValidApiKey(req, res))) return;
const loan_code = req.params.loan_code;
const result = await getLoanByCodeV2(loan_code); const loan_code = req.params.loan_code;
if (result.success) { const result = await getLoanByCodeV2(loan_code);
res.status(200).json({ data: result.data }); if (result.success) {
} else { res.status(200).json({ data: result.data });
res.status(404).json({ message: "Loan not found" }); } else {
} res.status(404).json({ message: "Loan not found" });
} }
}); });
// Route for API to set the return date by the loan code // Route for API to set the return date by the loan code
router.post("/setReturnDate/:key/:loan_code", async (req, res) => { router.post("/setReturnDate/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) { if (!(await ensureValidApiKey(req, res))) return;
const loanCode = req.params.loan_code;
const result = await setReturnDateV2(loanCode); const loanCode = req.params.loan_code;
if (result.success) { const result = await setReturnDateV2(loanCode);
res.status(200).json({ data: result.data }); if (result.success) {
} else { res.status(200).json({ data: result.data });
res.status(500).json({ message: "Failed to set return date" });
}
} else { } 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 // Route for API to set the take away date by the loan code
router.post("/setTakeDate/:key/:loan_code", async (req, res) => { router.post("/setTakeDate/:key/:loan_code", async (req, res) => {
if (req.params.key === process.env.ADMIN_ID) { if (!(await ensureValidApiKey(req, res))) return;
const loanCode = req.params.loan_code;
const result = await setTakeDateV2(loanCode); const loanCode = req.params.loan_code;
if (result.success) { const result = await setTakeDateV2(loanCode);
res.status(200).json({ data: result.data }); if (result.success) {
} else { res.status(200).json({ data: result.data });
res.status(500).json({ message: "Failed to set take date" });
}
} else { } 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 // Route for API to get ALL loans from the database without sensitive info
router.get("/allLoans", async (req, res) => { router.get("/allLoans/:key", async (req, res) => {
if (!(await ensureValidApiKey(req, res))) return;
const result = await getAllLoansV2(); const result = await getAllLoansV2();
if (result.success) { if (result.success) {
return res.status(200).json(result.data); return res.status(200).json(result.data);
@@ -101,8 +118,10 @@ router.get("/allLoans", async (req, res) => {
return res.status(500).json({ message: "Failed to fetch loans" }); return res.status(500).json({ message: "Failed to fetch loans" });
}); });
// Route for API to get ALL items form the database without key // Route for API to get ALL items form the database
router.get("/allItems", async (req, res) => { router.get("/allItems/:key", async (req, res) => {
if (!(await ensureValidApiKey(req, res))) return;
const result = await getItemsFromDatabaseV2(); const result = await getItemsFromDatabaseV2();
if (result.success) { if (result.success) {
res.status(200).json(result.data); res.status(200).json(result.data);

View File

@@ -457,3 +457,36 @@ export const getAllLoansV2 = async () => {
} }
return { success: false }; 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 };
};