18 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
24 changed files with 281 additions and 140 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

@@ -12,23 +12,35 @@ 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"; 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();
const createHandler = (name) => barClient.createBar(name, bars, createSuccess, createError, setBars, setOpen) const createHandler = (id, name) => {
if (id) {
barClient.copyBar(id, name, setBars, bars, createError, createSuccess, setOpen)
} else {
barClient.createBar(name, bars, createSuccess, createError, setBars, setOpen)
}
}
// eslint-disable-next-line
useEffect(() => barClient.getBarList(setBars, createError), []); useEffect(() => barClient.getBarList(setBars, createError), []);
return (<> return (<>
<BarCreateModal open={open} setOpen={setOpen} 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>
@@ -36,18 +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>
{b.active && <IconButton disabled> <Box>
<PowerIcon/> <IconButton onClick={() => {
</IconButton>} setOldId(b.id)
{!b.active && <Box> setOpen(true);
<IconButton onClick={() => barClient.deleteBar(b, bars, createError, createSuccess, setBars)}> }}>
<DeleteIcon/> <ContentCopyIcon/>
</IconButton> </IconButton>
<IconButton {b.active && <IconButton disabled>
onClick={() => barClient.changeBar(b.id, bars, createWarning, createSuccess, createError, setBars)}> <PowerIcon/>
<ElectricalServicesIcon/> </IconButton>}
</IconButton> {!b.active && <>
</Box>} <IconButton
onClick={() => barClient.deleteBar(b, bars, createError, createSuccess, setBars)}>
<DeleteIcon/>
</IconButton>
<IconButton
onClick={() => barClient.changeBar(b.id, bars, createWarning, createSuccess, createError, setBars)}>
<ElectricalServicesIcon/>
</IconButton>
</>}
</Box>
</Stack> </Stack>
</Card> </Card>
})} })}

View File

