mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -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:
		| @@ -6,11 +6,17 @@ | ||||
| :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  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -209,10 +209,6 @@ export default { | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     getImage(image) { | ||||
|       return api.recipes.recipeSmallImage(image); | ||||
|     }, | ||||
|  | ||||
|     formatDate(date) { | ||||
|       if (!date) return null; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   | ||||
| @@ -59,8 +59,8 @@ export default { | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     getImage(image) { | ||||
|       return api.recipes.recipeSmallImage(image); | ||||
|     getImage(slug) { | ||||
|       return api.recipes.recipeSmallImage(slug, this.image); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|       @click="$emit('click')" | ||||
|       min-height="275" | ||||
|     > | ||||
|       <CardImage icon-size="200" :slug="slug"> | ||||
|       <CardImage icon-size="200" :slug="slug" :image-version="image"> | ||||
|         <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%;"> | ||||
|             <v-card-text class="v-card--text-show white--text"> | ||||
| @@ -65,8 +65,8 @@ export default { | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     getImage(image) { | ||||
|       return api.recipes.recipeSmallImage(image); | ||||
|     getImage(slug) { | ||||
|       return api.recipes.recipeSmallImage(slug, this.image); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user