mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-01-02 15:11:24 -05:00
Feature/import export single recipe (#576)
* remove duplicate keys * show context menu when not logged in * remove console.log * hide menu when printing * add response to event * add type definitions * show context menu always * add image name enums * upload/download single recipe * cleanup menu views+ localization Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -4,7 +4,7 @@ from typing import Optional
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jose import JWTError, jwt
|
||||
from mealie.core.config import settings
|
||||
from mealie.core.config import app_dirs, settings
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.schema.auth import TokenData
|
||||
@@ -100,3 +100,13 @@ def validate_file_token(token: Optional[str] = None) -> Path:
|
||||
raise credentials_exception
|
||||
|
||||
return file_path
|
||||
|
||||
|
||||
async def temporary_zip_path() -> Path:
|
||||
temp_path = app_dirs.TEMP_DIR.mkdir(exist_ok=True, parents=True)
|
||||
temp_path = app_dirs.TEMP_DIR.joinpath("my_zip_archive.zip")
|
||||
|
||||
try:
|
||||
yield temp_path
|
||||
finally:
|
||||
temp_path.unlink(missing_ok=True)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import json
|
||||
import shutil
|
||||
from shutil import copyfileobj
|
||||
from zipfile import ZipFile
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, status
|
||||
from fastapi.datastructures import UploadFile
|
||||
@@ -6,8 +9,8 @@ from mealie.core.config import settings
|
||||
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, is_logged_in
|
||||
from mealie.schema.recipe import Recipe, RecipeAsset, RecipeURLIn
|
||||
from mealie.routes.deps import get_current_user, is_logged_in, temporary_zip_path
|
||||
from mealie.schema.recipe import Recipe, RecipeAsset, RecipeImageTypes, RecipeURLIn
|
||||
from mealie.schema.user import UserInDB
|
||||
from mealie.services.events import create_recipe_event
|
||||
from mealie.services.image.image import scrape_image, write_image
|
||||
@@ -16,6 +19,7 @@ from mealie.services.scraper.scraper import create_from_url
|
||||
from scrape_schema_recipe import scrape_url
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm.session import Session
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
router = APIRouter(prefix="/api/recipes", tags=["Recipe CRUD"])
|
||||
logger = get_logger()
|
||||
@@ -87,6 +91,54 @@ def get_recipe(recipe_slug: str, session: Session = Depends(generate_session), i
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, {"details": "unauthorized"})
|
||||
|
||||
|
||||
@router.post("/create-from-zip", dependencies=[Depends(get_current_user)])
|
||||
async def create_recipe_from_zip(
|
||||
session: Session = Depends(generate_session),
|
||||
temp_path=Depends(temporary_zip_path),
|
||||
archive: UploadFile = File(...),
|
||||
):
|
||||
""" Create recipe from archive """
|
||||
|
||||
with temp_path.open("wb") as buffer:
|
||||
shutil.copyfileobj(archive.file, buffer)
|
||||
|
||||
recipe_dict = None
|
||||
recipe_image = None
|
||||
|
||||
with ZipFile(temp_path) as myzip:
|
||||
for file in myzip.namelist():
|
||||
if file.endswith(".json"):
|
||||
with myzip.open(file) as myfile:
|
||||
recipe_dict = json.loads(myfile.read())
|
||||
elif file.endswith(".webp"):
|
||||
with myzip.open(file) as myfile:
|
||||
recipe_image = myfile.read()
|
||||
|
||||
recipe: Recipe = db.recipes.create(session, Recipe(**recipe_dict))
|
||||
|
||||
write_image(recipe.slug, recipe_image, "webp")
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
@router.get("/{recipe_slug}/zip")
|
||||
async def get_recipe_as_zip(
|
||||
recipe_slug: str, session: Session = Depends(generate_session), temp_path=Depends(temporary_zip_path)
|
||||
):
|
||||
""" Get a Recipe and It's Original Image as a Zip File """
|
||||
recipe: Recipe = db.recipes.get(session, recipe_slug)
|
||||
|
||||
image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value)
|
||||
|
||||
with ZipFile(temp_path, "w") as myzip:
|
||||
myzip.writestr(f"{recipe_slug}.json", recipe.json())
|
||||
|
||||
if image_asset.is_file():
|
||||
myzip.write(image_asset, arcname=image_asset.name)
|
||||
|
||||
return FileResponse(temp_path, filename=f"{recipe_slug}.zip")
|
||||
|
||||
|
||||
@router.put("/{recipe_slug}", dependencies=[Depends(get_current_user)])
|
||||
def update_recipe(
|
||||
recipe_slug: str,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
@@ -11,6 +12,12 @@ from pydantic.utils import GetterDict
|
||||
from slugify import slugify
|
||||
|
||||
|
||||
class RecipeImageTypes(str, Enum):
|
||||
original = "original.webp"
|
||||
min = "min-original.webp"
|
||||
tiny = "tiny-original.webp"
|
||||
|
||||
|
||||
class RecipeSettings(CamelModel):
|
||||
public: bool = True
|
||||
show_nutrition: bool = True
|
||||
|
||||
Reference in New Issue
Block a user