Add Domain entity
parent
cbcebdffb5
commit
fad06c45f8
|
@ -1,8 +0,0 @@
|
||||||
from typing import Protocol
|
|
||||||
|
|
||||||
from fastfood_two.application.contracts.responses import MenuResponse
|
|
||||||
|
|
||||||
|
|
||||||
class MenuGateway(Protocol):
|
|
||||||
async def get_all_menus(self) -> list[MenuResponse]:
|
|
||||||
raise NotImplementedError
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AddMenuDTO:
|
||||||
|
title: str
|
||||||
|
description: str | None
|
|
@ -3,7 +3,7 @@ from uuid import UUID
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class MenuResponse:
|
class MenuDTO:
|
||||||
id: UUID
|
id: UUID
|
||||||
title: str
|
title: str
|
||||||
description: str
|
description: str | None
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from fastfood_two.application.abstractions.interactor import Interactor
|
||||||
|
from fastfood_two.application.contracts.requests import AddMenuDTO
|
||||||
|
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, Menu, MenuId, Title
|
||||||
|
|
||||||
|
|
||||||
|
class AddMenu(Interactor[AddMenuDTO, MenuDTO]):
|
||||||
|
def __init__(self, gateway: MenuGateway) -> None:
|
||||||
|
self._menu_gateway = gateway
|
||||||
|
|
||||||
|
async def __call__(self, request: AddMenuDTO) -> MenuDTO:
|
||||||
|
|
||||||
|
menu = await self._menu_gateway.insert_menu(
|
||||||
|
menu=Menu(
|
||||||
|
id=MenuId(uuid4()),
|
||||||
|
title=Title(request.title),
|
||||||
|
description=Description(request.description),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return MenuDTO(
|
||||||
|
id=menu.id.value,
|
||||||
|
title=menu.title.value,
|
||||||
|
description=menu.description.value,
|
||||||
|
)
|
|
@ -1,12 +1,19 @@
|
||||||
from fastfood_two.application.abstractions.interactor import Interactor
|
from fastfood_two.application.abstractions.interactor import Interactor
|
||||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
from fastfood_two.application.contracts.responses import MenuDTO
|
||||||
from fastfood_two.application.contracts.responses import MenuResponse
|
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||||
|
|
||||||
|
|
||||||
class GetAllMenus(Interactor[None, list[MenuResponse]]):
|
class GetAllMenus(Interactor[None, list[MenuDTO]]):
|
||||||
def __init__(self, gateway: MenuGateway) -> None:
|
def __init__(self, gateway: MenuGateway) -> None:
|
||||||
self._menu_gateway = gateway
|
self._menu_gateway = gateway
|
||||||
|
|
||||||
async def __call__(self, request=None) -> list[MenuResponse]:
|
async def __call__(self, request=None) -> list[MenuDTO]:
|
||||||
menus = await self._menu_gateway.get_all_menus()
|
menus = await self._menu_gateway.get_all_menus()
|
||||||
return menus
|
return [
|
||||||
|
MenuDTO(
|
||||||
|
id=menu.id.value,
|
||||||
|
title=menu.title.value,
|
||||||
|
description=menu.description.value,
|
||||||
|
)
|
||||||
|
for menu in menus
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
|
from fastfood_two.domain.core.value_obj import DomainValueObject
|
||||||
|
|
||||||
|
EntityId = TypeVar("EntityId", bound=DomainValueObject)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DomainEntity(Generic[EntityId]):
|
||||||
|
id: EntityId
|
|
@ -0,0 +1,8 @@
|
||||||
|
class DomainError(Exception):
|
||||||
|
def __init__(self, message: str, *args: object) -> None:
|
||||||
|
self.message = message
|
||||||
|
super().__init__(*args)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainValidationError(DomainError):
|
||||||
|
pass
|
|
@ -0,0 +1,6 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DomainValueObject:
|
||||||
|
pass
|
|
@ -0,0 +1,9 @@
|
||||||
|
from fastfood_two.domain.core.error import DomainError
|
||||||
|
|
||||||
|
|
||||||
|
class MenuNotFoundError(DomainError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MenuDataValidationError(DomainError):
|
||||||
|
pass
|
|
@ -0,0 +1,21 @@
|
||||||
|
from typing import Protocol
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastfood_two.domain.menu.menu_entity import Menu
|
||||||
|
|
||||||
|
|
||||||
|
class MenuGateway(Protocol):
|
||||||
|
async def get_menu_by_id(self, id: UUID) -> Menu | None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def insert_menu(self, menu: Menu) -> Menu:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def get_all_menus(self) -> list[Menu]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def update_menu(self, updated_menu: Menu) -> Menu | None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def delete_menu(self, id: UUID) -> None:
|
||||||
|
raise NotImplementedError
|
|
@ -0,0 +1,34 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastfood_two.domain.core.entity import DomainEntity
|
||||||
|
from fastfood_two.domain.core.value_obj import DomainValueObject
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MenuId(DomainValueObject):
|
||||||
|
value: UUID
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Title(DomainValueObject):
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Description(DomainValueObject):
|
||||||
|
value: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Menu(DomainEntity[MenuId]):
|
||||||
|
title: Title
|
||||||
|
description: Description
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(id: UUID, title: str, description: str | None) -> "Menu":
|
||||||
|
return Menu(
|
||||||
|
id=MenuId(id),
|
||||||
|
title=Title(title),
|
||||||
|
description=Description(description),
|
||||||
|
)
|
|
@ -1,15 +1,27 @@
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from fastfood_two.application.contracts.responses import MenuResponse
|
from fastfood_two.domain.menu.menu_entity import Menu
|
||||||
from fastfood_two.infrastructure.pg_storage.mappers.menu_mapper import entity_to_dto
|
from fastfood_two.infrastructure.pg_storage.mappers.menu_mapper import (
|
||||||
|
db_entity_to_domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MenuGatewayImpl:
|
class MenuGatewayImpl:
|
||||||
def __init__(self, session: AsyncSession) -> None:
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
self._session = session
|
self._session = session
|
||||||
|
|
||||||
async def get_all_menus(self) -> list[MenuResponse]:
|
async def get_all_menus(self) -> list[Menu]:
|
||||||
query = text("SELECT * FROM menu;")
|
query = text("SELECT * FROM menu;")
|
||||||
menus = await self._session.execute(query)
|
menus = await self._session.execute(query)
|
||||||
return [entity_to_dto(menu) for menu in menus.scalars().all()]
|
return [db_entity_to_domain(menu) for menu in menus.scalars().all()]
|
||||||
|
|
||||||
|
async def get_menu_by_id(self, id: UUID) -> Menu | None: ...
|
||||||
|
|
||||||
|
async def insert_menu(self, menu: Menu) -> Menu: ...
|
||||||
|
|
||||||
|
async def update_menu(self, updated_menu: Menu) -> Menu | None: ...
|
||||||
|
|
||||||
|
async def delete_menu(self, id: UUID) -> None: ...
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from fastfood_two.application.contracts.responses import MenuResponse
|
from fastfood_two.domain.menu.menu_entity import Menu
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from fastfood_two.infrastructure.pg_storage.models import Menu
|
from fastfood_two.infrastructure.pg_storage.models import SQLAMenu
|
||||||
|
|
||||||
|
|
||||||
def entity_to_dto(menu: "Menu") -> MenuResponse:
|
def db_entity_to_domain(menu: "SQLAMenu") -> Menu:
|
||||||
return MenuResponse(
|
return Menu(
|
||||||
id=menu.id,
|
id=menu.id,
|
||||||
title=menu.title,
|
title=menu.title,
|
||||||
description=menu.description or "",
|
description=menu.description,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from .base import Base
|
from .base import Base
|
||||||
from .menu_model import Menu
|
from .menu_model import SQLAMenu
|
||||||
from .dish_model import Dish
|
from .dish_model import Dish
|
||||||
from .common_attrs import uuidpk, str_25
|
from .common_attrs import uuidpk, str_25
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Base",
|
"Base",
|
||||||
"Menu",
|
"SQLAMenu",
|
||||||
"Dish",
|
"Dish",
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,18 +5,18 @@ from .base import Base
|
||||||
from .common_attrs import str_25, uuidpk
|
from .common_attrs import str_25, uuidpk
|
||||||
|
|
||||||
|
|
||||||
class Menu(Base):
|
class SQLAMenu(Base):
|
||||||
__tablename__ = "menu"
|
__tablename__ = "menus"
|
||||||
|
|
||||||
id: Mapped[uuidpk]
|
id: Mapped[uuidpk]
|
||||||
title: Mapped[str_25]
|
title: Mapped[str_25]
|
||||||
description: Mapped[str | None]
|
description: Mapped[str | None]
|
||||||
|
|
||||||
parent: Mapped[uuidpk | None] = mapped_column(ForeignKey("menu.id", ondelete="CASCADE"), nullable=True)
|
parent: Mapped[uuidpk | None] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"), nullable=True)
|
||||||
|
|
||||||
submenus: Mapped[list["Menu"]] = relationship(
|
submenus: Mapped[list["SQLAMenu"]] = relationship(
|
||||||
"Menu",
|
"SQLAMenu",
|
||||||
backref="menu",
|
backref="menus",
|
||||||
lazy="selectin",
|
lazy="selectin",
|
||||||
cascade="all, delete",
|
cascade="all, delete",
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,8 +5,8 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
|
||||||
|
|
||||||
from fastfood_two.application.common.logger import configure_logger
|
from fastfood_two.application.common.logger import configure_logger
|
||||||
from fastfood_two.application.config import Config
|
from fastfood_two.application.config import Config
|
||||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
|
||||||
from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus
|
from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus
|
||||||
|
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.config import get_settings
|
||||||
from fastfood_two.presentation.fastapi_backend.depends.gateways import get_menu_gateway
|
from fastfood_two.presentation.fastapi_backend.depends.gateways import get_menu_gateway
|
||||||
from fastfood_two.presentation.fastapi_backend.depends.session import (
|
from fastfood_two.presentation.fastapi_backend.depends.session import (
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import Annotated
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||||
from fastfood_two.infrastructure.menu_gateway import MenuGatewayImpl
|
from fastfood_two.infrastructure.menu_gateway import MenuGatewayImpl
|
||||||
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,15 @@ from typing import Annotated
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
|
|
||||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
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.get_all_menus import GetAllMenus
|
||||||
|
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||||
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
||||||
|
|
||||||
|
|
||||||
def get_all_menus_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGateway))]) -> GetAllMenus:
|
def get_all_menus_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGateway))]) -> GetAllMenus:
|
||||||
return GetAllMenus(gateway=gateway)
|
return GetAllMenus(gateway=gateway)
|
||||||
|
|
||||||
|
|
||||||
|
def add_menu_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGateway))]) -> AddMenu:
|
||||||
|
return AddMenu(gateway=gateway)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class AddMenuPDModel(BaseModel):
|
||||||
|
title: str
|
||||||
|
description: str | None
|
|
@ -2,20 +2,36 @@ from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from fastfood_two.application.contracts.responses import MenuResponse
|
from fastfood_two.application.contracts.requests import AddMenuDTO
|
||||||
|
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.get_all_menus import GetAllMenus
|
||||||
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
||||||
|
from fastfood_two.presentation.fastapi_backend.pdmodels.menu import AddMenuPDModel
|
||||||
|
|
||||||
router = APIRouter(prefix="/menu", tags=["Menu"])
|
router = APIRouter(prefix="/menu", tags=["Menu"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[MenuResponse])
|
@router.get("/", response_model=list[MenuDTO])
|
||||||
async def get_all_menus(
|
async def get_all_menus(
|
||||||
usecase: Annotated[GetAllMenus, Depends(Stub(GetAllMenus))],
|
usecase: Annotated[GetAllMenus, Depends(Stub(GetAllMenus))],
|
||||||
) -> list[MenuResponse]:
|
) -> list[MenuDTO]:
|
||||||
"""Get all menus.
|
"""Get all menus.
|
||||||
|
|
||||||
Endpoint returns list of all available food menus
|
Endpoint returns list of all available food menus
|
||||||
"""
|
"""
|
||||||
menus = await usecase()
|
menus = await usecase()
|
||||||
return menus
|
return menus
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=MenuDTO)
|
||||||
|
async def add_menu(
|
||||||
|
request: AddMenuPDModel,
|
||||||
|
usecase: Annotated[AddMenu, Depends(Stub(AddMenu))],
|
||||||
|
) -> MenuDTO:
|
||||||
|
"""Get all menus.
|
||||||
|
|
||||||
|
Endpoint returns list of all available food menus
|
||||||
|
"""
|
||||||
|
menus = await usecase(request=AddMenuDTO(title=request.title, description=request.description))
|
||||||
|
return menus
|
||||||
|
|
Loading…
Reference in New Issue