32 Commits

Author SHA1 Message Date
Kayashov.SM
060188e9e0 #2 баг загрузки ингредиента из модального окна коктейлей 2026-01-05 00:15:51 +04:00
Kayashov.SM
1fa351357b правка поиска коктейлей - переведено на стримы 2025-12-28 02:09:35 +04:00
Kayashov.SM
a84a37c1aa удален рудиментный столбец для типов ингредиентов 2025-12-28 02:06:53 +04:00
Kayashov.SM
74f8a0b864 удален рудиментный столбец для типов ингредиентов 2025-12-28 02:06:48 +04:00
Kayashov.SM
b585214f58 правка бага отображения карточки коктейля на странице калькулятора 2025-12-28 02:06:05 +04:00
Kayashov.SM
e282681679 добавлена кнопка для скрытия коктейля из списка коктейлей 2025-12-27 20:55:40 +04:00
Kayashov.SM
de96c83ddf удалены замечания реакта 2025-12-27 20:31:17 +04:00
Kayashov.SM
21e44246b9 добавлена возможность скрытия коктейля из подборки 2025-12-27 20:19:12 +04:00
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
78 changed files with 1689 additions and 1300 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,39 @@ 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 [oldId, setOldId] = useState(null);
const {createError, createSuccess, createWarning} = useAlert(); const {createError, createSuccess, createWarning} = 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() { // eslint-disable-next-line
setOpen(false) useEffect(() => barClient.getBarList(setBars, createError), []);
}
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 +48,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>
<Box>
<IconButton onClick={() => {
setOldId(b.id)
setOpen(true);
}}>
<ContentCopyIcon/>
</IconButton>
{b.active && <IconButton disabled> {b.active && <IconButton disabled>
<PowerIcon/> <PowerIcon/>
</IconButton>} </IconButton>}
{!b.active && <Box> {!b.active && <>
<IconButton onClick={() => deleteHandler(b)}> <IconButton
onClick={() => barClient.deleteBar(b, bars, createError, createSuccess, setBars)}>
<DeleteIcon/> <DeleteIcon/>
</IconButton> </IconButton>
<IconButton onClick={() => changeHandler(b)}> <IconButton
onClick={() => barClient.changeBar(b.id, bars, createWarning, createSuccess, createError, setBars)}>
<ElectricalServicesIcon/> <ElectricalServicesIcon/>
</IconButton> </IconButton>
</Box>} </>}
</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, 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';
// Стилизуем контейнер счетчика
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,10 +33,10 @@ 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([])
const chips = [];
const [page, setPage] = useState(-1); const [page, setPage] = useState(-1);
const [load, setLoad] = useState(false); const [load, setLoad] = useState(false);
const [isEnd, setIsEnd] = useState(false); const [isEnd, setIsEnd] = useState(false);
@@ -53,36 +49,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 +64,8 @@ const CocktailsPageContent = () => {
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
// eslint-disable-next-line // eslint-disable-next-line
}, [loading]); }, [loading]);
useEffect(() => {
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 // eslint-disable-next-line
}, []) useEffect(() => loading(), [filter])
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 +87,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 +113,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,16 +121,15 @@ 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) => { const hideHandler = (id) => {
api().delete(requests.cocktails.cocktail + row.id) cocktailClient.hiddenCocktail(id)
.then(() => { .then(() => {
setRows(rows.filter((r) => r.id !== row.id)) createSuccess("Коктейль скрыт успешно");
createSuccess("Коктейль удален") setRows(rows.filter((r) => r.id !== id))
}) }).catch(() => createError("Ошибка при попытке скрыть коктейль"))
.catch(() => createError("Ошибка удаления коктейля"))
} }
return ( return (
@@ -233,6 +165,7 @@ const CocktailsPageContent = () => {
handleChangeRating={handleChangeRating} handleChangeRating={handleChangeRating}
handleSelect={handleSelectCocktail} handleSelect={handleSelectCocktail}
deleteHandler={deleteHandle} deleteHandler={deleteHandle}
hideHandler={hideHandler}
/> />
) )
})} })}

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,16 @@ 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) {
setCocktail(emptyCocktail);
return;
}
api().get(requests.cocktails.cocktail + selected)
.then((r) => {
setCocktail(r.data)
})
.catch(() => getError());
// eslint-disable-next-line // eslint-disable-next-line
}, [selected]) useEffect(() => cocktailClient.getOneCocktail(selected, setCocktail, getError, emptyCocktail), [selected])
const saveHandler = () => cocktailClient.saveChangeCocktail(cocktail, createError, createSuccess)
const deleteHandle = () => cocktailClient.deleteCocktailFromEdit(setCocktails, setCocktail, createError, cocktails, cocktail, emptyCocktail)
const changeCocktailValue = (name, value) => { const changeCocktailValue = (name, value) => {
if (name === "tags") { if (name === "tags") {
@@ -127,25 +95,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 +151,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

@@ -10,9 +10,11 @@ import {CocktailDescription} from "./CocktailDescription";
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import LocalBarIcon from '@mui/icons-material/LocalBar'; import LocalBarIcon from '@mui/icons-material/LocalBar';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
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/>;
@@ -34,8 +36,8 @@ function renderRating(handleChangeRating, row) {
) )
} }
export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler}) { export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler, hideHandler}) {
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,17 +61,22 @@ 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'/>
</IconButton> </IconButton>
<IconButton size='small' onClick={() => hideHandler(row.id)}>
<VisibilityOffIcon fontSize='small'/>
</IconButton>
<IconButton size='small' onClick={() => deleteHandler(row)}> <IconButton size='small' onClick={() => deleteHandler(row)}>
<DeleteIcon fontSize='small'/> <DeleteIcon fontSize='small'/>
</IconButton> </IconButton>

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,9 @@ 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(() => {
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 // eslint-disable-next-line
}, [row]); useEffect(() => cocktailClient.getCocktailForModal(row, setLoading, setCocktail, closeCocktail, getError), [row]);
if (!row || !cocktail) { if (!row || !cocktail) {
return null; return null;
@@ -132,9 +85,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 +95,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 +114,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,10 +88,10 @@ 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}

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>*/}
{!user.name ? <MenuItem onClick={() => window.location.replace(paths.auth.signIn)}>
<ListItemIcon>
<SignInIcon fontSize="var(--icon-fontSize-md)"/>
</ListItemIcon>
Вход
</MenuItem> :
<MenuItem onClick={handleSignOut}> <MenuItem onClick={handleSignOut}>
<ListItemIcon> <ListItemIcon>
<SignOutIcon fontSize="var(--icon-fontSize-md)"/> <SignOutIcon fontSize="var(--icon-fontSize-md)"/>
</ListItemIcon> </ListItemIcon>
Выход Выход
</MenuItem> </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,216 @@
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("Ошибка удаления коктейля"))
}
async hiddenCocktail(id) {
return api().post(requests.cocktails.hide + id);
}
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.ingredient.crud}?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,52 @@ 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",
hide: routes.cocktail + "/hidden/",
},
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" unit: routes.unit + "/units"
},
users: {
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();
} else { String password = dto.getPassword();
return parseCode(dto.getCode());
}
}
private AuthResponseDto parseCode(String code) {
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()) { if (login == null || login.isEmpty() || password == null || password.isEmpty()) {
return new AuthResponseDto(null, "Поля не могут быть пустые"); return new AuthResponseDto(null, "Поля не могут быть пустые");
} }
Optional<Visitor> visitorOpt = visitorsRepository.findByLogin(login); Optional<Visitor> visitorOpt = visitorsRepository.findByLogin(login);
if (visitorOpt.isEmpty()) { if (visitorOpt.isEmpty()) {
return new AuthResponseDto(null, "Не найдет пользователь " + login); responseDto = 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 { } else {
return new AuthResponseDto(null, "Неверный логин или пароль"); visitor = visitorOpt.get();
responseDto = checkLogin(visitor, dto.getPassword());
} }
} else {
String decode = new String(Base64.getDecoder().decode(dto.getCode()), StandardCharsets.UTF_8);
String[] decodeArr = decode.split(":");
visitor = visitorsRepository.findById(Long.valueOf(decodeArr[0]))
.orElseThrow();
responseDto = parseCode(Integer.valueOf(decodeArr[1]), visitor);
} }
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,12 +56,17 @@ 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);
} }
@PostMapping("/hidden/{id}")
public void hiddenCocktail(@PathVariable Long id) {
cocktailService.hiddenCocktail(id);
}
@GetMapping @GetMapping
public CocktailForListResponseDto getOne(@RequestParam Long id) { public CocktailForListResponseDto getOne(@RequestParam Long id) {
return cocktailService.findById(id); return cocktailService.findById(id);
@@ -72,11 +77,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 +112,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

@@ -32,4 +32,12 @@ public class BarEntity {
inverseJoinColumns = @JoinColumn(name = "ingredient") inverseJoinColumns = @JoinColumn(name = "ingredient")
) )
private List<IngredientEntity> ingredients; private List<IngredientEntity> ingredients;
@ManyToMany
@JoinTable(
name = "hidden_cocktails",
joinColumns = @JoinColumn(name = "bar"),
inverseJoinColumns = @JoinColumn(name = "cocktail")
)
private List<CocktailEntity> hiddenCocktails;
} }

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

