* convert from db object to entity in repo implementation instead of usecase
* add description as column to product db object
* remove old ui stuff
* return dict instead of tuple in sql
* add TODOs which should be implemented
uebung_entities
michael 2025-01-27 14:00:51 +01:00
parent 7bc1345d60
commit 821ef8a8d4
26 changed files with 114 additions and 207 deletions

View File

@ -7,6 +7,7 @@ class Product:
name: str name: str
description: str description: str
price: float price: float
quantity: int
def post_init(self): def post_init(self):
if self.price < 0: if self.price < 0:

View File

@ -0,0 +1,7 @@
#python imports
from dataclasses import dataclass
@dataclass
class User:
pass
#TODO: implement

View File

@ -41,6 +41,7 @@ def main():
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
price REAL NOT NULL, price REAL NOT NULL,
description TEXT NOT NULL,
quantity INTEGER NOT NULL quantity INTEGER NOT NULL
)""" )"""

Binary file not shown.

View File

@ -35,6 +35,14 @@ const Controller = {
event.preventDefault(); event.preventDefault();
alert('User profile functionality not implemented.'); alert('User profile functionality not implemented.');
}); });
document.querySelectorAll('.product-link').forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
const productId = event.target.getAttribute('data-id');
Controller.showProduct(productId);
});
});
}, },
addProductClickHandlers() { addProductClickHandlers() {

View File

@ -1,65 +0,0 @@
body {
font-family: Arial, sans-serif;
}
header {
background-color: #f8f9fa;
padding: 1rem;
text-align: center;
}
main {
padding: 1rem;
}
.btn-primary {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 4px;
text-align: center;
text-decoration: none;
cursor: pointer;
}
.btn-primary:hover {
background-color: #0056b3;
}
.product-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.product-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #ddd;
}
/* Styling der Produktnamen, -mengen und -preise */
.product-name {
font-weight: bold;
}
.product-quantity {
color: #555;
}
.product-price {
color: #555;
}
/* Styling für den Gesamtpreis */
.total-price {
font-size: 18px;
font-weight: bold;
margin-top: 20px;
}

View File

@ -1,3 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
console.log('JavaScript is loaded');
});

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Shop{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('custom_static', filename='css/style.css') }}">
</head>
<body>
<header>
<h1>Shop</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
<script src="{{ url_for('custom_static', filename='js/script.js') }}"></script>
</body>
</html>

View File

@ -1,22 +0,0 @@
{% extends "base.html" %}
{% block title %}Cart{% endblock %}
{% block content %}
<h2>Cart Items</h2>
<ul class="product-list">
{% for item in cart_items['items'] %}
<li class="product-item">
<div class="product-name">Product ID: {{ item.product_id }}</div>
<div class="product-quantity">Quantity: {{ item.quantity }}</div>
<div class="product-price">Price: {{ item.price }} €</div>
</li>
{% endfor %}
</ul>
<p class="total-price">Total Price: {{ cart_items.total_price }} €</p>
<a href="{{ url_for('ui') }}" class="btn btn-primary">Back to the prodcuts</a>
{% endblock %}

View File

@ -1,18 +0,0 @@
{% extends "base.html" %}
{% block title %}Products{% endblock %}
{% block content %}
<h2>Product Items</h2>
<ul class="product-list">
{% for product in products %}
<li class="product-item">
<div class="product-name">{{ product.name }}</div>
<div class="product-price">${{ product.price }}</div>
</li>
{% endfor %}
</ul>
<a href="{{ url_for('ui_view_cart') }}" class="btn btn-primary">Go to Cart</a>
{% endblock %}

View File

@ -3,18 +3,12 @@ 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 fastapi.templating import Jinja2Templates
import httpx
#dependency imports #dependency imports
from use_cases import ViewCart from use_cases import ViewCart
from interface_adapters.dtos import ViewCartRequestDTO, ViewCartResponseDTO, CartItemDTO from interface_adapters.dtos import ViewCartRequestDTO, ViewCartResponseDTO, CartItemDTO
from interface_adapters.repositories import SQLCartRepository from interface_adapters.repositories import SQLCartRepository
# Set up templates
templates = Jinja2Templates(directory="framework_driver/ui/templates")
# Initialize components # Initialize components
cart_repository = SQLCartRepository() cart_repository = SQLCartRepository()
view_cart_use_case = ViewCart(cart_repository) view_cart_use_case = ViewCart(cart_repository)
@ -36,14 +30,3 @@ async def view_cart(request_dto: ViewCartRequestDTO):
return response_dto return response_dto
@router.get("/ui/view_cart", response_class=HTMLResponse, name="ui_view_cart")
async def ui_view_cart(request: Request, user_id: int=1):
request_dto = {"user_id": user_id}
async with httpx.AsyncClient() as client:
response = await client.post("http://127.0.0.1:8000/view_cart", json=request_dto)
response.raise_for_status() # Raise an exception for HTTP errors
response_dto = response.json()
return templates.TemplateResponse("cart.html", {"request": request, "cart_items": response_dto})

