mealie-recipes / mealie

Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and mealie will automatically import the relevant data or add a family recipe with the UI editor
https://docs.mealie.io
GNU Affero General Public License v3.0
5.78k stars 628 forks source link

[BUG] - Nextcloud migration fails due to malformed nutrition field #3543

Closed mspl13 closed 2 months ago

mspl13 commented 2 months ago

First Check

What is the issue you are experiencing?

The Nextcloud Cookbook migration fails for me; the status in the interface is stuck at "In progress", and the log shows (exemplary) error below.

Investigating a bit further, I was able to narrow the issue down to two recipes that seems to have a malformed "nutrition" entry. While the first one has an empty list as nutrition field

...
  "nutrition": [],
...

the second one uses integers instead of strings to specify the amount of certain nutritions

...
  "nutrition": {
    "calories": 732,
    "proteinContent": 15,
    "fatContent": 36,
    "carbohydrateContent": 82,
    "@type": "NutritionInformation"
  },
...

I attached both files that were created by Nextcloud Cookbook (I only added the whitespaces to make it more readable).

While I think that, in the first case, the nutrition information should simply be discarded, I'm not sure how to deal with the second case, as the metric is not immediately clear.

Steps to Reproduce

  1. Setup a clean Mealie instance.
  2. Upload the provided .zip archive containing two recipes.

nextcloud.zip

Please provide relevant logs

ERROR    2024-05-02T21:52:23 - Exception in ASGI application
 Traceback (most recent call last):
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 411, in run_asgi
     result = await app(  # type: ignore[func-returns-value]
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 69, in __call__
     return await self.app(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
     await super().__call__(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
     await self.middleware_stack(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__
     raise exc
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
     await self.app(scope, receive, _send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/middleware/gzip.py", line 24, in __call__
     await responder(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/middleware/gzip.py", line 44, in __call__
     await self.app(scope, receive, self.send_with_gzip)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in __call__
     await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
     raise exc
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
     await app(scope, receive, sender)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/routing.py", line 756, in __call__
     await self.middleware_stack(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/routing.py", line 776, in app
     await route.handle(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/routing.py", line 297, in handle
     await self.app(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/routing.py", line 77, in app
     await wrap_app_handling_exceptions(app, request)(scope, receive, send)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
     raise exc
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
     await app(scope, receive, sender)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/routing.py", line 72, in app
     response = await func(request)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 278, in app
     raw_response = await run_endpoint_function(
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 193, in run_endpoint_function
     return await run_in_threadpool(dependant.call, **values)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/starlette/concurrency.py", line 42, in run_in_threadpool
     return await anyio.to_thread.run_sync(func, *args)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/anyio/to_thread.py", line 33, in run_sync
     return await get_asynclib().run_sync_in_worker_thread(
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 877, in run_sync_in_worker_thread
     return await future
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 807, in run
     result = context.run(func, *args)
   File "/app/mealie/routes/groups/controller_migrations.py", line 69, in start_data_migration
     return migrator.migrate(f"{migration_type.value.title()} Migration")
   File "/app/mealie/services/migrations/_migration_base.py", line 119, in migrate
     self._migrate()
   File "/app/mealie/services/migrations/nextcloud.py", line 69, in _migrate
     recipe = self.clean_recipe_dictionary(nc_dir.recipe)
   File "/app/mealie/services/migrations/_migration_base.py", line 246, in clean_recipe_dictionary
     return Recipe(**recipe_dict)
   File "/opt/pysetup/.venv/lib/python3.10/site-packages/pydantic/main.py", line 175, in __init__
     self.__pydantic_validator__.validate_python(data, self_instance=self)
 pydantic_core._pydantic_core.ValidationError: 4 validation errors for Recipe
 nutrition.calories
   Input should be a valid string [type=string_type, input_value=732, input_type=int]
     For further information visit https://errors.pydantic.dev/2.7/v/string_type
 nutrition.fatContent
   Input should be a valid string [type=string_type, input_value=36, input_type=int]
     For further information visit https://errors.pydantic.dev/2.7/v/string_type
 nutrition.proteinContent
   Input should be a valid string [type=string_type, input_value=15, input_type=int]
     For further information visit https://errors.pydantic.dev/2.7/v/string_type
 nutrition.carbohydrateContent
   Input should be a valid string [type=string_type, input_value=82, input_type=int]
     For further information visit https://errors.pydantic.dev/2.7/v/string_type

Mealie Version

I used a fresh mealie instance, setup with docker compose and sqlite (as explained in the docs). According to the settings page, this is v1.5.1 and build "faf716cb7e60b9e31451bdc1caec88e8a78f4db7".

Deployment

Docker (Linux)

Additional Deployment Details

No response

michael-genson commented 2 months ago

Confirmed in nightly, thanks!