Beispiel #1
0
def login():
    """Check user credentials and log in if authorized."""
    if session.get("authorized"):
        return utils.success_response("User already authorized!",
                                      user=session.get("user"))
    else:
        username = request.form.get("login")
        password = request.form.get("password")
        try:
            user = get_user(username)
            current_app.logger.debug(
                f"User: {user.username}, {user.displayname}, {user.is_authenticated(password)}"
            )
        except User.DoesNotExist:
            return utils.error_response("Invalid username or password!"), 401

        if user.is_authenticated(password):
            session["authorized"] = True
            session["user"] = user.displayname
            session["uid"] = user.id
            session["admin"] = user.admin
            # For non-admin users make session expire when closing browser
            if not user.admin:
                session.permanent = False
            current_app.logger.debug("User %s logged in successfully" %
                                     username)
            return utils.success_response("User %s logged in successfully!" %
                                          username,
                                          user=user.displayname,
                                          admin=user.admin)
        return utils.error_response("Invalid username or password!"), 401
Beispiel #2
0
def parse_from_url():
    """Extract recipe data from given url and return response with recipe data."""
    url = request.args.get("url")
    if not url.startswith("http"):
        url = "http://" + url
    if not utils.valid_url(url):
        return utils.error_response(f"Invalid URL: {url}."), 400

    import_parsers()

    # Collect all parser classes
    all_parsers = [parser for parser in GeneralParser.__subclasses__()]

    p = find_parser(all_parsers, url)
    if p:
        recipe = {}
        parser = p(url)
        recipe["title"] = parser.title
        recipe["contents"] = parser.contents
        recipe["ingredients"] = parser.ingredients
        recipe["source"] = parser.url
        recipe["portions_text"] = parser.portions
        image_path = download_image(parser.image)
        recipe["image"] = image_path

        return utils.success_response("Successfully extracted recipe.",
                                      data=recipe)
    else:
        return utils.error_response(f"No parser found for URL {url}."), 400
Beispiel #3
0
def add_recpie():
    """Add new recipe to the data base."""
    recipe_id = None
    filename = None
    try:
        data = request.form.to_dict()
        data = utils.deserialize(data)
        data["user"] = session.get("uid")
        data["published"] = False if data.get("published", True).lower() == "false" else True
        image_file = request.files.get("image")
        recipe_id = recipemodel.add_recipe(data)
        url = utils.make_url(data["title"], recipe_id)
        recipemodel.set_url(recipe_id, url)
        tagmodel.add_tags(data, recipe_id)
        storedmodel.add_recipe(recipe_id)
        save_image(data, recipe_id, image_file)
        return utils.success_response(msg="Recipe saved", url=url)

    except pw.IntegrityError:
        return utils.error_response("Recipe title already exists!"), 409

    except Exception as e:
        # Delete recipe data and image
        if recipe_id is not None:
            storedmodel.delete_recipe(recipe_id)
            recipemodel.delete_recipe(recipe_id)
        if filename is not None:
            img_path = os.path.join(current_app.instance_path, current_app.config.get("IMAGE_PATH"))
            filepath = os.path.join(img_path, filename)
            try:
                utils.remove_file(filepath)
            except Exception:
                current_app.logger.warning(f"Could not delete file: {filepath}")
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to save data: {e}"), 400
Beispiel #4
0
def clean_tmp_data():
    """Clean temporary data like uploaded images."""
    password = request.args.get("password")
    if password != current_app.config.get("ADMIN_PASSWORD"):
        return utils.error_response("Failed to confirm password."), 500
    try:
        tmp_path = os.path.join(current_app.instance_path, current_app.config.get("TMP_DIR"))
        data = utils.clean_tmp_folder(tmp_path)
        return utils.success_response(f"Successfully cleaned temporary data!",
                                      removed_files=data)
    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Cleanup failed: {e}"), 400
