def get_user_info(id):
    '''
    Given a user's id, return the details associated with that user.

    A user is only allowed to request their own user information.

    @Return
     - Returns the username, id, email, profile picture and associated recipes of the user.

    @Return Codes
     - 401 - user is unauthorised (A user isn't logged in or trying to view someone elses information)
     - 400 - the requested user does not exist in the DB.
    '''
    # If the requesting user is not the user who's info is requested.
    if g.user.id != id:
        raise ErrorException('Your authentication is invalid', 401)
    user = User.query.get(id)
    # Can't find user in db.
    if not user:
        raise ErrorException('This user does not exist in the database', 500)
    page_num = request.args.get('page_num', default=1, type=int)
    page_size = request.args.get('page_size', default=12, type=int)
    response = Recipe.get_recipes_by_user_id(g.user.id, page_num, page_size)
    response.update({
        'user_id': user.id,
        'email': user.email,
        'username': user.username,
        'profile_pic': user.profile_pic,
        'statusCode': 200,
        'status': 'success'
    })
    return response
    def add_recipe(name, instruction, mealType, ingredients, user_id, image=None):
        if image:
            recipe = Recipe(name=name, image=image)
        else:
            recipe = Recipe(name=name)
        db.session.add(recipe)

        for step in instruction:
            recipe_instruction = RecipeInstructions(instruction=step)
            recipe.instructions.append(recipe_instruction)

        mealtype = Mealtype.query.filter(func.lower(Mealtype.name) == func.lower(mealType)).first()
        if not mealtype:
            db.session.rollback()
            raise ErrorException('Mealtype does not exist: ' + mealType, 400)

        recipe.mealtypes.append(mealtype)
        user = User.query.filter_by(id=user_id).first()

        for ingredient in ingredients:
            db_ingredient = Ingredient.query.filter(func.lower(Ingredient.name) == func.lower(ingredient['name'])).first()
            if not db_ingredient:
                db.session.rollback()
                raise ErrorException('Ingredient does not exist: ' + ingredient['name'], 400)

            recipe_ingredient = RecipeIngredients(quantity=ingredient['quantity'])
            recipe_ingredient.ingredients = db_ingredient
            recipe.ingredients.append(recipe_ingredient)

        user.recipes.append(recipe)
        db.session.commit()
        IngredientSets.remove_sets(recipe)
        return recipe
def new_user():
    '''
    Given the signup details of a user, register them in the database so that login attempts can be made.

    @params username, password, (email coming soon?)

    @Returns
        - the username of the created user
    @Return codes
        - 400 - the correct signup details were not submitted OR the username already exists in the system.
        - 201 - code indicating the cresource has been created.
        - (TODO) - Add meaningful message to differentiate insufficient signup details vs username already taken.

    '''
    username = request.json.get('username')
    password = request.json.get('password')
    if not username or not password:
        raise ErrorException('Username or Password is not entered', 500)
    if User.query.filter_by(username=username).first() is not None:
        raise ErrorException('This user already exists', 500)
    user = User(username=username)
    user.hash_password(password)
    db.session.add(user)
    db.session.commit()
    return jsonify({
        'username': user.username,
        'statusCode': 201,
        'status': 'success'
    }), 201
def recipe_delete(recipe_id):
    """ Delete a recipe by its id"""
    recipe = Recipe.get_recipe_by_id(recipe_id)
    if not recipe:
        raise ErrorException('This recipe does not exist', 500)
    if g.user.id != recipe["user_id"]:
        raise ErrorException('Your authentication is invalid',
                             401)  # Cant delete a recipe that isn't yours
    if Recipe.recipe_delete(recipe_id):
        return jsonify({
            'message': 'Recipe deleted.',
            'statusCode': 200,
            'status': 'success'
        })
    def add_ingredient(name, category):
        db_category = Category.query.filter(func.lower(Category.name) == func.lower(category)).first()
        if not db_category:
            raise ErrorException('Category does not exist: ' + category, 400)

        ingredient = Ingredient(name=name)
        ingredient.categories.append(db_category)
        db.session.add(ingredient)
        db.session.commit()
        return ingredient
 def get_recipe_by_id(id):
     recipe = Recipe.query.get(id)
     if not recipe:
         raise ErrorException('Recipe id does not exist: ' + str(id), 400)
     instructions = [x.instruction for x in recipe.instructions]
     count = recipe.rating.count()
     schema = RecipeSchema(many=False)
     recipe = schema.dump(recipe)
     rating = Recipe.get_rating(recipe['id'])
     recipe['rating'] = rating
     recipe['rating_count'] = count
     recipe['instruction'] = instructions
     return recipe
def recipe_image_upload(recipe_id):
    '''

        On a POST request with /recipe_image_upload will upload a picture and store in our storage

    '''
    msg, code = extract_photo(request)
    if code == 200:  #TODO HANDLE ERRORS - Turn into objects?
        picture_path = request.url_root + url_for('static', filename=msg)
        Recipe.upload_recipe_image(recipe_id, picture_path)
    else:
        raise ErrorException(msg, code)
    return jsonify({'msg': msg, 'statusCode': 201, 'status': 'success'}), code
