Frontend Refactor + Bug Fixes

* merge category and tag selector

* unifiy category selector

* add hint

* spacing

* fix nextcloud migration

* simplify email validator #261

* formatting

* cleanup

* auto-gen

* format

* update run script

* unified category/tag selector

* rename component

* Add advanced search link

* remove old code

* convert keywords to tags

* add proper behavior on rename

* proper image name association on rename

* fix test cleanup

* changelog

* set docker comppand

* minify on migration

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden
2021-04-06 22:29:02 -08:00
committed by GitHub
parent a396604520
commit 1cf95bb3b0
32 changed files with 334 additions and 356 deletions

View File

@@ -127,5 +127,3 @@ class AppSettings(BaseSettings):
settings = AppSettings()
print(settings.dict())

View File

@@ -9,7 +9,8 @@ from mealie.db.models.users import User
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
from mealie.schema.meal import MealPlanInDB
from mealie.schema.recipe import Recipe
from mealie.schema.settings import CustomPageOut, SiteSettings as SiteSettingsSchema
from mealie.schema.settings import CustomPageOut
from mealie.schema.settings import SiteSettings as SiteSettingsSchema
from mealie.schema.sign_up import SignUpOut
from mealie.schema.theme import SiteTheme
from mealie.schema.user import GroupInDB, UserInDB

View File

@@ -60,7 +60,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
@validates("name")
def validate_name(self, key, name):
assert not name == ""
assert name != ""
return name
def __init__(
@@ -92,11 +92,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
self.image = image
self.recipeCuisine = recipeCuisine
if self.nutrition:
self.nutrition = Nutrition(**nutrition)
else:
self.nutrition = Nutrition()
self.nutrition = Nutrition(**nutrition) if self.nutrition else Nutrition()
self.tools = [Tool(tool=x) for x in tools] if tools else []
self.recipeYield = recipeYield

View File

@@ -1,7 +1,7 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.db.models.model_base import SqlAlchemyBase
from fastapi.logger import logger
from mealie.db.models.model_base import SqlAlchemyBase
from slugify import slugify
from sqlalchemy.orm import validates
@@ -25,7 +25,7 @@ class Tag(SqlAlchemyBase):
assert name != ""
return name
def __init__(self, name) -> None:
def __init__(self, name, session=None) -> None:
self.name = name.strip()
self.slug = slugify(self.name)

View File

@@ -7,7 +7,7 @@ from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.recipe import Recipe, RecipeURLIn
from mealie.schema.snackbar import SnackResponse
from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, write_image
from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, rename_image, write_image
from mealie.services.scraper.scraper import create_from_url
from sqlalchemy.orm.session import Session
@@ -61,6 +61,9 @@ def update_recipe(
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
if recipe_slug != recipe.slug:
rename_image(original_slug=recipe_slug, new_slug=recipe.slug)
return recipe.slug

View File

@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.category import RecipeTagResponse
from mealie.schema.category import RecipeTagResponse, TagIn
from mealie.schema.snackbar import SnackResponse
from sqlalchemy.orm.session import Session
@@ -19,6 +19,14 @@ async def get_all_recipe_tags(session: Session = Depends(generate_session)):
""" Returns a list of available tags in the database """
return db.tags.get_all_limit_columns(session, ["slug", "name"])
@router.post("")
async def create_recipe_tag(
tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
):
""" Creates a Tag in the database """
return db.tags.create(session, tag.dict())
@router.get("/{tag}", response_model=RecipeTagResponse)
def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)):

View File

@@ -4,7 +4,7 @@ from datetime import timedelta
from fastapi import APIRouter, Depends, File, UploadFile
from fastapi.responses import FileResponse
from mealie.core import security
from mealie.core.config import settings, app_dirs
from mealie.core.config import app_dirs, settings
from mealie.core.security import get_password_hash, verify_password
from mealie.db.database import db
from mealie.db.db_setup import generate_session

View File

@@ -3,24 +3,30 @@
# Get Reload Arg `run.sh reload` for dev server
ARG1=${1:-production}
# Initialize Database Prerun
python mealie/db/init_db.py
python mealie/services/image/minify.py
# Set Script Directory - Used for running the script from a different directory.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
## Migrations
# # Initialize Database Prerun
poetry run python $DIR/db/init_db.py
poetry run python $DIR/services/image/minify.py
# Migrations
# TODO
# Migrations
# Set Port from ENV Variable
if [ "$ARG1" = "reload" ]
if [[ "$ARG1" = "reload" ]]
then
echo "Hot reload"
echo "Hot Reload!"
# Start API
uvicorn mealie.app:app --host 0.0.0.0 --port 9000 --reload
else
echo "Production config"
echo "Production"
# Web Server
caddy start --config ./Caddyfile
# Start API
uvicorn mealie.app:app --host 0.0.0.0 --port 9000
fi
fi

View File

@@ -23,6 +23,10 @@ class RecipeCategoryResponse(CategoryBase):
schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}
class TagIn(CategoryIn):
pass
class TagBase(CategoryBase):
pass

View File

