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:
2025-11-11 17:08:45 +01:00
parent 974a5a75d8
commit a8b4ac3d60
26 changed files with 605 additions and 347 deletions

View File

@@ -3,11 +3,7 @@ import { useEffect } from "react";
import Dashboard from "./Dashboard";
import Login from "./Login";
import Cookies from "js-cookie";
const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8002";
import { API_BASE } from "@/config/api.config";
const Layout: React.FC = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
@@ -15,12 +11,15 @@ const Layout: React.FC = () => {
useEffect(() => {
if (Cookies.get("token")) {
const verifyToken = async () => {
const response = await fetch(`${API_BASE}/api/verifyToken`, {
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
});
const response = await fetch(
`${API_BASE}/api/admin/user-mgmt/verify-token`,
{
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
}
);
if (response.ok) {
setIsLoggedIn(true);
} else {

View File

@@ -1,5 +1,7 @@
import React from "react";
import { useEffect, useState } from "react";
import { Box, Flex, VStack, Heading, Text, Link } from "@chakra-ui/react";
import { API_BASE } from "@/config/api.config";
type SidebarProps = {
viewAusleihen: () => void;
@@ -15,10 +17,22 @@ const Sidebar: React.FC<SidebarProps> = ({
viewUser,
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 (
<Box
as="aside"
w="260px"
w="180px"
minH="100vh"
bg="gray.800"
color="gray.100"
@@ -72,7 +86,33 @@ const Sidebar: React.FC<SidebarProps> = ({
</VStack>
<Box mt="auto" pt={8} fontSize="xs" color="gray.500">
<Text>&copy; Made with by Theis Gaedigk</Text>
<Text mb={2}>&copy; 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>
</Flex>
</Box>

View File

@@ -17,17 +17,14 @@ import { useState, useEffect } from "react";
import { deleteAPKey } from "@/utils/userActions";
import AddAPIKey from "./AddAPIKey";
import { formatDateTime } from "@/utils/userFuncs";
const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8002";
import { API_BASE } from "@/config/api.config";
type Items = {
id: number;
apiKey: string;
user: string;
api_key: string;
entry_name: string;
entry_created_at: string;
last_used_at: string | null;
};
const APIKeyTable: React.FC = () => {
@@ -56,13 +53,17 @@ const APIKeyTable: React.FC = () => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(`${API_BASE}/api/apiKeys`, {
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
});
const response = await fetch(
`${API_BASE}/api/admin/api-data/get-api-keys`,
{
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
}
);
const data = await response.json();
console.log(data);
return data;
} catch (error) {
setError("error", "Failed to fetch items", "There is an error");
@@ -159,29 +160,37 @@ const APIKeyTable: React.FC = () => {
<strong>API Key</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Benutzer</strong>
<strong>Name</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Eintrag erstellt am</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Zuletzt benutzt am</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Aktionen</strong>
</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{items.map((apiKey) => (
<Table.Row key={apiKey.id}>
<Table.Cell>{apiKey.id}</Table.Cell>
<Table.Cell>{apiKey.apiKey}</Table.Cell>
<Table.Cell>{apiKey.user}</Table.Cell>
<Table.Cell>{formatDateTime(apiKey.entry_created_at)}</Table.Cell>
{items.map((item) => (
<Table.Row key={item.id}>
<Table.Cell>{item.id}</Table.Cell>
<Table.Cell>{item.api_key}</Table.Cell>
<Table.Cell>{item.entry_name}</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>
<Button
onClick={() =>
deleteAPKey(apiKey.id).then((response) => {
deleteAPKey(item.id).then((response) => {
if (response.success) {
setItems(items.filter((i) => i.id !== apiKey.id));
setItems(items.filter((i) => i.id !== item.id));
setError(
"success",
"Gegenstand gelöscht",

View File

@@ -1,6 +1,15 @@
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 { useState } from "react";
type AddAPIKeyProps = {
onClose: () => void;
@@ -12,6 +21,8 @@ type AddAPIKeyProps = {
};
const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
const [value, setValue] = useState("");
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">
@@ -23,13 +34,26 @@ const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
</Card.Header>
<Card.Body>
<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.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.Label>Name</Field.Label>
<Input id="name" type="text" />
</Field.Root>
</Stack>
</Card.Body>
@@ -44,14 +68,14 @@ const AddAPIKey: React.FC<AddAPIKeyProps> = ({ onClose, alert }) => {
(
document.getElementById("apiKey") as HTMLInputElement
)?.value.trim() || "";
const user =
const name =
(
document.getElementById("user") as HTMLInputElement
document.getElementById("name") as HTMLInputElement
)?.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) {
alert(
"success",

View File

@@ -1,5 +1,13 @@
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";
type AddFormProps = {
@@ -12,73 +20,128 @@ type AddFormProps = {
};
const AddForm: React.FC<AddFormProps> = ({ onClose, alert }) => {
const [admin, setAdmin] = React.useState(false);
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 Nutzer erstellen</Card.Title>
<Card.Description>
Füllen Sie das folgende Formular aus, um einen Nutzer zu erstellen.
</Card.Description>
</Card.Header>
<Card.Body>
<Stack gap="4" w="full">
<Field.Root>
<Field.Label>Username</Field.Label>
<Input id="username" />
</Field.Root>
<Field.Root>
<Field.Label>Password</Field.Label>
<Input id="password" type="password" />
</Field.Root>
<Field.Root>
<Field.Label>Role</Field.Label>
<Input id="role" type="number" />
</Field.Root>
</Stack>
</Card.Body>
<Card.Footer justifyContent="flex-end">
<Button variant="outline" onClick={onClose}>
Abbrechen
</Button>
<Button
variant="solid"
onClick={async () => {
const username =
(
document.getElementById("username") as HTMLInputElement
)?.value.trim() || "";
const password =
(document.getElementById("password") as HTMLInputElement)
?.value || "";
const role = Number(
(document.getElementById("role") as HTMLInputElement)?.value
);
<form
onSubmit={(e) => {
e.preventDefault();
}}
>
<Card.Root maxW="sm">
<Card.Header>
<Card.Title>Neuen Nutzer erstellen</Card.Title>
<Card.Description>
Füllen Sie das folgende Formular aus, um einen Nutzer zu
erstellen.
</Card.Description>
</Card.Header>
if (!username || !password || Number.isNaN(role)) return;
<Card.Body>
<Stack gap="4" w="full">
<Field.Root>
<Field.Label>Benutzername</Field.Label>
<Input id="username" />
</Field.Root>
<Field.Root>
<Field.Label>Passwort</Field.Label>
<Input id="password" type="password" />
</Field.Root>
<Field.Root>
<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>
const res = await createUser(username, role, password);
if (res.success) {
alert(
"success",
"Nutzer erstellt",
"Der Nutzer wurde erfolgreich erstellt."
{/* 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" />
</Field.Root>
</Stack>
</Card.Body>
<Card.Footer justifyContent="flex-end">
<Text>Der Benutzername kann nicht mehr geändert werden.</Text>
<Button variant="outline" onClick={onClose}>
Abbrechen
</Button>
<Button
variant="solid"
type="submit"
onClick={async () => {
const username =
(
document.getElementById("username") as HTMLInputElement
)?.value.trim() || "";
const password =
(document.getElementById("password") as HTMLInputElement)
?.value || "";
const role = Number(
(document.getElementById("role") as HTMLInputElement)?.value
);
onClose();
} else {
alert(
"error",
"Fehler beim Erstellen des Nutzers",
"Es gab einen Fehler beim Erstellen des Nutzers. Vielleicht gibt es bereits einen Nutzer mit diesem Benutzernamen."
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() || "";
// admin kommt jetzt zuverlässig aus dem State
const res = await createUser(
username,
role,
password,
firstname,
lastname,
email,
admin
);
onClose();
}
}}
>
Erstellen
</Button>
</Card.Footer>
</Card.Root>
if (res.success) {
alert(
"success",
"Nutzer erstellt",
"Der Nutzer wurde erfolgreich erstellt."
);
onClose();
} else {
alert(
"error",
"Fehler beim Erstellen des Nutzers",
"Es gab einen Fehler beim Erstellen des Nutzers. Vielleicht gibt es bereits einen Nutzer mit diesem Benutzernamen."
);
onClose();
}
}}
>
Erstellen
</Button>
</Card.Footer>
</Card.Root>
</form>
</div>
);
};

View File

@@ -30,18 +30,17 @@ import {
} from "@/utils/userActions";
import AddItemForm from "./AddItemForm";
import { formatDateTime } from "@/utils/userFuncs";
const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8002";
import { API_BASE } from "@/config/api.config";
type Items = {
id: number;
item_name: string;
can_borrow_role: string;
inSafe: boolean;
in_safe: boolean;
entry_created_at: string;
entry_updated_at: string;
last_borrowed_person: string | null;
currently_borrowing: string | null;
};
const ItemTable: React.FC = () => {
@@ -82,12 +81,15 @@ const ItemTable: React.FC = () => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(`${API_BASE}/api/allItems`, {
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
});
const response = await fetch(
`${API_BASE}/api/admin/item-data/all-items`,
{
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
}
);
const data = await response.json();
return data;
} catch (error) {
@@ -193,6 +195,15 @@ const ItemTable: React.FC = () => {
<Table.ColumnHeader>
<strong>Eintrag erstellt am</strong>
</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>
<strong>Aktionen</strong>
</Table.ColumnHeader>
@@ -229,31 +240,34 @@ const ItemTable: React.FC = () => {
py={1}
gap={2}
variant="ghost"
color={item.inSafe ? "green.600" : "red.600"}
color={item.in_safe ? "green.600" : "red.600"}
borderWidth="1px"
borderColor={item.inSafe ? "green.300" : "red.300"}
borderColor={item.in_safe ? "green.300" : "red.300"}
_hover={{
bg: item.inSafe ? "green.50" : "red.50",
borderColor: item.inSafe ? "green.400" : "red.400",
bg: item.in_safe ? "green.50" : "red.50",
borderColor: item.in_safe ? "green.400" : "red.400",
transform: "translateY(-1px)",
shadow: "sm",
}}
_active={{ transform: "translateY(0)" }}
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
as={item.inSafe ? CheckCircle2 : XCircle}
as={item.in_safe ? CheckCircle2 : XCircle}
boxSize={3.5}
mr={2}
/>
<Text as="span" fontSize="xs" fontWeight="semibold">
{item.inSafe ? "Yes" : "No"}
{item.in_safe ? "Yes" : "No"}
</Text>
</Button>
</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>
<Button
onClick={() =>

View File

@@ -17,11 +17,7 @@ import MyAlert from "./myChakra/MyAlert";
import { formatDateTime } from "@/utils/userFuncs";
import { Trash2, RefreshCcwDot } from "lucide-react";
import { deleteLoan } from "@/utils/userActions";
const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8002";
import { API_BASE } from "@/config/api.config";
const LoanTable: React.FC = () => {
const [items, setItems] = useState<Loan[]>([]);
@@ -55,18 +51,22 @@ const LoanTable: React.FC = () => {
created_at: string;
loaned_items_name: string[];
deleted: boolean;
note: string;
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(`${API_BASE}/api/allLoans`, {
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
});
const response = await fetch(
`${API_BASE}/api/admin/loan-data/all-loans`,
{
method: "GET",
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},
}
);
const data = await response.json();
return data;
} catch (error) {
@@ -161,6 +161,9 @@ const LoanTable: React.FC = () => {
<Table.ColumnHeader>
<strong>Ausgeliehene Artikel</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Notiz</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Aktionen</strong>
</Table.ColumnHeader>
@@ -180,6 +183,7 @@ const LoanTable: React.FC = () => {
<Table.Cell>{formatDateTime(item.returned_date)}</Table.Cell>
<Table.Cell>{formatDateTime(item.created_at)}</Table.Cell>
<Table.Cell>{item.loaned_items_name.join(", ")}</Table.Cell>
<Table.Cell>{item.note}</Table.Cell>
<Table.Cell>
<Button
onClick={() =>

View File

@@ -10,6 +10,7 @@ import {
HStack,
IconButton,
Heading,
Switch, // neu
} from "@chakra-ui/react";
import { Tooltip } from "@/components/ui/tooltip";
import { fetchUserData } from "@/utils/fetcher";
@@ -23,9 +24,13 @@ import ChangePWform from "./ChangePWform";
type User = {
id: number;
username: string;
password: string;
role: string;
first_name: string;
last_name: string;
email: string;
is_admin: boolean;
role: number;
entry_created_at: string;
entry_updated_at: string;
};
const UserTable: React.FC = () => {
@@ -52,10 +57,20 @@ const UserTable: React.FC = () => {
setIsError(true);
};
const handleInputChange = (userId: number, field: string, value: string) => {
const handleInputChange = (userId: number, field: string, value: any) => {
setUsers((prevUsers) =>
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);
try {
const data = await fetchUserData();
console.log("user api response", data);
console.log(data);
if (Array.isArray(data)) {
setUsers(data);
} else {
@@ -189,6 +204,18 @@ const UserTable: React.FC = () => {
<Table.ColumnHeader>
<strong>Benutzername</strong>
</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>
<strong>Passwort ändern</strong>
</Table.ColumnHeader>
@@ -198,6 +225,9 @@ const UserTable: React.FC = () => {
<Table.ColumnHeader>
<strong>Eintrag erstellt am</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Eintrag aktualisiert am</strong>
</Table.ColumnHeader>
<Table.ColumnHeader>
<strong>Aktionen</strong>
</Table.ColumnHeader>
@@ -207,14 +237,58 @@ const UserTable: React.FC = () => {
{users.map((user) => (
<Table.Row key={user.id}>
<Table.Cell>{user.id}</Table.Cell>
<Table.Cell>{user.username}</Table.Cell>
{/* Vorname */}
<Table.Cell>
<Input
size="sm"
value={user.first_name ?? ""}
onChange={(e) =>
handleInputChange(user.id, "username", e.target.value)
handleInputChange(user.id, "first_name", e.target.value)
}
value={user.username}
/>
</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>
<Button onClick={() => handlePasswordChange(user.username)}>
Passwort ändern
@@ -230,13 +304,17 @@ const UserTable: React.FC = () => {
/>
</Table.Cell>
<Table.Cell>{formatDateTime(user.entry_created_at)}</Table.Cell>
<Table.Cell>{formatDateTime(user.entry_updated_at)}</Table.Cell>
<Table.Cell>
<Button
onClick={() =>
handleEdit(
user.id,
user.username,
user.role,
user.first_name,
user.last_name,
user.email,
user.is_admin,
Number(user.role)
).then((response) => {
if (response.success) {
setError(

View File

@@ -1,12 +1,8 @@
import Cookies from "js-cookie";
const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8002";
import { API_BASE } from "@/config/api.config";
export const fetchUserData = async () => {
const response = await fetch(`${API_BASE}/api/allUsers`, {
const response = await fetch(`${API_BASE}/api/admin/user-data/users`, {
headers: {
Authorization: `Bearer ${Cookies.get("token")}`,
},

View File

@@ -1,9 +1,5 @@
import Cookies from "js-cookie";
const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8002";
import { API_BASE } from "@/config/api.config";
export type LoginSuccess = { success: true };
export type LoginFailure = {
@@ -18,12 +14,20 @@ export const loginFunc = async (
password: string
): Promise<LoginResult> => {
try {
const response = await fetch(`${API_BASE}/api/loginAdmin`, {
const response = await fetch(`${API_BASE}/api/admin/user-mgmt/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
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) {
return {
success: false,
@@ -39,6 +43,7 @@ export const loginFunc = async (
return { success: true };
} catch (error) {
console.error("Error logging in:", error);
return {
success: false,
message: "Login failed!",

View File

@@ -1,14 +1,10 @@
import Cookies from "js-cookie";
const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8002";
import { API_BASE } from "@/config/api.config";
export const handleDelete = async (userId: number) => {
try {
const response = await fetch(
`${API_BASE}/api/deleteUser/${userId}`,
`${API_BASE}/api/admin/user-data/delete-user/${userId}`,
{
method: "DELETE",
headers: {
@@ -28,19 +24,28 @@ export const handleDelete = async (userId: number) => {
export const handleEdit = async (
userId: number,
username: string,
role: string
first_name: string,
last_name: string,
email: string,
is_admin: boolean,
role: number
) => {
try {
const response = await fetch(
`${API_BASE}/api/editUser/${userId}`,
`${API_BASE}/api/admin/user-data/edit-user/${userId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ username, role }),
body: JSON.stringify({
first_name,
last_name,
role,
email,
is_admin,
}),
}
);
if (!response.ok) {
@@ -56,17 +61,32 @@ export const handleEdit = async (
export const createUser = async (
username: string,
role: number,
password: string
password: string,
first_name: string,
last_name: string,
email: string,
isAdmin: boolean
) => {
try {
const response = await fetch(`${API_BASE}/api/createUser`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ username, role, password }),
});
const response = await fetch(
`${API_BASE}/api/admin/user-data/create-user`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({
username,
role,
password,
isAdmin,
email,
first_name,
last_name,
}),
}
);
if (!response.ok) {
throw new Error("Failed to create user");
}
@@ -79,14 +99,17 @@ export const createUser = async (
export const changePW = async (newPassword: string, username: string) => {
try {
const response = await fetch(`${API_BASE}/api/changePWadmin`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ newPassword, username }),
});
const response = await fetch(
`${API_BASE}/api/admin/user-data/change-password`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ username, password: newPassword }),
}
);
if (!response.ok) {
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) => {
try {
const response = await fetch(
`${API_BASE}/api/deleteLoan/${loanId}`,
`${API_BASE}/api/admin/loan-data/delete-loan/${loanId}`,
{
method: "DELETE",
headers: {
@@ -121,7 +144,7 @@ export const deleteLoan = async (loanId: number) => {
export const deleteItem = async (itemId: number) => {
try {
const response = await fetch(
`${API_BASE}/api/deleteItem/${itemId}`,
`${API_BASE}/api/admin/item-data/delete-item/${itemId}`,
{
method: "DELETE",
headers: {
@@ -144,14 +167,17 @@ export const createItem = async (
can_borrow_role: number
) => {
try {
const response = await fetch(`${API_BASE}/api/createItem`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ item_name, can_borrow_role }),
});
const response = await fetch(
`${API_BASE}/api/admin/item-data/create-item`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ item_name, can_borrow_role }),
}
);
if (!response.ok) {
return {
success: false,
@@ -172,14 +198,17 @@ export const handleEditItems = async (
can_borrow_role: string
) => {
try {
const response = await fetch(`${API_BASE}/api/updateItemByID`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ itemId, item_name, can_borrow_role }),
});
const response = await fetch(
`${API_BASE}/api/admin/item-data/edit-item/${itemId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ item_name, can_borrow_role }),
}
);
if (!response.ok) {
throw new Error("Failed to edit item");
}
@@ -193,9 +222,9 @@ export const handleEditItems = async (
export const changeSafeState = async (itemId: number) => {
try {
const response = await fetch(
`${API_BASE}/api/changeSafeState/${itemId}`,
`${API_BASE}/api/admin/item-data/change-safe-state/${itemId}`,
{
method: "PUT",
method: "POST",
headers: {
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 {
const response = await fetch(`${API_BASE}/api/createAPIentry`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ apiKey, user }),
});
const response = await fetch(
`${API_BASE}/api/admin/api-data/create-api-key`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
body: JSON.stringify({ apiKey, entryName: name }),
}
);
if (!response.ok) {
return {
success: false,
@@ -238,7 +270,7 @@ export const createAPIentry = async (apiKey: string, user: string) => {
export const deleteAPKey = async (apiKeyId: number) => {
try {
const response = await fetch(
`${API_BASE}/api/deleteAPKey/${apiKeyId}`,
`${API_BASE}/api/admin/api-data/delete-api-key/${apiKeyId}`,
{
method: "DELETE",
headers: {

View File

@@ -8,21 +8,21 @@ dotenv.config();
import {
getAllApiKeys,
createAPIentry,
deleteAPIKey,
deleteAPKey,
} from "./database/apiDataMgmt.database.js";
router.get("/get-api-keys", authenticateAdmin, async (req, res) => {
const result = await getAllApiKeys();
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" });
});
router.post("/create-api-key", authenticateAdmin, async (req, res) => {
const apiKey = req.body.apiKey;
const username = req.body.username;
const result = await createAPIentry(apiKey, username);
const entryName = req.body.entryName;
const result = await createAPIentry(apiKey, entryName);
if (result.success) {
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) => {
const apiKeyId = req.params.id;
const result = await deleteAPIKey(apiKeyId);
const result = await deleteAPKey(apiKeyId);
if (result.success) {
return res.status(200).json({ message: "API key deleted successfully" });
}

View File

@@ -19,10 +19,10 @@ export const getAllApiKeys = async () => {
return { success: false };
};
export const createAPIentry = async (apiKey, user) => {
export const createAPIentry = async (apiKey, entryName) => {
const [result] = await pool.query(
"INSERT INTO apiKeys (api_key, username) VALUES (?, ?)",
[apiKey, user]
"INSERT INTO apiKeys (api_key, entry_name) VALUES (?, ?)",
[apiKey, entryName]
);
if (result.affectedRows > 0) return { success: true };
return { success: false };

View File

@@ -26,7 +26,7 @@ export const deleteItemById = async (itemId) => {
export const createItem = async (item_name, can_borrow_role, in_safe) => {
const [result] = await pool.query(
"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 };
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) => {
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]
);
if (result.affectedRows > 0) return { success: true };
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 };
};

View File

@@ -61,7 +61,7 @@ export const editUserById = async (
export const getAllUsers = async () => {
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 };
return { success: false };

View File

@@ -16,7 +16,7 @@ import {
router.get("/all-items", authenticateAdmin, async (req, res) => {
const result = await getAllItems();
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" });
});
@@ -31,8 +31,8 @@ router.delete("/delete-item/:id", authenticateAdmin, async (req, res) => {
});
router.post("/create-item", authenticateAdmin, async (req, res) => {
const { item_name, can_borrow_role, in_safe } = req.body;
const result = await createItem(item_name, can_borrow_role, in_safe);
const { item_name, can_borrow_role } = req.body;
const result = await createItem(item_name, can_borrow_role);
if (result.success) {
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) => {
const itemId = req.params.id;
const { in_safe } = req.body;
const result = await changeSafeState(itemId, in_safe);
const result = await changeSafeState(itemId);
if (result.success) {
return res.status(200).json({ message: "Safe state changed successfully" });
}

View File

@@ -13,7 +13,7 @@ import {
router.get("/all-loans", authenticateAdmin, async (req, res) => {
const result = await getAllLoans();
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" });
});

View File

@@ -47,7 +47,6 @@ router.delete("/delete-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 last_name = req.body.last_name;
const role = req.body.role;
@@ -57,7 +56,6 @@ router.post("/edit-user/:id", authenticateAdmin, async (req, res) => {
const result = await editUserById(
userId,
password,
first_name,
last_name,
role,
@@ -109,7 +107,7 @@ router.post("/edit-user/:id", authenticateAdmin, async (req, res) => {
router.get("/users", authenticateAdmin, async (req, res) => {
const result = await getAllUsers();
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" });
});

View File

@@ -1,7 +1,9 @@
import express from "express";
import { authenticate, generateToken } from "../../services/authentication.js";
import {
generateToken,
authenticateAdmin,
} from "../../services/authentication.js";
const router = express.Router();
import nodemailer from "nodemailer";
import dotenv from "dotenv";
dotenv.config();
@@ -9,7 +11,12 @@ dotenv.config();
import { loginAdmin } from "./database/userMgmt.database.js";
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) {
const token = await generateToken({
@@ -18,7 +25,11 @@ router.post("/login", async (req, res) => {
last_name: result.data.last_name,
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") {
@@ -27,3 +38,9 @@ router.post("/login", async (req, res) => {
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;

View File

@@ -1,3 +1,5 @@
import express from "express";
const router = express.Router();
export default router;

View File

@@ -1,5 +1,5 @@
import express from "express";
import { authenticate, generateToken } from "../services/tokenService.js";
import { authenticate, generateToken } from "../../services/authentication.js";
const router = express.Router();
import nodemailer from "nodemailer";
import dotenv from "dotenv";
@@ -21,3 +21,4 @@ router.post("/login", async (req, res) => {
}
});
export default router;

View File

@@ -1,91 +1,39 @@
-- MUST BE UPDATED BEFORE USE
-- Mock data for borrow_system_new
USE borrow_system_new;
-- Optional: keep insert order predictable
SET time_zone = '+00:00';
START TRANSACTION;
-- 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
('alice', 'password123', 'Alice', 'Andersen', 1, false),
('bob', 'password123', 'Bob', 'Berg', 2, false),
('carol', 'password123', 'Carol', 'Christie', 2, false),
('dave', 'password123', 'Dave', 'Dawson', 1, false),
('eve', 'password123', 'Eve', 'Evans', 1, false),
('admin', 'password123', 'Admin', 'User', 3, true);
('admin', '$2b$12$adminhashedpasswordplaceholder0000000000', 'admin@example.com', 'System', 'Admin', 99, TRUE, '2025-01-01 08:00:00'),
('alice', '$2b$12$alicehashedpasswordplaceholder0000000000', 'alice@example.com', 'Alice', 'Anderson', 1, FALSE, '2025-06-01 09:10:00'),
('bob', '$2b$12$bobhashedpasswordplaceholder000000000000', 'bob@example.com', 'Bob', 'Brown', 2, FALSE, '2025-06-02 10:15:00'),
('carol', '$2b$12$carolhashedpasswordplaceholder00000000000', 'carol@example.com', 'Carol', 'Clark', 0, FALSE, '2025-06-03 11:20:00');
-- Items
INSERT INTO items (item_name, can_borrow_role, in_safe, last_borrowed_person, currently_borrowing)
-- Items (ids will start at 1)
INSERT INTO items (item_name, can_borrow_role, in_safe, entry_created_at, last_borrowed_person, currently_borrowing)
VALUES
('Canon EOS 90D Camera', 1, false, 'bob', 'alice'),
('Rode NT1 Microphone', 1, true, 'dave', NULL),
('MacBook Pro 13', 2, false, 'bob', 'carol'),
('Tripod Manfrotto', 1, false, 'carol', 'alice'),
('LED Panel Aputure', 1, true, NULL, NULL),
('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');
('MacBook Pro 16\"', 1, TRUE, '2025-05-01 09:00:00', 'alice', NULL),
('Projector Epson X200', 2, TRUE, '2025-04-20 10:00:00', 'bob', NULL),
('Canon EOS R6', 1, TRUE, '2025-03-15 14:30:00', NULL, NULL),
('Wireless Microphone', 0, TRUE,'2025-05-10 12:00:00', 'carol', NULL),
('USB-C Charger', 0, FALSE, '2025-05-11 12:30:00', 'alice', 'alice');
-- Loans
INSERT INTO loans (
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)
INSERT INTO loans (username, loan_code, start_date, end_date, take_date, returned_date, created_at, loaned_items_id, loaned_items_name, deleted, note)
VALUES
(71002123, 'alice'),
(71002124, 'bob'),
(71002125, 'carol'),
(99999999, 'admin');
('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',
JSON_ARRAY(1,5), JSON_ARRAY('MacBook Pro 16\"','USB-C Charger'), FALSE, 'For project work'),
('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',
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;

View File

@@ -17,7 +17,7 @@ CREATE TABLE users (
CREATE TABLE loans (
id int NOT NULL AUTO_INCREMENT,
username varchar(100) NOT NULL,
loan_code int NOT NULL UNIQUE,
loan_code Char(6) NOT NULL UNIQUE,
start_date timestamp NOT NULL,
end_date timestamp NOT NULL,
take_date timestamp NULL DEFAULT NULL,
@@ -28,10 +28,7 @@ CREATE TABLE loans (
deleted bool NOT NULL DEFAULT false,
note varchar(500) DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_loans_username
FOREIGN KEY (username) REFERENCES users(username)
ON UPDATE CASCADE
ON DELETE RESTRICT
CHECK (loan_code REGEXP '^[0-9]{6}$')
) ENGINE=InnoDB;
CREATE TABLE items (
@@ -47,15 +44,11 @@ CREATE TABLE items (
);
CREATE TABLE apiKeys (
id int NOT NULL AUTO_INCREMENT,
id INT NOT NULL AUTO_INCREMENT,
api_key CHAR(15) NOT NULL UNIQUE,
username VARCHAR(100) NOT NULL,
last_used_at timestamp DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
entry_created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
entry_name VARCHAR(100) NOT NULL,
last_used_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT chk_api_key_len CHECK (CHAR_LENGTH(api_key) = 15),
CONSTRAINT fk_apikeys_username
FOREIGN KEY (username) REFERENCES users(username)
ON UPDATE CASCADE
ON DELETE RESTRICT
CHECK (api_key REGEXP '^[0-9]{15}$')
) ENGINE=InnoDB;

View File

@@ -1,43 +1,51 @@
import express from "express";
import cors from "cors";
import env from "dotenv";
import info from "./info.json" assert { type: "json" };
// frontend routes
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
import userDataMgmtRouter from "./routes/admin/userDataMgmt.route.js";
import loanDataMgmtRouter from "./routes/admin/loanDataMgmt.route.js";
import itemDataMgmtRouter from "./routes/admin/itemDataMgmt.route.js";
import apiDataMgmtRouter from "./routes/admin/apiDataMgmt.route.js";
import userMgmtRouterADMIN from "./routes/admin/userMgmt.route.js";
env.config();
const app = express();
const port = 8002;
const port = 8004;
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
app.use("/api/loans", loansMgmtRouter);
app.use("/api/users", userMgmtRouter);
app.use("/api/users", userMgmtRouterAPP);
// admin routes
app.use("/api/admin/loan-data", loanDataMgmtRouter);
app.use("/api/admin/user-data", userDataMgmtRouter);
app.use("/api/admin/item-data", itemDataMgmtRouter);
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.use(express.json({ limit: "10mb" }));
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
app.get("/", (req, res) => {
res.send(info);
});
// error handling code
app.use((err, req, res, next) => {
// Log the error stack and send a generic error response
console.error(err.stack);
res.status(500).send("Something broke!");
});

View File

@@ -1,6 +1,6 @@
import { SignJWT, jwtVerify } from "jose";
import env from "dotenv";
import { getAllApiKeys } from "./database";
import { getAllApiKeys } from "./database.js";
env.config();
const secretKey = process.env.SECRET_KEY;

View File

@@ -43,7 +43,7 @@ services:
DB_HOST: mysql_v2
DB_USER: root
DB_PASSWORD: ${DB_PASSWORD_V2}
DB_NAME: borrow_system_v2
DB_NAME: borrow_system_new
depends_on:
- mysql_v2
restart: unless-stopped
@@ -68,7 +68,7 @@ services:
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD_V2}
MYSQL_DATABASE: borrow_system_v2
MYSQL_DATABASE: borrow_system_new
TZ: Europe/Berlin
volumes:
- mysql-v2-data:/var/lib/mysql