From 36627d409cff91017c56ea45f39ea5803fb655f9 Mon Sep 17 00:00:00 2001 From: Sergey Vanyushkin Date: Wed, 4 Sep 2024 23:50:50 +0000 Subject: [PATCH] ADD update_menu usecase, fix mapper --- .../application/contracts/requests.py | 8 +++ .../application/usecases/menu/update_menu.py | 24 +++++++ src/fastfood_two/domain/menu/gateway.py | 9 ++- .../infrastructure/menu_gateway.py | 23 ++++++- .../pg_storage/mappers/menu_mapper.py | 4 +- .../fastapi_backend/dependencies.py | 3 + .../fastapi_backend/depends/usecases.py | 5 ++ .../fastapi_backend/pdmodels/menu.py | 5 ++ .../fastapi_backend/routers/menu.py | 66 +++++++++++++++++-- 9 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 src/fastfood_two/application/usecases/menu/update_menu.py diff --git a/src/fastfood_two/application/contracts/requests.py b/src/fastfood_two/application/contracts/requests.py index 9f6fff8..6f8041e 100644 --- a/src/fastfood_two/application/contracts/requests.py +++ b/src/fastfood_two/application/contracts/requests.py @@ -1,7 +1,15 @@ from dataclasses import dataclass +from uuid import UUID @dataclass(frozen=True) class AddMenuDTO: title: str description: str | None + + +@dataclass(frozen=True) +class UpdateMenuDTO: + id: UUID + title: str | None + description: str | None diff --git a/src/fastfood_two/application/usecases/menu/update_menu.py b/src/fastfood_two/application/usecases/menu/update_menu.py new file mode 100644 index 0000000..17d0180 --- /dev/null +++ b/src/fastfood_two/application/usecases/menu/update_menu.py @@ -0,0 +1,24 @@ +from fastfood_two.application.abstractions.interactor import Interactor +from fastfood_two.application.contracts.requests import UpdateMenuDTO +from fastfood_two.application.contracts.responses import MenuDTO +from fastfood_two.domain.menu.gateway import MenuGateway +from fastfood_two.domain.menu.menu_entity import Description, MenuId, Title + + +class UpdateMenu(Interactor[UpdateMenuDTO, MenuDTO]): + def __init__(self, gateway: MenuGateway) -> None: + self._menu_gateway = gateway + + async def __call__(self, request: UpdateMenuDTO) -> MenuDTO: + + menu = await self._menu_gateway.update_menu( + id=MenuId(request.id), + title=Title(request.title) if request.title else None, + description=Description(request.description) if request.description else None, + ) + + return MenuDTO( + id=menu.id.value, + title=menu.title.value, + description=menu.description.value, + ) diff --git a/src/fastfood_two/domain/menu/gateway.py b/src/fastfood_two/domain/menu/gateway.py index ffb763a..991d474 100644 --- a/src/fastfood_two/domain/menu/gateway.py +++ b/src/fastfood_two/domain/menu/gateway.py @@ -1,7 +1,7 @@ from typing import Protocol from uuid import UUID -from fastfood_two.domain.menu.menu_entity import Menu +from fastfood_two.domain.menu.menu_entity import Description, Menu, MenuId, Title class MenuGateway(Protocol): @@ -14,7 +14,12 @@ class MenuGateway(Protocol): async def get_all_menus(self) -> list[Menu]: raise NotImplementedError - async def update_menu(self, updated_menu: Menu) -> Menu | None: + async def update_menu( + self, + id: MenuId, + title: Title | None, + description: Description | None, + ) -> Menu: raise NotImplementedError async def delete_menu(self, id: UUID) -> None: diff --git a/src/fastfood_two/infrastructure/menu_gateway.py b/src/fastfood_two/infrastructure/menu_gateway.py index fb922e0..b322905 100644 --- a/src/fastfood_two/infrastructure/menu_gateway.py +++ b/src/fastfood_two/infrastructure/menu_gateway.py @@ -3,7 +3,7 @@ from uuid import UUID from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession -from fastfood_two.domain.menu.menu_entity import Menu +from fastfood_two.domain.menu.menu_entity import Description, Menu, MenuId, Title from fastfood_two.infrastructure.pg_storage.mappers.menu_mapper import ( db_entity_to_domain, ) @@ -16,7 +16,7 @@ class MenuGatewayImpl: async def get_all_menus(self) -> list[Menu]: query = text("SELECT * FROM menu;") menus = await self._session.execute(query) - return [db_entity_to_domain(menu) for menu in menus] + return [db_entity_to_domain(menu.tuple()) for menu in menus] async def get_menu_by_id(self, id: UUID) -> Menu | None: ... @@ -33,6 +33,23 @@ class MenuGatewayImpl: await self._session.commit() return menu - async def update_menu(self, updated_menu: Menu) -> Menu | None: ... + async def update_menu(self, id: MenuId, title: Title | None, description: Description | None) -> Menu: + query = text( + "UPDATE menu SET {}{} {} WHERE id = :id RETURNING *;".format( + "title = :title " if title is not None else "", + "," if all([title is not None, description is not None]) else "", + "description = :description" if description is not None else "", + ) + ) + menu = await self._session.execute( + query, + { + "id": id.value, + "title": title.value if title else None, + "description": description.value if description else None, + }, + ) + await self._session.commit() + return db_entity_to_domain(menu.one().tuple()) async def delete_menu(self, id: UUID) -> None: ... diff --git a/src/fastfood_two/infrastructure/pg_storage/mappers/menu_mapper.py b/src/fastfood_two/infrastructure/pg_storage/mappers/menu_mapper.py index d1fbbf7..570bc3f 100644 --- a/src/fastfood_two/infrastructure/pg_storage/mappers/menu_mapper.py +++ b/src/fastfood_two/infrastructure/pg_storage/mappers/menu_mapper.py @@ -1,7 +1,9 @@ +from uuid import UUID + from fastfood_two.domain.menu.menu_entity import Description, Menu, MenuId, Title -def db_entity_to_domain(menu) -> Menu: +def db_entity_to_domain(menu: tuple[UUID, str, str | None]) -> Menu: print(menu) return Menu( id=MenuId(menu[0]), diff --git a/src/fastfood_two/presentation/fastapi_backend/dependencies.py b/src/fastfood_two/presentation/fastapi_backend/dependencies.py index d2b0b47..75d26e1 100644 --- a/src/fastfood_two/presentation/fastapi_backend/dependencies.py +++ b/src/fastfood_two/presentation/fastapi_backend/dependencies.py @@ -7,6 +7,7 @@ from fastfood_two.application.common.logger import configure_logger from fastfood_two.application.config import Config from fastfood_two.application.usecases.menu.add_menu import AddMenu from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus +from fastfood_two.application.usecases.menu.update_menu import UpdateMenu from fastfood_two.domain.menu.gateway import MenuGateway from fastfood_two.presentation.fastapi_backend.depends.config import get_settings from fastfood_two.presentation.fastapi_backend.depends.gateways import get_menu_gateway @@ -18,6 +19,7 @@ from fastfood_two.presentation.fastapi_backend.depends.session import ( from fastfood_two.presentation.fastapi_backend.depends.usecases import ( add_menu_usecase, get_all_menus_usecase, + update_menu_usecase, ) logger = logging.getLogger(__name__) @@ -41,5 +43,6 @@ def init_dependencies(app: FastAPI) -> None: app.dependency_overrides[GetAllMenus] = get_all_menus_usecase app.dependency_overrides[AddMenu] = add_menu_usecase + app.dependency_overrides[UpdateMenu] = update_menu_usecase logger.info("Dependencies initialized") diff --git a/src/fastfood_two/presentation/fastapi_backend/depends/usecases.py b/src/fastfood_two/presentation/fastapi_backend/depends/usecases.py index 4768271..7956f98 100644 --- a/src/fastfood_two/presentation/fastapi_backend/depends/usecases.py +++ b/src/fastfood_two/presentation/fastapi_backend/depends/usecases.py @@ -4,6 +4,7 @@ from fastapi import Depends from fastfood_two.application.usecases.menu.add_menu import AddMenu from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus +from fastfood_two.application.usecases.menu.update_menu import UpdateMenu from fastfood_two.domain.menu.gateway import MenuGateway from fastfood_two.presentation.fastapi_backend.depends.stub import Stub @@ -14,3 +15,7 @@ def get_all_menus_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGatew def add_menu_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGateway))]) -> AddMenu: return AddMenu(gateway=gateway) + + +def update_menu_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGateway))]) -> UpdateMenu: + return UpdateMenu(gateway=gateway) diff --git a/src/fastfood_two/presentation/fastapi_backend/pdmodels/menu.py b/src/fastfood_two/presentation/fastapi_backend/pdmodels/menu.py index c2841f2..6889861 100644 --- a/src/fastfood_two/presentation/fastapi_backend/pdmodels/menu.py +++ b/src/fastfood_two/presentation/fastapi_backend/pdmodels/menu.py @@ -4,3 +4,8 @@ from pydantic import BaseModel class AddMenuPDModel(BaseModel): title: str description: str | None + + +class UpdateMenuPDModel(BaseModel): + title: str | None = None + description: str | None = None diff --git a/src/fastfood_two/presentation/fastapi_backend/routers/menu.py b/src/fastfood_two/presentation/fastapi_backend/routers/menu.py index 6e063f3..1dbbc32 100644 --- a/src/fastfood_two/presentation/fastapi_backend/routers/menu.py +++ b/src/fastfood_two/presentation/fastapi_backend/routers/menu.py @@ -1,13 +1,18 @@ from typing import Annotated +from uuid import UUID from fastapi import APIRouter, Depends -from fastfood_two.application.contracts.requests import AddMenuDTO +from fastfood_two.application.contracts.requests import AddMenuDTO, UpdateMenuDTO from fastfood_two.application.contracts.responses import MenuDTO from fastfood_two.application.usecases.menu.add_menu import AddMenu from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus +from fastfood_two.application.usecases.menu.update_menu import UpdateMenu from fastfood_two.presentation.fastapi_backend.depends.stub import Stub -from fastfood_two.presentation.fastapi_backend.pdmodels.menu import AddMenuPDModel +from fastfood_two.presentation.fastapi_backend.pdmodels.menu import ( + AddMenuPDModel, + UpdateMenuPDModel, +) router = APIRouter(prefix="/menu", tags=["Menu"]) @@ -19,6 +24,15 @@ async def get_all_menus( """Get all menus. Endpoint returns list of all available food menus + + Parameters + ---------- + None + + Returns + ------- + list[MenuDTO] + list of created menu items """ menus = await usecase() return menus @@ -29,9 +43,53 @@ async def add_menu( request: AddMenuPDModel, usecase: Annotated[AddMenu, Depends(Stub(AddMenu))], ) -> MenuDTO: - """Get all menus. + """Add menus. - Endpoint returns list of all available food menus + Endpoint allows to add new food menu + + Parameters + ---------- + title: str + title of the menu + description: str + description of the menu + + Returns + ------- + MenuDTO + created menu """ menus = await usecase(request=AddMenuDTO(title=request.title, description=request.description)) return menus + + +@router.patch("/{menu_id}", response_model=MenuDTO) +async def update_menu( + menu_id: UUID, + request: UpdateMenuPDModel, + usecase: Annotated[UpdateMenu, Depends(Stub(UpdateMenu))], +) -> MenuDTO: + """Update menus. + + Endpoint allows to update food menu + + Parameters + ---------- + title: str | None + new title of the menu or None if you don't want to update it + description: str | None + new description of the menu or None if you don't want to update it + + Returns + ------- + MenuDTO + updated menu + """ + menus = await usecase( + request=UpdateMenuDTO( + id=menu_id, + title=request.title, + description=request.description, + ) + ) + return menus