From 962cc8dcef6f33291081e08551ddea0558284514 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Fri, 5 Dec 2025 21:48:13 +0100 Subject: [PATCH] feat: add POST endpoint for admin marketplace import jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add POST /api/v1/admin/marketplace-import-jobs endpoint to allow admins to create import jobs for any vendor. Changes: - Add AdminMarketplaceImportJobRequest schema with vendor_id field - Add create_marketplace_import_job endpoint in admin/marketplace.py - Make vendor_code and vendor_name optional in response model to handle edge cases where vendor relationship may not be loaded This fixes the 405 Method Not Allowed error when trying to import products from the admin marketplace page. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/api/v1/admin/marketplace.py | 48 ++++++++++++++++++++++++- models/schema/marketplace_import_job.py | 31 ++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) 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