@@ -1,16 +1,15 @@
package ru.kayashov.bar.model.entity; package ru.kayashov.bar.model.entity;
import javax.persistence.Column; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List; import java.util.List;
@Entity @Entity
@@ -19,17 +18,12 @@ import java.util.List;
@Table(name = "ingredient_type") @Table(name = "ingredient_type")
@NoArgsConstructor @NoArgsConstructor
public class TypeEntity { public class TypeEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
private String name; private String name;
@Column(name = "en_name")
private String enName;
@OneToMany(mappedBy = "type") @OneToMany(mappedBy = "type")
private List<IngredientEntity> ingredients; private List<IngredientEntity> ingredients;
public TypeEntity(String enName) {
this.enName = enName;
}
} }

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

@@ -1,18 +1,11 @@
package ru.kayashov.bar.repository; package ru.kayashov.bar.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import ru.kayashov.bar.model.entity.TypeEntity; import ru.kayashov.bar.model.entity.TypeEntity;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface TypeRepository extends JpaRepository<TypeEntity, Long> { public interface TypeRepository extends JpaRepository<TypeEntity, Long> {
Optional<TypeEntity> findByEnNameIgnoreCase(String name);
Optional<TypeEntity> findByName(String name); Optional<TypeEntity> findByName(String name);
@Query("select i from TypeEntity i where upper(i.name) like upper(?1) or upper(i.enName) like upper(?1)")
List<TypeEntity> findByWord(String word);
} }

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,133 @@
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;
@Transactional
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, barEntity.getHiddenCocktails().stream().map(CocktailEntity::getId).toList()));
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<Long> hiddenCocktails) {
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())
&& !hiddenCocktails.contains(cocktail.getId())) {
cocktail.setAllowed(true);
result.add(cocktail);
}
}
}
return result;
}
}