View File

@ -3,9 +3,6 @@ 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 fastapi.templating import Jinja2Templates
import httpx
#dependency imports #dependency imports
from use_cases import ViewProduct, ViewAllProducts from use_cases import ViewProduct, ViewAllProducts
@ -40,7 +37,8 @@ async def view_products():
if not products: if not products:
raise HTTPException(status_code=404, detail="Product not found") raise HTTPException(status_code=404, detail="Product not found")
for product in products:
print(product)
response_dto = ViewAllProductsResponseDTO( response_dto = ViewAllProductsResponseDTO(
products=[ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price) for product in products] products=[ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price) for product in products]
) )

View File

@ -0,0 +1,16 @@
#python imports
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
#dependency imports
#TODO: use correct dependencies
# Initialize components
#TODO: initialize
router = APIRouter()
#TODO: implement routes for creating and viewing user

View File

@ -4,11 +4,13 @@ import sqlite3
#dependency imports #dependency imports
from use_cases import ICartRepository from use_cases import ICartRepository
from entities import Cart, CartItem
class SQLCartRepository(ICartRepository): class SQLCartRepository(ICartRepository):
def __init__(self, db_file="framework_driver/db/shop.db"): def __init__(self, db_file="framework_driver/db/shop.db"):
"""Initialize the CartDatabase with a connection to the SQLite database.""" """Initialize the CartDatabase with a connection to the SQLite database."""
self.conn = sqlite3.connect(db_file) self.conn = sqlite3.connect(db_file)
self.conn.row_factory = sqlite3.Row
def add_to_cart(self, user_id, product_id, name, quantity, price): def add_to_cart(self, user_id, product_id, name, quantity, price):
"""Add a product to the user's cart.""" """Add a product to the user's cart."""
@ -21,14 +23,14 @@ class SQLCartRepository(ICartRepository):
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
def get_cart_by_user_id(self, user_id: int) -> Optional[List[dict]]: def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]:
"""Fetch cart items from the database for a given user ID.""" """Fetch cart items from the database for a given user ID."""
sql = "SELECT product_id, name, quantity, price FROM cart WHERE user_id = ?" sql = "SELECT product_id, name, quantity, price FROM cart WHERE user_id = ?"
try: try:
c = self.conn.cursor() c = self.conn.cursor()
c.execute(sql, (user_id,)) c.execute(sql, (user_id,))
rows = c.fetchall() rows = c.fetchall()
return [{'product_id':r[0], 'name':r[1], 'quantity':r[2], 'price':r[3]} for r in rows] return Cart([CartItem(**item) for item in rows])
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
return None return None
@ -66,7 +68,7 @@ class SQLCartRepository(ICartRepository):
def main(): def main():
database = "framework_driver/db/shop.db" database = "framework_driver/db/shop.db"
cart_db = CartDatabase(database) cart_db = SQLCartRepository(database)
ex_user_id = 1 ex_user_id = 1
@ -76,11 +78,11 @@ def main():
# user id, product id, name, quantity, price # 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, 1, "Unsichtbare Tinte", 2, 3.0)
cart_db.add_to_cart(ex_user_id, 3, "Geräuschloser Wecker", 5, 15.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.fetch_cart_items(ex_user_id)) print(f"Cart items for user {ex_user_id}:", cart_db.get_cart_by_user_id(ex_user_id))
# user id, product id, quantity # user id, product id, quantity
cart_db.update_cart_item(ex_user_id, 2, 3) cart_db.update_cart_item(ex_user_id, 2, 3)
print(f"Cart items for user {ex_user_id}:", cart_db.fetch_cart_items(ex_user_id)) print(f"Cart items for user {ex_user_id}:", cart_db.get_cart_by_user_id(ex_user_id))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -4,15 +4,17 @@ import sqlite3
#dependency imports #dependency imports
from use_cases import IProductRepository from use_cases import IProductRepository
from entities import Product
class SQLProductRepository(IProductRepository): class SQLProductRepository(IProductRepository):
def __init__(self, db_file="framework_driver/db/shop.db"): def __init__(self, db_file="framework_driver/db/shop.db"):
"""Initialize the ProductDatabase with a connection to the SQLite database.""" """Initialize the ProductDatabase with a connection to the SQLite database."""
self.conn = sqlite3.connect(db_file) self.conn = sqlite3.connect(db_file)
self.conn.row_factory = sqlite3.Row
def add_product(self, product): def add_product(self, product):
"""Add a new product to the products table.""" """Add a new product to the products table."""
sql = "INSERT INTO products (name, price, quantity) VALUES (?, ?, ?)" sql = "INSERT INTO products (name, price, description, quantity) VALUES (?, ?, ?, ?)"
try: try:
c = self.conn.cursor() c = self.conn.cursor()
c.execute(sql, product) c.execute(sql, product)
@ -21,26 +23,26 @@ class SQLProductRepository(IProductRepository):
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
def get_product_by_id(self, product_id): def get_product_by_id(self, product_id) -> Optional[Product]:
"""Retrieve a product by its ID.""" """Retrieve a product by its ID."""
sql = "SELECT * FROM products WHERE id = ?" sql = "SELECT * FROM products WHERE id = ?"
try: try:
c = self.conn.cursor() c = self.conn.cursor()
c.execute(sql, (product_id,)) c.execute(sql, (product_id,))
r = c.fetchone() r = c.fetchone()
return {'id':r[0], 'name':str(r[1]), 'description':str(r[2]), 'price':r[3]} return Product(**r)
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
return None return None
def get_all_products(self): def get_all_products(self) -> Optional[List[Product]]:
"""Retrieve all products.""" """Retrieve all products."""
sql = "SELECT * FROM products" sql = "SELECT * FROM products"
try: try:
c = self.conn.cursor() c = self.conn.cursor()
c.execute(sql) c.execute(sql)
rows = c.fetchall() rows = c.fetchall()
return [{'id':r[0], 'name':str(r[1]), 'description':str(r[2]), 'price':r[3]} for r in rows] return [Product(**product) for product in rows]
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
return [] return []
@ -68,16 +70,16 @@ class SQLProductRepository(IProductRepository):
def main(): def main():
database = "framework_driver/db/shop.db" database = "framework_driver/db/shop.db"
product_db = ProductDatabase(database) product_db = SQLProductRepository(database)
# delete all products # delete all products
for product in product_db.get_all_products(): for product in product_db.get_all_products():
product_db.delete_product(product[0]) product_db.delete_product(product[0])
# name, price, quantity # name, price, quantity
products = [("Unsichtbare Tinte", 2.5, 5), products = [("Unsichtbare Tinte", 2.5, "unsichtbar", 5),
("Luftdichter Sieb", 7.5, 10), ("Luftdichter Sieb", 7.5, "luftdicht", 10),
("Geräuschloser Wecker", 15.0, 7)] ("Geräuschloser Wecker", 15.0, "geräuschlos", 7)]
for product in products: for product in products:
product_db.add_product(product) product_db.add_product(product)

