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:
@@ -11,17 +11,11 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { Tooltip } from "@/components/ui/tooltip";
|
||||
import MyAlert from "./myChakra/MyAlert";
|
||||
import {
|
||||
Trash2,
|
||||
RefreshCcwDot,
|
||||
CirclePlus,
|
||||
} from "lucide-react";
|
||||
import { Trash2, RefreshCcwDot, CirclePlus } from "lucide-react";
|
||||
import Cookies from "js-cookie";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
deleteItem,
|
||||
} from "@/utils/userActions";
|
||||
import AddItemForm from "./AddItemForm";
|
||||
import { deleteAPKey } from "@/utils/userActions";
|
||||
import AddAPIKey from "./AddAPIKey";
|
||||
import { formatDateTime } from "@/utils/userFuncs";
|
||||
|
||||
type Items = {
|
||||
@@ -57,7 +51,7 @@ const APIKeyTable: React.FC = () => {
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch("http://localhost:8002/api/keys", {
|
||||
const response = await fetch("http://localhost:8002/api/apiKeys", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||
@@ -118,7 +112,7 @@ const APIKeyTable: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<CirclePlus size={18} style={{ marginRight: 6 }} />
|
||||
Neuen Gegenstand hinzufügen
|
||||
Neuen API Key hinzufügen
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
@@ -141,7 +135,7 @@ const APIKeyTable: React.FC = () => {
|
||||
</VStack>
|
||||
)}
|
||||
{addAPIForm && (
|
||||
<AddItemForm
|
||||
<AddAPIKey
|
||||
onClose={() => {
|
||||
setAddAPIForm(false);
|
||||
setReload(!reload);
|
||||
@@ -175,14 +169,12 @@ const APIKeyTable: React.FC = () => {
|
||||
<Table.Row key={apiKey.id}>
|
||||
<Table.Cell>{apiKey.id}</Table.Cell>
|
||||
<Table.Cell>{apiKey.apiKey}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Table.Cell>{apiKey.user}</Table.Cell>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{apiKey.user}</Table.Cell>
|
||||
<Table.Cell>{formatDateTime(apiKey.entry_created_at)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
deleteItem(apiKey.id).then((response) => {
|
||||
deleteAPKey(apiKey.id).then((response) => {
|
||||
if (response.success) {
|
||||
setItems(items.filter((i) => i.id !== apiKey.id));
|
||||
setError(
|
||||
|
73
admin/src/components/AddAPIKey.tsx
Normal file
73
admin/src/components/AddAPIKey.tsx
Normal 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;
|
@@ -201,3 +201,44 @@ export const changeSafeState = async (itemId: number) => {
|
||||
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 };
|
||||
}
|
||||
};
|
||||
|
@@ -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,77 @@ 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.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;
|
||||
|
@@ -7,93 +7,110 @@ import {
|
||||
setTakeDateV2,
|
||||
getLoanByCodeV2,
|
||||
getAllLoansV2,
|
||||
getAPIkey,
|
||||
} from "../services/database.js";
|
||||
|
||||
dotenv.config();
|
||||
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
|
||||
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" });
|
||||
}
|
||||
if (!(await ensureValidApiKey(req, res))) return;
|
||||
|
||||
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) {
|
||||
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" });
|
||||
}
|
||||
if (!(await ensureValidApiKey(req, res))) return;
|
||||
|
||||
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(400).json({ message: "Invalid state value" });
|
||||
res.status(500).json({ message: "Failed to update item state" });
|
||||
}
|
||||
} 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
|
||||
router.get("/getLoanByCode/:key/:loan_code", async (req, res) => {
|
||||
if (req.params.key === process.env.ADMIN_ID) {
|
||||
const loan_code = req.params.loan_code;
|
||||
if (!(await ensureValidApiKey(req, res))) return;
|
||||
|
||||
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" });
|
||||
}
|
||||
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;
|
||||
if (!(await ensureValidApiKey(req, res))) return;
|
||||
|
||||
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" });
|
||||
}
|
||||
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;
|
||||
if (!(await ensureValidApiKey(req, res))) return;
|
||||
|
||||
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" });
|
||||
}
|
||||
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", async (req, res) => {
|
||||
if (!(await ensureValidApiKey(req, res))) return;
|
||||
|
||||
const result = await getAllLoansV2();
|
||||
if (result.success) {
|
||||
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" });
|
||||
});
|
||||
|
||||
// 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", async (req, res) => {
|
||||
if (!(await ensureValidApiKey(req, res))) return;
|
||||
|
||||
const result = await getItemsFromDatabaseV2();
|
||||
if (result.success) {
|
||||
res.status(200).json(result.data);
|
||||
|
@@ -457,3 +457,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 };
|
||||
};
|
||||
|
Reference in New Issue
Block a user