diff --git a/src/entities/cart.py b/src/entities/cart.py index 805143c..dcb6d08 100644 --- a/src/entities/cart.py +++ b/src/entities/cart.py @@ -1,6 +1,6 @@ #python imports from typing import List -from dataclasses import dataclass +from dataclasses import dataclass, field #dependency imports from .cart_item import CartItem @@ -9,6 +9,7 @@ from .product import Product @dataclass class Cart: items: List[CartItem] + total_price: float = field(init=False, default=0.0) def add_item(self, product: Product, quantity: int): for item in self.items: @@ -21,7 +22,7 @@ class Cart: self.items = [item for item in self.items if item.product.id != product_id] def calculate_total_price(self) -> float: - return sum(item.calculate_total_price() for item in self.items) + self.total_price = sum(item.calculate_total_price() for item in self.items) def list_items(self) -> List[str]: return [f"{item.quantity} x {item.product.name} - {item.calculate_total_price()} EUR" for item in self.items] \ No newline at end of file diff --git a/src/framework_driver/ui/src/controller.js b/src/framework_driver/ui/src/controller.js index be844c3..298a58e 100644 --- a/src/framework_driver/ui/src/controller.js +++ b/src/framework_driver/ui/src/controller.js @@ -7,9 +7,12 @@ const Controller = { }, async showCart() { - const userId = 1; // Hardcoded for simplicity - const cart = await Model.fetchCart(userId); - View.renderCart(cart); + const userId = "1"; // Hardcoded for simplicity + const cartResponse = await Model.fetchCart(userId); + if(cartResponse.status=="success"){ + const cart = cartResponse.data; + View.renderCart(cart); + } }, async showProduct(productId) { diff --git a/src/interface_adapters/controllers/base_controller.py b/src/interface_adapters/controllers/base_controller.py new file mode 100644 index 0000000..e85a764 --- /dev/null +++ b/src/interface_adapters/controllers/base_controller.py @@ -0,0 +1,9 @@ +from use_cases.interfaces import BaseInputPort, BaseOutputPort + +class BaseController: + def __init__(self, input_port: BaseInputPort) -> None: + self.input_port = input_port + + def handle_request(self, request) -> dict: + """Handles the incoming API request and delegates to the use case.""" + return self.input_port.execute(request) \ No newline at end of file diff --git a/src/interface_adapters/controllers/base_endpoint.py b/src/interface_adapters/controllers/base_endpoint.py new file mode 100644 index 0000000..12abe3b --- /dev/null +++ b/src/interface_adapters/controllers/base_endpoint.py @@ -0,0 +1,28 @@ +from typing import Type +from fastapi import APIRouter, HTTPException + +from .base_controller import BaseController +from use_cases.interfaces import BaseOutputPort, BaseInputPort, BaseRepository + +class Endpoint: + def __init__(self, + router: APIRouter, + controller_impl: Type[BaseController], + presenter_impl: Type[BaseOutputPort], + repository_impl: Type[BaseRepository], + use_case_impl: Type[BaseInputPort] + ): + self.router = router + self.presenter = presenter_impl() + self.repository = repository_impl() + self.use_case = use_case_impl(repository=self.repository, output_port=self.presenter) + self.controller = controller_impl(input_port=self.use_case) + + def create(self, route: str, request_model: Type, response_model: Type): + @self.router.post(route, response_model=response_model) + async def endpoint(request: request_model): + try: + response = self.controller.handle_request(request) + return response + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/src/interface_adapters/controllers/cart_controller.py b/src/interface_adapters/controllers/cart_controller.py index d288c6a..64f007a 100644 --- a/src/interface_adapters/controllers/cart_controller.py +++ b/src/interface_adapters/controllers/cart_controller.py @@ -3,18 +3,63 @@ from fastapi import APIRouter, HTTPException from fastapi.responses import JSONResponse from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse - +from pydantic import BaseModel #dependency imports -from use_cases import ViewCart -from interface_adapters.dtos import ViewCartRequestDTO, ViewCartResponseDTO, CartItemDTO +from use_cases.interactors import ViewCart +from use_cases.interfaces import BaseInputPort, BaseOutputPort +from interface_adapters.dtos import ViewCartRequestDTO, ViewCartResponseDTO, CartItemDTO, CartDTO +from .base_controller import BaseController + + + +#move to main +from .base_endpoint import Endpoint from interface_adapters.repositories import SQLCartRepository -# Initialize components -cart_repository = SQLCartRepository() -view_cart_use_case = ViewCart(cart_repository) +class ViewCartRequest(BaseModel): + user_id: str + +class ViewCartResponse(BaseModel): + status: str + data: CartDTO + +class ViewCartController(BaseController): + def __init__(self, input_port: BaseInputPort) -> None: + self.input_port = input_port + + def handle_request(self, request) -> dict: + """Handles the incoming API request and delegates to the use case.""" + return self.input_port.execute(request) + +class ViewCartPresenter(BaseOutputPort): + def present(self, output) -> dict: + """Format the response data for API.""" + cart_items=[CartItemDTO(product_id=item.product_id, name=item.name, quantity=item.quantity, price=item.price) for item in output.items] + cart_data = CartDTO(items=cart_items,total_price=output.total_price) + response = ViewCartResponse(status="success",data=cart_data) + return response router = APIRouter() +endpoint = Endpoint(router, ViewCartController, ViewCartPresenter, SQLCartRepository, ViewCart) +endpoint.create("/view_cart", ViewCartRequest, ViewCartResponse) + +""" +# Initialize components +presenter = ViewCartPresenter() +repository = SQLCartRepository() +use_case = ViewCart(repository=repository, output_port=presenter) +controller = ViewCartController(input_port=use_case) + + +@router.post("/view_cart/") +async def view_cart(request: ViewCartRequest): + try: + result = controller.handle_request(request.user_id) + return result # Returns a dictionary which FastAPI will convert to JSON + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + @router.post("/view_cart", response_model=ViewCartResponseDTO) async def view_cart(request_dto: ViewCartRequestDTO): @@ -29,4 +74,4 @@ async def view_cart(request_dto: ViewCartRequestDTO): ) return response_dto - +""" diff --git a/src/interface_adapters/controllers/product_controller.py b/src/interface_adapters/controllers/product_controller.py index 07e94e8..3d4efd9 100644 --- a/src/interface_adapters/controllers/product_controller.py +++ b/src/interface_adapters/controllers/product_controller.py @@ -5,7 +5,7 @@ from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse #dependency imports -from use_cases import ViewProduct, ViewAllProducts +from use_cases.interactors import ViewProduct, ViewAllProducts from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ViewAllProductsResponseDTO, ProductDTO from interface_adapters.repositories import SQLProductRepository diff --git a/src/interface_adapters/dtos/cart_item_dto.py b/src/interface_adapters/dtos/cart_dto.py similarity index 63% rename from src/interface_adapters/dtos/cart_item_dto.py rename to src/interface_adapters/dtos/cart_dto.py index 1588812..2abb0c4 100644 --- a/src/interface_adapters/dtos/cart_item_dto.py +++ b/src/interface_adapters/dtos/cart_dto.py @@ -6,4 +6,8 @@ class CartItemDTO(BaseModel): product_id: int name: str quantity: int - price: float \ No newline at end of file + price: float + +class CartDTO(BaseModel): + items: List[CartItemDTO] + total_price: float \ No newline at end of file diff --git a/src/interface_adapters/dtos/view_cart_response.py b/src/interface_adapters/dtos/view_cart_response.py index 9f61107..4284ef2 100644 --- a/src/interface_adapters/dtos/view_cart_response.py +++ b/src/interface_adapters/dtos/view_cart_response.py @@ -2,7 +2,7 @@ from pydantic import BaseModel from typing import List, Optional -from .cart_item_dto import CartItemDTO +from .cart_dto import CartItemDTO class ViewCartResponseDTO(BaseModel): diff --git a/src/interface_adapters/repositories/sql_cart_repository.py b/src/interface_adapters/repositories/sql_cart_repository.py index def6a23..a404efb 100644 --- a/src/interface_adapters/repositories/sql_cart_repository.py +++ b/src/interface_adapters/repositories/sql_cart_repository.py @@ -3,7 +3,7 @@ from typing import Optional, List import sqlite3 #dependency imports -from use_cases import ICartRepository +from use_cases.interfaces import ICartRepository from entities import Cart, CartItem class SQLCartRepository(ICartRepository): diff --git a/src/interface_adapters/repositories/sql_product_repository.py b/src/interface_adapters/repositories/sql_product_repository.py index 0bdbe0d..d19e9eb 100644 --- a/src/interface_adapters/repositories/sql_product_repository.py +++ b/src/interface_adapters/repositories/sql_product_repository.py @@ -3,7 +3,7 @@ from typing import Optional, List import sqlite3 #dependency imports -from use_cases import IProductRepository +from use_cases.interfaces import IProductRepository from entities import Product class SQLProductRepository(IProductRepository): diff --git a/src/interface_adapters/repositories/sql_user_repository.py b/src/interface_adapters/repositories/sql_user_repository.py index c0f3bc0..60fb5a1 100644 --- a/src/interface_adapters/repositories/sql_user_repository.py +++ b/src/interface_adapters/repositories/sql_user_repository.py @@ -3,7 +3,7 @@ from typing import Optional, List import sqlite3 #dependency imports -from use_cases import IUserRepository +from use_cases.interfaces import IUserRepository from entities import User class SQLUserRepository(IUserRepository): diff --git a/src/main.py b/src/main.py index 4eb0880..4e5f402 100644 --- a/src/main.py +++ b/src/main.py @@ -32,3 +32,4 @@ async def root(): if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000) + diff --git a/src/use_cases/__init__.py b/src/use_cases/interactors/__init__.py similarity index 100% rename from src/use_cases/__init__.py rename to src/use_cases/interactors/__init__.py diff --git a/src/use_cases/create_user.py b/src/use_cases/interactors/create_user.py similarity index 100% rename from src/use_cases/create_user.py rename to src/use_cases/interactors/create_user.py diff --git a/src/use_cases/view_all_products.py b/src/use_cases/interactors/view_all_products.py similarity index 83% rename from src/use_cases/view_all_products.py rename to src/use_cases/interactors/view_all_products.py index ea41298..6b3c765 100644 --- a/src/use_cases/view_all_products.py +++ b/src/use_cases/interactors/view_all_products.py @@ -3,7 +3,7 @@ from typing import Optional, List #dependency imports from entities import Product -from .product_repository import IProductRepository +from use_cases.interfaces.product_repository import IProductRepository class ViewAllProducts: def __init__(self, product_repository: IProductRepository): diff --git a/src/use_cases/interactors/view_cart.py b/src/use_cases/interactors/view_cart.py new file mode 100644 index 0000000..a232b9a --- /dev/null +++ b/src/use_cases/interactors/view_cart.py @@ -0,0 +1,34 @@ +#python imports +from typing import Optional + +#dependency imports +from entities import Cart, CartItem +from use_cases.interfaces.cart_repository import ICartRepository +from use_cases.interfaces.base_output_port import BaseOutputPort +from use_cases.interfaces.base_input_port import BaseInputPort + +""" +class ViewCartInputPort(): + def execute(self, name: str, email: str) -> None: + pass + +class ViewCartOutputPort(): + def present(self, user: dict) -> dict: + pass +""" +class ViewCart(BaseInputPort): + def __init__(self, repository: ICartRepository, output_port: BaseOutputPort): + self.repository = repository + self.output_port = output_port + + def execute(self, input_data) -> Optional[Cart]: + """ + Fetches the cart data from the repository. + """ + if not hasattr(input_data, 'user_id'): + raise ValueError("Missing 'user_id' in input_data") + cart = self.repository.get_cart_by_user_id(input_data.user_id) + if not cart: + return None + cart.calculate_total_price() + return self.output_port.present(cart) \ No newline at end of file diff --git a/src/use_cases/view_product.py b/src/use_cases/interactors/view_product.py similarity index 83% rename from src/use_cases/view_product.py rename to src/use_cases/interactors/view_product.py index 271f2f1..13ba9f0 100644 --- a/src/use_cases/view_product.py +++ b/src/use_cases/interactors/view_product.py @@ -3,7 +3,7 @@ from typing import Optional #dependency imports from entities import Product -from .product_repository import IProductRepository +from use_cases.interfaces.product_repository import IProductRepository class ViewProduct: def __init__(self, product_repository: IProductRepository): diff --git a/src/use_cases/view_user.py b/src/use_cases/interactors/view_user.py similarity index 100% rename from src/use_cases/view_user.py rename to src/use_cases/interactors/view_user.py diff --git a/src/use_cases/interfaces/__init__.py b/src/use_cases/interfaces/__init__.py new file mode 100644 index 0000000..10727b3 --- /dev/null +++ b/src/use_cases/interfaces/__init__.py @@ -0,0 +1,11 @@ +import importlib +import pkgutil +import inspect + +__all__ = [] +for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): + module = importlib.import_module(f".{module_name}", __name__) + for name, obj in inspect.getmembers(module, inspect.isclass): + if obj.__module__ == module.__name__: + globals()[name] = obj + __all__.append(name) \ No newline at end of file diff --git a/src/use_cases/interfaces/base_input_port.py b/src/use_cases/interfaces/base_input_port.py new file mode 100644 index 0000000..585fb54 --- /dev/null +++ b/src/use_cases/interfaces/base_input_port.py @@ -0,0 +1,6 @@ +class BaseInputPort: + def execute(self, input_data): + """ + Abstract method to execute use case. + """ + raise NotImplementedError("execute must be implemented by a subclass") \ No newline at end of file diff --git a/src/use_cases/interfaces/base_output_port.py b/src/use_cases/interfaces/base_output_port.py new file mode 100644 index 0000000..5dc5848 --- /dev/null +++ b/src/use_cases/interfaces/base_output_port.py @@ -0,0 +1,6 @@ +class BaseOutputPort: + def present(self, output_data): + """ + Abstract method to present use case output. + """ + raise NotImplementedError("present must be implemented by a subclass") \ No newline at end of file diff --git a/src/use_cases/interfaces/base_repository.py b/src/use_cases/interfaces/base_repository.py new file mode 100644 index 0000000..a49804f --- /dev/null +++ b/src/use_cases/interfaces/base_repository.py @@ -0,0 +1,2 @@ +class BaseRepository: + pass \ No newline at end of file diff --git a/src/use_cases/cart_repository.py b/src/use_cases/interfaces/cart_repository.py similarity index 79% rename from src/use_cases/cart_repository.py rename to src/use_cases/interfaces/cart_repository.py index 1503235..54abece 100644 --- a/src/use_cases/cart_repository.py +++ b/src/use_cases/interfaces/cart_repository.py @@ -2,8 +2,9 @@ from typing import Optional, List #dependency imports from entities import Cart +from .base_repository import BaseRepository -class ICartRepository: +class ICartRepository(BaseRepository): def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]: """ Abstract method to fetch cart data by user ID. diff --git a/src/use_cases/product_repository.py b/src/use_cases/interfaces/product_repository.py similarity index 100% rename from src/use_cases/product_repository.py rename to src/use_cases/interfaces/product_repository.py diff --git a/src/use_cases/user_repository.py b/src/use_cases/interfaces/user_repository.py similarity index 100% rename from src/use_cases/user_repository.py rename to src/use_cases/interfaces/user_repository.py diff --git a/src/use_cases/view_cart.py b/src/use_cases/view_cart.py deleted file mode 100644 index 9f7008b..0000000 --- a/src/use_cases/view_cart.py +++ /dev/null @@ -1,20 +0,0 @@ -#python imports -from typing import Optional - -#dependency imports -from entities import Cart, CartItem -from .cart_repository import ICartRepository - -class ViewCart: - def __init__(self, cart_repository: ICartRepository): - self.cart_repository = cart_repository - - def execute(self, user_id: int) -> Optional[Cart]: - """ - Fetches the cart data from the repository. - """ - cart_data = self.cart_repository.get_cart_by_user_id(user_id) - if not cart_data: - return None - - return cart_data \ No newline at end of file