From 249220ab1c4bd28e70294404c7315e42bff7ffec Mon Sep 17 00:00:00 2001 From: Furqan <22556762+trulyfurqan@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:55:06 +0500 Subject: [PATCH] Inventory Management and Cart Features --- python_codebase/authentication.py | 36 ++-- .../inventory_management_api/app.py | 13 ++ .../inventory_management_api/cache.py | 10 + .../inventory_management_api/config.py | 11 + .../inventory_management_api/rate_limiter.py | 4 + .../routes/__init__.py | 0 .../inventory_management_api/routes/cart.py | 60 ++++++ .../routes/inventory.py | 122 +++++++++++ .../routes/recommendation.py | 64 ++++++ python_codebase/main.py | 196 +++++++++++------- python_codebase/models.py | 48 +++-- 11 files changed, 463 insertions(+), 101 deletions(-) create mode 100644 python_codebase/inventory_management_api/app.py create mode 100644 python_codebase/inventory_management_api/cache.py create mode 100644 python_codebase/inventory_management_api/config.py create mode 100644 python_codebase/inventory_management_api/rate_limiter.py create mode 100644 python_codebase/inventory_management_api/routes/__init__.py create mode 100644 python_codebase/inventory_management_api/routes/cart.py create mode 100644 python_codebase/inventory_management_api/routes/inventory.py create mode 100644 python_codebase/inventory_management_api/routes/recommendation.py diff --git a/python_codebase/authentication.py b/python_codebase/authentication.py index d800dea..e827c3d 100644 --- a/python_codebase/authentication.py +++ b/python_codebase/authentication.py @@ -14,44 +14,43 @@ def get_hashed_password(password): async def very_token(token: str): - '''verify token from login''' + """verify token from login""" try: - payload = jwt.decode(token, get_settings().SECRET, - algorithms=["HS256"]) + payload = jwt.decode(token, get_settings().SECRET, algorithms=["HS256"]) user = await User.get(id=payload.get("id")) except: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Token", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) return await user async def very_token_email(token: str): - '''verify token from email''' + """verify token from email""" try: - payload = jwt.decode(token, get_settings().SECRET, - algorithms=["HS256"]) + payload = jwt.decode(token, get_settings().SECRET, algorithms=["HS256"]) user = await User.get(id=payload.get("id"), email=payload.get("email")) except: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Token", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) return await user + # bug: sam_ple@gma.com:Trye ; sam_p_le@gma.com: False!! -regex = '^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$' +regex = "^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$" def is_not_email(email): - """if valid mail: return 'True' \n - ** This is a simple way to do this and is not recommended for a real project ** """ - if(re.search(regex, email)): + """if valid mail: return 'True' \n + ** This is a simple way to do this and is not recommended for a real project **""" + if re.search(regex, email): return False else: return True @@ -68,7 +67,7 @@ async def authenticate_user(username: str, password: str): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email not verifide", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) return user return False @@ -81,12 +80,13 @@ async def token_generator(username: str, password: str): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Username or Password", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) - token_data = { - "id": user.id, - "username": user.username - } + token_data = {"id": user.id, "username": user.username} token = jwt.encode(token_data, get_settings().SECRET, algorithm="HS256") return token + + +async def get_current_user(token: str = Depends(oauth_scheme)): + return await very_token(token) diff --git a/python_codebase/inventory_management_api/app.py b/python_codebase/inventory_management_api/app.py new file mode 100644 index 0000000..a245d38 --- /dev/null +++ b/python_codebase/inventory_management_api/app.py @@ -0,0 +1,13 @@ +from flask import Flask +from config import configure_app +from rate_limiter import limiter +from routes.inventory import inventory_bp + +app = Flask(__name__) +configure_app(app) +limiter.init_app(app) + +app.register_blueprint(inventory_bp, url_prefix="/inventory") + +if __name__ == "__main__": + app.run(debug=True) diff --git a/python_codebase/inventory_management_api/cache.py b/python_codebase/inventory_management_api/cache.py new file mode 100644 index 0000000..915b57a --- /dev/null +++ b/python_codebase/inventory_management_api/cache.py @@ -0,0 +1,10 @@ +import redis +from flask import current_app + + +def get_cache(): + return redis.Redis( + host=current_app.config["REDIS_HOST"], + port=current_app.config["REDIS_PORT"], + db=0, + ) diff --git a/python_codebase/inventory_management_api/config.py b/python_codebase/inventory_management_api/config.py new file mode 100644 index 0000000..5fe11af --- /dev/null +++ b/python_codebase/inventory_management_api/config.py @@ -0,0 +1,11 @@ +import os + + +def configure_app(app): + app.config["REDIS_HOST"] = "localhost" + app.config["REDIS_PORT"] = 6379 + app.config["DB_HOST"] = "localhost" + app.config["DB_USER"] = "user" + app.config["DB_PASSWORD"] = os.getenv("DB_PASSWORD", "default") + app.config["DB_NAME_1"] = "inventory_db1" + app.config["DB_NAME_2"] = "inventory_db2" diff --git a/python_codebase/inventory_management_api/rate_limiter.py b/python_codebase/inventory_management_api/rate_limiter.py new file mode 100644 index 0000000..f6fa960 --- /dev/null +++ b/python_codebase/inventory_management_api/rate_limiter.py @@ -0,0 +1,4 @@ +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address + +limiter = Limiter(key_func=get_remote_address, default_limits=["100 per minute"]) diff --git a/python_codebase/inventory_management_api/routes/__init__.py b/python_codebase/inventory_management_api/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_codebase/inventory_management_api/routes/cart.py b/python_codebase/inventory_management_api/routes/cart.py new file mode 100644 index 0000000..3f2e08e --- /dev/null +++ b/python_codebase/inventory_management_api/routes/cart.py @@ -0,0 +1,60 @@ +from fastapi import APIRouter, Depends, HTTPException +from models import User, Cart, CartItem, Product +from authentication import get_current_user + +router = APIRouter() + + +@router.post("/cart/add") +async def add_to_cart( + product_id: int, quantity: int, user: User = Depends(get_current_user) +): + product = await Product.get(id=product_id) + cart, _ = await Cart.get_or_create(user=user) + cart_item, created = await CartItem.get_or_create(cart=cart, product=product) + + if not created: + cart_item.quantity += quantity + else: + cart_item.quantity = quantity + + cart_item.price = product.new_price + await cart_item.save() + return {"message": "Item added to cart"} + + +@router.get("/cart") +async def view_cart(user: User = Depends(get_current_user)): + cart, _ = await Cart.get_or_create(user=user) + items = await CartItem.filter(cart=cart).prefetch_related("product") + total = sum(item.quantity * item.price for item in items) + return { + "items": [ + { + "product": item.product.name, + "quantity": item.quantity, + "price": item.price, + "total": item.quantity * item.price, + } + for item in items + ], + "total": total, + } + + +@router.put("/cart/update/{item_id}") +async def update_cart_item( + item_id: int, quantity: int, user: User = Depends(get_current_user) +): + cart = await Cart.get(user=user) + item = await CartItem.get(id=item_id, cart=cart) + item.quantity = quantity + await item.save() + return {"message": "Cart item updated"} + + +@router.delete("/cart/remove/{item_id}") +async def remove_from_cart(item_id: int, user: User = Depends(get_current_user)): + cart = await Cart.get(user=user) + await CartItem.filter(id=item_id, cart=cart).delete() + return {"message": "Item removed from cart"} diff --git a/python_codebase/inventory_management_api/routes/inventory.py b/python_codebase/inventory_management_api/routes/inventory.py new file mode 100644 index 0000000..a557cf9 --- /dev/null +++ b/python_codebase/inventory_management_api/routes/inventory.py @@ -0,0 +1,122 @@ +from flask import Blueprint, request, jsonify +import mysql.connector +from cache import get_cache +from db import get_shards, get_shard, execute_query +from rate_limiter import limiter + + +class InventoryRoutes: + def __init__(self): + self.shards = get_shards() + self.cache = get_cache() + + def get_inventory(self): + try: + product_id = request.args.get("product_id") + page = int(request.args.get("page", 1)) + per_page = int(request.args.get("per_page", 10)) + + offset = (page - 1) * per_page + cache_key = f"inventory_{product_id}_{page}_{per_page}" + cached_inventory = self.cache.get(cache_key) + + if cached_inventory: + return jsonify({"inventory": cached_inventory.decode("utf-8")}) + + shard = get_shard(int(product_id), self.shards) + query = f""" + SELECT + i.product_id, i.quantity, p.name AS product_name, c.name AS category_name + FROM + inventory i + JOIN + products p ON i.product_id = p.id + JOIN + categories c ON p.category_id = c.id + WHERE + i.product_id = %s + LIMIT %s OFFSET %s + """ + cursor = execute_query(shard, query, (product_id, per_page, offset)) + inventory = cursor.fetchall() + + if inventory: + self.cache.setex(cache_key, 300, str(inventory)) # Cache for 5 minutes + return jsonify({"inventory": inventory}) + + return jsonify({"error": "Product not found"}), 404 + except mysql.connector.Error as err: + return jsonify({"error": str(err)}), 500 + + def add_inventory(self): + try: + data = request.json + product_id = data.get("product_id") + quantity = data.get("quantity") + + if not product_id or not quantity: + return jsonify({"error": "Invalid data"}), 400 + + shard = get_shard(int(product_id), self.shards) + query = "INSERT INTO inventory (product_id, quantity) VALUES (%s, %s)" + cursor = execute_query(shard, query, (product_id, quantity)) + shard.commit() + + return jsonify({"message": "Inventory added successfully"}), 201 + except mysql.connector.Error as err: + return jsonify({"error": str(err)}), 500 + + def update_inventory(self): + try: + data = request.json + product_id = data.get("product_id") + quantity = data.get("quantity") + + if not product_id or not quantity: + return jsonify({"error": "Invalid data"}), 400 + + shard = get_shard(int(product_id), self.shards) + query = "UPDATE inventory SET quantity = %s WHERE product_id = %s" + cursor = execute_query(shard, query, (quantity, product_id)) + shard.commit() + + # Invalidate cache after update + self.cache.delete(f"inventory_{product_id}") + + return jsonify({"message": "Inventory updated successfully"}), 200 + except mysql.connector.Error as err: + return jsonify({"error": str(err)}), 500 + + def delete_inventory(self): + try: + data = request.json + product_id = data.get("product_id") + + if not product_id: + return jsonify({"error": "Invalid data"}), 400 + + shard = get_shard(int(product_id), self.shards) + query = "DELETE FROM inventory WHERE product_id = %s" + cursor = execute_query(shard, query, (product_id,)) + shard.commit() + + # Invalidate cache after delete + self.cache.delete(f"inventory_{product_id}") + + # Confirm deletion + if cursor.rowcount == 0: + return jsonify({"error": "Product not found"}), 404 + + return jsonify({"message": "Inventory deleted successfully"}), 200 + except mysql.connector.Error as err: + return jsonify({"error": str(err)}), 500 + + +inventory_routes = InventoryRoutes() + +inventory_bp = Blueprint("inventory", __name__) + +inventory_bp.route("/", methods=["GET"])(inventory_routes.get_inventory) +inventory_bp.route("/add", methods=["POST"])(inventory_routes.add_inventory) +inventory_bp.route("/update", methods=["POST"])(inventory_routes.update_inventory) +inventory_bp.route("/delete", methods=["POST"])(inventory_routes.delete_inventory) diff --git a/python_codebase/inventory_management_api/routes/recommendation.py b/python_codebase/inventory_management_api/routes/recommendation.py new file mode 100644 index 0000000..cbffc3f --- /dev/null +++ b/python_codebase/inventory_management_api/routes/recommendation.py @@ -0,0 +1,64 @@ +from collections import defaultdict +from db import execute_query, get_shards, get_shard + + +class RecommendationEngine: + def __init__(self): + self.shards = get_shards() + + def get_recommendations(self, user_id, num_recommendations=5): + try: + product_counts = defaultdict(int) + + # Fetch past orders of the user + for shard in self.shards: + query = """ + SELECT + oi.product_id, COUNT(*) AS count + FROM + orders o + JOIN + order_items oi ON o.id = oi.order_id + WHERE + o.user_id = %s + GROUP BY + oi.product_id + """ + cursor = execute_query(shard, query, (user_id,)) + results = cursor.fetchall() + + for product_id, count in results: + product_counts[product_id] += count + + # Fetch recent search history of the user + for shard in self.shards: + query = """ + SELECT + product_id + FROM + search_history + WHERE + user_id = %s + ORDER BY + search_time DESC + LIMIT %s + """ + cursor = execute_query(shard, query, (user_id, num_recommendations)) + results = cursor.fetchall() + + for (product_id,) in results: + product_counts[ + product_id + ] += 1 # Increment count for searched products + + # Aggregate recommendations from past orders and recent searches + recommendations = sorted( + product_counts.items(), key=lambda x: x[1], reverse=True + )[:num_recommendations] + return [product_id for product_id, _ in recommendations] + except Exception as e: + print(f"Error occurred while fetching recommendations: {str(e)}") + return [] + + +recommendation_engine = RecommendationEngine() diff --git a/python_codebase/main.py b/python_codebase/main.py index add5f63..b6e67bf 100644 --- a/python_codebase/main.py +++ b/python_codebase/main.py @@ -1,20 +1,32 @@ - -from fastapi import (FastAPI, status, Request, - HTTPException, Depends, Query) +from fastapi import FastAPI, status, Request, HTTPException, Depends, Query from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates + # database from tortoise.contrib.fastapi import register_tortoise -from models import (User, Business, Product, - user_pydantic, user_pydanticIn, user_pydanticOut, - business_pydantic, business_pydanticIn, - product_pydantic, product_pydanticIn) +from models import ( + User, + Business, + Product, + user_pydantic, + user_pydanticIn, + user_pydanticOut, + business_pydantic, + business_pydanticIn, + product_pydantic, + product_pydanticIn, +) from datetime import datetime + # authentication -from authentication import (get_hashed_password, - very_token, very_token_email, - is_not_email, token_generator) -from fastapi.security import (OAuth2PasswordBearer, OAuth2PasswordRequestForm) +from authentication import ( + get_hashed_password, + very_token, + very_token_email, + is_not_email, + token_generator, +) +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm # signal from tortoise.signals import post_save @@ -32,11 +44,21 @@ # env file from config import get_settings + SITE_URL = get_settings().SITE_URL -app = FastAPI(title="E-commerce API", version="0.1.1", - description=" E-commerce API created with FastAPI and jwt Authenticated") +# add cart +from routes import cart + +app.include_router(cart.router, prefix="/api", tags=["Cart"]) + + +app = FastAPI( + title="E-commerce API", + version="0.1.1", + description=" E-commerce API created with FastAPI and jwt Authenticated", +) oauth_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -60,7 +82,7 @@ async def client_data(user: user_pydanticIn = Depends(get_current_user)): business = await Business.get(owner=user) logo = business.logo - logo = f'{SITE_URL}{logo}' + logo = f"{SITE_URL}{logo}" return { "status": "ok", @@ -70,41 +92,45 @@ async def client_data(user: user_pydanticIn = Depends(get_current_user)): "is_verified": user.is_verified, "join_date": user.join_date.strftime("%b %d %Y"), "logo": logo, - "business": await business_pydantic.from_tortoise_orm(business) - } + "business": await business_pydantic.from_tortoise_orm(business), + }, } @app.get("/users/", tags=["User"], response_model=List[user_pydanticOut]) -async def get_users(user: user_pydanticIn = Depends(get_current_user), - limit: int = Query(100, le=100), - skip: int = Query(0, ge=0) - ): +async def get_users( + user: user_pydanticIn = Depends(get_current_user), + limit: int = Query(100, le=100), + skip: int = Query(0, ge=0), +): - users = await User.filter(id__gt=skip, id__lte=skip+limit) + users = await User.filter(id__gt=skip, id__lte=skip + limit) return users @post_save(User) async def create_business( - sender: "Type[User]", - instance: User, - created: bool, - using_db: "Optional[BaseDBAsyncClient]", - update_fields: List[str]) -> None: + sender: "Type[User]", + instance: User, + created: bool, + using_db: "Optional[BaseDBAsyncClient]", + update_fields: List[str], +) -> None: if created: business_obj = await Business.create( - business_name=instance.username, - owner=instance) + business_name=instance.username, owner=instance + ) await business_pydantic.from_tortoise_orm(business_obj) # send the email await send_mail([instance.email], instance) @app.put("/business/{id}", response_model=business_pydantic, tags=["Business"]) -async def update_business(id: int, - update_business: business_pydanticIn, - user: user_pydantic = Depends(get_current_user)): +async def update_business( + id: int, + update_business: business_pydanticIn, + user: user_pydantic = Depends(get_current_user), +): business = await Business.get(id=id) owner = await business.owner @@ -119,11 +145,16 @@ async def update_business(id: int, raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated to perform this action", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) -@app.post("/users/", tags=["User"], status_code=status.HTTP_201_CREATED, response_model=user_pydanticOut) +@app.post( + "/users/", + tags=["User"], + status_code=status.HTTP_201_CREATED, + response_model=user_pydanticOut, +) async def user_registration(user: user_pydanticIn): user_info = user.dict(exclude_unset=True) @@ -131,20 +162,27 @@ async def user_registration(user: user_pydanticIn): # TODO fix this, create custom 'user_pydanticIn for validate data ' if len(user_info["password"]) < 8: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Password must be longer than 8 characters") + status_code=status.HTTP_400_BAD_REQUEST, + detail="Password must be longer than 8 characters", + ) if len(user_info["username"]) < 5: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Username must be longer than 5 characters") + status_code=status.HTTP_400_BAD_REQUEST, + detail="Username must be longer than 5 characters", + ) if is_not_email(user_info["email"]): raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="This is not a valid email") + status_code=status.HTTP_400_BAD_REQUEST, detail="This is not a valid email" + ) if await User.exists(username=user_info.get("username")): raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Username already exists") + status_code=status.HTTP_400_BAD_REQUEST, detail="Username already exists" + ) if await User.exists(email=user_info.get("email")): raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="Email already exists") + status_code=status.HTTP_400_BAD_REQUEST, detail="Email already exists" + ) user_info["password"] = get_hashed_password(user_info["password"]) @@ -166,20 +204,22 @@ async def email_verification(request: Request, token: str): context = { "request": request, "is_verified": user.is_verified, - "username": user.username + "username": user.username, } return template.TemplateResponse("verification.html", context) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Token or expired token", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) @app.post("/uploadfile/profile", tags=["User"]) -async def upload_profile_image(file: UploadFile = File(..., max_lenght=10485760), - user: user_pydantic = Depends(get_current_user)): +async def upload_profile_image( + file: UploadFile = File(..., max_lenght=10485760), + user: user_pydantic = Depends(get_current_user), +): FILEPATH = "./static/images/" file_name = file.filename @@ -188,8 +228,10 @@ async def upload_profile_image(file: UploadFile = File(..., max_lenght=10485760) extension = file_name.split(".")[1] finally: if extension not in ["png", "jpg", "jpeg"]: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, - detail="File extension not allowed") + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="File extension not allowed", + ) token_name = "logo" + secrets.token_hex(10) + "." + extension generated_name = FILEPATH + token_name @@ -214,15 +256,16 @@ async def upload_profile_image(file: UploadFile = File(..., max_lenght=10485760) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated to perform this action", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) @app.post("/uploadfile/product/{id}", tags=["Product"]) async def upload_product_image( - id: int, - file: UploadFile = File(..., max_lenght=10485760), - user: user_pydantic = Depends(get_current_user)): + id: int, + file: UploadFile = File(..., max_lenght=10485760), + user: user_pydantic = Depends(get_current_user), +): FILEPATH = "./static/images/" file_name = file.filename @@ -231,8 +274,10 @@ async def upload_product_image( extension = file_name.split(".")[1] finally: if extension not in ["png", "jpg", "jpeg"]: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, - detail="File extension not allowed") + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="File extension not allowed", + ) token_name = "product" + secrets.token_hex(10) + "." + extension generated_name = FILEPATH + token_name @@ -247,8 +292,7 @@ async def upload_product_image( owner = await business.owner else: raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Product Not Found" + status_code=status.HTTP_404_NOT_FOUND, detail="Product Not Found" ) if owner == user: product.product_image = generated_name[1:] @@ -258,26 +302,31 @@ async def upload_product_image( raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated to perform this action", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) @app.post("/products/", tags=["Product"], response_model=product_pydantic) -async def add_new_product(product: product_pydanticIn, - user: user_pydantic = Depends(get_current_user)): +async def add_new_product( + product: product_pydanticIn, user: user_pydantic = Depends(get_current_user) +): product = product.dict(exclude_unset=True) if product["original_price"] > 0: product["percentage_discount"] = ( - (product["original_price"] - product["new_price"]) / product["original_price"]) * 100 + (product["original_price"] - product["new_price"]) + / product["original_price"] + ) * 100 product_obj = await Product.create(**product, business=user) product_obj = await product_pydantic.from_tortoise_orm(product_obj) return product_obj else: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, - detail="The original price must be greater than 0") + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The original price must be greater than 0", + ) @app.delete("/products/{id}", tags=["Product"], status_code=status.HTTP_204_NO_CONTENT) @@ -293,21 +342,25 @@ async def delete_product(id: int, user: user_pydantic = Depends(get_current_user raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated to perform this action", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) @app.put("/products/{id}", tags=["Product"]) -async def update_product(id: int, - updated_product: product_pydanticIn, - user: user_pydantic = Depends(get_current_user)): +async def update_product( + id: int, + updated_product: product_pydanticIn, + user: user_pydantic = Depends(get_current_user), +): product = await Product.get(id=id) business = await product.business owner = await business.owner updated_product = updated_product.dict(exclude_unset=True) updated_product["percentage_discount"] = ( - (updated_product["original_price"] - updated_product["new_price"]) / updated_product["original_price"]) * 100 + (updated_product["original_price"] - updated_product["new_price"]) + / updated_product["original_price"] + ) * 100 if user == owner and updated_product["original_price"] > 0: await product.update_from_dict(updated_product) @@ -317,15 +370,16 @@ async def update_product(id: int, raise HTTPException( status_code=status.HTTP_400_UNAUTHORIZED, detail="Not authenticated to perform this action or Invalid user input", - headers={"WWW-Authenticate": "Bearer"} + headers={"WWW-Authenticate": "Bearer"}, ) @app.get("/products", tags=["Product"], response_model=List[product_pydantic]) -async def get_product_list(limit: int = Query(100, le=100), - skip: int = Query(0, ge=0)): +async def get_product_list(limit: int = Query(100, le=100), skip: int = Query(0, ge=0)): - response = await product_pydantic.from_queryset(Product.filter(id__gt=skip, id__lte=skip+limit)) + response = await product_pydantic.from_queryset( + Product.filter(id__gt=skip, id__lte=skip + limit) + ) return response @@ -344,11 +398,11 @@ async def get_product_detail(id: int): "city": business.city, "region": business.region, "description": business.business_description, - "logo": f'{SITE_URL}{business.logo}', + "logo": f"{SITE_URL}{business.logo}", "owner_id": owner.id, "email": owner.email, - "join_date": owner.join_date.strftime("%b %d %Y") - } + "join_date": owner.join_date.strftime("%b %d %Y"), + }, } @@ -357,5 +411,5 @@ async def get_product_detail(id: int): db_url="sqlite://db.sqlite3", modules={"models": ["models"]}, generate_schemas=True, - add_exception_handlers=True + add_exception_handlers=True, ) diff --git a/python_codebase/models.py b/python_codebase/models.py index cb6e61e..b15afe3 100644 --- a/python_codebase/models.py +++ b/python_codebase/models.py @@ -16,8 +16,7 @@ class Business(Model): id = fields.IntField(pk=True, index=True) business_name = fields.CharField(max_length=30, null=False, unique=True) city = fields.CharField(max_length=100, null=False, default="Unspecified") - region = fields.CharField( - max_length=100, null=False, default="Unspecified") + region = fields.CharField(max_length=100, null=False, default="Unspecified") business_description = fields.TextField(null=True) logo = fields.CharField(max_length=200, null=False, default="default.jpg") owner = fields.ForeignKeyField("models.User", related_name="business") @@ -32,26 +31,51 @@ class Product(Model): percentage_discount = fields.IntField() offer_expiration_date = fields.DateField(default=datetime.utcnow) product_image = fields.CharField( - max_length=200, null=False, default="productDefault.jpg") + max_length=200, null=False, default="productDefault.jpg" + ) date_published = fields.DatetimeField(default=datetime.utcnow) - business = fields.ForeignKeyField( - "models.Business", related_name="product") + business = fields.ForeignKeyField("models.Business", related_name="product") -user_pydantic = pydantic_model_creator( - User, name="User", exclude=("is_verifide", )) +user_pydantic = pydantic_model_creator(User, name="User", exclude=("is_verifide",)) user_pydanticIn = pydantic_model_creator( - User, name="UserIn", exclude_readonly=True, exclude=("is_verifide", "join_date")) + User, name="UserIn", exclude_readonly=True, exclude=("is_verifide", "join_date") +) -user_pydanticOut = pydantic_model_creator( - User, name="UserOut", exclude=("password", )) +user_pydanticOut = pydantic_model_creator(User, name="UserOut", exclude=("password",)) business_pydantic = pydantic_model_creator(Business, name="Business") business_pydanticIn = pydantic_model_creator( - Business, name="BusinessIn", exclude_readonly=True, exclude=("logo", )) + Business, name="BusinessIn", exclude_readonly=True, exclude=("logo",) +) product_pydantic = pydantic_model_creator(Product, name="Product") product_pydanticIn = pydantic_model_creator( - Product, name="ProductIn", exclude=("percentage_discount", "id", "product_image", "date_published")) + Product, + name="ProductIn", + exclude=("percentage_discount", "id", "product_image", "date_published"), +) + + +class Cart(Model): + id = fields.IntField(pk=True, index=True) + user = fields.ForeignKeyField("models.User", related_name="cart") + created_at = fields.DatetimeField(default=datetime.utcnow) + updated_at = fields.DatetimeField(default=datetime.utcnow) + + +class CartItem(Model): + id = fields.IntField(pk=True, index=True) + cart = fields.ForeignKeyField("models.Cart", related_name="items") + product = fields.ForeignKeyField("models.Product", related_name="cart_items") + quantity = fields.IntField(default=1) + price = fields.DecimalField(max_digits=10, decimal_places=2) + + +class Wishlist(Model): + id = fields.IntField(pk=True, index=True) + user = fields.ForeignKeyField("models.User", related_name="wishlist") + product = fields.ForeignKeyField("models.Product", related_name="wishlists") + created_at = fields.DatetimeField(default=datetime.utcnow)