From e9a2ebd2c0ebe6437247c4910cecca0bd21c3032 Mon Sep 17 00:00:00 2001 From: jwkaterina Date: Tue, 16 Apr 2024 14:44:34 +0200 Subject: [PATCH] get menus and recipes inside --- functions/db-controllers/food-controllers.js | 2 +- functions/db-controllers/menu-controllers.js | 4 +- .../db-controllers/recipe-controllers.js | 103 +++++++++++++++++- functions/models/menu.js | 65 +---------- functions/routes/food-routes.js | 2 +- functions/routes/menu-routes.js | 4 +- functions/routes/recipe-routes.js | 8 +- .../analysis/components/form/menu-form.tsx | 8 +- .../analysis/components/form/recipe-form.tsx | 32 +----- .../components/form/recipe-select.tsx | 7 +- .../navigation/menus/openanalysis-menu.tsx | 22 +++- .../components/slider/slides/menu-slide.tsx | 40 ++++++- 12 files changed, 186 insertions(+), 111 deletions(-) diff --git a/functions/db-controllers/food-controllers.js b/functions/db-controllers/food-controllers.js index d19a426..a0b8cc4 100644 --- a/functions/db-controllers/food-controllers.js +++ b/functions/db-controllers/food-controllers.js @@ -96,7 +96,7 @@ const createFood = async (req, res, next) => { }; const deleteFood = async (req, res, next) => { - const foodId = req.params.pid; + const foodId = req.params.id; let food; try { diff --git a/functions/db-controllers/menu-controllers.js b/functions/db-controllers/menu-controllers.js index 540a3db..49298e2 100644 --- a/functions/db-controllers/menu-controllers.js +++ b/functions/db-controllers/menu-controllers.js @@ -101,7 +101,7 @@ const createMenu = async (req, res, next) => { const updateMenu = async (req, res, next) => { const { updatedMenu } = req.body; - const menuId = req.params.pid; + const menuId = req.params.id; console.log(updatedMenu); let menu; @@ -146,7 +146,7 @@ const updateMenu = async (req, res, next) => { }; const deleteMenu = async (req, res, next) => { - const menuId = req.params.pid; + const menuId = req.params.id; let menu; try { diff --git a/functions/db-controllers/recipe-controllers.js b/functions/db-controllers/recipe-controllers.js index 4eab911..19c470a 100644 --- a/functions/db-controllers/recipe-controllers.js +++ b/functions/db-controllers/recipe-controllers.js @@ -7,7 +7,7 @@ const Recipe = require('../models/recipe'); const User = require('../models/user'); const gcpStorage = require('../storage-controllers/gcpStorage-controllers'); -const getRecipes = async (req, res, next) => { +const getAllRecipes = async (req, res, next) => { const userId = req.userData.userId; let userWithRecipe; @@ -36,6 +36,33 @@ const getRecipes = async (req, res, next) => { }); }; +const getRecipesById = async (req, res, next) => { + const recipeID = req.params.id; + let recipe; + try { + recipe = await Recipe.findById(recipeID); + } catch (err) { + console.error(err); + const error = new HttpError( + 'Could not find recipe. Try again later.', + 500 + ); + return next(error); + } + + if (!recipe) { + console.error('Could not find recipe by id.'); + return next( + new HttpError('Could not find recipe. Try again later.', 404) + ); + } + + res.json({ + recipe: recipe.toObject({ getters: true }) + }); + +} + const createRecipe = async (req, res, next) => { const { recipe } = req.body; @@ -106,7 +133,7 @@ const updateRecipe = async (req, res, next) => { const updatedRecipe = JSON.parse(recipeString); const updatedImage = req.image && req.image.url; - const recipeId = req.params.pid; + const recipeId = req.params.id; let recipe; try { @@ -158,7 +185,7 @@ const updateRecipe = async (req, res, next) => { }; const deleteRecipe = async (req, res, next) => { - const recipeId = req.params.pid; + const recipeId = req.params.id; let recipe; try { @@ -211,10 +238,78 @@ const deleteRecipe = async (req, res, next) => { } } + try { + await modifyMenus(recipeId, recipe.creator, next); + } catch(err) { + console.error(err); + const error = new HttpError( + 'Could not modify menu. Try again later.', + 500 + ); + return next(error); + } + res.status(200).json({ message: 'Deleted recipe.' }); }; -exports.getRecipes = getRecipes; +const modifyMenus = async(recipeId, user, next) => { + let userWithMenu; + try { + userWithMenu = await User.findById(user.id).populate('menus'); + } catch (err) { + console.error(err); + const error = new HttpError( + 'Could not find menu. Try again later.', + 500 + ); + return next(error); + } + + if (!userWithMenu) { + console.error('Could not find menu by id.') + return next( + new HttpError('Could not find menu. Try again later.', 404) + ); + } + userWithMenu.menus.forEach(async(menu) => { + menu.menu.recipes = menu.menu.recipes.filter(recipe => recipe.selectedRecipe != recipeId); + if(menu.menu.recipes.length == 0 && menu.menu.ingredients.length == 0) { + console.log('empty menu'); + try { + const sess = await mongoose.startSession(); + sess.startTransaction(); + await menu.deleteOne({ session: sess }); + user.menus.pull(menu); + await user.save({ session: sess }); + await sess.commitTransaction(); + return next(); + + } catch (err) { + console.error(err); + const error = new HttpError( + 'Could not delete empty menu. Try again later.', + 500 + ); + return next(error); + } + } + try { + await menu.save(); + } catch (err) { + console.error(err); + const error = new HttpError( + 'Could not update menu in favorites. Try again later.', + 500 + ); + return next(error); + } + }); + + next(); +} + +exports.getAllRecipes = getAllRecipes; +exports.getRecipesById = getRecipesById; exports.createRecipe = createRecipe; exports.updateRecipe = updateRecipe; exports.deleteRecipe = deleteRecipe; diff --git a/functions/models/menu.js b/functions/models/menu.js index 31ad853..aebe70d 100644 --- a/functions/models/menu.js +++ b/functions/models/menu.js @@ -2,73 +2,16 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; -const Nutrient = new Schema({ - label: { type: String, required: true }, - quantity: { type: Number, required: true }, - unit: { type: String, required: true } -}) - -const Nutrients = new Schema({ - CA: { type: Nutrient, required: false }, - CHOCDF: { type: Nutrient, required: false }, - CHOLE: { type: Nutrient, required: false }, - ENERC_KCAL: { type: Nutrient, required: false }, - FAMS: { type: Nutrient, required: false }, - FAPU: { type: Nutrient, required: false }, - FASAT: { type: Nutrient, required: false }, - FAT: { type: Nutrient, required: false }, - FATRN: { type: Nutrient, required: false }, - FE: { type: Nutrient, required: false }, - FIBTG: { type: Nutrient, required: false }, - FOLAC: { type: Nutrient, required: false }, - FOLDFE: { type: Nutrient, required: false }, - FOLFD: { type: Nutrient, required: false }, - K: { type: Nutrient, required: false }, - MG: { type: Nutrient, required: false }, - NA: { type: Nutrient, required: false }, - NIA: { type: Nutrient, required: false }, - P: { type: Nutrient, required: false }, - PROCNT: { type: Nutrient, required: false }, - RIBF: { type: Nutrient, required: false }, - SUGAR: { type: Nutrient, required: false }, - THIA: { type: Nutrient, required: false }, - TOCPHA: { type: Nutrient, required: false }, - VITA_RAE: { type: Nutrient, required: false }, - VITB12: { type: Nutrient, required: false }, - VITB6A: { type: Nutrient, required: false }, - VITC: { type: Nutrient, required: false }, - VITD: { type: Nutrient, required: false }, - VITK1: { type: Nutrient, required: false }, - WATER: { type: Nutrient, required: false }, - ZN: { type: Nutrient, required: false } -}) - -const Recipe = new Schema({ - selectedRecipe: { - name: { type: String, required: true }, - image: { type: String, required: false }, - servings: { type: Number, required: true }, - ingredients: [{ type: String, required: true }], - nutrients: { - calories: { type: Number, required: true }, - totalNutrients: { type: Nutrients, required: true }, - totalDaily: { type: Nutrients, required: true }, - totalWeight: { type: Number, required: true } - }}, - selectedServings: { type: Number, required: false }, +const MenuRecipe = new Schema({ + selectedRecipe: { type: mongoose.Types.ObjectId, required: true, ref: 'Recipe'}, + selectedServings: { type: Number, required: true }, }) const menuSchema = new Schema({ menu:{ name: { type: String, required: true }, ingredients: [{ type: String, required: false }], - nutrients: { - calories: { type: Number, required: true }, - totalNutrients: { type: Nutrients, required: true }, - totalDaily: { type: Nutrients, required: true }, - totalWeight: { type: Number, required: true } - }, - recipes: [{ type: Recipe, required: false}], + recipes: [{ type: MenuRecipe, required: false}], }, creator: { type: mongoose.Types.ObjectId, required: true, ref: 'User'} }); diff --git a/functions/routes/food-routes.js b/functions/routes/food-routes.js index ca39343..1937306 100644 --- a/functions/routes/food-routes.js +++ b/functions/routes/food-routes.js @@ -12,6 +12,6 @@ router.post( '/', foodControllers.createFood ); -router.delete('/:pid', foodControllers.deleteFood); +router.delete('/:id', foodControllers.deleteFood); module.exports = router; diff --git a/functions/routes/menu-routes.js b/functions/routes/menu-routes.js index 3814a83..01d4ac4 100644 --- a/functions/routes/menu-routes.js +++ b/functions/routes/menu-routes.js @@ -12,8 +12,8 @@ router.post( '/', menuControllers.createMenu ); -router.patch('/:pid', menuControllers.updateMenu); +router.patch('/:id', menuControllers.updateMenu); -router.delete('/:pid', menuControllers.deleteMenu); +router.delete('/:id', menuControllers.deleteMenu); module.exports = router; diff --git a/functions/routes/recipe-routes.js b/functions/routes/recipe-routes.js index a4a7e1c..0eb694c 100644 --- a/functions/routes/recipe-routes.js +++ b/functions/routes/recipe-routes.js @@ -8,7 +8,9 @@ const checkAuth = require('../middleware/check-auth'); const router = express.Router(); router.use(checkAuth); -router.get('/', recipeControllers.getRecipes); +router.get('/', recipeControllers.getAllRecipes); + +router.get('/:id', recipeControllers.getRecipesById); router.post( '/', @@ -19,13 +21,13 @@ router.post( ); router.patch( - '/:pid', + '/:id', multer.all, compress.compressFile, gcpStorageControllers.putImage, recipeControllers.updateRecipe ); -router.delete('/:pid', recipeControllers.deleteRecipe); +router.delete('/:id', recipeControllers.deleteRecipe); module.exports = router; diff --git a/src/app/analysis/components/form/menu-form.tsx b/src/app/analysis/components/form/menu-form.tsx index 38ef6ba..f4ac8aa 100644 --- a/src/app/analysis/components/form/menu-form.tsx +++ b/src/app/analysis/components/form/menu-form.tsx @@ -52,7 +52,6 @@ const MenuForm = ({ searchCleared, setClearSearch }: MenuFormProps): JSX.Element setMessage('You do not have any favorite recipes.'); } else { setLoadedRecipes(recipes); - setInputsnumber(inputsnumber + 1); } } catch (err) { setIsLoading(false); @@ -82,7 +81,9 @@ const MenuForm = ({ searchCleared, setClearSearch }: MenuFormProps): JSX.Element setInputsnumber(currentMenu.menu.recipes.length); setCurrentRecipes(currentMenu.menu.recipes); } - if(currentMenu.menu && currentMenu.mode == AnalysisMode.EDIT && currentMenu.menu.recipes.length > 0) fetchRecipes(); + if(currentMenu.menu && currentMenu.mode == AnalysisMode.EDIT && currentMenu.menu.recipes.length > 0) { + fetchRecipes(); + } }, [currentMenu]); const ArrayfromString = (string: string): string[] => { @@ -146,14 +147,13 @@ const MenuForm = ({ searchCleared, setClearSearch }: MenuFormProps): JSX.Element } const handleAddRecipe = async() => { - console.log(loadedRecipes); - console.log(currentRecipes); if(!token) { setStatus(StatusType.ERROR); setMessage('You need to be logged in to add a recipe'); return; } fetchRecipes(); + setInputsnumber(inputsnumber + 1); } if(currentMenu.menu && cardOpen == CardState.OPEN) return ( diff --git a/src/app/analysis/components/form/recipe-form.tsx b/src/app/analysis/components/form/recipe-form.tsx index 5605d68..26bdd33 100644 --- a/src/app/analysis/components/form/recipe-form.tsx +++ b/src/app/analysis/components/form/recipe-form.tsx @@ -99,7 +99,7 @@ const RecipeForm = ({ searchCleared, setClearSearch, setFile }: RecipeFormProps) setIsLoading(false); return; } - if(await menusWithRecipe()) return; + if(!confirmed()) return; try { await sendRequest( @@ -119,35 +119,13 @@ const RecipeForm = ({ searchCleared, setClearSearch, setFile }: RecipeFormProps) } catch (err) {} } - const menusWithRecipe = async() => { - if(deleteReady) return false; - - const responseData = await sendRequest( - `/menus`,'GET', null, { - Authorization: 'Bearer ' + token - }, true, false - ); - const menus = () => { - if (!responseData.menus || !responseData.menus.length) { - return null; - } - for (const menu of responseData.menus) { - if (menu.menu.recipes.length > 0) { - const matchingRecipe = menu.menu.recipes.find((recipe: RecipeWithServings) => recipe.selectedRecipe.name === currentRecipe.recipe?.name); - if (matchingRecipe) { - return menu; - } - } - } - //TODO: delete menu - return null; - } - if(!menus()) return false; + const confirmed = () => { + if(deleteReady) return true; setStatus(StatusType.ERROR); - setMessage('Menus with this recipe will be deleted as well. If you agree press delete button again'); + setMessage('Menus with this recipe will be modified. If you agree press delete button again'); setDeleteReady(true); - return true; + return false; } const handleNameInput = (e: React.FormEvent) => { diff --git a/src/app/analysis/components/form/recipe-select.tsx b/src/app/analysis/components/form/recipe-select.tsx index f99061f..3040c42 100644 --- a/src/app/analysis/components/form/recipe-select.tsx +++ b/src/app/analysis/components/form/recipe-select.tsx @@ -16,6 +16,7 @@ const RecipeSelect = ({ inputs, currentRecipes, setCurrentRecipes, loadedRecipes if (loadedRecipes && loadedRecipes.length > 0) { if (inputs > currentRecipes.length) { const newRecipes: RecipeWithServings[] = Array(inputs - currentRecipes.length).fill({ + selectedRecipeId: loadedRecipes[0].id, selectedRecipe: loadedRecipes[0].recipe, selectedServings: 1 }); @@ -28,9 +29,10 @@ const RecipeSelect = ({ inputs, currentRecipes, setCurrentRecipes, loadedRecipes const SelectInputs = () => { const handleInputChange = (index: number, id: string) => { - const newRecipe = loadedRecipes.find(recipe => recipe.id === id)!.recipe; + const newRecipe = loadedRecipes.find(recipe => recipe.id === id)!; setCurrentRecipes(currentRecipes.map((recipe, i) => i === index ? { - selectedRecipe: newRecipe, + selectedRecipeId: newRecipe.id, + selectedRecipe: newRecipe.recipe, selectedServings: recipe.selectedServings } : recipe)); } @@ -59,6 +61,7 @@ const RecipeSelect = ({ inputs, currentRecipes, setCurrentRecipes, loadedRecipes const handleInputChange = (index: number, newValue: number) => { setCurrentRecipes(currentRecipes.map((recipe, i) => i === index ? { + selectedRecipeId: recipe.selectedRecipeId, selectedRecipe: recipe.selectedRecipe, selectedServings: newValue } : recipe)); diff --git a/src/app/components/navigation/menus/openanalysis-menu.tsx b/src/app/components/navigation/menus/openanalysis-menu.tsx index e3fe7d7..84c3504 100644 --- a/src/app/components/navigation/menus/openanalysis-menu.tsx +++ b/src/app/components/navigation/menus/openanalysis-menu.tsx @@ -86,7 +86,16 @@ const OpenAnalysisMenu = ({ file, setFile }: OpenAnalysisMenuProps): JSX.Element } const addMenuToFavorites = async () => { - const Menu: MenuProp | null = currentMenu!.menu; + const menuRecipes = currentMenu!.menu?.recipes.map(recipe => { + return { + selectedRecipe: recipe.selectedRecipeId, + selectedServings: recipe.selectedServings + }}); + const Menu = { + name: currentMenu!.menu?.name, + ingredients: currentMenu!.menu?.ingredients, + recipes: menuRecipes + }; if(!token) { setStatus(StatusType.ERROR); setMessage('You must be logged in to add menu to favorites.'); @@ -136,7 +145,16 @@ const OpenAnalysisMenu = ({ file, setFile }: OpenAnalysisMenuProps): JSX.Element } const updateMenu = async () => { - const Menu: MenuProp | null = currentMenu!.menu; + const menuRecipes = currentMenu!.menu?.recipes.map(recipe => { + return { + selectedRecipe: recipe.selectedRecipeId, + selectedServings: recipe.selectedServings + }}); + const Menu = { + name: currentMenu!.menu?.name, + ingredients: currentMenu!.menu?.ingredients, + recipes: menuRecipes + }; if(!token) { setStatus(StatusType.ERROR); setMessage('You must be logged in to update menu.'); diff --git a/src/app/components/slider/slides/menu-slide.tsx b/src/app/components/slider/slides/menu-slide.tsx index c1fc823..2a6529c 100644 --- a/src/app/components/slider/slides/menu-slide.tsx +++ b/src/app/components/slider/slides/menu-slide.tsx @@ -5,13 +5,15 @@ import MenuCard from '../../cards/menu-cards/menu-card'; import Slide from './slide'; import { AuthContext } from '@/app/context/auth-context'; import { useHttpClient } from '@/app/hooks/http-hook'; -import { LoadedMenu } from '@/app/types/types'; +import { useMenuFetch } from '@/app/hooks/menu-hook'; +import { LoadedMenu, LoadedRecipe, Nutrients, RecipeWithServings } from '@/app/types/types'; const MenuSlide = (): JSX.Element => { const { sendRequest } = useHttpClient(); const [menuList, setMenuList] = useState([]); const { token } = useContext(AuthContext); + const { fetchMenuNutrients} = useMenuFetch(); useEffect(() => { if(!token) { @@ -25,7 +27,20 @@ const MenuSlide = (): JSX.Element => { Authorization: 'Bearer ' + token }, true, false ); - const menuList = responseData.menus.map((menu: LoadedMenu, index: number) => { + const menus = await Promise.all(responseData.menus.map(async (menu: LoadedMenu) => { + const recipeWithServings = await fetchRecipes(menu); + const nutrients: Nutrients | null = await fetchMenuNutrients(menu.menu.ingredients, recipeWithServings); + return { + menu: { + name: menu.menu.name, + ingredients: menu.menu.ingredients, + nutrients: nutrients, + recipes: recipeWithServings + }, + id: menu.id + } + })); + const menuList = menus.map((menu: LoadedMenu, index: number) => { return ( ) @@ -36,6 +51,27 @@ const MenuSlide = (): JSX.Element => { fetchMenus(); }, [token]); + const fetchRecipes = async(menu: LoadedMenu) => { + const recipes = await Promise.all(menu.menu.recipes.map(async(recipe: RecipeWithServings) => { + const menuRecipe = await sendRequest( + `/recipes/${recipe.selectedRecipe}`,'GET', null, { + Authorization: 'Bearer ' + token + }, true, false + ); + return { + selectedRecipeId: menuRecipe.recipe.id, + selectedRecipe: { + name: menuRecipe.recipe.recipe.name, + servings: menuRecipe.recipe.recipe.servings, + ingredients: menuRecipe.recipe.recipe.ingredients, + nutrients: menuRecipe.recipe.recipe.nutrients + }, + selectedServings: recipe.selectedServings + } + })); + return recipes + } + return ( {menuList.length > 0 && menuList}