from flask import Flask, Response, render_template, request, session, redirect, url_for from flask_bcrypt import Bcrypt import functools import re import Constants from urllib.parse import urlparse from scripts.scraping.scraper import scrape from scripts.scraping.ai_scraping import run_ai_query from scripts.recipes.handle_recipes import recipe_exists, add_single_recipe, get_all_recipes, get_single_recipe, get_facets, delete_single_recipe, search_recipes, delete_multiple_recipes from scripts.users.handle_users import user_exists, add_user, delete_single_user, update_user app = Flask(__name__) bcrypt = Bcrypt(app) app.config["TEMPLATES_AUTO_RELOAD"] = True app.secret_key = Constants.SECRET_KEY # Decorator function to check for username in session to protect routes def login_required(func): @functools.wraps(func) def secure_function(*args, **kwargs): if "username" not in session: return redirect(url_for("login")) return func(*args, **kwargs) return secure_function # Pages @app.route("/home") @login_required def home_page(): tags = get_facets(session["username"]) all_recipes = get_all_recipes(session["username"]) return render_template("/pages/home.html",recipes=all_recipes, facets=tags) @app.route("/") def landing_page(): return render_template("/pages/landing.html") @app.route("/recipe/") @login_required def single_recipe_page(recipe_id): recipe_details = get_single_recipe(recipe_id) # If there are instruction sections, be sure to render correctly if "subInstructions" in recipe_details["instructions"][0]: new_instructions = [{"i": x["subInstructions"], "n": x["name"]} for x in recipe_details["instructions"]] recipe_details["instructions"] = new_instructions recipe_details["has_subsections"] = True return render_template("/pages/single-recipe.html", single_recipe=recipe_details) @app.route("/login") def login(): return render_template("/pages/login.html") @app.route("/signup") def signup(): return render_template("/pages/signup.html") @app.route("/account") def account(): if "username" not in session: return redirect(url_for("login")) else: return render_template("/pages/account.html") @app.route("/about") def about(): return render_template("/pages/about.html") # API routes @app.post("/recipes/add") def add_recipe(): req_data = request.form.to_dict() submitted_url = req_data["url"] if submitted_url == '': return "No recipe supplied!", 422 does_recipe_exist = recipe_exists(session["username"], submitted_url) if does_recipe_exist: return "Recipe already exists!", 422 else: response = scrape(submitted_url) if response["success"]: add_single_recipe(response["data"]) all_recipes = get_all_recipes(session["username"]) all_facets = get_facets(session["username"]) return render_template("/components/app.html", recipes=all_recipes, facets=all_facets) elif not response["success"]: ai_res = run_ai_query(submitted_url) add_single_recipe(ai_res["data"]) all_recipes = get_all_recipes(session["username"]) all_facets = get_facets(session["username"]) return render_template("/components/app.html", recipes=all_recipes, facets=all_facets) else: return "something went wrong", 400 @app.post("/recipes/search") def search(): req_data = request.form.to_dict() all_recipes = search_recipes(req_data, session["username"]) all_facets = get_facets(session["username"], req_data) return render_template("/components/app.html", recipes=all_recipes, facets=all_facets) @app.delete("/recipes/delete/") def delete_recipe(recipe_id): res = delete_single_recipe(recipe_id) if res["success"]: return f'

Recipe {recipe_id} successfully deleted, Go home' else: error_html = f'

Error deleting {recipe_id}

' return error_html @app.post("/login") def login_user(): req_data = request.form.to_dict() user = req_data["username"] pw = req_data["password"] res = user_exists(user) if res["success"] and res["user"] is None: return "

User doesn't exist, signup instead

" elif res["success"] and res["user"] is not None: pw_matches = bcrypt.check_password_hash(res["password"], pw) if pw_matches: session["username"] = user session["isAiSubscriber"] = res["isAiSubscriber"] return Response(headers={"HX-Redirect": "/home"}) else: return "Incorrect password, try again" else: return "

Something went wrong with the login

" @app.post("/signup") def signup_user(): req_data = request.form.to_dict() user = req_data.get("username") pw = req_data.get("password") pw_confirm = req_data.get("password-confirm") if user == "" or pw == "": return "

You must fill in both username and password

" if pw != pw_confirm: return "

Passwords do not match

" res = user_exists(user) if res["success"] and res["user"] is not None: return "

User already exists, Login instead

" elif res["success"] and res["user"] is None: pattern = r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$' is_strong = bool(re.match(pattern, pw)) if is_strong: pw_hash = bcrypt.generate_password_hash(pw) res = add_user(user, pw_hash) if res["success"]: session["username"] = user return Response(headers={"HX-Redirect": "/home"}) else: return "

Something went wrong

" else: return "

Password must be at least 8 characters long and contain at least 1 digit and 1 number

" @app.post("/logout") def logout_user(): session.pop('username', None) return Response(headers={"HX-Redirect": "/"}) @app.delete("/delete-account") def delete_user(): delete_user_res = delete_single_user(session["username"]) delete_recipes_res = delete_multiple_recipes(session["username"]) if delete_user_res["success"] and delete_recipes_res["success"]: session.pop("username", None) return Response(headers={"HX-Redirect": "/"}) else: return "Something went wrong deleting the user and recipes" @app.post("/update-account") def update_account(): user_prefs = request.form.to_dict() response = update_user(session["username"], user_prefs) if response["success"]: session["isAiSubscriber"] = True return Response(headers={"HX-Redirect": "/account"}) else: return f"{response['message']}", 422 if __name__ == "__main__": app.run(host="0.0.0.0")