Merge remote-tracking branch 'origin/main'
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
package de.htwsaar.webshop.cronjob;
|
||||
|
||||
import de.htwsaar.webshop.service.SessionService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static de.htwsaar.webshop.util.TimeUtil.MILLIS_TO_WEEK;
|
||||
|
||||
/**
|
||||
* CronJob for deleting expired sessions in fixed intervals
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ExpiredSessionDeleteJob {
|
||||
|
||||
private final SessionService sessionService;
|
||||
|
||||
/**
|
||||
* Constructor of the class
|
||||
* @param sessionService used in the class
|
||||
*/
|
||||
public ExpiredSessionDeleteJob(SessionService sessionService) {
|
||||
this.sessionService = sessionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method running the job calling the referring service method
|
||||
*/
|
||||
@Scheduled(fixedRate = MILLIS_TO_WEEK)
|
||||
public void runFixedRateTask() {
|
||||
log.info("Deleting expired sessions...");
|
||||
sessionService.deleteExpired();
|
||||
log.info("Deleted expired sessions.");
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,6 @@ public interface SessionRepository extends JpaRepository<Session, Long> {
|
||||
Session findByAccount(Account account);
|
||||
|
||||
Session getSessionByToken(UUID token);
|
||||
|
||||
void deleteSessionsByTimeoutBefore(Long timeoutBefore);
|
||||
}
|
||||
|
||||
@@ -19,4 +19,6 @@ public interface SessionService {
|
||||
boolean isValid(UUID token, String email);
|
||||
|
||||
boolean isAdmin(UUID token, String email);
|
||||
|
||||
void deleteExpired();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class SessionServiceImpl implements SessionService {
|
||||
if (!session.getAccount().equals(accountEmail)) {
|
||||
return false;
|
||||
}
|
||||
if (session.getTimeout() >= System.currentTimeMillis()) {
|
||||
if (session.getTimeout() <= System.currentTimeMillis()) {
|
||||
log.info("Session with email {} is expired", email);
|
||||
delete(session);
|
||||
return false;
|
||||
@@ -92,4 +92,9 @@ public class SessionServiceImpl implements SessionService {
|
||||
log.info("Session with email {} allowed to access Admin Services", email);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpired() {
|
||||
sessionRepository.deleteSessionsByTimeoutBefore(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class TimeUtil {
|
||||
}
|
||||
|
||||
public static long nowMonthsAgo(long months) {
|
||||
return LocalDateTime.now().minusMonths(months).atZone(ZoneId.systemDefault()).toEpochSecond();
|
||||
return LocalDateTime.now().minusMonths(months).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public static long monthLength(long epoch) {
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"discount100": "Rabatt in %",
|
||||
"deleteProduct": "Produkt löschen",
|
||||
"description": "Beschreibung",
|
||||
"images": "Bilder"
|
||||
"images": "Bilder",
|
||||
"loggedInAs": "Angemeldet als"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"discount100": "Discount in %",
|
||||
"deleteProduct": "Delete Product",
|
||||
"description": "Description",
|
||||
"images": "Images"
|
||||
"images": "Images",
|
||||
"loggedInAs": "Logged in as"
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export const AccountProvider = ({ children }: { children: ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const useAccount = () => {
|
||||
const context = useContext(AccountContext);
|
||||
if (!context) {
|
||||
|
||||
@@ -44,6 +44,7 @@ export const BasketProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const useBasket = () => {
|
||||
const context = useContext(BasketContext);
|
||||
if (!context) {
|
||||
|
||||
@@ -4,8 +4,13 @@ import {Box, Button, IconButton, Toolbar, useTheme} from "@mui/material";
|
||||
import Item from "../../components/Item";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {DataGrid, GridColDef, GridRowId, GridRowSelectionModel} from "@mui/x-data-grid";
|
||||
import {useState} from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {Gauge, gaugeClasses} from "@mui/x-charts";
|
||||
import {useAccount} from "../AccountProvider.tsx";
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import {fetchItems} from "../query/Queries.tsx";
|
||||
import Select from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
|
||||
export default function ItemsInfo() {
|
||||
const theme = useTheme();
|
||||
@@ -56,37 +61,25 @@ export default function ItemsInfo() {
|
||||
console.log("IconEdit", item);
|
||||
}
|
||||
|
||||
const _rows: Item[] = [
|
||||
{
|
||||
id: "1",
|
||||
uuid: "uuid123",
|
||||
name: "Item",
|
||||
description: "Super duper geil <b>wichtiger text </b>",
|
||||
price100: 1000,
|
||||
stock: 100,
|
||||
stockExpected: 1200,
|
||||
category: "garden",
|
||||
rating: 7,
|
||||
discount100: 21,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
uuid: "uuid12312412",
|
||||
name: "Schlauch",
|
||||
description: "Schlauchiger Schlauch für Schlauchige Angelegenheiten",
|
||||
price100: 10,
|
||||
stock: 50,
|
||||
stockExpected: 100,
|
||||
category: "technicalComponents",
|
||||
rating: 10,
|
||||
discount100: 21,
|
||||
},
|
||||
]
|
||||
|
||||
//TODO: get per REST
|
||||
const [rows, setRows] = useState<Item[]>(_rows);
|
||||
const [rows, setRows] = useState<Item[]>([]);
|
||||
const [selectedRows, setSelectedRows] = useState<Set<GridRowId>>(new Set());
|
||||
|
||||
const {user: loginData} = useAccount();
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: ["fetchItems", loginData],
|
||||
queryFn: () => fetchItems(loginData? loginData : {email: "", password: "", session: "", customerId: -1}),
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setRows(data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleSelectionChange = (newSelection: GridRowSelectionModel) => {
|
||||
setSelectedRows(newSelection.ids);
|
||||
};
|
||||
@@ -112,7 +105,7 @@ export default function ItemsInfo() {
|
||||
{
|
||||
field: 'name',
|
||||
headerName: t('name'),
|
||||
width: 150,
|
||||
width: 200,
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
@@ -120,6 +113,7 @@ export default function ItemsInfo() {
|
||||
headerName: t('category'),
|
||||
width: 150,
|
||||
editable: true,
|
||||
valueFormatter: (val) => t(val),
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
@@ -132,7 +126,8 @@ export default function ItemsInfo() {
|
||||
headerName: t('price100'),
|
||||
width: 100,
|
||||
editable: true,
|
||||
type: 'number'
|
||||
type: 'number',
|
||||
valueFormatter: (val) => (val / 100).toFixed(2),
|
||||
},
|
||||
{
|
||||
field: 'discount100',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AdbIcon from '@mui/icons-material/Adb';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { Autocomplete, TextField } from '@mui/material';
|
||||
import { Autocomplete, Badge, TextField } from '@mui/material';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Box from '@mui/material/Box';
|
||||
@@ -22,15 +22,22 @@ import { useAccount } from '../AccountProvider';
|
||||
import { fetchItemList } from '../query/Queries';
|
||||
import LoginDialog from './LoginDialog';
|
||||
import './NavBar.css';
|
||||
import { useBasket } from '../BasketProvider';
|
||||
|
||||
export default function NavBar() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null);
|
||||
const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);
|
||||
const [avatarName, setAvatarName] = React.useState<string>(''); // Für Avatar-Tooltip
|
||||
|
||||
const { user, logout } = useAccount();
|
||||
|
||||
const { basket } = useBasket();
|
||||
|
||||
const totalQuantity = basket?.reduce((sum, item) => sum + item.quantity, 0) ?? 0;
|
||||
|
||||
|
||||
const [loginOpen, setLoginOpen] = React.useState(false);
|
||||
const [loginData, setLoginData] = React.useState({ password: '', email: '', customerId: 0 });
|
||||
|
||||
@@ -42,6 +49,11 @@ export default function NavBar() {
|
||||
const pages = pageKeys.map(key => ({ key, label: t(key) }));
|
||||
const settings = user
|
||||
? [
|
||||
{
|
||||
key: 'email',
|
||||
label: `${t('loggedInAs')}: ${user.email}`,
|
||||
disabled: true // wir nutzen dieses Flag gleich zur Erkennung
|
||||
},
|
||||
{ key: 'account', label: t('account') },
|
||||
{ key: 'orders', label: t('orders') },
|
||||
{ key: 'logout', label: t('logout') }
|
||||
@@ -96,6 +108,13 @@ export default function NavBar() {
|
||||
setItemNames(items.map((item) => item.name));
|
||||
}, [items]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Setze den Avatar-Namen, wenn der Benutzer angemeldet ist
|
||||
if (user) {
|
||||
setAvatarName(user.email.toUpperCase());
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleSearch = (_: React.SyntheticEvent, value: string | null) => {
|
||||
|
||||
if (!value) {
|
||||
@@ -178,15 +197,36 @@ export default function NavBar() {
|
||||
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2, marginLeft: 'auto' }}>
|
||||
<Box sx={{ display: { xs: "none", md: "flex" }, gap: 2 }}>
|
||||
{pages.map(({ key, label }) => (
|
||||
<Button
|
||||
key={key}
|
||||
onClick={() => handleCloseNavMenu(key)}
|
||||
sx={{ color: "white", fontWeight: 500 }}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
))}
|
||||
{pages.map(({ key, label }) => {
|
||||
if (key === 'checkout') {
|
||||
return (
|
||||
<Button
|
||||
key={key}
|
||||
onClick={() => handleCloseNavMenu(key)}
|
||||
sx={{ color: "white", fontWeight: 500 }}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={totalQuantity}
|
||||
color="error"
|
||||
overlap="rectangular"
|
||||
showZero={false}
|
||||
>
|
||||
{label}
|
||||
</Badge>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={key}
|
||||
onClick={() => handleCloseNavMenu(key)}
|
||||
sx={{ color: "white", fontWeight: 500 }}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: { xs: "flex", md: "none" } }}>
|
||||
@@ -216,7 +256,7 @@ export default function NavBar() {
|
||||
<ThemeToggle />
|
||||
<Tooltip title={t('openSettings')} placement='bottom-end'>
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
<Avatar alt="Florian Speicher" src="/static/images/avatar/2.jpg" />
|
||||
<Avatar alt={avatarName} src="/static/images/avatar/2.jpg" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
@@ -226,8 +266,14 @@ export default function NavBar() {
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={() => setAnchorElUser(null)}
|
||||
>
|
||||
{settings.map(({ key, label }) => (
|
||||
<MenuItem key={key} onClick={() => handleCloseUserMenu(key)}>
|
||||
{settings.map(({ key, label, disabled }) => (
|
||||
<MenuItem
|
||||
key={key}
|
||||
onClick={() => {
|
||||
if (!disabled) handleCloseUserMenu(key);
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Typography sx={{ textAlign: "center" }}>{label}</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
|
||||
@@ -59,11 +59,14 @@ export default function ProductInfo({ item }: { item: Item }) {
|
||||
const fetchImage = async () => {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:8085/image?uuid=${item.uuid}`);
|
||||
const data = await response.text();
|
||||
var data = await response.text();
|
||||
if(data.length == 0) {
|
||||
console.error("Got emtpy picture for article ", item.uuid);
|
||||
}
|
||||
setImageUrl("data:image/jpeg;base64," + data);
|
||||
if(!data.startsWith("data:image/")) {
|
||||
data = "data:image/jpeg;base64," + data
|
||||
}
|
||||
setImageUrl(data);
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Bildes:", error);
|
||||
}
|
||||
|
||||
@@ -157,4 +157,12 @@ export const fetchAccounts = async (loginData: User) => {
|
||||
throw new Error("Login failed");
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const fetchItems = async (loginData: User) => {
|
||||
const response = await fetch("http://localhost:8085/article/all?email=" + loginData.email + "&session=" + loginData.session);
|
||||
if (!response.ok) {
|
||||
throw new Error("Login failed");
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
@@ -15,17 +15,17 @@ import {
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { TFunction } from 'i18next';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Item from '../components/Item';
|
||||
import { BasketItem, useBasket } from '../helper/BasketProvider';
|
||||
import { OrderStatusEnum, ShippingDetails, SubmitOrder } from '../components/Order';
|
||||
import { useAccount } from '../helper/AccountProvider';
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { submitCustomer, submitOrder } from '../helper/query/Queries';
|
||||
import { CustomerType } from '../components/Account';
|
||||
import Item from '../components/Item';
|
||||
import { OrderStatusEnum, SubmitOrder } from '../components/Order';
|
||||
import { useAccount } from '../helper/AccountProvider';
|
||||
import { BasketItem, useBasket } from '../helper/BasketProvider';
|
||||
import { fetchCustomer, submitCustomer, submitOrder } from '../helper/query/Queries';
|
||||
|
||||
function getDiscountedPrice(item: Item): number {
|
||||
return (item.price100 / 100 * (100-item.discount100)/100);
|
||||
@@ -70,26 +70,17 @@ export default function Payment() {
|
||||
const { basket, clearBasket } = useBasket();
|
||||
const navigator = useNavigate();
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [shippingDetails, setShippingDetails] = useState<ShippingDetails>({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
telefon: '',
|
||||
const [shippingDetails, setShippingDetails] = useState<CustomerType>({
|
||||
id: 0, // This will be set by the backend or user data
|
||||
name: '',
|
||||
surname: '',
|
||||
address: '',
|
||||
postalCode: '',
|
||||
city: '',
|
||||
zip: '',
|
||||
country: 'Deutschland',
|
||||
});
|
||||
const [orderNumber, setOrderNumber] = useState<string | null>(null);
|
||||
const steps = [t('reviewCart'), t('shippingDetails'), t('payment'), t('orderSummary')];
|
||||
const { user } = useAccount();
|
||||
const customerData: CustomerType = {
|
||||
id: 0,
|
||||
name: shippingDetails.firstName,
|
||||
surname: shippingDetails.lastName,
|
||||
address: shippingDetails.address,
|
||||
country: shippingDetails.country,
|
||||
zip: shippingDetails.postalCode,
|
||||
};
|
||||
|
||||
const submitOrderData: SubmitOrder = {
|
||||
id: 0, // This will be set by the backend
|
||||
@@ -104,8 +95,8 @@ export default function Payment() {
|
||||
};
|
||||
|
||||
const { refetch: refetchCustomer } = useQuery({
|
||||
queryKey: ["submitCustomer", customerData],
|
||||
queryFn: () => submitCustomer(customerData),
|
||||
queryKey: ["submitCustomer", shippingDetails],
|
||||
queryFn: () => submitCustomer(shippingDetails),
|
||||
retry: 0,
|
||||
retryDelay: 1000,
|
||||
enabled: false,
|
||||
@@ -117,6 +108,28 @@ export default function Payment() {
|
||||
</Alert>
|
||||
};
|
||||
|
||||
const { refetch: customerData } = useQuery<CustomerType>({
|
||||
queryKey: ['fetchCustomer', user?.customerId],
|
||||
queryFn: () => fetchCustomer(user?.customerId || 0), // Funktion zum Abrufen der Kundendaten
|
||||
enabled: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchShippingDetails = async () => {
|
||||
if (user) {
|
||||
try {
|
||||
const userShippingDetails = (await customerData()).data;
|
||||
setShippingDetails(userShippingDetails || shippingDetails);
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden der Kundendaten:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchShippingDetails();
|
||||
}, [user, customerData, shippingDetails]);
|
||||
|
||||
|
||||
// Verwende useMutation statt useQuery für submitOrder
|
||||
const { mutateAsync: submitOrderMutation } = useMutation({
|
||||
mutationFn: (orderData: SubmitOrder) => submitOrder(orderData),
|
||||
@@ -174,11 +187,10 @@ export default function Payment() {
|
||||
// Hilfsfunktion prüfen, ob alle Pflichtfelder ausgefüllt sind
|
||||
const isShippingDetailsValid = () => {
|
||||
return (
|
||||
shippingDetails.firstName.trim() !== '' &&
|
||||
shippingDetails.lastName.trim() !== '' &&
|
||||
shippingDetails.name.trim() !== '' &&
|
||||
shippingDetails.surname.trim() !== '' &&
|
||||
shippingDetails.address.trim() !== '' &&
|
||||
shippingDetails.postalCode.trim() !== '' &&
|
||||
shippingDetails.city.trim() !== '' &&
|
||||
shippingDetails.zip.trim() !== '' &&
|
||||
shippingDetails.country.trim() !== ''
|
||||
);
|
||||
};
|
||||
@@ -210,8 +222,8 @@ export default function Payment() {
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('firstName')}
|
||||
name="firstName"
|
||||
value={shippingDetails.firstName}
|
||||
name="name"
|
||||
value={shippingDetails.name}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
@@ -219,21 +231,12 @@ export default function Payment() {
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('lastName')}
|
||||
name="lastName"
|
||||
value={shippingDetails.lastName}
|
||||
name="surname"
|
||||
value={shippingDetails.surname}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('phone')}
|
||||
name="telefon"
|
||||
value={shippingDetails.telefon}
|
||||
onChange={handleInputChange}
|
||||
type='number'
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('address')}
|
||||
@@ -246,18 +249,8 @@ export default function Payment() {
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('postalCode')}
|
||||
name="postalCode"
|
||||
value={shippingDetails.postalCode}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
type='number'
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('city')}
|
||||
name="city"
|
||||
value={shippingDetails.city}
|
||||
name="zip"
|
||||
value={shippingDetails.zip}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
@@ -299,10 +292,9 @@ export default function Payment() {
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="h6">{t('shippingDetails')}:</Typography>
|
||||
<Typography variant="body2">
|
||||
{shippingDetails.firstName} {shippingDetails.lastName}<br />
|
||||
{shippingDetails.name} {shippingDetails.surname}<br />
|
||||
{shippingDetails.address}<br />
|
||||
{shippingDetails.postalCode} {shippingDetails.city}<br />
|
||||
{shippingDetails.country}
|
||||
{shippingDetails.zip} {shippingDetails.country}<br />
|
||||
</Typography>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="h6">{t('orderedItems')}:</Typography>
|
||||
|
||||
@@ -39,9 +39,9 @@ Webshop-Projekt für htw saar Digitale Produktionssysteme
|
||||
|
||||
### 🤮 Windows
|
||||
|
||||
- TODO: make script
|
||||
- Check if in parent folder, because after exit script is in frontend folder. So retry after cd .. only possible
|
||||
```shell
|
||||
|
||||
./start.ps1
|
||||
```
|
||||
|
||||
# Contributors
|
||||
|
||||
60
start.ps1
Normal file
60
start.ps1
Normal file
@@ -0,0 +1,60 @@
|
||||
# Funktion zur Bereinigung beim Beenden
|
||||
function OnExit {
|
||||
Write-Host "`n[DPS] Cleaning up..."
|
||||
if ($backendProcess -and !$backendProcess.HasExited) {
|
||||
$backendProcess.Kill()
|
||||
}
|
||||
if ($frontendProcess -and !$frontendProcess.HasExited) {
|
||||
$frontendProcess.Kill()
|
||||
}
|
||||
Write-Host "[DPS] Cleaned up..."
|
||||
|
||||
# Einen Ordner höher gehen
|
||||
Set-Location (Resolve-Path "..").Path
|
||||
Write-Host "[DPS] Returned to parent directory: $(Get-Location)"
|
||||
|
||||
exit
|
||||
}
|
||||
|
||||
Write-Host "[DPS] trapped"
|
||||
|
||||
# Backend starten
|
||||
Set-Location ./00-backend
|
||||
$backendOutLog = "../backend_latest.out.log"
|
||||
$backendErrLog = "../backend_latest.err.log"
|
||||
$backendProcess = Start-Process "java" -ArgumentList "-jar", "target/webshop-0.0.1-SNAPSHOT.jar" `
|
||||
-RedirectStandardOutput $backendOutLog `
|
||||
-RedirectStandardError $backendErrLog `
|
||||
-NoNewWindow -PassThru
|
||||
Write-Host "[DPS] Backend started with PID $($backendProcess.Id)"
|
||||
|
||||
# Frontend starten (ohne cmd.exe)
|
||||
Set-Location ../01-frontend
|
||||
$frontendOutLog = "../frontend_latest.out.log"
|
||||
$frontendErrLog = "../frontend_latest.err.log"
|
||||
|
||||
$frontendProcess = Start-Process "node" `
|
||||
-ArgumentList "node_modules/.bin/react-scripts", "start" `
|
||||
-RedirectStandardOutput $frontendOutLog `
|
||||
-RedirectStandardError $frontendErrLog `
|
||||
-NoNewWindow -PassThru
|
||||
|
||||
Write-Host "[DPS] Frontend started with PID $($frontendProcess.Id)"
|
||||
|
||||
Write-Host "[DPS] Ctrl+C to stop"
|
||||
|
||||
Register-EngineEvent PowerShell.Exiting -Action { OnExit } -SupportEvent
|
||||
|
||||
try {
|
||||
while ($true) {
|
||||
Start-Sleep -Seconds 1
|
||||
if ($backendProcess.HasExited -and $frontendProcess.HasExited) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception -is [System.Management.Automation.PipelineStoppedException]) {
|
||||
# This block will not execute on CTRL-C
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user