View File

@ -4,11 +4,13 @@ import sqlite3
#dependency imports #dependency imports
from use_cases import IUserRepository from use_cases import IUserRepository
from entities import User
class SQLUserRepository(IUserRepository): class SQLUserRepository(IUserRepository):
def __init__(self, db_file="framework_driver/db/shop.db"): def __init__(self, db_file="framework_driver/db/shop.db"):
"""Initialize the UserDatabase with a connection to the SQLite database.""" """Initialize the UserDatabase with a connection to the SQLite database."""
self.conn = sqlite3.connect(db_file) self.conn = sqlite3.connect(db_file)
self.conn.row_factory = sqlite3.Row
def add_user(self, user): def add_user(self, user):
"""Add a new user to the users table and return the new user's id.""" """Add a new user to the users table and return the new user's id."""
@ -22,7 +24,7 @@ class SQLUserRepository(IUserRepository):
print(e) print(e)
return None return None
def get_user_by_id(self, user_id): def get_user_by_id(self, user_id) -> Optional[User]:
"""Retrieve a user by their ID.""" """Retrieve a user by their ID."""
sql = "SELECT * FROM users WHERE id = ?" sql = "SELECT * FROM users WHERE id = ?"
try: try:
@ -67,7 +69,7 @@ class SQLUserRepository(IUserRepository):
def main(): def main():
database = "framework_driver/db/shop.db" database = "framework_driver/db/shop.db"
user_db = UserDatabase(database) user_db = SQLUserRepository(database)
# delete all users # delete all users
for user in user_db.get_all_users(): for user in user_db.get_all_users():

View File

