Exemplo n.º 1
0
 def decorated_function(*args, **kwargs):
     api_enabled = configparser_results(
         current_app.config['CONFIG_INI']).getboolean(
             'general', 'api_enabled')
     api_key = configparser_results(current_app.config['CONFIG_INI']).get(
         'general', 'api_key')
     if api_enabled and request.headers.get(
             'X-Salt-to-Taste-API-Key') and request.headers.get(
                 'X-Salt-to-Taste-API-Key') == api_key:
         return view_function(*args, **kwargs)
     else:
         abort(401)
Exemplo n.º 2
0
 def decorated_function(*args, **kwargs):
     authentication_enabled = configparser_results(
         current_app.config['CONFIG_INI']).getboolean(
             'general', 'authentication_enabled')
     user_exists = get_user_by_id(1)
     userless_recipes = configparser_results(
         current_app.config['CONFIG_INI']).getboolean(
             'general', 'userless_recipes')
     if (not userless_recipes and
         (authentication_enabled
          and user_exists)) and not current_user.is_authenticated:
         return current_app.login_manager.unauthorized()
     else:
         return view_function(*args, **kwargs)
Exemplo n.º 3
0
def backup_image_file(filename):
    date = datetime.today().timestamp()
    config = configparser_results(current_app.config['CONFIG_INI'])
    root_ext = os.path.splitext(filename)
    backup_dir = f"{DATA_DIR}/backups/"
    backup_filename = f"{root_ext[0].lower()}.backup-{date}{root_ext[1].lower()}"
    files = [
        name for name in os.listdir(backup_dir)
        if os.path.isfile(os.path.join(backup_dir, name))
        if root_ext[0] in name if root_ext[1] in name
    ]

    # Delete oldest backup if the backup_count has been met
    if config.getboolean(
            'general',
            'backups_enabled') and config['general']['backup_count']:
        if files and config.getint('general', 'backup_count') <= len(files):
            os.remove(os.path.join(backup_dir, files[0]))

        print(f' + Backing up {filename}')
        try:
            shutil.copyfile(f"{current_app.config['RECIPE_IMAGES']}{filename}",
                            f'{backup_dir}{backup_filename}')
        except Exception as e:
            print(f' * ERROR: Failed to backup {filename}')
            print(f' * {e}')
Exemplo n.º 4
0
def read_config():
    config = configparser_results(current_app.config['CONFIG_INI'])

    return dict(
        user_exists = get_user_by_id(1),
        authentication_enabled = config.getboolean('general', 'authentication_enabled'),
        api_enabled = config.getboolean('general', 'api_enabled'),
        backups_enabled = config.getboolean('general', 'backups_enabled'),
        custom_tags = decode_tags(config)
    )
Exemplo n.º 5
0
def settings_json():
    if request.method == 'GET':
        config = configparser_results(current_app.config['CONFIG_INI'])

        return jsonify({'settings': config._sections})

    if request.method == 'PUT':
        config = request.get_json()
        update_file = update_configfile(config)

        if update_file:
            return jsonify({'success': 'settings updated'})

        return jsonify({'error': 'settings were unable to be updated'})
Exemplo n.º 6
0
def settings():
    config = configparser_results(current_app.config['CONFIG_INI'])

    user_query = get_user_by_id(1)

    form = SettingsForm()

    if form.validate_on_submit():
        config_data = form.data
        parsed_data = parse_settings(config, config_data)
        parsed_data['tags'] = encode_tags(config, form.tag_name.data, form.tag_icon.data, form.tag_color.data, form.tag_bcolor.data)

        if user_query and form.username.data != '' and form.password.data != ('**********' or ''):
            print ('user_query was true, form had username, password was not placeholder. UPDATING USER.')
            user_query.username = form.username.data
            user_query.password = form.password.data
            db.session.commit()

        elif not user_query and form.username.data != '' and form.password.data !='':
            print ('user_query was false, form had username, form had password. ADDING USER')
            user = User(username=form.username.data, password=form.password.data, role='admin')
            db.session.add(user)
            db.session.commit()

        elif user_query and form.username.data == '' and form.password.data == '':
            print ('username and password removed. DELETING USER')
            delete_user_by_id(user_query.id)

        update_configfile(parsed_data)
        flash('Settings saved.', 'success')

        return redirect(url_for('main.settings'))

    form.authentication_enabled.data = config['general']['authentication_enabled']
    if user_query:
        form.username.data = user_query.username
        form.password.data = '**********'
    form.userless_recipes.data = config['general']['userless_recipes']
    form.api_enabled.data = config['general']['api_enabled']
    form.api_key.data = config['general']['api_key']
    form.backups_enabled.data = config['general']['backups_enabled']
    form.backup_count.data = config['general']['backup_count']
    form.edamam_id.data = config['third_party']['edamam_id']
    form.edamam_key.data = config['third_party']['edamam_key']

    return render_template("settings.html", form=form)