Beispiel #5
0
def get_random_recipe():
    """Return one recipe at random from randomizer categories in config."""
    tags = current_app.config.get("RANDOM_TAGS", [])

    or_expressions = reduce(pw.operator.or_, [
        pw.fn.FIND_IN_SET(tag, pw.fn.group_concat(tagmodel.Tag.tagname))
        for tag in tags
    ])

    try:
        recipes = recipemodel.Recipe.select(
            recipemodel.Recipe, storedmodel.Stored, pw.fn.group_concat(tagmodel.Tag.tagname).alias("taglist")
        ).where(
            recipemodel.Recipe.published == True
        ).join(
            storedmodel.Stored, pw.JOIN.LEFT_OUTER, on=(storedmodel.Stored.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.RecipeTags, pw.JOIN.LEFT_OUTER, on=(tagmodel.RecipeTags.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.Tag, pw.JOIN.LEFT_OUTER, on=(tagmodel.Tag.id == tagmodel.RecipeTags.tagID)
        ).group_by(
            recipemodel.Recipe.id
        ).having(
            or_expressions
        )

        recipe = [random.choice(recipemodel.get_recipes(recipes))]
        return utils.success_response(msg="Got random recipe", data=recipe, hits=len(recipe))
    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to load data: {e}")
Beispiel #6
0
def edit_recpie():
    """Edit a recipe that already exists in the data base."""
    try:
        data = request.form.to_dict()
        data = utils.deserialize(data)
        data["user"] = session.get("uid")  # Store info about which user edited last
        data["published"] = False if data.get("published", True).lower() == "false" else True
        url = utils.make_url(data["title"], data["id"])
        data["url"] = url
        image_file = request.files.get("image")
        if not image_file and not data["image"]:
            recipe = recipemodel.Recipe.get(recipemodel.Recipe.id == data["id"])
            if recipe.image:
                try:
                    utils.remove_file(utils.remove_file(os.path.join(current_app.config.get("IMAGE_PATH"), recipe.image)))
                except OSError:
                    current_app.logger.warning(traceback.format_exc())
        else:
            save_image(data, data["id"], image_file)
        recipemodel.edit_recipe(data["id"], data)
        tagmodel.add_tags(data, data["id"])
        return utils.success_response(msg="Recipe saved", url=url)

    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to save data: {e}"), 400
Beispiel #7
0
def suggest_recipe():
    """Save a recipe suggestion in the data base (published=False)."""
    recipe_id = None
    filename = None
    try:
        data = request.form.to_dict()
        data = utils.deserialize(data)
        data["user"] = session.get("uid")
        data["published"] = False
        image_file = request.files.get("image")
        recipe_id = recipemodel.add_recipe(data)
        url = utils.make_url(data["title"], recipe_id)
        recipemodel.set_url(recipe_id, url)
        tagmodel.add_tags(data, recipe_id)
        storedmodel.add_recipe(recipe_id)
        save_image(data, recipe_id, image_file)

        # Attempt to send email to admins
        try:
            msg = ("Hej kalufs-admin!\n\nEtt nytt receptförslag med titel \"{}\" har lämnats in av {}.\n"
                   "Logga in på https://kalufs.lol/recept för att granska och publicera receptet.\n\n"
                   "Vänliga hälsningar,\nkalufs.lol"
                   ).format(data.get("title"), data.get("suggester"))
            utils.send_mail(current_app.config.get("EMAIL_TO"), "Nytt receptförslag!", msg)
        except Exception:
            current_app.logger.error(traceback.format_exc())

        return utils.success_response(msg="Recipe saved", url=url)

    except pw.IntegrityError:
        return utils.error_response("Recipe title already exists!"), 409

    except Exception as e:
        # Delete recipe data and image
        if recipe_id is not None:
            storedmodel.delete_recipe(recipe_id)
            recipemodel.delete_recipe(recipe_id)
        if filename is not None:
            img_path = os.path.join(current_app.instance_path, current_app.config.get("IMAGE_PATH"))
            filepath = os.path.join(img_path, filename)
            try:
                utils.remove_file(filepath)
            except Exception:
                current_app.logger.warning(f"Could not delete file: {filepath}")
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to save data: {e}"), 400
Beispiel #8
0
def get_recipe_from_db(convert=False):
    """Get data for one recipe. Convert to html if convert=True."""
    recipe_id = request.args.get("id")
    title = request.args.get("title")
    try:
        Changed = User.alias()
        recipes = recipemodel.Recipe.select(
            recipemodel.Recipe, User, Changed, storedmodel.Stored,
            pw.fn.group_concat(tagmodel.Tag.tagname).alias("taglist")
        ).where(
            recipemodel.Recipe.id == recipe_id if recipe_id else
            recipemodel.Recipe.title == title
        ).join(
            storedmodel.Stored, pw.JOIN.LEFT_OUTER, on=(storedmodel.Stored.recipeID == recipemodel.Recipe.id)
        ).switch(
            recipemodel.Recipe
        ).join(
            User, pw.JOIN.LEFT_OUTER, on=(User.id == recipemodel.Recipe.created_by).alias("a")
        ).switch(
            recipemodel.Recipe
        ).join(
            Changed, pw.JOIN.LEFT_OUTER, on=(Changed.id == recipemodel.Recipe.changed_by).alias("b")
        ).switch(
            recipemodel.Recipe
        ).join(
            tagmodel.RecipeTags, pw.JOIN.LEFT_OUTER, on=(tagmodel.RecipeTags.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.Tag, pw.JOIN.LEFT_OUTER, on=(tagmodel.Tag.id == tagmodel.RecipeTags.tagID)
        ).group_by(recipemodel.Recipe.id)
        recipe = recipemodel.get_recipe(recipes[0])

        if convert:
            recipe = utils.recipe2html(recipe)
        if not recipe:
            return utils.error_response(f"Could not find recipe '{title}'."), 404

        return utils.success_response(msg="Data loaded", data=recipe)

    except IndexError:
        return utils.error_response(f"Could not find recipe with ID '{recipe_id}'"), 404

    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to load recipe: {e}"), 400
Beispiel #9
0
def toggle_stored():
    """Toggle the 'stored' value of a recipe."""
    try:
        data = request.get_json()
        stored = data.get("stored", False)
        storedmodel.toggle_stored(data["id"], stored)
        if stored:
            return utils.success_response(msg="Recipe stored")
        else:
            return utils.success_response(msg="Recipe unstored")

    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to save data: {e}"), 400
Beispiel #10
0
def get_parsers():
    """Get a list of recipe pages for which there is a parser available."""
    import_parsers()
    try:
        pages_list = [{
            "domain": p.domain,
            "name": p.name,
            "address": p.address
        } for p in GeneralParser.__subclasses__()]
        return utils.success_response(
            "Successfully retrieved list of parsable pages.", data=pages_list)
    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(
            f"Could not retrieve list of parsable pages: {e}"), 500
Beispiel #11
0
def toggle_needs_fix():
    """Toggle the 'needs_fix' value of a recipe."""
    try:
        data = request.form.to_dict()
        data = utils.deserialize(data)
        needs_fix = data.get("needs_fix", False)
        recipemodel.toggle_needs_fix(data["id"], needs_fix)
        if needs_fix:
            return utils.success_response(msg="Recipe marked as 'needs_fix'")
        else:
            return utils.success_response(msg="Recipe unmarked")

    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to save data: {e}"), 400
Beispiel #12
0
def preview_data():
    """Generate recipe preview. Convert markdown data to html."""
    try:
        data = utils.recipe2html(request.form.to_dict())
        data = utils.deserialize(data)
        image_file = request.files.get("image")
        if image_file:
            filename = utils.make_random_filename(image_file, file_extension=".jpg")
            directory = os.path.join(current_app.instance_path, current_app.config.get("TMP_DIR"))
            utils.save_upload_image(image_file, filename, directory)
            data["image"] = "tmp/" + filename
        return utils.success_response(msg="Data converted", data=data)
    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to convert data: {e}")
Beispiel #13
0
def delete_recpie():
    """Remove recipe from data base."""
    try:
        recipe_id = request.args.get("id")
        recipe = recipemodel.Recipe.get(recipemodel.Recipe.id == recipe_id)
        if recipe.image:
            utils.remove_file(os.path.join(current_app.config.get("IMAGE_PATH"), recipe.image))
            utils.remove_file(os.path.join(current_app.config.get("THUMBNAIL_PATH"), recipe.image))
            utils.remove_file(os.path.join(current_app.config.get("MEDIUM_IMAGE_PATH"), recipe.image))
        tagmodel.delete_recipe(recipe_id)
        storedmodel.delete_recipe(recipe_id)
        recipemodel.delete_recipe(recipe_id)
        return utils.success_response(msg="Recipe removed")
    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to remove recipe: {e}"), 400
Beispiel #14
0
def needs_fix_recipes():
    """Return data for all recipes that need fixes."""
    try:
        recipes = recipemodel.Recipe.select(
            recipemodel.Recipe, storedmodel.Stored, pw.fn.group_concat(tagmodel.Tag.tagname).alias("taglist")
        ).where(
            recipemodel.Recipe.needs_fix == True
        ).join(
            storedmodel.Stored, pw.JOIN.LEFT_OUTER, on=(storedmodel.Stored.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.RecipeTags, pw.JOIN.LEFT_OUTER, on=(tagmodel.RecipeTags.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.Tag, pw.JOIN.LEFT_OUTER, on=(tagmodel.Tag.id == tagmodel.RecipeTags.tagID)
        ).group_by(recipemodel.Recipe.id)

        data = recipemodel.get_recipes(recipes)
        return utils.success_response(msg="Data loaded", data=data, hits=len(data))
    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to load data: {e}")
Beispiel #15
0
def get_recipe_data(published=False, complete_data=False):
    """Return published or unpublished recipe data."""
    try:
        Changed = User.alias()
        recipes = recipemodel.Recipe.select(
            recipemodel.Recipe, storedmodel.Stored,
            pw.fn.group_concat(tagmodel.Tag.tagname).alias("taglist")
        ).where(
            recipemodel.Recipe.published == published
        ).join(
            storedmodel.Stored, pw.JOIN.LEFT_OUTER, on=(storedmodel.Stored.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.RecipeTags, pw.JOIN.LEFT_OUTER, on=(tagmodel.RecipeTags.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.Tag, pw.JOIN.LEFT_OUTER, on=(tagmodel.Tag.id == tagmodel.RecipeTags.tagID)
        ).group_by(
            recipemodel.Recipe.id)

        if complete_data:
            # Load in User table
            recipes = recipes.select(
                User, Changed, recipemodel.Recipe, storedmodel.Stored,
                pw.fn.group_concat(tagmodel.Tag.tagname).alias("taglist")
            ).switch(
                recipemodel.Recipe
            ).join(
                User, pw.JOIN.LEFT_OUTER, on=(User.id == recipemodel.Recipe.created_by).alias("a")
            ).switch(
                recipemodel.Recipe
            ).join(
                Changed, pw.JOIN.LEFT_OUTER, on=(Changed.id == recipemodel.Recipe.changed_by).alias("b"))

        data = recipemodel.get_recipes(recipes, complete_data=complete_data)
        return utils.success_response(msg="Data loaded", data=data, hits=len(data))
    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Failed to load data: {e}")
Beispiel #16
0
def search():
    """Search recipe data base."""
    try:
        tag = request.args.get("tag")
        user = request.args.get("user")
        q = request.args.get("q")

        if tag:
            # Tag Search
            querytype = "tag"

            tagset = set(tag.split(","))
            tagstructure = tagmodel.get_tag_structure(simple=True)
            taggroups = []
            for cat in tagstructure:
                selected_tags = list(set(cat.get("tags")).intersection(tagset))
                if selected_tags:
                    taggroups.append(selected_tags)

            # Chain tags with OR within a category and with AND between categories
            and_expressions = []
            for taggroup in taggroups:
                or_expressions = [
                    pw.fn.FIND_IN_SET(tag, pw.fn.group_concat(tagmodel.Tag.tagname))
                    for tag in taggroup
                ]
                and_expressions.append(reduce(pw.operator.or_, or_expressions))
            expr = reduce(pw.operator.and_, and_expressions)

        elif user:
            # User search
            querytype = "user"
            expr = ((User.displayname == user) | recipemodel.Recipe.suggester.contains(user))

        else:
            # String search: seperate by whitespace and search in all relevant fields
            querytype = "q"
            if len(q) > 1 and q.startswith('"') and q.endswith('"'):
                searchitems = [q[1:-1]]
            else:
                searchitems = q.split(" ")
                searchitems = [i.rstrip(",") for i in searchitems]

            expr_list = [
                (
                    recipemodel.Recipe.title.contains(s)
                    | recipemodel.Recipe.contents.contains(s)
                    | recipemodel.Recipe.ingredients.contains(s)
                    | recipemodel.Recipe.source.contains(s)
                    | User.username.contains(s)
                    | pw.fn.FIND_IN_SET(s, pw.fn.group_concat(tagmodel.Tag.tagname))
                ) for s in searchitems
            ]
            expr = reduce(pw.operator.and_, expr_list)

        # Build query
        Changed = User.alias()
        query = recipemodel.Recipe.select(
            recipemodel.Recipe, User, Changed, storedmodel.Stored,
            pw.fn.group_concat(tagmodel.Tag.tagname).alias("taglist")
        ).join(
            storedmodel.Stored, pw.JOIN.LEFT_OUTER, on=(storedmodel.Stored.recipeID == recipemodel.Recipe.id)
        ).join(
            User, pw.JOIN.LEFT_OUTER, on=(User.id == recipemodel.Recipe.created_by).alias("a")
        ).switch(
            recipemodel.Recipe
        ).join(
            Changed, pw.JOIN.LEFT_OUTER, on=(Changed.id == recipemodel.Recipe.changed_by).alias("b")
        ).switch(
            recipemodel.Recipe
        ).join(
            tagmodel.RecipeTags, pw.JOIN.LEFT_OUTER, on=(tagmodel.RecipeTags.recipeID == recipemodel.Recipe.id)
        ).join(
            tagmodel.Tag, pw.JOIN.LEFT_OUTER, on=(tagmodel.Tag.id == tagmodel.RecipeTags.tagID)
        ).group_by(
            recipemodel.Recipe.id
        ).where(
            (recipemodel.Recipe.published == True)
        ).having(expr)

        data = recipemodel.get_recipes(query)
        message = f"Query: {querytype}={q}"
        return utils.success_response(msg=message, data=data, hits=len(data))

    except Exception as e:
        current_app.logger.error(traceback.format_exc())
        return utils.error_response(f"Query failed: {e}"), 400
Beispiel #17
0
def handle_unauthorized(e):
    """Handle 401."""
    return utils.error_response("Unauthorized.")
Beispiel #18
0
def page_not_found(e):
    """Handle 404."""
    return utils.error_response("Page not found.")