Teams Purpose
The purpose of our group’s program is a random recipe generator with a ai bot that can assist users in the webiste, functions to post, store, retrieve, edit, and delete recipes. Along with a way to leave feedback on recipes
My Feature
Frontend Input
fetchRandomRecipe() {
fetch('http://127.0.0.1:8887/api/chinese_recipe/KungPaoChicken')
.then(response => response.json())
.then(data => displayRecipe(data))
.catch(error => console.error('Error fetching recipe:', error));
}
JSON Response: The backend responds with the JSON data, which is then processed and displayed on the webpage. Dictionaries were used to seperate these recipes into categories of chicken, beef, vegan, fish, and lamb. Here is an example of the data response.
class chinese_recipe_API:
@staticmethod
def get_chicken_recipe(name):
recipes = {
"dish": "Kung Pao Chicken",
"time": 25,
"ingredients": ["chicken", "peanuts", "soy sauce", "garlic", "ginger"],
"instructions": "Stir-fry chicken with sauce and peanuts."
}"
{
"dish": "Kung Pao Chicken",
"time": 25,
"ingredients": ["chicken", "peanuts", "soy sauce", "garlic", "ginger"],
"instructions": "Stir-fry chicken with sauce and peanuts."
}
Backend Operations
The database stores recipes in a table where each row represents a single recipe and each column represents a recipe attribute (e.g., dish, ingredients). SQLAlchemy maps the rows of this table to Python objects.
class Recipe(db.Model):
id = db.Column(db.Integer, primary_key=True)
dish = db.Column(db.String(100), nullable=False)
time = db.Column(db.Integer)
ingredients = db.Column(db.String(200))
instructions = db.Column(db.String(500))
def to_dict(self):
return {
'id': self.id,
'dish': self.dish,
'time': self.time,
'ingredients': self.ingredients.split(','),
'instructions': self.instructions
}
Api methods for CRUD
@app.route('/api/recipes', methods=['POST'])
def create_recipe():
data = request.get_json() # Get the request body as JSON
new_recipe = Recipe(
dish=data['dish'],
time=data['time'],
ingredients=','.join(data['ingredients']), # Store ingredients as a string
instructions=data['instructions']
)
db.session.add(new_recipe) # Add new recipe to the session
db.session.commit() # Commit changes to the database
return jsonify(new_recipe.to_dict()), 201 # Return the new recipe as JSON
GET (Read): Fetches a recipe by its ID.
@app.route('/api/recipes/<int:id>', methods=['GET'])
def get_recipe(id):
recipe = Recipe.query.get(id)
if recipe:
return jsonify(recipe.to_dict())
return jsonify({"error": "Recipe not found"}), 404 # Handle case when recipe is not found
PUT (Update): Updates an existing recipe with new data.
@app.route('/api/recipes/<int:id>', methods=['PUT'])
def update_recipe(id):
data = request.get_json()
recipe = Recipe.query.get(id)
if recipe:
recipe.dish = data['dish']
recipe.time = data['time']
recipe.ingredients = ','.join(data['ingredients'])
recipe.instructions = data['instructions']
db.session.commit()
return jsonify(recipe.to_dict())
return jsonify({"error": "Recipe not found"}), 404
This code updates a recipe in the database using the recipe’s id. It gets the new data from the request, updates the recipe, and saves the changes. If the recipe isn’t found, it returns an error.
DELETE (Delete): Deletes a recipe from the database.
@app.route('/api/recipes/<int:id>', methods=['DELETE'])
def delete_recipe(id):
recipe = Recipe.query.get(id)
if recipe:
db.session.delete(recipe)
db.session.commit() #
return jsonify({"message": "Recipe deleted successfully"}), 200
return jsonify({"error": "Recipe not found"}), 404
This code deletes a recipe from the database using its id. If the recipe is found, it deletes it and confirms the deletion. If not, it returns an error.
Database Queries
recipes = db.session.query(Recipe).all()
recipe = db.session.query(Recipe).filter(Recipe.dish == 'Kung Pao Chicken').first()
the first line Recipes, will retrieve all the Recipes in the database and then the second shows it will request the specific recipe in the database with the name of Kung Pao Chicken.
Error handling
Error handling in case users try to fetch an error that doesn’t exist
if not recipe:
return jsonify({"error": "Recipe not found"}), 404
Backend
Heres an example of the data for a chinese cuisine Kung Pao Chicken:
"Kung Pao Chicken": {
"dish": "Kung Pao Chicken",
"time": 30,
"ingredients": "Chicken breast (500g), Dried red chilies (10-12) Peanuts (50g),Soy sauce (2 tbsp),Rice vinegar (1 tbsp),Sugar (1 tsp), Cornstarch (1 tsp), Garlic (3 cloves) Ginger (1-inch piece),Spring onions (2 stalks)",
"instructions":
"Cut chicken into small cubes and marinate with soy sauce and cornstarch for 10 minutes. Heat oil in a wok, fry dried chilies and peanuts until fragrant. Add garlic and ginger, stir-fry for 30 seconds. Add chicken and stir-fry until golden brown. Mix soy sauce, rice vinegar, sugar, and stir into the wok. Add spring onions and stir-fry for 2 more minutes before serving.",
},
These recipes are all seperated by the type of meat or if it is a vegan recipe, and all are stored using a dictionaries and here is an example of one for chicken recipes.
def get_chicken_recipe(name):
Then this code will define the recipes and that it will be using a get method to retrieve the recipe data from the API and return it in JSON or give an error message if it was not found.
class _KungPaoChicken(Resource):
def get(self):
recipe = chinese_recipe_API.get_chicken_recipe("Kung Pao Chicken")
if recipe:
return jsonify(recipe)
return {"Data not found"}, 404
Then here, endpoints for the recipes are created to be called to the frontend.
api.add_resource(chinese_recipe_API._KungPaoChicken, '/chinese_recipe/KungPaoChicken')
Heres the code in main.py which creates the flask route to store the recipes variables of name, dish, time, ingredients, and instructions.
@app.route('/api/chinese_recipe/save_recipe', methods=['POST'])
def save_recipe_route():
data = request.get_json()
name = data.get('name')
dish = data.get('dish')
time = data.get('time')
ingredients = data.get('ingredients')
instructions = data.get('instructions')
recipe = save_recipe(name, dish, time, ingredients, instructions)
if recipe:
return jsonify({"message": "Recipe saved successfully", "recipe": recipe.read()}), 201
else:
return jsonify({"error": "Recipe could not be created"}), 400
This is another flask route that will use GET method to fetch all the data from the database. This route will be used to fetch all the data when users click on the stored recipes button from the frontend.
@app.route('/get_recipes', methods=['GET'])
def get_recipes():
try:
recipes = Recipe.query.all()
recipes_list = [recipe.read() for recipe in recipes]
return jsonify(recipes_list), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
This is another flask route but instead for deleting recipes that users no longer want to be stored in the database. It is used to handle requests for deleting data stored in the database, it calls the delete method to use it and has error handling to show the user if something goes wrong or if their recipe was saved.
@app.route('/api/chinese_recipe/delete_recipe/<int:recipe_id>', methods=['DELETE'])
def delete_recipe(recipe_id):
try:
recipe = Recipe.query.get(recipe_id)
if recipe:
recipe.delete()
return jsonify({"message": "Recipe deleted successfully"}), 200
else:
return jsonify({"error": "Recipe not found"}), 404
except Exception as e:
return jsonify({"error": str(e)}), 500
This creates the save recipe parameters which are also name, dish, time, ingredients and insructions, then uses a create method in order to save that data and store it into the database.
def save_recipe(name, dish, time, ingredients, instructions):
new_recipe = Recipe(name=name, dish=dish, time=time, ingredients=ingredients, instructions=instructions)
return new_recipe.create()
Frontend
When users click on Shuffle Recipes, the frontend is setup to fetch 5 recipes from each category and display them on the website using the url endpoints created in the backend. at random.
const selectedUrls = {
chicken: apiUrls.chicken[Math.floor(Math.random() * apiUrls.chicken.length)],
beef: apiUrls.beef[Math.floor(Math.random() * apiUrls.beef.length)],
vegan: apiUrls.vegan[Math.floor(Math.random() * apiUrls.vegan.length)],
fish: apiUrls.fish[Math.floor(Math.random() * apiUrls.fish.length)],
lamb: apiUrls.lamb[Math.floor(Math.random() * apiUrls.lamb.length)]
};
try {
const recipeDataDiv = document.getElementById('recipe-data');
recipeDataDiv.innerHTML = ''; // Clear previous recipes
const fetchPromises = Object.values(selectedUrls).map(async (url) => {
const response = await fetch(url);
if (response.ok) {
return await response.json();
} else {
throw new Error('Error fetching a recipe');
}
});
const recipes = await Promise.all(fetchPromises);
recipes.forEach(recipe => {
const recipeDiv = document.createElement('div');
recipeDiv.classList.add('recipe-card');
recipeDiv.innerHTML = `
<h3>${recipe.dish}</h3>
<p><strong>Time:</strong> ${recipe.time} minutes</p>
<p><strong>Ingredients:</strong> ${Array.isArray(recipe.ingredients) ? recipe.ingredients.join(', ') : recipe.ingredients}</p>
<p><strong>Instructions:</strong> ${recipe.instructions}</p>
<button onclick='saveRecipe(${JSON.stringify(recipe)})'>Save Recipe</button>
`;
recipeDataDiv.appendChild(recipeDiv);
});
recipeDataDiv.style.display = 'flex';
} catch (error) {
document.getElementById('recipe-data').innerText = `Error: ${error.message}`;
}
}
Users will also be able to store recipes they catch their eyes but still want to keep shuffling. This will be done using a button with the following code for this function which will cause the recipe to be stored in the database with a POST request and have the data turned into a string to be sent to the database.
async function saveRecipe(recipe) {
try {
const response = await fetch('http://127.0.0.1:8887/save_recipe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"title": recipe.dish,
"ingredients": recipe.ingredients,
"instructions": recipe.instructions,
"time": recipe.time
})
});
In order to view the recipes that users store they can click a button below the shuffle recipes button called Stored recipes, These will be called from the database with the following code, which uses the fetch API, and ensuring that the data is in JSON format which then creates the HTML elements of the stored recipes along with the button to delete the recipe.
async function viewStoredRecipes() {
try {
const response = await fetch('http://127.0.0.1:8887/get_recipes');
const contentType = response.headers.get("content-type");
if (contentType && contentType.indexOf("application/json") !== -1) {
const recipes = await response.json();
const recipeDataDiv = document.getElementById('recipe-data');
recipeDataDiv.innerHTML = ''; // Clear previous recipes
recipes.forEach(recipe => {
const recipeDiv = document.createElement('div');
recipeDiv.classList.add('recipe-card');
recipeDiv.innerHTML = `
<h3>${recipe.dish}</h3>
<p><strong>Ingredients:</strong> ${recipe.ingredients}</p>
<p><strong>Instructions:</strong> ${recipe.instructions}</p>
<button onclick='deleteRecipe(${recipe.id})'>Delete Recipe</button> <!-- Add delete button -->
`;
recipeDataDiv.appendChild(recipeDiv);
});
} else {
throw new Error('Invalid response from server');
}
} catch (error) {
document.getElementById('recipe-data').innerText = `Error: ${error.message}`;
}
}
Then this function sends a delete request to the server using fetch in order to remove recipe data from the database that users no longer want.
async function deleteRecipe(recipeId) {
try {
const response = await fetch(`http://127.0.0.1:8887/api/chinese_recipe/delete_recipe/${recipeId}`, {
method: 'DELETE',
});
if (response.ok) {
alert('Recipe deleted successfully');
viewStoredRecipes();
} else {
const data = await response.json();
alert(data.error || 'Error deleting recipe');
}
} catch (error) {
alert(`Error: ${error.message}`);
}
}