Merge branch 'layer_framework_driver' of https://gitty.informatik.hs-mannheim.de/3016498/IWS_WS24_clean_architecture into layer_framework_driver
commit
3b8c48f5cf
|
|
@ -0,0 +1,3 @@
|
|||
# UI
|
||||
|
||||
run with: python -m http.server -b localhost 8001 -d ./
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
|
@ -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>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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] = []
|
||||
|
|
@ -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] = []
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue