Merge branch 'layer_framework_driver' of https://gitty.informatik.hs-mannheim.de/3016498/IWS_WS24_clean_architecture into layer_framework_driver

uebung_entities
Felix Jan Michael Mucha 2025-01-17 13:48:14 +01:00
commit 3b8c48f5cf
14 changed files with 258 additions and 14 deletions

View File

@ -0,0 +1,3 @@
# UI
run with: python -m http.server -b localhost 8001 -d ./

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Webshop</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
header {
background: #f4f4f4;
padding: 10px;
display: flex;
justify-content: space-around;
}
header a {
text-decoration: none;
color: #333;
font-weight: bold;
cursor: pointer;
}
#main {
padding: 20px;
}
</style>
</head>
<body>
<header>
<a id="view-products" href="#">Products</a>
<a id="view-cart" href="#">Cart</a>
<a id="view-user" href="#">User</a>
</header>
<main id="main"></main>
<script src="./src/view.js"></script>
<script src="./src/model.js"></script>
<script src="./src/controller.js"></script>
</body>
</html>

View File

@ -0,0 +1,56 @@
// Controller: Handles user interactions
const Controller = {
async showProducts() {
const products = await Model.fetchProducts();
View.renderProducts(products);
Controller.addProductClickHandlers();
},
async showCart() {
const userId = 1; // Hardcoded for simplicity
const cart = await Model.fetchCart(userId);
View.renderCart(cart);
},
async showProduct(productId) {
const product = await Model.fetchProduct(productId);
View.renderProduct(product);
document.getElementById('back-button').addEventListener('click', () => {
Controller.showProducts();
});
},
addEventListeners() {
document.getElementById('view-products').addEventListener('click', (event) => {
event.preventDefault();
Controller.showProducts();
});
document.getElementById('view-cart').addEventListener('click', (event) => {
event.preventDefault();
Controller.showCart();
});
document.getElementById('view-user').addEventListener('click', (event) => {
event.preventDefault();
alert('User profile functionality not implemented.');
});
},
addProductClickHandlers() {
document.querySelectorAll('.product-link').forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
const productId = event.target.getAttribute('data-id');
Controller.showProduct(productId);
});
});
},
init() {
this.addEventListeners();
this.showProducts(); // Default view
},
};
// Initialize the application
Controller.init();

View File

@ -0,0 +1,29 @@
// Model: Handles data fetching
const Model = {
async fetchProducts() {
const response = await fetch('http://127.0.0.1:8000/view_products');
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) {
const response = await fetch(`http://127.0.0.1:8000/view_product/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ product_id: productId }),
});
return response.json();
},
};

View File

@ -0,0 +1,51 @@
// View: Handles rendering
const View = {
renderProducts(products) {
var products = products.products;
const main = document.getElementById('main');
main.innerHTML = `
<h1>Products</h1>
<ul>
${products.map(product => View.renderSingleProduct(product)).join('')}
</ul>
`;
},
renderSingleProduct(product) {
return `
<li>
<a href="#" data-id="${product.id}" class="product-link">
${product.name} - $${product.price}
</a>
</li>
`;
},
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) {
var product = product.product;
const main = document.getElementById('main');
main.innerHTML = `
<h1>${product.name}</h1>
<p>Price: $${product.price}</p>
<p>Description: ${product.description}</p>
<button id="back-button">Back to Products</button>
`;
},
};

View File

@ -8,13 +8,14 @@ from fastapi.templating import Jinja2Templates
import httpx
#dependency imports
from use_cases import ViewProduct
from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ProductDTO
from use_cases import ViewProduct, ViewAllProducts
from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ViewAllProductsResponseDTO, ProductDTO
from interface_adapters.repositories import SQLProductRepository
# Initialize components
product_repository = SQLProductRepository()
view_product_use_case = ViewProduct(product_repository)
view_all_products_use_case = ViewAllProducts(product_repository)
router = APIRouter()
@ -31,3 +32,17 @@ async def view_product(request_dto: ViewProductRequestDTO):
)
return response_dto
@router.get("/view_products", response_model=ViewAllProductsResponseDTO)
async def view_products():
products = view_all_products_use_case.execute()
if not products:
raise HTTPException(status_code=404, detail="Product not found")
response_dto = ViewAllProductsResponseDTO(
products=[ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price) for product in products]
)
return response_dto

View File

@ -0,0 +1,9 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
class CartItemDTO(BaseModel):
product_id: int
name: str
quantity: int
price: float

View File

@ -0,0 +1,9 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
class ProductDTO(BaseModel):
id: int
name: str
description: str
price: float

View File

@ -0,0 +1,8 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
from .product_dto import ProductDTO
class ViewAllProductsResponseDTO(BaseModel):
products: List[ProductDTO] = []

View File

@ -2,11 +2,8 @@
from pydantic import BaseModel
from typing import List, Optional
class CartItemDTO(BaseModel):
product_id: int
name: str
quantity: int
price: float
from .cart_item_dto import CartItemDTO
class ViewCartResponseDTO(BaseModel):
items: List[CartItemDTO] = []

View File

@ -2,11 +2,7 @@
from pydantic import BaseModel
from typing import List, Optional
class ProductDTO(BaseModel):
id: int
name: str
description: str
price: float
from .product_dto import ProductDTO
class ViewProductResponseDTO(BaseModel):
product: ProductDTO

View File

@ -39,7 +39,8 @@ class SQLProductRepository(IProductRepository):
try:
c = self.conn.cursor()
c.execute(sql)
return c.fetchall()
rows = c.fetchall()
return [{'id':r[0], 'name':str(r[1]), 'description':str(r[2]), 'price':r[3]} for r in rows]
except sqlite3.Error as e:
print(e)
return []

View File

@ -3,6 +3,12 @@ from typing import Optional, List
class IProductRepository:
def get_product_by_id(self, product_id: int):
"""
Abstract method to fetch product data by user ID.
Abstract method to fetch product data by ID.
"""
raise NotImplementedError("get_product_by_id must be implemented by a subclass")
def get_all_products(self):
"""
Abstract method to fetch product data.
"""
raise NotImplementedError("get_all_products must be implemented by a subclass")

View File

@ -0,0 +1,20 @@
#python imports
from typing import Optional
#dependency imports
from entities import Product
from .product_repository import IProductRepository
class ViewAllProducts:
def __init__(self, product_repository: IProductRepository):
self.product_repository = product_repository
def execute(self) -> Optional[Product]:
"""
Fetches the product data from the repository, converts it to Product entity.
"""
product_data = self.product_repository.get_all_products()
if not product_data:
return None
products = [Product(**product) for product in product_data]
return products