@ -1,8 +1,8 @@
from interface_adapters.controllers.cart_controller import router as cart_router 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 fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
@ -26,33 +26,9 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
# Serve static files
# TODO fix this
#app.mount("/static", StaticFiles(directory="framework_driver/ui/static"), name="static")
# TODO so this is not needed
@app.get("/custom_static/{filename:path}", response_class=FileResponse, name="custom_static", include_in_schema=False)
async def custom_static(filename: str):
file_path = os.path.join("framework_driver/ui/static", filename)
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="File not found")
return FileResponse(file_path)
# Set up templates
templates = Jinja2Templates(directory="framework_driver/ui/templates")
@app.get("/", include_in_schema=False) @app.get("/", include_in_schema=False)
async def root(): async def root():
return RedirectResponse(url="/docs") return RedirectResponse(url="/docs")
@app.get("/ui", response_class=HTMLResponse)
async def ui(request: Request):
products = [
{"id": 1, "name": "Product 1", "price": 10.0},
{"id": 2, "name": "Product 2", "price": 20.0}
]
return templates.TemplateResponse("products.html", {"request": request, "products": products})
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000) uvicorn.run(app, host="127.0.0.1", port=8000)

8
src/setup_db.py 100644
View File

@ -0,0 +1,8 @@
from framework_driver.db import db_setup
from interface_adapters.repositories import sql_cart_repository
from interface_adapters.repositories import sql_product_repository
from interface_adapters.repositories import sql_user_repository
db_setup.main()
sql_cart_repository.main()
sql_product_repository.main()
sql_user_repository.main()

View File

@ -1,7 +1,10 @@
from typing import Optional, List from typing import Optional, List
#dependency imports
from entities import Cart
class ICartRepository: class ICartRepository:
def get_cart_by_user_id(self, user_id: int) -> Optional[List[dict]]: def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]:
""" """
Abstract method to fetch cart data by user ID. Abstract method to fetch cart data by user ID.
""" """

View File

@ -0,0 +1,14 @@
#python imports
from typing import Optional
#dependency imports
#TODO: use correct dependencies
class CreateUser:
def __init__(self):
pass
#TODO: initialize required Interfaces
def execute(self):
pass
#TODO: implement

View File

@ -1,13 +1,16 @@
from typing import Optional, List from typing import Optional, List
#dependency imports
from entities import Product
class IProductRepository: class IProductRepository:
def get_product_by_id(self, product_id: int): def get_product_by_id(self, product_id: int) -> Optional[Product]:
""" """
Abstract method to fetch product data by 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): def get_all_products(self) -> Optional[List[Product]]:
""" """
Abstract method to fetch product data. Abstract method to fetch product data.
""" """

View File

@ -1,8 +1,5 @@
from typing import Optional, List from typing import Optional, List
class IUserRepository: class IUserRepository:
def get_user_by_id(self, user_id: int): pass
""" #TODO: define interface functions for creating and getting user
Abstract method to fetch user data by user ID.
"""
raise NotImplementedError("get_user_by_id must be implemented by a subclass")

View File

@ -1,5 +1,5 @@
#python imports #python imports
from typing import Optional from typing import Optional, List
#dependency imports #dependency imports
from entities import Product from entities import Product
@ -9,12 +9,11 @@ class ViewAllProducts:
def __init__(self, product_repository: IProductRepository): def __init__(self, product_repository: IProductRepository):
self.product_repository = product_repository self.product_repository = product_repository
def execute(self) -> Optional[Product]: def execute(self) -> Optional[List[Product]]:
""" """
Fetches the product data from the repository, converts it to Product entity. Fetches the product data from the repository.
""" """
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
products = [Product(**product) for product in product_data] return product_data
return products

View File

@ -11,12 +11,10 @@ class ViewCart:
def execute(self, user_id: int) -> Optional[Cart]: def execute(self, user_id: int) -> Optional[Cart]:
""" """
Fetches the cart data from the repository, converts it to Cart entity. Fetches the cart data from the repository.
""" """
cart_data = self.cart_repository.get_cart_by_user_id(user_id) cart_data = self.cart_repository.get_cart_by_user_id(user_id)
if not cart_data: if not cart_data:
return None return None
# Convert raw data to domain entities return cart_data
items = [CartItem(**item) for item in cart_data]
return Cart(items)

View File

@ -11,9 +11,10 @@ class ViewProduct:
def execute(self, product_id: int) -> Optional[Product]: def execute(self, product_id: int) -> Optional[Product]:
""" """
Fetches the product data from the repository, converts it to Product entity. Fetches the product data from the repository.
""" """
product_data = self.product_repository.get_product_by_id(product_id) product_data = self.product_repository.get_product_by_id(product_id)
if not product_data: if not product_data:
return None return None
return Product(**product_data)
return product_data

View File

@ -0,0 +1,14 @@
#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