shop product refactoring

This commit is contained in:
2025-10-04 23:38:53 +02:00
parent 4d2866af5e
commit 0114b6c46e
68 changed files with 2234 additions and 2236 deletions

View File

@@ -14,9 +14,9 @@ from sqlalchemy.orm import Session
from app.core.database import get_db
from middleware.auth import AuthManager
from middleware.rate_limiter import RateLimiter
from models.database.shop import Shop
from models.database.vendor import Vendor
from models.database.user import User
from app.exceptions import (AdminRequiredException,ShopNotFoundException, UnauthorizedShopAccessException)
from app.exceptions import (AdminRequiredException, VendorNotFoundException, UnauthorizedVendorAccessException)
# Set auto_error=False to prevent automatic 403 responses
security = HTTPBearer(auto_error=False)
@@ -43,18 +43,18 @@ def get_current_admin_user(current_user: User = Depends(get_current_user)):
return auth_manager.require_admin(current_user)
def get_user_shop(
shop_code: str,
def get_user_vendor(
vendor_code: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get shop and verify user ownership."""
shop = db.query(Shop).filter(Shop.shop_code == shop_code.upper()).first()
if not shop:
raise ShopNotFoundException(shop_code)
"""Get vendor and verify user ownership."""
vendor = db.query(Vendor).filter(Vendor.vendor_code == vendor_code.upper()).first()
if not vendor:
raise VendorNotFoundException(vendor_code)
if current_user.role != "admin" and shop.owner_id != current_user.id:
raise UnauthorizedShopAccessException(shop_code, current_user.id)
if current_user.role != "admin" and vendor.owner_id != current_user.id:
raise UnauthorizedVendorAccessException(vendor_code, current_user.id)
return shop
return vendor

View File

@@ -9,7 +9,7 @@ This module provides classes and functions for:
from fastapi import APIRouter
from app.api.v1 import admin, auth, marketplace, shop, stats, stock
from app.api.v1 import admin, auth, marketplace, vendor, stats, stock
api_router = APIRouter()
@@ -17,6 +17,6 @@ api_router = APIRouter()
api_router.include_router(admin.router, tags=["admin"])
api_router.include_router(auth.router, tags=["authentication"])
api_router.include_router(marketplace.router, tags=["marketplace"])
api_router.include_router(shop.router, tags=["shop"])
api_router.include_router(vendor.router, tags=["vendor"])
api_router.include_router(stats.router, tags=["statistics"])
api_router.include_router(stock.router, tags=["stock"])

View File

@@ -4,7 +4,7 @@ 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)
- Vendor management (view, verify, toggle status)
- Marketplace import job monitoring
- Admin dashboard statistics
"""
@@ -20,7 +20,7 @@ from app.core.database import get_db
from app.services.admin_service import admin_service
from models.schemas.auth import UserResponse
from models.schemas.marketplace_import_job import MarketplaceImportJobResponse
from models.schemas.shop import ShopListResponse
from models.schemas.vendor import VendorListResponse
from models.database.user import User
router = APIRouter()
@@ -50,37 +50,37 @@ def toggle_user_status(
return {"message": message}
@router.get("/admin/shops", response_model=ShopListResponse)
def get_all_shops_admin(
@router.get("/admin/vendors", response_model=VendorListResponse)
def get_all_vendors_admin(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get all shops with admin view (Admin only)."""
shops, total = admin_service.get_all_shops(db=db, skip=skip, limit=limit)
return ShopListResponse(shops=shops, total=total, skip=skip, limit=limit)
"""Get all vendors with admin view (Admin only)."""
vendors, total = admin_service.get_all_vendors(db=db, skip=skip, limit=limit)
return VendorListResponse(vendors=vendors, total=total, skip=skip, limit=limit)
@router.put("/admin/shops/{shop_id}/verify")
def verify_shop(
shop_id: int,
@router.put("/admin/vendors/{vendor_id}/verify")
def verify_vendor(
vendor_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Verify/unverify shop (Admin only)."""
shop, message = admin_service.verify_shop(db, shop_id)
"""Verify/unverify vendor (Admin only)."""
vendor, message = admin_service.verify_vendor(db, vendor_id)
return {"message": message}
@router.put("/admin/shops/{shop_id}/status")
def toggle_shop_status(
shop_id: int,
@router.put("/admin/vendors/{vendor_id}/status")
def toggle_vendor_status(
vendor_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Toggle shop active status (Admin only)."""
shop, message = admin_service.toggle_shop_status(db, shop_id)
"""Toggle vendor active status (Admin only)."""
vendor, message = admin_service.toggle_vendor_status(db, vendor_id)
return {"message": message}
@@ -89,7 +89,7 @@ def toggle_shop_status(
)
def get_all_marketplace_import_jobs(
marketplace: Optional[str] = Query(None),
shop_name: Optional[str] = Query(None),
vendor_name: Optional[str] = Query(None),
status: Optional[str] = Query(None),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=100),
@@ -100,7 +100,7 @@ def get_all_marketplace_import_jobs(
return admin_service.get_marketplace_import_jobs(
db=db,
marketplace=marketplace,
shop_name=shop_name,
vendor_name=vendor_name,
status=status,
skip=skip,
limit=limit,
@@ -116,10 +116,10 @@ def get_user_statistics(
return admin_service.get_user_statistics(db)
@router.get("/admin/stats/shops")
def get_shop_statistics(
@router.get("/admin/stats/vendors")
def get_vendor_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)
"""Get vendor statistics for admin dashboard (Admin only)."""
return admin_service.get_vendor_statistics(db)

View File

@@ -42,7 +42,7 @@ logger = logging.getLogger(__name__)
@router.get("/marketplace/product/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"),
vendor_name: Optional[str] = Query(None, description="Filter by vendor name"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
@@ -50,14 +50,14 @@ async def export_csv(
def generate_csv():
return marketplace_product_service.generate_csv_export(
db=db, marketplace=marketplace, shop_name=shop_name
db=db, marketplace=marketplace, vendor_name=vendor_name
)
filename = "marketplace_products_export"
if marketplace:
filename += f"_{marketplace}"
if shop_name:
filename += f"_{shop_name}"
if vendor_name:
filename += f"_{vendor_name}"
filename += ".csv"
return StreamingResponse(
@@ -75,12 +75,12 @@ def get_products(
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"),
vendor_name: Optional[str] = Query(None, description="Filter by vendor 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)."""
"""Get products with advanced filtering including marketplace and vendor (Protected)."""
products, total = marketplace_product_service.get_products_with_filters(
db=db,
skip=skip,
@@ -89,7 +89,7 @@ def get_products(
category=category,
availability=availability,
marketplace=marketplace,
shop_name=shop_name,
vendor_name=vendor_name,
search=search,
)
@@ -166,7 +166,7 @@ async def import_products_from_marketplace(
):
"""Import products from marketplace CSV with background processing (Protected)."""
logger.info(
f"Starting marketplace import: {request.marketplace} -> {request.shop_code} by user {current_user.username}"
f"Starting marketplace import: {request.marketplace} -> {request.vendor_code} by user {current_user.username}"
)
# Create import job through service
@@ -178,7 +178,7 @@ async def import_products_from_marketplace(
import_job.id,
request.url,
request.marketplace,
request.shop_code,
request.vendor_code,
request.batch_size or 1000,
)
@@ -186,9 +186,9 @@ async def import_products_from_marketplace(
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,
vendor_code=request.vendor_code,
vendor_id=import_job.vendor_id,
vendor_name=import_job.vendor_name,
message=f"Marketplace import started from {request.marketplace}. Check status with "
f"/import-status/{import_job.id}",
)
@@ -212,7 +212,7 @@ def get_marketplace_import_status(
)
def get_marketplace_import_jobs(
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
shop_name: Optional[str] = Query(None, description="Filter by shop name"),
vendor_name: Optional[str] = Query(None, description="Filter by vendor name"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
db: Session = Depends(get_db),
@@ -223,7 +223,7 @@ def get_marketplace_import_jobs(
db=db,
user=current_user,
marketplace=marketplace,
shop_name=shop_name,
vendor_name=vendor_name,
skip=skip,
limit=limit,
)

View File

@@ -1,137 +0,0 @@
# app/api/v1/shop.py
"""
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, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user, get_user_shop
from app.core.database import get_db
from app.services.shop_service import shop_service
from models.schemas.shop import (ShopCreate, ShopListResponse,ShopResponse)
from models.schemas.product import (ProductCreate,ProductResponse)
from models.database.user import User
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/shop", response_model=ShopResponse)
def create_shop(
shop_data: ShopCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Create a new shop (Protected)."""
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)
def get_shops(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
active_only: bool = Query(True),
verified_only: bool = Query(False),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get shops with filtering (Protected)."""
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)
@router.get("/shop/{shop_code}", response_model=ShopResponse)
def get_shop(
shop_code: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get shop details (Protected)."""
shop = shop_service.get_shop_by_code(
db=db, shop_code=shop_code, current_user=current_user
)
return ShopResponse.model_validate(shop)
@router.post("/shop/{shop_code}/products", response_model=ProductResponse)
def add_product_to_shop(
shop_code: str,
product: ProductCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Add existing product to shop catalog with shop-specific settings (Protected)."""
# Get and verify shop (using existing dependency)
shop = get_user_shop(shop_code, current_user, db)
# Add product to shop
new_product = shop_service.add_product_to_shop(
db=db, shop=shop, product=product
)
# Return with product details
response = ProductResponse.model_validate(new_product)
response.marketplace_product = new_product.marketplace_product
return response
@router.get("/shop/{shop_code}/products")
def get_products(
shop_code: str,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
active_only: bool = Query(True),
featured_only: bool = Query(False),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get products in shop catalog (Protected)."""
# Get shop
shop = shop_service.get_shop_by_code(
db=db, shop_code=shop_code, current_user=current_user
)
# Get shop products
vendor_products, total = shop_service.get_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 vp in vendor_products:
product_response = ProductResponse.model_validate(vp)
product_response.marketplace_product = vp.marketplace_product
products.append(product_response)
return {
"products": products,
"total": total,
"skip": skip,
"limit": limit,
"shop": ShopResponse.model_validate(shop),
}

View File

@@ -36,7 +36,7 @@ def get_stats(
unique_brands=stats_data["unique_brands"],
unique_categories=stats_data["unique_categories"],
unique_marketplaces=stats_data["unique_marketplaces"],
unique_shops=stats_data["unique_shops"],
unique_vendors=stats_data["unique_vendors"],
total_stock_entries=stats_data["total_stock_entries"],
total_inventory_quantity=stats_data["total_inventory_quantity"],
)
@@ -87,7 +87,7 @@ def get_stats(
unique_brands=stats_data["unique_brands"],
unique_categories=stats_data["unique_categories"],
unique_marketplaces=stats_data["unique_marketplaces"],
unique_shops=stats_data["unique_shops"],
unique_vendors=stats_data["unique_vendors"],
total_stock_entries=stats_data["total_stock_entries"],
total_inventory_quantity=stats_data["total_inventory_quantity"],
)
@@ -104,7 +104,7 @@ def get_marketplace_stats(
MarketplaceStatsResponse(
marketplace=stat["marketplace"],
total_products=stat["total_products"],
unique_shops=stat["unique_shops"],
unique_vendors=stat["unique_vendors"],
unique_brands=stat["unique_brands"],
)
for stat in marketplace_stats

137
app/api/v1/vendor.py Normal file
View File

@@ -0,0 +1,137 @@
# app/api/v1/vendor.py
"""
Vendor endpoints - simplified with service-level exception handling.
This module provides classes and functions for:
- Vendor CRUD operations and management
- Vendor product catalog management
- Vendor filtering and verification
"""
import logging
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user, get_user_vendor
from app.core.database import get_db
from app.services.vendor_service import vendor_service
from models.schemas.vendor import (VendorCreate, VendorListResponse, VendorResponse)
from models.schemas.product import (ProductCreate,ProductResponse)
from models.database.user import User
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/vendor", response_model=VendorResponse)
def create_vendor(
vendor_data: VendorCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Create a new vendor (Protected)."""
vendor = vendor_service.create_vendor(
db=db, vendor_data=vendor_data, current_user=current_user
)
return VendorResponse.model_validate(vendor)
@router.get("/vendor", response_model=VendorListResponse)
def get_vendors(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
active_only: bool = Query(True),
verified_only: bool = Query(False),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get vendors with filtering (Protected)."""
vendors, total = vendor_service.get_vendors(
db=db,
current_user=current_user,
skip=skip,
limit=limit,
active_only=active_only,
verified_only=verified_only,
)
return VendorListResponse(vendors=vendors, total=total, skip=skip, limit=limit)
@router.get("/vendor/{vendor_code}", response_model=VendorResponse)
def get_vendor(
vendor_code: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get vendor details (Protected)."""
vendor = vendor_service.get_vendor_by_code(
db=db, vendor_code=vendor_code, current_user=current_user
)
return VendorResponse.model_validate(vendor)
@router.post("/vendor/{vendor_code}/products", response_model=ProductResponse)
def add_product_to_catalog(
vendor_code: str,
product: ProductCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Add existing product to vendor catalog with vendor -specific settings (Protected)."""
# Get and verify vendor (using existing dependency)
vendor = get_user_vendor(vendor_code, current_user, db)
# Add product to vendor
new_product = vendor_service.add_product_to_catalog(
db=db, vendor=vendor, product=product
)
# Return with product details
response = ProductResponse.model_validate(new_product)
response.marketplace_product = new_product.marketplace_product
return response
@router.get("/vendor/{vendor_code}/products")
def get_products(
vendor_code: str,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
active_only: bool = Query(True),
featured_only: bool = Query(False),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get products in vendor catalog (Protected)."""
# Get vendor
vendor = vendor_service.get_vendor_by_code(
db=db, vendor_code=vendor_code, current_user=current_user
)
# Get vendor products
vendor_products, total = vendor_service.get_products(
db=db,
vendor=vendor,
current_user=current_user,
skip=skip,
limit=limit,
active_only=active_only,
featured_only=featured_only,
)
# Format response
products = []
for vp in vendor_products:
product_response = ProductResponse.model_validate(vp)
product_response.marketplace_product = vp.marketplace_product
products.append(product_response)
return {
"products": products,
"total": total,
"skip": skip,
"limit": limit,
"vendor": VendorResponse.model_validate(vendor),
}