24 Commits

Author SHA1 Message Date
Kayashov.SM
ed72511ed4 правка докер контейнера 2025-12-27 16:17:00 +04:00
Kayashov.SM
1f26015bad добавлен функционал копирования баров 2025-12-27 16:16:48 +04:00
Kayashov.SM
f573660325 правка метода копирования бара 2025-12-27 16:14:36 +04:00
Kayashov.SM
9fb5cdf87c добавлен эндпоинт копирования бара 2025-12-27 15:15:41 +04:00
Kayashov.SM
0cb15aae70 добавлен метод копирования бара 2025-12-27 15:15:31 +04:00
Kayashov.SM
da188a65a4 правка конфигурации 2025-11-29 14:53:37 +04:00
Kayashov.SM
1fdf084a25 правка конфигурации 2025-11-29 14:53:33 +04:00
Kayashov.SM
d9f5a32486 правка отображения страницы калькулятора 2025-11-29 14:51:38 +04:00
Kayashov.SM
8bc3501998 правка отображения страницы калькулятора 2025-11-29 14:50:28 +04:00
Kayashov.SM
5abfc4d6c3 правка фильтров фронта 2025-11-29 14:48:44 +04:00
Kayashov.SM
ba1c0e747c правка фильтра по избранным 2025-11-29 14:48:07 +04:00
Kayashov.SM
60b792ebc3 правка бага с открытием модельного окна не авторизированным пользователем 2025-11-29 14:47:39 +04:00
Kayashov.SM
13fd6b176a добавлены события 2025-08-26 01:19:27 +04:00
Kayashov.SM
fc6c60ad0c изменение сущности под новую страницу пользователя 2025-08-25 19:25:28 +04:00
Kayashov.SM
015416c43b Переведены все запросы на клиенты 2025-08-25 18:27:19 +04:00
Kayashov.SM
6fe2ca6c57 Добавлен атрибут картинки для ингредиентов и удален атрибут английского названия 2025-08-25 14:57:06 +04:00
Kayashov.SM
56f81ea54b Прокинуты публичные методы 2025-08-23 23:53:11 +04:00
Kayashov.SM
92f2523396 Добавлена возможность создания ингредиентов 2025-08-23 23:52:46 +04:00
Kayashov.SM
91770b99b7 Изменен порядок авторизации 2025-08-23 23:52:04 +04:00
Kayashov.SM
10634242e4 большая доработка чистоты кода в части бек 2025-08-22 03:20:42 +04:00
Kayashov.SM
198be069b4 большая доработка чистоты кода в части бек 2025-08-22 03:18:30 +04:00
Kayashov.SM
488638885c правка бага отображения посуды и категорий для редактирования коктейлей 2025-08-21 14:33:47 +04:00
Kayashov.SM
d98686de12 добавлена страница калькулятора 2025-08-21 02:50:09 +04:00
Kayashov.SM
9da769beb5 добавлена страница калькулятора 2025-08-21 02:49:24 +04:00
75 changed files with 1536 additions and 1219 deletions

View File