@@ -12,7 +12,7 @@ export function CalcPage() {
const {createError} = useAlert(); const {createError} = useAlert();
const [cocktails, setCocktails] = React.useState([]); const [cocktails, setCocktails] = React.useState([]);
const [load, setLoad] = React.useState(false); const [load, setLoad] = React.useState(false);
const [cocktailMap, setCocktailMap] = React.useState([]); const [cocktailMap, setCocktailMap] = React.useState({});
const changeHandler = (id, value) => { const changeHandler = (id, value) => {
setCocktailMap((prev) => ({ setCocktailMap((prev) => ({
@@ -67,6 +67,8 @@ export function CalcPage() {
setCocktails(state); setCocktails(state);
} }
console.log(cocktailMap)
return ( return (
<Box padding={2}> <Box padding={2}>
<Typography variant="h4" align="center">Коктейли</Typography> <Typography variant="h4" align="center">Коктейли</Typography>

View File

@@ -9,7 +9,7 @@ import {Counter} from "./Counter";
export function CocktailItemCalc({cocktail, deleteHandler, changeHandler}) { export function CocktailItemCalc({cocktail, deleteHandler, changeHandler}) {
return ( return (
<Card sx={{mb: 1, height: '250px', display: 'relative', p: 2}}> <Card sx={{mb: 1, display: 'relative', p: 2}}>
<Stack justifyContent={'start'} spacing={2}> <Stack justifyContent={'start'} spacing={2}>
<Stack direction='row' justifyContent='start' alignItems='center'> <Stack direction='row' justifyContent='start' alignItems='center'>
<Box sx={{width: '100px', height: '100px'}}> <Box sx={{width: '100px', height: '100px'}}>

View File

@@ -3,7 +3,7 @@ import {Box, TextField, Button} from '@mui/material';
import {styled} from '@mui/material/styles'; import {styled} from '@mui/material/styles';
// Стилизуем контейнер счетчика // Стилизуем контейнер счетчика
const CounterContainer = styled(Box)` styled(Box)`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@@ -35,7 +35,8 @@ const CocktailsPageContent = () => {
const {createError, createSuccess} = useAlert(); const {createError, createSuccess} = useAlert();
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);
@@ -63,6 +64,7 @@ const CocktailsPageContent = () => {
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
// eslint-disable-next-line // eslint-disable-next-line
}, [loading]); }, [loading]);
// eslint-disable-next-line
useEffect(() => loading(), [filter]) useEffect(() => loading(), [filter])
const renderSkeleton = () => { const renderSkeleton = () => {
@@ -122,6 +124,13 @@ const CocktailsPageContent = () => {
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) => cocktailClient.deleteCocktail(row.id, rows, setRows, createSuccess, createError)
const hideHandler = (id) => {
cocktailClient.hiddenCocktail(id)
.then(() => {
createSuccess("Коктейль скрыт успешно");
setRows(rows.filter((r) => r.id !== id))
}).catch(() => createError("Ошибка при попытке скрыть коктейль"))
}
return ( return (
<Box> <Box>
@@ -156,6 +165,7 @@ const CocktailsPageContent = () => {
handleChangeRating={handleChangeRating} handleChangeRating={handleChangeRating}
handleSelect={handleSelectCocktail} handleSelect={handleSelectCocktail}
deleteHandler={deleteHandle} deleteHandler={deleteHandle}
hideHandler={hideHandler}
/> />
) )
})} })}

View File

@@ -81,6 +81,7 @@ export function EditCocktailPage() {
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
// eslint-disable-next-line
useEffect(() => cocktailClient.getOneCocktail(selected, setCocktail, getError, emptyCocktail), [selected]) useEffect(() => cocktailClient.getOneCocktail(selected, setCocktail, getError, emptyCocktail), [selected])
const saveHandler = () => cocktailClient.saveChangeCocktail(cocktail, createError, createSuccess) const saveHandler = () => cocktailClient.saveChangeCocktail(cocktail, createError, createSuccess)
const deleteHandle = () => cocktailClient.deleteCocktailFromEdit(setCocktails, setCocktail, createError, cocktails, cocktail, emptyCocktail) const deleteHandle = () => cocktailClient.deleteCocktailFromEdit(setCocktails, setCocktail, createError, cocktails, cocktail, emptyCocktail)

View File

@@ -8,7 +8,7 @@ import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
export function BarCreateModal({open, setOpen, create}) { export function BarCreateModal({open, setOpen, create, id}) {
const [value, setValue] = useState(""); const [value, setValue] = useState("");
return ( return (
<Dialog fullWidth={true} <Dialog fullWidth={true}
@@ -26,13 +26,17 @@ export function BarCreateModal({open, setOpen, 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

@@ -10,6 +10,7 @@ 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";
@@ -35,7 +36,7 @@ function renderRating(handleChangeRating, row) {
) )
} }
export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler}) { export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler, hideHandler}) {
const {createError, createSuccess} = useAlert(); const {createError, createSuccess} = useAlert();
const {user} = useUser(); const {user} = useUser();
return ( return (
@@ -73,6 +74,9 @@ export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect
<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

@@ -30,6 +30,7 @@ export function CocktailInfoModal({row}) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const {closeCocktail, selectIngredient, getIngredient, getOpenCocktail} = useSelect(); const {closeCocktail, selectIngredient, getIngredient, getOpenCocktail} = useSelect();
// eslint-disable-next-line
useEffect(() => cocktailClient.getCocktailForModal(row, setLoading, setCocktail, closeCocktail, getError), [row]); useEffect(() => cocktailClient.getCocktailForModal(row, setLoading, setCocktail, closeCocktail, getError), [row]);
if (!row || !cocktail) { if (!row || !cocktail) {

View File

@@ -88,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

@@ -58,6 +58,17 @@ class BarClient {
setOpen(false) setOpen(false)
}).catch(() => createError("Ошибка создания списка")) }).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(); export const barClient = new BarClient();

View File

@@ -76,8 +76,13 @@ class CocktailClient {
return; return;
} }
setCocktails(data); setCocktails(data);
let map = []; let map = {};
map = data.map((d) => map[d.id] = 1) data.forEach((d) => {
map = {
...map,
[d.id]: 1
}
})
setCocktailMap(map); setCocktailMap(map);
setLoad(true); setLoad(true);
}) })
@@ -114,6 +119,10 @@ class CocktailClient {
.catch(() => createError("Ошибка удаления коктейля")) .catch(() => createError("Ошибка удаления коктейля"))
} }
async hiddenCocktail(id) {
return api().post(requests.cocktails.hide + id);
}
saveChangeCocktail (cocktail, createError, createSuccess) { saveChangeCocktail (cocktail, createError, createSuccess) {
api().patch(requests.cocktails.basic, cocktail) api().patch(requests.cocktails.basic, cocktail)
.then((r) => { .then((r) => {

View File

@@ -70,7 +70,7 @@ class IngredientClient {
} }
findOne(id, selectIngredient, createError) { findOne(id, selectIngredient, createError) {
api().get(`${requests.bar.ingredient}?id=${id}`) api().get(`${requests.ingredient.crud}?id=${id}`)
.then((r) => { .then((r) => {
selectIngredient(r.data) selectIngredient(r.data)
}) })

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

@@ -38,6 +38,7 @@ export const requests = {
favourite: routes.cocktail + "/favourite?id=", favourite: routes.cocktail + "/favourite?id=",
rating: routes.cocktail + "/rating?id=", rating: routes.cocktail + "/rating?id=",
drink: routes.cocktail + "/drink", drink: routes.cocktail + "/drink",
hide: routes.cocktail + "/hidden/",
}, },
glass: { glass: {
list: routes.glass, list: routes.glass,

View File

@@ -28,6 +28,11 @@ public class BarController {
barService.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) {
barService.deleteBar(id); barService.deleteBar(id);

View File

@@ -62,6 +62,11 @@ public class CocktailController {
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);

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

@@ -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

@@ -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

@@ -30,6 +30,7 @@ public class BarService {
private final CocktailRepository cocktailRepository; private final CocktailRepository cocktailRepository;
private final EventService eventService; private final EventService eventService;
@Transactional
public void changeActiveBar(Long id) { public void changeActiveBar(Long id) {
Optional<BarEntity> lastBarOpt = barEntityRepository.findByActiveTrue(); Optional<BarEntity> lastBarOpt = barEntityRepository.findByActiveTrue();
if (lastBarOpt.isPresent()) { if (lastBarOpt.isPresent()) {
@@ -55,7 +56,7 @@ public class BarService {
.map(ingredientRepository::save) .map(ingredientRepository::save)
.toList(); .toList();
cocktailRepository.saveAll(findAllowedCocktails(ingredients)); cocktailRepository.saveAll(findAllowedCocktails(ingredients, barEntity.getHiddenCocktails().stream().map(CocktailEntity::getId).toList()));
barEntityRepository.save(barEntity); barEntityRepository.save(barEntity);
@@ -92,7 +93,27 @@ public class BarService {
return BarResponseDto.mapToDto(bar); return BarResponseDto.mapToDto(bar);
} }
private List<CocktailEntity> findAllowedCocktails(List<IngredientEntity> ingredients) { @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<>(); List<CocktailEntity> result = new ArrayList<>();
for (IngredientEntity ingredient : ingredients) { for (IngredientEntity ingredient : ingredients) {
List<CocktailEntity> cocktails = ingredient.getReceipts().stream() List<CocktailEntity> cocktails = ingredient.getReceipts().stream()
@@ -100,7 +121,8 @@ public class BarService {
.toList(); .toList();
for (CocktailEntity cocktail : cocktails) { for (CocktailEntity cocktail : cocktails) {
if (cocktail.getReceipt().stream().allMatch(r -> r.getIngredient().getIsHave())) { if (cocktail.getReceipt().stream().allMatch(r -> r.getIngredient().getIsHave())
&& !hiddenCocktails.contains(cocktail.getId())) {
cocktail.setAllowed(true); cocktail.setAllowed(true);
result.add(cocktail); result.add(cocktail);
} }

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;
@@ -18,6 +16,7 @@ import ru.kayashov.bar.controller.dto.cocktail.SortingEnum;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto; 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.Event;
@@ -25,25 +24,24 @@ 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.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
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.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;
@Slf4j @Slf4j
@Service @Service
@@ -52,6 +50,7 @@ public class CocktailService {
private final CocktailRepository cocktailRepository; private final CocktailRepository cocktailRepository;
private final EventService eventService; private final EventService eventService;
private final BarEntityRepository barRepository;
@Value("${cocktail.photo.path}") @Value("${cocktail.photo.path}")
private String photoFolder; private String photoFolder;
@@ -84,65 +83,6 @@ public class CocktailService {
return mapper.cocktailsToDtoList(criteria(dto), false, true); return mapper.cocktailsToDtoList(criteria(dto), false, true);
} }
private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) {
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()));
}
//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;
}
public CocktailForListResponseDto findById(Long id) { public CocktailForListResponseDto findById(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(); CocktailEntity cocktail = repository.findById(id).orElseThrow();
return mapper.cocktailToFullDto(cocktail); return mapper.cocktailToFullDto(cocktail);
@@ -219,11 +159,6 @@ public class CocktailService {
repository.delete(cocktail); repository.delete(cocktail);
} }
public String findInstructions(Long id) {
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
return cocktail.getInstructions();
}
public List<CocktailForIngredientModalDto> findByIngredient(Long id) { public List<CocktailForIngredientModalDto> findByIngredient(Long id) {
return ingredientRepository.findById(id).orElseThrow(RuntimeException::new) return ingredientRepository.findById(id).orElseThrow(RuntimeException::new)
.getReceipts() .getReceipts()
@@ -234,6 +169,72 @@ public class CocktailService {
.toList(); .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: попробовать отыграть эту связку каскадами //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) {
@@ -299,18 +300,65 @@ public class CocktailService {
return sb.toString(); return sb.toString();
} }
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() //todo: оставить до написания функции в БД
.type(EventType.DRINK) // private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) {
.date(LocalDateTime.now()) // Instant now = Instant.now();
.oldState(cocktail.getName()) // Session session = entityManager.unwrap(Session.class);
.newState(String.valueOf(count)) // CriteriaBuilder cb = session.getCriteriaBuilder();
.build()); // 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()));
// }
} // //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("Найдено {} коктейлей за {} ms", cocktailEntities.size(), Duration.between(now, Instant.now()).toMillis());
// return cocktailEntities;
// }
} }

View File

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