Exemplo n.º 7
0
def backup_config_file():
    date = datetime.today().timestamp()
    config = configparser_results(current_app.config['CONFIG_INI'])
    backup_dir = f"{DATA_DIR}/backups/"
    files = [
        name for name in os.listdir(backup_dir)
        if os.path.isfile(os.path.join(backup_dir, name)) if 'config' in name
        if '.ini' in name
    ]

    if config.getboolean(
            'general',
            'backups_enabled') and config['general']['backup_count']:
        if files and config.getint('general', 'backup_count') <= len(files):
            os.remove(os.path.join(backup_dir, files[0]))

        print(' + Backing up config.ini')
        shutil.copyfile(f'{DATA_DIR}/config.ini',
                        f'{backup_dir}config.backup-{date}.ini')
Exemplo n.º 8
0
def delete(recipe_id):
    recipe_query = get_recipe(recipe_id)
    config = configparser_results(current_app.config['CONFIG_INI'])

    if recipe_query:
        if config.getboolean('general', 'backups_enabled'):
            backup_recipe_file(recipe_query['filename'])
            backup_database_file()

        delete_file(current_app.config['RECIPE_FILES'], recipe_query['filename'])
        if 'image' in recipe_query and recipe_query['image'] != None:
            if config.getboolean('general', 'backups_enabled'):
                backup_image_file(recipe_query['image'])

            delete_file(current_app.config['RECIPE_IMAGES'], recipe_query['image'])
        delete_recipe(recipe_id)

        flash(f"Recipe \"{recipe_query['title']}\" deleted.", 'success')
        return redirect(url_for('main.index'))

    flash("Recipe not deleted, ID not found.", 'danger')
    return redirect(url_for('main.index'))
Exemplo n.º 9
0
def delete_recipe_json(recipe_id):
    recipe_query = get_recipe(recipe_id)
    config = configparser_results(current_app.config['CONFIG_INI'])

    if recipe_query:
        if config.getboolean('general', 'backups_enabled'):
            backup_recipe_file(recipe_query['filename'])
            backup_database_file()

        delete_file(current_app.config['RECIPE_FILES'],
                    recipe_query['filename'])
        if 'image' in recipe_query and recipe_query['image'] != None:
            if config.getboolean('general', 'backups_enabled'):
                backup_image_file(recipe_query['image'])

            delete_file(current_app.config['RECIPE_IMAGES'],
                        recipe_query['image'])
        delete_recipe(recipe_id)

        return jsonify({'success': 'recipe deleted'})

    return jsonify({'error': 'recipe ID not found'})
Exemplo n.º 10
0
def backup_database_file():
    date = datetime.today().timestamp()
    config = configparser_results(current_app.config['CONFIG_INI'])
    backup_dir = f"{DATA_DIR}/backups/"
    files = [
        name for name in os.listdir(backup_dir)
        if os.path.isfile(os.path.join(backup_dir, name)) if 'database' in name
        if '.db' in name
    ]

    if config.getboolean(
            'general',
            'backups_enabled') and config['general']['backup_count']:
        if files and config.getint('general', 'backup_count') <= len(files):
            os.remove(os.path.join(backup_dir, files[0]))

        print(' + Backing up database.db')
        try:
            shutil.copyfile(f'{DATA_DIR}/database.db',
                            f'{backup_dir}database.backup-{date}.db')
        except Exception as e:
            print(f' * ERROR: Failed to backup database')
            print(f' * {e}')
