feat: added "last-modified" header to supported record types (#1379)
* fixed type error * exposed created/updated timestamps to shopping list schema * added custom route to mix in "last-modified" header when available in CRUD routes * mixed in MealieCrudRoute to APIRouters * added HEAD route for shopping lists/list-items * replaced default serializer with FastAPI's
This commit is contained in:
parent
5db4dedc3f
commit
292bf7068a
10 changed files with 67 additions and 25 deletions
|
@ -1,6 +1,11 @@
|
|||
from typing import Optional
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
from json.decoder import JSONDecodeError
|
||||
from typing import Optional, Union
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from mealie.core.dependencies import get_admin_user, get_current_user
|
||||
|
||||
|
@ -8,20 +13,34 @@ from mealie.core.dependencies import get_admin_user, get_current_user
|
|||
class AdminAPIRouter(APIRouter):
|
||||
"""Router for functions to be protected behind admin authentication"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tags: Optional[list[str]] = None,
|
||||
prefix: str = "",
|
||||
):
|
||||
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_admin_user)])
|
||||
def __init__(self, tags: Optional[list[Union[str, Enum]]] = None, prefix: str = "", **kwargs):
|
||||
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_admin_user)], **kwargs)
|
||||
|
||||
|
||||
class UserAPIRouter(APIRouter):
|
||||
"""Router for functions to be protected behind user authentication"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tags: Optional[list[str]] = None,
|
||||
prefix: str = "",
|
||||
):
|
||||
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_current_user)])
|
||||
def __init__(self, tags: Optional[list[Union[str, Enum]]] = None, prefix: str = "", **kwargs):
|
||||
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_current_user)], **kwargs)
|
||||
|
||||
|
||||
class MealieCrudRoute(APIRoute):
|
||||
"""Route class to include the last-modified header when returning a MealieModel, when available"""
|
||||
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
try:
|
||||
response = await original_route_handler(request)
|
||||
response_body = json.loads(response.body)
|
||||
if type(response_body) == dict:
|
||||
if last_modified := response_body.get("updateAt"):
|
||||
response.headers["last-modified"] = last_modified
|
||||
|
||||
except JSONDecodeError:
|
||||
pass
|
||||
|
||||
return response
|
||||
|
||||
return custom_route_handler
|
||||
|
|
|
@ -6,12 +6,13 @@ from pydantic import UUID4
|
|||
from mealie.core.exceptions import mealie_registered_exceptions
|
||||
from mealie.routes._base import BaseUserController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
|
||||
from mealie.services.event_bus_service.message_types import EventTypes
|
||||
|
||||
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"])
|
||||
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"], route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@controller(router)
|
||||
|
|
|
@ -6,6 +6,7 @@ from pydantic import UUID4
|
|||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute
|
||||
from mealie.schema.group.group_events import (
|
||||
GroupEventNotifierCreate,
|
||||
GroupEventNotifierOut,
|
||||
|
@ -17,7 +18,9 @@ from mealie.schema.mapper import cast
|
|||
from mealie.schema.query import GetAll
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||
|
||||
router = APIRouter(prefix="/groups/events/notifications", tags=["Group: Event Notifications"])
|
||||
router = APIRouter(
|
||||
prefix="/groups/events/notifications", tags=["Group: Event Notifications"], route_class=MealieCrudRoute
|
||||
)
|
||||
|
||||
|
||||
@controller(router)
|
||||
|
|
|
@ -6,6 +6,7 @@ from pydantic import UUID4
|
|||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute
|
||||
from mealie.schema.labels import (
|
||||
MultiPurposeLabelCreate,
|
||||
MultiPurposeLabelOut,
|
||||
|
@ -16,7 +17,7 @@ from mealie.schema.labels import (
|
|||
from mealie.schema.mapper import cast
|
||||
from mealie.schema.query import GetAll
|
||||
|
||||
router = APIRouter(prefix="/groups/labels", tags=["Group: Multi Purpose Labels"])
|
||||
router = APIRouter(prefix="/groups/labels", tags=["Group: Multi Purpose Labels"], route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@controller(router)
|
||||
|
|
|
@ -6,6 +6,7 @@ from pydantic import UUID4
|
|||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute
|
||||
from mealie.schema.group.group_shopping_list import (
|
||||
ShoppingListCreate,
|
||||
ShoppingListItemCreate,
|
||||
|
@ -23,7 +24,9 @@ from mealie.services.event_bus_service.event_bus_service import EventBusService,
|
|||
from mealie.services.event_bus_service.message_types import EventTypes
|
||||
from mealie.services.group_services.shopping_lists import ShoppingListService
|
||||
|
||||
item_router = APIRouter(prefix="/groups/shopping/items", tags=["Group: Shopping List Items"])
|
||||
item_router = APIRouter(
|
||||
prefix="/groups/shopping/items", tags=["Group: Shopping List Items"], route_class=MealieCrudRoute
|
||||
)
|
||||
|
||||
|
||||
@controller(item_router)
|
||||
|
@ -95,6 +98,7 @@ class ShoppingListItemController(BaseUserController):
|
|||
|
||||
return shopping_list_item
|
||||
|
||||
@item_router.head("/{item_id}", response_model=ShoppingListItemOut)
|
||||
@item_router.get("/{item_id}", response_model=ShoppingListItemOut)
|
||||
def get_one(self, item_id: UUID4):
|
||||
return self.mixins.get_one(item_id)
|
||||
|
@ -144,7 +148,7 @@ class ShoppingListItemController(BaseUserController):
|
|||
return shopping_list_item
|
||||
|
||||
|
||||
router = APIRouter(prefix="/groups/shopping/lists", tags=["Group: Shopping Lists"])
|
||||
router = APIRouter(prefix="/groups/shopping/lists", tags=["Group: Shopping Lists"], route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@controller(router)
|
||||
|
@ -189,6 +193,7 @@ class ShoppingListController(BaseUserController):
|
|||
|
||||
return val
|
||||
|
||||
@router.head("/{item_id}", response_model=ShoppingListOut)
|
||||
@router.get("/{item_id}", response_model=ShoppingListOut)
|
||||
def get_one(self, item_id: UUID4):
|
||||
return self.mixins.get_one(item_id)
|
||||
|
|
|
@ -19,7 +19,7 @@ from mealie.pkgs import cache
|
|||
from mealie.repos.repository_recipes import RepositoryRecipes
|
||||
from mealie.routes._base import BaseUserController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import UserAPIRouter
|
||||
from mealie.routes._base.routers import MealieCrudRoute, UserAPIRouter
|
||||
from mealie.schema.query import GetAll
|
||||
from mealie.schema.recipe import Recipe, RecipeImageTypes, ScrapeRecipe
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, CreateRecipeByUrlBulk, RecipeSummary
|
||||
|
@ -112,7 +112,7 @@ class RecipeExportController(BaseRecipeController):
|
|||
return FileResponse(temp_path, filename=f"{slug}.zip")
|
||||
|
||||
|
||||
router = UserAPIRouter(prefix="/recipes", tags=["Recipe: CRUD"])
|
||||
router = UserAPIRouter(prefix="/recipes", tags=["Recipe: CRUD"], route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@controller(router)
|
||||
|
|
|
@ -6,12 +6,13 @@ from pydantic import UUID4
|
|||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.query import GetAll
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientFood, IngredientFood, MergeFood, SaveIngredientFood
|
||||
from mealie.schema.response.responses import SuccessResponse
|
||||
|
||||
router = APIRouter(prefix="/foods", tags=["Recipes: Foods"])
|
||||
router = APIRouter(prefix="/foods", tags=["Recipes: Foods"], route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@controller(router)
|
||||
|
|
|
@ -6,12 +6,13 @@ from pydantic import UUID4
|
|||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.query import GetAll
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientUnit, IngredientUnit, MergeUnit, SaveIngredientUnit
|
||||
from mealie.schema.response.responses import SuccessResponse
|
||||
|
||||
router = APIRouter(prefix="/units", tags=["Recipes: Units"])
|
||||
router = APIRouter(prefix="/units", tags=["Recipes: Units"], route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@controller(router)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
|
@ -38,6 +39,9 @@ class ShoppingListItemCreate(MealieModel):
|
|||
label_id: Optional[UUID4] = None
|
||||
recipe_references: list[ShoppingListItemRecipeRef] = []
|
||||
|
||||
created_at: Optional[datetime]
|
||||
update_at: Optional[datetime]
|
||||
|
||||
|
||||
class ShoppingListItemUpdate(ShoppingListItemCreate):
|
||||
id: UUID4
|
||||
|
@ -45,7 +49,7 @@ class ShoppingListItemUpdate(ShoppingListItemCreate):
|
|||
|
||||
class ShoppingListItemOut(ShoppingListItemUpdate):
|
||||
label: Optional[MultiPurposeLabelSummary]
|
||||
recipe_references: list[ShoppingListItemRecipeRefOut] = []
|
||||
recipe_references: list[Union[ShoppingListItemRecipeRef, ShoppingListItemRecipeRefOut]] = []
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
@ -54,6 +58,9 @@ class ShoppingListItemOut(ShoppingListItemUpdate):
|
|||
class ShoppingListCreate(MealieModel):
|
||||
name: str = None
|
||||
|
||||
created_at: Optional[datetime]
|
||||
update_at: Optional[datetime]
|
||||
|
||||
|
||||
class ShoppingListRecipeRefOut(MealieModel):
|
||||
id: UUID4
|
||||
|
|
|
@ -46,6 +46,8 @@ def serialize_list_items(list_items: list[ShoppingListItemOut]) -> list:
|
|||
item_dict["id"] = str(item.id)
|
||||
as_dict.append(item_dict)
|
||||
|
||||
# the default serializer fails on certain complex objects, so we use FastAPI's serliazer first
|
||||
as_dict = utils.jsonify(as_dict)
|
||||
return as_dict
|
||||
|
||||
|
||||
|
@ -151,6 +153,8 @@ def test_shopping_list_items_update_many_reorder(
|
|||
as_dict.append(item_dict)
|
||||
|
||||
# update list
|
||||
# the default serializer fails on certain complex objects, so we use FastAPI's serliazer first
|
||||
as_dict = utils.jsonify(as_dict)
|
||||
response = api_client.put(Routes.items, json=as_dict, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
|
Loading…
Reference in a new issue