From d43cf483894f58b678942d1977860891536c3f12 Mon Sep 17 00:00:00 2001 From: Flavien G <44913995+fgaujard@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:25:09 +0100 Subject: [PATCH] FavorisOk --- backend/database/schema.sql | 150 ++++++++++++---------- backend/src/controllers/favControllers.js | 54 ++++++-- backend/src/models/FavManager.js | 40 +++++- backend/src/router.js | 4 +- frontend/package-lock.json | 34 +++++ frontend/package.json | 1 + frontend/src/pages/Detail.jsx | 89 ++++++++++--- frontend/src/pages/style/RecipeList.scss | 4 +- 8 files changed, 268 insertions(+), 108 deletions(-) diff --git a/backend/database/schema.sql b/backend/database/schema.sql index 06122fa..f9fa754 100644 --- a/backend/database/schema.sql +++ b/backend/database/schema.sql @@ -89,81 +89,95 @@ VALUES ('hugoD','hugo.durand@gmail.com','Hugo','Durand','1989-02-05','test5',0) ; -INSERT INTO recipe (name,title, user_ID, prep_time, nb_people, difficulty,tag1, tag2,tag3,is_verified, total_kcal ) +-- Ajout de nouvelles recettes +INSERT INTO recipe (name, title, user_ID, prep_time, nb_people, difficulty, tag1, tag2, tag3, is_verified, total_kcal) VALUES - ('oeufs au plat', 'simple comme bonjour',1, 15, 1, 'facile', 'végétarien', 'sans gluten',NULL,true,200), - ('pâtes au beurre','la spécialité des étudiants!', 2, 25, 2, 'facile', 'végétarien', NULL,NULL,true,500), - ('religieuse au chocolat','pour les experts en pâtisserie', 5, 90, 2, 'difficile', 'dessert', 'gourmand',NULL,true,800), - ('ramen', 'Comment réussir un vrai ramen maison comme au Japon?', 2, 180, 2, 'difficile', 'japon', 'gourmand', 'soupe',true,400), - ('cheeseburger', 'Créez votre propre cheeseburger maison!',3,45,2,'facile','burger','gourmand',NULL,true,1200), - ('Les inimitables crêpes','Rapide et efficace, le tout en quelques étapes!', 5, 20, 4, 'facile', 'rapide', 'gourmand','dessert',true,400) -; + ("salade de pâtes méditerranéenne", "Un voyage en Méditerranée", 1, 25, 3, "moyen", "salade", "italien", "repas", true, 450), + ("tacos au poulet", "Saveurs mexicaines", 2, 40, 4, "moyen", "mexicain", "poulet", "dîner", true, 700), + ("curry de légumes", "Épices indiennes", 3, 35, 4, "moyen", "végétarien", "indien", "curry", true, 550), + ("poke bowl au saumon", "Frais et sain", 4, 20, 2, "facile", "poisson", "hawaïen", "déjeuner", true, 400), + ("risotto aux champignons", "Crémeux et délicieux", 5, 45, 3, "moyen", "italien", "champignon", "repas", true, 600), + ("wraps végétariens", "Légers et savoureux", 6, 30, 3, "facile", "végétarien", "dîner", "wrap", true, 350), + ("soupe miso", "Authentique et réconfortante", 1, 15, 2, "facile", "japonais", "soupe", NULL, true, 200), + ("lasagnes bolognaises", "Classique italien", 2, 60, 6, "difficile", "italien", "viande", "repas", true, 800), + ("salade de quinoa aux fruits de mer", "Fraîcheur marine", 3, 20, 2, "facile", "salade", "poisson", NULL, true, 350), + ("poulet rôti aux herbes", "Savoureux et simple", 4, 75, 4, "moyen", "viande", "poulet", "repas", true, 900), + ("spaghetti carbonara", "L'italie à la maison", 5, 30, 2, "moyen", "italien", "pâtes", "repas", true, 550), + ("tarte aux pommes", "Douceur sucrée", 6, 50, 8, "facile", "dessert", "fruits", "pâtisserie", true, 600); + +-- Ajout de nouveaux ingrédients INSERT INTO ingredient (name, unit, kcal) VALUES -('oeuf', 'pièce(s)', 20), -('beurre', 'grammes', 5), -('chocolat noir', 'grammes', 3), -('farine de blé', 'grammes', 1), -('pâtes', 'grammes', 2), -('nouilles udon', 'grammes',50), -('steak haché', 'pièce(s)',100), -('pain bun','pièce(s)', 40), -('cheddar', 'grammes', 200), -('tomate','pièce(s)',10), -('lait','centilitres',5), -('farine', 'grammes', 0.5) -; + ('pâtes', 'grammes', 200), + ('tomate', 'pièce(s)', 2), + ('olives noires', 'grammes', 30), + ('feta', 'grammes', 100), + ('tortillas', 'pièce(s)', 6), + ('filet de poulet', 'grammes', 300), + ('taco seasoning', 'cuillère(s) à soupe', 2), + ('riz basmati', 'grammes', 150), + ('saumon frais', 'grammes', 250), + ('edamame', 'grammes', 50), + ('champignons', 'grammes', 100), + ('wrap', 'pièce(s)', 4), + ('tofu', 'grammes', 200), + ('curry powder', 'cuillère(s) à café', 1), + ('algues', 'grammes', 20), + ('parmesan', 'grammes', 50), + ('fruits de mer assortis', 'grammes', 200), + ('herbes de Provence', 'cuillère(s) à café', 1), + ('pommes', 'pièce(s)', 4), + ('pâte feuilletée', 'grammes', 200); +-- Ajout de nouvelles instructions INSERT INTO instruction (description, recipe_id) VALUES - ("Préchauffez la poêle",1), - ("rajoutez le beurre dans la poêle",1), - ("cassez les oeufs et les versez dans la poêle sans les mélanger",1), - ("Une fois la texture souhaitée, disposez dans l'assiette", 1), - ("Faites chauffer l'équivalent de 3 volumes d'eau par volume de pâtes dans une casserole en ajoutant du sel",2), - ("Une fois que l'eau bout, insérez les pâtes dans l'eau frémissante et respectez le temps de cuisson indiquée sur le sachet",2), - ("Disposez les pâtes dans l'assiette avec le beurre et mélangez",2), - ("Savourez!",2), - ("mélangez les oeufs et le lait",6), - ("rajoutez la farine",6), - ("laissez reposer 3 heures au minimum", 6), - ("Disposez la pâte sur une poêle chaude",6) -; - - -INSERT INTO tag (name) -VALUES - ('vegan'), - ('végétarien'), - ('sans gluten'), - ('Japon'), - ('Soupe'), - ('Burger'), - ('dessert') -; - - -INSERT INTO comment (description, user_id, recipe_id) -VALUES - ("Belle recette, merci !", 1, 1), - ("Il manque un peu de beurre à mon goût...", 2, 1), - ("J'ai pas eu besoin de spatule pour ça!",5,2) -; + ("Cuisez les pâtes selon les instructions sur l'emballage", 10), + ("Coupez les tomates, les olives et émiettez la feta", 10), + ("Mélangez les pâtes cuites avec les légumes et la feta", 10), + ("Faites griller les tortillas et remplissez-les avec le mélange", 11), + ("Cuisez le poulet et assaisonnez avec le mélange d'épices pour tacos", 11), + ("Préparez le riz basmati selon les indications sur l'emballage", 12), + ("Cuisez le saumon et coupez-le en morceaux", 12), + ("Disposez le riz dans un bol, ajoutez le saumon, les edamames et les algues", 12), + ("Préparez le rizotto en cuisant le riz avec les champignons", 13), + ("Préparez les wraps en remplissant les tortillas de tofu et de légumes", 13), + ("Faites chauffer le bouillon et ajoutez-le progressivement au riz, en remuant constamment", 14), + ("Disposez les wraps dans un plat de cuisson et versez la sauce tomate par-dessus", 14), + ("Cuisez au four préchauffé à 180°C pendant 30 minutes", 14), + ("Mélangez les fruits de mer cuits avec la salade de quinoa", 15), + ("Assaisonnez avec une vinaigrette légère à base d'huile d'olive", 15), + ("Assaisonnez le poulet avec des herbes de Provence et faites-le rôtir au four", 16), + ("Préparez une sauce carbonara en mélangeant des œufs, du parmesan et du poivre", 17), + ("Faites cuire les spaghetti et mélangez-les avec la sauce carbonara", 17), + ("Préparez une pâte feuilletée et garnissez-la avec des tranches de pommes", 18), + ("Cuisez au four préchauffé à 200°C pendant 40 minutes", 18); +-- Ajout de nouveaux liens entre recettes et ingrédients INSERT INTO recipe_ingredient (recipe_id, ingredient_id, quantity) VALUES - (1,1,2), - (1,2,10), - (4,1,2), - (4,6,200), - (5,7,2), - (5, 8,2), - (5,9,200), - (5,10,1), - (6,1,6), - (6,2,20), - (6,10,20), - (6,11,200) -; - + (10, 1, 200), + (10, 2, 4), + (10, 3, 30), + (10, 4, 100), + (11, 5, 6), + (11, 6, 300), + (11, 7, 2), + (12, 8, 150), + (12, 9, 250), + (12, 10, 50), + (13, 11, 100), + (13, 12, 4), + (13, 13, 200), + (14, 8, 200), + (14, 14, 6), + (14, 15, 2), + (15, 16, 200), + (15, 17, 50), + (16, 6, 400), + (16, 18, 4), + (17, 1, 300), + (17, 19, 50), + (18, 20, 4), + (18, 21, 200); \ No newline at end of file diff --git a/backend/src/controllers/favControllers.js b/backend/src/controllers/favControllers.js index 67d952c..5366e14 100644 --- a/backend/src/controllers/favControllers.js +++ b/backend/src/controllers/favControllers.js @@ -1,4 +1,5 @@ // Import access to database tables +const jwt = require("jsonwebtoken"); const tables = require("../tables"); // BROWSE @@ -11,14 +12,18 @@ const browse = async (req, res, next) => { } }; -// READ -const readByRecipe = async (req, res, next) => { +const readByUser = async (req, res, next) => { try { - const fav = await tables.fav.readByRecipe(req.params.id); - if (fav == null) { - res.sendStatus(404); - } else { - res.json(fav); + const { id } = req.params; + const { token } = req.cookies; + if (token) { + const user = jwt.verify(token, process.env.APP_SECRET); + const [result] = await tables.fav.readUserFav(id, user.id); + if (!result) { + res.sendStatus(404); + } else { + res.json(result); + } } } catch (err) { next(err); @@ -31,24 +36,45 @@ const readByRecipe = async (req, res, next) => { // The A of BREAD - Add (Create) operation const add = async (req, res, next) => { // Extract the user data from the request body - const fav = req.body; - try { - const insertId = await tables.fav.create(fav); - res.status(201).json({ insertId }); + const { id } = req.body; + const { token } = req.cookies; + if (token) { + const user = jwt.verify(token, process.env.APP_SECRET); + const insertId = await tables.fav.create(id, user.id); + res.status(201).json({ insertId }); + } else { + res.status(401); + } } catch (err) { next(err); } }; // The D of BREAD - Destroy (Delete) operation -// This operation is not yet implemented + +const destroy = async (req, res, next) => { + // Extract the item data from the request body + try { + const { id } = req.params; + const { token } = req.cookies; + if (token) { + const user = jwt.verify(token, process.env.APP_SECRET); + const affectedRows = await tables.fav.delete(id, user.id); + res.status(201).json({ affectedRows }); + } else { + res.status(401); + } + } catch (err) { + next(err); + } +}; // Ready to export the controller functions module.exports = { browse, - readByRecipe, + readByUser, // edit, add, - // destroy, + destroy, }; diff --git a/backend/src/models/FavManager.js b/backend/src/models/FavManager.js index dbde4d0..102d3d9 100644 --- a/backend/src/models/FavManager.js +++ b/backend/src/models/FavManager.js @@ -5,18 +5,35 @@ class CommentManager extends AbstractManager { super({ table: "fav" }); } + async create(id, userId) { + const [result] = await this.database.query( + `INSERT INTO ${this.table} (recipe_id, user_id) VALUES (?, ?)`, + [id, userId] + ); + return result.insertId; + } + async readAll() { const [rows] = await this.database.query(`select * from ${this.table}`); return rows; } + async readUserFav(id, userId) { + const [rows] = await this.database.query( + `SELECT id FROM ${this.table} WHERE recipe_id = ? AND user_id = ?`, + [id, userId] + ); + + return rows; + } + async readByRecipe(id) { const [rows] = await this.database.query( `SELECT f.id FROM fav AS f -JOIN recipe AS r ON r.ID = f.recipe_ID -JOIN user AS u ON u.ID = f.user_ID -WHERE r.ID = ?`, +JOIN recipe AS r ON r.id = f.recipe_id +JOIN user AS u ON u.id = f.user_id +WHERE r.id = ?`, [id] ); return rows; @@ -25,12 +42,23 @@ WHERE r.ID = ?`, async readByUser(id) { const [rows] = await this.database.query( `SELECT f.id, r.name, r.titre FROM fav AS f -JOIN recipe AS r ON r.ID = f.recipe_ID -JOIN user AS u ON u.ID = f.user_ID -WHERE r.ID = ?`, +JOIN recipe AS r ON r.id = f.recipe_id +JOIN user AS u ON u.id = f.user_id +WHERE r.id = ?`, [id] ); return rows; } + + async delete(id, userId) { + // Execute the SQL INSERT query to add a new item to the "item" table + const [result] = await this.database.query( + `DELETE FROM ${this.table} WHERE recipe_id = ? AND user_id = ?`, + [id, userId] + ); + + // Return the ID of the newly inserted item + return result.affectedRows; + } } module.exports = CommentManager; diff --git a/backend/src/router.js b/backend/src/router.js index bbc50a4..90a1694 100644 --- a/backend/src/router.js +++ b/backend/src/router.js @@ -114,7 +114,9 @@ router.get("/instructionbyrecipe/:id", InstructionControllers.readByRecipe); // // Import recipeControllers module for handling item-related operations const FavControllers = require("./controllers/favControllers"); -router.get("/favbyrecipe/:id", FavControllers.readByRecipe); // Route to get a specific item by ID +router.get("/favbyrecipe/:id", FavControllers.readByUser); // Route to get a specific item by ID +router.post("/favbyrecipe", FavControllers.add); +router.delete("/favbyrecipe/:id", FavControllers.destroy); const TagControllers = require("./controllers/tagControllers"); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 46c8592..435fea5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,6 +7,7 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.6", "@mui/material": "^5.15.6", "axios": "^1.6.2", "prop-types": "^15.8.1", @@ -1119,6 +1120,31 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.15.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.6.tgz", + "integrity": "sha512-GnkxMtlhs+8ieHLmCytg00ew0vMOiXGFCw8Ra9nxMsBjBqnrOI5gmXqUm+sGggeEU/HG8HyeqC1MX/IxOBJHzA==", + "dependencies": { + "@babel/runtime": "^7.23.8" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.15.6", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.6.tgz", @@ -6329,6 +6355,14 @@ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.6.tgz", "integrity": "sha512-0aoWS4qvk1uzm9JBs83oQmIMIQeTBUeqqu8u+3uo2tMznrB5fIKqQVCbCgq+4Tm4jG+5F7dIvnjvQ2aV7UKtdw==" }, + "@mui/icons-material": { + "version": "5.15.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.6.tgz", + "integrity": "sha512-GnkxMtlhs+8ieHLmCytg00ew0vMOiXGFCw8Ra9nxMsBjBqnrOI5gmXqUm+sGggeEU/HG8HyeqC1MX/IxOBJHzA==", + "requires": { + "@babel/runtime": "^7.23.8" + } + }, "@mui/material": { "version": "5.15.6", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index 410302a..8dfe97b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.6", "@mui/material": "^5.15.6", "axios": "^1.6.2", "prop-types": "^15.8.1", diff --git a/frontend/src/pages/Detail.jsx b/frontend/src/pages/Detail.jsx index cd5ebfb..e21b521 100644 --- a/frontend/src/pages/Detail.jsx +++ b/frontend/src/pages/Detail.jsx @@ -4,6 +4,9 @@ import PropTypes from "prop-types"; import axios from "axios"; import Button from "@mui/material/Button"; +import Checkbox from "@mui/material/Checkbox"; +import FavoriteBorder from "@mui/icons-material/FavoriteBorder"; +import Favorite from "@mui/icons-material/Favorite"; import img from "../assets/crepe.jpeg"; @@ -12,6 +15,7 @@ import "./style/detail.scss"; function Detail({ name, prep, id }) { const [ingredients, setIngredients] = useState([]); const [instructions, setInstructions] = useState([]); + const [isFav, setIsFav] = useState(false); const test = img; @@ -36,28 +40,79 @@ function Detail({ name, prep, id }) { }); }, []); + useEffect(() => { + axios + .get(`${import.meta.env.VITE_BACKEND_URL}/api/favbyrecipe/${id}`, { + withCredentials: true, + }) + .then((res) => { + console.info(res); + if (res) { + setIsFav(true); + } + }) + .catch(() => { + setIsFav(false); + }); + }, []); + + const handleClickLike = () => { + if (isFav === false) { + axios + .post( + `${import.meta.env.VITE_BACKEND_URL}/api/favbyrecipe`, + { id }, + { + withCredentials: true, + } + ) + .then(() => { + setIsFav(true); + }); + } else { + axios + .delete(`${import.meta.env.VITE_BACKEND_URL}/api/favbyrecipe/${id}`, { + withCredentials: true, + }) + .then(() => { + setIsFav(false); + }); + } + return null; + }; + + console.info(isFav); + return (

{name}

- +
+ } + checkedIcon={} + checked={isFav} + /> + +
recette

Préparation : {prep} minutes

diff --git a/frontend/src/pages/style/RecipeList.scss b/frontend/src/pages/style/RecipeList.scss index 03b2862..d070eb3 100644 --- a/frontend/src/pages/style/RecipeList.scss +++ b/frontend/src/pages/style/RecipeList.scss @@ -30,10 +30,9 @@ background-color: $white-color; border-radius: 1.5rem; min-height: 200px; - min-width: 300px; + min-width: 370px; max-width: 420px; box-shadow: 0px 15px 20px rgba(0, 0, 0, 0.1); - margin: 0 1rem; display: flex; @@ -113,6 +112,7 @@ .recipe-content { .recipe-list { grid-template-columns: repeat(1, 1fr); + margin: 0 0.5rem; } } }