Exemplo n.º 11
0
def update(recipe_id):
    # Class to convert recipe dict to obj
    class Struct:
        def __init__(self, **entries):
            self.__dict__.update(entries)

    config = configparser_results(current_app.config['CONFIG_INI'])
    recipe_query = get_recipe(recipe_id)

    if not recipe_query:
        abort(404)

    recipe_query['tags'] = ", ".join(recipe_query['tags'])
    recipe_obj = Struct(**recipe_query)
    form = UpdateRecipeForm(obj=recipe_obj)

    if form.validate_on_submit():
        title_formatted = form.title.data.replace(" ", "-").lower()
        filename = f"{title_formatted}.yaml"
        image_data = form.image.data
        tags = form.tags.data.split(',')

        while("" in tags) :
            tags.remove("")

        if hasattr(image_data, 'filename'):
            if config.getboolean('general', 'backups_enabled'):
                backup_image_file(recipe_query['image'])

            ext_new = image_data.filename.rsplit('.', 1)[1].lower()
            ext_old = recipe_query['image'].rsplit('.', 1)[1].lower

            if ext_new != ext_old:
                delete_file(current_app.config['RECIPE_IMAGES'], recipe_query['image'])

            image_data.filename = f'{title_formatted}.{ext_new}'
            image = secure_filename(image_data.filename)
            save_image(current_app.config['RECIPE_IMAGES'], image, image_data)
        else:
            image = recipe_query['image']

        recipe = OrderedDict({
            'layout' : form.layout.data or recipe_query['layout'],
            'title' : form.title.data,
            'title_formatted' : title_formatted,
            'image' : image,
            'imagecredit' : form.imagecredit.data or None,
            'tags' : [tag.strip(' ').lower() for tag in tags],
            'source' : form.source.data or None,
            'prep' : form.prep.data or None,
            'cook' : form.cook.data or None,
            'ready' : form.ready.data or None,
            'servings' : form.servings.data or None,
            'calories' : form.calories.data or None,
            'description' : form.description.data or None,
            'ingredients' : [x for x in form.ingredients.data if x != ''],
            'directions' : [x for x in form.directions.data if x != ''],
            'notes' : [x for x in form.notes.data if x != ''],
            'filename' : filename
        })

        if recipe['title'] != recipe_query['title']:
            if recipe['title_formatted'] != recipe_query['title_formatted']:
                # Rename image
                ext = image.rsplit('.', 1)[1].lower()
                updated_filename = f'{title_formatted}.{ext}'
                secure_file = secure_filename(updated_filename)
                rename_file(os.path.join(current_app.config['RECIPE_IMAGES'], image), os.path.join(current_app.config['RECIPE_IMAGES'], secure_file))
                recipe['image'] = secure_file

            # Rename file
            rename_file(os.path.join(current_app.config['RECIPE_FILES'], recipe_query['filename']), os.path.join(current_app.config['RECIPE_FILES'], recipe['filename']))

        if config.getboolean('general', 'backups_enabled'):
            backup_recipe_file(recipe_query['filename'])
            backup_database_file()

        create_recipe_file(current_app.config['RECIPE_FILES'], recipe)
        file = os.path.join(current_app.config['RECIPE_FILES'], recipe['filename'])
        recipe['file_hash'] = hash_file(file)
        update_recipe(recipe_query['filename'], recipe_query['title'], recipe)

        return redirect(url_for('main.recipe', title_formatted=recipe['title_formatted']))

    return render_template("update.html", form=form, id=recipe_query['id'], image=recipe_query['image'])
