""" 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 logging import getLogger import time from typing import Optional # Third-party imports from fastapi import APIRouter, Depends, Query # 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 utils.responses import error_response, success_response from services.fudo_service import get_products_by_table # 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 = await product_data_service.get_all() all_products = list(map(apply_promo_price, all_products)) if status is not None: 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: str, 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: user_data_service.set_reward_progress(current_user.id, 0) 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(product.model_dump(), message= SuccessResponse.PRODUCT_CREATE_SUCCESS, 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: str, 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: str, 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: str, 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(data=edited_product.model_dump(), message=SuccessResponse.PRODUCT_EDIT_SUCCESS) # Return 403 if user lacks permissions return error_response(message=UserResponse.NOT_PERMITTED, status_code=403) @product_router.get("/table/{table_number}") async def get_table_items(table_number: int, _: User = Depends(get_current_user)): """Get items for a specific table""" logger.info(f"Fetching items for table {table_number}") # Retrieve items for table items = get_products_by_table(table_number) if not items: return error_response(message=ErrorResponse.SALE_NOT_FOUND, status_code=404) logger.info(f"Items for table {table_number} retrieved successfully") return success_response(data=items, message=SuccessResponse.PRODUCTS_FETCH_SUCCESS)