migration changes
@@ -7,6 +7,7 @@ from fastapi.staticfiles import StaticFiles
|
||||
from routes import (
|
||||
backup_routes,
|
||||
meal_routes,
|
||||
migration_routes,
|
||||
recipe_routes,
|
||||
setting_routes,
|
||||
static_routes,
|
||||
@@ -31,6 +32,7 @@ app.include_router(meal_routes.router)
|
||||
app.include_router(setting_routes.router)
|
||||
app.include_router(backup_routes.router)
|
||||
app.include_router(user_routes.router)
|
||||
app.include_router(migration_routes.router)
|
||||
|
||||
# API 404 Catch all CALL AFTER ROUTERS
|
||||
@app.get("/api/{full_path:path}", status_code=404, include_in_schema=False)
|
||||
|
||||
BIN
mealie/data/img/banana-bread.jpg
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
mealie/data/img/broccoli-beer-cheese-soup.jpg
Normal file
|
After Width: | Height: | Size: 519 KiB |
BIN
mealie/data/img/cauliflower-cacciatore.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 363 KiB |
BIN
mealie/data/img/crispy-carrots.jpg
Normal file
|
After Width: | Height: | Size: 794 KiB |
BIN
mealie/data/img/crockpot-buffalo-chicken.jpg
Normal file
|
After Width: | Height: | Size: 572 KiB |
BIN
mealie/data/img/downtown-marinade.jpg
Normal file
|
After Width: | Height: | Size: 528 KiB |
BIN
mealie/data/img/falafel-hummus-plate.jpg
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
mealie/data/img/french-toast.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
mealie/data/img/graham-cracker-crust.jpg
Normal file
|
After Width: | Height: | Size: 997 KiB |
BIN
mealie/data/img/green-chile-stew.jpg
Normal file
|
After Width: | Height: | Size: 556 KiB |
BIN
mealie/data/img/green-spaghetti.jpg
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
mealie/data/img/jalapeno-cornbread.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
mealie/data/img/mississippi-pot-roast.jpg
Normal file
|
After Width: | Height: | Size: 622 KiB |
BIN
mealie/data/img/mongolian-beef.jpg
Normal file
|
After Width: | Height: | Size: 812 KiB |
BIN
mealie/data/img/mushroom-risotto.jpg
Normal file
|
After Width: | Height: | Size: 650 KiB |
BIN
mealie/data/img/new-york-strip.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
mealie/data/img/nilla-wafer-french-toast.jpg
Normal file
|
After Width: | Height: | Size: 889 KiB |
BIN
mealie/data/img/one-minute-muffin.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
mealie/data/img/pace-pork.jpg
Normal file
|
After Width: | Height: | Size: 602 KiB |
|
Before Width: | Height: | Size: 74 KiB |
BIN
mealie/data/img/pork-steaks.jpg
Normal file
|
After Width: | Height: | Size: 788 KiB |
BIN
mealie/data/img/red-berry-tart.jpg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
mealie/data/img/red-berry-topping.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
mealie/data/img/red-lentil-salad.jpg
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
mealie/data/img/roasted-brussels-sprouts.jpg
Normal file
|
After Width: | Height: | Size: 664 KiB |
BIN
mealie/data/img/roasted-okra.jpg
Normal file
|
After Width: | Height: | Size: 985 KiB |
BIN
mealie/data/img/salt-vinegar-potatoes.jpg
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
mealie/data/img/smashed-carrots.jpg
Normal file
|
After Width: | Height: | Size: 413 KiB |
|
Before Width: | Height: | Size: 7.4 MiB |
|
Before Width: | Height: | Size: 115 KiB |
BIN
mealie/data/img/stuffed-peppers.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
mealie/data/img/sweet-potato-cakes-with-poached-eggs.jpg
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
mealie/data/img/tequila-beer-and-citrus-cocktail.jpg
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
mealie/data/img/vanilla-custard.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
24
mealie/routes/migration_routes.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from models.backup_models import BackupJob
|
||||
from services.migrations.chowdown import chowdown_migrate as chowdow_migrate
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/api/migration/chowdown/repo/", tags=["Migration"])
|
||||
async def import_chowdown_recipes(repo: dict):
|
||||
""" Import Chowsdown Recipes from Repo URL """
|
||||
try:
|
||||
report = chowdow_migrate(repo.get("url"))
|
||||
return SnackResponse.success(
|
||||
"Recipes Imported from Git Repo, see report for failures.",
|
||||
additional_data=report,
|
||||
)
|
||||
except:
|
||||
return HTTPException(
|
||||
status_code=400,
|
||||
detail=SnackResponse.error(
|
||||
"Unable to Migrate Recipes. See Log for Details"
|
||||
),
|
||||
)
|
||||
@@ -24,6 +24,7 @@ async def get_all_recipes(
|
||||
async def get_recipe(recipe_slug: str):
|
||||
""" Takes in a recipe slug, returns all data for a recipe """
|
||||
recipe = Recipe.get_by_slug(recipe_slug)
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
|
||||
@@ -1,26 +1,106 @@
|
||||
import collections
|
||||
import shutil
|
||||
from os.path import join
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
|
||||
import pdfkit
|
||||
import requests
|
||||
import git
|
||||
import yaml
|
||||
from git.util import join_path
|
||||
|
||||
from db.mongo_setup import global_init
|
||||
from services.image_services import IMG_DIR
|
||||
from services.recipe_services import Recipe
|
||||
|
||||
Cron = collections.namedtuple("Cron", "hours minutes")
|
||||
try:
|
||||
from yaml import CDumper as Dumper
|
||||
from yaml import CLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import Dumper, Loader
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
file = f"/home/hayden/Projects/mealie-fastAPI/mealie/chowdown.md"
|
||||
|
||||
repo = "https://github.com/clarklab/chowdown"
|
||||
|
||||
|
||||
# def cron_parser(time_str: str) -> Cron:
|
||||
# time = time_str.split(":")
|
||||
# cron = Cron(hours=time[0], minutes=time[1])
|
||||
def pull_repo(repo):
|
||||
dest_dir = CWD.joinpath("data/temp/migration/git_pull")
|
||||
if dest_dir.exists():
|
||||
shutil.rmtree(dest_dir)
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
git.Git(dest_dir).clone(repo)
|
||||
|
||||
# print(cron.hours, cron.minutes)
|
||||
repo_name = repo.split("/")[-1]
|
||||
recipe_dir = dest_dir.joinpath(repo_name, "_recipes")
|
||||
image_dir = dest_dir.joinpath(repo_name, "images")
|
||||
|
||||
return recipe_dir, image_dir
|
||||
|
||||
|
||||
# cron_parser("12:45")
|
||||
def read_chowdown_file(recipe_file: Path) -> Recipe:
|
||||
with open(recipe_file, "r") as stream:
|
||||
recipe_description: str = str
|
||||
recipe_data: dict = {}
|
||||
try:
|
||||
for x, item in enumerate(yaml.load_all(stream, Loader=Loader)):
|
||||
print(item)
|
||||
if x == 0:
|
||||
recipe_data = item
|
||||
|
||||
URL = "https://home.homelabhome.com/api/webhook/test_msg"
|
||||
from services.meal_services import MealPlan
|
||||
elif x == 1:
|
||||
recipe_description = str(item)
|
||||
|
||||
global_init()
|
||||
todays_meal = MealPlan.today()
|
||||
except yaml.YAMLError as exc:
|
||||
print(exc)
|
||||
return
|
||||
|
||||
requests.post(URL, todays_meal)
|
||||
reformat_data = {
|
||||
"name": recipe_data.get("title"),
|
||||
"description": recipe_description,
|
||||
"image": recipe_data.get("image", ""),
|
||||
"recipeIngredient": recipe_data.get("ingredients"),
|
||||
"recipeInstructions": recipe_data.get("directions"),
|
||||
"tags": recipe_data.get("tags").split(","),
|
||||
}
|
||||
|
||||
pprint(reformat_data)
|
||||
new_recipe = Recipe(**reformat_data)
|
||||
|
||||
reformated_list = []
|
||||
for instruction in new_recipe.recipeInstructions:
|
||||
reformated_list.append({"text": instruction})
|
||||
|
||||
new_recipe.recipeInstructions = reformated_list
|
||||
|
||||
return new_recipe
|
||||
|
||||
|
||||
def main():
|
||||
from db.mongo_setup import global_init
|
||||
|
||||
global_init()
|
||||
recipe_dir, image_dir = pull_repo(repo)
|
||||
|
||||
failed_images = []
|
||||
for image in image_dir.iterdir():
|
||||
try:
|
||||
image.rename(IMG_DIR.joinpath(image.name))
|
||||
except:
|
||||
failed_images.append(image.name)
|
||||
|
||||
failed_recipes = []
|
||||
for recipe in recipe_dir.glob("*.md"):
|
||||
print(recipe.name)
|
||||
try:
|
||||
new_recipe = read_chowdown_file(recipe)
|
||||
new_recipe.save_to_db()
|
||||
|
||||
except:
|
||||
failed_recipes.append(recipe.name)
|
||||
|
||||
report = {"failedImages": failed_images, "failedRecipes": failed_recipes}
|
||||
|
||||
print(report)
|
||||
|
||||
|
||||
main()
|
||||
|
||||
91
mealie/services/migrations/chowdown.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import git
|
||||
import yaml
|
||||
from services.image_services import IMG_DIR
|
||||
from services.recipe_services import Recipe
|
||||
|
||||
try:
|
||||
from yaml import CLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import Loader
|
||||
|
||||
TEMP_DIR = Path(__file__).parent.parent.parent.joinpath("temp")
|
||||
|
||||
|
||||
def pull_repo(repo):
|
||||
dest_dir = TEMP_DIR.joinpath("/migration/git_pull")
|
||||
if dest_dir.exists():
|
||||
shutil.rmtree(dest_dir)
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
git.Git(dest_dir).clone(repo)
|
||||
|
||||
repo_name = repo.split("/")[-1]
|
||||
recipe_dir = dest_dir.joinpath(repo_name, "_recipes")
|
||||
image_dir = dest_dir.joinpath(repo_name, "images")
|
||||
|
||||
return recipe_dir, image_dir
|
||||
|
||||
|
||||
def read_chowdown_file(recipe_file: Path) -> Recipe:
|
||||
with open(recipe_file, "r") as stream:
|
||||
recipe_description: str = str
|
||||
recipe_data: dict = {}
|
||||
try:
|
||||
for x, item in enumerate(yaml.load_all(stream, Loader=Loader)):
|
||||
print(item)
|
||||
if x == 0:
|
||||
recipe_data = item
|
||||
|
||||
elif x == 1:
|
||||
recipe_description = str(item)
|
||||
|
||||
except yaml.YAMLError as exc:
|
||||
print(exc)
|
||||
return
|
||||
|
||||
reformat_data = {
|
||||
"name": recipe_data.get("title"),
|
||||
"description": recipe_description,
|
||||
"image": recipe_data.get("image", ""),
|
||||
"recipeIngredient": recipe_data.get("ingredients"),
|
||||
"recipeInstructions": recipe_data.get("directions"),
|
||||
"tags": recipe_data.get("tags").split(","),
|
||||
}
|
||||
|
||||
new_recipe = Recipe(**reformat_data)
|
||||
|
||||
reformated_list = []
|
||||
for instruction in new_recipe.recipeInstructions:
|
||||
reformated_list.append({"text": instruction})
|
||||
|
||||
new_recipe.recipeInstructions = reformated_list
|
||||
|
||||
return new_recipe
|
||||
|
||||
|
||||
def chowdown_migrate(repo):
|
||||
recipe_dir, image_dir = pull_repo(repo)
|
||||
|
||||
failed_images = []
|
||||
for image in image_dir.iterdir():
|
||||
try:
|
||||
shutil.copy(image, IMG_DIR.joinpath(image.name))
|
||||
except:
|
||||
failed_images.append(image.name)
|
||||
|
||||
failed_recipes = []
|
||||
for recipe in recipe_dir.glob("*.md"):
|
||||
print(recipe.name)
|
||||
try:
|
||||
new_recipe = read_chowdown_file(recipe)
|
||||
new_recipe.save_to_db()
|
||||
|
||||
except:
|
||||
failed_recipes.append(recipe.name)
|
||||
|
||||
|
||||
report = {"failedImages": failed_images, "failedRecipes": failed_recipes}
|
||||
|
||||
return report
|
||||
@@ -1,32 +1,39 @@
|
||||
class SnackResponse:
|
||||
@staticmethod
|
||||
def _create_response(message: str, type: str) -> dict:
|
||||
return {"snackbar": {"text": message, "type": type}}
|
||||
def _create_response(message: str, type: str, additional_data: dict = None) -> dict:
|
||||
|
||||
snackbar = {"snackbar": {"text": message, "type": type}}
|
||||
|
||||
if additional_data:
|
||||
snackbar.update(additional_data)
|
||||
print(snackbar)
|
||||
|
||||
return snackbar
|
||||
|
||||
@staticmethod
|
||||
def primary(message: str) -> dict:
|
||||
return SnackResponse._create_response(message, "primary")
|
||||
def primary(message: str, additional_data: dict = None) -> dict:
|
||||
return SnackResponse._create_response(message, "primary", additional_data)
|
||||
|
||||
@staticmethod
|
||||
def accent(message: str) -> dict:
|
||||
return SnackResponse._create_response(message, "accent")
|
||||
def accent(message: str, additional_data: dict = None) -> dict:
|
||||
return SnackResponse._create_response(message, "accent", additional_data)
|
||||
|
||||
@staticmethod
|
||||
def secondary(message: str) -> dict:
|
||||
return SnackResponse._create_response(message, "secondary")
|
||||
def secondary(message: str, additional_data: dict = None) -> dict:
|
||||
return SnackResponse._create_response(message, "secondary", additional_data)
|
||||
|
||||
@staticmethod
|
||||
def success(message: str) -> dict:
|
||||
return SnackResponse._create_response(message, "success")
|
||||
def success(message: str, additional_data: dict = None) -> dict:
|
||||
return SnackResponse._create_response(message, "success", additional_data)
|
||||
|
||||
@staticmethod
|
||||
def info(message: str) -> dict:
|
||||
return SnackResponse._create_response(message, "info")
|
||||
def info(message: str, additional_data: dict = None) -> dict:
|
||||
return SnackResponse._create_response(message, "info", additional_data)
|
||||
|
||||
@staticmethod
|
||||
def warning(message: str) -> dict:
|
||||
return SnackResponse._create_response(message, "warning")
|
||||
def warning(message: str, additional_data: dict = None) -> dict:
|
||||
return SnackResponse._create_response(message, "warning", additional_data)
|
||||
|
||||
@staticmethod
|
||||
def error(message: str) -> dict:
|
||||
return SnackResponse._create_response(message, "error")
|
||||
def error(message: str, additional_data: dict = None) -> dict:
|
||||
return SnackResponse._create_response(message, "error", additional_data)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
const config = (() => {
|
||||
return {
|
||||
"VUE_APP_API_BASE_URL": "REPLACE_ME",
|
||||
};
|
||||
})();
|
||||
@@ -1 +0,0 @@
|
||||
.card-btn{margin-top:-10px}.disabled-card{opacity:1%}.img-input{position:absolute;bottom:0}
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>frontend</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/static/css/app.e50b23f1.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.e0416589.css" rel="preload" as="style"><link href="/static/js/app.b457c0af.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.a435ad20.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.e0416589.css" rel="stylesheet"><link href="/static/css/app.e50b23f1.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.a435ad20.js"></script><script src="/static/js/app.b457c0af.js"></script></body></html>
|
||||