feat: add user recipe export functionality (#845)

* feat(frontend):  add user recipe export functionality

* remove depreciated folders

* change/remove depreciated folders

* add testing variable in config

* add GUID support for group_id

* improve testing feedback on 422 errors

* remove/cleanup files/folders

* initial user export support

* delete unused css

* update backup page UI

* remove depreciated settings

* feat:  export download links

* fix #813

* remove top level statements

* show footer

* add export purger to scheduler

* update purge glob

* fix meal-planner lockout

* feat:  add bulk delete/purge exports

* style(frontend): 💄 update UI for site settings

* feat:  add version checker

* update documentation

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden
2021-12-04 14:18:46 -09:00
committed by GitHub
parent 2ce195a0d4
commit c32d7d7486
84 changed files with 1329 additions and 667 deletions

View File

@@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends
from sqlalchemy.orm.session import Session
from mealie.core.config import get_app_settings
from mealie.core.release_checker import get_latest_version
from mealie.core.settings.static import APP_VERSION
from mealie.db.database import get_database
from mealie.db.db_setup import generate_session
@@ -18,6 +19,7 @@ async def get_app_info():
return AdminAboutInfo(
production=settings.PRODUCTION,
version=APP_VERSION,
versionLatest=get_latest_version(),
demo_status=settings.IS_DEMO,
api_port=settings.API_PORT,
api_docs=settings.API_DOCS,
@@ -49,4 +51,5 @@ async def check_app_config():
email_ready=settings.SMTP_ENABLE,
ldap_ready=settings.LDAP_ENABLED,
base_url_set=url_set,
is_up_to_date=get_latest_version() == APP_VERSION,
)

View File

@@ -1,70 +0,0 @@
from fastapi import BackgroundTasks, Depends, HTTPException, status
from sqlalchemy.orm.session import Session
from mealie.core.dependencies import get_current_user
from mealie.db.database import get_database
from mealie.db.db_setup import generate_session
from mealie.routes.routers import AdminAPIRouter
from mealie.schema.user import GroupBase, GroupInDB, PrivateUser, UpdateGroup
from mealie.services.events import create_group_event
router = AdminAPIRouter(prefix="/groups")
@router.get("", response_model=list[GroupInDB])
async def get_all_groups(session: Session = Depends(generate_session)):
"""Returns a list of all groups in the database"""
db = get_database(session)
return db.groups.get_all()
@router.post("", status_code=status.HTTP_201_CREATED, response_model=GroupInDB)
async def create_group(
background_tasks: BackgroundTasks,
group_data: GroupBase,
session: Session = Depends(generate_session),
):
"""Creates a Group in the Database"""
db = get_database(session)
try:
new_group = db.groups.create(group_data.dict())
background_tasks.add_task(create_group_event, "Group Created", f"'{group_data.name}' created", session)
return new_group
except Exception:
raise HTTPException(status.HTTP_400_BAD_REQUEST)
@router.put("/{id}")
async def update_group_data(id: int, group_data: UpdateGroup, session: Session = Depends(generate_session)):
"""Updates a User Group"""
db = get_database(session)
db.groups.update(id, group_data.dict())
@router.delete("/{id}")
async def delete_user_group(
background_tasks: BackgroundTasks,
id: int,
current_user: PrivateUser = Depends(get_current_user),
session: Session = Depends(generate_session),
):
"""Removes a user group from the database"""
db = get_database(session)
if id == 1:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="DEFAULT_GROUP")
group: GroupInDB = db.groups.get(id)
if not group:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_NOT_FOUND")
if group.users != []:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_WITH_USERS")
background_tasks.add_task(
create_group_event, "Group Deleted", f"'{group.name}' deleted by {current_user.full_name}", session
)
db.groups.delete(id)

View File

@@ -18,7 +18,7 @@ def log_wrapper(request: Request, e):
def register_debug_handler(app: FastAPI):
settings = get_app_settings()
if settings.PRODUCTION:
if settings.PRODUCTION and not settings.TESTING:
return
@app.exception_handler(RequestValidationError)

View File

@@ -13,3 +13,4 @@ router.include_router(recipe_crud_routes.user_router, prefix=prefix, tags=["Reci
router.include_router(image_and_assets.user_router, prefix=prefix, tags=["Recipe: Images and Assets"])
router.include_router(comments.router, prefix=prefix, tags=["Recipe: Comments"])
router.include_router(bulk_actions.router, prefix=prefix, tags=["Recipe: Bulk Actions"])
router.include_router(bulk_actions.export_router, prefix=prefix, tags=["Recipe: Bulk Exports"])

View File

@@ -1,7 +1,10 @@
from pathlib import Path
from fastapi import APIRouter, Depends
from fastapi.responses import FileResponse
from mealie.core.dependencies.dependencies import temporary_zip_path
from mealie.core.security import create_file_token
from mealie.schema.group.group_exports import GroupDataExport
from mealie.schema.recipe.recipe_bulk_actions import (
AssignCategories,
AssignTags,
@@ -38,7 +41,10 @@ def bulk_delete_recipes(
bulk_service.delete_recipes(delete_recipes.recipes)
@router.post("/export", response_class=FileResponse)
export_router = APIRouter(prefix="/bulk-actions")
@export_router.post("/export")
def bulk_export_recipes(
export_recipes: ExportRecipes,
temp_path=Depends(temporary_zip_path),
@@ -46,4 +52,26 @@ def bulk_export_recipes(
):
bulk_service.export_recipes(temp_path, export_recipes.recipes)
return FileResponse(temp_path, filename="recipes.zip")
# return FileResponse(temp_path, filename="recipes.zip")
@export_router.get("/export/download")
def get_exported_data_token(path: Path, _: RecipeBulkActions = Depends(RecipeBulkActions.private)):
# return FileResponse(temp_path, filename="recipes.zip")
"""Returns a token to download a file"""
return {"fileToken": create_file_token(path)}
@export_router.get("/export", response_model=list[GroupDataExport])
def get_exported_data(bulk_service: RecipeBulkActions = Depends(RecipeBulkActions.private)):
return bulk_service.get_exports()
# return FileResponse(temp_path, filename="recipes.zip")
@export_router.delete("/export/purge")
def purge_export_data(bulk_service: RecipeBulkActions = Depends(RecipeBulkActions.private)):
"""Remove all exports data, including items on disk without database entry"""
amountDelete = bulk_service.purge_exports()
return {"message": f"{amountDelete} exports deleted"}

View File

@@ -21,8 +21,13 @@ logger = get_logger()
@user_router.get("", response_model=list[RecipeSummary])
async def get_all(start=0, limit=None, service: RecipeService = Depends(RecipeService.private)):
json_compatible_item_data = jsonable_encoder(service.get_all(start, limit))
async def get_all(
start: int = 0,
limit: int = None,
load_foods: bool = False,
service: RecipeService = Depends(RecipeService.private),
):
json_compatible_item_data = jsonable_encoder(service.get_all(start, limit, load_foods))
return JSONResponse(content=json_compatible_item_data)