From 503fe5cb2e6494cd4d03825ac25542a56f3e7a26 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 23 May 2021 15:05:39 -0800 Subject: [PATCH] bug/bug-fixes (#424) * fix image write/caching * Caddyfile Caching header * more aggressive caching Co-authored-by: hay-kot --- Caddyfile | 7 +++++++ frontend/src/api/recipe.js | 12 ++++++------ frontend/src/components/MealPlan/MealPlanNew.vue | 4 ---- frontend/src/components/Recipe/CardImage.vue | 11 +++++++---- frontend/src/components/Recipe/MobileRecipeCard.vue | 4 ++-- frontend/src/components/Recipe/RecipeCard.vue | 6 +++--- frontend/src/pages/Recipe/ViewRecipe.vue | 11 +++++++---- mealie/db/database.py | 5 +++-- mealie/routes/recipe/recipe_crud_routes.py | 9 ++++----- mealie/services/image/image.py | 2 +- mealie/services/image/minify.py | 7 ++++--- 11 files changed, 44 insertions(+), 34 deletions(-) diff --git a/Caddyfile b/Caddyfile index 73fb21ab345b..ee380a944b2b 100644 --- a/Caddyfile +++ b/Caddyfile @@ -5,12 +5,18 @@ :80 { @proxied path /api/* /docs /openapi.json + + @static { + file + path *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.woff *.woff2 *.webp + } encode gzip zstd uri strip_suffix / # Handles Recipe Images / Assets handle_path /api/media/recipes/* { + header @static Cache-Control max-age=31536000 root * /app/data/recipes/ file_server } @@ -20,6 +26,7 @@ } handle { + header @static Cache-Control max-age=31536000 root * /app/dist try_files {path}.html {path} /index.html file_server diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index 1577a672cc54..7323ae333962 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -146,16 +146,16 @@ export const recipeAPI = { return response.data; }, - recipeImage(recipeSlug) { - return `/api/media/recipes/${recipeSlug}/images/original.webp`; + recipeImage(recipeSlug, version = null, key = null) { + return `/api/media/recipes/${recipeSlug}/images/original.webp?&rnd=${key}&version=${version}`; }, - recipeSmallImage(recipeSlug) { - return `/api/media/recipes/${recipeSlug}/images/min-original.webp`; + recipeSmallImage(recipeSlug, version = null, key = null) { + return `/api/media/recipes/${recipeSlug}/images/min-original.webp?&rnd=${key}&version=${version}`; }, - recipeTinyImage(recipeSlug) { - return `/api/media/recipes/${recipeSlug}/images/tiny-original.webp`; + recipeTinyImage(recipeSlug, version = null, key = null) { + return `/api/media/recipes/${recipeSlug}/images/tiny-original.webp?&rnd=${key}&version=${version}`; }, recipeAssetPath(recipeSlug, assetName) { diff --git a/frontend/src/components/MealPlan/MealPlanNew.vue b/frontend/src/components/MealPlan/MealPlanNew.vue index 00d41f632f42..d5bf0582868f 100644 --- a/frontend/src/components/MealPlan/MealPlanNew.vue +++ b/frontend/src/components/MealPlan/MealPlanNew.vue @@ -209,10 +209,6 @@ export default { } }, - getImage(image) { - return api.recipes.recipeSmallImage(image); - }, - formatDate(date) { if (!date) return null; diff --git a/frontend/src/components/Recipe/CardImage.vue b/frontend/src/components/Recipe/CardImage.vue index 061d704130fe..f3667b3c2aa1 100644 --- a/frontend/src/components/Recipe/CardImage.vue +++ b/frontend/src/components/Recipe/CardImage.vue @@ -42,6 +42,9 @@ export default { slug: { default: null, }, + imageVersion: { + default: null, + }, height: { default: 200, }, @@ -65,14 +68,14 @@ export default { }; }, methods: { - getImage(image) { + getImage(slug) { switch (this.imageSize) { case "tiny": - return api.recipes.recipeTinyImage(image); + return api.recipes.recipeTinyImage(slug, this.imageVersion); case "small": - return api.recipes.recipeSmallImage(image); + return api.recipes.recipeSmallImage(slug, this.imageVersion); case "large": - return api.recipes.recipeImage(image); + return api.recipes.recipeImage(slug, this.imageVersion); } }, }, diff --git a/frontend/src/components/Recipe/MobileRecipeCard.vue b/frontend/src/components/Recipe/MobileRecipeCard.vue index f1941012cdfc..2fa0add0dd1a 100644 --- a/frontend/src/components/Recipe/MobileRecipeCard.vue +++ b/frontend/src/components/Recipe/MobileRecipeCard.vue @@ -59,8 +59,8 @@ export default { }; }, methods: { - getImage(image) { - return api.recipes.recipeSmallImage(image); + getImage(slug) { + return api.recipes.recipeSmallImage(slug, this.image); }, }, }; diff --git a/frontend/src/components/Recipe/RecipeCard.vue b/frontend/src/components/Recipe/RecipeCard.vue index 8a22176d1740..ed9d1d6c8a2e 100644 --- a/frontend/src/components/Recipe/RecipeCard.vue +++ b/frontend/src/components/Recipe/RecipeCard.vue @@ -7,7 +7,7 @@ @click="$emit('click')" min-height="275" > - +
@@ -65,8 +65,8 @@ export default { }; }, methods: { - getImage(image) { - return api.recipes.recipeSmallImage(image); + getImage(slug) { + return api.recipes.recipeSmallImage(slug, this.image); }, }, }; diff --git a/frontend/src/pages/Recipe/ViewRecipe.vue b/frontend/src/pages/Recipe/ViewRecipe.vue index 47d960c580f3..849a2b2c01df 100644 --- a/frontend/src/pages/Recipe/ViewRecipe.vue +++ b/frontend/src/pages/Recipe/ViewRecipe.vue @@ -155,9 +155,9 @@ export default { this.recipeDetails = await api.recipes.requestDetails(this.currentRecipe); this.skeleton = false; }, - getImage(image) { - if (image) { - return api.recipes.recipeImage(image) + "?&rnd=" + this.imageKey; + getImage(slug) { + if (slug) { + return api.recipes.recipeImage(slug, this.imageKey, this.recipeDetails.image); } }, async deleteRecipe() { @@ -175,7 +175,9 @@ export default { }, async saveImage(overrideSuccessMsg = false) { if (this.fileObject) { - if (api.recipes.updateImage(this.recipeDetails.slug, this.fileObject, overrideSuccessMsg)) { + const newVersion = await api.recipes.updateImage(this.recipeDetails.slug, this.fileObject, overrideSuccessMsg); + if (newVersion) { + this.recipeDetails.image = newVersion.data.version; this.imageKey += 1; } } @@ -192,6 +194,7 @@ export default { if (slug != this.recipeDetails.slug) { this.$router.push(`/recipe/${slug}`); } + window.URL.revokeObjectURL(this.getImage(this.recipeDetails.slug)); } }, printPage() { diff --git a/mealie/db/database.py b/mealie/db/database.py index e8bf5be6b7b5..6a1b42143a7d 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -1,4 +1,5 @@ from logging import getLogger +from random import randint from mealie.db.db_base import BaseDocument from mealie.db.models.event import Event, EventNotification @@ -34,10 +35,10 @@ class _Recipes(BaseDocument): def update_image(self, session: Session, slug: str, extension: str = None) -> str: entry: RecipeModel = self._query_one(session, match_value=slug) - entry.image = f"{slug}.{extension}" + entry.image = randint(0, 255) session.commit() - return f"{slug}.{extension}" + return entry.image def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int: return self._count_attribute( diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index f1671ab4a2c1..60a49789b275 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -128,19 +128,18 @@ def delete_recipe( raise HTTPException(status.HTTP_400_BAD_REQUEST) -@router.put("/{recipe_slug}/image") +@router.put("/{recipe_slug}/image", dependencies=[Depends(get_current_user)]) def update_recipe_image( recipe_slug: str, image: bytes = File(...), extension: str = Form(...), session: Session = Depends(generate_session), - current_user=Depends(get_current_user), ): """ Removes an existing image and replaces it with the incoming file. """ - response = write_image(recipe_slug, image, extension) - db.recipes.update_image(session, recipe_slug, extension) + write_image(recipe_slug, image, extension) + new_version = db.recipes.update_image(session, recipe_slug, extension) - return response + return {"image": new_version} @router.post("/{recipe_slug}/image") diff --git a/mealie/services/image/image.py b/mealie/services/image/image.py index 229b2d08dea2..3b429fbaa375 100644 --- a/mealie/services/image/image.py +++ b/mealie/services/image/image.py @@ -36,7 +36,7 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path: shutil.copyfileobj(file_data, f) print(image_path) - minify.minify_image(image_path) + minify.minify_image(image_path, force=True) return image_path diff --git a/mealie/services/image/minify.py b/mealie/services/image/minify.py index 9dddcb63bc86..6393cab6951a 100644 --- a/mealie/services/image/minify.py +++ b/mealie/services/image/minify.py @@ -21,7 +21,7 @@ def get_image_sizes(org_img: Path, min_img: Path, tiny_img: Path) -> ImageSizes: return ImageSizes(org=sizeof_fmt(org_img), min=sizeof_fmt(min_img), tiny=sizeof_fmt(tiny_img)) -def minify_image(image_file: Path) -> ImageSizes: +def minify_image(image_file: Path, force=False) -> ImageSizes: """Minifies an image in it's original file format. Quality is lost Args: @@ -39,7 +39,7 @@ def minify_image(image_file: Path) -> ImageSizes: min_dest = image_file.parent.joinpath("min-original.webp") tiny_dest = image_file.parent.joinpath("tiny-original.webp") - if min_dest.exists() and tiny_dest.exists() and org_dest.exists(): + if min_dest.exists() and tiny_dest.exists() and org_dest.exists() and not force: return try: img = Image.open(image_file) @@ -56,7 +56,8 @@ def minify_image(image_file: Path) -> ImageSizes: cleanup_images = True - except Exception: + except Exception as e: + logger.error(e) shutil.copy(image_file, min_dest) shutil.copy(image_file, tiny_dest)