@@ -5,7 +5,7 @@ COPY . /build/source
WORKDIR /build/source WORKDIR /build/source
RUN mvn -am clean package RUN mvn -am clean package
# Создание образа my-bar # Создание образа my-bar
FROM openjdk:17-jdk-slim as my-bar FROM openjdk:17-ea-jdk-slim as my-bar
COPY --from=build /build/source/target/*.jar /data/app/my-bar.jar COPY --from=build /build/source/target/*.jar /data/app/my-bar.jar
WORKDIR /data/app WORKDIR /data/app
EXPOSE 8080 EXPOSE 8080

View File

@@ -13,6 +13,7 @@ import {EditIngredientPage} from "./pages/ingredients/EditIngredientPage";
import {EditCocktailPage} from "./pages/cocktails/EditCocktailPage"; import {EditCocktailPage} from "./pages/cocktails/EditCocktailPage";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {BarChangePage} from "./pages/BarChangePage"; import {BarChangePage} from "./pages/BarChangePage";
import {CalcPage} from "./pages/calc/CalcPage";
export function NavigationRoutes() { export function NavigationRoutes() {
const {auth} = useAuth(); const {auth} = useAuth();
@@ -59,6 +60,11 @@ const authPages = [
children: (<LoginPage/>), children: (<LoginPage/>),
isPrivate: false, isPrivate: false,
}, },
{
path: paths.bar.calc,
children: (<CalcPage/>),
isPrivate: true,
},
{ {
path: paths.dashboard.overview, path: paths.dashboard.overview,
isPrivate: true, isPrivate: true,
@@ -96,11 +102,16 @@ const authPages = [
const guestPages = [ const guestPages = [
{ {
path: paths.home, path: paths.dashboard.overview,
isPrivate: false, isPrivate: true,
children: (<HomeRedirect auth={false}/>), children: (<MenuPage/>),
exact: true, exact: true,
}, },
{
children: (<HomeRedirect auth={true}/>),
isPrivate: false,
path: paths.home,
},
{ {
path: paths.auth.tg, path: paths.auth.tg,
isPrivate: false, isPrivate: false,

View File

@@ -1,7 +1,5 @@
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {api} from "../../lib/clients/api";
import {requests} from "../../requests";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import {Card} from "@mui/material"; import {Card} from "@mui/material";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
@@ -10,79 +8,38 @@ import IconButton from "@mui/material/IconButton";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import ElectricalServicesIcon from '@mui/icons-material/ElectricalServices'; import ElectricalServicesIcon from '@mui/icons-material/ElectricalServices';
import {getComparator} from "../../components/core/getComparator";
import Toolbar from "@mui/material/Toolbar"; import Toolbar from "@mui/material/Toolbar";
import AddCircleIcon from '@mui/icons-material/AddCircle'; import AddCircleIcon from '@mui/icons-material/AddCircle';
import {BarCreateModal} from "../../components/BarCreateModal"; import {BarCreateModal} from "../../components/BarCreateModal";
import PowerIcon from '@mui/icons-material/Power'; import PowerIcon from '@mui/icons-material/Power';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import {barClient} from "../../lib/clients/BarClient";
export function BarChangePage() { export function BarChangePage() {
const [bars, setBars] = useState([]) const [bars, setBars] = useState([])
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const {createError, createSuccess, createWarning} = useAlert(); const [oldId, setOldId] = useState(null);
const {createError, createSuccess, createWarning, notImplement} = useAlert();
useEffect(() => { const createHandler = (id, name) => {
api().get(requests.bar.list) if (id) {
.then((r) => { barClient.copyBar(id, name, setBars, bars, createError, createSuccess, setOpen)
setBars(r.data.sort(getComparator("name"))) } else {
}) barClient.createBar(name, bars, createSuccess, createError, setBars, setOpen)
.catch(() => {
createError("Ошибка получения списков")
})
}, []);
const changeHandler = (bar) => {
createWarning("Дождитесь окончания операции")
api().post(`${requests.bar.change}/${bar.id}`)
.then(() => createSuccess("Список изменен"))
.catch(() => createError("Ошибка изменения активного списка"))
const newState = bars.map((b) => {
if (b.active) {
return {
...b, active: false
}
}
if (b.id === bar.id) {
return {
...b, active: true
}
}
return b;
})
setBars(newState);
}
const deleteHandler = (bar) => {
if (bar.active) {
createError("Нельзя удалить активный бар!")
return;
} }
api().delete(requests.bar.crud + bar.id)
.then(() => createSuccess("Список удален"))
.catch(() => createError("Ошибка удаления. Обновите страницу"))
setBars(bars.filter((b) => b.id !== bar.id));
}
const createHandler = (name) => {
api().post(requests.bar.crud + name)
.then((r) => {
createSuccess("Cписок создан");
let state = bars;
state.push(r.data);
setBars(state)
setOpen(false)
}).catch(() => createError("Ошибка создания списка"))
} }
function closeHandler() { useEffect(() => barClient.getBarList(setBars, createError), []);
setOpen(false)
}
return (<> return (<>
<BarCreateModal open={open} close={closeHandler} create={createHandler}/> <BarCreateModal open={open} setOpen={setOpen} create={createHandler} id={oldId}/>
<Paper sx={{p: 1}}> <Paper sx={{p: 1}}>
<Toolbar> <Toolbar>
<Typography variant='h6'>Списки ингредиентов (бары)</Typography> <Typography variant='h6'>Списки ингредиентов (бары)</Typography>
<IconButton edge="end" onClick={() => setOpen(true)}> <IconButton edge="end" onClick={() => {
setOldId(null);
setOpen(true);
}}>
<AddCircleIcon/> <AddCircleIcon/>
</IconButton> </IconButton>
</Toolbar> </Toolbar>
@@ -90,17 +47,27 @@ export function BarChangePage() {
return <Card key={b.id} sx={{m: 2, p: 2}}> return <Card key={b.id} sx={{m: 2, p: 2}}>
<Stack direction='row' justifyContent={'space-between'}> <Stack direction='row' justifyContent={'space-between'}>
<Typography>{b.name}</Typography> <Typography>{b.name}</Typography>
{b.active && <IconButton disabled> <Box>
<PowerIcon/> <IconButton onClick={() => {
</IconButton>} setOldId(b.id)
{!b.active && <Box> setOpen(true);
<IconButton onClick={() => deleteHandler(b)}> }}>
<DeleteIcon/> <ContentCopyIcon/>
</IconButton> </IconButton>
<IconButton onClick={() => changeHandler(b)}> {b.active && <IconButton disabled>
<ElectricalServicesIcon/> <PowerIcon/>
</IconButton> </IconButton>}
</Box>} {!b.active && <>
<IconButton
onClick={() => barClient.deleteBar(b, bars, createError, createSuccess, setBars)}>
<DeleteIcon/>
</IconButton>
<IconButton
onClick={() => barClient.changeBar(b.id, bars, createWarning, createSuccess, createError, setBars)}>
<ElectricalServicesIcon/>
</IconButton>
</>}
</Box>
</Stack> </Stack>
</Card> </Card>
})} })}

View File

@@ -1,28 +1,14 @@
import * as React from "react"; import * as React from "react";
import {useSearchParams} from "react-router-dom"; import {useSearchParams} from "react-router-dom";
import {Loading} from "../../../../components/core/Loading"; import {Loading} from "../../../../components/core/Loading";
import {api} from "../../../../lib/clients/api";
import {requests} from "../../../../requests";
import {useAuth} from "../../../../hooks/useAuth"; import {useAuth} from "../../../../hooks/useAuth";
import {authClient} from "../../../../lib/clients/AuthClient";
export function TelegramCode() { export function TelegramCode() {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const {checkSession} = useAuth(); const {checkSession} = useAuth();
let code = searchParams.get("code"); authClient.loginByCode(searchParams.get("code"), checkSession)
const request = {
byLogin: false,
code: code
}
api().post(requests.auth.login, request)
.then(async (response) => {
if (response.data.error) {
return;
}
localStorage.setItem("token", response.data.token);
await checkSession?.();
window.location.reload();
})
return ( return (
<Loading loading={true}/> <Loading loading={true}/>

View File

@@ -0,0 +1,90 @@
import Typography from "@mui/material/Typography";
import * as React from "react";
import {useEffect, useMemo} from "react";
import {useAlert} from "../../../hooks/useAlert";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import {CocktailItemCalc} from "./CocktailItemCalc";
import {IngredientCalcCard} from "./IngredientCalcCard";
import {cocktailClient} from "../../../lib/clients/CocktailClient";
export function CalcPage() {
const {createError} = useAlert();
const [cocktails, setCocktails] = React.useState([]);
const [load, setLoad] = React.useState(false);
const [cocktailMap, setCocktailMap] = React.useState({});
const changeHandler = (id, value) => {
setCocktailMap((prev) => ({
...prev,
[id]: value
}));
}
useEffect(() => {
cocktailClient.getCocktailsForCalcPage(load, setLoad, setCocktails, setCocktailMap, createError)
// eslint-disable-next-line
}, [load]);
const ingredients = useMemo(() => {
let map = {}
if (!cocktails) {
return [];
}
cocktails.forEach((c) => {
const receipts = c.receipt;
const countMeter = cocktailMap[c.id];
if (!receipts) {
return
}
receipts.forEach((r) => {
const ingredient = r.ingredient;
const id = ingredient.id;
const ingredientCount = r.count;
const resultCount = ingredientCount * countMeter;
if (map[id]) {
map[id] = {
...map[id],
count: map[id].count + resultCount
}
} else {
map[id] = {
ingredient: ingredient,
count: resultCount
}
}
})
})
return Object.values(map);
},
[cocktails, cocktailMap])
const deleteHandler = (id) => {
const state = cocktails.filter((c) => c.id !== id);
setCocktails(state);
}
console.log(cocktailMap)
return (
<Box padding={2}>
<Typography variant="h4" align="center">Коктейли</Typography>
<Stack mt={2}>
{cocktails.map((item, i) => (
<CocktailItemCalc key={i} cocktail={item} deleteHandler={deleteHandler}
changeHandler={changeHandler}/>
))}
</Stack>
<Typography variant="h4" mt={2} align="center">Ингредиенты</Typography>
<Stack mt={2}>
{ingredients.map((item, i) => (
<IngredientCalcCard key={i} count={item.count} ingredient={item.ingredient}/>
))}
</Stack>
</Box>
)
}

View File

@@ -0,0 +1,40 @@
import {Card} from "@mui/material";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import DeleteIcon from "@mui/icons-material/Delete";
import React from "react";
import Typography from "@mui/material/Typography";
import {Counter} from "./Counter";
export function CocktailItemCalc({cocktail, deleteHandler, changeHandler}) {
return (
<Card sx={{mb: 1, height: '250px', display: 'relative', p: 2}}>
<Stack justifyContent={'start'} spacing={2}>
<Stack direction='row' justifyContent='start' alignItems='center'>
<Box sx={{width: '100px', height: '100px'}}>
<img src={cocktail.image} loading='lazy' height={'100px'} width={'100px'} alt={cocktail.id}/>
</Box>
<Box sx={{width: 'calc(90% - 100px)', pr: 2, ml: 2}}>
<Stack>
<Typography>{cocktail.name}</Typography>
<Typography>{cocktail.volume}</Typography>
<Typography>{cocktail.category}</Typography>
<Typography>{cocktail.alcoholic}</Typography>
<Typography color={'textSecondary'}>{cocktail.components}</Typography>
</Stack>
</Box>
<Stack direction='row'>
<Stack sx={{width: '5%'}} spacing={1} justifyContent='flex-start'>
<IconButton size='small' onClick={() => deleteHandler(cocktail.id)}>
<DeleteIcon/>
</IconButton>
</Stack>
</Stack>
</Stack>
<Counter id={cocktail.id} changeHandler={changeHandler}/>
</Stack>
</Card>
)
}

View File

@@ -0,0 +1,65 @@
import React, {useState} from 'react';
import {Box, TextField, Button} from '@mui/material';
import {styled} from '@mui/material/styles';
// Стилизуем контейнер счетчика
const CounterContainer = styled(Box)`
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
border-radius: 8px;
//box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
//background-color: #ffffff;
`;
export function Counter({id, changeHandler}) {
const [value, setValue] = useState(1);
const handleChange = (newValue) => {
setValue(newValue);
changeHandler(id, newValue);
}
return (
<Box>
<Button onClick={() => {
if (value > 0) {
setValue(value - 1);
}
}}
sx={{
width: '20px',
height: '55px',
borderRadius: '50%',
margin: '0 8px',
backgroundColor: 'transparent',
}}></Button>
<TextField
value={value}
onChange={(e) => {
const newValue = parseInt(e.target.value, 10);
if (!isNaN(newValue)) {
handleChange(newValue);
}
}}
inputProps={{inputMode: 'numeric'}}
sx={{
width: '40px',
height: '15px',
fontSize: '10px',
textAlign: 'center'
}}
/>
<Button onClick={() => handleChange(value + 1)}
sx={{
width: '20px',
height: '55px',
borderRadius: '50%',
margin: '0 8px',
backgroundColor: 'transparent',
}}>+</Button>
</Box>
);
}

View File

@@ -0,0 +1,21 @@
import {Card} from "@mui/material";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import React from "react";
export function IngredientCalcCard({ingredient, count}) {
return (
<Card sx={{mb: 1, height: '130px', display: 'relative', pt: 1}}>
<Stack direction='row' justifyContent='start' alignItems='center'>
<Box sx={{width: '100px', height: '100px'}}>
<img src={ingredient.image} loading='lazy' height={'100px'} width={'100px'} alt={ingredient.id}/>
</Box>
<Box sx={{width: 'calc(90% - 100px)', pr: 2}}>{ingredient.name}</Box>
<Stack direction='row'>
<Box mr={1} pt={'3px'}>{count}</Box>
</Stack>
</Stack>
</Card>
)
}

View File

@@ -5,20 +5,16 @@ import {useCallback, useEffect, useState} from "react";
import {Cocktail} from "../../../components/cocktails/Cocktail"; import {Cocktail} from "../../../components/cocktails/Cocktail";
import {Fab, Skeleton} from "@mui/material"; import {Fab, Skeleton} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import {requests} from "../../../requests";
import {NoResult} from "../../../components/cocktails/NoResult"; import {NoResult} from "../../../components/cocktails/NoResult";
import {FilterBlock} from "../../../components/cocktails/FilterBlock"; import {FilterBlock} from "../../../components/cocktails/FilterBlock";
import {api} from "../../../lib/clients/api";
import {CocktailInfoModal} from "../../../components/cocktails/CocktailInfoModal"; import {CocktailInfoModal} from "../../../components/cocktails/CocktailInfoModal";
import {useUser} from "../../../hooks/useUser"; import {useUser} from "../../../hooks/useUser";
import {blue} from "@mui/material/colors"; import {blue} from "@mui/material/colors";
import UpIcon from "@mui/icons-material/KeyboardArrowUp"; import UpIcon from "@mui/icons-material/KeyboardArrowUp";
import {sortList} from "../../../components/cocktails/sortingList";
import Button from "@mui/material/Button";
import {useSelect} from "../../../hooks/useSelect"; import {useSelect} from "../../../hooks/useSelect";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import CheckMarks from "../../../components/cocktails/CheckMarks"; import CheckMarks from "../../../components/cocktails/CheckMarks";
import {getComparator} from "../../../components/core/getComparator"; import {cocktailClient} from "../../../lib/clients/CocktailClient";
const emptyFilter = { const emptyFilter = {
search: "", search: "",
@@ -37,7 +33,6 @@ const emptyFilter = {
const CocktailsPageContent = () => { const CocktailsPageContent = () => {
const {user} = useUser(); const {user} = useUser();
const {createError, createSuccess} = useAlert(); const {createError, createSuccess} = useAlert();
const [allowIngredients, setAllowIngredients] = useState([])
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [filter, setFilter] = useState(emptyFilter) const [filter, setFilter] = useState(emptyFilter)
const [chips, setChips] = useState([]) const [chips, setChips] = useState([])
@@ -53,36 +48,10 @@ const CocktailsPageContent = () => {
if (load || (!isNew && isEnd)) { if (load || (!isNew && isEnd)) {
return false; return false;
} }
setLoad(true); cocktailClient.getMenu(setRows, setIsNew, setPage, setLoad, setIsEnd, isNew, rows, page, size, filter, createError);
const request = {
...filter,
sort: sortList.find((s) => s.name === filter.sorting).id,
page: page + 1,
size: size,
notHaveCount: Array.isArray(filter.iCount) ? null : filter.iCount
}
api().post(requests.cocktails.menu, request)
.then((r) => {
if (r.data.length === 0) {
if (isNew) {
setRows([]);
}
setIsEnd(true);
setLoad(false);
return;
}
const cocktails = isNew ? r.data : rows.concat(r.data);
setRows(cocktails);
setIsNew(false);
setPage(page + 1);
setLoad(false);
})
.catch((r) => {
setLoad(false);
createError("Ошибка загрузки данных от сервера Status:" + r.code)
})
// eslint-disable-next-line // eslint-disable-next-line
}, [load, isEnd, page]); }, [load, isEnd, page]);
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
const {scrollTop, scrollHeight, clientHeight} = document.documentElement; const {scrollTop, scrollHeight, clientHeight} = document.documentElement;
@@ -94,34 +63,7 @@ const CocktailsPageContent = () => {
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
// eslint-disable-next-line // eslint-disable-next-line
}, [loading]); }, [loading]);
useEffect(() => { useEffect(() => loading(), [filter])
api().get(requests.bar.ingredientSimple)
.then((r) => {
const arr = r.data.filter((i) => i.isHave)
.map((i) => i.name)
setAllowIngredients(arr)
})
.catch(() => createError("Ошибка получения ингредиентов"))
// eslint-disable-next-line
}, [])
useEffect(() => {
loading();
// eslint-disable-next-line
}, [filter])
useEffect(() => {
if (!filter.all) {
return;
}
const ingredients = new Set();
rows.map((c) => c.components)
.map((c) => c.split(", "))
.map((c) => c.filter((i) => !allowIngredients.includes(i)))
.filter((nhc) => nhc.length === 1)
.map((fc) => fc[0])
.forEach((i) => ingredients.add(i))
setChips(Array.from(ingredients).sort(getComparator()));
// eslint-disable-next-line
}, [rows, allowIngredients])
const renderSkeleton = () => { const renderSkeleton = () => {
return Array.from({length: 3}, () => null) return Array.from({length: 3}, () => null)
@@ -143,11 +85,7 @@ const CocktailsPageContent = () => {
} }
return r; return r;
}) })
api().post(`${requests.cocktails.rating}${row.id}&rating=${value}`) cocktailClient.changeRating(row.id, newState, value, setRows, createSuccess, createError)
.then(() => {
setRows(newState);
createSuccess("Спасибо за оценку!")
}).catch(() => createError("Ошибка сохранения"))
} }
const handleFilterChange = (filterName, value) => { const handleFilterChange = (filterName, value) => {
@@ -173,14 +111,7 @@ const CocktailsPageContent = () => {
} }
return r; return r;
}); });
let url = `${requests.cocktails.favourite}${row.id}`; cocktailClient.changeFavourite(value, row.id, newState, setRows, createSuccess, createError)
let request = value ? api().put(url) : api().delete(url);
request
.then(() => {
setRows(newState);
createSuccess("Спасибо за оценку!")
}).catch(() => createError("Ошибка сохранения"))
} }
const handleFilterClear = () => { const handleFilterClear = () => {
setFilter(emptyFilter); setFilter(emptyFilter);
@@ -188,17 +119,9 @@ const CocktailsPageContent = () => {
setIsEnd(false); setIsEnd(false);
setPage(-1); setPage(-1);
} }
const handleSelectCocktail = (row) => {
selectCocktail(row.id) const handleSelectCocktail = (row) => selectCocktail(row.id)
} const deleteHandle = (row) => cocktailClient.deleteCocktail(row.id, rows, setRows, createSuccess, createError)
const deleteHandle = (row) => {
api().delete(requests.cocktails.cocktail + row.id)
.then(() => {
setRows(rows.filter((r) => r.id !== row.id))
createSuccess("Коктейль удален")
})
.catch(() => createError("Ошибка удаления коктейля"))
}
return ( return (
<Box> <Box>

View File

@@ -6,19 +6,18 @@ import {useEffect, useState} from "react";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import {Autocomplete} from "@mui/material"; import {Autocomplete} from "@mui/material";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import {api} from "../../../lib/clients/api";
import {requests} from "../../../requests";
import {useAlert} from "../../../hooks/useAlert"; import {useAlert} from "../../../hooks/useAlert";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import CheckMarks from "../../../components/cocktails/CheckMarks";
import {EditCocktailReceipt} from "../../../components/cocktails/EditCocktailReceipt"; import {EditCocktailReceipt} from "../../../components/cocktails/EditCocktailReceipt";
import {SelectEdit} from "../../../components/cocktails/SelectEdit"; import {SelectEdit} from "../../../components/cocktails/SelectEdit";
import {getComparator} from "../../../components/core/getComparator";
import {useSearchParams} from "react-router-dom"; import {useSearchParams} from "react-router-dom";
import {Loading} from "../../../components/core/Loading"; import {Loading} from "../../../components/core/Loading";
import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import {styled} from "@mui/material/styles"; import {styled} from "@mui/material/styles";
import {cocktailClient} from "../../../lib/clients/CocktailClient";
import {categoryClient} from "../../../lib/clients/CategoryClient";
import {glassClient} from "../../../lib/clients/GlassClient";
const emptyCocktail = { const emptyCocktail = {
id: null, id: null,
@@ -76,47 +75,15 @@ export function EditCocktailPage() {
const [category, setCategory] = useState([]); const [category, setCategory] = useState([]);
useEffect(() => { useEffect(() => {
api().get(requests.cocktails.simple) cocktailClient.getSimpleList(setCocktails, setSelected, setLoading, createError, searchParams.get("id"))
.then((r) => { categoryClient.getCategoryList(setCategory, createError);
const arr = r.data.sort(getComparator("asc", "name")); glassClient.getGlassList(setGlass, createError)
setCocktails(arr)
const currentId = searchParams.get("id");
if (!currentId) {
setLoading(false);
return;
}
const currentCocktail = arr.find((r) => r.id === (currentId * 1));
if (!currentCocktail) {
setLoading(false);
return;
}
setSelected(currentCocktail.id);
setLoading(false);
})
.catch(() => createError("Ошибка получения данных"))
api().get(requests.bar.category)
.then((r) => setCategory(r.data.sort(getComparator())))
.catch(() => createError("Ошибка получения категорий"))
api().get(requests.bar.glass)
.then((r) => setGlass(r.data.sort(getComparator())))
.catch(() => createError("Ошибка получения посуды"))
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
useEffect(() => {
if (!selected) { useEffect(() => cocktailClient.getOneCocktail(selected, setCocktail, getError, emptyCocktail), [selected])
setCocktail(emptyCocktail); const saveHandler = () => cocktailClient.saveChangeCocktail(cocktail, createError, createSuccess)
return; const deleteHandle = () => cocktailClient.deleteCocktailFromEdit(setCocktails, setCocktail, createError, cocktails, cocktail, emptyCocktail)
}
api().get(requests.cocktails.cocktail + selected)
.then((r) => {
setCocktail(r.data)
})
.catch(() => getError());
// eslint-disable-next-line
}, [selected])
const changeCocktailValue = (name, value) => { const changeCocktailValue = (name, value) => {
if (name === "tags") { if (name === "tags") {
@@ -127,25 +94,7 @@ export function EditCocktailPage() {
[name]: value [name]: value
})) }))
} }
const saveHandler = () => {
api().patch(requests.cocktails.edit, cocktail)
.then((r) => {
if (!r.data.error) {
createSuccess("Сохранено")
return;
}
createError("Ошибка на сервере: " + r.data.error)
})
.catch(() => createError("Неизвестная ошибка"))
}
const deleteHandle = () => {
api().delete(requests.cocktails.cocktail + cocktail.id)
.then(() => {
setCocktails(cocktails.filter((r) => r.id !== cocktail.id))
setCocktail(emptyCocktail);
})
.catch(() => createError("Ошибка удаления коктейля"))
}
return ( return (
<Box> <Box>
{/*Загрузка*/} {/*Загрузка*/}
@@ -201,14 +150,7 @@ export function EditCocktailPage() {
<VisuallyHiddenInput <VisuallyHiddenInput
type="file" type="file"
accept=".jpg,.jpeg,.png" accept=".jpg,.jpeg,.png"
onChange={(event) => { onChange={(event) => cocktailClient.savePhoto(event, changeCocktailValue, getError)}
const file = event.target.files[0];
let formData = new FormData();
formData.append('file', file);
api().post(requests.cocktails.savePhoto, formData)
.then((r) => changeCocktailValue("image", r.data))
.catch(() => getError())
}}
/> />
</Button> </Button>

View File

@@ -5,8 +5,6 @@ import * as React from "react";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import {Autocomplete, FormControl, FormControlLabel, InputLabel} from "@mui/material"; import {Autocomplete, FormControl, FormControlLabel, InputLabel} from "@mui/material";
import {api} from "../../../lib/clients/api";
import {requests} from "../../../requests";
import {useAlert} from "../../../hooks/useAlert"; import {useAlert} from "../../../hooks/useAlert";
import {useSearchParams} from "react-router-dom"; import {useSearchParams} from "react-router-dom";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
@@ -15,7 +13,7 @@ import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Select from "@mui/material/Select"; import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import {getComparator} from "../../../components/core/getComparator"; import {ingredientClient} from "../../../lib/clients/IngredientClient";
const emptyIngredient = { const emptyIngredient = {
id: null, id: null,
@@ -36,25 +34,8 @@ export function EditIngredientPage() {
const [ingredient, setIngredient] = useState(emptyIngredient) const [ingredient, setIngredient] = useState(emptyIngredient)
const {createError, createSuccess} = useAlert(); const {createError, createSuccess} = useAlert();
useEffect(() => { useEffect(() => {
api().get(requests.bar.ingredientList) ingredientClient.allList(searchParams.get("id"), setIngredients, setIngredient, createError)
.then((r) => { ingredientClient.getType(setTypes)
const arr = r.data.sort(getComparator("asc", "name"));
setIngredients(arr)
const currentId = searchParams.get("id");
if (!currentId) {
return;
}
const currentIngredient = arr.find((r) => r.id === (currentId * 1));
if (!currentIngredient) {
return;
}
setIngredient(currentIngredient);
})
.catch(() => createError("Ошибка получения данных"))
api().get(requests.bar.type)
.then((r) => setTypes(r.data.sort(getComparator("asc", "name"))))
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
@@ -64,11 +45,6 @@ export function EditIngredientPage() {
[name]: value [name]: value
})) }))
} }
const saveIngredientHandler = () => {
api().patch(requests.bar.ingredient, ingredient)
.then(() => createSuccess("Ингредиент сохранен"))
.catch(() => createError("Ошибка сохранения"))
}
return ( return (
<Box> <Box>
@@ -111,10 +87,6 @@ export function EditIngredientPage() {
variant="outlined" label={"Название"} variant="outlined" label={"Название"}
value={ingredient.name} value={ingredient.name}
onChange={(e) => changeIngredientValue("name", e.target.value)}/> onChange={(e) => changeIngredientValue("name", e.target.value)}/>
<TextField sx={{mr: 1, mb: 2, minWidth: 330}}
label="Английское название" variant="outlined"
value={ingredient.enName}
onChange={(e) => changeIngredientValue("enName", e.target.value)}/>
</Box> </Box>
<Box height={70} mt={1} ml={1}> <Box height={70} mt={1} ml={1}>
@@ -162,7 +134,8 @@ export function EditIngredientPage() {
</Stack> </Stack>
</Paper> </Paper>
<Box display={'flex'} justifyContent={'flex-end'}> <Box display={'flex'} justifyContent={'flex-end'}>
<Button variant='contained' onClick={() => saveIngredientHandler()}>Сохранить</Button> <Button variant='contained'
onClick={() => ingredientClient.saveIngredient(ingredient, createSuccess, createError)}>Сохранить</Button>
</Box> </Box>
</Box> </Box>
) )

View File

@@ -8,10 +8,8 @@ import SearchIcon from "@mui/icons-material/Search";
import * as React from "react"; import * as React from "react";
import {useEffect, useMemo, useState} from "react"; import {useEffect, useMemo, useState} from "react";
import {Loading} from "../../../components/core/Loading"; import {Loading} from "../../../components/core/Loading";
import {requests} from "../../../requests";
import {useAlert} from "../../../hooks/useAlert"; import {useAlert} from "../../../hooks/useAlert";
import {IngredientInfoModal} from "../../../components/Ingredients/IngredientInfoModal"; import {IngredientInfoModal} from "../../../components/Ingredients/IngredientInfoModal";
import {api} from "../../../lib/clients/api";
import Tab from "@mui/material/Tab"; import Tab from "@mui/material/Tab";
import {a11yProps} from "../../../components/core/tabProps"; import {a11yProps} from "../../../components/core/tabProps";
import {CustomTabPanel} from "../../../components/core/TabPanel"; import {CustomTabPanel} from "../../../components/core/TabPanel";
@@ -19,6 +17,7 @@ import {IngredientList} from "../../../components/Ingredients/IngredientList";
import {blue} from "@mui/material/colors"; import {blue} from "@mui/material/colors";
import UpIcon from "@mui/icons-material/KeyboardArrowUp"; import UpIcon from "@mui/icons-material/KeyboardArrowUp";
import {useSelect} from "../../../hooks/useSelect"; import {useSelect} from "../../../hooks/useSelect";
import {ingredientClient} from "../../../lib/clients/IngredientClient";
export function IngredientsPage() { export function IngredientsPage() {
const [value, setValue] = React.useState(0); const [value, setValue] = React.useState(0);
@@ -30,15 +29,7 @@ export function IngredientsPage() {
const {createError, createSuccess} = useAlert(); const {createError, createSuccess} = useAlert();
useEffect(() => { useEffect(() => {
api().get(requests.bar.ingredientList) ingredientClient.getAllIngredients(setIngredients, setLoading, createError)
.then((r) => {
setIngredients(r.data)
setLoading(false);
})
.catch(() => {
createError("Ошибка получения списка ингредиентов");
setLoading(false);
})
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
@@ -63,26 +54,12 @@ export function IngredientsPage() {
return ingredient; return ingredient;
} }
}) })
const url = `${requests.bar.ingredient}?id=${row.id}`; ingredientClient.changeIngredientIsHave(row.id, value, newState, setIngredients, createError)
const request = value ? api().put(url) : api().delete(url);
request
.then(() => {
setIngredients(newState);
})
.catch(() => {
createError("Ошибка изменения ингредиента");
});
}
const handleOpenModal = (i) => {
selectIngredient(i)
} }
const handleDelete = (id) => { const handleDelete = (id) => {
const newState = ingredients.filter((ingredient) => ingredient.id !== id); const newState = ingredients.filter((ingredient) => ingredient.id !== id);
setIngredients(newState) setIngredients(newState)
ingredientClient.deleteIngredientIsHave(id, createSuccess, createError)
api().delete(`${requests.bar.ingredient}/${id}`)
.then((r) => createSuccess("Ингредиент удален"))
.catch(() => createError("Ошибка удаления ингредиента. Перезагрузите страницу"))
} }
return ( return (
@@ -118,11 +95,11 @@ export function IngredientsPage() {
<Box> <Box>
<CustomTabPanel value={value} index={0}> <CustomTabPanel value={value} index={0}>
<IngredientList rows={ingredientsInBar} value={false} changeHandler={changeHandler} <IngredientList rows={ingredientsInBar} value={false} changeHandler={changeHandler}
infoHandler={handleOpenModal}/> infoHandler={selectIngredient}/>
</CustomTabPanel> </CustomTabPanel>
<CustomTabPanel value={value} index={1}> <CustomTabPanel value={value} index={1}>
<IngredientList rows={ingredientsToAdd} value={true} changeHandler={changeHandler} <IngredientList rows={ingredientsToAdd} value={true} changeHandler={changeHandler}
infoHandler={handleOpenModal}/> infoHandler={selectIngredient}/>
</CustomTabPanel> </CustomTabPanel>
</Box> </Box>
<Fab sx={{ <Fab sx={{

View File

@@ -1,18 +1,18 @@
import Dialog from "@mui/material/Dialog"; import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle"; import DialogTitle from "@mui/material/DialogTitle";
import * as React from "react"; import * as React from "react";
import {useState} from "react";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import DialogContent from "@mui/material/DialogContent"; import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import {useState} from "react";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
export function BarCreateModal({open, close, create}) { export function BarCreateModal({open, setOpen, create, id}) {
const [value, setValue] = useState(""); const [value, setValue] = useState("");
return ( return (
<Dialog fullWidth={true} <Dialog fullWidth={true}
open={open} onClose={close} open={open} onClose={() => setOpen(false)}
sx={{ sx={{
'& .MuiDialog-paper': { '& .MuiDialog-paper': {
margin: '8px', margin: '8px',
@@ -26,13 +26,17 @@ export function BarCreateModal({open, close, create}) {
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<TextField sx={{width: '75%'}} <TextField sx={{width: '75%'}}
label={"Название списка"} variant='outlined' label={<Typography pt={'4px'}>
Название списка</Typography>} variant='outlined'
value={!value ? "" : value} value={!value ? "" : value}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => create(value)}>Создать</Button> <Button onClick={() => {
create(id, value);
setValue("");
}}>Создать</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
) )

View File

@@ -4,17 +4,16 @@ import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import * as React from "react"; import * as React from "react";
import {useEffect, useState} from "react";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import List from "@mui/material/List"; import List from "@mui/material/List";
import {useEffect, useState} from "react";
import {api} from "../../lib/clients/api";
import {requests} from "../../requests";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
import {useSelect} from "../../hooks/useSelect"; import {useSelect} from "../../hooks/useSelect";
import {IngredientAlert} from "./IngredientAlert"; import {IngredientAlert} from "./IngredientAlert";
import {useUser} from "../../hooks/useUser"; import {useUser} from "../../hooks/useUser";
import {cocktailClient} from "../../lib/clients/CocktailClient";
export function IngredientInfoModal({ingredient, handleDelete}) { export function IngredientInfoModal({ingredient, handleDelete}) {
const {user} = useUser(); const {user} = useUser();
@@ -28,11 +27,7 @@ export function IngredientInfoModal({ingredient, handleDelete}) {
}; };
useEffect(() => { useEffect(() => {
if(!ingredient) { cocktailClient.getCocktailByIngredient(ingredient, setCocktails)
return
}
api().get(requests.cocktails.byIngredient + ingredient.id)
.then((r) => setCocktails(r.data))
.catch(() => createError()) .catch(() => createError())
// eslint-disable-next-line // eslint-disable-next-line
}, [ingredient]); }, [ingredient]);
@@ -59,7 +54,7 @@ export function IngredientInfoModal({ingredient, handleDelete}) {
</Stack> </Stack>
{cocktails.length > 0 && ( {cocktails.length > 0 && (
<> <>
<Typography sx={{ mt:2}}>Коктейли:</Typography> <Typography sx={{mt: 2}}>Коктейли:</Typography>
<List> <List>
{cocktails.map((c) => { {cocktails.map((c) => {
return ( return (
@@ -69,7 +64,7 @@ export function IngredientInfoModal({ingredient, handleDelete}) {
}}> }}>
<Stack direction={'row'}> <Stack direction={'row'}>
<img src={c.image} alt={c.name} loading={"eager"} width={"50"}/> <img src={c.image} alt={c.name} loading={"eager"} width={"50"}/>
<Typography sx={{mx:1}}>{c.name}</Typography> <Typography sx={{mx: 1}}>{c.name}</Typography>
{c.rating.rating > 0 && <Typography> {`${c.rating.rating}/5`}</Typography>} {c.rating.rating > 0 && <Typography> {`${c.rating.rating}/5`}</Typography>}
</Stack> </Stack>
</ListItem> </ListItem>
@@ -83,7 +78,8 @@ export function IngredientInfoModal({ingredient, handleDelete}) {
{user.role !== 'USER' && <Button onClick={() => setOpen(true)}>Удалить</Button>} {user.role !== 'USER' && <Button onClick={() => setOpen(true)}>Удалить</Button>}
<Button onClick={closeIngredient}>Закрыть</Button> <Button onClick={closeIngredient}>Закрыть</Button>
</DialogActions> </DialogActions>
<IngredientAlert handleDelete={handleDelete} handleClose={handleClose} open={open} id={ingredient.id} handleCloseParent={closeIngredient}/> <IngredientAlert handleDelete={handleDelete} handleClose={handleClose} open={open} id={ingredient.id}
handleCloseParent={closeIngredient}/>
</Dialog> </Dialog>
); );
} }

View File

@@ -10,12 +10,11 @@ import Button from "@mui/material/Button";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import {red} from "@mui/material/colors"; import {red} from "@mui/material/colors";
import {requests} from "../../requests";
import {useAuth} from "../../hooks/useAuth"; import {useAuth} from "../../hooks/useAuth";
import {api} from "../../lib/clients/api"; import {authClient} from "../../lib/clients/AuthClient";
const emptyRequest = { const emptyRequest = {
byLogin: false, byLogin: true,
code: "", code: "",
login: "", login: "",
password: "" password: ""
@@ -25,7 +24,7 @@ export function SignInForm() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [request, setRequest] = useState(emptyRequest); const [request, setRequest] = useState(emptyRequest);
const [pass, setPass] = useState(false) const [pass, setPass] = useState(true)
const {checkSession} = useAuth(); const {checkSession} = useAuth();
const buttonSx = { const buttonSx = {
@@ -38,21 +37,9 @@ export function SignInForm() {
}), }),
}; };
const handleButtonClick = async () => { const handleButtonClick = async () => {
setLoading(true); await authClient.login(request, setLoading, setError, checkSession)
const response = await api().post(requests.auth.login, request);
if (response.data.error) {
setError(response.data.error);
setLoading(false);
return;
}
localStorage.setItem("token", response.data.token);
await checkSession?.();
window.location.reload();
} }
const renderByCode = () => { const renderByCode = () => {
return ( return (
<Stack direction='row' mt={1}> <Stack direction='row' mt={1}>

View File

@@ -1,40 +0,0 @@
import {Card} from "@mui/material";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import React from "react";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
const role = (myRole) => {
switch (myRole) {
case "ADMIN":
return "Администратор";
case "ADMIN_NOT_BARMEN":
return "Управляющий";
default:
return null;
}
}
export function BarItem({row, changeHandler, all, enterExist}) {
return (
<Card sx={{mb: 1, height: '100px', display: 'relative', p: 1}}>
<Stack direction='row' justifyContent='start' alignItems='start'>
<Box sx={{width: '75%', pr: 2}}>
<Typography variant='h6'>{row.name}</Typography>
<Typography>{role(row.myRole)}</Typography>
</Box>
<Stack sx={{width: '25%'}} spacing={1} justifyContent='flex-end' display='flex'>
<Typography color={row.open ? 'green' : 'red'}>{row.open ? "Бар открыт" : "Бар закрыт"}</Typography>
<Button variant='contained'
color={row.enter ? 'error' : 'success'}
onClick={() => changeHandler(row, !row.enter)}
disabled={!row.enter && enterExist}
>
{all ? "Добавить" : row.enter ? "Выйти" : "Войти"}
</Button>
</Stack>
</Stack>
</Card>
)
}

View File

@@ -1,70 +0,0 @@
import Box from "@mui/material/Box";
import {useEffect, useMemo, useState} from "react";
import {api} from "../../lib/clients/api";
import {requests} from "../../requests";
import {useAlert} from "../../hooks/useAlert";
import {BarItem} from "./BarItem";
import {Loading} from "../core/Loading";
import * as React from "react";
import {useUser} from "../../hooks/useUser";
export function BarList({all}) {
const {getError, createError} = useAlert();
const {refresh} = useUser();
const [bars, setBars] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
api().get(`${requests.bar.list}?my=${!all}`)
.then((r) => {
setBars(r.data)
setLoading(false);
})
.catch(() => getError())
// eslint-disable-next-line
}, []);
const enterExist = useMemo(() => bars.find((b) => b.enter), [bars])
const changeHandler = (row, value) => {
let request;
let newState;
if (!all) {
if (value && enterExist) {
//todo: добавить переключение
createError("Нельзя войти более чем в один бар одновременно")
return;
}
request = api().patch(`${requests.bar.enter}${row.id}&value=${value}`);
newState = bars.map((b) => {
if (b.id !== row.id) {
return b;
}
return {
...b,
enter: value
}
})
} else {
request = api().post(requests.bar.addToMyList, row);
newState = bars.filter((b) => b.id !== row.id);
}
request.then(() => {
setBars(newState)
refresh();
}).catch(() => getError())
}
return (
<Box mt={2}>
{
bars.map((row) => {
return (
<BarItem key={row.id} row={row} changeHandler={changeHandler}
all={all} enterExist={enterExist}/>
)
})
}
{/*Загрузчик*/}
<Loading loading={loading}/>
</Box>
)
}

View File

@@ -13,6 +13,7 @@ import LocalBarIcon from '@mui/icons-material/LocalBar';
import {paths} from "../../path"; import {paths} from "../../path";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import {useUser} from "../../hooks/useUser"; import {useUser} from "../../hooks/useUser";
import {cocktailClient} from "../../lib/clients/CocktailClient";
function renderFavouriteBadge(handleFavourite, row) { function renderFavouriteBadge(handleFavourite, row) {
const childIcon = row.rating.favourite ? <FavoriteIcon/> : <FavoriteBorderIcon/>; const childIcon = row.rating.favourite ? <FavoriteIcon/> : <FavoriteBorderIcon/>;
@@ -35,7 +36,7 @@ function renderRating(handleChangeRating, row) {
} }
export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler}) { export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler}) {
const {notImplement} = useAlert(); const {createError, createSuccess} = useAlert();
const {user} = useUser(); const {user} = useUser();
return ( return (
<Grid item sx={{pr: 2}}> <Grid item sx={{pr: 2}}>
@@ -59,13 +60,15 @@ export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect
image={row.image.includes("thecocktaildb") ? (row.image + "/preview") : row.image} image={row.image.includes("thecocktaildb") ? (row.image + "/preview") : row.image}
/> />
<CardActions> <CardActions>
<IconButton sx={{m: 0}} size='small' onClick={() => notImplement()}> <IconButton sx={{m: 0}} size='small'
onClick={() => cocktailClient.drinkCocktail(row.id, createSuccess, createError)}>
<LocalBarIcon fontSize='small'/> <LocalBarIcon fontSize='small'/>
</IconButton> </IconButton>
{renderFavouriteBadge(handleFavourite, row)} {renderFavouriteBadge(handleFavourite, row)}
{renderRating(handleChangeRating, row)} {renderRating(handleChangeRating, row)}
{user.role !== 'USER' && {
(user.role && user.role !== 'USER') &&
<> <>
<IconButton size='small' href={`${paths.bar.cocktailEdit}?id=${row.id}`}> <IconButton size='small' href={`${paths.bar.cocktailEdit}?id=${row.id}`}>
<EditIcon fontSize='small'/> <EditIcon fontSize='small'/>

View File

@@ -15,13 +15,13 @@ import IconButton from "@mui/material/IconButton";
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import {IngredientInfoModal} from "../Ingredients/IngredientInfoModal"; import {IngredientInfoModal} from "../Ingredients/IngredientInfoModal";
import {api} from "../../lib/clients/api";
import {requests} from "../../requests";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import {paths} from "../../path"; import {paths} from "../../path";
import {Loading} from "../core/Loading"; import {Loading} from "../core/Loading";
import {useUser} from "../../hooks/useUser"; import {useUser} from "../../hooks/useUser";
import {useSelect} from "../../hooks/useSelect"; import {useSelect} from "../../hooks/useSelect";
import {cocktailClient} from "../../lib/clients/CocktailClient";
import {ingredientClient} from "../../lib/clients/IngredientClient";
export function CocktailInfoModal({row}) { export function CocktailInfoModal({row}) {
const {user} = useUser(); const {user} = useUser();
@@ -29,56 +29,8 @@ export function CocktailInfoModal({row}) {
const [cocktail, setCocktail] = useState(null) const [cocktail, setCocktail] = useState(null)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const {closeCocktail, selectIngredient, getIngredient, getOpenCocktail} = useSelect(); const {closeCocktail, selectIngredient, getIngredient, getOpenCocktail} = useSelect();
const openIngredientModalHandler = (id) => {
api().get(`${requests.bar.ingredient}?id=${id}`)
.then((r) => {
selectIngredient(r.data)
})
.catch(() => createError("Ошибка получения информации об ингредиенте"))
}
const selectIngredientHandler = (ingredient) => {
const url = `${requests.bar.ingredient}?id=${ingredient.id}`;
const request = ingredient.isHave ? api().delete(url) : api().put(url);
const value = !ingredient.isHave;
request.then(() => {
const newReceipts = cocktail.receipt.map((r) => {
if (r.ingredient.id !== ingredient.id) {
return r;
}
return {
...r,
ingredient: {
...ingredient,
isHave: value
}
}
})
setCocktail({
...cocktail,
receipt: newReceipts
})
createSuccess("Сохранено")
}).catch(() => createError("Ошибка сохранения"))
}
useEffect(() => { useEffect(() => cocktailClient.getCocktailForModal(row, setLoading, setCocktail, closeCocktail, getError), [row]);
setLoading(true)
if (!row) {
setLoading(false)
return;
}
api().get(requests.cocktails.modal + row)
.then((r) => {
setCocktail(r.data)
setLoading(false)
})
.catch(() => {
getError();
setLoading(false)
closeCocktail();
})
// eslint-disable-next-line
}, [row]);
if (!row || !cocktail) { if (!row || !cocktail) {
return null; return null;
@@ -132,9 +84,9 @@ export function CocktailInfoModal({row}) {
<Stack key={r.ingredient.id} direction='row' justifyContent={'space-between'} <Stack key={r.ingredient.id} direction='row' justifyContent={'space-between'}
mt={1}> mt={1}>
<Stack direction='row'> <Stack direction='row'>
{user.role !== "USER" && ( {(user.role && user.role !== "USER") && (
<IconButton size="small" sx={{pb: "2px"}} <IconButton size="small" sx={{pb: "2px"}}
onClick={() => selectIngredientHandler(r.ingredient)}> onClick={() => ingredientClient.changeIngredientInBar(r.ingredient, cocktail, setCocktail, createSuccess, createError)}>
{r.ingredient.isHave {r.ingredient.isHave
? (<DeleteIcon fontSize="small"/>) ? (<DeleteIcon fontSize="small"/>)
: (<ShoppingCartIcon fontSize="small"/>) : (<ShoppingCartIcon fontSize="small"/>)
@@ -142,7 +94,7 @@ export function CocktailInfoModal({row}) {
</IconButton> </IconButton>
)} )}
<Typography <Typography
onClick={() => openIngredientModalHandler(r.ingredient.id)}>{r.ingredient.name}</Typography> onClick={() => ingredientClient.findOne(r.ingredient.id, selectIngredient, createError)}>{r.ingredient.name}</Typography>
</Stack> </Stack>
<Typography color={hasError && 'red'}>{measure}</Typography> <Typography color={hasError && 'red'}>{measure}</Typography>
</Stack> </Stack>
@@ -161,7 +113,7 @@ export function CocktailInfoModal({row}) {
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
{user.role.includes("ADMIN") && ( {(user.role && user.role.includes("ADMIN")) && (
<Button href={`${paths.bar.cocktailEdit}?id=${cocktail.id}`}>Редактировать</Button> <Button href={`${paths.bar.cocktailEdit}?id=${cocktail.id}`}>Редактировать</Button>
)} )}
<Button onClick={closeCocktail}>Закрыть</Button> <Button onClick={closeCocktail}>Закрыть</Button>

View File

@@ -6,13 +6,11 @@ import AddIcon from "@mui/icons-material/Add";
import * as React from "react"; import * as React from "react";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import {api} from "../../lib/clients/api";
import {requests} from "../../requests";
import {getComparator} from "../core/getComparator";
import {Card} from "@mui/material"; import {Card} from "@mui/material";
import {SelectEdit} from "./SelectEdit"; import {SelectEdit} from "./SelectEdit";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import {ingredientClient} from "../../lib/clients/IngredientClient";
export function EditCocktailReceipt({receipt, handler}) { export function EditCocktailReceipt({receipt, handler}) {
const {createError} = useAlert() const {createError} = useAlert()
@@ -20,13 +18,9 @@ export function EditCocktailReceipt({receipt, handler}) {
const [units, setUnits] = useState([]) const [units, setUnits] = useState([])
useEffect(() => { useEffect(() => {
api().get(requests.bar.ingredientList) ingredientClient.findAll(setIngredients, createError)
.then((r) => setIngredients(r.data.sort(getComparator("asc", "name")))) ingredientClient.findUnit(setUnits, createError)
.catch(() => createError("Ошибка получения списка ингредиентов")) // eslint-disable-next-line
api().get(requests.bar.unit)
.then((r) => setUnits(r.data.sort(getComparator("asc", "name"))))
.catch(() => createError("Ошибка получения единиц измерения"))
}, []); }, []);
const selectHandler = (name, value) => { const selectHandler = (name, value) => {
@@ -152,7 +146,8 @@ export function EditCocktailReceipt({receipt, handler}) {
value={r.count} value={r.count}
onChange={(e) => countHandler(i, e.target.value)} onChange={(e) => countHandler(i, e.target.value)}
/> />
<SelectEdit width={'calc(65% - 28px)'} array={units} value={!r.unit ? null : r.unit.name} <SelectEdit width={'calc(65% - 28px)'} array={units}
value={!r.unit ? null : r.unit.name}
handler={unitHandler} label={"Ед."} handler={unitHandler} label={"Ед."}
margin={1} attributeName={i} margin={1} attributeName={i}
/> />

View File

@@ -10,27 +10,21 @@ import CheckMarks from "./CheckMarks";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import * as React from "react"; import * as React from "react";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {requests} from "../../requests";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import {api} from "../../lib/clients/api"; import {categoryClient} from "../../lib/clients/CategoryClient";
import {sortList} from "./sortingList"; import {glassClient} from "../../lib/clients/GlassClient";
export function FilterBlock({filter, handleFilterChange, handleClearFilter, barmen}) { export function FilterBlock({filter, handleFilterChange, handleClearFilter, barmen}) {
const {createError} = useAlert(); const {createError} = useAlert();
const [glass, setGlass] = useState([]); const [glass, setGlass] = useState([]);
const [category, setCategory] = useState([]); const [category, setCategory] = useState([]);
const alcohol = ['Алкогольный', 'Безалкогольный']; const alcohol = ['Алкогольный', 'Безалкогольный'];
const ingredientCount = [1,2,3,4,5]; const ingredientCount = [1, 2, 3, 4, 5];
const sort = ['Название по возрастанию', 'Название по убыванию']; const sort = ['Название по возрастанию', 'Название по убыванию'];
useEffect(() => { useEffect(() => {
api().get(requests.bar.category) categoryClient.getCategoryList(setCategory, createError)
.then((r) => setCategory(r.data)) glassClient.getGlassList(setGlass, createError)
.catch(() => createError("Ошибка получения категорий"))
api().get(requests.bar.glass)
.then((r) => setGlass(r.data))
.catch(() => createError("Ошибка получения посуды"))
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
@@ -94,15 +88,15 @@ export function FilterBlock({filter, handleFilterChange, handleClearFilter, barm
{/*Фильтр по категории*/} {/*Фильтр по категории*/}
{category.length > 0 && ( {category.length > 0 && (
<CheckMarks rows={category} name={"Категории"} filterValue={filter.category} <CheckMarks rows={category} name={"Категории"} filterValue={filter.category}
filterName={"category"} handleChange={handleFilterChange} identity/>)} filterName={"category"} handleChange={handleFilterChange}/>)}
{/*Фильтр по посуде*/} {/*Фильтр по посуде*/}
{glass.length > 0 && (<CheckMarks rows={glass} name={"Подача"} handleChange={handleFilterChange} {glass.length > 0 && (<CheckMarks rows={glass} name={"Подача"} handleChange={handleFilterChange}
filterValue={filter.glass} filterName={"glass"} identity/>)} filterValue={filter.glass} filterName={"glass"}/>)}
{/*Фильтр по нехватке ингредиентов*/} {/*Фильтр по нехватке ингредиентов*/}
{(barmen && filter.all) && (<CheckMarks rows={ingredientCount} name={"Не хватает ингредиентов"} {(barmen && filter.all) && (<CheckMarks rows={ingredientCount} name={"Не хватает ингредиентов"}
handleChange={handleFilterChange} handleChange={handleFilterChange}
nonMulti nullValue identity nonMulti nullValue identity
filterValue={filter.iCount} filterName={"iCount"}/>)} filterValue={filter.iCount} filterName={"iCount"}/>)}
<Button onClick={() => handleClearFilter()}>Сбросить</Button> <Button onClick={() => handleClearFilter()}>Сбросить</Button>
</Grid> </Grid>
</Box> </Box>

View File

@@ -4,6 +4,7 @@ import MenuItem from "@mui/material/MenuItem";
import * as React from "react"; import * as React from "react";
export function SelectEdit({label, value, array, handler, attributeName, width, margin}) { export function SelectEdit({label, value, array, handler, attributeName, width, margin}) {
return ( return (
<FormControl sx={{width: width, m: margin}}> <FormControl sx={{width: width, m: margin}}>
<InputLabel>{label}</InputLabel> <InputLabel>{label}</InputLabel>

View File

@@ -7,11 +7,13 @@ import MenuList from '@mui/material/MenuList';
import Popover from '@mui/material/Popover'; import Popover from '@mui/material/Popover';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import {SignOut as SignOutIcon} from '@phosphor-icons/react/dist/ssr/SignOut'; import {SignOut as SignOutIcon} from '@phosphor-icons/react/dist/ssr/SignOut';
import {SignIn as SignInIcon} from '@phosphor-icons/react/dist/ssr/SignIn';
import {logger} from "../../lib/DefaultLogger"; import {logger} from "../../lib/DefaultLogger";
import {useAuth} from "../../hooks/useAuth"; import {useAuth} from "../../hooks/useAuth";
import {authClient} from "../../lib/clients/AuthClient"; import {authClient} from "../../lib/clients/AuthClient";
import {useLocation} from "react-router-dom"; import {useLocation} from "react-router-dom";
import {useUser} from "../../hooks/useUser"; import {useUser} from "../../hooks/useUser";
import {paths} from "../../path";
export function UserPopover({anchorEl, onClose, open}) { export function UserPopover({anchorEl, onClose, open}) {
const {checkSession} = useAuth(); const {checkSession} = useAuth();
@@ -37,6 +39,7 @@ export function UserPopover({anchorEl, onClose, open}) {
} catch (err) { } catch (err) {
logger.error('Sign out error', err); logger.error('Sign out error', err);
} }
// eslint-disable-next-line
}, [checkSession, location]); }, [checkSession, location]);
return ( return (
@@ -64,24 +67,31 @@ export function UserPopover({anchorEl, onClose, open}) {
{/* </ListItemIcon>*/} {/* </ListItemIcon>*/}
{/* Профиль*/} {/* Профиль*/}
{/*</MenuItem>*/} {/*</MenuItem>*/}
<MenuItem onClick={handleSignOut}> {!user.name ? <MenuItem onClick={() => window.location.replace(paths.auth.signIn)}>
<ListItemIcon> <ListItemIcon>
<SignOutIcon fontSize="var(--icon-fontSize-md)"/> <SignInIcon fontSize="var(--icon-fontSize-md)"/>
</ListItemIcon> </ListItemIcon>
Выход Вход
</MenuItem> </MenuItem> :
<MenuItem onClick={handleSignOut}>
<ListItemIcon>
<SignOutIcon fontSize="var(--icon-fontSize-md)"/>
</ListItemIcon>
Выход
</MenuItem>
}
</MenuList> </MenuList>
</Popover> </Popover>
); );
} }
function userDescriptor(user, session) { function userDescriptor(user) {
if (!user) { if (!user) {
return (<Typography variant="subtitle1">Ошибка загрузки данных</Typography>); return null;
}
if (!user.name) {
return (<Typography variant="subtitle1">Гость</Typography>);
} }
const open = (session.isActive && user.invited) ? "открыт" : "закрыт";
return ( return (
<> <>
<Typography variant="subtitle1">{user.name + " " + user.lastName}</Typography> <Typography variant="subtitle1">{user.name + " " + user.lastName}</Typography>

View File

@@ -14,7 +14,8 @@ import {
Martini, Martini,
Storefront, Storefront,
Users, Users,
Wallet Wallet,
Calculator
} from "@phosphor-icons/react"; } from "@phosphor-icons/react";
export const navIcons = { export const navIcons = {
@@ -31,6 +32,7 @@ export const navIcons = {
'chart-pie': ChartPieIcon, 'chart-pie': ChartPieIcon,
'gear-six': GearSixIcon, 'gear-six': GearSixIcon,
'plugs-connected': PlugsConnectedIcon, 'plugs-connected': PlugsConnectedIcon,
'calc': Calculator,
'x-square': XSquare, 'x-square': XSquare,
user: UserIcon, user: UserIcon,
users: UsersIcon, users: UsersIcon,

View File

@@ -1,5 +1,5 @@
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import {isNavItemActive} from "../../lib/isNavItemActive"; import {isNavItemActive} from "./isNavItemActive";
import {navIcons} from "../core/navIcons"; import {navIcons} from "../core/navIcons";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";

View File

@@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import {useCallback, useEffect} from 'react'; import {useCallback, useEffect} from 'react';
import {logger} from "../lib/DefaultLogger"; import {logger} from "../lib/DefaultLogger";
import {tokenUtil} from "../lib/TokenUtil"; import {tokenUtil} from "../lib/clients/TokenUtil";
export const AuthContext = React.createContext(undefined); export const AuthContext = React.createContext(undefined);

View File

@@ -1,10 +1,8 @@
import * as React from "react"; import * as React from "react";
import {createContext, useCallback, useEffect, useState} from "react";
import {logger} from "../lib/DefaultLogger"; import {logger} from "../lib/DefaultLogger";
import {userClient} from "../lib/clients/UserClient"; import {userClient} from "../lib/clients/UserClient";
import {tokenUtil} from "../lib/TokenUtil"; import {tokenUtil} from "../lib/clients/TokenUtil";
import {createContext, useCallback, useEffect, useState} from "react";
import {api} from "../lib/clients/api";
import {requests} from "../requests";
export const UserContext = createContext(undefined); export const UserContext = createContext(undefined);
@@ -24,19 +22,10 @@ export function UserProvider({children}) {
const checkSession = useCallback(async () => { const checkSession = useCallback(async () => {
try { try {
setState((prev) => ({...prev, isLoading: true})); setState((prev) => ({...prev, isLoading: true}));
if (!await tokenUtil.checkToken(tokenUtil.getToken())) { if (!tokenUtil.checkToken(tokenUtil.getToken())) {
setState((prev) => ({...prev, error: '', isLoading: false, user: {}})); setState((prev) => ({...prev, error: '', isLoading: false, user: {}}));
return; return;
} }
api().get(requests.bar.session.status)
.then((r) => setState((prevState) => ({
...prevState,
session: r.data
})))
.catch(() => setState((prevState) => ({
...prevState,
session: {}
})))
if (Object.keys(state.user).length === 0) { if (Object.keys(state.user).length === 0) {
const {data, errorData} = await userClient.getMe(); const {data, errorData} = await userClient.getMe();
if (errorData) { if (errorData) {

View File

@@ -1,9 +1,45 @@
import {api} from "./api";
import {requests} from "../../requests";
class AuthClient { class AuthClient {
async signOut() { async signOut() {
localStorage.removeItem("token"); localStorage.removeItem("token");
return {}; return {};
} }
async login(request, setLoading, setError, checkSession) {
setLoading(true);
const response = await api().post(requests.auth.login, request);
if (response.data.error) {
setError(response.data.error);
setLoading(false);
return;
}
localStorage.setItem("token", response.data.token);
await checkSession?.();
window.location.reload();
}
loginByCode(code, checkSession) {
const request = {
byLogin: false,
code: code
}
api().post(requests.auth.login, request)
.then(async (response) => {
if (response.data.error) {
return;
}
localStorage.setItem("token", response.data.token);
await checkSession?.();
window.location.reload();
})
}
} }
export const authClient = new AuthClient(); export const authClient = new AuthClient();

View File

@@ -0,0 +1,74 @@
import {api} from "./api";
import {requests} from "../../requests";
import {getComparator} from "../../components/core/getComparator";
class BarClient {
getBarList(setBars, createError) {
api().get(requests.bar.all)
.then((r) => {
setBars(r.data.sort(getComparator("name")))
})
.catch(() => {
createError("Ошибка получения списков")
})
}
changeBar(id, bars, createWarning, createSuccess, createError, setBars) {
createWarning("Дождитесь окончания операции")
api().post(`${requests.bar.change}/${id}`)
.then(() => createSuccess("Список изменен"))
.catch(() => createError("Ошибка изменения активного списка"))
const newState = bars.map((b) => {
if (b.active) {
return {
...b, active: false
}
}
if (b.id === id) {
return {
...b, active: true
}
}
return b;
})
setBars(newState);
}
deleteBar(bar, bars, createError, createSuccess, setBars) {
if (bar.active) {
createError("Нельзя удалить активный бар!")
return;
}
api().delete(requests.bar.crud + bar.id)
.then(() => createSuccess("Список удален"))
.catch(() => createError("Ошибка удаления. Обновите страницу"))
setBars(bars.filter((b) => b.id !== bar.id));
}
createBar(name, bars, createSuccess, createError, setBars, setOpen) {
api().post(requests.bar.crud + name)
.then((r) => {
createSuccess("Cписок создан");
let state = bars;
state.push(r.data);
setBars(state)
setOpen(false)
}).catch(() => createError("Ошибка создания списка"))
}
copyBar(oldId, newName, setBars, bars, createError, createSuccess, setOpen) {
api().post(requests.bar.crud + "copy/" + oldId + "/" + newName)
.then((r) => {
const state = bars;
state.push(r.data)
setBars(state);
createSuccess("Бар скопирован")
setOpen(false)
}).catch(() => createError("Ошибка при копировании бара"))
}
}
export const barClient = new BarClient();

View File

@@ -0,0 +1,19 @@
import {api} from "./api";
import {requests} from "../../requests";
import {getComparator} from "../../components/core/getComparator";
class CategoryClient {
getCategoryList(setCategory, createError) {
api().get(requests.category.basic)
.then((r) => {
setCategory(r.data.sort(getComparator())
.map((item, i) => {
return {id: i, name: item}
}))
})
.catch(() => createError("Ошибка получения категорий"))
}
};
export const categoryClient = new CategoryClient();

View File

@@ -0,0 +1,212 @@
import {api} from "./api";
import {requests} from "../../requests";
import {sortList} from "../../components/cocktails/sortingList";
import {getComparator} from "../../components/core/getComparator";
class CocktailClient {
emptyCocktailForEditPage = {
id: null,
name: "",
alcoholic: "",
category: "",
components: "",
glass: "",
image: "",
instructions: "",
isAllowed: false,
rating: {
rating: 0,
favourite: false
},
receipt: [],
tags: "",
video: ""
}
getMenu(setRows, setIsNew, setPage, setLoad, setIsEnd, isNew, rows, page, size, filter, createError) {
setLoad(true);
const request = {
...filter,
sort: sortList.find((s) => s.name === filter.sorting).id,
page: page + 1,
size: size,
notHaveCount: Array.isArray(filter.iCount) ? null : filter.iCount
}
api().post(requests.cocktails.menu, request)
.then((r) => {
if (r.data.length === 0) {
if (isNew) {
setRows([]);
}
setIsEnd(true);
setLoad(false);
return;
}
const cocktails = isNew ? r.data : rows.concat(r.data);
setRows(cocktails);
setIsNew(false);
setPage(page + 1);
setLoad(false);
})
.catch((r) => {
setLoad(false);
createError("Ошибка загрузки данных от сервера Status:" + r.code)
})
}
async getCocktailByIngredient(ingredient, setCocktails) {
if(!ingredient) {
return
}
api().get(requests.cocktails.byIngredient + ingredient.id)
.then((r) => setCocktails(r.data))
}
getCocktailsForCalcPage(load,setLoad, setCocktails, setCocktailMap, createError) {
if (load) {
return;
}
api().get(requests.cocktails.calc)
.then((r) => {
const data = r.data;
if (data.length === 0) {
setLoad(false);
return;
}
setCocktails(data);
let map = {};
data.forEach((d) => {
map = {
...map,
[d.id]: 1
}
})
setCocktailMap(map);
setLoad(true);
})
.catch((r) => {
setLoad(true);
createError("Ошибка загрузки данных от сервера Status:" + r.code)
})
}
savePhoto(event, changeCocktailValue, getError) {
const file = event.target.files[0];
let formData = new FormData();
formData.append('file', file);
api().post(requests.cocktails.photo, formData)
.then((r) => changeCocktailValue("image", r.data))
.catch(() => getError())
}
deleteCocktailFromEdit(setCocktails, setCocktail, createError, cocktails, cocktail, emptyCocktail) {
api().delete(requests.cocktails.cocktail + cocktail.id)
.then(() => {
setCocktails(cocktails.filter((r) => r.id !== cocktail.id))
setCocktail(emptyCocktail);
})
.catch(() => createError("Ошибка удаления коктейля"))
}
deleteCocktail(id, rows, setRows, createSuccess, createError) {
api().delete(requests.cocktails.cocktail + id)
.then(() => {
setRows(rows.filter((r) => r.id !== id))
createSuccess("Коктейль удален")
})
.catch(() => createError("Ошибка удаления коктейля"))
}
saveChangeCocktail (cocktail, createError, createSuccess) {
api().patch(requests.cocktails.basic, cocktail)
.then((r) => {
if (!r.data.error) {
createSuccess("Сохранено")
return;
}
createError("Ошибка на сервере: " + r.data.error)
})
.catch(() => createError("Неизвестная ошибка"))
}
getOneCocktail (selected, setCocktail, getError, emptyCocktail) {
if (!selected) {
setCocktail(emptyCocktail);
return;
}
api().get(requests.cocktails.cocktail + selected)
.then((r) => {
setCocktail(r.data)
})
.catch(() => getError());
}
getSimpleList(setCocktails, setSelected, setLoading, createError, currentId) {
api().get(requests.cocktails.simple)
.then((r) => {
const arr = r.data.sort(getComparator("asc", "name"));
setCocktails(arr)
if (!currentId) {
setLoading(false);
return;
}
const currentCocktail = arr.find((r) => r.id === (currentId * 1));
if (!currentCocktail) {
setLoading(false);
return;
}
setSelected(currentCocktail.id);
setLoading(false);
})
.catch(() => createError("Ошибка получения данных"))
}
getCocktailForModal (row, setLoading, setCocktail, closeCocktail, getError) {
setLoading(true)
if (!row) {
setLoading(false)
return;
}
api().get(requests.cocktails.modal + row)
.then((r) => {
setCocktail(r.data)
setLoading(false)
})
.catch(() => {
getError();
setLoading(false)
closeCocktail();
})
}
changeFavourite(value, id, newState, setRows, createSuccess, createError) {
const url = `${requests.cocktails.favourite}${id}`;
const request = value ? api().put(url) : api().delete(url);
request
.then(() => {
setRows(newState);
createSuccess("Спасибо за оценку!")
}).catch(() => createError("Ошибка сохранения"))
}
changeRating(id, newState, value, setRows, createSuccess, createError) {
api().post(`${requests.cocktails.rating}${id}&rating=${value}`)
.then(() => {
setRows(newState);
createSuccess("Спасибо за оценку!")
}).catch(() => createError("Ошибка сохранения"))
}
drinkCocktail(id, createSuccess, createError) {
api().post(`${requests.cocktails.drink}/${id}`)
.then(() => createSuccess("Бон аппетит"))
.catch(() => createError("Ошибка отметки коктейля"))
}
}
export const cocktailClient = new CocktailClient();

View File

@@ -0,0 +1,17 @@
import {api} from "./api";
import {requests} from "../../requests";
import {getComparator} from "../../components/core/getComparator";
class GlassClient {
getGlassList(setGlass, createError) {
api().get(requests.glass.list)
.then((r) => setGlass(r.data.sort(getComparator())
.map((item, i) => {
return {id: i, name: item}
})))
.catch(() => createError("Ошибка получения посуды"))
}
}
export const glassClient = new GlassClient();

View File

@@ -0,0 +1,112 @@
import {api} from "./api";
import {requests} from "../../requests";
import {getComparator} from "../../components/core/getComparator";
class IngredientClient {
allList(currentId, setIngredients, setIngredient, createError) {
api().get(requests.ingredient.all)
.then((r) => {
const arr = r.data.sort(getComparator("asc", "name"));
setIngredients(arr)
if (!currentId) {
return;
}
const currentIngredient = arr.find((r) => r.id === (currentId * 1));
if (!currentIngredient) {
return;
}
setIngredient(currentIngredient);
})
.catch(() => createError("Ошибка получения данных"))
}
getType(setTypes) {
api().get(requests.ingredient.type)
.then((r) => setTypes(r.data.sort(getComparator("asc", "name"))))
}
findAll(setIngredients, createError) {
api().get(requests.ingredient.all)
.then((r) => setIngredients(r.data.sort(getComparator("asc", "name"))))
.catch(() => createError("Ошибка получения списка ингредиентов"))
}
getAllIngredients(setIngredients, setLoading, createError) {
api().get(requests.ingredient.all)
.then((r) => {
setIngredients(r.data)
setLoading(false);
})
.catch(() => {
createError("Ошибка получения списка ингредиентов");
setLoading(false);
})
}
changeIngredientIsHave(id, value, state, setIngredients, createError) {
const url = `${requests.ingredient.crud}?id=${id}`;
const request = value ? api().put(url) : api().delete(url);
request
.then(() => {
setIngredients(state);
})
.catch(() => {
createError("Ошибка изменения ингредиента");
});
}
deleteIngredientIsHave(id, createSuccess, createError) {
api().delete(`${requests.ingredient.crud}/${id}`)
.then(() => createSuccess("Ингредиент удален"))
.catch(() => createError("Ошибка удаления ингредиента. Перезагрузите страницу"))
}
saveIngredient(ingredient, createSuccess, createError) {
api().patch(requests.ingredient.crud, ingredient)
.then(() => createSuccess("Ингредиент сохранен"))
.catch(() => createError("Ошибка сохранения"))
}
findOne(id, selectIngredient, createError) {
api().get(`${requests.bar.ingredient}?id=${id}`)
.then((r) => {
selectIngredient(r.data)
})
.catch(() => createError("Ошибка получения информации об ингредиенте"))
}
changeIngredientInBar(ingredient, cocktail, setCocktail, createSuccess, createError) {
const url = `${requests.ingredient.crud}?id=${ingredient.id}`;
const request = ingredient.isHave ? api().delete(url) : api().put(url);
const value = !ingredient.isHave;
request.then(() => {
const newReceipts = cocktail.receipt.map((r) => {
if (r.ingredient.id !== ingredient.id) {
return r;
}
return {
...r,
ingredient: {
...ingredient,
isHave: value
}
}
})
setCocktail({
...cocktail,
receipt: newReceipts
})
createSuccess("Сохранено")
}).catch(() => createError("Ошибка сохранения"))
}
findUnit(setUnits, createError) {
api().get(requests.unit)
.then((r) => setUnits(r.data.sort(getComparator("asc", "name"))))
.catch(() => createError("Ошибка получения единиц измерения"))
}
}
export const ingredientClient = new IngredientClient()

View File

@@ -1,5 +1,5 @@
import {decodeToken, isExpired} from "react-jwt"; import {decodeToken, isExpired} from "react-jwt";
import {requests} from "../requests"; import {requests} from "../../requests";
import axios from "axios"; import axios from "axios";
class TokenUtil { class TokenUtil {

View File

@@ -5,7 +5,7 @@ class UserClient {
async getMe() { async getMe() {
try{ try{
let url = requests.users.getMe let url = requests.auth.getMe
const response = await api().get(url); const response = await api().get(url);
return {data: response.data} return {data: response.data}
} catch (e) { } catch (e) {

View File

@@ -1,9 +1,9 @@
import axios from "axios"; import axios from "axios";
import {tokenUtil} from "../TokenUtil"; import {tokenUtil} from "./TokenUtil";
const host = "localhost:8080"; //дебаг вместе с беком // const host = "localhost:8080"; //дебаг вместе с беком
// const host = "192.168.1.100:8091"; //дебаг фронта // const host = "192.168.1.100:8091"; //дебаг фронта
// const host = "bar.kayashov.keenetic.pro"; //прод const host = "bar.kayashov.keenetic.pro"; //прод
export const api = () => { export const api = () => {
const result = axios; const result = axios;
result.defaults.baseURL = `${window.location.protocol}//${host}/`; result.defaults.baseURL = `${window.location.protocol}//${host}/`;

View File

@@ -1,18 +0,0 @@
export const sliceData = (rows, page, elementOnPage) => {
if (!rows || rows.length === 0) {
return [];
}
const maxPage = Math.ceil(rows.length / elementOnPage);
// const start = (page - 1) * elementOnPage;
const start = 0;
console.log(maxPage, start)
let end;
if (page === maxPage) {
end = rows.length;
} else {
end = start + elementOnPage;
}
return rows.slice(start, end);
}

View File

@@ -5,5 +5,6 @@ export const navItems = [
{key: 'barList', title: 'Список баров', href: paths.bar.list, icon: 'basket', forBarmen: true}, {key: 'barList', title: 'Список баров', href: paths.bar.list, icon: 'basket', forBarmen: true},
{key: 'ingredients', title: 'Список ингредиентов', href: paths.bar.ingredients, icon: 'basket', forBarmen: true}, {key: 'ingredients', title: 'Список ингредиентов', href: paths.bar.ingredients, icon: 'basket', forBarmen: true},
{key: 'ingredientEdit', title: 'Ингредиенты', href: paths.bar.ingredientEdit, icon: 'ingredients', forAdmin: true}, {key: 'ingredientEdit', title: 'Ингредиенты', href: paths.bar.ingredientEdit, icon: 'ingredients', forAdmin: true},
{key: 'cocktailEdit', title: 'Коктейли', href: paths.bar.cocktailEdit, icon: 'cocktail', forAdmin: true} {key: 'cocktailEdit', title: 'Коктейли', href: paths.bar.cocktailEdit, icon: 'cocktail', forAdmin: true},
{key: 'calc', title: 'Калькулятор', href: paths.bar.calc, icon: 'calc', forAdmin: true},
]; ];

View File

@@ -17,7 +17,8 @@ export const paths = {
cocktails: "/cocktails", cocktails: "/cocktails",
ingredientEdit: '/ingredients/edit', ingredientEdit: '/ingredients/edit',
cocktailEdit: '/cocktail/edit', cocktailEdit: '/cocktail/edit',
menu: '/menuList' menu: '/menuList',
calc: '/calc',
}, },
errors: {notFound: '/errors/not-found'}, errors: {notFound: '/errors/not-found'},
notFound: '*', notFound: '*',

View File

@@ -2,70 +2,51 @@ const host = "api/";
const routes = { const routes = {
auth: host + "auth/", auth: host + "auth/",
users: host + "users/",
operations: host + "operations/",
bar: host + "bar/", bar: host + "bar/",
session: host + "bar/session", category: host + "category",
cocktail: host + "cocktail",
glass: host + "glass",
ingredient: host + "ingredient", ingredient: host + "ingredient",
order: host + "order", receipt: host + "receipt",
cocktails: host + "cocktail", unit: host + "unit",
visitor: host + "visitors"
} }
export const requests = { export const requests = {
auth: { auth: {
login: routes.auth + "login", login: routes.auth + "login",
refresh: routes.auth + "refresh", refresh: routes.auth + "refresh",
singOut: "signOut" singOut: "signOut",
}, getMe: routes.auth + "getMe",
cocktails: {
menu: routes.cocktails + "/menu",
simple: routes.cocktails + "/simple",
cocktail: routes.cocktails + "?id=",
modal: routes.cocktails + "/modal?id=",
edit: routes.cocktails,
savePhoto: routes.cocktails + "/photo",
favourite: routes.cocktails + "/favourite?id=",
rating: routes.cocktails + "/rating?id=",
receipts: routes.cocktails + "/receipts?id=",
byIngredient: routes.cocktails + "/byIngredient?id=",
instructions: routes.cocktails + "/instructions?id="
},
visitors: {
all: routes.visitor,
invite: routes.visitor + "/invite?"
}, },
bar: { bar: {
list: routes.bar + "all",
change: routes.bar + "change",
crud: routes.bar, crud: routes.bar,
addToMyList: routes.bar + "addToMyList", all: routes.bar + "all",
enter: routes.bar + "enter?id=", change: routes.bar + "change",
pay: routes.order + "?", },
order: routes.order, category: {
myOrders: routes.order + "/my", basic: routes.category,
purchases: routes.bar + "purchases", },
menu: routes.bar + "menu", cocktails: {
ingredients: routes.ingredient, menu: routes.cocktail + "/menu",
ingredientSimple: routes.ingredient + "/simple", calc: routes.cocktail + "/calc",
ingredient: routes.ingredient, byIngredient: routes.cocktail + "/byIngredient?id=",
ingredientList: routes.ingredient + "/all", photo: routes.cocktail + "/photo",
glass: routes.bar + "glass", cocktail: routes.cocktail + "?id=",
category: routes.bar + "category", basic: routes.cocktail,
receipts: routes.bar + "receipt?id=", simple: routes.cocktail + "/simple",
tags: routes.bar + "tags", modal: routes.cocktail + "/modal?id=",
favourite: routes.cocktail + "/favourite?id=",
rating: routes.cocktail + "/rating?id=",
drink: routes.cocktail + "/drink",
},
glass: {
list: routes.glass,
},
ingredient: {
all: routes.ingredient + "/all",
simple: routes.ingredient + "/simple",
type: routes.ingredient + "/type", type: routes.ingredient + "/type",
session: { crud: routes.ingredient,
status: routes.session + "/info",
change: routes.session
},
unit: routes.bar + "units"
}, },
users: { unit: routes.unit + "/units"
getMe: routes.bar + "getMe",
},
operations: {
getAll: routes.operations,
create: routes.operations,
}
} }

View File

@@ -4,6 +4,7 @@ import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
@@ -11,11 +12,18 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import ru.kayashov.bar.controller.dto.AuthRequestDto; import ru.kayashov.bar.controller.dto.AuthRequestDto;
import ru.kayashov.bar.controller.dto.AuthResponseDto; import ru.kayashov.bar.controller.dto.AuthResponseDto;
import ru.kayashov.bar.controller.dto.VisitorResponseDto;
import ru.kayashov.bar.model.entity.Event;
import ru.kayashov.bar.model.entity.EventType;
import ru.kayashov.bar.model.entity.Visitor; import ru.kayashov.bar.model.entity.Visitor;
import ru.kayashov.bar.repository.VisitorsRepository; import ru.kayashov.bar.repository.VisitorsRepository;
import ru.kayashov.bar.security.JwtTokenProvider; import ru.kayashov.bar.security.JwtTokenProvider;
import ru.kayashov.bar.service.EventService;
import ru.kayashov.bar.service.VisitorService;
import javax.annotation.security.PermitAll;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Base64; import java.util.Base64;
import java.util.Optional; import java.util.Optional;
@@ -28,53 +36,47 @@ public class AuthController {
private final JwtTokenProvider jwtTokenProvider; private final JwtTokenProvider jwtTokenProvider;
private final VisitorsRepository visitorsRepository; private final VisitorsRepository visitorsRepository;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final VisitorService visitorService;
private final EventService eventService;
@PermitAll
@PostMapping("/login") @PostMapping("/login")
public AuthResponseDto checkTelegramChat(@RequestBody AuthRequestDto dto) { public AuthResponseDto checkTelegramChat(@RequestBody AuthRequestDto dto) {
AuthResponseDto responseDto;
Visitor visitor = null;
if (dto.getByLogin()) { if (dto.getByLogin()) {
return checkLogin(dto.getLogin(), dto.getPassword()); String login = dto.getLogin();
String password = dto.getPassword();
if (login == null || login.isEmpty() || password == null || password.isEmpty()) {
return new AuthResponseDto(null, "Поля не могут быть пустые");
}
Optional<Visitor> visitorOpt = visitorsRepository.findByLogin(login);
if (visitorOpt.isEmpty()) {
responseDto = new AuthResponseDto(null, "Не найдет пользователь " + login);
} else {
visitor = visitorOpt.get();
responseDto = checkLogin(visitor, dto.getPassword());
}
} else { } else {
return parseCode(dto.getCode()); String decode = new String(Base64.getDecoder().decode(dto.getCode()), StandardCharsets.UTF_8);
} String[] decodeArr = decode.split(":");
} visitor = visitorsRepository.findById(Long.valueOf(decodeArr[0]))
.orElseThrow();
private AuthResponseDto parseCode(String code) { responseDto = parseCode(Integer.valueOf(decodeArr[1]), visitor);
String decode = new String(Base64.getDecoder().decode(code), StandardCharsets.UTF_8);
String[] decodeArr = decode.split(":");
Visitor visitor = visitorsRepository.findById(Long.valueOf(decodeArr[0]))
.orElseThrow();
log.info("Попытка авторизации пользователя {}", visitor.getId());
Integer visitorCode = visitor.getCode();
if (visitorCode == null) {
return new AuthResponseDto(null, "Повторите запрос кода из бота");
}
if (Integer.valueOf(decodeArr[1]).equals(visitor.getCode())) {
visitor.setCode(null);
visitor = visitorsRepository.save(visitor);
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
}
return new AuthResponseDto(null, "Неверный код подтверждения");
}
private AuthResponseDto checkLogin(String login, String password) {
if (login == null || login.isEmpty() || password == null || password.isEmpty()) {
return new AuthResponseDto(null, "Поля не могут быть пустые");
}
Optional<Visitor> visitorOpt = visitorsRepository.findByLogin(login);
if (visitorOpt.isEmpty()) {
return new AuthResponseDto(null, "Не найдет пользователь " + login);
}
Visitor visitor = visitorOpt.get();
log.info("Попытка авторизации пользователя {}", visitor.getId());
if (passwordEncoder.matches(password, visitor.getPassword())) {
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
} else {
return new AuthResponseDto(null, "Неверный логин или пароль");
} }
eventService.createEvent(Event.builder()
.date(LocalDateTime.now())
.type(EventType.LOGIN)
.newState(visitor != null ? visitor.getName() : null)
.oldState(responseDto.getError() != null ? responseDto.getError() : "Выполнен вход")
.build());
return responseDto;
} }
@PermitAll
@PostMapping("refresh") @PostMapping("refresh")
public AuthResponseDto refreshToken(@RequestHeader("Authorization") String token) { public AuthResponseDto refreshToken(@RequestHeader("Authorization") String token) {
Claims claims = jwtTokenProvider.extractAllClaims(token); Claims claims = jwtTokenProvider.extractAllClaims(token);
@@ -84,4 +86,38 @@ public class AuthController {
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null); return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
} }
@GetMapping("/getMe")
public VisitorResponseDto getMe() {
Visitor visitor = visitorService.getCurrentVisitor();
VisitorResponseDto dto = VisitorResponseDto.mapToDto(visitor, true, visitor.getRole().toString(), true);
log.info("Запрос информации о пользователе: {}-{} {}, вошел в бар",
dto.getId(),
dto.getName().strip(),
dto.getLastName() != null ? dto.getLastName().strip() : "");
return dto;
}
private AuthResponseDto checkLogin(Visitor visitor, String password) {
log.info("Попытка авторизации пользователя {}", visitor.getId());
if (passwordEncoder.matches(password, visitor.getPassword())) {
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
} else {
return new AuthResponseDto(null, "Неверный логин или пароль");
}
}
private AuthResponseDto parseCode(Integer code, Visitor visitor) {
log.info("Попытка авторизации пользователя {}", visitor.getId());
Integer visitorCode = visitor.getCode();
if (visitorCode == null) {
return new AuthResponseDto(null, "Повторите запрос кода из бота");
}
if (code.equals(visitor.getCode())) {
visitor.setCode(null);
visitor = visitorsRepository.save(visitor);
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
}
return new AuthResponseDto(null, "Неверный код подтверждения");
}
} }

View File

@@ -8,82 +8,43 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import ru.kayashov.bar.controller.dto.VisitorResponseDto;
import ru.kayashov.bar.controller.dto.bar.BarResponseDto; import ru.kayashov.bar.controller.dto.bar.BarResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto; import ru.kayashov.bar.service.BarService;
import ru.kayashov.bar.model.entity.Category;
import ru.kayashov.bar.model.entity.Glass;
import ru.kayashov.bar.model.entity.Unit;
import ru.kayashov.bar.model.entity.UserRole;
import ru.kayashov.bar.model.entity.Visitor;
import ru.kayashov.bar.repository.UnitRepository;
import ru.kayashov.bar.service.SessionService;
import ru.kayashov.bar.service.VisitorService;
import java.util.Arrays;
import java.util.List; import java.util.List;
@Slf4j @Slf4j
@CrossOrigin(origins = {"*"}) @CrossOrigin(origins = {"*"})
@RestController @RestController
@RequestMapping("/api/bar/") @RequestMapping("/api/bar")
@RequiredArgsConstructor @RequiredArgsConstructor
public class BarController { public class BarController {
private final SessionService sessionService; private final BarService barService;
private final VisitorService visitorService;
private final UnitRepository unitRepository;
@GetMapping("/units")
public List<Unit> getUnitList() {
return unitRepository.findAll();
}
@GetMapping("/glass")
public List<String> getGlass() {
return Arrays.stream(Glass.values()).map(Glass::getName).toList();
}
@GetMapping("/category")
public List<String> getCategory() {
return Arrays.stream(Category.values()).map(Category::getName).toList();
}
@GetMapping("/receipt")
public List<ReceiptResponseDto> getReceipt(@RequestParam("id") Long id) {
return sessionService.getReceiptList(id);
}
@PostMapping("/change/{id}") @PostMapping("/change/{id}")
public void changeActiveBar(@PathVariable Long id) { public void changeActiveBar(@PathVariable Long id) {
sessionService.changeActiveBar(id); barService.changeActiveBar(id);
}
@PostMapping("/copy/{id}/{name}")
public BarResponseDto copyActiveBar(@PathVariable Long id, @PathVariable String name) {
return barService.copyBar(id, name);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public void deleteBar(@PathVariable Long id) { public void deleteBar(@PathVariable Long id) {
sessionService.deleteBar(id); barService.deleteBar(id);
} }
@PostMapping("/{name}") @PostMapping("/{name}")
public BarResponseDto createBar(@PathVariable String name) { public BarResponseDto createBar(@PathVariable String name) {
return sessionService.createBar(name); return barService.createBar(name);
} }
@GetMapping("/all") @GetMapping("/all")
public List<BarResponseDto> getAll() { public List<BarResponseDto> getAll() {
return sessionService.findAllBar(); return barService.findAllBar();
}
@GetMapping("/getMe")
public VisitorResponseDto getMe() {
Visitor visitor = visitorService.getCurrentVisitor();
VisitorResponseDto dto = VisitorResponseDto.mapToDto(visitor, true, visitor.getRole().toString(), true);
log.info("Запрос информации о пользователе: {}-{} {}, вошел в бар",
dto.getId(),
dto.getName().strip(),
dto.getLastName() != null ? dto.getLastName().strip() : "");
return dto;
} }
} }

View File

@@ -0,0 +1,25 @@
package ru.kayashov.bar.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.kayashov.bar.model.entity.Category;
import javax.annotation.security.PermitAll;
import java.util.Arrays;
import java.util.List;
@Slf4j
@CrossOrigin(origins = {"*"})
@RestController
@RequestMapping("/api/category")
public class CategoryController {
@PermitAll
@GetMapping
public List<String> getCategory() {
return Arrays.stream(Category.values()).map(Category::getName).toList();
}
}

View File

@@ -3,11 +3,11 @@ package ru.kayashov.bar.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -15,16 +15,15 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import ru.kayashov.bar.controller.dto.ErrorDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailFilterRequestDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailFilterRequestDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailForIngredientModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailForIngredientModalDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
import ru.kayashov.bar.controller.dto.ErrorDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
import ru.kayashov.bar.model.entity.Visitor;
import ru.kayashov.bar.service.CocktailService; import ru.kayashov.bar.service.CocktailService;
import javax.annotation.security.PermitAll;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -39,14 +38,15 @@ public class CocktailController {
private final CocktailService cocktailService; private final CocktailService cocktailService;
//получить все //получить все
@PermitAll
@PostMapping("menu") @PostMapping("menu")
public List<CocktailForListResponseDto> menu(@RequestBody CocktailFilterRequestDto dto) { public List<CocktailForListResponseDto> menu(@RequestBody CocktailFilterRequestDto dto) {
return cocktailService.getMenu(dto); return cocktailService.getMenu(dto);
} }
@GetMapping("/instructions") @GetMapping("calc")
public String getInstructions(@RequestParam("id") Long id) { public List<CocktailForListResponseDto> calc() {
return cocktailService.findInstructions(id); return cocktailService.calc();
} }
@GetMapping("/byIngredient") @GetMapping("/byIngredient")
@@ -56,7 +56,7 @@ public class CocktailController {
@PostMapping("/photo") @PostMapping("/photo")
public String savePhoto(@RequestBody MultipartFile file) throws IOException { public String savePhoto(@RequestBody MultipartFile file) throws IOException {
if(file == null) { if (file == null) {
return ""; return "";
} }
return cocktailService.savePhoto(file); return cocktailService.savePhoto(file);
@@ -72,11 +72,6 @@ public class CocktailController {
return cocktailService.getSimple(); return cocktailService.getSimple();
} }
@GetMapping("/receipts")
public List<ReceiptResponseDto> getReceipts(@RequestParam Long id) {
return cocktailService.getReceipts(id);
}
@GetMapping("/modal") @GetMapping("/modal")
public CocktailModalDto getForModal(@RequestParam Long id) { public CocktailModalDto getForModal(@RequestParam Long id) {
return cocktailService.getForModal(id); return cocktailService.getForModal(id);
@@ -112,4 +107,9 @@ public class CocktailController {
public void addRating(@RequestParam("id") Long id, @RequestParam("rating") Integer rating) { public void addRating(@RequestParam("id") Long id, @RequestParam("rating") Integer rating) {
cocktailService.setRating(id, rating); cocktailService.setRating(id, rating);
} }
@PostMapping("/drink/{id}")
public void drinkCocktail(@PathVariable Long id) {
cocktailService.drink(id);
}
} }

View File

@@ -0,0 +1,27 @@
package ru.kayashov.bar.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.kayashov.bar.model.entity.Glass;
import javax.annotation.security.PermitAll;
import java.util.Arrays;
import java.util.List;
@Slf4j
@CrossOrigin(origins = {"*"})
@RestController
@RequestMapping("/api/glass")
@RequiredArgsConstructor
public class GlassController {
@PermitAll
@GetMapping
public List<String> getGlass() {
return Arrays.stream(Glass.values()).map(Glass::getName).toList();
}
}

View File

@@ -39,6 +39,8 @@ public class IngredientController {
return ingredientService.getAllSimple(); return ingredientService.getAllSimple();
} }
//todo: перевести на enum
@Deprecated
@GetMapping("/type") @GetMapping("/type")
public List<TypeResponseDto> getTypes() { public List<TypeResponseDto> getTypes() {
return typeRepository.findAll().stream() return typeRepository.findAll().stream()

View File

@@ -0,0 +1,29 @@
package ru.kayashov.bar.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.kayashov.bar.model.entity.Unit;
import ru.kayashov.bar.repository.UnitRepository;
import javax.annotation.security.PermitAll;
import java.util.List;
@Slf4j
@CrossOrigin(origins = {"*"})
@RestController
@RequestMapping("/api/unit")
@RequiredArgsConstructor
public class UnitController {
private final UnitRepository unitRepository;
@PermitAll
@GetMapping("/units")
public List<Unit> getUnitList() {
return unitRepository.findAll();
}
}

View File

@@ -13,6 +13,7 @@ public class VisitorResponseDto {
private String lastName; private String lastName;
private Boolean invited; private Boolean invited;
private Boolean isActive; private Boolean isActive;
private String image;
private String role; private String role;
public static VisitorResponseDto mapToDto(Visitor e) { public static VisitorResponseDto mapToDto(Visitor e) {
@@ -21,8 +22,8 @@ public class VisitorResponseDto {
d.setId(e.getId()); d.setId(e.getId());
d.setName(e.getName()); d.setName(e.getName());
d.setLastName(e.getLastName()); d.setLastName(e.getLastName());
// d.setInvited(e.getInvited()); d.setRole(e.getRole().toString());
// d.setRole(e.getRole().toString()); d.setImage(e.getImage());
return d; return d;
} }

View File

@@ -10,7 +10,7 @@ import java.util.List;
@Setter @Setter
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class CocktailFilterRequestDto { public class CocktailFilterRequestDto {
private String search; private String search = "";
private Boolean onlyFavourite; private Boolean onlyFavourite;
private List<String> glass; private List<String> glass;
private List<String> category; private List<String> category;

View File

@@ -37,27 +37,4 @@ public class CocktailForListResponseDto {
private List<ReceiptResponseDto> receipt; private List<ReceiptResponseDto> receipt;
private RatingResponseDto rating; private RatingResponseDto rating;
public static CocktailForListResponseDto mapToDto(CocktailEntity e, Long visitorId) {
CocktailForListResponseDto d = new CocktailForListResponseDto();
d.setId(e.getId());
d.setName(e.getName());
d.setImage(e.getImage());
d.setVideo(e.getVideo());
d.setInstructions(e.getInstructions());
d.setCategory(e.getCategory().getName());
d.setAlcoholic(e.getAlcoholic().getValue());
d.setGlass(e.getGlass().getName());
String components = e.getReceipt().stream().map(r -> r.getIngredient().getName()).collect(Collectors.joining(", "));
d.setComponents(components);
d.setReceipt(e.getReceipt().stream().map(ReceiptResponseDto::mapToDto).toList());
RatingResponseDto rating = new RatingResponseDto();
rating.setFavourite(e.getIsFavorite());
rating.setRating(e.getRating());
d.setRating(rating);
return d;
}
} }

View File

@@ -15,7 +15,6 @@ import ru.kayashov.bar.model.entity.IngredientEntity;
public class IngredientResponseDto { public class IngredientResponseDto {
private Long id; private Long id;
private String name; private String name;
// private String enName;
private Boolean alcohol; private Boolean alcohol;
private Integer abv; private Integer abv;
private boolean isHave; private boolean isHave;
@@ -29,12 +28,11 @@ public class IngredientResponseDto {
.builder() .builder()
.id(i.getId()) .id(i.getId())
.name(i.getName()) .name(i.getName())
// .enName(i.getEnName())
.alcohol(i.getAlcohol()) .alcohol(i.getAlcohol())
.type(i.getType() != null ? i.getType().getName() : null) .type(i.getType() != null ? i.getType().getName() : null)
.abv(i.getAbv()) .abv(i.getAbv())
// .isHave(i.getIsHave()) .isHave(i.getIsHave())
.image("https://thecocktaildb.com/images/ingredients/" + i.getEnName() + "-Medium.png") .image(i.getImage())
.description(i.getDescription()) .description(i.getDescription())
.build(); .build();
} }

View File

@@ -11,9 +11,12 @@ import ru.kayashov.bar.model.entity.IngredientEntity;
public class IngredientSimpleResponseDto { public class IngredientSimpleResponseDto {
private Long id; private Long id;
private String name; private String name;
private String image;
private Boolean isHave; private Boolean isHave;
public static IngredientSimpleResponseDto mapToDto(IngredientEntity ingredient) { public static IngredientSimpleResponseDto mapToDto(IngredientEntity ingredient) {
return new IngredientSimpleResponseDto(ingredient.getId(), ingredient.getName(), false/*ingredient.getIsHave()*/); return new IngredientSimpleResponseDto(ingredient.getId(), ingredient.getName(),
ingredient.getImage(),
ingredient.getIsHave());
} }
} }

View File

@@ -1,6 +1,8 @@
package ru.kayashov.bar.mapper; package ru.kayashov.bar.mapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -10,15 +12,12 @@ import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
import ru.kayashov.bar.controller.dto.cocktail.RatingResponseDto; import ru.kayashov.bar.controller.dto.cocktail.RatingResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto; import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto; import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
import ru.kayashov.bar.model.entity.BarEntity;
import ru.kayashov.bar.model.entity.CocktailEntity; import ru.kayashov.bar.model.entity.CocktailEntity;
import ru.kayashov.bar.model.entity.IngredientEntity; import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.model.entity.ReceiptEntity; import ru.kayashov.bar.model.entity.ReceiptEntity;
import ru.kayashov.bar.model.entity.Visitor; import ru.kayashov.bar.model.entity.Visitor;
import ru.kayashov.bar.repository.BarEntityRepository;
import ru.kayashov.bar.repository.VisitorsRepository; import ru.kayashov.bar.repository.VisitorsRepository;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -27,25 +26,15 @@ import java.util.stream.Collectors;
public class CocktailMapper { public class CocktailMapper {
private final VisitorsRepository visitorsRepository; private final VisitorsRepository visitorsRepository;
private final BarEntityRepository barRepository;
@Transactional @Transactional
public List<CocktailForListResponseDto> cocktailsToDtoList(List<CocktailEntity> cocktails, Boolean all) { public List<CocktailForListResponseDto> cocktailsToDtoList(List<CocktailEntity> cocktails, Boolean all, boolean withReceipts) {
Visitor visitor = getCurrentVisitor();
// if(checkUserNotInBar(visitor)) {
// return new ArrayList<>();
// }
List<Long> barStopList = new ArrayList<>();
List<Long> allowedIngredients = getAllowedIngredients();
return cocktails.stream() return cocktails.stream()
.map(c -> cocktailToDto(c, visitor, allowedIngredients, barStopList)) .map(c -> cocktailToDto(c, withReceipts))
// .filter(c -> all || c.getIsAllowed())
// .filter(c -> all || c.getInMenu())
.toList(); .toList();
} }
public CocktailForListResponseDto cocktailToFullDto(CocktailEntity e) { public CocktailForListResponseDto cocktailToFullDto(CocktailEntity e) {
List<Long> allowed = getAllowedIngredients();
return CocktailForListResponseDto.builder() return CocktailForListResponseDto.builder()
.id(e.getId()) .id(e.getId())
.name(e.getName()) .name(e.getName())
@@ -55,11 +44,11 @@ public class CocktailMapper {
.glass(e.getGlass().getName()) .glass(e.getGlass().getName())
.instructions(e.getInstructions()) .instructions(e.getInstructions())
.video(e.getVideo()) .video(e.getVideo())
.receipt(createReceiptDtoList(e.getReceipt(), allowed)) .receipt(createReceiptDtoList(e.getReceipt()))
.build(); .build();
} }
private CocktailForListResponseDto cocktailToDto(CocktailEntity e, Visitor visitor, List<Long> allowedIngredients, List<Long> barStopList) { private CocktailForListResponseDto cocktailToDto(CocktailEntity e, boolean withReceipts) {
boolean hasError = false; boolean hasError = false;
int volume = 0; int volume = 0;
Float abv = 0f; Float abv = 0f;
@@ -104,33 +93,9 @@ public class CocktailMapper {
.glass(e.getGlass().getName()) .glass(e.getGlass().getName())
.components(containCocktailComponents(e.getReceipt())) .components(containCocktailComponents(e.getReceipt()))
.rating(createRatingDto(e)) .rating(createRatingDto(e))
.isAllowed(calculateAllowed(e.getReceipt(), allowedIngredients)) .isAllowed(e.getAllowed())
.inMenu(!barStopList.contains(e.getId())) .receipt(!withReceipts ? null : e.getReceipt().stream().map(ReceiptResponseDto::mapToDto).toList())
.build(); .build();
// d.setReceipt(e.getReceipt().stream().map(ReceiptResponseDto::mapToDto).toList());
}
private List<Long> getAllowedIngredients() {
return barRepository.findByActiveTrue()
.map(BarEntity::getIngredients)
.orElseThrow()
.stream()
.map(IngredientEntity::getId)
.toList();
// return visitor.getResidents().stream()
// .filter(BarResident::getActive)
// .map(BarResident::getBar)
// .map(BarEntity::getIngredients)
// .flatMap(List::stream)
// .map(BarIngredientStorage::getIngredient)
// .map(IngredientEntity::getId)
// .toList();
}
private Boolean calculateAllowed(List<ReceiptEntity> e, List<Long> allowedIngredients) {
return e.stream()
.map(ReceiptEntity::getIngredient)
.allMatch(i -> allowedIngredients.contains(i.getId()));
} }
private RatingResponseDto createRatingDto(CocktailEntity entity) { private RatingResponseDto createRatingDto(CocktailEntity entity) {
@@ -148,40 +113,49 @@ public class CocktailMapper {
} }
private Visitor getCurrentVisitor() { private Visitor getCurrentVisitor() {
Long visitorId = ((Visitor) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId(); SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
Long visitorId;
if (authentication.getPrincipal() instanceof Visitor) {
visitorId = ((Visitor) authentication.getPrincipal()).getId();
} else {
visitorId = 1L;
}
return visitorsRepository.findById(visitorId).orElseThrow(RuntimeException::new); return visitorsRepository.findById(visitorId).orElseThrow(RuntimeException::new);
} }
public CocktailModalDto cocktailToModalDto(CocktailEntity e) { public CocktailModalDto cocktailToModalDto(CocktailEntity e) {
List<Long> allowedIngredients = getAllowedIngredients();
return CocktailModalDto.builder() return CocktailModalDto.builder()
.id(e.getId()) .id(e.getId())
.name(e.getName()) .name(e.getName())
.image(e.getImage()) .image(e.getImage())
.instructions(e.getInstructions()) .instructions(e.getInstructions())
.rating(createRatingDto(e)) .rating(createRatingDto(e))
.receipt(createReceiptDtoList(e.getReceipt(), allowedIngredients)) .receipt(createReceiptDtoList(e.getReceipt()))
.build(); .build();
} }
private List<ReceiptResponseDto> createReceiptDtoList(List<ReceiptEntity> receipts, List<Long> allowedIngredients) { private List<ReceiptResponseDto> createReceiptDtoList(List<ReceiptEntity> receipts) {
return receipts.stream() return receipts.stream()
.map(r -> createReceiptDto(r, allowedIngredients)) .map(this::createReceiptDto)
.toList(); .toList();
} }
private ReceiptResponseDto createReceiptDto(ReceiptEntity e, List<Long> allowedIngredients) { private ReceiptResponseDto createReceiptDto(ReceiptEntity e) {
return ReceiptResponseDto.builder() return ReceiptResponseDto.builder()
.id(e.getId()) .id(e.getId())
.ingredient(createIngredientResponseDto(e.getIngredient(), allowedIngredients)) .ingredient(createIngredientResponseDto(e.getIngredient()))
.count(e.getCount()) .count(e.getCount())
.unit(e.getUnit()) .unit(e.getUnit())
.measure(e.getMeasure()) .measure(e.getMeasure())
.build(); .build();
} }
private IngredientSimpleResponseDto createIngredientResponseDto(IngredientEntity i, List<Long> allowedIngredients) { private IngredientSimpleResponseDto createIngredientResponseDto(IngredientEntity i) {
return new IngredientSimpleResponseDto(i.getId(), i.getName(), allowedIngredients.contains(i.getId())); return new IngredientSimpleResponseDto(i.getId(),
i.getName(),
i.getImage(),
i.getIsHave());
} }

View File

@@ -4,9 +4,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ru.kayashov.bar.controller.dto.ingredient.IngredientResponseDto; import ru.kayashov.bar.controller.dto.ingredient.IngredientResponseDto;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto; import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
import ru.kayashov.bar.model.entity.BarEntity;
import ru.kayashov.bar.model.entity.IngredientEntity; import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.repository.BarEntityRepository;
import java.util.List; import java.util.List;
@@ -14,25 +12,13 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class IngredientMapper { public class IngredientMapper {
private final BarEntityRepository barRepository;
public List<IngredientResponseDto> mapIngredientsToDtoList(List<IngredientEntity> ingredients) { public List<IngredientResponseDto> mapIngredientsToDtoList(List<IngredientEntity> ingredients) {
List<Long> allowedIngredients = getAllowedIngredients();
return ingredients.stream() return ingredients.stream()
.map(i -> mapIngredientToDto(i, allowedIngredients)) .map(this::mapIngredientToDto)
.toList(); .toList();
} }
private List<Long> getAllowedIngredients() { private IngredientResponseDto mapIngredientToDto(IngredientEntity i) {
return barRepository.findByActiveTrue()
.map(BarEntity::getIngredients)
.orElseThrow()
.stream()
.map(IngredientEntity::getId)
.toList();
}
private IngredientResponseDto mapIngredientToDto(IngredientEntity i, List<Long> allowedIngredients) {
return IngredientResponseDto return IngredientResponseDto
.builder() .builder()
.id(i.getId()) .id(i.getId())
@@ -40,20 +26,21 @@ public class IngredientMapper {
.alcohol(i.getAlcohol()) .alcohol(i.getAlcohol())
.type(i.getType() != null ? i.getType().getName() : null) .type(i.getType() != null ? i.getType().getName() : null)
.abv(i.getAbv()) .abv(i.getAbv())
.isHave(allowedIngredients.contains(i.getId())) .isHave(i.getIsHave())
.image("https://thecocktaildb.com/images/ingredients/" + i.getEnName() + "-Medium.png") .image(i.getImage())
.description(i.getDescription()) .description(i.getDescription())
.build(); .build();
} }
public List<IngredientSimpleResponseDto> mapIngredientsToSimpleDtoList(List<IngredientEntity> ingredients) { public List<IngredientSimpleResponseDto> mapIngredientsToSimpleDtoList(List<IngredientEntity> ingredients) {
List<Long> allowedIngredients = getAllowedIngredients();
return ingredients.stream() return ingredients.stream()
.map(i -> mapIngredientToSimpleDto(i, allowedIngredients)) .map(this::mapIngredientToSimpleDto)
.toList(); .toList();
} }
private IngredientSimpleResponseDto mapIngredientToSimpleDto(IngredientEntity i, List<Long> allowedIngredients) { private IngredientSimpleResponseDto mapIngredientToSimpleDto(IngredientEntity i) {
return new IngredientSimpleResponseDto(i.getId(), i.getName(), allowedIngredients.contains(i.getId())); return new IngredientSimpleResponseDto(i.getId(), i.getName(),
i.getImage(),
i.getIsHave());
} }
} }

View File

@@ -7,16 +7,10 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor @RequiredArgsConstructor
public enum Category { public enum Category {
COCKTAIL("Коктейль"), COCKTAIL("Коктейль"),
PUNCH("Пунш"),
SHAKE("Шейк"),
OTHER("Другие"),
BEER("Пиво"), BEER("Пиво"),
HOMEMADE_LIQUEUR("Домашний ликер"),
SHOT("Шот"), SHOT("Шот"),
COCOA("Какао"),
COFFEE_TEA("Кофе-Чай"), COFFEE_TEA("Кофе-Чай"),
SOFT("Безалкогольный напиток"), SOFT("Безалкогольный напиток");
ORDINARY("Обычный напиток");
private final String name; private final String name;

View File

@@ -27,6 +27,7 @@ import java.util.List;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class CocktailEntity { public class CocktailEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@@ -46,10 +47,14 @@ public class CocktailEntity {
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private Glass glass; private Glass glass;
private Integer rating; private Boolean allowed;
private Integer rating = 0;
@Column(name = "favourite") @Column(name = "favourite")
private Boolean isFavorite; private Boolean isFavorite = false;
private Integer countDrink = 0;
@OneToMany(mappedBy = "cocktail", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "cocktail", cascade = CascadeType.REMOVE)
private List<ReceiptEntity> receipt; private List<ReceiptEntity> receipt;

View File

@@ -0,0 +1,32 @@
package ru.kayashov.bar.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private LocalDateTime date;
private String oldState;
private String newState;
@Enumerated(EnumType.STRING)
private EventType type;
}

View File

@@ -0,0 +1,23 @@
package ru.kayashov.bar.model.entity;
public enum EventType {
LOGIN,
DRINK,
COCKTAIL_CREATE,
COCKTAIL_EDIT,
COCKTAIL_DELETE,
COCKTAIL_UPLOAD_PHOTO,
INGREDIENT_CREATE,
INGREDIENT_EDIT,
INGREDIENT_DELETE,
INGREDIENT_ADD,
INGREDIENT_REMOVE,
INGREDIENT_UPLOAD_PHOTO,
BAR_CREATE,
BAR_DELETE,
BAR_CHANGE,
USER_CREATE,
USER_EDIT,
USER_UPLOAD_PHOTO
}

View File

@@ -6,23 +6,20 @@ import lombok.RequiredArgsConstructor;
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public enum Glass { public enum Glass {
HIGHBALL("Хайболл"), HIGHBALL("Хайболл",250),
COCKTAIL("Коктейльный бокал"), COCKTAIL("Коктейльный бокал",120),
COLLINS("Стакан"), COLLINS("Стакан", 200),
POUSSE("Рюмка на ножке"), CHAMPAGNE("Бокал флюте", 200),
CHAMPAGNE("Бокал флюте"), HURRICANE("Ураган", 350),
BRANDY("Коньячный бокал"), COFFEE("Кофейная кружка", 250),
HURRICANE("Ураган"), SHOT("Рюмка",50),
COFFEE("Кофейная кружка"), CORDIAL("Ликерная рюмка",150),
SHOT("Рюмка"), BEER("Пивной бокал", 500),
JAR("Банка"), WINE("Бокал для вина", 350),
PITCHER("Кувшин"), MARGARITA("Бокал Маргарита", 260);
CORDIAL("Ликерная рюмка"),
BEER("Пивной бокал"),
WINE("Бокал для вина"),
MARGARITA("Бокал Маргарита");
private final String name; private final String name;
private final Integer capacity;
public static Glass findValue(String name) { public static Glass findValue(String name) {
for (Glass glass : Glass.values()) { for (Glass glass : Glass.values()) {

View File

@@ -9,6 +9,8 @@ import lombok.Setter;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.JoinTable; import javax.persistence.JoinTable;
@@ -26,13 +28,15 @@ import java.util.List;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class IngredientEntity { public class IngredientEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
private String name; private String name;
private String enName;
private Boolean alcohol; private Boolean alcohol;
private Integer abv; private Integer abv;
private Boolean isHave; private Boolean isHave;
private String image;
@Column(columnDefinition = "text") @Column(columnDefinition = "text")
private String description; private String description;
@@ -44,7 +48,7 @@ public class IngredientEntity {
@JoinColumn(name = "type") @JoinColumn(name = "type")
private TypeEntity type; private TypeEntity type;
@ManyToMany(cascade = CascadeType.REMOVE) @ManyToMany
@JoinTable( @JoinTable(
name = "bar_ingredient", name = "bar_ingredient",
joinColumns = @JoinColumn(name = "ingredient"), joinColumns = @JoinColumn(name = "ingredient"),

View File

@@ -26,6 +26,7 @@ public class Visitor implements UserDetails {
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private UserRole role; private UserRole role;
private Integer code; private Integer code;
private String image;
private String login; private String login;
private String password; private String password;
private LocalDateTime expired; private LocalDateTime expired;
@@ -33,13 +34,6 @@ public class Visitor implements UserDetails {
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return role.getAuthorities(); return role.getAuthorities();
// return UserRole.ADMIN.getAuthorities();
// return residents.stream()
// .filter(BarResident::getActive)
// .map(BarResident::getRole)
// .map(UserRole::getAuthorities)
// .flatMap(Collection::stream)
// .collect(Collectors.toSet());
} }
@Override @Override

View File

@@ -8,11 +8,17 @@ import org.springframework.transaction.annotation.Transactional;
import ru.kayashov.bar.model.entity.CocktailEntity; import ru.kayashov.bar.model.entity.CocktailEntity;
public interface CocktailRepository extends JpaRepository<CocktailEntity, Long> { public interface CocktailRepository extends JpaRepository<CocktailEntity, Long> {
@Modifying @Modifying
@Transactional @Transactional
@Query("UPDATE CocktailEntity c SET c.isFavorite = :favorite WHERE c.id = :id") @Query("UPDATE CocktailEntity c SET c.isFavorite = :favorite WHERE c.id = :id")
void updateFavouriteById(@Param("id") Long id, @Param("favorite") Boolean favorite); void updateFavouriteById(@Param("id") Long id, @Param("favorite") Boolean favorite);
@Modifying
@Transactional
@Query("UPDATE CocktailEntity c SET c.allowed = false WHERE c.allowed = true")
void markAllAsNotAllowed();
@Modifying @Modifying
@Transactional @Transactional
@Query("UPDATE CocktailEntity c SET c.rating = :rating WHERE c.id = :id") @Query("UPDATE CocktailEntity c SET c.rating = :rating WHERE c.id = :id")

View File

@@ -0,0 +1,7 @@
package ru.kayashov.bar.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.kayashov.bar.model.entity.Event;
public interface EventRepository extends JpaRepository<Event, Integer> {
}

View File

@@ -6,6 +6,4 @@ import ru.kayashov.bar.model.entity.IngredientEntity;
import java.util.Optional; import java.util.Optional;
public interface IngredientRepository extends JpaRepository<IngredientEntity, Long> { public interface IngredientRepository extends JpaRepository<IngredientEntity, Long> {
Optional<IngredientEntity> findByEnNameIgnoreCase(String name);
} }

View File

@@ -7,6 +7,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -26,6 +27,7 @@ import static org.springframework.security.config.http.SessionCreationPolicy.STA
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity @EnableMethodSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor @RequiredArgsConstructor
public class SecurityConfig { public class SecurityConfig {
@@ -51,8 +53,13 @@ public class SecurityConfig {
private void authorizeConfiguration(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry request) { private void authorizeConfiguration(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry request) {
request request
// Можно указать конкретный путь, * - 1 уровень вложенности, ** - любое количество уровней вложенности // Можно указать конкретный путь, * - 1 уровень вложенности, ** - любое количество уровней вложенности
.antMatchers("/api/auth/**") .antMatchers("/api/auth/**").permitAll()
.permitAll() .antMatchers("/api/cocktail/menu").permitAll()
.antMatchers("/api/cocktail/drink/**").permitAll()
.antMatchers("/api/cocktail/modal").permitAll()
.antMatchers("/api/category").permitAll()
.antMatchers("/api/glass").permitAll()
.antMatchers("/api/ingredient/simple").permitAll()
.anyRequest() .anyRequest()
.authenticated(); .authenticated();
} }

View File

@@ -0,0 +1,131 @@
package ru.kayashov.bar.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.kayashov.bar.controller.dto.bar.BarResponseDto;
import ru.kayashov.bar.model.entity.BarEntity;
import ru.kayashov.bar.model.entity.CocktailEntity;
import ru.kayashov.bar.model.entity.Event;
import ru.kayashov.bar.model.entity.EventType;
import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.model.entity.ReceiptEntity;
import ru.kayashov.bar.repository.BarEntityRepository;
import ru.kayashov.bar.repository.CocktailRepository;
import ru.kayashov.bar.repository.IngredientRepository;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Slf4j
@Service
@RequiredArgsConstructor
public class BarService {
private final BarEntityRepository barEntityRepository;
private final IngredientRepository ingredientRepository;
private final CocktailRepository cocktailRepository;
private final EventService eventService;
public void changeActiveBar(Long id) {
Optional<BarEntity> lastBarOpt = barEntityRepository.findByActiveTrue();
if (lastBarOpt.isPresent()) {
BarEntity lastBar = lastBarOpt.get();
lastBar.setActive(false);
barEntityRepository.save(lastBar);
lastBar.getIngredients().stream()
.peek(i -> i.setIsHave(false))
.forEach(ingredientRepository::save);
cocktailRepository.markAllAsNotAllowed();
log.info("Бар {} отключен", lastBar.getName());
}
BarEntity barEntity = barEntityRepository.findById(id).orElseThrow();
barEntity.setActive(true);
List<IngredientEntity> ingredients = barEntity.getIngredients().stream()
.peek(i -> i.setIsHave(true))
.map(ingredientRepository::save)
.toList();
cocktailRepository.saveAll(findAllowedCocktails(ingredients));
barEntityRepository.save(barEntity);
eventService.createEvent(Event.builder()
.date(LocalDateTime.now())
.type(EventType.BAR_CHANGE)
.newState(barEntity.getName())
.oldState(lastBarOpt.map(BarEntity::getName).orElse(null))
.build());
log.info("Бар {} подключен", barEntity.getName());
}
public List<BarResponseDto> findAllBar() {
List<BarEntity> barEntities = barEntityRepository.findAll();
log.info("По запросу найдено {} баров", barEntities.size());
return barEntities.stream()
.map(BarResponseDto::mapToDto)
.toList();
}
public void deleteBar(Long id) {
barEntityRepository.deleteById(id);
log.info("Бар с id {} удален", id);
}
public BarResponseDto createBar(String name) {
BarEntity bar = new BarEntity();
bar.setName(name);
bar.setActive(false);
bar = barEntityRepository.save(bar);
log.info("Создан бар {}", name);
return BarResponseDto.mapToDto(bar);
}
@Transactional
public BarResponseDto copyBar(Long id, String name) {
BarEntity barEntity = barEntityRepository.findById(id).orElseThrow();
BarEntity entity = new BarEntity();
entity.setName(name);
entity.setActive(false);
entity.setIngredients(new ArrayList<>());
barEntityRepository.save(entity);
List<IngredientEntity> ingredients = barEntity.getIngredients()
.stream()
.peek(i -> i.getBars().add(entity))
.toList();
entity.setIngredients(ingredients);
ingredientRepository.saveAll(ingredients);
log.info("Бар {} - {}, скопирован как {} - {}", barEntity.getId(), barEntity.getName(), entity.getId(), name);
return BarResponseDto.mapToDto(entity);
}
private List<CocktailEntity> findAllowedCocktails(List<IngredientEntity> ingredients) {
List<CocktailEntity> result = new ArrayList<>();
for (IngredientEntity ingredient : ingredients) {
List<CocktailEntity> cocktails = ingredient.getReceipts().stream()
.map(ReceiptEntity::getCocktail)
.toList();
for (CocktailEntity cocktail : cocktails) {
if (cocktail.getReceipt().stream().allMatch(r -> r.getIngredient().getIsHave())) {
cocktail.setAllowed(true);
result.add(cocktail);
}
}
}
return result;
}
}

View File

@@ -14,10 +14,14 @@ import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto; import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.SortingEnum;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
import ru.kayashov.bar.mapper.CocktailMapper; import ru.kayashov.bar.mapper.CocktailMapper;
import ru.kayashov.bar.model.entity.Alcoholic; import ru.kayashov.bar.model.entity.Alcoholic;
import ru.kayashov.bar.model.entity.Category; import ru.kayashov.bar.model.entity.Category;
import ru.kayashov.bar.model.entity.CocktailEntity; import ru.kayashov.bar.model.entity.CocktailEntity;
import ru.kayashov.bar.model.entity.Event;
import ru.kayashov.bar.model.entity.EventType;
import ru.kayashov.bar.model.entity.Glass; import ru.kayashov.bar.model.entity.Glass;
import ru.kayashov.bar.model.entity.IngredientEntity; import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.model.entity.ReceiptEntity; import ru.kayashov.bar.model.entity.ReceiptEntity;
@@ -28,21 +32,18 @@ import ru.kayashov.bar.repository.ReceiptRepository;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join; import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order; import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j @Slf4j
@Service @Service
@@ -50,6 +51,7 @@ import java.util.stream.Stream;
public class CocktailService { public class CocktailService {
private final CocktailRepository cocktailRepository; private final CocktailRepository cocktailRepository;
private final EventService eventService;
@Value("${cocktail.photo.path}") @Value("${cocktail.photo.path}")
private String photoFolder; private String photoFolder;
@@ -67,7 +69,19 @@ public class CocktailService {
*/ */
@Transactional @Transactional
public List<CocktailForListResponseDto> getMenu(CocktailFilterRequestDto dto) { public List<CocktailForListResponseDto> getMenu(CocktailFilterRequestDto dto) {
return mapper.cocktailsToDtoList(criteria(dto), dto.getAll()); return mapper.cocktailsToDtoList(criteria(dto), dto.getAll(), false);
}
@Transactional
public List<CocktailForListResponseDto> calc() {
CocktailFilterRequestDto dto = new CocktailFilterRequestDto();
dto.setPage(0);
dto.setSize(1000);
dto.setAll(false);
dto.setOnlyFavourite(false);
dto.setSort(SortingEnum.NAME_ASC);
return mapper.cocktailsToDtoList(criteria(dto), false, true);
} }
private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) { private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) {
@@ -79,9 +93,7 @@ public class CocktailService {
criteriaQuery.distinct(true); criteriaQuery.distinct(true);
if (!dto.getAll()) { if (!dto.getAll()) {
// List<Long> cocktailIds = findICountCocktailIds(0, new ArrayList<>()); Predicate pr = cb.isTrue(root.get("allowed"));
List<Long> cocktailIds = findCocktailByCountNotHaveIngredient();
Predicate pr = root.get("id").in(cocktailIds);
predicates.add(pr); predicates.add(pr);
} }
@@ -97,7 +109,7 @@ public class CocktailService {
} }
if (dto.getOnlyFavourite()) { if (dto.getOnlyFavourite()) {
predicates.add(cb.isTrue(root.get("favourite"))); predicates.add(cb.isTrue(root.get("isFavorite")));
} }
if (dto.getGlass() != null && !dto.getGlass().isEmpty()) { if (dto.getGlass() != null && !dto.getGlass().isEmpty()) {
@@ -112,18 +124,6 @@ public class CocktailService {
predicates.add(root.get("alcoholic").in(dto.getAlcohol().stream().map(Alcoholic::findValue).toList())); predicates.add(root.get("alcoholic").in(dto.getAlcohol().stream().map(Alcoholic::findValue).toList()));
} }
if (dto.getNotHaveCount() != null) {
List<Long> approveCocktail = findICountCocktailIds(dto.getNotHaveCount(), dto.getIngredient());
predicates.add(root.get("id").in(approveCocktail));
}
//
// if (!dto.getSortField().equals("name") || !dto.getSortOrder().equals("asc")) {
// cb.asc(root.get("name"));
//// query.orderBy(cb.asc(root.get("name").get(dto.getSortField())));
// } else {
// criteriaQuery.orderBy(cb.asc(root.get("name")));
// }
//todo: доделать другие виды сортировки //todo: доделать другие виды сортировки
Order order; Order order;
switch (dto.getSort()) { switch (dto.getSort()) {
@@ -143,100 +143,6 @@ public class CocktailService {
return cocktailEntities; return cocktailEntities;
} }
private List<Long> findICountCocktailIds(Integer iCount, List<String> ingredientFilter) {
List<Long> allowedIngredient = visitorService.getAllowedIngredients();
Stream<List<ReceiptEntity>> receiptStream = receiptRepository.findAll()
.stream()
.collect(Collectors.groupingBy(k -> k.getCocktail().getId()))
.values()
.stream()
.filter(l -> getCountNotHaveIngredient(allowedIngredient, l).equals(iCount));
if (!ingredientFilter.isEmpty()) {
receiptStream = receiptStream.filter(l -> findIngredientInReceipts(ingredientFilter, l));
}
return receiptStream
.map(l -> l.get(0))
.map(ReceiptEntity::getCocktail)
.map(CocktailEntity::getId)
.toList();
}
private boolean findIngredientInReceipts(List<String> ingredientFilter, List<ReceiptEntity> receipts) {
return receipts.stream().anyMatch(r -> ingredientFilter.contains(r.getIngredient().getName()));
}
private Integer getCountNotHaveIngredient(List<Long> allowedIngredientIds, List<ReceiptEntity> receipts) {
return Math.toIntExact(receipts.size() - receipts.stream()
.filter(r -> allowedIngredientIds.contains(r.getIngredient().getId()))
.count());
}
// private List<Long> getAllowedCocktailIds(Long barId) {
// CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// CriteriaQuery<Long> query = cb.createQuery(Long.class);
//
// Root<ReceiptEntity> receiptRoot = query.from(ReceiptEntity.class);
// Join<ReceiptEntity, IngredientEntity> ingredientJoin = receiptRoot.join("ingredient", JoinType.LEFT);
// Join<IngredientEntity, BarEntity> barIngredientStorageJoin = ingredientJoin.join("bars", JoinType.LEFT);
//
// // Внешний подзапрос с NOT EXISTS
// Subquery<Long> subquery = query.subquery(Long.class);
// Root<ReceiptEntity> receiptSubRoot = subquery.from(ReceiptEntity.class);
// Join<ReceiptEntity, IngredientEntity> ingredientSubJoin = receiptSubRoot.join("ingredient", JoinType.LEFT);
//
// // Внутренний подзапрос с NOT EXISTS
// Subquery<Long> innerSubquery = subquery.subquery(Long.class);
// Root<BarIngredientStorage> barIngredientStorageInnerRoot = innerSubquery.from(BarIngredientStorage.class);
//
// // Условия внутреннего подзапроса
// innerSubquery.select(barIngredientStorageInnerRoot.get("id"))
// .where(
// cb.equal(barIngredientStorageInnerRoot.get("ingredient"), ingredientSubJoin.get("id")),
// cb.equal(barIngredientStorageInnerRoot.get("bar").get("id"), barId)
// );
//
// // Условия внешнего подзапроса
// subquery.select(receiptSubRoot.get("id"))
// .where(
// cb.equal(receiptSubRoot.get("cocktail").get("id"), receiptRoot.get("cocktail").get("id")),
// cb.not(cb.exists(innerSubquery))
// );
//
// // Основной запрос
// query.select(receiptRoot.get("cocktail").get("id"))
// .distinct(true)
// .where(
// cb.equal(barIngredientStorageJoin.get("bar").get("id"), barId),
// cb.not(cb.exists(subquery))
// );
//
// return entityManager.createQuery(query).getResultList();
// }
/*
select cifc.cocktail_id
from (select r.cocktail_id, COUNT(CASE WHEN i.is_have THEN 0 END) as false_count
from receipt r
left join public.ingredient i on i.id = r.ingredient_id
group by r.cocktail_id) as cifc
where false_count = 0
*/
//todo: так и не придумал я нормальный запрос
private List<Long> findCocktailByCountNotHaveIngredient() {
String sql = "SELECT cifc.cocktail_id" +
" FROM (SELECT r.cocktail_id," +
" COUNT(CASE WHEN i.is_have = false THEN 1 END) AS false_count" +
" FROM receipt r" +
" LEFT JOIN public.ingredient i ON i.id = r.ingredient_id" +
" GROUP BY r.cocktail_id) AS cifc" +
" WHERE false_count = 0";
javax.persistence.Query query = entityManager.createNativeQuery(sql);
return query.getResultList();
}
public CocktailForListResponseDto findById(Long id) { public CocktailForListResponseDto findById(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(); CocktailEntity cocktail = repository.findById(id).orElseThrow();
return mapper.cocktailToFullDto(cocktail); return mapper.cocktailToFullDto(cocktail);
@@ -256,6 +162,11 @@ public class CocktailService {
.orElseThrow(() -> new RuntimeException("Не удалось найти коктейль с id " + dto.getId())); .orElseThrow(() -> new RuntimeException("Не удалось найти коктейль с id " + dto.getId()));
} }
boolean allowed = dto.getReceipt().stream()
.map(ReceiptResponseDto::getIngredient)
.allMatch(IngredientSimpleResponseDto::getIsHave);
cocktail.setAllowed(allowed);
cocktail.setName(dto.getName()); cocktail.setName(dto.getName());
cocktail.setInstructions(dto.getInstructions()); cocktail.setInstructions(dto.getInstructions());
cocktail.setImage(dto.getImage()); cocktail.setImage(dto.getImage());
@@ -264,19 +175,66 @@ public class CocktailService {
cocktail.setGlass(Glass.findValue(dto.getGlass())); cocktail.setGlass(Glass.findValue(dto.getGlass()));
cocktail.setAlcoholic(Alcoholic.findValue(dto.getAlcoholic())); cocktail.setAlcoholic(Alcoholic.findValue(dto.getAlcoholic()));
cocktail.setRating(cocktail.getRating()); cocktail.setRating(cocktail.getRating());
repository.save(cocktail); cocktail = repository.save(cocktail);
log.info("{} коктейль {}", dto.getId() == null ? "Создан" : "Изменен", cocktail.getName());
editCocktailReceipts(cocktail.getReceipt(), dto.getReceipt(), cocktail); editCocktailReceipts(cocktail.getReceipt(), dto.getReceipt(), cocktail);
} }
public void editFavourite(Long cocktailId, boolean put) { public void editFavourite(Long cocktailId, boolean put) {
log.info("Коктейль с id {} {}", cocktailId, put ? "добавлен в избранное" : "удален из избранного");
cocktailRepository.updateFavouriteById(cocktailId, put); cocktailRepository.updateFavouriteById(cocktailId, put);
} }
public void setRating(Long cocktailId, Integer rating) { public void setRating(Long cocktailId, Integer rating) {
log.info("Коктейлю с id {} выставлена новая оценка {}", cocktailId, rating);
cocktailRepository.updateRatingById(cocktailId, rating); cocktailRepository.updateRatingById(cocktailId, rating);
} }
public CocktailModalDto getForModal(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
return mapper.cocktailToModalDto(cocktail);
}
public List<CocktailSimpleResponseDto> getSimple() {
return repository.findAll().stream()
.map(CocktailSimpleResponseDto::mapToDto)
.toList();
}
public String savePhoto(MultipartFile file) throws IOException {
File folder = new File(photoFolder);
List<File> files = Arrays.asList(Objects.requireNonNull(folder.listFiles()));
String fileName = getPhotoPath(files, file.getOriginalFilename());
String fullName = photoFolder + "/" + fileName;
file.transferTo(new File(fullName));
log.info("сохранено фото {}", fileName);
return "/assets/cocktails/" + fileName;
}
public void delete(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
log.info("Удален коктейль {}", cocktail);
repository.delete(cocktail);
}
public String findInstructions(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
return cocktail.getInstructions();
}
public List<CocktailForIngredientModalDto> findByIngredient(Long id) {
return ingredientRepository.findById(id).orElseThrow(RuntimeException::new)
.getReceipts()
.stream()
.map(ReceiptEntity::getCocktail)
.distinct()
.map(mapper::cocktailToIngredientDtoList)
.toList();
}
//todo: попробовать отыграть эту связку каскадами
private void editCocktailReceipts(List<ReceiptEntity> old, List<ReceiptResponseDto> actual, CocktailEntity cocktail) { private void editCocktailReceipts(List<ReceiptEntity> old, List<ReceiptResponseDto> actual, CocktailEntity cocktail) {
for (ReceiptResponseDto receipt : actual) { for (ReceiptResponseDto receipt : actual) {
if (receipt.getId() == null) { if (receipt.getId() == null) {
@@ -324,33 +282,6 @@ public class CocktailService {
receiptRepository.save(receiptEntity); receiptRepository.save(receiptEntity);
} }
public CocktailModalDto getForModal(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
return mapper.cocktailToModalDto(cocktail);
}
public List<ReceiptResponseDto> getReceipts(Long id) {
return null;
}
public List<CocktailSimpleResponseDto> getSimple() {
return repository.findAll().stream()
.map(CocktailSimpleResponseDto::mapToDto)
.toList();
}
public String savePhoto(MultipartFile file) throws IOException {
File folder = new File(photoFolder);
List<File> files = Arrays.asList(Objects.requireNonNull(folder.listFiles()));
String fileName = getPhotoPath(files, file.getOriginalFilename());
String fullName = photoFolder + "/" + fileName;
file.transferTo(new File(fullName));
log.info("сохранено фото {}", fileName);
return "/assets/cocktails/" + fileName;
}
private String getPhotoPath(List<File> files, String originalName) { private String getPhotoPath(List<File> files, String originalName) {
if (files.stream().map(File::getName).anyMatch(name -> name.equals(originalName))) { if (files.stream().map(File::getName).anyMatch(name -> name.equals(originalName))) {
String[] split = originalName.split("\\."); String[] split = originalName.split("\\.");
@@ -368,24 +299,18 @@ public class CocktailService {
return sb.toString(); return sb.toString();
} }
public void delete(Long id) { public void drink(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new); CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
log.info("Удален коктейль {}", cocktail); int count = cocktail.getCountDrink() + 1;
repository.delete(cocktail); cocktail.setCountDrink(count);
} repository.save(cocktail);
public String findInstructions(Long id) { eventService.createEvent(Event.builder()
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new); .type(EventType.DRINK)
return cocktail.getInstructions(); .date(LocalDateTime.now())
} .oldState(cocktail.getName())
.newState(String.valueOf(count))
.build());
public List<CocktailForIngredientModalDto> findByIngredient(Long id) {
return ingredientRepository.findById(id).orElseThrow(RuntimeException::new)
.getReceipts()
.stream()
.map(ReceiptEntity::getCocktail)
.distinct()
.map(mapper::cocktailToIngredientDtoList)
.toList();
} }
} }

View File

@@ -0,0 +1,23 @@
package ru.kayashov.bar.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.kayashov.bar.model.entity.Event;
import ru.kayashov.bar.repository.EventRepository;
import java.util.concurrent.ExecutorService;
@Slf4j
@Service
@RequiredArgsConstructor
public class EventService {
private final EventRepository eventRepository;
private final ExecutorService executorService;
public void createEvent(Event event) {
executorService.submit(() -> {eventRepository.save(event);});
}
}

View File

@@ -45,11 +45,13 @@ public class IngredientService {
*/ */
public List<IngredientResponseDto> getAll() { public List<IngredientResponseDto> getAll() {
List<IngredientEntity> ingredients = repository.findAll(); List<IngredientEntity> ingredients = repository.findAll();
log.info("По запросу найдено {} ингредиентов", ingredients.size());
return mapper.mapIngredientsToDtoList(ingredients); return mapper.mapIngredientsToDtoList(ingredients);
} }
public List<IngredientSimpleResponseDto> getAllSimple() { public List<IngredientSimpleResponseDto> getAllSimple() {
List<IngredientEntity> ingredients = repository.findAll(); List<IngredientEntity> ingredients = repository.findAll();
log.info("По запросу найдено {} ингредиентов", ingredients.size());
return mapper.mapIngredientsToSimpleDtoList(ingredients); return mapper.mapIngredientsToSimpleDtoList(ingredients);
} }
@@ -60,25 +62,44 @@ public class IngredientService {
public void changeBarIngredient(Long id, boolean isHave) { public void changeBarIngredient(Long id, boolean isHave) {
BarEntity bar = barEntityRepository.findByActiveTrue().orElseThrow(); BarEntity bar = barEntityRepository.findByActiveTrue().orElseThrow();
IngredientEntity ingredientEntity = getIngredientById(id); IngredientEntity ingredientEntity = getIngredientById(id);
if (isHave) {
bar.getIngredients().add(ingredientEntity);
} else {
bar.getIngredients().remove(ingredientEntity);
}
ingredientEntity.setIsHave(isHave); ingredientEntity.setIsHave(isHave);
ingredientRepository.save(ingredientEntity); ingredientRepository.save(ingredientEntity);
List<CocktailEntity> cocktails = ingredientEntity.getReceipts().stream().map(ReceiptEntity::getCocktail).toList();
List<CocktailEntity> listForSave;
if (isHave) {
bar.getIngredients().add(ingredientEntity);
listForSave = new ArrayList<>();
for (CocktailEntity cocktail : cocktails) {
if (cocktail.getReceipt().stream().map(ReceiptEntity::getIngredient).allMatch(IngredientEntity::getIsHave)) {
cocktail.setAllowed(true);
listForSave.add(cocktail);
log.info("Коктейль {} стал доступен", cocktail.getName());
}
}
} else {
bar.getIngredients().remove(ingredientEntity);
listForSave = cocktails.stream().peek(c -> c.setAllowed(false)).toList();
log.info("Коктейли {} пропали из доступа", listForSave);
}
cocktailRepository.saveAll(listForSave);
barEntityRepository.save(bar); barEntityRepository.save(bar);
log.info("Ингредиент {} {}", ingredientEntity.getName(), isHave ? "добавлен в бар" : "удален из бара");
} }
public boolean saveChange(IngredientResponseDto dto) { public boolean saveChange(IngredientResponseDto dto) {
IngredientEntity entity;
if (dto.getId() == null) { if (dto.getId() == null) {
return false; entity = new IngredientEntity();
} entity.setIsHave(false);
IngredientEntity entity = repository.findById(dto.getId()) } else {
.orElse(null); entity = repository.findById(dto.getId())
if (entity == null) { .orElse(null);
return false; if (entity == null) {
return false;
}
} }
entity.setName(dto.getName()); entity.setName(dto.getName());
@@ -87,11 +108,12 @@ public class IngredientService {
entity.setAlcohol(dto.getAlcohol()); entity.setAlcohol(dto.getAlcohol());
TypeEntity type = findTypeByName(dto.getType()); TypeEntity type = findTypeByName(dto.getType());
if (type == null) { // if (type == null) {
return false; // return false;
} // }
entity.setType(type); entity.setType(type);
repository.save(entity); repository.save(entity);
log.info("Ингредиент {} {}", entity.getName(), dto.getId() == null ? "создан" : "изменен");
return true; return true;
} }

View File

@@ -0,0 +1,30 @@
package ru.kayashov.bar.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
import ru.kayashov.bar.model.entity.CocktailEntity;
import ru.kayashov.bar.repository.CocktailRepository;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class ReceiptService {
private final CocktailRepository cocktailRepository;
public List<ReceiptResponseDto> getReceiptList(Long cocktailId) {
CocktailEntity cocktail = cocktailRepository.findById(cocktailId).orElseThrow();
return cocktail.getReceipt().stream()
.map(e -> ReceiptResponseDto.builder()
.id(e.getId())
.ingredient(IngredientSimpleResponseDto.mapToDto(e.getIngredient()))
.measure(e.getMeasure())
.build())
.toList();
}
}

View File

@@ -1,89 +0,0 @@
package ru.kayashov.bar.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import ru.kayashov.bar.controller.dto.bar.BarResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
import ru.kayashov.bar.model.entity.BarEntity;
import ru.kayashov.bar.model.entity.CocktailEntity;
import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.model.entity.Visitor;
import ru.kayashov.bar.repository.BarEntityRepository;
import ru.kayashov.bar.repository.CocktailRepository;
import ru.kayashov.bar.repository.IngredientRepository;
import ru.kayashov.bar.repository.VisitorsRepository;
import java.util.List;
import java.util.concurrent.ExecutorService;
@Slf4j
@Service
@RequiredArgsConstructor
public class SessionService {
private final VisitorsRepository visitorsRepository;
private final CocktailRepository cocktailRepository;
private final BarEntityRepository barEntityRepository;
private final IngredientRepository ingredientRepository;
public Visitor getVisitor() {
Long id = ((Visitor) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal())
.getId();
return visitorsRepository.findById(id)
.orElseThrow();
}
public List<ReceiptResponseDto> getReceiptList(Long cocktailId) {
CocktailEntity cocktail = cocktailRepository.findById(cocktailId).orElseThrow();
return cocktail.getReceipt().stream()
.map(e -> ReceiptResponseDto.builder()
.id(e.getId())
.ingredient(IngredientSimpleResponseDto.mapToDto(e.getIngredient()))
.measure(e.getMeasure())
.build())
.toList();
}
public void changeActiveBar(Long id) {
BarEntity lastBar = barEntityRepository.findByActiveTrue().orElseThrow();
lastBar.setActive(false);
barEntityRepository.save(lastBar);
lastBar.getIngredients().stream()
.peek(i -> i.setIsHave(false))
.forEach(ingredientRepository::save);
BarEntity barEntity = barEntityRepository.findById(id).orElseThrow();
barEntity.setActive(true);
barEntity.getIngredients().stream()
.peek(i -> i.setIsHave(true))
.forEach(ingredientRepository::save);
barEntityRepository.save(barEntity);
}
public List<BarResponseDto> findAllBar() {
return barEntityRepository.findAll().stream()
.map(BarResponseDto::mapToDto)
.toList();
}
public void deleteBar(Long id) {
barEntityRepository.deleteById(id);
}
public BarResponseDto createBar(String name) {
BarEntity bar = new BarEntity();
bar.setName(name);
bar.setActive(false);
bar = barEntityRepository.save(bar);
return BarResponseDto.mapToDto(bar);
}
}

View File

@@ -3,20 +3,14 @@ package ru.kayashov.bar.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.model.entity.Visitor; import ru.kayashov.bar.model.entity.Visitor;
import ru.kayashov.bar.repository.BarEntityRepository;
import ru.kayashov.bar.repository.VisitorsRepository; import ru.kayashov.bar.repository.VisitorsRepository;
import java.util.ArrayList;
import java.util.List;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class VisitorService { public class VisitorService {
private final VisitorsRepository visitorsRepository; private final VisitorsRepository visitorsRepository;
private final BarEntityRepository barRepository;
public Visitor getCurrentVisitor() { public Visitor getCurrentVisitor() {
Long id = ((Visitor) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId(); Long id = ((Visitor) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
@@ -26,12 +20,4 @@ public class VisitorService {
public Visitor findById(Long id) { public Visitor findById(Long id) {
return visitorsRepository.findById(id).orElseThrow(() -> new RuntimeException("Visitor not found. id: " + id)); return visitorsRepository.findById(id).orElseThrow(() -> new RuntimeException("Visitor not found. id: " + id));
} }
public List<Long> getAllowedIngredients() {
return barRepository.findByActiveTrue().orElseThrow()
.getIngredients()
.stream()
.map(IngredientEntity::getId)
.toList();
}
} }

View File

@@ -1,53 +0,0 @@
package ru.kayashov.bar.service.integration.translate;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import ru.kayashov.bar.model.api.translate.Request;
import ru.kayashov.bar.model.api.translate.Response;
import ru.kayashov.bar.model.api.translate.Translate;
import ru.kayashov.bar.service.RestUtil;
import java.util.List;
import java.util.Optional;
@Slf4j
@UtilityClass
public class TranslateService {
private static final String TOKEN = "t1.9euelZrJyoyTmZGQyc2TiZCWlYyLiu3rnpWazsubi8vOzcycms6Znc2dkI7l8_dgXlNC-e9UAyJ4_t3z9yANUUL571QDInj-zef1656Vms2Uio2JmJ6OxpucmoqNjZ6J7_zF656Vms2Uio2JmJ6OxpucmoqNjZ6J.uXSVsrpZcSgQ9qz0-wx6WR79rvq14QCtlC6tnWvah33YRrNqSEBFXBwqMoEq18nG3wHElKE4NsHXK3lxV9SSBQ";
public static String translate(String text) {
return sendTranslate(text)
.map(TranslateService::getTranslateText)
.orElseThrow(RuntimeException::new);
}
public static String softTranslate(String text) {
Optional<ResponseEntity<Response>> opt = sendTranslate(text);
if(opt.isPresent()) {
return TranslateService.getTranslateText(opt.get());
} else {
log.warn("Не удалось перевести текст {}", text);
return text;
}
}
private static Optional<ResponseEntity<Response>> sendTranslate(String text) {
return RestUtil.sendRequest(Response.class,
RequestEntity.post("https://translate.api.cloud.yandex.net/translate/v2/translate")
.header("Authorization", "Bearer " + TOKEN)
.body(new Request(List.of(text))));
}
private static String getTranslateText(ResponseEntity<Response> response) {
Response resp = response.getBody();
StringBuilder sb = new StringBuilder();
List<Translate> translates = resp.getTranslations();
for(Translate translate : translates) {
sb.append(translate.getText());
}
return sb.toString();
}
}

View File

@@ -3,9 +3,9 @@ spring.application.name=myBar
cocktail.photo.path=${COCKTAIL_PHOTO_PATH:/mnt/sdb1/my-bar-front/build/assets/cocktails} cocktail.photo.path=${COCKTAIL_PHOTO_PATH:/mnt/sdb1/my-bar-front/build/assets/cocktails}
spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=${DB_URL:jdbc:postgresql://192.168.1.100:5432/drinks} spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5433/drink}
spring.datasource.username=${DB_NAME:nextcloud} spring.datasource.username=${DB_NAME:postgres}
spring.datasource.password=${DB_PASSWORD:kayash73} spring.datasource.password=${DB_PASSWORD:pgpass}
spring.datasource.hikari.minimum-idle=15 spring.datasource.hikari.minimum-idle=15
spring.datasource.hikari.maximum-pool-size=50 spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.idle-timeout=30000 spring.datasource.hikari.idle-timeout=30000
@@ -15,4 +15,6 @@ spring.jpa.generate-ddl=true
token.signing.key=${SIGNING_KEY:ThisIsKayashovBarSecretKey-1.0.0Version} token.signing.key=${SIGNING_KEY:ThisIsKayashovBarSecretKey-1.0.0Version}
spring.jpa.show-sql=false spring.jpa.show-sql=false
#server.port=8081