Compare commits
2 Commits
uebung_ent
...
main
Author | SHA1 | Date |
---|---|---|
|
d1f0ff3375 | |
|
569589a4f5 |
|
@ -2,5 +2,4 @@
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
/env
|
Binary file not shown.
17
README.md
17
README.md
|
@ -3,12 +3,11 @@
|
|||
** Master MDS HSMA **
|
||||
Clean Architecture - Architekturstil am praktischen Beispiel z.B. in Python.
|
||||
|
||||
Check README in layer for task description
|
||||
|
||||
## API:
|
||||
- 0: open Terminal
|
||||
- 1: navigate to src: cd .\src\
|
||||
- 2: run python main.py
|
||||
- 3 go to url: [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)
|
||||
- 4: close with: strg + c
|
||||
|
||||
## Setup Test
|
||||
1. falls python noch nicht installiert, installieren
|
||||
2. navigiere zum repo ```cd IWS_WS24_clean_architecture```
|
||||
3. ```pip install -r requirements.txt```
|
||||
4. ```python main.py``` ausführen und Terminal offen lassen
|
||||
5. index.html mit Browser öffnen
|
||||
6. Wenn alles geklappt hat sollte die index.html Hello World anzeigen
|
||||
7. mit ```STRG + C``` kann das script im Terminal gestoppt werden
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Setup Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="greetings">Not Working</h1>
|
||||
|
||||
<script>
|
||||
// Fetch Hello World from the FastAPI endpoint
|
||||
fetch('http://127.0.0.1:8000/greetings')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const greetingsContainer = document.getElementById('greetings');
|
||||
greetingsContainer.innerHTML = '';
|
||||
data.greetings.forEach(greeting => {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = `${greeting[1]}`;
|
||||
greetingsContainer.appendChild(div);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
import sqlite3
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import uvicorn
|
||||
|
||||
app = FastAPI()
|
||||
# Allow CORS for all origins (for simplicity)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/greetings")
|
||||
async def get_greetings():
|
||||
connection = sqlite3.connect("HelloWorld.db")
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT * FROM greetings")
|
||||
rows = cursor.fetchall()
|
||||
return {"greetings": rows}
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
@ -0,0 +1,2 @@
|
|||
fastapi
|
||||
uvicorn
|
|
@ -1,17 +0,0 @@
|
|||
# ENTITIES
|
||||
"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
|
||||
matter so long as the entities can be used by many different applications in the
|
||||
enterprise.
|
||||
If you don’t have an enterprise and are writing just a single application, then these
|
||||
entities are the business objects of the application. They encapsulate the most general
|
||||
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
|
||||
to page navigation or security. No operational change to any particular application
|
||||
should affect the entity layer." -- Robert C. Martin, Clean Architecture
|
||||
|
||||
# Aufgabe
|
||||
|
||||
### Anforderungen:
|
||||
|
||||
- **Entity** Implemntiere eine user entity in user.py
|
|
@ -1,38 +0,0 @@
|
|||
class Product:
|
||||
"""
|
||||
Represents a product with an ID, name, description, price, and quantity.
|
||||
"""
|
||||
|
||||
def __init__(self, id: str, name: str, description: str, price: float, quantity: int):
|
||||
"""
|
||||
Initializes a new product.
|
||||
|
||||
Args:
|
||||
id (str): The unique identifier for the product.
|
||||
name (str): The name of the product.
|
||||
description (str): A brief description of the product.
|
||||
price (float): The price of the product.
|
||||
quantity (int): The available quantity of the product.
|
||||
"""
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.price = price
|
||||
self.quantity = quantity
|
||||
self.post_init()
|
||||
|
||||
def post_init(self):
|
||||
"""
|
||||
Validates the product's price.
|
||||
|
||||
Raises:
|
||||
ValueError: If the price is negative.
|
||||
"""
|
||||
if self.price < 0:
|
||||
raise ValueError("Der Preis darf nicht negativ sein.")
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns a string representation of the product.
|
||||
"""
|
||||
return f"Product(id={self.id}, name={self.name}, description={self.description}, price={self.price}, quantity={self.quantity})"
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
class User:
|
||||
#TODO: implement in exercise 'entities'
|
||||
#TODO: user should have: id (int) but what other attributes should a user have?
|
||||
#TODO: user should have methods but what methods should a user have?
|
||||
|
||||
pass
|
|
@ -1,16 +0,0 @@
|
|||
# FRAMEWORKS AND DRIVERS
|
||||
The outermost layer of the model in Figure 22.1 is generally composed of
|
||||
frameworks and tools such as the database and the web framework. Generally you
|
||||
don’t write much code in this layer, other than glue code that communicates to the
|
||||
next circle inward.
|
||||
The frameworks and drivers layer is where all the details go. The web is a detail. The
|
||||
database is a detail. We keep these things on the outside where they can do little
|
||||
harm.
|
||||
|
||||
# Aufgabe
|
||||
|
||||
### Anforderungen:
|
||||
|
||||
#### Implementierungen:
|
||||
- **UI**: Die UI ist ein Framework. Innerhalb der UI ist es möglich ebenfalls Clean Architecture zu verwenden. In diesem Fall wird MVC verwendet. Implementiere die `createUser`-Methode in `conroller.js` um einen User über die UI anzulegen.
|
||||
- **DB**: Die Datenbank ist ein Driver. Um einen User anlegen zu können wird eine passende Tabelle in der Datenbank benötigt. Lege diese über das `db/db_setup.py` script an. Über `python setup_db.py` in `src` kann die Datenbank neu aufgesetzt werden.
|
|
@ -1,60 +0,0 @@
|
|||
import sqlite3
|
||||
|
||||
def create_connection(db_file):
|
||||
"""Create a database connection to the SQLite database specified by db_file."""
|
||||
conn = None
|
||||
try:
|
||||
conn = sqlite3.connect(db_file)
|
||||
print(f"Connected to {db_file}")
|
||||
except sqlite3.Error as e:
|
||||
print(e)
|
||||
return conn
|
||||
|
||||
def create_table(conn, create_table_sql):
|
||||
"""Create a table from the create_table_sql statement."""
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(create_table_sql)
|
||||
print("Table created successfully")
|
||||
except sqlite3.Error as e:
|
||||
print(e)
|
||||
|
||||
def delete_table(conn, table_name):
|
||||
"""Delete a table specified by table_name."""
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute(f"DROP TABLE IF EXISTS {table_name}")
|
||||
print(f"Table {table_name} deleted successfully")
|
||||
except sqlite3.Error as e:
|
||||
print(e)
|
||||
|
||||
def main():
|
||||
database = "framework_driver/db/shop.db"
|
||||
|
||||
#TODO: Create User Table
|
||||
sql_user_table = """
|
||||
"""
|
||||
|
||||
sql_product_table = """CREATE TABLE IF NOT EXISTS products (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL
|
||||
)"""
|
||||
|
||||
conn = create_connection(database)
|
||||
|
||||
if conn is not None:
|
||||
# Delete tables if they exist
|
||||
delete_table(conn, "products")
|
||||
delete_table(conn, "users")
|
||||
|
||||
# Create tables
|
||||
create_table(conn, sql_user_table)
|
||||
create_table(conn, sql_product_table)
|
||||
else:
|
||||
print("Error! Cannot create the database connection.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,43 +0,0 @@
|
|||
<!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="products-tab" href="#">Products</a>
|
||||
<a id="user-tab" 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>
|
|
@ -1,75 +0,0 @@
|
|||
// Controller: Handles user interactions
|
||||
const Controller = {
|
||||
|
||||
async createUser(name, email, password){
|
||||
//TODO: use model to send request to backend, then call View to render response
|
||||
alert('User Creation functionality not implemented.');
|
||||
},
|
||||
|
||||
async showProducts() {
|
||||
const productsResponse = await Model.fetchProducts();
|
||||
if(productsResponse.status=="success"){
|
||||
const products = productsResponse.data;
|
||||
View.renderProducts(products);
|
||||
}
|
||||
Controller.addProductClickHandlers();
|
||||
},
|
||||
|
||||
async showProduct(productId) {
|
||||
const productResponse = await Model.fetchProduct(productId);
|
||||
if(productResponse.status=="success"){
|
||||
const product = productResponse.data;
|
||||
View.renderProduct(product);
|
||||
}
|
||||
document.getElementById('back-button').addEventListener('click', () => {
|
||||
Controller.showProducts();
|
||||
});
|
||||
},
|
||||
|
||||
showUserRegister(){
|
||||
View.renderUserRegister();
|
||||
document.getElementById('create-user-button').addEventListener('click', () => {
|
||||
const name = document.getElementById('user-name').value;
|
||||
const email = document.getElementById('user-email').value;
|
||||
const password = document.getElementById('user-password').value;
|
||||
Controller.createUser(name, email, password);
|
||||
});
|
||||
},
|
||||
|
||||
addEventListeners() {
|
||||
document.getElementById('products-tab').addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
Controller.showProducts();
|
||||
});
|
||||
|
||||
document.getElementById('user-tab').addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
Controller.showUserRegister();
|
||||
});
|
||||
|
||||
document.querySelectorAll('.product-link').forEach((link) => {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
const productId = event.target.getAttribute('data-id');
|
||||
Controller.showProduct(productId);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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();
|
|
@ -1,20 +0,0 @@
|
|||
// Model: Handles data fetching
|
||||
const Model = {
|
||||
//TODO: Add function to send CreateUserRequest to Backend
|
||||
|
||||
async fetchProducts() {
|
||||
const response = await fetch('http://127.0.0.1:8000/view_products');
|
||||
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();
|
||||
},
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
// View: Handles rendering
|
||||
const View = {
|
||||
//TODO: Add function to render UserCreateResponse
|
||||
|
||||
renderProducts(products) {
|
||||
const main = document.getElementById('main');
|
||||
main.innerHTML = `
|
||||
<h1>Products</h1>
|
||||
<ul>
|
||||
${products.map(product => View.renderProductInList(product)).join('')}
|
||||
</ul>
|
||||
`;
|
||||
},
|
||||
|
||||
renderProductInList(product) {
|
||||
return `
|
||||
<li>
|
||||
<a href="#" data-id="${product.id}" class="product-link">
|
||||
${product.name} - $${product.price}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
|
||||
renderProduct(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>
|
||||
`;
|
||||
},
|
||||
|
||||
renderUserRegister() {
|
||||
const main = document.getElementById('main');
|
||||
main.innerHTML = `
|
||||
<div class="form-container" id="user-form-container">
|
||||
<h2>Create User</h2>
|
||||
<div id="user-form">
|
||||
<input type="text" id="user-name" name="name" placeholder="Enter your name" required>
|
||||
<input type="text" id="user-email" name="email" placeholder="Enter your email" required>
|
||||
<input type="password" id="user-password" name="password" placeholder="Enter your password" required>
|
||||
<button id="create-user-button">Create Account</button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
};
|
|
@ -1,27 +0,0 @@
|
|||
# INTERFACE ADAPTERS
|
||||
"The software in the interface adapters layer is a set of adapters that convert data from
|
||||
the format most convenient for the use cases and entities, to the format most
|
||||
convenient for some external agency such as the database or the web. It is this layer,
|
||||
for example, that will wholly contain the MVC architecture of a GUI. The
|
||||
presenters, views, and controllers all belong in the interface adapters layer. The
|
||||
models are likely just data structures that are passed from the controllers to the use
|
||||
cases, and then back from the use cases to the presenters and views.
|
||||
Similarly, data is converted, in this layer, from the form most convenient for entities
|
||||
and use cases, to the form most convenient for whatever persistence framework is
|
||||
being used (i.e., the database). No code inward of this circle should know anything at
|
||||
all about the database. If the database is a SQL database, then all SQL should be
|
||||
restricted to this layer—and in particular to the parts of this layer that have to do with
|
||||
the database.
|
||||
Also in this layer is any other adapter necessary to convert data from some external
|
||||
form, such as an external service, to the internal form used by the use cases and
|
||||
entities." -- Robert C. Martin, Clean Architecture
|
||||
|
||||
# Aufgabe
|
||||
|
||||
### Anforderungen:
|
||||
|
||||
#### Implementierungen:
|
||||
- **CreateUserRequest, CreateUserResponse**: Diese Objekte werden für die Kommunikation mit Frameworks wie der UI verwendet. Überlege welche Informationen zwischen Frontend und Backend ausgetauscht werden.
|
||||
- **CreateUserController**: Der Controller ist dafür verantwortlich die Request entgegenzunehmen, zu validieren und in ein passendes Datenformat für das UseCase umzuwandeln.
|
||||
- **CreateUserPresenter**: Der Presenter implementiert den OutputPort des UseCases. Er ist dafür verantwortlich die Asugabe zu formatieren und in ein Response DTO umzuwandlen.
|
||||
- **SQLUserRepository**: Das SQLUserRepository implementiert das IUserRepository Interface und verwendet SQL-Statements um Daten aus der Datenbank zu holen und in ein passendes Format für das UseCase zu konvertieren.
|
|
@ -1,84 +0,0 @@
|
|||
#python imports
|
||||
from typing import List
|
||||
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 entities.product import Product
|
||||
from use_cases.view_all_products import ViewAllProducts, IViewAllProductsInputPort, IViewAllProductsOutputPort
|
||||
from use_cases.view_product import ViewProduct, IViewProductInputPort, IViewProductOutputPort
|
||||
from interface_adapters.sql_product_repository import SQLProductRepository
|
||||
|
||||
class ProductDTO(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: str
|
||||
price: float
|
||||
|
||||
class ViewProductRequest(BaseModel):
|
||||
product_id: int
|
||||
|
||||
class ViewProductResponse(BaseModel):
|
||||
status: str
|
||||
data: ProductDTO
|
||||
|
||||
class ViewProductPresenter(IViewProductOutputPort):
|
||||
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: IViewProductInputPort) -> 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()
|
||||
view_product_presenter = ViewProductPresenter()
|
||||
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(IViewAllProductsOutputPort):
|
||||
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: IViewAllProductsInputPort) -> 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.post("/view_product", response_model=ViewProductResponse)
|
||||
async def view_product(request: ViewProductRequest):
|
||||
return view_product_controller.handle_request(request)
|
||||
|
||||
@router.get("/view_products", response_model=ViewAllProductsResponse)
|
||||
async def view_products():
|
||||
return view_all_products_controller.handle_request()
|
|
@ -1,74 +0,0 @@
|
|||
#python imports
|
||||
from typing import Optional, List
|
||||
import sqlite3
|
||||
|
||||
#dependency imports
|
||||
from use_cases.product_repository import IProductRepository
|
||||
from entities.product import Product
|
||||
|
||||
class SQLProductRepository(IProductRepository):
|
||||
def __init__(self, db_file="framework_driver/db/shop.db"):
|
||||
"""Initialize the ProductDatabase with a connection to the SQLite database."""
|
||||
self.conn = sqlite3.connect(db_file)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
|
||||
def get_product_by_id(self, product_id) -> Optional[Product]:
|
||||
"""Retrieve a product by its ID."""
|
||||
sql = "SELECT * FROM products WHERE id = ?"
|
||||
try:
|
||||
c = self.conn.cursor()
|
||||
c.execute(sql, (product_id,))
|
||||
r = c.fetchone()
|
||||
return Product(**r)
|
||||
except sqlite3.Error as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def add_product(self, product):
|
||||
"""Add a new product to the products table."""
|
||||
sql = "INSERT INTO products (name, price, description, quantity) VALUES (?, ?, ?, ?)"
|
||||
try:
|
||||
c = self.conn.cursor()
|
||||
c.execute(sql, product)
|
||||
self.conn.commit()
|
||||
return c.lastrowid
|
||||
except sqlite3.Error as e:
|
||||
print(e)
|
||||
|
||||
def get_all_products(self) -> Optional[List[Product]]:
|
||||
"""Retrieve all products."""
|
||||
sql = "SELECT * FROM products"
|
||||
try:
|
||||
c = self.conn.cursor()
|
||||
c.execute(sql)
|
||||
rows = c.fetchall()
|
||||
return [Product(**product) for product in rows]
|
||||
except sqlite3.Error as e:
|
||||
print(e)
|
||||
return []
|
||||
|
||||
def main():
|
||||
database = "framework_driver/db/shop.db"
|
||||
|
||||
product_db = SQLProductRepository(database)
|
||||
|
||||
# delete all products
|
||||
for product in product_db.get_all_products():
|
||||
product_db.delete_product(product[0])
|
||||
|
||||
# name, price, quantity
|
||||
products = [("Unsichtbare Tinte", 2.5, "unsichtbar", 5),
|
||||
("Luftdichter Sieb", 7.5, "luftdicht", 10),
|
||||
("Geräuschloser Wecker", 15.0, "geräuschlos", 7)]
|
||||
|
||||
for product in products:
|
||||
product_db.add_product(product)
|
||||
|
||||
products = product_db.get_all_products()
|
||||
print("All products:")
|
||||
for product in products:
|
||||
print(product)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,13 +0,0 @@
|
|||
#python imports
|
||||
from typing import Optional, List
|
||||
import sqlite3
|
||||
|
||||
#dependency imports
|
||||
from use_cases.user_repository import IUserRepository
|
||||
from entities.user import User
|
||||
|
||||
class SQLUserRepository(IUserRepository):
|
||||
def __init__(self, db_file="framework_driver/db/shop.db"):
|
||||
"""Initialize the UserDatabase with a connection to the SQLite database."""
|
||||
self.conn = sqlite3.connect(db_file)
|
||||
self.conn.row_factory = sqlite3.Row
|
|
@ -1,34 +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 fastapi.templating import Jinja2Templates
|
||||
from pydantic import BaseModel
|
||||
|
||||
#dependency imports
|
||||
#TODO: use correct dependencies
|
||||
|
||||
|
||||
#TODO: define request and response data
|
||||
|
||||
class CreateUserRequest(BaseModel):
|
||||
pass
|
||||
|
||||
class CreateUserResponse(BaseModel):
|
||||
pass
|
||||
|
||||
#TODO: implement CreateUserController and CreateUserPresenter
|
||||
|
||||
|
||||
#TODO: initialize all components
|
||||
create_user_controller = None
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
@router.post("/create_user", response_model=CreateUserResponse)
|
||||
async def create_user(request:CreateUserRequest):
|
||||
response = create_user_controller.handle_request(request)
|
||||
if not response:
|
||||
raise HTTPException(status_code=500, detail="Could not create user")
|
||||
return response
|
34
src/main.py
34
src/main.py
|
@ -1,34 +0,0 @@
|
|||
from interface_adapters.product_controller import router as product_router
|
||||
from interface_adapters.user_controller import router as user_router
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.requests import Request
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi import FastAPI
|
||||
import uvicorn
|
||||
import os
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.include_router(product_router)
|
||||
app.include_router(user_router)
|
||||
|
||||
# Allow CORS for all origins (for simplicity)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
async def root():
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
fastapi
|
||||
httpx
|
||||
pydantic
|
||||
uvicorn
|
||||
sqllite3
|
|
@ -1,4 +0,0 @@
|
|||
from framework_driver.db import db_setup
|
||||
from interface_adapters import sql_product_repository
|
||||
db_setup.main()
|
||||
sql_product_repository.main()
|
|
@ -1,2 +0,0 @@
|
|||
# Tests
|
||||
To run tests: python -m unittest discover
|
|
@ -1,11 +0,0 @@
|
|||
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)
|
|
@ -1,21 +0,0 @@
|
|||
#python imports
|
||||
from typing import Optional, List
|
||||
|
||||
#dependency imports
|
||||
from use_cases import ICartRepository
|
||||
|
||||
class CartDatabase(ICartRepository):
|
||||
def __init__(self):
|
||||
# Mock database setup
|
||||
self.mock_db = {
|
||||
1: [
|
||||
{"product_id": 101, "name": "Apple", "quantity": 2, "price": 0.5},
|
||||
{"product_id": 102, "name": "Milk", "quantity": 1, "price": 1.5},
|
||||
],
|
||||
2: [
|
||||
{"product_id": 201, "name": "Bread", "quantity": 1, "price": 2.0},
|
||||
],
|
||||
}
|
||||
|
||||
def get_cart_by_user_id(self, user_id: int) -> Optional[List[dict]]:
|
||||
return self.mock_db.get(user_id)
|
|
@ -1,16 +0,0 @@
|
|||
#python imports
|
||||
from typing import Optional, List
|
||||
|
||||
#dependency imports
|
||||
from use_cases import IProductRepository
|
||||
|
||||
class ProductDatabase(IProductRepository):
|
||||
def __init__(self):
|
||||
# Mock database setup
|
||||
self.mock_db = {
|
||||
1: {"id": 1, "name": "Apple", "description": "gsdg", "price": 0.5},
|
||||
2: {"id": 2, "name": "Bread", "description": "sfg", "price": 2.0},
|
||||
}
|
||||
|
||||
def get_product_by_id(self, product_id: int) -> Optional[List[dict]]:
|
||||
return self.mock_db.get(product_id)
|
|
@ -1,19 +0,0 @@
|
|||
from use_cases import ViewCart
|
||||
from tests.mocks import CartDatabase
|
||||
import unittest
|
||||
|
||||
|
||||
class TestViewCart(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cart_database = CartDatabase()
|
||||
self.view_cart_use_case = ViewCart(self.cart_database)
|
||||
|
||||
def test_view_cart_valid_id(self):
|
||||
result = self.view_cart_use_case.execute(1)
|
||||
self.assertIsNotNone(result)
|
||||
#self.assertEqual(result['id'], 1)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,19 +0,0 @@
|
|||
from use_cases import ViewProduct
|
||||
from tests.mocks import ProductDatabase
|
||||
import unittest
|
||||
|
||||
|
||||
class TestViewProduct(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.product_database = ProductDatabase()
|
||||
self.view_product_use_case = ViewProduct(self.product_database)
|
||||
|
||||
def test_view_product_valid_id(self):
|
||||
result = self.view_product_use_case.execute(1)
|
||||
self.assertIsNotNone(result)
|
||||
#self.assertEqual(result['id'], 1)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,23 +0,0 @@
|
|||
# USE CASES
|
||||
"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
|
||||
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.
|
||||
We do not expect changes in this layer to affect the entities. We also do not expect
|
||||
this layer to be affected by changes to externalities such as the database, the UI, or
|
||||
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
|
||||
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." -- Robert C. Martin, Clean Architecture
|
||||
|
||||
# Aufgabe
|
||||
|
||||
### Anforderungen:
|
||||
|
||||
#### Interfaces:
|
||||
- **CreateUserInputPort**: Dieses Interface wird durch die execute methode des UseCases implementiert. Dadurch wird der Controller unabhängig von Änderungen im UseCase. Überlege dir welche Daten das UseCase als Input bekommt und definiere ein passendes Interface.
|
||||
- **CreateUserOutputPort**: Dieses Interface wird vom UseCase verwendet um Daten auszugeben. Es wird durch die present methode des Presenters implementiert. Dadurch wird das UseCase unabhängig vom Presenter. Überlege dir welche daten das UseCase zurück gibt und definiere ein passendes Interface.
|
||||
- **IUserRepository**: Dieses Interface wird vom UseCase verwendet um Daten aus der Datenbank zu holen. Dadurch wird das UseCase unabhängig von Datenbanken. Überlege dir welche Queries ausgeführt werden müssen und definiere ein passendes Interface.
|
||||
|
||||
#### UseCase
|
||||
Implementiere einen UseCase zum Erstellen eines Users. Das UseCase sollte den CreateUserInputPort implementieren, das IUserRepository verwenden um mit der Datenbank zu kommunizieren und den CreateUserOutputPort verwenden um Daten auszugeben.
|
|
@ -1,19 +0,0 @@
|
|||
#python imports
|
||||
from typing import Optional
|
||||
|
||||
#dependency imports
|
||||
#TODO: A1: Use correct dependencies
|
||||
|
||||
#TODO: A2: Create InputPort
|
||||
|
||||
#TODO: A3: Create OutputPort
|
||||
|
||||
#TODO: A5: Use Case CreateUser:
|
||||
class CreateUser:
|
||||
def __init__(self):
|
||||
pass
|
||||
#TODO: Initialize required Interfaces
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
#TODO: Implement Use Case
|
|
@ -1,17 +0,0 @@
|
|||
from typing import Optional, List
|
||||
|
||||
#dependency imports
|
||||
from entities.product import Product
|
||||
|
||||
class IProductRepository:
|
||||
def get_product_by_id(self, product_id: int) -> Optional[Product]:
|
||||
"""
|
||||
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) -> Optional[List[Product]]:
|
||||
"""
|
||||
Abstract method to fetch product data.
|
||||
"""
|
||||
raise NotImplementedError("get_all_products must be implemented by a subclass")
|
|
@ -1,5 +0,0 @@
|
|||
from typing import Optional, List
|
||||
|
||||
class IUserRepository:
|
||||
pass
|
||||
#TODO: A4: Define interface functions for creating a user
|
|
@ -1,34 +0,0 @@
|
|||
#python imports
|
||||
from typing import Optional, List
|
||||
|
||||
#dependency imports
|
||||
from entities.product import Product
|
||||
from use_cases.product_repository import IProductRepository
|
||||
|
||||
class IViewAllProductsInputPort:
|
||||
def execute(self):
|
||||
"""
|
||||
Abstract method to execute use case.
|
||||
"""
|
||||
raise NotImplementedError("execute must be implemented by a subclass")
|
||||
|
||||
class IViewAllProductsOutputPort:
|
||||
def present(self, product_list:List[Product]):
|
||||
"""
|
||||
Abstract method to execute use case.
|
||||
"""
|
||||
raise NotImplementedError("execute must be implemented by a subclass")
|
||||
|
||||
class ViewAllProducts(IViewAllProductsInputPort):
|
||||
def __init__(self, product_repository: IProductRepository, output_port: IViewAllProductsOutputPort):
|
||||
self.product_repository = product_repository
|
||||
self.output_port = output_port
|
||||
|
||||
def execute(self) -> Optional[List[Product]]:
|
||||
"""
|
||||
Fetches the product data from the repository.
|
||||
"""
|
||||
product_data = self.product_repository.get_all_products()
|
||||
if not product_data:
|
||||
return None
|
||||
return self.output_port.present(product_data)
|
|
@ -1,35 +0,0 @@
|
|||
#python imports
|
||||
from typing import Optional
|
||||
|
||||
#dependency imports
|
||||
from entities.product import Product
|
||||
from use_cases.product_repository import IProductRepository
|
||||
|
||||
class IViewProductInputPort:
|
||||
def execute(self, product_id:int):
|
||||
"""
|
||||
Abstract method to execute use case.
|
||||
"""
|
||||
raise NotImplementedError("execute must be implemented by a subclass")
|
||||
|
||||
class IViewProductOutputPort:
|
||||
def present(self, product:Product):
|
||||
"""
|
||||
Abstract method to execute use case.
|
||||
"""
|
||||
raise NotImplementedError("execute must be implemented by a subclass")
|
||||
|
||||
class ViewProduct(IViewProductInputPort):
|
||||
def __init__(self, product_repository: IProductRepository, output_port: IViewProductOutputPort):
|
||||
self.product_repository = product_repository
|
||||
self.output_port = output_port
|
||||
|
||||
def execute(self, product_id: int):
|
||||
"""
|
||||
Fetches the product data from the repository.
|
||||
"""
|
||||
product_data = self.product_repository.get_product_by_id(product_id)
|
||||
if not product_data:
|
||||
return None
|
||||
|
||||
return self.output_port.present(product_data)
|
Loading…
Reference in New Issue