View File

@@ -2,8 +2,6 @@ package ru.kayashov.bar.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.Session;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -14,35 +12,36 @@ 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.BarEntity;
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;
import ru.kayashov.bar.repository.BarEntityRepository;
import ru.kayashov.bar.repository.CocktailRepository; import ru.kayashov.bar.repository.CocktailRepository;
import ru.kayashov.bar.repository.IngredientRepository; import ru.kayashov.bar.repository.IngredientRepository;
import ru.kayashov.bar.repository.ReceiptRepository; import ru.kayashov.bar.repository.ReceiptRepository;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
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.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j @Slf4j
@Service @Service
@@ -50,6 +49,8 @@ import java.util.stream.Stream;
public class CocktailService { public class CocktailService {
private final CocktailRepository cocktailRepository; private final CocktailRepository cocktailRepository;
private final EventService eventService;
private final BarEntityRepository barRepository;
@Value("${cocktail.photo.path}") @Value("${cocktail.photo.path}")
private String photoFolder; private String photoFolder;
@@ -67,174 +68,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);
} }
private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) { @Transactional
Session session = entityManager.unwrap(Session.class); public List<CocktailForListResponseDto> calc() {
CriteriaBuilder cb = session.getCriteriaBuilder(); CocktailFilterRequestDto dto = new CocktailFilterRequestDto();
CriteriaQuery<CocktailEntity> criteriaQuery = cb.createQuery(CocktailEntity.class); dto.setPage(0);
Root<CocktailEntity> root = criteriaQuery.from(CocktailEntity.class); dto.setSize(1000);
List<Predicate> predicates = new ArrayList<>(); dto.setAll(false);
dto.setOnlyFavourite(false);
dto.setSort(SortingEnum.NAME_ASC);
criteriaQuery.distinct(true); return mapper.cocktailsToDtoList(criteria(dto), false, true);
if (!dto.getAll()) {
// List<Long> cocktailIds = findICountCocktailIds(0, new ArrayList<>());
List<Long> cocktailIds = findCocktailByCountNotHaveIngredient();
Predicate pr = root.get("id").in(cocktailIds);
predicates.add(pr);
}
if (!dto.getSearch().isEmpty()) {
String[] search = dto.getSearch().split(" ");
List<Predicate> in = new ArrayList<>();
Join<CocktailEntity, ReceiptEntity> receiptJoin = root.join("receipt", JoinType.LEFT);
for (String s : search) {
in.add(cb.like(cb.lower(root.get("name")), "%" + s.toLowerCase() + "%"));
in.add(cb.like(cb.lower(receiptJoin.get("ingredient").get("name")), "%" + s.toLowerCase() + "%"));
}
predicates.add(cb.or(in.toArray(new Predicate[0])));
}
if (dto.getOnlyFavourite()) {
predicates.add(cb.isTrue(root.get("favourite")));
}
if (dto.getGlass() != null && !dto.getGlass().isEmpty()) {
predicates.add(root.get("glass").in(dto.getGlass().stream().map(Glass::findValue).toList()));
}
if (dto.getCategory() != null && !dto.getCategory().isEmpty()) {
predicates.add(root.get("category").in(dto.getCategory().stream().map(Category::findValue).toList()));
}
if (dto.getAlcohol() != null && !dto.getAlcohol().isEmpty()) {
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: доделать другие виды сортировки
Order order;
switch (dto.getSort()) {
case NAME_ASC -> order = cb.asc(root.get("name"));
case NAME_DESC -> order = cb.desc(root.get("name"));
default -> order = cb.asc(root.get("name"));
}
criteriaQuery.where(predicates.toArray(new Predicate[0]))
.orderBy(order);
Query<CocktailEntity> query = session.createQuery(criteriaQuery);
query.setFirstResult(dto.getPage() * dto.getSize());
query.setMaxResults(dto.getSize());
List<CocktailEntity> cocktailEntities = query.getResultList();
log.info("Найдено {} коктейлей", cocktailEntities.size());
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) {
@@ -256,6 +102,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 +115,127 @@ 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 List<CocktailForIngredientModalDto> findByIngredient(Long id) {
return ingredientRepository.findById(id).orElseThrow(RuntimeException::new)
.getReceipts()
.stream()
.map(ReceiptEntity::getCocktail)
.distinct()
.map(mapper::cocktailToIngredientDtoList)
.toList();
}
public void hiddenCocktail(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
BarEntity bar = barRepository.findByActiveTrue().orElseThrow();
bar.getHiddenCocktails().add(cocktail);
barRepository.save(bar);
cocktail.setAllowed(false);
repository.save(cocktail);
log.info("Коктейль {} - {} был скрыт для бара {} - {}", cocktail.getId(), cocktail.getName(), bar.getId(), bar.getName());
}
public void drink(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
int count = cocktail.getCountDrink() + 1;
cocktail.setCountDrink(count);
repository.save(cocktail);
eventService.createEvent(Event.builder()
.type(EventType.DRINK)
.date(LocalDateTime.now())
.oldState(cocktail.getName())
.newState(String.valueOf(count))
.build());
}
//todo: написать функцию в БД, чтобы ускорить работу и сохранить читаемый код
private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) {
Instant now = Instant.now();
List<CocktailEntity> cocktails = repository.findAll()
.stream()
//todo: доделать другие виды сортировки
.sorted(Comparator.comparing(CocktailEntity::getName))
.filter(c -> c.getAllowed() || dto.getAll())
.filter(c -> c.getIsFavorite() || !dto.getOnlyFavourite())
.filter(c -> isEmpty(dto.getGlass()) || dto.getGlass().contains(c.getGlass().getName()))
.filter(c -> isEmpty(dto.getCategory()) || dto.getCategory().contains(c.getCategory().getName()))
.filter(c -> isEmpty(dto.getAlcohol()) || dto.getAlcohol().contains(c.getAlcoholic().getValue()))
.filter(c -> dto.getSearch().isEmpty() || textSearchFilter(c, dto.getSearch()))
.skip((long) dto.getPage() * dto.getSize())
.limit(dto.getSize())
.toList();
log.info("Найдено {} коктейлей за {} ms", cocktails.size(), Duration.between(now, Instant.now()).toMillis());
return cocktails;
}
private boolean textSearchFilter(CocktailEntity cocktail, String founding) {
String[] search = founding.split(" ");
if (Arrays.stream(search).anyMatch(s -> cocktail.getName().toLowerCase().contains(s.toLowerCase()))) {
return true;
}
Set<String> ingredientsNames = cocktail.getReceipt().stream()
.map(ReceiptEntity::getIngredient)
.map(IngredientEntity::getName)
.map(String::toLowerCase)
.collect(Collectors.toSet());
return Arrays.stream(search).anyMatch(s -> ingredientsNames.contains(s.toLowerCase()));
}
private boolean isEmpty(List<?> value) {
return value == null || value.isEmpty();
}
//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 +283,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 +300,65 @@ public class CocktailService {
return sb.toString(); return sb.toString();
} }
public void delete(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
log.info("Удален коктейль {}", cocktail);
repository.delete(cocktail);
}
public String findInstructions(Long id) { //todo: оставить до написания функции в БД
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new); // private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) {
return cocktail.getInstructions(); // Instant now = Instant.now();
} // Session session = entityManager.unwrap(Session.class);
// CriteriaBuilder cb = session.getCriteriaBuilder();
// CriteriaQuery<CocktailEntity> criteriaQuery = cb.createQuery(CocktailEntity.class);
// Root<CocktailEntity> root = criteriaQuery.from(CocktailEntity.class);
// List<Predicate> predicates = new ArrayList<>();
//
// criteriaQuery.distinct(true);
// if (!dto.getAll()) {
// Predicate pr = cb.isTrue(root.get("allowed"));
// predicates.add(pr);
// }
//
// if (!dto.getSearch().isEmpty()) {
// String[] search = dto.getSearch().split(" ");
// List<Predicate> in = new ArrayList<>();
// Join<CocktailEntity, ReceiptEntity> receiptJoin = root.join("receipt", JoinType.LEFT);
// for (String s : search) {
// in.add(cb.like(cb.lower(root.get("name")), "%" + s.toLowerCase() + "%"));
// in.add(cb.like(cb.lower(receiptJoin.get("ingredient").get("name")), "%" + s.toLowerCase() + "%"));
// }
// predicates.add(cb.or(in.toArray(new Predicate[0])));
// }
//
// if (dto.getOnlyFavourite()) {
// predicates.add(cb.isTrue(root.get("isFavorite")));
// }
//
// if (dto.getGlass() != null && !dto.getGlass().isEmpty()) {
// predicates.add(root.get("glass").in(dto.getGlass().stream().map(Glass::findValue).toList()));
// }
//
// if (dto.getCategory() != null && !dto.getCategory().isEmpty()) {
// predicates.add(root.get("category").in(dto.getCategory().stream().map(Category::findValue).toList()));
// }
//
// if (dto.getAlcohol() != null && !dto.getAlcohol().isEmpty()) {
// predicates.add(root.get("alcoholic").in(dto.getAlcohol().stream().map(Alcoholic::findValue).toList()));
// }
public List<CocktailForIngredientModalDto> findByIngredient(Long id) { // //todo: доделать другие виды сортировки
return ingredientRepository.findById(id).orElseThrow(RuntimeException::new) // Order order;
.getReceipts() // switch (dto.getSort()) {
.stream() // case NAME_ASC -> order = cb.asc(root.get("name"));
.map(ReceiptEntity::getCocktail) // case NAME_DESC -> order = cb.desc(root.get("name"));
.distinct() // default -> order = cb.asc(root.get("name"));
.map(mapper::cocktailToIngredientDtoList) // }
.toList(); //
} // criteriaQuery.where(predicates.toArray(new Predicate[0]))
// .orderBy(order);
// Query<CocktailEntity> query = session.createQuery(criteriaQuery);
// query.setFirstResult(dto.getPage() * dto.getSize());
// query.setMaxResults(dto.getSize());
// List<CocktailEntity> cocktailEntities = query.getResultList();
//
// log.info("Найдено {} коктейлей за {} ms", cocktailEntities.size(), Duration.between(now, Instant.now()).toMillis());
// return cocktailEntities;
// }
} }

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,26 +62,45 @@ 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 {
entity = repository.findById(dto.getId())
.orElse(null); .orElse(null);
if (entity == null) { if (entity == null) {
return false; return false;
} }
}
entity.setName(dto.getName()); entity.setName(dto.getName());
entity.setDescription(dto.getDescription()); entity.setDescription(dto.getDescription());
@@ -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
@@ -16,3 +16,5 @@ 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