Exemplo n.º 12
0
def fetch_recipe_nutrition(title, servings, ingredient_list):
    argument = argparser_results()
    DATA_DIR = os.path.abspath(argument['DATA_DIR'])
    config = configparser_results(f'{DATA_DIR}/config.ini')

    app_id = config.get('third_party', 'edamam_id')
    app_key = config.get('third_party', 'edamam_key')

    if not app_id or not app_key:
        return False

    print(f' * Searching for nutrition information on {title}')

    params = {'app_id': app_id, 'app_key': app_key}
    headers = {'Content-Type': 'application/json'}
    ingredient_dict = {
        'title': title,
        'yield': servings,
        'ingr': ingredient_list
    }

    r = requests.post('https://api.edamam.com/api/nutrition-details',
                      params=params,
                      headers=headers,
                      json=ingredient_dict)

    if r.status_code != 200:
        print(
            f" * ERROR: Could not fetch nutrition data. | Code:({r.status_code} Message:{r.json()['error']})"
        )
        return False

    r_json = r.json()

    nutrition_dict = {
        'weight': r_json.get('totalWeight'),
        'nutrients': {
            'calcium': r_json['totalNutrients'].get('CA'),
            'carbs': r_json['totalNutrients'].get('CHOCDF'),
            'cholesterol': r_json['totalNutrients'].get('CHOLE'),
            'energy': r_json['totalNutrients'].get('ENERC_KCAL'),
            'fat': r_json['totalNutrients'].get('FAT'),
            'fiber': r_json['totalNutrients'].get('FIBTG'),
            'folate_equivalent': r_json['totalNutrients'].get('FOLDFE'),
            'folate_food': r_json['totalNutrients'].get('FOLFD'),
            'iron': r_json['totalNutrients'].get('FE'),
            'magnesium': r_json['totalNutrients'].get('MG'),
            'monounsaturated': r_json['totalNutrients'].get('FAMS'),
            'niacin_b3': r_json['totalNutrients'].get('NIA'),
            'phosphorus': r_json['totalNutrients'].get('P'),
            'polyunsaturated': r_json['totalNutrients'].get('FAPU'),
            'potassium': r_json['totalNutrients'].get('K'),
            'protein': r_json['totalNutrients'].get('PROCNT'),
            'riboflavin_b2': r_json['totalNutrients'].get('RIBF'),
            'saturated': r_json['totalNutrients'].get('FASAT'),
            'sodium': r_json['totalNutrients'].get('NA'),
            'sugars': r_json['totalNutrients'].get('SUGAR'),
            'sugars_added': r_json['totalNutrients'].get('SUGAR.added'),
            'thiamin_b1': r_json['totalNutrients'].get('THIA'),
            'trans': r_json['totalNutrients'].get('FATRN'),
            'vitamin_a': r_json['totalNutrients'].get('VITA_RAE'),
            'vitamin_b12': r_json['totalNutrients'].get('VITB12'),
            'vitamin_b6': r_json['totalNutrients'].get('VITB6A'),
            'vitamin_c': r_json['totalNutrients'].get('VITC'),
            'vitamin_d': r_json['totalNutrients'].get('VITD'),
            'vitamin_e': r_json['totalNutrients'].get('TOCPHA'),
            'vitamin_k': r_json['totalNutrients'].get('VITK1')
        },
        'daily': {
            'calcium': r_json['totalDaily'].get('CA'),
            'carbs': r_json['totalDaily'].get('CHOCDF'),
            'cholesterol': r_json['totalDaily'].get('CHOLE'),
            'energy': r_json['totalDaily'].get('ENERC_KCAL'),
            'fat': r_json['totalDaily'].get('FAT'),
            'fiber': r_json['totalDaily'].get('FIBTG'),
            'folate_equivalent': r_json['totalDaily'].get('FOLDFE'),
            'folate_food': r_json['totalDaily'].get('FOLFD'),
            'iron': r_json['totalDaily'].get('FE'),
            'magnesium': r_json['totalDaily'].get('MG'),
            'monounsaturated': r_json['totalDaily'].get('FAMS'),
            'niacin_b3': r_json['totalDaily'].get('NIA'),
            'phosphorus': r_json['totalDaily'].get('P'),
            'polyunsaturated': r_json['totalDaily'].get('FAPU'),
            'potassium': r_json['totalDaily'].get('K'),
            'protein': r_json['totalDaily'].get('PROCNT'),
            'riboflavin_b2': r_json['totalDaily'].get('RIBF'),
            'saturated': r_json['totalDaily'].get('FASAT'),
            'sodium': r_json['totalDaily'].get('NA'),
            'sugars': r_json['totalDaily'].get('SUGAR'),
            'sugars_added': r_json['totalDaily'].get('SUGAR.added'),
            'thiamin_b1': r_json['totalDaily'].get('THIA'),
            'trans': r_json['totalDaily'].get('FATRN'),
            'vitamin_a': r_json['totalDaily'].get('VITA_RAE'),
            'vitamin_b12': r_json['totalDaily'].get('VITB12'),
            'vitamin_b6': r_json['totalDaily'].get('VITB6A'),
            'vitamin_c': r_json['totalDaily'].get('VITC'),
            'vitamin_d': r_json['totalDaily'].get('VITD'),
            'vitamin_e': r_json['totalDaily'].get('TOCPHA'),
            'vitamin_k': r_json['totalDaily'].get('VITK1')
        }
    }

    # Only keep the quantity value for each item
    keys = nutrition_dict['nutrients'].keys()
    for section in ['nutrients', 'daily']:
        for key in keys:
            if nutrition_dict[section][key]:
                nutrition_dict[section][key] = nutrition_dict[section][key][
                    'quantity']

    return nutrition_dict
