diff --git a/.vscode/settings.json b/.vscode/settings.json index 21ff09b98..6e45ef01b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "python.formatting.provider": "black", "python.pythonPath": ".venv/bin/python3.9", - "python.linting.pylintEnabled": true, + "python.linting.pylintEnabled": false, "python.linting.enabled": true, "python.testing.unittestEnabled": false, "python.testing.nosetestsEnabled": false, @@ -24,5 +24,6 @@ "postgres", "webp" ], - "search.mode": "reuseEditor" + "search.mode": "reuseEditor", + "python.linting.flake8Enabled": true } diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index 4410b23a1..948f766d5 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -135,14 +135,18 @@ export const recipeAPI = { }, recipeImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/images/original.webp`; + return `/api/media/recipes/${recipeSlug}/images/original.webp`; }, recipeSmallImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/images/min-original.webp`; + return `/api/media/recipes/${recipeSlug}/images/min-original.webp`; }, recipeTinyImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/images/tiny-original.webp`; + return `/api/media/recipes/${recipeSlug}/images/tiny-original.webp`; + }, + + recipeAssetPath(recipeSlug, assetName) { + return `api/media/recipes/${recipeSlug}/assets/${assetName}`; }, }; diff --git a/frontend/src/components/Recipe/Parts/Assets.vue b/frontend/src/components/Recipe/Parts/Assets.vue index 62119ba21..5479bb6d0 100644 --- a/frontend/src/components/Recipe/Parts/Assets.vue +++ b/frontend/src/components/Recipe/Parts/Assets.vue @@ -14,14 +14,7 @@ - + mdi-download
@@ -118,6 +111,9 @@ export default { }, }, methods: { + assetURL(assetName) { + return api.recipes.recipeAssetPath(this.slug, assetName); + }, setFileObject(obj) { this.fileObject = obj; }, diff --git a/frontend/src/components/Recipe/RecipeViewer/index.vue b/frontend/src/components/Recipe/RecipeViewer/index.vue index d4a4fac9a..74498da71 100644 --- a/frontend/src/components/Recipe/RecipeViewer/index.vue +++ b/frontend/src/components/Recipe/RecipeViewer/index.vue @@ -18,7 +18,7 @@ color="secondary darken-1" class="rounded-sm static" > - {{ yields }} + {{ recipe.yields }} diff --git a/frontend/src/pages/Admin/Dashboard/BackupViewer.vue b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue index 5ef125968..6752d8dd2 100644 --- a/frontend/src/pages/Admin/Dashboard/BackupViewer.vue +++ b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue @@ -71,7 +71,7 @@ export default { components: { StatCard, ImportDialog, TheUploadBtn, ImportSummaryDialog }, data() { return { - color: "secondary", + color: "accent", selectedName: "", selectedDate: "", loading: false, diff --git a/frontend/src/pages/Admin/Dashboard/EventViewer.vue b/frontend/src/pages/Admin/Dashboard/EventViewer.vue index 5070c677b..e03186f94 100644 --- a/frontend/src/pages/Admin/Dashboard/EventViewer.vue +++ b/frontend/src/pages/Admin/Dashboard/EventViewer.vue @@ -54,7 +54,7 @@ export default { components: { StatCard }, data() { return { - color: "secondary", + color: "accent", total: 0, events: [], icons: { diff --git a/mealie/app.py b/mealie/app.py index 162351990..f493e2432 100644 --- a/mealie/app.py +++ b/mealie/app.py @@ -1,18 +1,20 @@ import uvicorn from fastapi import FastAPI -from mealie.core import root_logger from mealie.core.config import APP_VERSION, settings -from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes, utility_routes +from mealie.core.root_logger import get_logger +from mealie.routes import (backup_routes, debug_routes, migration_routes, + theme_routes, utility_routes) from mealie.routes.about import about_router -from mealie.routes.groups import groups -from mealie.routes.mealplans import mealplans -from mealie.routes.recipe import router as recipe_router -from mealie.routes.site_settings import all_settings -from mealie.routes.users import users +from mealie.routes.groups import groups_router +from mealie.routes.mealplans import meal_plan_router +from mealie.routes.media import media_router +from mealie.routes.recipe import recipe_router +from mealie.routes.site_settings import settings_router +from mealie.routes.users import user_router from mealie.services.events import create_general_event -logger = root_logger.get_logger() +logger = get_logger() app = FastAPI( title="Mealie", @@ -29,15 +31,16 @@ def start_scheduler(): def api_routers(): # Authentication - app.include_router(users.router) - app.include_router(groups.router) + app.include_router(user_router) + app.include_router(groups_router) # Recipes app.include_router(recipe_router) + app.include_router(media_router) app.include_router(about_router) # Meal Routes - app.include_router(mealplans.router) + app.include_router(meal_plan_router) # Settings Routes - app.include_router(all_settings.router) + app.include_router(settings_router) app.include_router(theme_routes.router) # Backups/Imports Routes app.include_router(backup_routes.router) diff --git a/mealie/routes/about/__init__.py b/mealie/routes/about/__init__.py index e36affaa9..70eb8f5c8 100644 --- a/mealie/routes/about/__init__.py +++ b/mealie/routes/about/__init__.py @@ -1,7 +1,7 @@ from fastapi import APIRouter -from .events import router as events_router +from . import events about_router = APIRouter(prefix="/api/about") -about_router.include_router(events_router) +about_router.include_router(events.router) diff --git a/mealie/routes/groups/__init__.py b/mealie/routes/groups/__init__.py index e69de29bb..f8935bdb6 100644 --- a/mealie/routes/groups/__init__.py +++ b/mealie/routes/groups/__init__.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +from . import crud, groups + +groups_router = APIRouter() + +groups_router.include_router(crud.router) +groups_router.include_router(groups.router) diff --git a/mealie/routes/mealplans/__init__.py b/mealie/routes/mealplans/__init__.py index e69de29bb..c2dfae352 100644 --- a/mealie/routes/mealplans/__init__.py +++ b/mealie/routes/mealplans/__init__.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter + +from . import crud, helpers, mealplans + +meal_plan_router = APIRouter() + +meal_plan_router.include_router(crud.router) +meal_plan_router.include_router(helpers.router) +meal_plan_router.include_router(mealplans.router) diff --git a/mealie/routes/media/__init__.py b/mealie/routes/media/__init__.py new file mode 100644 index 000000000..e28579ee6 --- /dev/null +++ b/mealie/routes/media/__init__.py @@ -0,0 +1,7 @@ +from fastapi import APIRouter + +from . import recipe + +media_router = APIRouter(prefix="/api/media", tags=["Site Media"]) + +media_router.include_router(recipe.router) diff --git a/mealie/routes/media/recipe.py b/mealie/routes/media/recipe.py new file mode 100644 index 000000000..ad8ffac4e --- /dev/null +++ b/mealie/routes/media/recipe.py @@ -0,0 +1,41 @@ +from enum import Enum + +from fastapi import APIRouter, HTTPException, status +from mealie.schema.recipe import Recipe +from starlette.responses import FileResponse + +""" +These routes are for development only! These assets are served by Caddy when not +in development mode. If you make changes, be sure to test the production container. +""" + +router = APIRouter(prefix="/recipes") + + +class ImageType(str, Enum): + original = "original.webp" + small = "min-original.webp" + tiny = "tiny-original.webp" + + +@router.get("/{recipe_slug}/images/{file_name}") +async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original): + """Takes in a recipe slug, returns the static image. This route is proxied in the docker image + and should not hit the API in production""" + recipe_image = Recipe(slug=recipe_slug).image_dir.joinpath(file_name.value) + + if recipe_image: + return FileResponse(recipe_image) + else: + raise HTTPException(status.HTTP_404_NOT_FOUND) + + +@router.get("/{recipe_slug}/assets/{file_name}") +async def get_recipe_asset(recipe_slug: str, file_name: str): + """ Returns a recipe asset """ + file = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) + + try: + return FileResponse(file) + except Exception: + raise HTTPException(status.HTTP_404_NOT_FOUND) diff --git a/mealie/routes/media/user.py b/mealie/routes/media/user.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/routes/recipe/__init__.py b/mealie/routes/recipe/__init__.py index 1d54034f0..fb9a0d55b 100644 --- a/mealie/routes/recipe/__init__.py +++ b/mealie/routes/recipe/__init__.py @@ -1,10 +1,9 @@ from fastapi import APIRouter -from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, recipe_media, tag_routes +from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, tag_routes -router = APIRouter() +recipe_router = APIRouter() -router.include_router(all_recipe_routes.router) -router.include_router(recipe_crud_routes.router) -router.include_router(recipe_media.router) -router.include_router(category_routes.router) -router.include_router(tag_routes.router) +recipe_router.include_router(all_recipe_routes.router) +recipe_router.include_router(recipe_crud_routes.router) +recipe_router.include_router(category_routes.router) +recipe_router.include_router(tag_routes.router) diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 6604b84e6..110393050 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -1,13 +1,17 @@ +from shutil import copyfileobj + from fastapi import APIRouter, Depends, File, Form, HTTPException, status +from fastapi.datastructures import UploadFile from mealie.core.root_logger import get_logger 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.recipe import Recipe, RecipeURLIn +from mealie.schema.recipe import Recipe, RecipeAsset, RecipeURLIn from mealie.services.events import create_recipe_event from mealie.services.image.image import scrape_image, write_image from mealie.services.recipe.media import check_assets, delete_assets from mealie.services.scraper.scraper import create_from_url +from slugify import slugify from sqlalchemy.orm.session import Session router = APIRouter(prefix="/api/recipes", tags=["Recipe CRUD"]) @@ -126,3 +130,30 @@ def scrape_image_url( """ Removes an existing image and replaces it with the incoming file. """ scrape_image(url.url, recipe_slug) + + +@router.post("/{recipe_slug}/assets", response_model=RecipeAsset) +def upload_recipe_asset( + recipe_slug: str, + name: str = Form(...), + icon: str = Form(...), + extension: str = Form(...), + file: UploadFile = File(...), + session: Session = Depends(generate_session), + current_user=Depends(get_current_user), +): + """ Upload a file to store as a recipe asset """ + file_name = slugify(name) + "." + extension + asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name) + dest = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) + + with dest.open("wb") as buffer: + copyfileobj(file.file, buffer) + + if not dest.is_file(): + raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) + + recipe: Recipe = db.recipes.get(session, recipe_slug) + recipe.assets.append(asset_in) + db.recipes.update(session, recipe_slug, recipe.dict()) + return asset_in diff --git a/mealie/routes/recipe/recipe_media.py b/mealie/routes/recipe/recipe_media.py deleted file mode 100644 index e7c3402a5..000000000 --- a/mealie/routes/recipe/recipe_media.py +++ /dev/null @@ -1,70 +0,0 @@ -import shutil -from enum import Enum - -from fastapi import APIRouter, Depends, File, Form, HTTPException, status -from fastapi.datastructures import UploadFile -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.recipe import Recipe, RecipeAsset -from slugify import slugify -from sqlalchemy.orm.session import Session -from starlette.responses import FileResponse - -router = APIRouter(prefix="/api/recipes/media", tags=["Recipe Media"]) - - -class ImageType(str, Enum): - original = "original.webp" - small = "min-original.webp" - tiny = "tiny-original.webp" - - -@router.get("/{recipe_slug}/images/{file_name}") -async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original): - """Takes in a recipe slug, returns the static image. This route is proxied in the docker image - and should not hit the API in production""" - recipe_image = Recipe(slug=recipe_slug).image_dir.joinpath(file_name.value) - - if recipe_image: - return FileResponse(recipe_image) - else: - raise HTTPException(status.HTTP_404_NOT_FOUND) - - -@router.get("/{recipe_slug}/assets/{file_name}") -async def get_recipe_asset(recipe_slug: str, file_name: str): - """ Returns a recipe asset """ - file = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) - - try: - return FileResponse(file) - except Exception: - raise HTTPException(status.HTTP_404_NOT_FOUND) - - -@router.post("/{recipe_slug}/assets", response_model=RecipeAsset) -def upload_recipe_asset( - recipe_slug: str, - name: str = Form(...), - icon: str = Form(...), - extension: str = Form(...), - file: UploadFile = File(...), - session: Session = Depends(generate_session), - current_user=Depends(get_current_user), -): - """ Upload a file to store as a recipe asset """ - file_name = slugify(name) + "." + extension - asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name) - dest = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) - - with dest.open("wb") as buffer: - shutil.copyfileobj(file.file, buffer) - - if not dest.is_file(): - raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) - - recipe: Recipe = db.recipes.get(session, recipe_slug) - recipe.assets.append(asset_in) - db.recipes.update(session, recipe_slug, recipe.dict()) - return asset_in diff --git a/mealie/routes/site_settings/__init__.py b/mealie/routes/site_settings/__init__.py index e69de29bb..ec356e63f 100644 --- a/mealie/routes/site_settings/__init__.py +++ b/mealie/routes/site_settings/__init__.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter + +from . import all_settings, custom_pages, site_settings + +settings_router = APIRouter() + +settings_router.include_router(all_settings.router) +settings_router.include_router(custom_pages.router) +settings_router.include_router(site_settings.router) diff --git a/mealie/routes/users/__init__.py b/mealie/routes/users/__init__.py index e69de29bb..c2e751728 100644 --- a/mealie/routes/users/__init__.py +++ b/mealie/routes/users/__init__.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter + +from . import auth, crud, sign_up + +user_router = APIRouter() + +user_router.include_router(auth.router) +user_router.include_router(crud.router) +user_router.include_router(sign_up.router) diff --git a/mealie/routes/users/users.py b/mealie/routes/users/users.py deleted file mode 100644 index 9f3088a99..000000000 --- a/mealie/routes/users/users.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import APIRouter -from mealie.routes.users import auth, crud, sign_up - -router = APIRouter() - -router.include_router(sign_up.router) -router.include_router(auth.router) -router.include_router(sign_up.router) -router.include_router(crud.router)