Exception handling enhancement

This commit is contained in:
2025-09-23 22:42:26 +02:00
parent b1a76cdb57
commit 98285aa8aa
35 changed files with 3283 additions and 1743 deletions

View File

@@ -7,7 +7,7 @@ This module provides classes and functions for:
- ....
"""
from fastapi import Depends, HTTPException
from fastapi import Depends
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session
@@ -16,6 +16,7 @@ from middleware.auth import AuthManager
from middleware.rate_limiter import RateLimiter
from models.database.shop import Shop
from models.database.user import User
from app.exceptions import (AdminRequiredException,ShopNotFoundException, UnauthorizedShopAccessException)
# Set auto_error=False to prevent automatic 403 responses
security = HTTPBearer(auto_error=False)
@@ -30,11 +31,13 @@ def get_current_user(
"""Get current authenticated user."""
# Check if credentials are provided
if not credentials:
raise HTTPException(status_code=401, detail="Authorization header required")
from app.exceptions.auth import InvalidTokenException
raise InvalidTokenException("Authorization header required")
return auth_manager.get_current_user(db, credentials)
def get_current_admin_user(current_user: User = Depends(get_current_user)):
"""Require admin user."""
return auth_manager.require_admin(current_user)
@@ -48,9 +51,10 @@ def get_user_shop(
"""Get shop and verify user ownership."""
shop = db.query(Shop).filter(Shop.shop_code == shop_code.upper()).first()
if not shop:
raise HTTPException(status_code=404, detail="Shop not found")
raise ShopNotFoundException(shop_code)
if current_user.role != "admin" and shop.owner_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied to this shop")
raise UnauthorizedShopAccessException(shop_code, current_user.id)
return shop

View File

@@ -1,16 +1,18 @@
# app/api/v1/admin.py
"""Summary description ....
"""
Admin endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- ....
- ....
- ....
- User management (view, toggle status)
- Shop management (view, verify, toggle status)
- Marketplace import job monitoring
- Admin dashboard statistics
"""
import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_user
@@ -25,7 +27,6 @@ router = APIRouter()
logger = logging.getLogger(__name__)
# Admin-only routes
@router.get("/admin/users", response_model=List[UserResponse])
def get_all_users(
skip: int = Query(0, ge=0),
@@ -34,12 +35,8 @@ def get_all_users(
current_admin: User = Depends(get_current_admin_user),
):
"""Get all users (Admin only)."""
try:
users = admin_service.get_all_users(db=db, skip=skip, limit=limit)
return [UserResponse.model_validate(user) for user in users]
except Exception as e:
logger.error(f"Error getting users: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
users = admin_service.get_all_users(db=db, skip=skip, limit=limit)
return [UserResponse.model_validate(user) for user in users]
@router.put("/admin/users/{user_id}/status")
@@ -49,14 +46,8 @@ def toggle_user_status(
current_admin: User = Depends(get_current_admin_user),
):
"""Toggle user active status (Admin only)."""
try:
user, message = admin_service.toggle_user_status(db, user_id, current_admin.id)
return {"message": message}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error toggling user {user_id} status: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
user, message = admin_service.toggle_user_status(db, user_id, current_admin.id)
return {"message": message}
@router.get("/admin/shops", response_model=ShopListResponse)
@@ -67,13 +58,8 @@ def get_all_shops_admin(
current_admin: User = Depends(get_current_admin_user),
):
"""Get all shops with admin view (Admin only)."""
try:
shops, total = admin_service.get_all_shops(db=db, skip=skip, limit=limit)
return ShopListResponse(shops=shops, total=total, skip=skip, limit=limit)
except Exception as e:
logger.error(f"Error getting shops: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
shops, total = admin_service.get_all_shops(db=db, skip=skip, limit=limit)
return ShopListResponse(shops=shops, total=total, skip=skip, limit=limit)
@router.put("/admin/shops/{shop_id}/verify")
@@ -83,14 +69,8 @@ def verify_shop(
current_admin: User = Depends(get_current_admin_user),
):
"""Verify/unverify shop (Admin only)."""
try:
shop, message = admin_service.verify_shop(db, shop_id)
return {"message": message}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error verifying shop {shop_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
shop, message = admin_service.verify_shop(db, shop_id)
return {"message": message}
@router.put("/admin/shops/{shop_id}/status")
@@ -100,14 +80,8 @@ def toggle_shop_status(
current_admin: User = Depends(get_current_admin_user),
):
"""Toggle shop active status (Admin only)."""
try:
shop, message = admin_service.toggle_shop_status(db, shop_id)
return {"message": message}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error toggling shop {shop_id} status: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
shop, message = admin_service.toggle_shop_status(db, shop_id)
return {"message": message}
@router.get(
@@ -123,15 +97,29 @@ def get_all_marketplace_import_jobs(
current_admin: User = Depends(get_current_admin_user),
):
"""Get all marketplace import jobs (Admin only)."""
try:
return admin_service.get_marketplace_import_jobs(
db=db,
marketplace=marketplace,
shop_name=shop_name,
status=status,
skip=skip,
limit=limit,
)
except Exception as e:
logger.error(f"Error getting marketplace import jobs: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return admin_service.get_marketplace_import_jobs(
db=db,
marketplace=marketplace,
shop_name=shop_name,
status=status,
skip=skip,
limit=limit,
)
@router.get("/admin/stats/users")
def get_user_statistics(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get user statistics for admin dashboard (Admin only)."""
return admin_service.get_user_statistics(db)
@router.get("/admin/stats/shops")
def get_shop_statistics(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get shop statistics for admin dashboard (Admin only)."""
return admin_service.get_shop_statistics(db)

View File

@@ -1,15 +1,16 @@
# app/api/v1/auth.py
"""Summary description ....
"""
Authentication endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- ....
- ....
- ....
- User registration and validation
- User authentication and JWT token generation
- Current user information retrieval
"""
import logging
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
@@ -23,37 +24,24 @@ router = APIRouter()
logger = logging.getLogger(__name__)
# Authentication Routes
@router.post("/auth/register", response_model=UserResponse)
def register_user(user_data: UserRegister, db: Session = Depends(get_db)):
"""Register a new user."""
try:
user = auth_service.register_user(db=db, user_data=user_data)
return UserResponse.model_validate(user)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error registering user: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
user = auth_service.register_user(db=db, user_data=user_data)
return UserResponse.model_validate(user)
@router.post("/auth/login", response_model=LoginResponse)
def login_user(user_credentials: UserLogin, db: Session = Depends(get_db)):
"""Login user and return JWT token."""
try:
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
return LoginResponse(
access_token=login_result["token_data"]["access_token"],
token_type=login_result["token_data"]["token_type"],
expires_in=login_result["token_data"]["expires_in"],
user=UserResponse.model_validate(login_result["user"]),
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error logging in user: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return LoginResponse(
access_token=login_result["token_data"]["access_token"],
token_type=login_result["token_data"]["token_type"],
expires_in=login_result["token_data"]["expires_in"],
user=UserResponse.model_validate(login_result["user"]),
)
@router.get("/auth/me", response_model=UserResponse)

View File

@@ -1,16 +1,17 @@
# app/api/v1/marketplace.py
"""Summary description ....
"""
Marketplace endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- ....
- ....
- ....
- Product import from marketplace CSV files
- Import job management and monitoring
- Import statistics and job cancellation
"""
import logging
from typing import List, Optional
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query
from fastapi import APIRouter, BackgroundTasks, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
@@ -26,7 +27,6 @@ router = APIRouter()
logger = logging.getLogger(__name__)
# Marketplace Import Routes (Protected)
@router.post("/marketplace/import-product", response_model=MarketplaceImportJobResponse)
@rate_limit(max_requests=10, window_seconds=3600) # Limit marketplace imports
async def import_products_from_marketplace(
@@ -36,42 +36,33 @@ async def import_products_from_marketplace(
current_user: User = Depends(get_current_user),
):
"""Import products from marketplace CSV with background processing (Protected)."""
try:
logger.info(
f"Starting marketplace import: {request.marketplace} -> {request.shop_code} by user {current_user.username}"
)
logger.info(
f"Starting marketplace import: {request.marketplace} -> {request.shop_code} by user {current_user.username}"
)
# Create import job through service
import_job = marketplace_service.create_import_job(db, request, current_user)
# Create import job through service
import_job = marketplace_service.create_import_job(db, request, current_user)
# Process in background
background_tasks.add_task(
process_marketplace_import,
import_job.id,
request.url,
request.marketplace,
request.shop_code,
request.batch_size or 1000,
)
# Process in background
background_tasks.add_task(
process_marketplace_import,
import_job.id,
request.url,
request.marketplace,
request.shop_code,
request.batch_size or 1000,
)
return MarketplaceImportJobResponse(
job_id=import_job.id,
status="pending",
marketplace=request.marketplace,
shop_code=request.shop_code,
shop_id=import_job.shop_id,
shop_name=import_job.shop_name,
message=f"Marketplace import started from {request.marketplace}. Check status with "
f"/import-status/{import_job.id}",
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
logger.error(f"Error starting marketplace import: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return MarketplaceImportJobResponse(
job_id=import_job.id,
status="pending",
marketplace=request.marketplace,
shop_code=request.shop_code,
shop_id=import_job.shop_id,
shop_name=import_job.shop_name,
message=f"Marketplace import started from {request.marketplace}. Check status with "
f"/import-status/{import_job.id}",
)
@router.get(
@@ -83,17 +74,8 @@ def get_marketplace_import_status(
current_user: User = Depends(get_current_user),
):
"""Get status of marketplace import job (Protected)."""
try:
job = marketplace_service.get_import_job_by_id(db, job_id, current_user)
return marketplace_service.convert_to_response_model(job)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
logger.error(f"Error getting import job status {job_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
job = marketplace_service.get_import_job_by_id(db, job_id, current_user)
return marketplace_service.convert_to_response_model(job)
@router.get(
@@ -108,21 +90,16 @@ def get_marketplace_import_jobs(
current_user: User = Depends(get_current_user),
):
"""Get marketplace import jobs with filtering (Protected)."""
try:
jobs = marketplace_service.get_import_jobs(
db=db,
user=current_user,
marketplace=marketplace,
shop_name=shop_name,
skip=skip,
limit=limit,
)
jobs = marketplace_service.get_import_jobs(
db=db,
user=current_user,
marketplace=marketplace,
shop_name=shop_name,
skip=skip,
limit=limit,
)
return [marketplace_service.convert_to_response_model(job) for job in jobs]
except Exception as e:
logger.error(f"Error getting import jobs: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return [marketplace_service.convert_to_response_model(job) for job in jobs]
@router.get("/marketplace/marketplace-import-stats")
@@ -130,13 +107,7 @@ def get_marketplace_import_stats(
db: Session = Depends(get_db), current_user: User = Depends(get_current_user)
):
"""Get statistics about marketplace import jobs (Protected)."""
try:
stats = marketplace_service.get_job_stats(db, current_user)
return stats
except Exception as e:
logger.error(f"Error getting import stats: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return marketplace_service.get_job_stats(db, current_user)
@router.put(
@@ -149,17 +120,8 @@ def cancel_marketplace_import_job(
current_user: User = Depends(get_current_user),
):
"""Cancel a pending or running marketplace import job (Protected)."""
try:
job = marketplace_service.cancel_import_job(db, job_id, current_user)
return marketplace_service.convert_to_response_model(job)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
logger.error(f"Error cancelling import job {job_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
job = marketplace_service.cancel_import_job(db, job_id, current_user)
return marketplace_service.convert_to_response_model(job)
@router.delete("/marketplace/import-jobs/{job_id}")
@@ -169,14 +131,5 @@ def delete_marketplace_import_job(
current_user: User = Depends(get_current_user),
):
"""Delete a completed marketplace import job (Protected)."""
try:
marketplace_service.delete_import_job(db, job_id, current_user)
return {"message": "Marketplace import job deleted successfully"}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
logger.error(f"Error deleting import job {job_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
marketplace_service.delete_import_job(db, job_id, current_user)
return {"message": "Marketplace import job deleted successfully"}

View File

@@ -1,16 +1,17 @@
# app/api/v1/product.py
"""Summary description ....
"""
Product endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- ....
- ....
- ....
- Product CRUD operations with marketplace support
- Advanced product filtering and search
- Product export functionality
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
@@ -26,183 +27,115 @@ router = APIRouter()
logger = logging.getLogger(__name__)
# Enhanced Product Routes with Marketplace Support
@router.get("/product", response_model=ProductListResponse)
def get_products(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
brand: Optional[str] = Query(None),
category: Optional[str] = Query(None),
availability: Optional[str] = Query(None),
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
shop_name: Optional[str] = Query(None, description="Filter by shop name"),
search: Optional[str] = Query(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
brand: Optional[str] = Query(None),
category: Optional[str] = Query(None),
availability: Optional[str] = Query(None),
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
shop_name: Optional[str] = Query(None, description="Filter by shop name"),
search: Optional[str] = Query(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get products with advanced filtering including marketplace and shop (Protected)."""
try:
products, total = product_service.get_products_with_filters(
db=db,
skip=skip,
limit=limit,
brand=brand,
category=category,
availability=availability,
marketplace=marketplace,
shop_name=shop_name,
search=search,
)
products, total = product_service.get_products_with_filters(
db=db,
skip=skip,
limit=limit,
brand=brand,
category=category,
availability=availability,
marketplace=marketplace,
shop_name=shop_name,
search=search,
)
return ProductListResponse(
products=products, total=total, skip=skip, limit=limit
)
except Exception as e:
logger.error(f"Error getting products: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return ProductListResponse(
products=products, total=total, skip=skip, limit=limit
)
@router.post("/product", response_model=ProductResponse)
def create_product(
product: ProductCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
product: ProductCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Create a new product with validation and marketplace support (Protected)."""
try:
logger.info(f"Starting product creation for ID: {product.product_id}")
logger.info(f"Starting product creation for ID: {product.product_id}")
# Check if product_id already exists
logger.info("Checking for existing product...")
existing = product_service.get_product_by_id(db, product.product_id)
logger.info(f"Existing product found: {existing is not None}")
db_product = product_service.create_product(db, product)
logger.info("Product created successfully")
if existing:
logger.info("Product already exists, raising 400 error")
raise HTTPException(
status_code=400, detail="Product with this ID already exists"
)
logger.info("No existing product found, proceeding to create...")
db_product = product_service.create_product(db, product)
logger.info("Product created successfully")
return db_product
except HTTPException as he:
logger.info(f"HTTPException raised: {he.status_code} - {he.detail}")
raise # Re-raise HTTP exceptions
except ValueError as e:
logger.error(f"ValueError: {str(e)}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Unexpected error: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
return db_product
@router.get("/product/{product_id}", response_model=ProductDetailResponse)
def get_product(
product_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
product_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get product with stock information (Protected)."""
try:
product = product_service.get_product_by_id(db, product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
product = product_service.get_product_by_id_or_raise(db, product_id)
# Get stock information if GTIN exists
stock_info = None
if product.gtin:
stock_info = product_service.get_stock_info(db, product.gtin)
# Get stock information if GTIN exists
stock_info = None
if product.gtin:
stock_info = product_service.get_stock_info(db, product.gtin)
return ProductDetailResponse(product=product, stock_info=stock_info)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting product {product_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return ProductDetailResponse(product=product, stock_info=stock_info)
@router.put("/product/{product_id}", response_model=ProductResponse)
def update_product(
product_id: str,
product_update: ProductUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
product_id: str,
product_update: ProductUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Update product with validation and marketplace support (Protected)."""
try:
product = product_service.get_product_by_id(db, product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
updated_product = product_service.update_product(db, product_id, product_update)
return updated_product
except HTTPException:
raise
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error updating product {product_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
updated_product = product_service.update_product(db, product_id, product_update)
return updated_product
@router.delete("/product/{product_id}")
def delete_product(
product_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
product_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Delete product and associated stock (Protected)."""
try:
product = product_service.get_product_by_id(db, product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
product_service.delete_product(db, product_id)
return {"message": "Product and associated stock deleted successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting product {product_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
product_service.delete_product(db, product_id)
return {"message": "Product and associated stock deleted successfully"}
# Export with streaming for large datasets (Protected)
@router.get("/export-csv")
async def export_csv(
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
shop_name: Optional[str] = Query(None, description="Filter by shop name"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
shop_name: Optional[str] = Query(None, description="Filter by shop name"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Export products as CSV with streaming and marketplace filtering (Protected)."""
try:
def generate_csv():
return product_service.generate_csv_export(
db=db, marketplace=marketplace, shop_name=shop_name
)
filename = "products_export"
if marketplace:
filename += f"_{marketplace}"
if shop_name:
filename += f"_{shop_name}"
filename += ".csv"
return StreamingResponse(
generate_csv(),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename={filename}"},
def generate_csv():
return product_service.generate_csv_export(
db=db, marketplace=marketplace, shop_name=shop_name
)
except Exception as e:
logger.error(f"Error exporting CSV: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
filename = "products_export"
if marketplace:
filename += f"_{marketplace}"
if shop_name:
filename += f"_{shop_name}"
filename += ".csv"
return StreamingResponse(
generate_csv(),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename={filename}"},
)

View File

@@ -1,15 +1,16 @@
# app/api/v1/shop.py
"""Summary description ....
"""
Shop endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- ....
- ....
- ....
- Shop CRUD operations and management
- Shop product catalog management
- Shop filtering and verification
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user, get_user_shop
@@ -23,7 +24,6 @@ router = APIRouter()
logger = logging.getLogger(__name__)
# Shop Management Routes
@router.post("/shop", response_model=ShopResponse)
def create_shop(
shop_data: ShopCreate,
@@ -31,16 +31,10 @@ def create_shop(
current_user: User = Depends(get_current_user),
):
"""Create a new shop (Protected)."""
try:
shop = shop_service.create_shop(
db=db, shop_data=shop_data, current_user=current_user
)
return ShopResponse.model_validate(shop)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error creating shop: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
shop = shop_service.create_shop(
db=db, shop_data=shop_data, current_user=current_user
)
return ShopResponse.model_validate(shop)
@router.get("/shop", response_model=ShopListResponse)
@@ -53,22 +47,16 @@ def get_shops(
current_user: User = Depends(get_current_user),
):
"""Get shops with filtering (Protected)."""
try:
shops, total = shop_service.get_shops(
db=db,
current_user=current_user,
skip=skip,
limit=limit,
active_only=active_only,
verified_only=verified_only,
)
shops, total = shop_service.get_shops(
db=db,
current_user=current_user,
skip=skip,
limit=limit,
active_only=active_only,
verified_only=verified_only,
)
return ShopListResponse(shops=shops, total=total, skip=skip, limit=limit)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting shops: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return ShopListResponse(shops=shops, total=total, skip=skip, limit=limit)
@router.get("/shop/{shop_code}", response_model=ShopResponse)
@@ -78,19 +66,12 @@ def get_shop(
current_user: User = Depends(get_current_user),
):
"""Get shop details (Protected)."""
try:
shop = shop_service.get_shop_by_code(
db=db, shop_code=shop_code, current_user=current_user
)
return ShopResponse.model_validate(shop)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting shop {shop_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
shop = shop_service.get_shop_by_code(
db=db, shop_code=shop_code, current_user=current_user
)
return ShopResponse.model_validate(shop)
# Shop Product Management
@router.post("/shop/{shop_code}/products", response_model=ShopProductResponse)
def add_product_to_shop(
shop_code: str,
@@ -99,24 +80,18 @@ def add_product_to_shop(
current_user: User = Depends(get_current_user),
):
"""Add existing product to shop catalog with shop-specific settings (Protected)."""
try:
# Get and verify shop (using existing dependency)
shop = get_user_shop(shop_code, current_user, db)
# Get and verify shop (using existing dependency)
shop = get_user_shop(shop_code, current_user, db)
# Add product to shop
new_shop_product = shop_service.add_product_to_shop(
db=db, shop=shop, shop_product=shop_product
)
# Add product to shop
new_shop_product = shop_service.add_product_to_shop(
db=db, shop=shop, shop_product=shop_product
)
# Return with product details
response = ShopProductResponse.model_validate(new_shop_product)
response.product = new_shop_product.product
return response
except HTTPException:
raise
except Exception as e:
logger.error(f"Error adding product to shop {shop_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
# Return with product details
response = ShopProductResponse.model_validate(new_shop_product)
response.product = new_shop_product.product
return response
@router.get("/shop/{shop_code}/products")
@@ -130,39 +105,33 @@ def get_shop_products(
current_user: User = Depends(get_current_user),
):
"""Get products in shop catalog (Protected)."""
try:
# Get shop
shop = shop_service.get_shop_by_code(
db=db, shop_code=shop_code, current_user=current_user
)
# Get shop
shop = shop_service.get_shop_by_code(
db=db, shop_code=shop_code, current_user=current_user
)
# Get shop products
shop_products, total = shop_service.get_shop_products(
db=db,
shop=shop,
current_user=current_user,
skip=skip,
limit=limit,
active_only=active_only,
featured_only=featured_only,
)
# Get shop products
shop_products, total = shop_service.get_shop_products(
db=db,
shop=shop,
current_user=current_user,
skip=skip,
limit=limit,
active_only=active_only,
featured_only=featured_only,
)
# Format response
products = []
for sp in shop_products:
product_response = ShopProductResponse.model_validate(sp)
product_response.product = sp.product
products.append(product_response)
# Format response
products = []
for sp in shop_products:
product_response = ShopProductResponse.model_validate(sp)
product_response.product = sp.product
products.append(product_response)
return {
"products": products,
"total": total,
"skip": skip,
"limit": limit,
"shop": ShopResponse.model_validate(shop),
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting products for shop {shop_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return {
"products": products,
"total": total,
"skip": skip,
"limit": limit,
"shop": ShopResponse.model_validate(shop),
}

View File

@@ -1,16 +1,17 @@
# app/api/v1/stats.py
"""Summary description ....
"""
Statistics endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- ....
- ....
- ....
- Comprehensive system statistics
- Marketplace-specific analytics
- Performance metrics and data insights
"""
import logging
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
@@ -23,27 +24,22 @@ router = APIRouter()
logger = logging.getLogger(__name__)
# Enhanced Statistics with Marketplace Support
@router.get("/stats", response_model=StatsResponse)
def get_stats(
db: Session = Depends(get_db), current_user: User = Depends(get_current_user)
):
"""Get comprehensive statistics with marketplace data (Protected)."""
try:
stats_data = stats_service.get_comprehensive_stats(db=db)
stats_data = stats_service.get_comprehensive_stats(db=db)
return StatsResponse(
total_products=stats_data["total_products"],
unique_brands=stats_data["unique_brands"],
unique_categories=stats_data["unique_categories"],
unique_marketplaces=stats_data["unique_marketplaces"],
unique_shops=stats_data["unique_shops"],
total_stock_entries=stats_data["total_stock_entries"],
total_inventory_quantity=stats_data["total_inventory_quantity"],
)
except Exception as e:
logger.error(f"Error getting comprehensive stats: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return StatsResponse(
total_products=stats_data["total_products"],
unique_brands=stats_data["unique_brands"],
unique_categories=stats_data["unique_categories"],
unique_marketplaces=stats_data["unique_marketplaces"],
unique_shops=stats_data["unique_shops"],
total_stock_entries=stats_data["total_stock_entries"],
total_inventory_quantity=stats_data["total_inventory_quantity"],
)
@router.get("/stats/marketplace", response_model=List[MarketplaceStatsResponse])
@@ -51,18 +47,65 @@ def get_marketplace_stats(
db: Session = Depends(get_db), current_user: User = Depends(get_current_user)
):
"""Get statistics broken down by marketplace (Protected)."""
try:
marketplace_stats = stats_service.get_marketplace_breakdown_stats(db=db)
marketplace_stats = stats_service.get_marketplace_breakdown_stats(db=db)
return [
MarketplaceStatsResponse(
marketplace=stat["marketplace"],
total_products=stat["total_products"],
unique_shops=stat["unique_shops"],
unique_brands=stat["unique_brands"],
)
for stat in marketplace_stats
]
except Exception as e:
logger.error(f"Error getting marketplace stats: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
# app/api/v1/stats.py
"""
Statistics endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- Comprehensive system statistics
- Marketplace-specific analytics
- Performance metrics and data insights
"""
import logging
from typing import List
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
from app.core.database import get_db
from app.services.stats_service import stats_service
from models.schemas.stats import MarketplaceStatsResponse, StatsResponse
from models.database.user import User
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/stats", response_model=StatsResponse)
def get_stats(
db: Session = Depends(get_db), current_user: User = Depends(get_current_user)
):
"""Get comprehensive statistics with marketplace data (Protected)."""
stats_data = stats_service.get_comprehensive_stats(db=db)
return StatsResponse(
total_products=stats_data["total_products"],
unique_brands=stats_data["unique_brands"],
unique_categories=stats_data["unique_categories"],
unique_marketplaces=stats_data["unique_marketplaces"],
unique_shops=stats_data["unique_shops"],
total_stock_entries=stats_data["total_stock_entries"],
total_inventory_quantity=stats_data["total_inventory_quantity"],
)
@router.get("/stats/marketplace", response_model=List[MarketplaceStatsResponse])
def get_marketplace_stats(
db: Session = Depends(get_db), current_user: User = Depends(get_current_user)
):
"""Get statistics broken down by marketplace (Protected)."""
marketplace_stats = stats_service.get_marketplace_breakdown_stats(db=db)
return [
MarketplaceStatsResponse(
marketplace=stat["marketplace"],
total_products=stat["total_products"],
unique_shops=stat["unique_shops"],
unique_brands=stat["unique_brands"],
)
for stat in marketplace_stats
]

View File

@@ -1,16 +1,17 @@
# app/api/v1/stock.py
"""Summary description ....
"""
Stock endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- ....
- ....
- ....
- Stock quantity management (set, add, remove)
- Stock information retrieval and filtering
- Location-based stock tracking
"""
import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
@@ -24,9 +25,6 @@ router = APIRouter()
logger = logging.getLogger(__name__)
# Stock Management Routes (Protected)
@router.post("/stock", response_model=StockResponse)
def set_stock(
stock: StockCreate,
@@ -34,14 +32,7 @@ def set_stock(
current_user: User = Depends(get_current_user),
):
"""Set exact stock quantity for a GTIN at a specific location (replaces existing quantity)."""
try:
result = stock_service.set_stock(db, stock)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error setting stock: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return stock_service.set_stock(db, stock)
@router.post("/stock/add", response_model=StockResponse)
@@ -51,14 +42,7 @@ def add_stock(
current_user: User = Depends(get_current_user),
):
"""Add quantity to existing stock for a GTIN at a specific location (adds to existing quantity)."""
try:
result = stock_service.add_stock(db, stock)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error adding stock: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return stock_service.add_stock(db, stock)
@router.post("/stock/remove", response_model=StockResponse)
@@ -68,14 +52,7 @@ def remove_stock(
current_user: User = Depends(get_current_user),
):
"""Remove quantity from existing stock for a GTIN at a specific location."""
try:
result = stock_service.remove_stock(db, stock)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error removing stock: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return stock_service.remove_stock(db, stock)
@router.get("/stock/{gtin}", response_model=StockSummaryResponse)
@@ -85,14 +62,7 @@ def get_stock_by_gtin(
current_user: User = Depends(get_current_user),
):
"""Get all stock locations and total quantity for a specific GTIN."""
try:
result = stock_service.get_stock_by_gtin(db, gtin)
return result
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Error getting stock for GTIN {gtin}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return stock_service.get_stock_by_gtin(db, gtin)
@router.get("/stock/{gtin}/total")
@@ -102,14 +72,7 @@ def get_total_stock(
current_user: User = Depends(get_current_user),
):
"""Get total quantity in stock for a specific GTIN."""
try:
result = stock_service.get_total_stock(db, gtin)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error getting total stock for GTIN {gtin}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return stock_service.get_total_stock(db, gtin)
@router.get("/stock", response_model=List[StockResponse])
@@ -122,14 +85,9 @@ def get_all_stock(
current_user: User = Depends(get_current_user),
):
"""Get all stock entries with optional filtering."""
try:
result = stock_service.get_all_stock(
db=db, skip=skip, limit=limit, location=location, gtin=gtin
)
return result
except Exception as e:
logger.error(f"Error getting all stock: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return stock_service.get_all_stock(
db=db, skip=skip, limit=limit, location=location, gtin=gtin
)
@router.put("/stock/{stock_id}", response_model=StockResponse)
@@ -140,14 +98,7 @@ def update_stock(
current_user: User = Depends(get_current_user),
):
"""Update stock quantity for a specific stock entry."""
try:
result = stock_service.update_stock(db, stock_id, stock_update)
return result
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Error updating stock {stock_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return stock_service.update_stock(db, stock_id, stock_update)
@router.delete("/stock/{stock_id}")
@@ -157,11 +108,5 @@ def delete_stock(
current_user: User = Depends(get_current_user),
):
"""Delete a stock entry."""
try:
stock_service.delete_stock(db, stock_id)
return {"message": "Stock entry deleted successfully"}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Error deleting stock {stock_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
stock_service.delete_stock(db, stock_id)
return {"message": "Stock entry deleted successfully"}