Exemplo n.º 13
0
def update_recipes_json(recipe_id):
    data = request.get_json()
    downloadImage = request.args.get('downloadImage')
    config = configparser_results(current_app.config['CONFIG_INI'])

    recipe_query = get_recipe(recipe_id)

    if not recipe_query:
        return jsonify({'error': 'recipe ID not found'})

    if not data.get('layout'):
        return jsonify({'error': 'layout missing'})

    if not data.get('title'):
        return jsonify({'error': 'title missing'})

    title_formatted = data.get('title').replace(" ", "-").lower()
    filename = f'{title_formatted}.yaml'

    if check_for_duplicate_title_f(recipe_id, title_formatted):
        return jsonify({'error': 'recipe name must be unique'})

    recipe = OrderedDict({
        'layout': data.get('layout'),
        'title': data.get('title'),
        'title_formatted': title_formatted,
        'image': data.get('image'),
        'imagecredit': data.get('imagecredit'),
        'tags': data.get('tags'),
        'source': data.get('source'),
        'prep': data.get('prep'),
        'cook': data.get('cook'),
        'ready': data.get('ready'),
        'servings': data.get('servings'),
        'calories': data.get('calories'),
        'description': data.get('description'),
        'ingredients': data.get('ingredients'),
        'directions': data.get('directions'),
        'notes': data.get('notes'),
        'filename': filename
    })

    if downloadImage:
        if downloadImage != "true":
            return jsonify(
                {'error': 'downloadImage set but has invalid value'})
        if not recipe['imagecredit']:
            return jsonify(
                {'error': 'downloadImage set but imagecredit is empty'})

        if config.getboolean('general', 'backups_enabled'):
            backup_image_file(recipe_query['image'])
        image = download_image(recipe['imagecredit'],
                               current_app.config['RECIPE_IMAGES'],
                               recipe['title_formatted'])
        if not image:
            return jsonify({
                'error': 'image download failed',
                'message': 'check imagecredit value'
            })
        recipe['image'] = image

    if recipe['title'] != recipe_query['title']:
        if recipe['title_formatted'] != recipe_query['title_formatted']:
            # Rename image
            ext = recipe['image'].rsplit('.', 1)[1].lower()
            updated_filename = f'{title_formatted}.{ext}'
            secure_file = secure_filename(updated_filename)
            rename_file(
                os.path.join(current_app.config['RECIPE_IMAGES'],
                             recipe_query['image']),
                os.path.join(current_app.config['RECIPE_IMAGES'], secure_file))
            recipe['image'] = secure_file

        # Rename file
        rename_file(
            os.path.join(current_app.config['RECIPE_FILES'],
                         recipe_query['filename']),
            os.path.join(current_app.config['RECIPE_FILES'],
                         recipe['filename']))

    if config.getboolean('general', 'backups_enabled'):
        backup_recipe_file(recipe_query['filename'])

    create_recipe_file(current_app.config['RECIPE_FILES'], recipe)
    file = os.path.join(current_app.config['RECIPE_FILES'], recipe['filename'])
    recipe['file_hash'] = hash_file(file)
    update_recipe(recipe_query['filename'], recipe_query['title'], recipe)

    return jsonify({'success': 'recipe updated'})
Exemplo n.º 14
0
def create_app(config_file='settings.py'):
    argument = argparser_results()
    DATA_DIR = os.path.abspath(argument['DATA_DIR'])

    app = Flask(__name__)
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    app.config['SQLALCHEMY_ECHO'] = False
    app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DATA_DIR}/database.db'
    app.config['WHOOSH_INDEX_PATH'] = f'{DATA_DIR}/whooshIndex'
    app.config['WHOOSH_ANALYZER'] = 'StemmingAnalyzer'
    app.config['DATA_DIR'] = DATA_DIR
    app.config['RECIPE_FILES'] = f'{DATA_DIR}/_recipes/'
    app.config['RECIPE_IMAGES'] = f'{DATA_DIR}/_images/'
    app.config['CONFIG_INI'] = f'{DATA_DIR}/config.ini'
    app.jinja_env.filters['capwords'] = capwords

    if not os.path.isfile(app.config['CONFIG_INI']):
        create_default_configfile()

    verify_configfile()
    config = configparser_results(app.config['CONFIG_INI'])

    try:
        app.config['SECRET_KEY'] = config.get('flask', 'secret_key')
    except TypeError:
        print('Error: Could not find Flask secret_key in config.ini.')
        sys.exit()

    # Register blueprints
    app.register_blueprint(main)
    app.register_blueprint(api, url_prefix='/api')

    # Create indexes of database tables
    wa.search_index(app, Recipe)
    wa.search_index(app, Tag)
    wa.search_index(app, Ingredient)
    wa.search_index(app, Direction)
    wa.search_index(app, Note)

    # Initalize and create the DB
    db.init_app(app)
    db.app = app
    db.create_all()

    # Initalize the login manager plugin
    login_manager.init_app(app)
    login_manager.login_view = 'main.login'
    login_manager.login_message_category = 'info'

    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(user_id)

    # Import phyiscal recipe files
    recipe_list = recipe_importer(app.config['RECIPE_FILES'])

    # Sync physical recipe files with database
    if not Recipe.query.first():
        add_all_recipes(recipe_list)
    else:
        add_new_recipes(recipe_list)
        remove_missing_recipes(recipe_list)
        update_recipes(recipe_list)

    db_cleanup()

    return app