remove any cart stuff. add port interfaces for products
parent
5c84416fe0
commit
d8ca863c8c
|
|
@ -1,5 +1,5 @@
|
||||||
# ENTITIES
|
# ENTITIES
|
||||||
Entities encapsulate enterprise-wide Critical Business Rules. An entity can be an
|
"Entities encapsulate enterprise-wide Critical Business Rules. An entity can be an
|
||||||
object with methods, or it can be a set of data structures and functions. It doesn’t
|
object with methods, or it can be a set of data structures and functions. It doesn’t
|
||||||
matter so long as the entities can be used by many different applications in the
|
matter so long as the entities can be used by many different applications in the
|
||||||
enterprise.
|
enterprise.
|
||||||
|
|
@ -8,4 +8,23 @@ entities are the business objects of the application. They encapsulate the most
|
||||||
and high-level rules. They are the least likely to change when something external
|
and high-level rules. They are the least likely to change when something external
|
||||||
changes. For example, you would not expect these objects to be affected by a change
|
changes. For example, you would not expect these objects to be affected by a change
|
||||||
to page navigation or security. No operational change to any particular application
|
to page navigation or security. No operational change to any particular application
|
||||||
should affect the entity layer.
|
should affect the entity layer." -- Robert C. Martin, Clean Architecture
|
||||||
|
|
||||||
|
# Aufgabe
|
||||||
|
|
||||||
|
### Anforderungen:
|
||||||
|
|
||||||
|
#### Attribute:
|
||||||
|
- **name**: Der Name des Benutzers.
|
||||||
|
- **email**: Die E-Mail-Adresse des Benutzers.
|
||||||
|
- **password**: Das Passwort des Benutzers.
|
||||||
|
- **id**: Eine eindeutige ID, die den Benutzer identifiziert.
|
||||||
|
|
||||||
|
#### Getter und Setter Methoden:
|
||||||
|
- Implementiere für jedes Attribut (`name`, `email`, `password`, `id`) Getter- und Setter-Methoden. Diese Methoden sollten es ermöglichen, die Werte der Attribute abzurufen und zu ändern.
|
||||||
|
|
||||||
|
#### Datenvalidierung (optional):
|
||||||
|
- Validiere die Eingabedaten, z.B. sicherstellen, dass die E-Mail-Adresse ein gültiges Format hat und dass das Passwort eine Mindestlänge aufweist.
|
||||||
|
|
||||||
|
#### Konstruktor:
|
||||||
|
- Erstelle einen Konstruktor, der die Attribute `name`, `email`, `password` und `id` beim Erstellen eines Benutzerobjekts initialisiert.
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#python imports
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
#dependency imports
|
|
||||||
from .cart_item import CartItem
|
|
||||||
from .product import Product
|
|
||||||
|
|
||||||
class Cart:
|
|
||||||
"""
|
|
||||||
Represents a shopping cart containing multiple cart items.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
Initializes a new shopping cart.
|
|
||||||
"""
|
|
||||||
self.items: List[CartItem] = []
|
|
||||||
self.total_price: float = 0.0
|
|
||||||
|
|
||||||
def add_item(self, product: Product, quantity: int):
|
|
||||||
"""
|
|
||||||
Adds a product to the cart or updates the quantity if it already exists.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
product (Product): The product to add to the cart.
|
|
||||||
quantity (int): The quantity of the product to add.
|
|
||||||
"""
|
|
||||||
for item in self.items:
|
|
||||||
if item.product_id == product.id:
|
|
||||||
item.quantity += quantity
|
|
||||||
return
|
|
||||||
self.items.append(CartItem(product_id=product.id, name=product.name, quantity=quantity, price=product.price))
|
|
||||||
|
|
||||||
def remove_item(self, product_id: str):
|
|
||||||
"""
|
|
||||||
Removes a product from the cart by its ID.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
product_id (str): The unique identifier of the product to remove.
|
|
||||||
"""
|
|
||||||
self.items = [item for item in self.items if item.product_id != product_id]
|
|
||||||
|
|
||||||
def calculate_total_price(self) -> float:
|
|
||||||
"""
|
|
||||||
Calculates the total price of all items in the cart.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
float: The total price of all items in the cart.
|
|
||||||
"""
|
|
||||||
self.total_price = sum(item.calculate_total_price() for item in self.items)
|
|
||||||
return self.total_price
|
|
||||||
|
|
||||||
def list_items(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
Lists all items in the cart with their quantities and total prices.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: A list of strings representing the items in the cart.
|
|
||||||
"""
|
|
||||||
return [f"{item.quantity} x {item.name} - {item.calculate_total_price()} EUR" for item in self.items]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""
|
|
||||||
Returns a string representation of the cart.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string representation of the cart.
|
|
||||||
"""
|
|
||||||
return f"Cart(items={self.items}, total_price={self.total_price})"
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
#python imports
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
class CartItem:
|
|
||||||
"""
|
|
||||||
Represents an item in the cart with a product ID, name, quantity, and price.
|
|
||||||
"""
|
|
||||||
def __init__(self, product_id: int, name: str, quantity: int, price: float):
|
|
||||||
"""
|
|
||||||
Initializes a new cart item.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
product_id (int): The unique identifier for the product.
|
|
||||||
name (str): The name of the product.
|
|
||||||
quantity (int): The quantity of the product in the cart.
|
|
||||||
price (float): The price of the product.
|
|
||||||
"""
|
|
||||||
self.product_id = product_id
|
|
||||||
self.name = name
|
|
||||||
self.quantity = quantity
|
|
||||||
self.price = price
|
|
||||||
self.post_init()
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
"""
|
|
||||||
Validates the cart item's quantity.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the quantity is less than or equal to 0.
|
|
||||||
"""
|
|
||||||
if self.quantity <= 0:
|
|
||||||
raise ValueError("Quantity has to be at least 1")
|
|
||||||
|
|
||||||
def calculate_total_price(self) -> float:
|
|
||||||
"""
|
|
||||||
Calculates the total price of the cart item.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
float: The total price of the cart item.
|
|
||||||
"""
|
|
||||||
return self.quantity * self.price
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""
|
|
||||||
Returns a string representation of the cart item.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A string representation of the cart item.
|
|
||||||
"""
|
|
||||||
return f"CartItem(product_id={self.product_id}, name={self.name}, quantity={self.quantity}, price={self.price})"
|
|
||||||
|
|
@ -33,7 +33,6 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<a id="products-tab" href="#">Products</a>
|
<a id="products-tab" href="#">Products</a>
|
||||||
<a id="cart-tab" href="#">Cart</a>
|
|
||||||
<a id="user-tab" href="#">User</a>
|
<a id="user-tab" href="#">User</a>
|
||||||
</header>
|
</header>
|
||||||
<main id="main"></main>
|
<main id="main"></main>
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,20 @@ const Controller = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async showProducts() {
|
async showProducts() {
|
||||||
const products = await Model.fetchProducts();
|
const productsResponse = await Model.fetchProducts();
|
||||||
View.renderProducts(products);
|
if(productsResponse.status=="success"){
|
||||||
|
const products = productsResponse.data;
|
||||||
|
View.renderProducts(products);
|
||||||
|
}
|
||||||
Controller.addProductClickHandlers();
|
Controller.addProductClickHandlers();
|
||||||
},
|
},
|
||||||
|
|
||||||
async showCart() {
|
|
||||||
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) {
|
async showProduct(productId) {
|
||||||
const product = await Model.fetchProduct(productId);
|
const productResponse = await Model.fetchProduct(productId);
|
||||||
View.renderProduct(product);
|
if(productResponse.status=="success"){
|
||||||
|
const product = productResponse.data;
|
||||||
|
View.renderProduct(product);
|
||||||
|
}
|
||||||
document.getElementById('back-button').addEventListener('click', () => {
|
document.getElementById('back-button').addEventListener('click', () => {
|
||||||
Controller.showProducts();
|
Controller.showProducts();
|
||||||
});
|
});
|
||||||
|
|
@ -45,11 +42,6 @@ const Controller = {
|
||||||
Controller.showProducts();
|
Controller.showProducts();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('cart-tab').addEventListener('click', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
Controller.showCart();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('user-tab').addEventListener('click', (event) => {
|
document.getElementById('user-tab').addEventListener('click', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
Controller.showUserRegister();
|
Controller.showUserRegister();
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,6 @@ const Model = {
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchCart(userId) {
|
|
||||||
const response = await fetch('http://127.0.0.1:8000/view_cart', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ user_id: userId }),
|
|
||||||
});
|
|
||||||
return response.json();
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchProduct(productId) {
|
async fetchProduct(productId) {
|
||||||
const response = await fetch(`http://127.0.0.1:8000/view_product/`, {
|
const response = await fetch(`http://127.0.0.1:8000/view_product/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// View: Handles rendering
|
// View: Handles rendering
|
||||||
const View = {
|
const View = {
|
||||||
renderProducts(products) {
|
renderProducts(products) {
|
||||||
var products = products.products;
|
|
||||||
const main = document.getElementById('main');
|
const main = document.getElementById('main');
|
||||||
main.innerHTML = `
|
main.innerHTML = `
|
||||||
<h1>Products</h1>
|
<h1>Products</h1>
|
||||||
|
|
@ -21,25 +20,7 @@ const View = {
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderCart(cart) {
|
|
||||||
var cartItems = cart.items;
|
|
||||||
const main = document.getElementById('main');
|
|
||||||
main.innerHTML = `
|
|
||||||
<h1>Your Cart</h1>
|
|
||||||
<ul>
|
|
||||||
${cartItems
|
|
||||||
.map(
|
|
||||||
(item) => `
|
|
||||||
<li>${item.name} - $${item.price} (Quantity: ${item.quantity})</li>`
|
|
||||||
)
|
|
||||||
.join('')}
|
|
||||||
</ul>
|
|
||||||
<p>Total: $${cart.total_price}</p>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderProduct(product) {
|
renderProduct(product) {
|
||||||
var product = product.product;
|
|
||||||
const main = document.getElementById('main');
|
const main = document.getElementById('main');
|
||||||
main.innerHTML = `
|
main.innerHTML = `
|
||||||
<h1>${product.name}</h1>
|
<h1>${product.name}</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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))
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
#python imports
|
|
||||||
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.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
|
|
||||||
|
|
||||||
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):
|
|
||||||
|
|
||||||
cart = view_cart_use_case.execute(request_dto.user_id)
|
|
||||||
|
|
||||||
if not cart:
|
|
||||||
raise HTTPException(status_code=404, detail="Cart not found")
|
|
||||||
|
|
||||||
response_dto = ViewCartResponseDTO(
|
|
||||||
items=[CartItemDTO(product_id=item.product_id, name=item.name, quantity=item.quantity, price=item.price) for item in cart.items],
|
|
||||||
total_price=sum(item.price * item.quantity for item in cart.items)
|
|
||||||
)
|
|
||||||
|
|
||||||
return response_dto
|
|
||||||
"""
|
|
||||||
|
|
@ -1,46 +1,76 @@
|
||||||
#python imports
|
#python imports
|
||||||
|
from typing import List
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
#dependency imports
|
#dependency imports
|
||||||
|
from entities import Product
|
||||||
from use_cases.interactors import ViewProduct, ViewAllProducts
|
from use_cases.interactors import ViewProduct, ViewAllProducts
|
||||||
from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ViewAllProductsResponseDTO, ProductDTO
|
from use_cases.interfaces.ports import ViewProductInputPort, ViewAllProductsInputPort, ViewProductOutputPort, ViewAllProductsOutputPort
|
||||||
|
from interface_adapters.dtos import ProductDTO
|
||||||
from interface_adapters.repositories import SQLProductRepository
|
from interface_adapters.repositories import SQLProductRepository
|
||||||
|
|
||||||
# Initialize components
|
|
||||||
|
class ViewProductRequest(BaseModel):
|
||||||
|
product_id: int
|
||||||
|
|
||||||
|
class ViewProductResponse(BaseModel):
|
||||||
|
status: str
|
||||||
|
data: ProductDTO
|
||||||
|
|
||||||
|
class ViewProductPresenter(ViewProductOutputPort):
|
||||||
|
def present(self, product:Product) -> ViewProductResponse:
|
||||||
|
"""Format the response data for API."""
|
||||||
|
product_dto=ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price)
|
||||||
|
response = ViewProductResponse(status="success",data=product_dto)
|
||||||
|
return response
|
||||||
|
|
||||||
|
class ViewProductController:
|
||||||
|
def __init__(self, input_port: ViewProductInputPort) -> None:
|
||||||
|
self.input_port = input_port
|
||||||
|
|
||||||
|
def handle_request(self, request:ViewProductRequest):
|
||||||
|
"""Handles the incoming API request and delegates to the use case."""
|
||||||
|
return self.input_port.execute(request.product_id)
|
||||||
|
|
||||||
product_repository = SQLProductRepository()
|
product_repository = SQLProductRepository()
|
||||||
view_product_use_case = ViewProduct(product_repository)
|
view_product_presenter = ViewProductPresenter()
|
||||||
view_all_products_use_case = ViewAllProducts(product_repository)
|
view_product_use_case = ViewProduct(product_repository, view_product_presenter)
|
||||||
|
view_product_controller = ViewProductController(view_product_use_case)
|
||||||
|
|
||||||
|
|
||||||
|
class ViewAllProductsResponse(BaseModel):
|
||||||
|
status: str
|
||||||
|
data: List[ProductDTO]
|
||||||
|
|
||||||
|
class ViewAllProductsPresenter(ViewAllProductsOutputPort):
|
||||||
|
def present(self, products:List[Product]) -> ViewAllProductsResponse:
|
||||||
|
"""Format the response data for API."""
|
||||||
|
products_dto=[ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price) for product in products]
|
||||||
|
response = ViewAllProductsResponse(status="success",data=products_dto)
|
||||||
|
return response
|
||||||
|
|
||||||
|
class ViewAllProductsController:
|
||||||
|
def __init__(self, input_port: ViewAllProductsInputPort) -> None:
|
||||||
|
self.input_port = input_port
|
||||||
|
|
||||||
|
def handle_request(self):
|
||||||
|
"""Handles the incoming API request and delegates to the use case."""
|
||||||
|
return self.input_port.execute()
|
||||||
|
|
||||||
|
view_all_products_presenter = ViewAllProductsPresenter()
|
||||||
|
view_all_products_use_case = ViewAllProducts(product_repository, view_all_products_presenter)
|
||||||
|
view_all_products_controller = ViewAllProductsController(view_all_products_use_case)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@router.post("/view_product", response_model=ViewProductResponseDTO)
|
@router.post("/view_product", response_model=ViewProductResponse)
|
||||||
async def view_product(request_dto: ViewProductRequestDTO):
|
async def view_product(request: ViewProductRequest):
|
||||||
|
return view_product_controller.handle_request(request)
|
||||||
|
|
||||||
product = view_product_use_case.execute(request_dto.product_id)
|
@router.get("/view_products", response_model=ViewAllProductsResponse)
|
||||||
|
|
||||||
if not product:
|
|
||||||
raise HTTPException(status_code=404, detail="Product not found")
|
|
||||||
|
|
||||||
response_dto = ViewProductResponseDTO(
|
|
||||||
product=ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price)
|
|
||||||
)
|
|
||||||
|
|
||||||
return response_dto
|
|
||||||
|
|
||||||
@router.get("/view_products", response_model=ViewAllProductsResponseDTO)
|
|
||||||
async def view_products():
|
async def view_products():
|
||||||
|
return view_all_products_controller.handle_request()
|
||||||
products = view_all_products_use_case.execute()
|
|
||||||
|
|
||||||
if not products:
|
|
||||||
raise HTTPException(status_code=404, detail="Product not found")
|
|
||||||
for product in products:
|
|
||||||
print(product)
|
|
||||||
response_dto = ViewAllProductsResponseDTO(
|
|
||||||
products=[ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price) for product in products]
|
|
||||||
)
|
|
||||||
|
|
||||||
return response_dto
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
#python imports
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
class CartItemDTO(BaseModel):
|
|
||||||
product_id: int
|
|
||||||
name: str
|
|
||||||
quantity: int
|
|
||||||
price: float
|
|
||||||
|
|
||||||
class CartDTO(BaseModel):
|
|
||||||
items: List[CartItemDTO]
|
|
||||||
total_price: float
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#python imports
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from .product_dto import ProductDTO
|
|
||||||
|
|
||||||
class ViewAllProductsResponseDTO(BaseModel):
|
|
||||||
products: List[ProductDTO] = []
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#python imports
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
class ViewCartRequestDTO(BaseModel):
|
|
||||||
user_id: int
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#python imports
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from .cart_dto import CartItemDTO
|
|
||||||
|
|
||||||
|
|
||||||
class ViewCartResponseDTO(BaseModel):
|
|
||||||
items: List[CartItemDTO] = []
|
|
||||||
total_price: float = 0.0
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#python imports
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
class ViewProductRequestDTO(BaseModel):
|
|
||||||
product_id: int
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#python imports
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from .product_dto import ProductDTO
|
|
||||||
|
|
||||||
class ViewProductResponseDTO(BaseModel):
|
|
||||||
product: ProductDTO
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
#python imports
|
|
||||||
from typing import Optional, List
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
#dependency imports
|
|
||||||
from use_cases.interfaces import ICartRepository
|
|
||||||
from entities import Cart, CartItem
|
|
||||||
|
|
||||||
class SQLCartRepository(ICartRepository):
|
|
||||||
def __init__(self, db_file="framework_driver/db/shop.db"):
|
|
||||||
"""Initialize the CartDatabase with a connection to the SQLite database."""
|
|
||||||
self.conn = sqlite3.connect(db_file)
|
|
||||||
self.conn.row_factory = sqlite3.Row
|
|
||||||
|
|
||||||
def add_to_cart(self, user_id, product_id, name, quantity, price):
|
|
||||||
"""Add a product to the user's cart."""
|
|
||||||
sql = "INSERT INTO cart (user_id, product_id, name, quantity, price) VALUES (?, ?, ?, ?, ?)"
|
|
||||||
try:
|
|
||||||
c = self.conn.cursor()
|
|
||||||
c.execute(sql, (user_id, product_id, name, quantity, price))
|
|
||||||
self.conn.commit()
|
|
||||||
return c.lastrowid
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]:
|
|
||||||
"""Fetch cart items from the database for a given user ID."""
|
|
||||||
sql = "SELECT product_id, name, quantity, price FROM cart WHERE user_id = ?"
|
|
||||||
try:
|
|
||||||
c = self.conn.cursor()
|
|
||||||
c.execute(sql, (user_id,))
|
|
||||||
rows = c.fetchall()
|
|
||||||
return Cart([CartItem(**item) for item in rows])
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(e)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update_cart_item(self, user_id, product_id, quantity):
|
|
||||||
"""Update the quantity of a specific cart item based on user_id and product_id."""
|
|
||||||
sql = "UPDATE cart SET quantity = ? WHERE user_id = ? AND product_id = ?"
|
|
||||||
try:
|
|
||||||
c = self.conn.cursor()
|
|
||||||
c.execute(sql, (quantity, user_id, product_id))
|
|
||||||
self.conn.commit()
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
def remove_from_cart(self, cart_id):
|
|
||||||
"""Remove a specific item from the cart."""
|
|
||||||
sql = "DELETE FROM cart WHERE id = ?"
|
|
||||||
try:
|
|
||||||
c = self.conn.cursor()
|
|
||||||
c.execute(sql, (cart_id,))
|
|
||||||
self.conn.commit()
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
def clear_cart(self, user_id):
|
|
||||||
"""Clear all items from a user's cart."""
|
|
||||||
sql = "DELETE FROM cart WHERE user_id = ?"
|
|
||||||
try:
|
|
||||||
c = self.conn.cursor()
|
|
||||||
c.execute(sql, (user_id,))
|
|
||||||
self.conn.commit()
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
database = "framework_driver/db/shop.db"
|
|
||||||
|
|
||||||
cart_db = SQLCartRepository(database)
|
|
||||||
|
|
||||||
ex_user_id = 1
|
|
||||||
|
|
||||||
# delete all cart items
|
|
||||||
cart_db.clear_cart(ex_user_id)
|
|
||||||
|
|
||||||
# user id, product id, name, quantity, price
|
|
||||||
cart_db.add_to_cart(ex_user_id, 1, "Unsichtbare Tinte", 2, 3.0)
|
|
||||||
cart_db.add_to_cart(ex_user_id, 3, "Geräuschloser Wecker", 5, 15.0)
|
|
||||||
print(f"Cart items for user {ex_user_id}:", cart_db.get_cart_by_user_id(ex_user_id))
|
|
||||||
|
|
||||||
# user id, product id, quantity
|
|
||||||
cart_db.update_cart_item(ex_user_id, 2, 3)
|
|
||||||
print(f"Cart items for user {ex_user_id}:", cart_db.get_cart_by_user_id(ex_user_id))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -3,7 +3,7 @@ from typing import Optional, List
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
#dependency imports
|
#dependency imports
|
||||||
from use_cases.interfaces import IProductRepository
|
from use_cases.interfaces.repositories import IProductRepository
|
||||||
from entities import Product
|
from entities import Product
|
||||||
|
|
||||||
class SQLProductRepository(IProductRepository):
|
class SQLProductRepository(IProductRepository):
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from typing import Optional, List
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
#dependency imports
|
#dependency imports
|
||||||
from use_cases.interfaces import IUserRepository
|
from use_cases.interfaces.repositories import IUserRepository
|
||||||
from entities import User
|
from entities import User
|
||||||
|
|
||||||
class SQLUserRepository(IUserRepository):
|
class SQLUserRepository(IUserRepository):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from interface_adapters.controllers.cart_controller import router as cart_router
|
|
||||||
from interface_adapters.controllers.product_controller import router as product_router
|
from interface_adapters.controllers.product_controller import router as product_router
|
||||||
from interface_adapters.controllers.user_controller import router as user_router
|
from interface_adapters.controllers.user_controller import router as user_router
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
@ -14,8 +13,8 @@ import os
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(cart_router)
|
|
||||||
app.include_router(product_router)
|
app.include_router(product_router)
|
||||||
|
app.include_router(user_router)
|
||||||
|
|
||||||
# Allow CORS for all origins (for simplicity)
|
# Allow CORS for all origins (for simplicity)
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# USE CASES
|
# USE CASES
|
||||||
The software in the use cases layer contains application-specific business rules. It
|
"The software in the use cases layer contains application-specific business rules. It
|
||||||
encapsulates and implements all of the use cases of the system. These use cases
|
encapsulates and implements all of the use cases of the system. These use cases
|
||||||
orchestrate the flow of data to and from the entities, and direct those entities to use
|
orchestrate the flow of data to and from the entities, and direct those entities to use
|
||||||
their Critical Business Rules to achieve the goals of the use case.
|
their Critical Business Rules to achieve the goals of the use case.
|
||||||
|
|
@ -8,4 +8,6 @@ this layer to be affected by changes to externalities such as the database, the
|
||||||
any of the common frameworks. The use cases layer is isolated from such concerns.
|
any of the common frameworks. The use cases layer is isolated from such concerns.
|
||||||
We do, however, expect that changes to the operation of the application will affect
|
We do, however, expect that changes to the operation of the application will affect
|
||||||
the use cases and, therefore, the software in this layer. If the details of a use case
|
the use cases and, therefore, the software in this layer. If the details of a use case
|
||||||
change, then some code in this layer will certainly be affected.
|
change, then some code in this layer will certainly be affected." -- Robert C. Martin, Clean Architecture
|
||||||
|
|
||||||
|
# Aufgabe
|
||||||
|
|
@ -3,11 +3,13 @@ from typing import Optional, List
|
||||||
|
|
||||||
#dependency imports
|
#dependency imports
|
||||||
from entities import Product
|
from entities import Product
|
||||||
from use_cases.interfaces.product_repository import IProductRepository
|
from use_cases.interfaces.repositories import IProductRepository
|
||||||
|
from use_cases.interfaces.ports import ViewAllProductsInputPort, ViewAllProductsOutputPort
|
||||||
|
|
||||||
class ViewAllProducts:
|
class ViewAllProducts(ViewAllProductsInputPort):
|
||||||
def __init__(self, product_repository: IProductRepository):
|
def __init__(self, product_repository: IProductRepository, output_port: ViewAllProductsOutputPort):
|
||||||
self.product_repository = product_repository
|
self.product_repository = product_repository
|
||||||
|
self.output_port = output_port
|
||||||
|
|
||||||
def execute(self) -> Optional[List[Product]]:
|
def execute(self) -> Optional[List[Product]]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -16,4 +18,4 @@ class ViewAllProducts:
|
||||||
product_data = self.product_repository.get_all_products()
|
product_data = self.product_repository.get_all_products()
|
||||||
if not product_data:
|
if not product_data:
|
||||||
return None
|
return None
|
||||||
return product_data
|
return self.output_port.present(product_data)
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#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)
|
|
||||||
|
|
@ -3,11 +3,13 @@ from typing import Optional
|
||||||
|
|
||||||
#dependency imports
|
#dependency imports
|
||||||
from entities import Product
|
from entities import Product
|
||||||
from use_cases.interfaces.product_repository import IProductRepository
|
from use_cases.interfaces.repositories import IProductRepository
|
||||||
|
from use_cases.interfaces.ports import ViewProductInputPort, ViewProductOutputPort
|
||||||
|
|
||||||
class ViewProduct:
|
class ViewProduct(ViewProductInputPort):
|
||||||
def __init__(self, product_repository: IProductRepository):
|
def __init__(self, product_repository: IProductRepository, output_port: ViewProductOutputPort):
|
||||||
self.product_repository = product_repository
|
self.product_repository = product_repository
|
||||||
|
self.output_port = output_port
|
||||||
|
|
||||||
def execute(self, product_id: int) -> Optional[Product]:
|
def execute(self, product_id: int) -> Optional[Product]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -17,4 +19,4 @@ class ViewProduct:
|
||||||
if not product_data:
|
if not product_data:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return product_data
|
return self.output_port.present(product_data)
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#python imports
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
#dependency imports
|
|
||||||
#TODO: use correct dependencies
|
|
||||||
|
|
||||||
class ViewUser:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
#TODO: initialize required Interfaces
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
pass
|
|
||||||
#TODO: implement
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
class BaseOutputPort:
|
|
||||||
def present(self, output_data):
|
|
||||||
"""
|
|
||||||
Abstract method to present use case output.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("present must be implemented by a subclass")
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
class BaseRepository:
|
|
||||||
pass
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
from typing import Optional, List
|
|
||||||
|
|
||||||
#dependency imports
|
|
||||||
from entities import Cart
|
|
||||||
from .base_repository import BaseRepository
|
|
||||||
|
|
||||||
class ICartRepository(BaseRepository):
|
|
||||||
def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]:
|
|
||||||
"""
|
|
||||||
Abstract method to fetch cart data by user ID.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("get_cart_by_user_id must be implemented by a subclass")
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
class BaseInputPort:
|
class ViewAllProductsInputPort:
|
||||||
def execute(self, input_data):
|
def execute(self):
|
||||||
"""
|
"""
|
||||||
Abstract method to execute use case.
|
Abstract method to execute use case.
|
||||||
"""
|
"""
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
from entities import Product
|
||||||
|
from typing import List
|
||||||
|
class ViewAllProductsOutputPort:
|
||||||
|
def present(self, product_list:List[Product]):
|
||||||
|
"""
|
||||||
|
Abstract method to execute use case.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("execute must be implemented by a subclass")
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
class ViewProductInputPort:
|
||||||
|
def execute(self, product_id:int):
|
||||||
|
"""
|
||||||
|
Abstract method to execute use case.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("execute must be implemented by a subclass")
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
from entities import Product
|
||||||
|
class ViewProductOutputPort:
|
||||||
|
def present(self, product:Product):
|
||||||
|
"""
|
||||||
|
Abstract method to execute use case.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("execute must be implemented by a subclass")
|
||||||
|
|
@ -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)
|
||||||
Loading…
Reference in New Issue