diff --git a/app/api/v1/admin/marketplace.py b/app/api/v1/admin/marketplace.py index 3319bb51..8fc3aa86 100644 --- a/app/api/v1/admin/marketplace.py +++ b/app/api/v1/admin/marketplace.py @@ -10,10 +10,16 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api from app.core.database import get_db +from app.exceptions import VendorNotFoundException from app.services.admin_service import admin_service +from app.services.marketplace_import_job_service import marketplace_import_job_service from app.services.stats_service import stats_service from models.database.user import User -from models.schema.marketplace_import_job import MarketplaceImportJobResponse +from models.database.vendor import Vendor +from models.schema.marketplace_import_job import ( + AdminMarketplaceImportJobRequest, + MarketplaceImportJobResponse, +) router = APIRouter(prefix="/marketplace-import-jobs") logger = logging.getLogger(__name__) @@ -40,6 +46,46 @@ def get_all_marketplace_import_jobs( ) +@router.post("", response_model=MarketplaceImportJobResponse) +def create_marketplace_import_job( + request: AdminMarketplaceImportJobRequest, + db: Session = Depends(get_db), + current_admin: User = Depends(get_current_admin_api), +): + """ + Create a new marketplace import job (Admin only). + + Admins can trigger imports for any vendor by specifying vendor_id. + """ + # Look up the vendor + vendor = db.query(Vendor).filter(Vendor.id == request.vendor_id).first() + if not vendor: + raise VendorNotFoundException(str(request.vendor_id), identifier_type="id") + + # Create the import job using the service + from models.schema.marketplace_import_job import MarketplaceImportJobRequest + + job_request = MarketplaceImportJobRequest( + source_url=request.source_url, + marketplace=request.marketplace, + batch_size=request.batch_size, + ) + + job = marketplace_import_job_service.create_import_job( + db=db, + request=job_request, + vendor=vendor, + user=current_admin, + ) + + logger.info( + f"Admin {current_admin.username} created import job {job.id} " + f"for vendor {vendor.vendor_code}" + ) + + return marketplace_import_job_service.convert_to_response_model(job) + + @router.get("/stats") def get_import_statistics( db: Session = Depends(get_db), diff --git a/models/schema/marketplace_import_job.py b/models/schema/marketplace_import_job.py index ec02ffd9..d99fef60 100644 --- a/models/schema/marketplace_import_job.py +++ b/models/schema/marketplace_import_job.py @@ -29,6 +29,33 @@ class MarketplaceImportJobRequest(BaseModel): return v.strip() +class AdminMarketplaceImportJobRequest(BaseModel): + """Request schema for admin-triggered marketplace import. + + Includes vendor_id since admin can import for any vendor. + """ + + vendor_id: int = Field(..., description="Vendor ID to import products for") + source_url: str = Field(..., description="URL to CSV file from marketplace") + marketplace: str = Field(default="Letzshop", description="Marketplace name") + batch_size: int | None = Field( + 1000, description="Processing batch size", ge=100, le=10000 + ) + + @field_validator("source_url") + @classmethod + def validate_url(cls, v): + # Basic URL security validation + if not v.startswith(("http://", "https://")): + raise ValueError("URL must start with http:// or https://") + return v.strip() + + @field_validator("marketplace") + @classmethod + def validate_marketplace(cls, v): + return v.strip() + + class MarketplaceImportJobResponse(BaseModel): """Response schema for marketplace import job.""" @@ -36,8 +63,8 @@ class MarketplaceImportJobResponse(BaseModel): job_id: int vendor_id: int - vendor_code: str # Populated from vendor relationship - vendor_name: str # Populated from vendor relationship + vendor_code: str | None = None # Populated from vendor relationship + vendor_name: str | None = None # Populated from vendor relationship marketplace: str source_url: str status: str