| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- """
- Product Routes Module
- This module defines all API endpoints for product management including:
- - Fetching products (all users)
- - Creating/editing products (permissions >= 1)
- - Deleting products (permissions == 2 only)
- Permission levels:
- - 0: Regular user (read-only access)
- - 1: Manager (can create/edit products)
- - 2: Admin (full access including delete)
- """
- # Standard library imports
- from math import prod
- from logging import getLogger
- import time
- from typing import Optional
- # Third-party imports
- from fastapi import APIRouter, Depends, Query
- from fastapi.responses import JSONResponse
- from h11 import Data
- # Local imports
- from auth.security import get_current_user
- from models.user import User
- from models.items import Product, ProductCreateRequest, ProductEditRequest
- from services.data_service import DataServiceFactory
- from config.messages import ErrorResponse, SuccessResponse, UserResponse
- from services.print_service import print_ticket
- from utils.responses import error_response, success_response
- # Initialize logger for this module
- logger = getLogger(__name__)
- # Initialize data services for products and users
- product_data_service = DataServiceFactory.get_product_service()
- user_data_service = DataServiceFactory.get_user_service()
- # Create router instance for product-related endpoints
- product_router = APIRouter()
- def apply_promo_price(product: Product):
- """Apply promotional price to a product if applicable."""
- #dia de la semana 1-7
- day_of_week = time.localtime().tm_wday + 1 # Convert to 1-7 range
- product_dict = product.model_dump(exclude={"promo_id", "promo_price", "promo_day"})
- if product.promo_id and product.promo_price and product.promo_day == day_of_week:
- product_dict['promotion'] = {
- "id": product.promo_id,
- "price": product.promo_price,
- }
- return product_dict
- @product_router.get("/")
- async def get_products(status: Optional[int] = Query(None), current_user = Depends(get_current_user)):
- """
- Get all products - Available to all authenticated users
-
- Returns:
- JSONResponse: List of all products with success message
- """
- logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
- logger.info("Fetching all products")
-
- # Retrieve all products and convert to dictionary format
- all_products = product_data_service.get_all()
- all_products = list(map(apply_promo_price, all_products))
- if status is not None:
- # Filter products by status if provided
- all_products = [product for product in all_products if product['status'] == status]
- return success_response({"products": all_products, "message": SuccessResponse.PRODUCTS_FETCH_SUCCESS})
- @product_router.get("/{product_id}")
- async def get_product(product_id: int, current_user = Depends(get_current_user)):
- """
- Get a specific product by ID - Available to all authenticated users
-
- Args:
- product_id (int): The ID of the product to retrieve
- current_user: Authenticated user (dependency injection)
-
- Returns:
- JSONResponse: Product data if found, error message if not found
- """
- logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
- logger.info(f"Fetching product with ID: {product_id}")
-
- # Attempt to find product by ID
- product = product_data_service.get_by_id(product_id)
- if product:
- return success_response({"product": product.model_dump(exclude={"promo_id", "promo_price", "promo_day"}), "message": SuccessResponse.PRODUCTS_FETCH_SUCCESS})
- # Return 404 if product not found
- return error_response({"message": UserResponse.USER_NOT_FOUND.format(user_id=product_id)}, status_code=404)
- @product_router.get("/free-beer/{table_id}")
- async def get_free_beer(table_id: int, current_user:User = Depends(get_current_user)):
- """
- Get the free beer product - Available to all authenticated users
-
- Returns:
- JSONResponse: Free beer product data if found, error message if not found
- """
- logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
- logger.info("Fetching free beer ticket")
-
- if current_user.reward_progress >= 100:
- print_ticket(table_id)
- return success_response({"message": SuccessResponse.REWARD_SUCCESS}, status_code=200)
- # Return 404 if free beer product not found
- return error_response({"message": UserResponse.USER_NOT_FOUND.format(user_id="free_beer")}, status_code=404)
- # MODERATE RISK OPERATIONS - Requires permissions >= 1 (Manager level or above)
- @product_router.post("/create")
- async def create_product(product: ProductCreateRequest, current_user = Depends(get_current_user)):
- """
- Create a new product - Requires manager permissions (level >= 1)
-
- Args:
- product (ProductCreateRequest): New product data
- current_user: Authenticated user (dependency injection)
-
- Returns:
- JSONResponse: Success message or permission denied message
- """
- logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
- logger.info("Creating a new product")
-
- # Check if user has sufficient permissions (manager level or above)
- if user_data_service.permissions(current_user.id) > 0:
- # Create new product with provided data
- product_data_service.create(**product.model_dump(exclude_unset=True))
- return success_response({"message": SuccessResponse.PRODUCT_CREATE_SUCCESS, "product": product.model_dump()}, status_code=201)
-
- # Return 403 if user lacks permissions
- return error_response({"message": UserResponse.NOT_PERMITTED}, status_code=403)
- @product_router.patch("/{product_id}/swap-status")
- async def switch_product_status(product_id: int, current_user = Depends(get_current_user)):
- """
- Toggle product status between active/inactive - Requires manager permissions (level >= 1)
-
- Args:
- product_id (int): ID of the product to update
- status (int): New status value (0=inactive, 1=active)
- current_user: Authenticated user (dependency injection)
-
- Returns:
- JSONResponse: Success message or permission denied message
- """
- logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
- logger.info(f"Switching status for product with ID: {product_id}")
-
- # Check if user has sufficient permissions (manager level or above)
- if user_data_service.permissions(current_user.id) > 0:
- # Update only the status field of the specified product
- product = product_data_service.get_by_id(product_id)
- if not product:
- return error_response({"message": ErrorResponse.PRODDUCT_NOT_FOUND.format(product_id=product_id)}, status_code=404)
- status = 0 if product.status == 1 else 1
- product_data_service.update(product_id, status=status)
- return success_response({"message": SuccessResponse.PRODUCT_EDIT_SUCCESS})
-
- # Return 403 if user lacks permissions
- return error_response({"message": UserResponse.NOT_PERMITTED}, status_code=403)
- # HIGH RISK OPERATIONS - Requires permissions == 2 (Admin level only)
- @product_router.delete("/{product_id}")
- async def delete_product(product_id: int, current_user = Depends(get_current_user)):
- """
- Delete a product permanently - Requires admin permissions (level == 2)
-
- This is a high-risk operation that permanently removes product data.
- Only users with admin-level permissions can perform this action.
-
- Args:
- product_id (int): ID of the product to delete
- current_user: Authenticated user (dependency injection)
-
- Returns:
- JSONResponse: Success message or permission denied message
- """
- logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
- logger.info(f"Deleting product with ID: {product_id}")
-
- # Check if user has admin permissions (exactly level 2)
- if user_data_service.permissions(current_user.id) == 2:
- # Permanently delete the product
- product_data_service.delete(product_id)
- return success_response({"message": SuccessResponse.PRODUCT_DELETE_SUCCESS})
-
- # Return 403 if user lacks admin permissions
- return error_response({"message": UserResponse.NOT_PERMITTED}, status_code=403)
- @product_router.patch("/{product_id}/edit")
- async def edit_product(product_id: int, product: ProductEditRequest, current_user = Depends(get_current_user)):
- """
- Edit an existing product - Requires manager permissions (level >= 1)
-
- Args:
- product (ProductEditRequest): Product data to update
- current_user: Authenticated user (dependency injection)
-
- Returns:
- JSONResponse: Updated product data or permission denied message
- """
- logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
- logger.info(f"Editing product: {product_id}")
-
- # Check if user has sufficient permissions (manager level or above)
- if user_data_service.permissions(current_user.id) > 1:
- # Update product with provided data (excluding unset fields)
- product_data_service.update(product_id, **product.model_dump(exclude_unset=True))
-
- # Retrieve updated product to return in response
- edited_product = product_data_service.get_by_id(product_id)
- if not edited_product:
- return error_response({"message": UserResponse.USER_NOT_FOUND.format(user_id=product_id)}, status_code=404)
-
- logger.info(f"Product {product_id} edited successfully")
- return success_response({"message": SuccessResponse.PRODUCT_EDIT_SUCCESS, "product": edited_product.model_dump()})
-
- # Return 403 if user lacks permissions
- return error_response({"message": UserResponse.NOT_PERMITTED}, status_code=403)
|