Compare commits

..

8 Commits

19 changed files with 812 additions and 563 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "no-as-a-service"]
path = no-as-a-service
url = https://github.com/hotheadhacker/no-as-a-service.git
+325 -257
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@chakra-ui/react": "^3.28.0", "@chakra-ui/react": "^3.28.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@lottiefiles/dotlottie-react": "^0.19.0",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.90.5", "@tanstack/react-query": "^5.90.5",
"i18next": "^25.6.0", "i18next": "^25.6.0",
+8
View File
@@ -1,6 +1,7 @@
import { import {
Button, Button,
Flex, Flex,
Image,
Heading, Heading,
Stack, Stack,
Text, Text,
@@ -190,6 +191,13 @@ export const Header = () => {
<Stack gap={1}> <Stack gap={1}>
{/* Titelzeile ohne Mobile-Menu (wurde nach oben verlegt) */} {/* Titelzeile ohne Mobile-Menu (wurde nach oben verlegt) */}
<Flex align="center" justify="space-between" gap={2}> <Flex align="center" justify="space-between" gap={2}>
<Image
src="/icon_borrow-system-frontend_dark.png"
alt="borrow-system logo"
boxSize="10"
objectFit="contain"
flexShrink={0}
/>
<Heading <Heading
size="2xl" size="2xl"
className="tracking-tight text-slate-900 dark:text-slate-100" className="tracking-tight text-slate-900 dark:text-slate-100"
+76 -3
View File
@@ -36,12 +36,43 @@ export const UserDialogue = (props: UserDialogueProps) => {
const [msgTitle, setMsgTitle] = useState(""); const [msgTitle, setMsgTitle] = useState("");
const [msgDescription, setMsgDescription] = useState(""); const [msgDescription, setMsgDescription] = useState("");
const [isMsgNAAS, setIsMsgNAAS] = useState(false);
const [msgStatusNAAS, setMsgStatusNAAS] = useState<"error" | "success">(
"error",
);
const [msgTitleNAAS, setMsgTitleNAAS] = useState("");
const [msgDescriptionNAAS, setMsgDescriptionNAAS] = useState("");
const [oldPassword, setOldPassword] = useState(""); const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState(""); const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
// Dialog control // Dialog control
const [isPwOpen, setPwOpen] = useState(false); const [isPwOpen, setPwOpen] = useState(false);
const [naasDialog, setNaasDialog] = useState(false);
const [naas, setNaas] = useState("");
const openNAAS = async () => {
try {
const response = await fetch(`${API_BASE}/no`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Cookies.get("token")}`,
},
});
const data = await response.json();
setNaas(data.reason);
setNaasDialog(true);
} catch (error) {
setMsgStatusNAAS("error");
setMsgTitleNAAS(t("naas-error"));
setMsgDescriptionNAAS(t("naas-error-desc"));
setIsMsgNAAS(true);
console.log(msgStatusNAAS, msgTitleNAAS, msgDescriptionNAAS);
}
};
const changePassword = async () => { const changePassword = async () => {
if (newPassword !== confirmPassword) { if (newPassword !== confirmPassword) {
@@ -147,14 +178,31 @@ export const UserDialogue = (props: UserDialogueProps) => {
</Button> </Button>
</Stack> </Stack>
</Card.Body> </Card.Body>
<Card.Footer justifyContent="flex-end"> <Card.Footer>
<Button variant="outline" onClick={() => props.setUserDialog(false)}> <Stack w="100%" gap={3}>
{isMsgNAAS && (
<MyAlert
status={msgStatusNAAS}
title={msgTitleNAAS}
description={msgDescriptionNAAS}
/>
)}
<HStack justify="flex-end" gap={2} wrap="wrap">
<Button
variant="outline"
onClick={() => props.setUserDialog(false)}
>
{t("cancel")} {t("cancel")}
</Button> </Button>
<Button variant="outline" onClick={() => openNAAS()}>
{t("try-naas")}
</Button>
</HStack>
</Stack>
</Card.Footer> </Card.Footer>
</Card.Root> </Card.Root>
{/* Passwort-Dialog (kontrolliert) */} {/* Passwort-Dialog */}
<Dialog.Root open={isPwOpen} onOpenChange={(e: any) => setPwOpen(e.open)}> <Dialog.Root open={isPwOpen} onOpenChange={(e: any) => setPwOpen(e.open)}>
<Portal> <Portal>
<Dialog.Backdrop /> <Dialog.Backdrop />
@@ -215,6 +263,31 @@ export const UserDialogue = (props: UserDialogueProps) => {
</Dialog.Positioner> </Dialog.Positioner>
</Portal> </Portal>
</Dialog.Root> </Dialog.Root>
<HStack wrap="wrap" gap="4">
<Dialog.Root
placement={"center"}
open={naasDialog}
motionPreset="slide-in-bottom"
>
<Portal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>{t("naas-header")}</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<p>{naas}</p>
</Dialog.Body>
<Dialog.CloseTrigger asChild>
<CloseButton onClick={() => setNaasDialog(false)} size="sm" />
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
</HStack>
</Flex> </Flex>
); );
}; };
+28
View File
@@ -0,0 +1,28 @@
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
export const unlockAnimation = () => {
return (
<DotLottieReact
src="https://lottie.host/f839baa1-9c64-44c4-9386-f0e4c87ab208/2Iw1m4k86d.lottie"
autoplay
/>
);
};
export const approvalAnimation = () => {
return (
<DotLottieReact
src="https://lottie.host/b7257009-9e3f-43e2-8112-a176f4696e4c/iQxxqAVOGX.lottie"
autoplay
/>
);
};
export const logoutAnimation = () => {
return (
<DotLottieReact
src="https://lottie.host/4975758c-de38-4d15-9f74-927709751d32/v8FtKpnD1y.lottie"
autoplay
/>
);
};
+24 -4
View File
@@ -18,6 +18,7 @@ import { borrowAbleItemsAtom } from "@/states/Atoms";
import { createLoan } from "@/utils/Fetcher"; import { createLoan } from "@/utils/Fetcher";
import { Header } from "@/components/Header"; import { Header } from "@/components/Header";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { approvalAnimation } from "@/components/dotLottie";
export interface User { export interface User {
username: string; username: string;
@@ -27,6 +28,8 @@ export interface User {
export const HomePage = () => { export const HomePage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [showAnimation, setShowAnimation] = useState(false);
const [borrowableItems, setBorrowableItems] = useAtom(borrowAbleItemsAtom); const [borrowableItems, setBorrowableItems] = useAtom(borrowAbleItemsAtom);
const [startDate, setStartDate] = useState(""); const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState(""); const [endDate, setEndDate] = useState("");
@@ -46,11 +49,26 @@ export const HomePage = () => {
setSelectedItems((prevSelected) => setSelectedItems((prevSelected) =>
prevSelected.includes(itemId) prevSelected.includes(itemId)
? prevSelected.filter((id) => id !== itemId) ? prevSelected.filter((id) => id !== itemId)
: [...prevSelected, itemId] : [...prevSelected, itemId],
); );
}; };
const showApprovalAnimation = (seconds: number) => {
const milliseconds = seconds * 1000;
setShowAnimation(true);
window.setTimeout(() => {
setShowAnimation(false);
}, milliseconds);
};
return ( return (
<>
{showAnimation && (
<div className="fixed inset-0 z-9999 flex items-center justify-center pointer-events-none">
<div>{approvalAnimation()}</div>
</div>
)}
<Container className="px-6 sm:px-8 pt-10"> <Container className="px-6 sm:px-8 pt-10">
<Header /> <Header />
{isMsg && ( {isMsg && (
@@ -158,7 +176,7 @@ export const HomePage = () => {
maxLength={MAX_CHARACTERS} maxLength={MAX_CHARACTERS}
onChange={(e) => { onChange={(e) => {
setNote( setNote(
e.currentTarget.value.slice(0, MAX_CHARACTERS) e.currentTarget.value.slice(0, MAX_CHARACTERS),
); );
}} }}
/> />
@@ -178,16 +196,17 @@ export const HomePage = () => {
setMsgStatus("error"); setMsgStatus("error");
setMsgTitle(response.title || t("error")); setMsgTitle(response.title || t("error"));
setMsgDescription( setMsgDescription(
response.description || t("unknown-error") response.description || t("unknown-error"),
); );
setIsMsg(true); setIsMsg(true);
return; return;
} }
showApprovalAnimation(3);
setMsgStatus("success"); setMsgStatus("success");
setMsgTitle(t("success")); setMsgTitle(t("success"));
setMsgDescription(t("loan-success")); setMsgDescription(t("loan-success"));
setIsMsg(true); setIsMsg(true);
} },
) )
} }
> >
@@ -196,5 +215,6 @@ export const HomePage = () => {
)} )}
</Stack> </Stack>
</Container> </Container>
</>
); );
}; };
+52 -12
View File
@@ -4,26 +4,47 @@ import { Button, Card, Field, Input, Stack } from "@chakra-ui/react";
import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms"; import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { Navigate, useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import { PasswordInput } from "@/components/ui/password-input"; import { PasswordInput } from "@/components/ui/password-input";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { API_BASE } from "@/config/api.config"; import { API_BASE } from "@/config/api.config";
import { unlockAnimation } from "@/components/dotLottie";
import { logoutAnimation } from "@/components/dotLottie";
export const LoginPage = () => { export const LoginPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isLoggedIn, setIsLoggedIn] = useAtom(setIsLoggedInAtom); const [isLoggedIn, setIsLoggedIn] = useAtom(setIsLoggedInAtom);
const [triggerLogout, setTriggerLogout] = useAtom(triggerLogoutAtom); const [triggerLogout, setTriggerLogout] = useAtom(triggerLogoutAtom);
const [showAnimation, setShowAnimation] = useState(false);
const [showLogout, setShowLogout] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const from = location.state?.from?.pathname || "/"; const from = location.state?.from?.pathname || "/";
useEffect(() => { useEffect(() => {
if (isLoggedIn) { if (triggerLogout) {
navigate(from, { replace: true }); setShowLogout(true);
window.location.reload(); // if deleted, the user context is not updated in time window.setTimeout(() => {
setShowLogout(false);
}, 4500);
} }
}, [isLoggedIn, navigate, from]);
if (!isLoggedIn) return;
// Existing sessions should redirect immediately, fresh logins wait for animation.
if (!showAnimation) {
navigate(from, { replace: true });
return;
}
const timeoutId = window.setTimeout(() => {
navigate(from, { replace: true });
window.location.reload(); // keeps user context in sync after login
}, 3000);
return () => window.clearTimeout(timeoutId);
}, [isLoggedIn, showAnimation, navigate, from]);
const loginFnc = async (username: string, password: string) => { const loginFnc = async (username: string, password: string) => {
const response = await fetch(`${API_BASE}/api/users/login`, { const response = await fetch(`${API_BASE}/api/users/login`, {
@@ -42,6 +63,8 @@ export const LoginPage = () => {
}; };
} }
setShowAnimation(true);
Cookies.set("token", data.token); Cookies.set("token", data.token);
setIsLoggedIn(true); setIsLoggedIn(true);
return { success: true }; return { success: true };
@@ -62,14 +85,22 @@ export const LoginPage = () => {
return; return;
} }
setTriggerLogout(false); setTriggerLogout(false);
navigate(from, { replace: true });
}; };
if (isLoggedIn) {
return <Navigate to={from} replace />;
}
return ( return (
<>
{showAnimation && (
<div className="fixed inset-0 z-9999 flex items-center justify-center pointer-events-none">
<div>{unlockAnimation()}</div>
</div>
)}
{showLogout && (
<div className="fixed inset-0 z-9999 flex items-center justify-center pointer-events-none">
<div>{logoutAnimation()}</div>
</div>
)}
<div className="flex flex-1 items-center justify-center p-4"> <div className="flex flex-1 items-center justify-center p-4">
<form onSubmit={(e) => e.preventDefault()}> <form onSubmit={(e) => e.preventDefault()}>
<Card.Root maxW="sm"> <Card.Root maxW="sm">
@@ -97,9 +128,17 @@ export const LoginPage = () => {
</Card.Body> </Card.Body>
<Card.Footer justifyContent="flex-end"> <Card.Footer justifyContent="flex-end">
{isError && ( {isError && (
<MyAlert status="error" title={errorMsg} description={errorDsc} /> <MyAlert
status="error"
title={errorMsg}
description={errorDsc}
/>
)} )}
<Button type="submit" onClick={() => handleLogin()} variant="solid"> <Button
type="submit"
onClick={() => handleLogin()}
variant="solid"
>
Login Login
</Button> </Button>
</Card.Footer> </Card.Footer>
@@ -115,5 +154,6 @@ export const LoginPage = () => {
</Card.Root> </Card.Root>
</form> </form>
</div> </div>
</>
); );
}; };
+8
View File
@@ -84,6 +84,14 @@ export const MyLoansPage = () => {
}); });
if (!res.ok) { if (!res.ok) {
if (res.status === 507) {
setMsgStatus("error");
setMsgTitle(t("error"));
setMsgDescription(t("error-deleting-loan-507"));
setIsMsg(true);
return;
}
setMsgStatus("error"); setMsgStatus("error");
setMsgTitle(t("error")); setMsgTitle(t("error"));
setMsgDescription(t("error-deleting-loan")); setMsgDescription(t("error-deleting-loan"));
+7 -1
View File
@@ -88,5 +88,11 @@
"take-loan-success": "Ausleihe erfolgreich abgeholt", "take-loan-success": "Ausleihe erfolgreich abgeholt",
"return-loan-success": "Ausleihe erfolgreich zurückgegeben", "return-loan-success": "Ausleihe erfolgreich zurückgegeben",
"network-error": "Netzwerkfehler. Kontaktieren Sie den Administrator.", "network-error": "Netzwerkfehler. Kontaktieren Sie den Administrator.",
"contactPage_messageDescription": "Bitte geben Sie hier Ihre Nachricht ein. Der Systemadministrator (Theis Gaedigk) wird sich so schnell wie möglich bei Ihnen melden." "contactPage_messageDescription": "Bitte geben Sie hier Ihre Nachricht ein. Der Systemadministrator (Theis Gaedigk) wird sich so schnell wie möglich bei Ihnen melden.",
"naas": "No-as-a-service",
"try-naas": "Klick mich",
"naas-error": "Fehler mit no-as-a-service",
"naas-error-desc": "Ein Fehler ist beim Kommunizieren mit no-as-a-service aufgetreten.",
"naas-header": "Eine gute Möglichkeit, nein zu sagen...",
"error-deleting-loan-507": "Die Ausleihe kann nicht gelöscht werden, da sie noch nicht zurückgegeben wurde."
} }
+7 -1
View File
@@ -88,5 +88,11 @@
"take-loan-success": "Loan taken successfully", "take-loan-success": "Loan taken successfully",
"return-loan-success": "Loan returned successfully", "return-loan-success": "Loan returned successfully",
"network-error": "Network error. Please contact the administrator.", "network-error": "Network error. Please contact the administrator.",
"contactPage_messageDescription": "Please enter your message here. The system administrator (Theis Gaedigk) will get back to you as soon as possible." "contactPage_messageDescription": "Please enter your message here. The system administrator (Theis Gaedigk) will get back to you as soon as possible.",
"naas": "No-as-a-service",
"try-naas": "Click me",
"naas-error": "Error with no-as-a-service",
"naas-error-desc": "An error occurred while communicating with no-as-a-service.",
"naas-header": "A good way to say no...",
"error-deleting-loan-507": "The loan cannot be deleted because it has not been returned yet."
} }
+3 -3
View File
@@ -1,11 +1,11 @@
{ {
"backend-info": { "backend-info": {
"version": "v2.1.1 (demo)" "version": "v2.2 (dev)"
}, },
"frontend-info": { "frontend-info": {
"version": "v2.1.2 (demo)" "version": "v2.2 (dev)"
}, },
"admin-panel-info": { "admin-panel-info": {
"version": "v1.3.2 (demo)" "version": "v1.3.2 (dev)"
} }
} }
@@ -234,6 +234,23 @@ export const getBorrowableItemsFromDatabase = async (
}; };
export const SETdeleteLoanFromDatabase = async (loanId) => { export const SETdeleteLoanFromDatabase = async (loanId) => {
const [checkIfdatesReturned] = await pool.query(
"SELECT take_date, returned_date FROM loans WHERE id = ? AND deleted = 0",
[loanId],
);
if (checkIfdatesReturned.length === 0) {
return { success: false, code: "LOAN_NOT_FOUND" };
}
const { take_date, returned_date } = checkIfdatesReturned[0];
const bothNull = take_date === null && returned_date === null;
const bothSet = take_date !== null && returned_date !== null;
if (!(bothNull || bothSet)) {
return { success: false, code: "LOAN_NOT_RETURNED" };
}
const [result] = await pool.query( const [result] = await pool.query(
"UPDATE loans SET deleted = 1 WHERE id = ?;", "UPDATE loans SET deleted = 1 WHERE id = ?;",
[loanId], [loanId],
+10
View File
@@ -134,6 +134,16 @@ router.delete("/delete-loan/:id", authenticate, async (req, res) => {
if (result.success) { if (result.success) {
res.status(200).json({ message: "Loan deleted successfully" }); res.status(200).json({ message: "Loan deleted successfully" });
} else { } else {
if (result.code === "LOAN_NOT_FOUND") {
res.status(404).json({ message: "Loan not found" });
}
if (result.code === "LOAN_NOT_RETURNED") {
res.status(507).json({
message: "Cannot delete loan that has not been returned",
});
}
res.status(500).json({ message: "Failed to delete loan" }); res.status(500).json({ message: "Failed to delete loan" });
} }
}); });
-100
View File
@@ -1,100 +0,0 @@
USE borrow_system_new;
-- USERS
INSERT INTO users (username, password, email, first_name, last_name, role, is_admin)
VALUES
('user1', 'passwordhash1', 'user1@example.com', 'First1', 'Last1', 1, false),
('user2', 'passwordhash2', 'user2@example.com', 'First2', 'Last2', 1, false),
('user3', 'passwordhash3', 'user3@example.com', 'First3', 'Last3', 2, false),
('admin1', 'passwordhash4', 'admin1@example.com', 'Admin', 'One', 9, true),
('admin2', 'passwordhash5', 'admin2@example.com', 'Admin', 'Two', 9, true);
-- ITEMS
INSERT INTO items (item_name, can_borrow_role, in_safe, safe_nr, door_key, last_borrowed_person, currently_borrowing)
VALUES
('Item1', 1, true, 1, 101, NULL, NULL),
('Item2', 1, true, 2, 102, 'user1', 'user1'),
('Item3', 2, true, 3, 103, 'user2', NULL),
('Item4', 1, false, NULL, NULL, NULL, NULL),
('Item5', 2, false, NULL, NULL, 'user3', 'user3');
-- LOANS
INSERT INTO loans (
username,
lockers,
loan_code,
start_date,
end_date,
take_date,
returned_date,
created_at,
loaned_items_id,
loaned_items_name,
deleted,
note
)
VALUES
(
'user1',
JSON_ARRAY('Locker1', 'Locker2'),
'123456',
'2026-02-01 09:00:00',
'2026-02-10 17:00:00',
'2026-02-01 09:15:00',
NULL,
'2026-02-01 09:00:00',
JSON_ARRAY(1, 2),
JSON_ARRAY('Item1', 'Item2'),
false,
'Erste allgemeine Ausleihe'
),
(
'user2',
JSON_ARRAY('Locker3'),
'234567',
'2026-02-02 10:00:00',
'2026-02-05 16:00:00',
'2026-02-02 10:05:00',
'2026-02-05 15:30:00',
'2026-02-02 10:00:00',
JSON_ARRAY(3),
JSON_ARRAY('Item3'),
false,
'Zurückgegeben vor Enddatum'
),
(
'user3',
JSON_ARRAY(),
'345678',
'2026-02-03 08:30:00',
'2026-02-15 18:00:00',
NULL,
NULL,
'2026-02-03 08:30:00',
JSON_ARRAY(5),
JSON_ARRAY('Item5'),
false,
'Noch ausgeliehen'
),
(
'user1',
JSON_ARRAY('Locker4'),
'456789',
'2025-12-01 09:00:00',
'2025-12-03 17:00:00',
'2025-12-01 09:10:00',
'2025-12-03 16:45:00',
'2025-12-01 09:00:00',
JSON_ARRAY(1),
JSON_ARRAY('Item1'),
true,
'Alte, gelöschte Ausleihe'
);
-- API KEYS
INSERT INTO apiKeys (api_key, entry_name)
VALUES
('10000001', 'Entry1'),
('10000002', 'Entry2'),
('10000003', 'Entry3'),
('10000004', 'Entry4');
+17 -2
View File
@@ -1,6 +1,6 @@
import express from "express"; import express from "express";
import cors from "cors"; import cors from "cors";
import env from "dotenv"; import dotenv from "dotenv";
import info from "./info.json" assert { type: "json" }; import info from "./info.json" assert { type: "json" };
import { authenticate } from "./services/authentication.js"; import { authenticate } from "./services/authentication.js";
@@ -18,9 +18,10 @@ import userMgmtRouterADMIN from "./routes/admin/userMgmt.route.js";
// API routes // API routes
import apiRouter from "./routes/api/api.route.js"; import apiRouter from "./routes/api/api.route.js";
env.config(); dotenv.config();
const app = express(); const app = express();
const port = 8004; const port = 8004;
const naasURL = process.env.NAAS_URL;
app.use(cors()); app.use(cors());
// Body-Parser VOR den Routen registrieren // Body-Parser VOR den Routen registrieren
@@ -47,6 +48,20 @@ app.listen(port, () => {
console.log(`Server is running on port: ${port}`); console.log(`Server is running on port: ${port}`);
}); });
app.get("/no", async (req, res) => {
try {
const response = await fetch(naasURL);
if (!response.ok) {
res.status(500).send("Request to no-as-a-service went wrong.");
}
const data = await response.json();
res.json(data);
} catch (error) {
console.error("Error communicating with no-as-a-service:", error);
res.status(500).send("Error communicating with no-as-a-service.");
}
});
app.get("/verify", authenticate, async (req, res) => { app.get("/verify", authenticate, async (req, res) => {
res.status(200).json({ message: "Token is valid", user: req.user }); res.status(200).json({ message: "Token is valid", user: req.user });
}); });
+36
View File
@@ -0,0 +1,36 @@
# Changelog for upcoming version: v2.2
This update provides some new features for the design. It also contains some improvements and I have also fixed some bugs.
## New features
- The overview page now has the note column and is overall better organised
- I also addded the regular header to the page
- I have added three animations to the Borrow System
- I have added a new icon for the frontend, which is now also used in the header and the favicon. It is a dark version of the old icon, which fits better to the overall design. I have made it with Icon Composer. The old icon is still used for the admin panel, which has a light design. (Maybe I will change the admin panel design in the future...)
- When you go to your user card (over the user icon in the header) you have a new button "Click me". If you click it, you will get an message... _I am just saying: I have implemented the no-as-a-service code in to my Backend._
## Improvements
- I have the error logging for the API route wehre you can take loans improved.
- If you try to delete a loan that has not been returned yet, you will get an 507 error code.
## Fixed bugs
- Fixed bug: #13
- Fixed bug for messaging when server has an error
- Fixed footer height
---
## New version numbers
**Backend:** v2.2
**Frontend:** v2.2
**Admin panel:** v1.3.2
---
-[Theis](https://portfolio-theis.de)
+13 -4
View File
@@ -4,14 +4,14 @@ services:
# build: ./FrontendV2 # build: ./FrontendV2
# ports: # ports:
# - "8001:80" # - "8001:80"
# restart: unless-stopped # restart: always
# admin-frontend: # admin-frontend:
# container_name: borrow_system-admin-frontend # container_name: borrow_system-admin-frontend
# build: ./admin # build: ./admin
# ports: # ports:
# - "8003:80" # - "8003:80"
# restart: unless-stopped # restart: always
backend_v2: backend_v2:
container_name: borrow_system-backend_v2 container_name: borrow_system-backend_v2
@@ -26,12 +26,12 @@ services:
DB_NAME: borrow_system_new DB_NAME: borrow_system_new
depends_on: depends_on:
- mysql_v2 - mysql_v2
restart: unless-stopped restart: always
mysql_v2: mysql_v2:
container_name: borrow_system-mysql-v2 container_name: borrow_system-mysql-v2
image: mysql:8.0 image: mysql:8.0
restart: unless-stopped restart: always
environment: environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD_V2} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD_V2}
MYSQL_DATABASE: borrow_system_new MYSQL_DATABASE: borrow_system_new
@@ -42,6 +42,15 @@ services:
ports: ports:
- "3310:3306" - "3310:3306"
no-as-a-service:
container_name: borrow_system-naas
ports:
- "3000:3000"
build:
context: ./no-as-a-service
dockerfile: Dockerfile
restart: always
volumes: volumes:
mysql-data: mysql-data:
mysql-v2-data: mysql-v2-data:
+1
Submodule no-as-a-service added at 764062a307