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
|
import httpx
|
||||||
|
|
||||||
#dependency imports
|
#dependency imports
|
||||||
from use_cases import ViewProduct
|
from use_cases import ViewProduct, ViewAllProducts
|
||||||
from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ProductDTO
|
from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ViewAllProductsResponseDTO, ProductDTO
|
||||||
from interface_adapters.repositories import SQLProductRepository
|
from interface_adapters.repositories import SQLProductRepository
|
||||||
|
|
||||||
# Initialize components
|
# Initialize components
|
||||||
product_repository = SQLProductRepository()
|
product_repository = SQLProductRepository()
|
||||||
view_product_use_case = ViewProduct(product_repository)
|
view_product_use_case = ViewProduct(product_repository)
|
||||||
|
view_all_products_use_case = ViewAllProducts(product_repository)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
@ -31,3 +32,17 @@ async def view_product(request_dto: ViewProductRequestDTO):
|
||||||
)
|
)
|
||||||
|
|
||||||
return response_dto
|
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 pydantic import BaseModel
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
class CartItemDTO(BaseModel):
|
from .cart_item_dto import CartItemDTO
|
||||||
product_id: int
|
|
||||||
name: str
|
|
||||||
quantity: int
|
|
||||||
price: float
|
|
||||||
|
|
||||||
class ViewCartResponseDTO(BaseModel):
|
class ViewCartResponseDTO(BaseModel):
|
||||||
items: List[CartItemDTO] = []
|
items: List[CartItemDTO] = []
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,7 @@
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
class ProductDTO(BaseModel):
|
from .product_dto import ProductDTO
|
||||||
id: int
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
price: float
|
|
||||||
|
|
||||||
class ViewProductResponseDTO(BaseModel):
|
class ViewProductResponseDTO(BaseModel):
|
||||||
product: ProductDTO
|
product: ProductDTO
|
||||||
|
|
@ -39,7 +39,8 @@ class SQLProductRepository(IProductRepository):
|
||||||
try:
|
try:
|
||||||
c = self.conn.cursor()
|
c = self.conn.cursor()
|
||||||
c.execute(sql)
|
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:
|
except sqlite3.Error as e:
|
||||||
print(e)
|
print(e)
|
||||||
return []
|
return []
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@ from typing import Optional, List
|
||||||
class IProductRepository:
|
class IProductRepository:
|
||||||
def get_product_by_id(self, product_id: int):
|
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")
|
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