# app/api/v1/marketplace_products.py """ MarketplaceProduct endpoints - simplified with service-level exception handling. This module provides classes and functions for: - MarketplaceProduct CRUD operations with marketplace support - Advanced product filtering and search - MarketplaceProduct export functionality - MarketplaceProduct 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, Query from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.marketplace_import_job_service import marketplace_import_job_service from app.services.marketplace_product_service import marketplace_product_service from app.tasks.background_tasks import process_marketplace_import from middleware.decorators import rate_limit from models.schemas.marketplace_import_job import (MarketplaceImportJobResponse, MarketplaceImportJobRequest) from models.schemas.marketplace_product import (MarketplaceProductCreate, MarketplaceProductDetailResponse, MarketplaceProductListResponse, MarketplaceProductResponse, MarketplaceProductUpdate) from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) # ============================================================================ # PRODUCT ENDPOINTS # ============================================================================ @router.get("/marketplace/product/export-csv") async def export_csv( marketplace: Optional[str] = Query(None, description="Filter by marketplace"), vendor_name: Optional[str] = Query(None, description="Filter by vendor name"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Export products as CSV with streaming and marketplace filtering (Protected).""" def generate_csv(): return marketplace_product_service.generate_csv_export( db=db, marketplace=marketplace, vendor_name=vendor_name ) filename = "marketplace_products_export" if marketplace: filename += f"_{marketplace}" if vendor_name: filename += f"_{vendor_name}" filename += ".csv" return StreamingResponse( generate_csv(), media_type="text/csv", headers={"Content-Disposition": f"attachment; filename={filename}"}, ) @router.get("/marketplace/product", response_model=MarketplaceProductListResponse) 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"), 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 vendor (Protected).""" products, total = marketplace_product_service.get_products_with_filters( db=db, skip=skip, limit=limit, brand=brand, category=category, availability=availability, marketplace=marketplace, vendor_name=vendor_name, search=search, ) return MarketplaceProductListResponse( products=products, total=total, skip=skip, limit=limit ) @router.post("/marketplace/product", response_model=MarketplaceProductResponse) def create_product( product: MarketplaceProductCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Create a new product with validation and marketplace support (Protected).""" logger.info(f"Starting product creation for ID: {product.marketplace_product_id}") db_product = marketplace_product_service.create_product(db, product) logger.info("MarketplaceProduct created successfully") return db_product @router.get("/marketplace/product/{marketplace_product_id}", response_model=MarketplaceProductDetailResponse) def get_product( marketplace_product_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get product with stock information (Protected).""" product = marketplace_product_service.get_product_by_id_or_raise(db, marketplace_product_id) # Get stock information if GTIN exists stock_info = None if product.gtin: stock_info = marketplace_product_service.get_stock_info(db, product.gtin) return MarketplaceProductDetailResponse(product=product, stock_info=stock_info) @router.put("/marketplace/product/{marketplace_product_id}", response_model=MarketplaceProductResponse) def update_product( marketplace_product_id: str, product_update: MarketplaceProductUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Update product with validation and marketplace support (Protected).""" updated_product = marketplace_product_service.update_product(db, marketplace_product_id, product_update) return updated_product @router.delete("/marketplace/product/{marketplace_product_id}") def delete_product( marketplace_product_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Delete product and associated stock (Protected).""" marketplace_product_service.delete_product(db, marketplace_product_id) return {"message": "MarketplaceProduct and associated stock deleted successfully"} # ============================================================================ # IMPORT JOB ENDPOINTS # ============================================================================ @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( request: MarketplaceImportJobRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Import products from marketplace CSV with background processing (Protected).""" logger.info( f"Starting marketplace import: {request.marketplace} -> {request.vendor_code} by user {current_user.username}" ) # Create import job through service import_job = marketplace_import_job_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.vendor_code, request.batch_size or 1000, ) return MarketplaceImportJobResponse( job_id=import_job.id, status="pending", marketplace=request.marketplace, 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}", ) @router.get( "/marketplace/import-status/{job_id}", response_model=MarketplaceImportJobResponse ) def get_marketplace_import_status( job_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get status of marketplace import job (Protected).""" job = marketplace_import_job_service.get_import_job_by_id(db, job_id, current_user) return marketplace_import_job_service.convert_to_response_model(job) @router.get( "/marketplace/import-jobs", response_model=List[MarketplaceImportJobResponse] ) def get_marketplace_import_jobs( marketplace: Optional[str] = Query(None, description="Filter by marketplace"), 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), current_user: User = Depends(get_current_user), ): """Get marketplace import jobs with filtering (Protected).""" jobs = marketplace_import_job_service.get_import_jobs( db=db, user=current_user, marketplace=marketplace, vendor_name=vendor_name, skip=skip, limit=limit, ) return [marketplace_import_job_service.convert_to_response_model(job) for job in jobs] @router.get("/marketplace/marketplace-import-stats") def get_marketplace_import_stats( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get statistics about marketplace import jobs (Protected).""" return marketplace_import_job_service.get_job_stats(db, current_user) @router.put( "/marketplace/import-jobs/{job_id}/cancel", response_model=MarketplaceImportJobResponse, ) def cancel_marketplace_import_job( job_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Cancel a pending or running marketplace import job (Protected).""" job = marketplace_import_job_service.cancel_import_job(db, job_id, current_user) return marketplace_import_job_service.convert_to_response_model(job) @router.delete("/marketplace/import-jobs/{job_id}") def delete_marketplace_import_job( job_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Delete a completed marketplace import job (Protected).""" marketplace_import_job_service.delete_import_job(db, job_id, current_user) return {"message": "Marketplace import job deleted successfully"}