@@ -29,7 +29,6 @@ def read_image(recipe_slug: str, image_type: str = "original") -> Path:
Returns:
Path: [description]
"""
print(image_type)
recipe_slug = recipe_slug.split(".")[0] # Incase of File Name
recipe_image_dir = app_dirs.IMG_DIR.joinpath(recipe_slug)
@@ -39,6 +38,18 @@ def read_image(recipe_slug: str, image_type: str = "original") -> Path:
return None
def rename_image(original_slug, new_slug) -> Path:
current_path = app_dirs.IMG_DIR.joinpath(original_slug)
new_path = app_dirs.IMG_DIR.joinpath(new_slug)
try:
new_path = current_path.rename(new_path)
except FileNotFoundError:
logger.error(f"Image Directory {original_slug} Doesn't Exist")
return new_path
def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path.name:
try:
delete_image(recipe_slug)

View File

@@ -1,8 +1,12 @@
import shutil
from pathlib import Path
from fastapi.logger import logger
from mealie.core.config import app_dirs
from PIL import Image, UnidentifiedImageError
from mealie.db.database import db
from mealie.db.db_setup import create_session
from PIL import Image
from sqlalchemy.orm.session import Session
def minify_image(image_file: Path, min_dest: Path, tiny_dest: Path):
@@ -24,7 +28,7 @@ def minify_image(image_file: Path, min_dest: Path, tiny_dest: Path):
tiny_image = crop_center(img)
tiny_image.save(tiny_dest, quality=70)
except:
except Exception:
shutil.copy(image_file, min_dest)
shutil.copy(image_file, tiny_dest)
@@ -59,6 +63,28 @@ def move_all_images():
image_file.rename(new_folder.joinpath(f"original{image_file.suffix}"))
def validate_slugs_in_database(session: Session = None):
def check_image_path(image_name: str, slug_path: str) -> bool:
existing_path: Path = app_dirs.IMG_DIR.joinpath(image_name)
slug_path: Path = app_dirs.IMG_DIR.joinpath(slug_path)
if existing_path.is_dir():
slug_path.rename(existing_path)
else:
logger.info("No Image Found")
session = session or create_session()
all_recipes = db.recipes.get_all(session)
slugs_and_images = [(x.slug, x.image) for x in all_recipes]
for slug, image in slugs_and_images:
image_slug = image.split(".")[0] # Remove Extension
if slug != image_slug:
logger.info(f"{slug}, Doesn't Match '{image_slug}'")
check_image_path(image, slug)
def migrate_images():
print("Checking for Images to Minify...")
@@ -77,10 +103,11 @@ def migrate_images():
org_size = sizeof_fmt(image.stat().st_size)
dest_size = sizeof_fmt(min_dest.stat().st_size)
tiny_size = sizeof_fmt(tiny_dest.stat().st_size)
print(f"{image.name} Minified: {org_size} -> {dest_size} -> {tiny_size}")
logger.info(f"{image.name} Minified: {org_size} -> {dest_size} -> {tiny_size}")
print("Finished Minification Check")
logger.info("Finished Minification Check")
if __name__ == "__main__":
migrate_images()
validate_slugs_in_database()

View File

@@ -6,6 +6,7 @@ from fastapi.logger import logger
from mealie.core.config import app_dirs
from mealie.db.database import db
from mealie.schema.recipe import Recipe
from mealie.services.image.minify import migrate_images
from mealie.utils.unzip import unpack_zip
from sqlalchemy.orm.session import Session
@@ -89,4 +90,5 @@ def chowdown_migrate(session: Session, zip_file: Path):
failed_images.append(image.name)
report = {"successful": successful_recipes, "failed": failed_recipes}
migrate_images()
return report

View File

@@ -7,6 +7,7 @@ from pathlib import Path
from mealie.core.config import app_dirs
from mealie.db.database import db
from mealie.schema.recipe import Recipe
from mealie.services.image import minify
from mealie.services.scraper.cleaner import Cleaner
@@ -23,39 +24,43 @@ def process_selection(selection: Path) -> Path:
return None
def clean_nextcloud_tags(nextcloud_tags: str):
if not isinstance(nextcloud_tags, str):
return None
return [x.title().lstrip() for x in nextcloud_tags.split(",") if x != ""]
def import_recipes(recipe_dir: Path) -> Recipe:
image = False
for file in recipe_dir.glob("full.*"):
image = file
break
for file in recipe_dir.glob("*.json"):
recipe_file = file
break
with open(recipe_file, "r") as f:
recipe_dict = json.loads(f.read())
recipe_data = Cleaner.clean(recipe_dict)
image_name = None
if image:
image_name = recipe_data["slug"] + image.suffix
recipe_data["image"] = image_name
else:
recipe_data["image"] = "none"
image_name = recipe_data["slug"]
recipe_data["image"] = recipe_data["slug"]
recipe_data["tags"] = clean_nextcloud_tags(recipe_data.get("keywords"))
recipe = Recipe(**recipe_data)
if image:
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image_name))
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image_name + image.suffix))
return recipe
def prep():
try:
shutil.rmtree(app_dirs.TEMP_DIR)
except:
pass
shutil.rmtree(app_dirs.TEMP_DIR, ignore_errors=True)
app_dirs.TEMP_DIR.mkdir(exist_ok=True, parents=True)
@@ -80,11 +85,13 @@ def migrate(session, selection: str):
db.recipes.create(session, recipe.dict())
successful_imports.append(recipe.name)
except:
except Exception:
session.rollback()
logging.error(f"Failed Nextcloud Import: {dir.name}")
logging.exception("")
failed_imports.append(dir.name)
cleanup()
minify.migrate_images()
return {"successful": successful_imports, "failed": failed_imports}