def profile_pic_upload():
    '''

        On a POST request with /profile_pic_upload will upload a picture and store in our storage

    '''
    msg, code = extract_photo(request)
    if code == 200:  #TODO HANDLE ERRORS
        picture_path = request.url_root + url_for('static', filename=msg)
        user_id = g.user.id
        User.upload_profile_image(user_id, picture_path)
    else:
        raise ErrorException(msg, code)
    return jsonify({'msg': msg, 'statusCode': 201, 'status': 'success'}), code
def edit_user(id):
    '''
        On POST edit a user's profile

    '''
    # If the requesting user is not the user who's info is requested.
    if g.user.id != id:
        raise ErrorException('Your authentication is invalid', 401)
    user = User.query.get(id)
    # Can't find user in db.
    if not user:
        raise ErrorException('This user does not exist in the database', 500)
    old_pass = request.json.get('old_password')
    new_pass = request.json.get('new_password')
    if user.verify_password(old_pass):
        user.hash_password(new_pass)
    else:
        raise ErrorException('Your old password is invalid', 500)
    db.session.commit()
    return jsonify({
        'message': 'Success',
        'statusCode': 200,
        'status': 'success'
    })
def delete_rating():
    '''

        On a POST request with /delete_rating will delete a rating
            for post, please supply 'ratingId'

    '''
    ratingId = request.json.get('ratingId')
    userId = request.json.get('userId')
    if not Rating.delete_rating(ratingId, userId):
        raise ErrorException('User does not own the rating', 500)
    return {
        'rating_id': ratingId,
        'message': 'Rating has been deleted',
        'statusCode': 201,
        'status': 'success'
    }
def rating(id):
    '''
        On a GET request with /rating/recipe_id it will return all the ratings for a recipe

        On a POST request with /rating/recipe_id will add a new rating
            for post, please supply 'rating' between 1 and 5, 'comment', 'user_id' as a JSON
    '''
    if request.method == 'POST':
        rating = request.json.get('rating')
        if int(rating) > 5 or int(rating) < 1:
            raise ErrorException('Ratings must be between 1 and 5', 500)
        comment = request.json.get('comment')
        user = User.query.filter_by(id=request.json.get('user_id')).first()
        recipe = Recipe.query.filter_by(id=id).first()
        new_rating = Rating(rating=int(rating), comment=comment)
        # Check if rating already exists by a user
        for user_rating in user.rating:
            for rating_recipe in user_rating.recipe:
                if rating_recipe == recipe:
                    user_rating.rating = rating
                    user_rating.comment = comment
                    db.session.commit()
                    return jsonify({
                        'id': user_rating.id,
                        'rating': user_rating.rating,
                        'comment': user_rating.comment
                    })
        # return jsonify(Rating.json_dump(user.rating))
        recipe.rating.append(new_rating)
        user.rating.append(new_rating)
        db.session.add(new_rating)
        db.session.commit()
        return jsonify({
            'id': new_rating.id,
            'rating': new_rating.rating,
            'comment': new_rating.comment
        })
    recipe = Recipe.query.filter_by(id=id).first()
    ratings = recipe.rating
    return jsonify(Rating.json_dump(ratings))
def edit_recipe():
    '''

        On a POST request with /edit_recipe will add a new recipe
            for post, please supply 'name', 'instruction', 'mealType' and 'ingredients'

    '''
    recipe_id = request.json.get('id')
    name = request.json.get('name')
    instruction = request.json.get('instruction')
    mealType = request.json.get('mealType')
    ingredients = request.json.get('ingredients')
    recipe = Recipe.edit_recipe(recipe_id, name, instruction, mealType,
                                ingredients)
    if g.user.id != recipe.user_id:
        raise ErrorException('This is not your recipe, you cannot edit it.',
                             401)
    return {
        'recipe_id': recipe.id,
        'message': 'Recipe has been edited',
        'statusCode': 201,
        'status': 'success'
    }
    def edit_recipe(recipe_id, name, instruction, mealType, ingredients):
        recipe = Recipe.query.get(recipe_id)
        if not recipe:
            raise ErrorException('Recipe id does not exist: ' + recipe_id, 400)

        recipe.name = name
        recipe.instructions = [RecipeInstructions(instruction=x) for x in instruction]

        db_mealtype = Mealtype.query.filter(func.lower(Mealtype.name) == func.lower(mealType)).first()
        if db_mealtype:
            recipe.mealtypes = [db_mealtype]

        # Check if any of the ingredients have been updated to avoid an additional delete and write
        recipe_ingredients = (RecipeIngredients
                              .query
                              .with_entities(Ingredient.name, RecipeIngredients.quantity)
                              .filter(RecipeIngredients.ingredient_id == Ingredient.id)
                              .filter(RecipeIngredients.recipe_id == recipe.id)
                              .all()
        )
        ingredients = [(x['name'], x['quantity']) for x in ingredients]

        if recipe_ingredients != ingredients:
            # Ingredients have changed, updating db..
            RecipeIngredients.query.filter_by(recipe_id=recipe.id).delete()
            for name, quantity in ingredients:
                ingredient = Ingredient.query.filter(func.lower(Ingredient.name) == func.lower(name)).first()
                # doesn't support ingredient creation
                if ingredient:
                    recipe_ingredient = RecipeIngredients(quantity=quantity)
                    recipe_ingredient.ingredients = ingredient
                    recipe.ingredients.append(recipe_ingredient)

        db.session.commit()
        IngredientSets.remove_sets(recipe)
        return recipe
def auth_error(status):
    raise ErrorException('Your authentication is invalid', 401)