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";
|
} 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(
|
||||||
|
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 };
|
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,
|
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;
|
||||||
|
@@ -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);
|
||||||
|
@@ -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 };
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user