Refactor loan and user management components and backend routes
- Updated LoanTable component to fetch loan data from new API endpoint and display notes. - Enhanced UserTable component to include additional user fields (first name, last name, email, admin status) and updated input handling. - Modified fetcher utility to use new user data API endpoint. - Adjusted login functionality to point to the new admin login endpoint and handle unauthorized access. - Refactored user actions utility to align with updated API endpoints for user management. - Updated backend routes for user and loan data management to reflect new structure and naming conventions. - Revised SQL schema and mock data to accommodate new fields and constraints. - Changed Docker configuration to use the new database name.
This commit is contained in:
@@ -3,11 +3,7 @@ import { useEffect } from "react";
|
|||||||
import Dashboard from "./Dashboard";
|
import Dashboard from "./Dashboard";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
const API_BASE =
|
|
||||||
(import.meta as any).env?.VITE_BACKEND_URL ||
|
|
||||||
import.meta.env.VITE_BACKEND_URL ||
|
|
||||||
"http://localhost:8002";
|
|
||||||
|
|
||||||
const Layout: React.FC = () => {
|
const Layout: React.FC = () => {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
@@ -15,12 +11,15 @@ const Layout: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Cookies.get("token")) {
|
if (Cookies.get("token")) {
|
||||||
const verifyToken = async () => {
|
const verifyToken = async () => {
|
||||||
const response = await fetch(`${API_BASE}/api/verifyToken`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/user-mgmt/verify-token`,
|
||||||
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setIsLoggedIn(true);
|
setIsLoggedIn(true);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Box, Flex, VStack, Heading, Text, Link } from "@chakra-ui/react";
|
import { Box, Flex, VStack, Heading, Text, Link } from "@chakra-ui/react";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
|
|
||||||
type SidebarProps = {
|
type SidebarProps = {
|
||||||
viewAusleihen: () => void;
|
viewAusleihen: () => void;
|
||||||
@@ -15,10 +17,22 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
viewUser,
|
viewUser,
|
||||||
viewAPI,
|
viewAPI,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [info, setInfo] = useState<any>(null);
|
||||||
|
|
||||||
|
const fetchInfo = async () => {
|
||||||
|
const response = await fetch(`${API_BASE}/`);
|
||||||
|
const data = await response.json();
|
||||||
|
setInfo(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInfo();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as="aside"
|
as="aside"
|
||||||
w="260px"
|
w="180px"
|
||||||
minH="100vh"
|
minH="100vh"
|
||||||
bg="gray.800"
|
bg="gray.800"
|
||||||
color="gray.100"
|
color="gray.100"
|
||||||
@@ -72,7 +86,33 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
<Box mt="auto" pt={8} fontSize="xs" color="gray.500">
|
<Box mt="auto" pt={8} fontSize="xs" color="gray.500">
|
||||||
<Text>© Made with ❤️ by Theis Gaedigk</Text>
|
<Text mb={2}>© Made with ❤️ by Theis Gaedigk</Text>
|
||||||
|
{info ? (
|
||||||
|
<Flex gap={2} wrap="wrap">
|
||||||
|
<Box
|
||||||
|
as="span"
|
||||||
|
px={2}
|
||||||
|
py={0.5}
|
||||||
|
rounded="full"
|
||||||
|
bg="gray.700"
|
||||||
|
color="gray.200"
|
||||||
|
>
|
||||||
|
Panel {info?.["admin-panel-info"]?.version ?? "—"}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
as="span"
|
||||||
|
px={2}
|
||||||
|
py={0.5}
|
||||||
|
rounded="full"
|
||||||
|
bg="gray.700"
|
||||||
|
color="gray.200"
|
||||||
|
>
|
||||||
|
Backend {info?.["backend-info"]?.version ?? "—"}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Text color="gray.600">Lade Versionsinfos…</Text>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -17,17 +17,14 @@ import { useState, useEffect } from "react";
|
|||||||
import { deleteAPKey } from "@/utils/userActions";
|
import { deleteAPKey } from "@/utils/userActions";
|
||||||
import AddAPIKey from "./AddAPIKey";
|
import AddAPIKey from "./AddAPIKey";
|
||||||
import { formatDateTime } from "@/utils/userFuncs";
|
import { formatDateTime } from "@/utils/userFuncs";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
const API_BASE =
|
|
||||||
(import.meta as any).env?.VITE_BACKEND_URL ||
|
|
||||||
import.meta.env.VITE_BACKEND_URL ||
|
|
||||||
"http://localhost:8002";
|
|
||||||
|
|
||||||
type Items = {
|
type Items = {
|
||||||
id: number;
|
id: number;
|
||||||
apiKey: string;
|
api_key: string;
|
||||||
user: string;
|
entry_name: string;
|
||||||
entry_created_at: string;
|
entry_created_at: string;
|
||||||
|
last_used_at: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const APIKeyTable: React.FC = () => {
|
const APIKeyTable: React.FC = () => {
|
||||||
@@ -56,13 +53,17 @@ const APIKeyTable: React.FC = () => {
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/apiKeys`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/api-data/get-api-keys`,
|
||||||
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError("error", "Failed to fetch items", "There is an error");
|
setError("error", "Failed to fetch items", "There is an error");
|
||||||
@@ -159,29 +160,37 @@ const APIKeyTable: React.FC = () => {
|
|||||||
<strong>API Key</strong>
|
<strong>API Key</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Benutzer</strong>
|
<strong>Name</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Eintrag erstellt am</strong>
|
<strong>Eintrag erstellt am</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Zuletzt benutzt am</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Aktionen</strong>
|
<strong>Aktionen</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{items.map((apiKey) => (
|
{items.map((item) => (
|
||||||
<Table.Row key={apiKey.id}>
|
<Table.Row key={item.id}>
|
||||||
<Table.Cell>{apiKey.id}</Table.Cell>
|
<Table.Cell>{item.id}</Table.Cell>
|
||||||
<Table.Cell>{apiKey.apiKey}</Table.Cell>
|
<Table.Cell>{item.api_key}</Table.Cell>
|
||||||
<Table.Cell>{apiKey.user}</Table.Cell>
|
<Table.Cell>{item.entry_name}</Table.Cell>
|
||||||
<Table.Cell>{formatDateTime(apiKey.entry_created_at)}</Table.Cell>
|
<Table.Cell>{formatDateTime(item.entry_created_at)}</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{!item.last_used_at
|
||||||
|
? "Nie benutzt"
|
||||||
|
: formatDateTime(item.last_used_at)}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
deleteAPKey(apiKey.id).then((response) => {
|
deleteAPKey(item.id).then((response) => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setItems(items.filter((i) => i.id !== apiKey.id));
|
setItems(items.filter((i) => i.id !== item.id));
|
||||||
setError(
|
setError(
|
||||||
"success",
|
"success",
|
||||||
"Gegenstand gelöscht",
|
"Gegenstand gelöscht",
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button, Card, Field, Input, Stack } from "@chakra-ui/react";
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Field,
|
||||||
|
Input,
|
||||||
|
Stack,
|
||||||
|
InputGroup,
|
||||||
|
Span,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { createAPIentry } from "@/utils/userActions";
|
import { createAPIentry } from "@/utils/userActions";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
type AddAPIKeyProps = {
|
type AddAPIKeyProps = {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -12,6 +21,8 @@ type AddAPIKeyProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
|
const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
|
||||||
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
<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.Root maxW="sm">
|
||||||
@@ -23,13 +34,26 @@ const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
|
|||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Stack gap="4" w="full">
|
<Stack gap="4" w="full">
|
||||||
|
<InputGroup
|
||||||
|
endElement={
|
||||||
|
<Span color="fg.muted" textStyle="xs">
|
||||||
|
{value.length} / {15}
|
||||||
|
</Span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Er muss 15 Zeichen lang sein"
|
||||||
|
value={value}
|
||||||
|
id="apiKey"
|
||||||
|
maxLength={15}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue(e.currentTarget.value.slice(0, 15));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
<Field.Root>
|
<Field.Root>
|
||||||
<Field.Label>API key</Field.Label>
|
<Field.Label>Name</Field.Label>
|
||||||
<Input type="number" id="apiKey" />
|
<Input id="name" type="text" />
|
||||||
</Field.Root>
|
|
||||||
<Field.Root>
|
|
||||||
<Field.Label>Benutzer</Field.Label>
|
|
||||||
<Input id="user" type="text" />
|
|
||||||
</Field.Root>
|
</Field.Root>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
@@ -44,14 +68,14 @@ const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
|
|||||||
(
|
(
|
||||||
document.getElementById("apiKey") as HTMLInputElement
|
document.getElementById("apiKey") as HTMLInputElement
|
||||||
)?.value.trim() || "";
|
)?.value.trim() || "";
|
||||||
const user =
|
const name =
|
||||||
(
|
(
|
||||||
document.getElementById("user") as HTMLInputElement
|
document.getElementById("name") as HTMLInputElement
|
||||||
)?.value.trim() || "";
|
)?.value.trim() || "";
|
||||||
|
|
||||||
if (!apiKey || !user) return;
|
if (!apiKey || !name) return;
|
||||||
|
|
||||||
const res = await createAPIentry(apiKey, user);
|
const res = await createAPIentry(apiKey, name);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
alert(
|
alert(
|
||||||
"success",
|
"success",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button, Card, Field, Input, Stack } from "@chakra-ui/react";
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Field,
|
||||||
|
Input,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Checkbox,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { createUser } from "@/utils/userActions";
|
import { createUser } from "@/utils/userActions";
|
||||||
|
|
||||||
type AddFormProps = {
|
type AddFormProps = {
|
||||||
@@ -12,37 +20,71 @@ type AddFormProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AddForm: React.FC<AddFormProps> = ({ onClose, alert }) => {
|
const AddForm: React.FC<AddFormProps> = ({ onClose, alert }) => {
|
||||||
|
const [admin, setAdmin] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Card.Root maxW="sm">
|
<Card.Root maxW="sm">
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Card.Title>Neuen Nutzer erstellen</Card.Title>
|
<Card.Title>Neuen Nutzer erstellen</Card.Title>
|
||||||
<Card.Description>
|
<Card.Description>
|
||||||
Füllen Sie das folgende Formular aus, um einen Nutzer zu erstellen.
|
Füllen Sie das folgende Formular aus, um einen Nutzer zu
|
||||||
|
erstellen.
|
||||||
</Card.Description>
|
</Card.Description>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
|
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Stack gap="4" w="full">
|
<Stack gap="4" w="full">
|
||||||
<Field.Root>
|
<Field.Root>
|
||||||
<Field.Label>Username</Field.Label>
|
<Field.Label>Benutzername</Field.Label>
|
||||||
<Input id="username" />
|
<Input id="username" />
|
||||||
</Field.Root>
|
</Field.Root>
|
||||||
<Field.Root>
|
<Field.Root>
|
||||||
<Field.Label>Password</Field.Label>
|
<Field.Label>Passwort</Field.Label>
|
||||||
<Input id="password" type="password" />
|
<Input id="password" type="password" />
|
||||||
</Field.Root>
|
</Field.Root>
|
||||||
<Field.Root>
|
<Field.Root>
|
||||||
<Field.Label>Role</Field.Label>
|
<Field.Label>Vorname</Field.Label>
|
||||||
|
<Input id="firstname" />
|
||||||
|
</Field.Root>
|
||||||
|
<Field.Root>
|
||||||
|
<Field.Label>Nachname</Field.Label>
|
||||||
|
<Input id="lastname" />
|
||||||
|
</Field.Root>
|
||||||
|
<Field.Root>
|
||||||
|
<Field.Label>E-Mail</Field.Label>
|
||||||
|
<Input id="email" type="email" />
|
||||||
|
</Field.Root>
|
||||||
|
|
||||||
|
{/* Kontrollierte Checkbox */}
|
||||||
|
<Checkbox.Root
|
||||||
|
checked={admin}
|
||||||
|
onCheckedChange={(e: any) => setAdmin(Boolean(e?.checked ?? e))}
|
||||||
|
>
|
||||||
|
<Checkbox.HiddenInput />
|
||||||
|
<Checkbox.Control />
|
||||||
|
<Checkbox.Label>Admin</Checkbox.Label>
|
||||||
|
</Checkbox.Root>
|
||||||
|
|
||||||
|
<Field.Root>
|
||||||
|
<Field.Label>Rolle</Field.Label>
|
||||||
<Input id="role" type="number" />
|
<Input id="role" type="number" />
|
||||||
</Field.Root>
|
</Field.Root>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
<Card.Footer justifyContent="flex-end">
|
<Card.Footer justifyContent="flex-end">
|
||||||
|
<Text>Der Benutzername kann nicht mehr geändert werden.</Text>
|
||||||
<Button variant="outline" onClick={onClose}>
|
<Button variant="outline" onClick={onClose}>
|
||||||
Abbrechen
|
Abbrechen
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
type="submit"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const username =
|
const username =
|
||||||
(
|
(
|
||||||
@@ -54,10 +96,30 @@ const AddForm: React.FC<AddFormProps> = ({ onClose, alert }) => {
|
|||||||
const role = Number(
|
const role = Number(
|
||||||
(document.getElementById("role") as HTMLInputElement)?.value
|
(document.getElementById("role") as HTMLInputElement)?.value
|
||||||
);
|
);
|
||||||
|
const firstname =
|
||||||
|
(
|
||||||
|
document.getElementById("firstname") as HTMLInputElement
|
||||||
|
)?.value.trim() || "";
|
||||||
|
const lastname =
|
||||||
|
(
|
||||||
|
document.getElementById("lastname") as HTMLInputElement
|
||||||
|
)?.value.trim() || "";
|
||||||
|
const email =
|
||||||
|
(
|
||||||
|
document.getElementById("email") as HTMLInputElement
|
||||||
|
)?.value.trim() || "";
|
||||||
|
|
||||||
if (!username || !password || Number.isNaN(role)) return;
|
// admin kommt jetzt zuverlässig aus dem State
|
||||||
|
const res = await createUser(
|
||||||
|
username,
|
||||||
|
role,
|
||||||
|
password,
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
|
email,
|
||||||
|
admin
|
||||||
|
);
|
||||||
|
|
||||||
const res = await createUser(username, role, password);
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
alert(
|
alert(
|
||||||
"success",
|
"success",
|
||||||
@@ -79,6 +141,7 @@ const AddForm: React.FC<AddFormProps> = ({ onClose, alert }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Card.Footer>
|
</Card.Footer>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,18 +30,17 @@ import {
|
|||||||
} from "@/utils/userActions";
|
} from "@/utils/userActions";
|
||||||
import AddItemForm from "./AddItemForm";
|
import AddItemForm from "./AddItemForm";
|
||||||
import { formatDateTime } from "@/utils/userFuncs";
|
import { formatDateTime } from "@/utils/userFuncs";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
const API_BASE =
|
|
||||||
(import.meta as any).env?.VITE_BACKEND_URL ||
|
|
||||||
import.meta.env.VITE_BACKEND_URL ||
|
|
||||||
"http://localhost:8002";
|
|
||||||
|
|
||||||
type Items = {
|
type Items = {
|
||||||
id: number;
|
id: number;
|
||||||
item_name: string;
|
item_name: string;
|
||||||
can_borrow_role: string;
|
can_borrow_role: string;
|
||||||
inSafe: boolean;
|
in_safe: boolean;
|
||||||
entry_created_at: string;
|
entry_created_at: string;
|
||||||
|
entry_updated_at: string;
|
||||||
|
last_borrowed_person: string | null;
|
||||||
|
currently_borrowing: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ItemTable: React.FC = () => {
|
const ItemTable: React.FC = () => {
|
||||||
@@ -82,12 +81,15 @@ const ItemTable: React.FC = () => {
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/allItems`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/item-data/all-items`,
|
||||||
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -193,6 +195,15 @@ const ItemTable: React.FC = () => {
|
|||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Eintrag erstellt am</strong>
|
<strong>Eintrag erstellt am</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Eintrag aktualisiert am</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Letzte ausleihende Person</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Derzeit ausgeliehen von</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Aktionen</strong>
|
<strong>Aktionen</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
@@ -229,31 +240,34 @@ const ItemTable: React.FC = () => {
|
|||||||
py={1}
|
py={1}
|
||||||
gap={2}
|
gap={2}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color={item.inSafe ? "green.600" : "red.600"}
|
color={item.in_safe ? "green.600" : "red.600"}
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
borderColor={item.inSafe ? "green.300" : "red.300"}
|
borderColor={item.in_safe ? "green.300" : "red.300"}
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: item.inSafe ? "green.50" : "red.50",
|
bg: item.in_safe ? "green.50" : "red.50",
|
||||||
borderColor: item.inSafe ? "green.400" : "red.400",
|
borderColor: item.in_safe ? "green.400" : "red.400",
|
||||||
transform: "translateY(-1px)",
|
transform: "translateY(-1px)",
|
||||||
shadow: "sm",
|
shadow: "sm",
|
||||||
}}
|
}}
|
||||||
_active={{ transform: "translateY(0)" }}
|
_active={{ transform: "translateY(0)" }}
|
||||||
aria-label={
|
aria-label={
|
||||||
item.inSafe ? "Mark as not in safe" : "Mark as in safe"
|
item.in_safe ? "Mark as not in safe" : "Mark as in safe"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
as={item.inSafe ? CheckCircle2 : XCircle}
|
as={item.in_safe ? CheckCircle2 : XCircle}
|
||||||
boxSize={3.5}
|
boxSize={3.5}
|
||||||
mr={2}
|
mr={2}
|
||||||
/>
|
/>
|
||||||
<Text as="span" fontSize="xs" fontWeight="semibold">
|
<Text as="span" fontSize="xs" fontWeight="semibold">
|
||||||
{item.inSafe ? "Yes" : "No"}
|
{item.in_safe ? "Yes" : "No"}
|
||||||
</Text>
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>{formatDateTime(item.entry_created_at)}</Table.Cell>
|
<Table.Cell>{formatDateTime(item.entry_created_at)}</Table.Cell>
|
||||||
|
<Table.Cell>{formatDateTime(item.entry_updated_at)}</Table.Cell>
|
||||||
|
<Table.Cell>{item.last_borrowed_person}</Table.Cell>
|
||||||
|
<Table.Cell>{item.currently_borrowing}</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
@@ -17,11 +17,7 @@ import MyAlert from "./myChakra/MyAlert";
|
|||||||
import { formatDateTime } from "@/utils/userFuncs";
|
import { formatDateTime } from "@/utils/userFuncs";
|
||||||
import { Trash2, RefreshCcwDot } from "lucide-react";
|
import { Trash2, RefreshCcwDot } from "lucide-react";
|
||||||
import { deleteLoan } from "@/utils/userActions";
|
import { deleteLoan } from "@/utils/userActions";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
const API_BASE =
|
|
||||||
(import.meta as any).env?.VITE_BACKEND_URL ||
|
|
||||||
import.meta.env.VITE_BACKEND_URL ||
|
|
||||||
"http://localhost:8002";
|
|
||||||
|
|
||||||
const LoanTable: React.FC = () => {
|
const LoanTable: React.FC = () => {
|
||||||
const [items, setItems] = useState<Loan[]>([]);
|
const [items, setItems] = useState<Loan[]>([]);
|
||||||
@@ -55,18 +51,22 @@ const LoanTable: React.FC = () => {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
loaned_items_name: string[];
|
loaned_items_name: string[];
|
||||||
deleted: boolean;
|
deleted: boolean;
|
||||||
|
note: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/allLoans`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/loan-data/all-loans`,
|
||||||
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -161,6 +161,9 @@ const LoanTable: React.FC = () => {
|
|||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Ausgeliehene Artikel</strong>
|
<strong>Ausgeliehene Artikel</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Notiz</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Aktionen</strong>
|
<strong>Aktionen</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
@@ -180,6 +183,7 @@ const LoanTable: React.FC = () => {
|
|||||||
<Table.Cell>{formatDateTime(item.returned_date)}</Table.Cell>
|
<Table.Cell>{formatDateTime(item.returned_date)}</Table.Cell>
|
||||||
<Table.Cell>{formatDateTime(item.created_at)}</Table.Cell>
|
<Table.Cell>{formatDateTime(item.created_at)}</Table.Cell>
|
||||||
<Table.Cell>{item.loaned_items_name.join(", ")}</Table.Cell>
|
<Table.Cell>{item.loaned_items_name.join(", ")}</Table.Cell>
|
||||||
|
<Table.Cell>{item.note}</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
HStack,
|
HStack,
|
||||||
IconButton,
|
IconButton,
|
||||||
Heading,
|
Heading,
|
||||||
|
Switch, // neu
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Tooltip } from "@/components/ui/tooltip";
|
import { Tooltip } from "@/components/ui/tooltip";
|
||||||
import { fetchUserData } from "@/utils/fetcher";
|
import { fetchUserData } from "@/utils/fetcher";
|
||||||
@@ -23,9 +24,13 @@ import ChangePWform from "./ChangePWform";
|
|||||||
type User = {
|
type User = {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
first_name: string;
|
||||||
role: string;
|
last_name: string;
|
||||||
|
email: string;
|
||||||
|
is_admin: boolean;
|
||||||
|
role: number;
|
||||||
entry_created_at: string;
|
entry_created_at: string;
|
||||||
|
entry_updated_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserTable: React.FC = () => {
|
const UserTable: React.FC = () => {
|
||||||
@@ -52,10 +57,20 @@ const UserTable: React.FC = () => {
|
|||||||
setIsError(true);
|
setIsError(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange = (userId: number, field: string, value: string) => {
|
const handleInputChange = (userId: number, field: string, value: any) => {
|
||||||
setUsers((prevUsers) =>
|
setUsers((prevUsers) =>
|
||||||
prevUsers.map((user) =>
|
prevUsers.map((user) =>
|
||||||
user.id === userId ? { ...user, [field]: value } : user
|
user.id === userId
|
||||||
|
? {
|
||||||
|
...user,
|
||||||
|
[field]:
|
||||||
|
field === "role"
|
||||||
|
? Number(value)
|
||||||
|
: field === "is_admin"
|
||||||
|
? value === true || value === "true" || value === 1
|
||||||
|
: value,
|
||||||
|
}
|
||||||
|
: user
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -70,7 +85,7 @@ const UserTable: React.FC = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await fetchUserData();
|
const data = await fetchUserData();
|
||||||
console.log("user api response", data);
|
console.log(data);
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
setUsers(data);
|
setUsers(data);
|
||||||
} else {
|
} else {
|
||||||
@@ -189,6 +204,18 @@ const UserTable: React.FC = () => {
|
|||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Benutzername</strong>
|
<strong>Benutzername</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Vorname</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Nachname</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>E-Mail</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Admin</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Passwort ändern</strong>
|
<strong>Passwort ändern</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
@@ -198,6 +225,9 @@ const UserTable: React.FC = () => {
|
|||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Eintrag erstellt am</strong>
|
<strong>Eintrag erstellt am</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
|
<Table.ColumnHeader>
|
||||||
|
<strong>Eintrag aktualisiert am</strong>
|
||||||
|
</Table.ColumnHeader>
|
||||||
<Table.ColumnHeader>
|
<Table.ColumnHeader>
|
||||||
<strong>Aktionen</strong>
|
<strong>Aktionen</strong>
|
||||||
</Table.ColumnHeader>
|
</Table.ColumnHeader>
|
||||||
@@ -207,14 +237,58 @@ const UserTable: React.FC = () => {
|
|||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<Table.Row key={user.id}>
|
<Table.Row key={user.id}>
|
||||||
<Table.Cell>{user.id}</Table.Cell>
|
<Table.Cell>{user.id}</Table.Cell>
|
||||||
|
<Table.Cell>{user.username}</Table.Cell>
|
||||||
|
|
||||||
|
{/* Vorname */}
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Input
|
<Input
|
||||||
|
size="sm"
|
||||||
|
value={user.first_name ?? ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(user.id, "username", e.target.value)
|
handleInputChange(user.id, "first_name", e.target.value)
|
||||||
}
|
}
|
||||||
value={user.username}
|
|
||||||
/>
|
/>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
|
|
||||||
|
{/* Nachname */}
|
||||||
|
<Table.Cell>
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
value={user.last_name ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(user.id, "last_name", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
|
||||||
|
{/* E-Mail */}
|
||||||
|
<Table.Cell>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
size="sm"
|
||||||
|
value={user.email ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(user.id, "email", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
|
||||||
|
{/* Admin */}
|
||||||
|
<Table.Cell>
|
||||||
|
<Switch.Root
|
||||||
|
size="sm"
|
||||||
|
checked={!!user.is_admin}
|
||||||
|
onCheckedChange={(details) =>
|
||||||
|
handleInputChange(user.id, "is_admin", details.checked)
|
||||||
|
}
|
||||||
|
aria-label="Adminrechte umschalten"
|
||||||
|
>
|
||||||
|
<Switch.Control>
|
||||||
|
<Switch.Thumb />
|
||||||
|
</Switch.Control>
|
||||||
|
<Switch.HiddenInput />
|
||||||
|
</Switch.Root>
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Button onClick={() => handlePasswordChange(user.username)}>
|
<Button onClick={() => handlePasswordChange(user.username)}>
|
||||||
Passwort ändern
|
Passwort ändern
|
||||||
@@ -230,13 +304,17 @@ const UserTable: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>{formatDateTime(user.entry_created_at)}</Table.Cell>
|
<Table.Cell>{formatDateTime(user.entry_created_at)}</Table.Cell>
|
||||||
|
<Table.Cell>{formatDateTime(user.entry_updated_at)}</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleEdit(
|
handleEdit(
|
||||||
user.id,
|
user.id,
|
||||||
user.username,
|
user.first_name,
|
||||||
user.role,
|
user.last_name,
|
||||||
|
user.email,
|
||||||
|
user.is_admin,
|
||||||
|
Number(user.role)
|
||||||
).then((response) => {
|
).then((response) => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setError(
|
setError(
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
const API_BASE =
|
|
||||||
(import.meta as any).env?.VITE_BACKEND_URL ||
|
|
||||||
import.meta.env.VITE_BACKEND_URL ||
|
|
||||||
"http://localhost:8002";
|
|
||||||
|
|
||||||
export const fetchUserData = async () => {
|
export const fetchUserData = async () => {
|
||||||
const response = await fetch(`${API_BASE}/api/allUsers`, {
|
const response = await fetch(`${API_BASE}/api/admin/user-data/users`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
const API_BASE =
|
|
||||||
(import.meta as any).env?.VITE_BACKEND_URL ||
|
|
||||||
import.meta.env.VITE_BACKEND_URL ||
|
|
||||||
"http://localhost:8002";
|
|
||||||
|
|
||||||
export type LoginSuccess = { success: true };
|
export type LoginSuccess = { success: true };
|
||||||
export type LoginFailure = {
|
export type LoginFailure = {
|
||||||
@@ -18,12 +14,20 @@ export const loginFunc = async (
|
|||||||
password: string
|
password: string
|
||||||
): Promise<LoginResult> => {
|
): Promise<LoginResult> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/loginAdmin`, {
|
const response = await fetch(`${API_BASE}/api/admin/user-mgmt/login`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username, password }),
|
body: JSON.stringify({ username, password }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (response.status === 403) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Login failed!",
|
||||||
|
description: "You are not an admin user.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -39,6 +43,7 @@ export const loginFunc = async (
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error logging in:", error);
|
console.error("Error logging in:", error);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Login failed!",
|
message: "Login failed!",
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
const API_BASE =
|
|
||||||
(import.meta as any).env?.VITE_BACKEND_URL ||
|
|
||||||
import.meta.env.VITE_BACKEND_URL ||
|
|
||||||
"http://localhost:8002";
|
|
||||||
|
|
||||||
export const handleDelete = async (userId: number) => {
|
export const handleDelete = async (userId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/api/deleteUser/${userId}`,
|
`${API_BASE}/api/admin/user-data/delete-user/${userId}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -28,19 +24,28 @@ export const handleDelete = async (userId: number) => {
|
|||||||
|
|
||||||
export const handleEdit = async (
|
export const handleEdit = async (
|
||||||
userId: number,
|
userId: number,
|
||||||
username: string,
|
first_name: string,
|
||||||
role: string
|
last_name: string,
|
||||||
|
email: string,
|
||||||
|
is_admin: boolean,
|
||||||
|
role: number
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/api/editUser/${userId}`,
|
`${API_BASE}/api/admin/user-data/edit-user/${userId}`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ username, role }),
|
body: JSON.stringify({
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
role,
|
||||||
|
email,
|
||||||
|
is_admin,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -56,17 +61,32 @@ export const handleEdit = async (
|
|||||||
export const createUser = async (
|
export const createUser = async (
|
||||||
username: string,
|
username: string,
|
||||||
role: number,
|
role: number,
|
||||||
password: string
|
password: string,
|
||||||
|
first_name: string,
|
||||||
|
last_name: string,
|
||||||
|
email: string,
|
||||||
|
isAdmin: boolean
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/createUser`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/user-data/create-user`,
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ username, role, password }),
|
body: JSON.stringify({
|
||||||
});
|
username,
|
||||||
|
role,
|
||||||
|
password,
|
||||||
|
isAdmin,
|
||||||
|
email,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to create user");
|
throw new Error("Failed to create user");
|
||||||
}
|
}
|
||||||
@@ -79,14 +99,17 @@ export const createUser = async (
|
|||||||
|
|
||||||
export const changePW = async (newPassword: string, username: string) => {
|
export const changePW = async (newPassword: string, username: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/changePWadmin`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/user-data/change-password`,
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ newPassword, username }),
|
body: JSON.stringify({ username, password: newPassword }),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to change password");
|
throw new Error("Failed to change password");
|
||||||
}
|
}
|
||||||
@@ -100,7 +123,7 @@ export const changePW = async (newPassword: string, username: string) => {
|
|||||||
export const deleteLoan = async (loanId: number) => {
|
export const deleteLoan = async (loanId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/api/deleteLoan/${loanId}`,
|
`${API_BASE}/api/admin/loan-data/delete-loan/${loanId}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -121,7 +144,7 @@ export const deleteLoan = async (loanId: number) => {
|
|||||||
export const deleteItem = async (itemId: number) => {
|
export const deleteItem = async (itemId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/api/deleteItem/${itemId}`,
|
`${API_BASE}/api/admin/item-data/delete-item/${itemId}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -144,14 +167,17 @@ export const createItem = async (
|
|||||||
can_borrow_role: number
|
can_borrow_role: number
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/createItem`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/item-data/create-item`,
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ item_name, can_borrow_role }),
|
body: JSON.stringify({ item_name, can_borrow_role }),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -172,14 +198,17 @@ export const handleEditItems = async (
|
|||||||
can_borrow_role: string
|
can_borrow_role: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/updateItemByID`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/item-data/edit-item/${itemId}`,
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ itemId, item_name, can_borrow_role }),
|
body: JSON.stringify({ item_name, can_borrow_role }),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to edit item");
|
throw new Error("Failed to edit item");
|
||||||
}
|
}
|
||||||
@@ -193,9 +222,9 @@ export const handleEditItems = async (
|
|||||||
export const changeSafeState = async (itemId: number) => {
|
export const changeSafeState = async (itemId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/api/changeSafeState/${itemId}`,
|
`${API_BASE}/api/admin/item-data/change-safe-state/${itemId}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
@@ -211,16 +240,19 @@ export const changeSafeState = async (itemId: number) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createAPIentry = async (apiKey: string, user: string) => {
|
export const createAPIentry = async (apiKey: string, name: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/api/createAPIentry`, {
|
const response = await fetch(
|
||||||
|
`${API_BASE}/api/admin/api-data/create-api-key`,
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ apiKey, user }),
|
body: JSON.stringify({ apiKey, entryName: name }),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -238,7 +270,7 @@ export const createAPIentry = async (apiKey: string, user: string) => {
|
|||||||
export const deleteAPKey = async (apiKeyId: number) => {
|
export const deleteAPKey = async (apiKeyId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/api/deleteAPKey/${apiKeyId}`,
|
`${API_BASE}/api/admin/api-data/delete-api-key/${apiKeyId}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ dotenv.config();
|
|||||||
import {
|
import {
|
||||||
getAllApiKeys,
|
getAllApiKeys,
|
||||||
createAPIentry,
|
createAPIentry,
|
||||||
deleteAPIKey,
|
deleteAPKey,
|
||||||
} from "./database/apiDataMgmt.database.js";
|
} from "./database/apiDataMgmt.database.js";
|
||||||
|
|
||||||
router.get("/get-api-keys", authenticateAdmin, async (req, res) => {
|
router.get("/get-api-keys", authenticateAdmin, async (req, res) => {
|
||||||
const result = await getAllApiKeys();
|
const result = await getAllApiKeys();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(200).json({ apiKeys: result.data });
|
return res.status(200).json(result.data);
|
||||||
}
|
}
|
||||||
return res.status(500).json({ message: "Failed to retrieve API keys" });
|
return res.status(500).json({ message: "Failed to retrieve API keys" });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/create-api-key", authenticateAdmin, async (req, res) => {
|
router.post("/create-api-key", authenticateAdmin, async (req, res) => {
|
||||||
const apiKey = req.body.apiKey;
|
const apiKey = req.body.apiKey;
|
||||||
const username = req.body.username;
|
const entryName = req.body.entryName;
|
||||||
const result = await createAPIentry(apiKey, username);
|
const result = await createAPIentry(apiKey, entryName);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(201).json({ message: "API key created successfully" });
|
return res.status(201).json({ message: "API key created successfully" });
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ router.post("/create-api-key", authenticateAdmin, async (req, res) => {
|
|||||||
|
|
||||||
router.delete("/delete-api-key/:id", authenticateAdmin, async (req, res) => {
|
router.delete("/delete-api-key/:id", authenticateAdmin, async (req, res) => {
|
||||||
const apiKeyId = req.params.id;
|
const apiKeyId = req.params.id;
|
||||||
const result = await deleteAPIKey(apiKeyId);
|
const result = await deleteAPKey(apiKeyId);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(200).json({ message: "API key deleted successfully" });
|
return res.status(200).json({ message: "API key deleted successfully" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ export const getAllApiKeys = async () => {
|
|||||||
return { success: false };
|
return { success: false };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createAPIentry = async (apiKey, user) => {
|
export const createAPIentry = async (apiKey, entryName) => {
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
"INSERT INTO apiKeys (api_key, username) VALUES (?, ?)",
|
"INSERT INTO apiKeys (api_key, entry_name) VALUES (?, ?)",
|
||||||
[apiKey, user]
|
[apiKey, entryName]
|
||||||
);
|
);
|
||||||
if (result.affectedRows > 0) return { success: true };
|
if (result.affectedRows > 0) return { success: true };
|
||||||
return { success: false };
|
return { success: false };
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const deleteItemById = async (itemId) => {
|
|||||||
export const createItem = async (item_name, can_borrow_role, in_safe) => {
|
export const createItem = async (item_name, can_borrow_role, in_safe) => {
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
"INSERT INTO items (item_name, can_borrow_role, in_safe) VALUES (?, ?, ?)",
|
"INSERT INTO items (item_name, can_borrow_role, in_safe) VALUES (?, ?, ?)",
|
||||||
[item_name, can_borrow_role, in_safe]
|
[item_name, can_borrow_role, true]
|
||||||
);
|
);
|
||||||
if (result.affectedRows > 0) return { success: true };
|
if (result.affectedRows > 0) return { success: true };
|
||||||
return { success: false };
|
return { success: false };
|
||||||
@@ -34,9 +34,37 @@ export const createItem = async (item_name, can_borrow_role, in_safe) => {
|
|||||||
|
|
||||||
export const editItemById = async (itemId, item_name, can_borrow_role) => {
|
export const editItemById = async (itemId, item_name, can_borrow_role) => {
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
"UPDATE items SET item_name = ?, can_borrow_role = ? WHERE id = ?",
|
"UPDATE items SET item_name = ?, can_borrow_role = ?, entry_updated_at = NOW() WHERE id = ?",
|
||||||
[item_name, can_borrow_role, itemId]
|
[item_name, can_borrow_role, itemId]
|
||||||
);
|
);
|
||||||
if (result.affectedRows > 0) return { success: true };
|
if (result.affectedRows > 0) return { success: true };
|
||||||
return { success: false };
|
return { success: false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const changeSafeState = async (itemId) => {
|
||||||
|
const currentState = await pool.query(
|
||||||
|
"SELECT in_safe FROM items WHERE id = ?",
|
||||||
|
[itemId]
|
||||||
|
);
|
||||||
|
if (currentState[0].length === 0) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentState[0][0].in_safe) {
|
||||||
|
const [result] = await pool.query(
|
||||||
|
"UPDATE items SET in_safe = false WHERE id = ?",
|
||||||
|
[itemId]
|
||||||
|
);
|
||||||
|
if (result.affectedRows > 0) return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentState[0][0].in_safe) {
|
||||||
|
const [result] = await pool.query(
|
||||||
|
"UPDATE items SET in_safe = true WHERE id = ?",
|
||||||
|
[itemId]
|
||||||
|
);
|
||||||
|
if (result.affectedRows > 0) return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const editUserById = async (
|
|||||||
|
|
||||||
export const getAllUsers = async () => {
|
export const getAllUsers = async () => {
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
"SELECT id, username, first_name, last_name, role, email, is_admin FROM users"
|
"SELECT id, username, first_name, last_name, role, email, is_admin, entry_created_at, entry_updated_at FROM users"
|
||||||
);
|
);
|
||||||
if (result.length > 0) return { success: true, data: result };
|
if (result.length > 0) return { success: true, data: result };
|
||||||
return { success: false };
|
return { success: false };
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
router.get("/all-items", authenticateAdmin, async (req, res) => {
|
router.get("/all-items", authenticateAdmin, async (req, res) => {
|
||||||
const result = await getAllItems();
|
const result = await getAllItems();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(200).json({ items: result.data });
|
return res.status(200).json(result.data);
|
||||||
}
|
}
|
||||||
return res.status(500).json({ message: "Failed to retrieve items" });
|
return res.status(500).json({ message: "Failed to retrieve items" });
|
||||||
});
|
});
|
||||||
@@ -31,8 +31,8 @@ router.delete("/delete-item/:id", authenticateAdmin, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post("/create-item", authenticateAdmin, async (req, res) => {
|
router.post("/create-item", authenticateAdmin, async (req, res) => {
|
||||||
const { item_name, can_borrow_role, in_safe } = req.body;
|
const { item_name, can_borrow_role } = req.body;
|
||||||
const result = await createItem(item_name, can_borrow_role, in_safe);
|
const result = await createItem(item_name, can_borrow_role);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(201).json({ message: "Item created successfully" });
|
return res.status(201).json({ message: "Item created successfully" });
|
||||||
}
|
}
|
||||||
@@ -55,8 +55,7 @@ router.post("/edit-item/:id", authenticateAdmin, async (req, res) => {
|
|||||||
|
|
||||||
router.post("/change-safe-state/:id", authenticateAdmin, async (req, res) => {
|
router.post("/change-safe-state/:id", authenticateAdmin, async (req, res) => {
|
||||||
const itemId = req.params.id;
|
const itemId = req.params.id;
|
||||||
const { in_safe } = req.body;
|
const result = await changeSafeState(itemId);
|
||||||
const result = await changeSafeState(itemId, in_safe);
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(200).json({ message: "Safe state changed successfully" });
|
return res.status(200).json({ message: "Safe state changed successfully" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
router.get("/all-loans", authenticateAdmin, async (req, res) => {
|
router.get("/all-loans", authenticateAdmin, async (req, res) => {
|
||||||
const result = await getAllLoans();
|
const result = await getAllLoans();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(200).json({ loans: result.data });
|
return res.status(200).json(result.data);
|
||||||
}
|
}
|
||||||
return res.status(500).json({ message: "Failed to retrieve loans" });
|
return res.status(500).json({ message: "Failed to retrieve loans" });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ router.delete("/delete-user/:id", authenticateAdmin, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post("/edit-user/:id", authenticateAdmin, async (req, res) => {
|
router.post("/edit-user/:id", authenticateAdmin, async (req, res) => {
|
||||||
const password = req.body.password;
|
|
||||||
const first_name = req.body.first_name;
|
const first_name = req.body.first_name;
|
||||||
const last_name = req.body.last_name;
|
const last_name = req.body.last_name;
|
||||||
const role = req.body.role;
|
const role = req.body.role;
|
||||||
@@ -57,7 +56,6 @@ router.post("/edit-user/:id", authenticateAdmin, async (req, res) => {
|
|||||||
|
|
||||||
const result = await editUserById(
|
const result = await editUserById(
|
||||||
userId,
|
userId,
|
||||||
password,
|
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
role,
|
role,
|
||||||
@@ -109,7 +107,7 @@ router.post("/edit-user/:id", authenticateAdmin, async (req, res) => {
|
|||||||
router.get("/users", authenticateAdmin, async (req, res) => {
|
router.get("/users", authenticateAdmin, async (req, res) => {
|
||||||
const result = await getAllUsers();
|
const result = await getAllUsers();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return res.status(200).json({ users: result.data });
|
return res.status(200).json(result.data);
|
||||||
}
|
}
|
||||||
return res.status(500).json({ message: "Failed to retrieve users" });
|
return res.status(500).json({ message: "Failed to retrieve users" });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { authenticate, generateToken } from "../../services/authentication.js";
|
import {
|
||||||
|
generateToken,
|
||||||
|
authenticateAdmin,
|
||||||
|
} from "../../services/authentication.js";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import nodemailer from "nodemailer";
|
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -9,7 +11,12 @@ dotenv.config();
|
|||||||
import { loginAdmin } from "./database/userMgmt.database.js";
|
import { loginAdmin } from "./database/userMgmt.database.js";
|
||||||
|
|
||||||
router.post("/login", async (req, res) => {
|
router.post("/login", async (req, res) => {
|
||||||
const result = await loginAdmin(req.body.username, req.body.password);
|
const { username, password } = req.body || {};
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).json({ message: "Missing username or password" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await loginAdmin(username, password);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const token = await generateToken({
|
const token = await generateToken({
|
||||||
@@ -18,7 +25,11 @@ router.post("/login", async (req, res) => {
|
|||||||
last_name: result.data.last_name,
|
last_name: result.data.last_name,
|
||||||
admin: result.data.is_admin,
|
admin: result.data.is_admin,
|
||||||
});
|
});
|
||||||
return res.status(200).json({ message: "Login erfolgreich", token });
|
return res.status(200).json({
|
||||||
|
message: "Login erfolgreich",
|
||||||
|
token,
|
||||||
|
first_name: result.data.first_name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.reason === "not_admin") {
|
if (result.reason === "not_admin") {
|
||||||
@@ -27,3 +38,9 @@ router.post("/login", async (req, res) => {
|
|||||||
|
|
||||||
return res.status(401).json({ message: "Ungültige Anmeldedaten" });
|
return res.status(401).json({ message: "Ungültige Anmeldedaten" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/verify-token", authenticateAdmin, async (req, res) => {
|
||||||
|
return res.status(200).json({ message: "Token is valid" });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { authenticate, generateToken } from "../services/tokenService.js";
|
import { authenticate, generateToken } from "../../services/authentication.js";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import nodemailer from "nodemailer";
|
import nodemailer from "nodemailer";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
@@ -21,3 +21,4 @@ router.post("/login", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|||||||
@@ -1,91 +1,39 @@
|
|||||||
-- MUST BE UPDATED BEFORE USE
|
-- Mock data for borrow_system_new
|
||||||
|
|
||||||
USE borrow_system_new;
|
USE borrow_system_new;
|
||||||
|
|
||||||
-- Optional: keep insert order predictable
|
START TRANSACTION;
|
||||||
SET time_zone = '+00:00';
|
|
||||||
|
|
||||||
-- Users
|
-- Users
|
||||||
INSERT INTO users (username, password, first_name, last_name, role, is_admin)
|
INSERT INTO users (username, password, email, first_name, last_name, role, is_admin, entry_created_at)
|
||||||
VALUES
|
VALUES
|
||||||
('alice', 'password123', 'Alice', 'Andersen', 1, false),
|
('admin', '$2b$12$adminhashedpasswordplaceholder0000000000', 'admin@example.com', 'System', 'Admin', 99, TRUE, '2025-01-01 08:00:00'),
|
||||||
('bob', 'password123', 'Bob', 'Berg', 2, false),
|
('alice', '$2b$12$alicehashedpasswordplaceholder0000000000', 'alice@example.com', 'Alice', 'Anderson', 1, FALSE, '2025-06-01 09:10:00'),
|
||||||
('carol', 'password123', 'Carol', 'Christie', 2, false),
|
('bob', '$2b$12$bobhashedpasswordplaceholder000000000000', 'bob@example.com', 'Bob', 'Brown', 2, FALSE, '2025-06-02 10:15:00'),
|
||||||
('dave', 'password123', 'Dave', 'Dawson', 1, false),
|
('carol', '$2b$12$carolhashedpasswordplaceholder00000000000', 'carol@example.com', 'Carol', 'Clark', 0, FALSE, '2025-06-03 11:20:00');
|
||||||
('eve', 'password123', 'Eve', 'Evans', 1, false),
|
|
||||||
('admin', 'password123', 'Admin', 'User', 3, true);
|
|
||||||
|
|
||||||
-- Items
|
-- Items (ids will start at 1)
|
||||||
INSERT INTO items (item_name, can_borrow_role, in_safe, last_borrowed_person, currently_borrowing)
|
INSERT INTO items (item_name, can_borrow_role, in_safe, entry_created_at, last_borrowed_person, currently_borrowing)
|
||||||
VALUES
|
VALUES
|
||||||
('Canon EOS 90D Camera', 1, false, 'bob', 'alice'),
|
('MacBook Pro 16\"', 1, TRUE, '2025-05-01 09:00:00', 'alice', NULL),
|
||||||
('Rode NT1 Microphone', 1, true, 'dave', NULL),
|
('Projector Epson X200', 2, TRUE, '2025-04-20 10:00:00', 'bob', NULL),
|
||||||
('MacBook Pro 13', 2, false, 'bob', 'carol'),
|
('Canon EOS R6', 1, TRUE, '2025-03-15 14:30:00', NULL, NULL),
|
||||||
('Tripod Manfrotto', 1, false, 'carol', 'alice'),
|
('Wireless Microphone', 0, TRUE,'2025-05-10 12:00:00', 'carol', NULL),
|
||||||
('LED Panel Aputure', 1, true, NULL, NULL),
|
('USB-C Charger', 0, FALSE, '2025-05-11 12:30:00', 'alice', 'alice');
|
||||||
('Zoom H6 Recorder', 1, true, 'dave', NULL),
|
|
||||||
('Wacom Intuos Tablet', 1, true, NULL, NULL),
|
|
||||||
('DJI Ronin-S Gimbal', 2, true, NULL, NULL),
|
|
||||||
('Sony A7 III Body', 2, false, 'carol', 'eve'),
|
|
||||||
('Sigma 24-70mm Lens', 2, false, 'carol', 'eve');
|
|
||||||
|
|
||||||
-- Capture item IDs for JSON arrays
|
|
||||||
SET @id_canon = (SELECT id FROM items WHERE item_name='Canon EOS 90D Camera');
|
|
||||||
SET @id_rode = (SELECT id FROM items WHERE item_name='Rode NT1 Microphone');
|
|
||||||
SET @id_mac13 = (SELECT id FROM items WHERE item_name='MacBook Pro 13');
|
|
||||||
SET @id_tripod = (SELECT id FROM items WHERE item_name='Tripod Manfrotto');
|
|
||||||
SET @id_led = (SELECT id FROM items WHERE item_name='LED Panel Aputure');
|
|
||||||
SET @id_zoom = (SELECT id FROM items WHERE item_name='Zoom H6 Recorder');
|
|
||||||
SET @id_tablet = (SELECT id FROM items WHERE item_name='Wacom Intuos Tablet');
|
|
||||||
SET @id_ronin = (SELECT id FROM items WHERE item_name='DJI Ronin-S Gimbal');
|
|
||||||
SET @id_sony = (SELECT id FROM items WHERE item_name='Sony A7 III Body');
|
|
||||||
SET @id_sigma = (SELECT id FROM items WHERE item_name='Sigma 24-70mm Lens');
|
|
||||||
|
|
||||||
-- Loans
|
-- Loans
|
||||||
INSERT INTO loans (
|
INSERT INTO loans (username, loan_code, start_date, end_date, take_date, returned_date, created_at, loaned_items_id, loaned_items_name, deleted, note)
|
||||||
username, loan_code, start_date, end_date, take_date, returned_date, loaned_items_id, loaned_items_name, deleted
|
|
||||||
) VALUES
|
|
||||||
-- Ongoing loan: Alice has Canon + Tripod
|
|
||||||
('alice', 100001, '2025-10-01 09:00:00', '2025-10-08 17:00:00', '2025-10-01 09:15:00', NULL,
|
|
||||||
JSON_ARRAY(@id_canon, @id_tripod),
|
|
||||||
JSON_ARRAY('Canon EOS 90D Camera','Tripod Manfrotto'),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
-- Ongoing loan: Carol has MacBook Pro 13
|
|
||||||
('carol', 100002, '2025-10-03 10:00:00', '2025-10-10 16:00:00', '2025-10-03 10:05:00', NULL,
|
|
||||||
JSON_ARRAY(@id_mac13),
|
|
||||||
JSON_ARRAY('MacBook Pro 13'),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
-- Returned loan: Dave had Zoom + Rode
|
|
||||||
('dave', 100003, '2025-09-10 08:30:00', '2025-09-12 16:00:00', '2025-09-10 08:45:00', '2025-09-12 15:40:00',
|
|
||||||
JSON_ARRAY(@id_zoom, @id_rode),
|
|
||||||
JSON_ARRAY('Zoom H6 Recorder','Rode NT1 Microphone'),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
-- Cancelled/deleted booking (never taken): Bob reserved Tablet
|
|
||||||
('bob', 100004, '2025-10-05 09:00:00', '2025-10-06 09:00:00', NULL, NULL,
|
|
||||||
JSON_ARRAY(@id_tablet),
|
|
||||||
JSON_ARRAY('Wacom Intuos Tablet'),
|
|
||||||
true
|
|
||||||
),
|
|
||||||
-- Ongoing loan, likely overdue: Eve has Sony + Sigma
|
|
||||||
('eve', 100005, '2025-10-15 11:00:00', '2025-10-20 12:00:00', '2025-10-15 11:10:00', NULL,
|
|
||||||
JSON_ARRAY(@id_sony, @id_sigma),
|
|
||||||
JSON_ARRAY('Sony A7 III Body','Sigma 24-70mm Lens'),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
-- Completed single-day loan: Bob used LED panel
|
|
||||||
('bob', 100006, '2025-09-20 13:00:00', '2025-09-20 18:00:00', '2025-09-20 13:05:00', '2025-09-20 17:30:00',
|
|
||||||
JSON_ARRAY(@id_led),
|
|
||||||
JSON_ARRAY('LED Panel Aputure'),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
-- API keys
|
|
||||||
INSERT INTO apiKeys (api_key, username)
|
|
||||||
VALUES
|
VALUES
|
||||||
(71002123, 'alice'),
|
('alice', '000101', '2025-06-10 09:00:00', '2025-06-17 09:00:00', '2025-06-10 09:05:00', NULL, '2025-06-10 09:00:00',
|
||||||
(71002124, 'bob'),
|
JSON_ARRAY(1,5), JSON_ARRAY('MacBook Pro 16\"','USB-C Charger'), FALSE, 'For project work'),
|
||||||
(71002125, 'carol'),
|
('bob', '000102', '2025-06-01 14:00:00', '2025-06-04 12:00:00', '2025-06-01 14:10:00', '2025-06-04 11:50:00', '2025-06-01 14:00:00',
|
||||||
(99999999, 'admin');
|
JSON_ARRAY(2), JSON_ARRAY('Projector Epson X200'), FALSE, NULL),
|
||||||
|
('carol', '000103', '2025-06-05 08:00:00', '2025-06-06 18:00:00', NULL, NULL, '2025-06-05 08:00:00',
|
||||||
|
JSON_ARRAY(4), JSON_ARRAY('Wireless Microphone'), FALSE, 'Reserved for event');
|
||||||
|
|
||||||
|
-- API keys (15 digits)
|
||||||
|
INSERT INTO apiKeys (api_key, entry_name, entry_created_at, last_used_at)
|
||||||
|
VALUES
|
||||||
|
('000000000000001', 'internal-service-key', '2025-01-02 07:00:00', NULL),
|
||||||
|
('123456789012345', 'ci-pipeline', '2025-02-15 08:30:00', '2025-06-10 09:00:00');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -17,7 +17,7 @@ CREATE TABLE users (
|
|||||||
CREATE TABLE loans (
|
CREATE TABLE loans (
|
||||||
id int NOT NULL AUTO_INCREMENT,
|
id int NOT NULL AUTO_INCREMENT,
|
||||||
username varchar(100) NOT NULL,
|
username varchar(100) NOT NULL,
|
||||||
loan_code int NOT NULL UNIQUE,
|
loan_code Char(6) NOT NULL UNIQUE,
|
||||||
start_date timestamp NOT NULL,
|
start_date timestamp NOT NULL,
|
||||||
end_date timestamp NOT NULL,
|
end_date timestamp NOT NULL,
|
||||||
take_date timestamp NULL DEFAULT NULL,
|
take_date timestamp NULL DEFAULT NULL,
|
||||||
@@ -28,10 +28,7 @@ CREATE TABLE loans (
|
|||||||
deleted bool NOT NULL DEFAULT false,
|
deleted bool NOT NULL DEFAULT false,
|
||||||
note varchar(500) DEFAULT NULL,
|
note varchar(500) DEFAULT NULL,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CONSTRAINT fk_loans_username
|
CHECK (loan_code REGEXP '^[0-9]{6}$')
|
||||||
FOREIGN KEY (username) REFERENCES users(username)
|
|
||||||
ON UPDATE CASCADE
|
|
||||||
ON DELETE RESTRICT
|
|
||||||
) ENGINE=InnoDB;
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
CREATE TABLE items (
|
CREATE TABLE items (
|
||||||
@@ -47,15 +44,11 @@ CREATE TABLE items (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE apiKeys (
|
CREATE TABLE apiKeys (
|
||||||
id int NOT NULL AUTO_INCREMENT,
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
api_key CHAR(15) NOT NULL UNIQUE,
|
api_key CHAR(15) NOT NULL UNIQUE,
|
||||||
username VARCHAR(100) NOT NULL,
|
entry_name VARCHAR(100) NOT NULL,
|
||||||
last_used_at timestamp DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
last_used_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||||
entry_created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CONSTRAINT chk_api_key_len CHECK (CHAR_LENGTH(api_key) = 15),
|
CHECK (api_key REGEXP '^[0-9]{15}$')
|
||||||
CONSTRAINT fk_apikeys_username
|
|
||||||
FOREIGN KEY (username) REFERENCES users(username)
|
|
||||||
ON UPDATE CASCADE
|
|
||||||
ON DELETE RESTRICT
|
|
||||||
) ENGINE=InnoDB;
|
) ENGINE=InnoDB;
|
||||||
@@ -1,43 +1,51 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import env from "dotenv";
|
import env from "dotenv";
|
||||||
|
import info from "./info.json" assert { type: "json" };
|
||||||
|
|
||||||
// frontend routes
|
// frontend routes
|
||||||
import loansMgmtRouter from "./routes/app/loanMgmt.route.js";
|
import loansMgmtRouter from "./routes/app/loanMgmt.route.js";
|
||||||
import userMgmtRouter from "./routes/app/userMgmt.route.js";
|
import userMgmtRouterAPP from "./routes/app/userMgmt.route.js";
|
||||||
|
|
||||||
// admin routes
|
// admin routes
|
||||||
import userDataMgmtRouter from "./routes/admin/userDataMgmt.route.js";
|
import userDataMgmtRouter from "./routes/admin/userDataMgmt.route.js";
|
||||||
import loanDataMgmtRouter from "./routes/admin/loanDataMgmt.route.js";
|
import loanDataMgmtRouter from "./routes/admin/loanDataMgmt.route.js";
|
||||||
import itemDataMgmtRouter from "./routes/admin/itemDataMgmt.route.js";
|
import itemDataMgmtRouter from "./routes/admin/itemDataMgmt.route.js";
|
||||||
import apiDataMgmtRouter from "./routes/admin/apiDataMgmt.route.js";
|
import apiDataMgmtRouter from "./routes/admin/apiDataMgmt.route.js";
|
||||||
|
import userMgmtRouterADMIN from "./routes/admin/userMgmt.route.js";
|
||||||
|
|
||||||
env.config();
|
env.config();
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 8002;
|
const port = 8004;
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
// Body-Parser VOR den Routen registrieren
|
||||||
|
app.use(express.json({ limit: "10mb" }));
|
||||||
|
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
||||||
|
|
||||||
// frontend routes
|
// frontend routes
|
||||||
app.use("/api/loans", loansMgmtRouter);
|
app.use("/api/loans", loansMgmtRouter);
|
||||||
app.use("/api/users", userMgmtRouter);
|
app.use("/api/users", userMgmtRouterAPP);
|
||||||
|
|
||||||
// admin routes
|
// admin routes
|
||||||
app.use("/api/admin/loan-data", loanDataMgmtRouter);
|
app.use("/api/admin/loan-data", loanDataMgmtRouter);
|
||||||
app.use("/api/admin/user-data", userDataMgmtRouter);
|
app.use("/api/admin/user-data", userDataMgmtRouter);
|
||||||
app.use("/api/admin/item-data", itemDataMgmtRouter);
|
app.use("/api/admin/item-data", itemDataMgmtRouter);
|
||||||
app.use("/api/admin/api-data", apiDataMgmtRouter);
|
app.use("/api/admin/api-data", apiDataMgmtRouter);
|
||||||
|
app.use("/api/admin/user-mgmt", userMgmtRouterADMIN);
|
||||||
|
|
||||||
// Increase body size limits to support large CSV JSON payloads
|
|
||||||
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
|
||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
app.use(express.json({ limit: "10mb" }));
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server is running on port: ${port}`);
|
console.log(`Server is running on port: ${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
res.send(info);
|
||||||
|
});
|
||||||
|
|
||||||
// error handling code
|
// error handling code
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
// Log the error stack and send a generic error response
|
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
res.status(500).send("Something broke!");
|
res.status(500).send("Something broke!");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { SignJWT, jwtVerify } from "jose";
|
import { SignJWT, jwtVerify } from "jose";
|
||||||
import env from "dotenv";
|
import env from "dotenv";
|
||||||
import { getAllApiKeys } from "./database";
|
import { getAllApiKeys } from "./database.js";
|
||||||
env.config();
|
env.config();
|
||||||
|
|
||||||
const secretKey = process.env.SECRET_KEY;
|
const secretKey = process.env.SECRET_KEY;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ services:
|
|||||||
DB_HOST: mysql_v2
|
DB_HOST: mysql_v2
|
||||||
DB_USER: root
|
DB_USER: root
|
||||||
DB_PASSWORD: ${DB_PASSWORD_V2}
|
DB_PASSWORD: ${DB_PASSWORD_V2}
|
||||||
DB_NAME: borrow_system_v2
|
DB_NAME: borrow_system_new
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql_v2
|
- mysql_v2
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -68,7 +68,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD_V2}
|
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD_V2}
|
||||||
MYSQL_DATABASE: borrow_system_v2
|
MYSQL_DATABASE: borrow_system_new
|
||||||
TZ: Europe/Berlin
|
TZ: Europe/Berlin
|
||||||
volumes:
|
volumes:
|
||||||
- mysql-v2-data:/var/lib/mysql
|
- mysql-v2-data:/var/lib/mysql
|
||||||
|
|||||||
Reference in New Issue
Block a user