mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	bug/bug-fixes (#424)
* fix image write/caching * Caddyfile Caching header * more aggressive caching Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
		| @@ -5,12 +5,18 @@ | |||||||
|  |  | ||||||
| :80 { | :80 { | ||||||
|   @proxied path /api/* /docs /openapi.json |   @proxied path /api/* /docs /openapi.json | ||||||
|  |  | ||||||
|  |   @static { | ||||||
|  |     file | ||||||
|  |     path *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.woff *.woff2 *.webp | ||||||
|  |   } | ||||||
|    |    | ||||||
|   encode gzip zstd |   encode gzip zstd | ||||||
|   uri strip_suffix / |   uri strip_suffix / | ||||||
|    |    | ||||||
|   # Handles Recipe Images / Assets |   # Handles Recipe Images / Assets | ||||||
|   handle_path /api/media/recipes/* { |   handle_path /api/media/recipes/* { | ||||||
|  |     header @static Cache-Control max-age=31536000 | ||||||
|     root * /app/data/recipes/ |     root * /app/data/recipes/ | ||||||
|     file_server |     file_server | ||||||
|   } |   } | ||||||
| @@ -20,6 +26,7 @@ | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   handle { |   handle { | ||||||
|  |     header @static Cache-Control max-age=31536000 | ||||||
|     root * /app/dist |     root * /app/dist | ||||||
|     try_files {path}.html {path} /index.html |     try_files {path}.html {path} /index.html | ||||||
|     file_server  |     file_server  | ||||||
|   | |||||||
| @@ -146,16 +146,16 @@ export const recipeAPI = { | |||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   recipeImage(recipeSlug) { |   recipeImage(recipeSlug, version = null, key = null) { | ||||||
|     return `/api/media/recipes/${recipeSlug}/images/original.webp`; |     return `/api/media/recipes/${recipeSlug}/images/original.webp?&rnd=${key}&version=${version}`; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   recipeSmallImage(recipeSlug) { |   recipeSmallImage(recipeSlug, version = null, key = null) { | ||||||
|     return `/api/media/recipes/${recipeSlug}/images/min-original.webp`; |     return `/api/media/recipes/${recipeSlug}/images/min-original.webp?&rnd=${key}&version=${version}`; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   recipeTinyImage(recipeSlug) { |   recipeTinyImage(recipeSlug, version = null, key = null) { | ||||||
|     return `/api/media/recipes/${recipeSlug}/images/tiny-original.webp`; |     return `/api/media/recipes/${recipeSlug}/images/tiny-original.webp?&rnd=${key}&version=${version}`; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   recipeAssetPath(recipeSlug, assetName) { |   recipeAssetPath(recipeSlug, assetName) { | ||||||
|   | |||||||
| @@ -209,10 +209,6 @@ export default { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     getImage(image) { |  | ||||||
|       return api.recipes.recipeSmallImage(image); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     formatDate(date) { |     formatDate(date) { | ||||||
|       if (!date) return null; |       if (!date) return null; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,6 +42,9 @@ export default { | |||||||
|     slug: { |     slug: { | ||||||
|       default: null, |       default: null, | ||||||
|     }, |     }, | ||||||
|  |     imageVersion: { | ||||||
|  |       default: null, | ||||||
|  |     }, | ||||||
|     height: { |     height: { | ||||||
|       default: 200, |       default: 200, | ||||||
|     }, |     }, | ||||||
| @@ -65,14 +68,14 @@ export default { | |||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     getImage(image) { |     getImage(slug) { | ||||||
|       switch (this.imageSize) { |       switch (this.imageSize) { | ||||||
|         case "tiny": |         case "tiny": | ||||||
|           return api.recipes.recipeTinyImage(image); |           return api.recipes.recipeTinyImage(slug, this.imageVersion); | ||||||
|         case "small": |         case "small": | ||||||
|           return api.recipes.recipeSmallImage(image); |           return api.recipes.recipeSmallImage(slug, this.imageVersion); | ||||||
|         case "large": |         case "large": | ||||||
|           return api.recipes.recipeImage(image); |           return api.recipes.recipeImage(slug, this.imageVersion); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -59,8 +59,8 @@ export default { | |||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     getImage(image) { |     getImage(slug) { | ||||||
|       return api.recipes.recipeSmallImage(image); |       return api.recipes.recipeSmallImage(slug, this.image); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
|       @click="$emit('click')" |       @click="$emit('click')" | ||||||
|       min-height="275" |       min-height="275" | ||||||
|     > |     > | ||||||
|       <CardImage icon-size="200" :slug="slug"> |       <CardImage icon-size="200" :slug="slug" :image-version="image"> | ||||||
|         <v-expand-transition v-if="description"> |         <v-expand-transition v-if="description"> | ||||||
|           <div v-if="hover" class="d-flex transition-fast-in-fast-out secondary v-card--reveal  " style="height: 100%;"> |           <div v-if="hover" class="d-flex transition-fast-in-fast-out secondary v-card--reveal  " style="height: 100%;"> | ||||||
|             <v-card-text class="v-card--text-show white--text"> |             <v-card-text class="v-card--text-show white--text"> | ||||||
| @@ -65,8 +65,8 @@ export default { | |||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     getImage(image) { |     getImage(slug) { | ||||||
|       return api.recipes.recipeSmallImage(image); |       return api.recipes.recipeSmallImage(slug, this.image); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -155,9 +155,9 @@ export default { | |||||||
|       this.recipeDetails = await api.recipes.requestDetails(this.currentRecipe); |       this.recipeDetails = await api.recipes.requestDetails(this.currentRecipe); | ||||||
|       this.skeleton = false; |       this.skeleton = false; | ||||||
|     }, |     }, | ||||||
|     getImage(image) { |     getImage(slug) { | ||||||
|       if (image) { |       if (slug) { | ||||||
|         return api.recipes.recipeImage(image) + "?&rnd=" + this.imageKey; |         return api.recipes.recipeImage(slug, this.imageKey, this.recipeDetails.image); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     async deleteRecipe() { |     async deleteRecipe() { | ||||||
| @@ -175,7 +175,9 @@ export default { | |||||||
|     }, |     }, | ||||||
|     async saveImage(overrideSuccessMsg = false) { |     async saveImage(overrideSuccessMsg = false) { | ||||||
|       if (this.fileObject) { |       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; |           this.imageKey += 1; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @@ -192,6 +194,7 @@ export default { | |||||||
|         if (slug != this.recipeDetails.slug) { |         if (slug != this.recipeDetails.slug) { | ||||||
|           this.$router.push(`/recipe/${slug}`); |           this.$router.push(`/recipe/${slug}`); | ||||||
|         } |         } | ||||||
|  |         window.URL.revokeObjectURL(this.getImage(this.recipeDetails.slug)); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     printPage() { |     printPage() { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| from logging import getLogger | from logging import getLogger | ||||||
|  | from random import randint | ||||||
|  |  | ||||||
| from mealie.db.db_base import BaseDocument | from mealie.db.db_base import BaseDocument | ||||||
| from mealie.db.models.event import Event, EventNotification | 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: |     def update_image(self, session: Session, slug: str, extension: str = None) -> str: | ||||||
|         entry: RecipeModel = self._query_one(session, match_value=slug) |         entry: RecipeModel = self._query_one(session, match_value=slug) | ||||||
|         entry.image = f"{slug}.{extension}" |         entry.image = randint(0, 255) | ||||||
|         session.commit() |         session.commit() | ||||||
|  |  | ||||||
|         return f"{slug}.{extension}" |         return entry.image | ||||||
|  |  | ||||||
|     def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int: |     def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int: | ||||||
|         return self._count_attribute( |         return self._count_attribute( | ||||||
|   | |||||||
| @@ -128,19 +128,18 @@ def delete_recipe( | |||||||
|         raise HTTPException(status.HTTP_400_BAD_REQUEST) |         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( | def update_recipe_image( | ||||||
|     recipe_slug: str, |     recipe_slug: str, | ||||||
|     image: bytes = File(...), |     image: bytes = File(...), | ||||||
|     extension: str = Form(...), |     extension: str = Form(...), | ||||||
|     session: Session = Depends(generate_session), |     session: Session = Depends(generate_session), | ||||||
|     current_user=Depends(get_current_user), |  | ||||||
| ): | ): | ||||||
|     """ Removes an existing image and replaces it with the incoming file. """ |     """ Removes an existing image and replaces it with the incoming file. """ | ||||||
|     response = write_image(recipe_slug, image, extension) |     write_image(recipe_slug, image, extension) | ||||||
|     db.recipes.update_image(session, recipe_slug, extension) |     new_version = db.recipes.update_image(session, recipe_slug, extension) | ||||||
|  |  | ||||||
|     return response |     return {"image": new_version} | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/{recipe_slug}/image") | @router.post("/{recipe_slug}/image") | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path: | |||||||
|             shutil.copyfileobj(file_data, f) |             shutil.copyfileobj(file_data, f) | ||||||
|  |  | ||||||
|     print(image_path) |     print(image_path) | ||||||
|     minify.minify_image(image_path) |     minify.minify_image(image_path, force=True) | ||||||
|  |  | ||||||
|     return image_path |     return image_path | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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)) |     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 |     """Minifies an image in it's original file format. Quality is lost | ||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
| @@ -39,7 +39,7 @@ def minify_image(image_file: Path) -> ImageSizes: | |||||||
|     min_dest = image_file.parent.joinpath("min-original.webp") |     min_dest = image_file.parent.joinpath("min-original.webp") | ||||||
|     tiny_dest = image_file.parent.joinpath("tiny-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 |         return | ||||||
|     try: |     try: | ||||||
|         img = Image.open(image_file) |         img = Image.open(image_file) | ||||||
| @@ -56,7 +56,8 @@ def minify_image(image_file: Path) -> ImageSizes: | |||||||
|  |  | ||||||
|         cleanup_images = True |         cleanup_images = True | ||||||
|  |  | ||||||
|     except Exception: |     except Exception as e: | ||||||
|  |         logger.error(e) | ||||||
|         shutil.copy(image_file, min_dest) |         shutil.copy(image_file, min_dest) | ||||||
|         shutil.copy(image_file, tiny_dest) |         shutil.copy(image_file, tiny_dest) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user