from sqlalchemy import func from sqlalchemy.orm import Session from models.database_models import MarketplaceImportJob, Shop, User from models.api_models import MarketplaceImportRequest, MarketplaceImportJobResponse from typing import Optional, List from datetime import datetime import logging logger = logging.getLogger(__name__) class MarketplaceService: def __init__(self): pass def validate_shop_access(self, db: Session, shop_code: str, user: User) -> Shop: """Validate that the shop exists and user has access to it""" # Explicit type hint to help type checker shop: Optional[Shop] # Use case-insensitive query to handle both uppercase and lowercase codes shop: Optional[Shop] = db.query(Shop).filter( func.upper(Shop.shop_code) == shop_code.upper() ).first() if not shop: raise ValueError("Shop not found") # Check permissions: admin can import for any shop, others only for their own if user.role != "admin" and shop.owner_id != user.id: raise PermissionError("Access denied to this shop") return shop def create_import_job( self, db: Session, request: MarketplaceImportRequest, user: User ) -> MarketplaceImportJob: """Create a new marketplace import job""" # Validate shop access first shop = self.validate_shop_access(db, request.shop_code, user) # Create marketplace import job record import_job = MarketplaceImportJob( status="pending", source_url=request.url, marketplace=request.marketplace, shop_id=shop.id, # Foreign key to shops table shop_name=shop.shop_name, # Use shop.shop_name (the display name) user_id=user.id, created_at=datetime.utcnow() ) db.add(import_job) db.commit() db.refresh(import_job) logger.info( f"Created marketplace import job {import_job.id}: {request.marketplace} -> {shop.shop_name} (shop_code: {shop.shop_code}) by user {user.username}") return import_job def get_import_job_by_id(self, db: Session, job_id: int, user: User) -> MarketplaceImportJob: """Get a marketplace import job by ID with access control""" job = db.query(MarketplaceImportJob).filter(MarketplaceImportJob.id == job_id).first() if not job: raise ValueError("Marketplace import job not found") # Users can only see their own jobs, admins can see all if user.role != "admin" and job.user_id != user.id: raise PermissionError("Access denied to this import job") return job def get_import_jobs( self, db: Session, user: User, marketplace: Optional[str] = None, shop_name: Optional[str] = None, skip: int = 0, limit: int = 50 ) -> List[MarketplaceImportJob]: """Get marketplace import jobs with filtering and access control""" query = db.query(MarketplaceImportJob) # Users can only see their own jobs, admins can see all if user.role != "admin": query = query.filter(MarketplaceImportJob.user_id == user.id) # Apply filters if marketplace: query = query.filter(MarketplaceImportJob.marketplace.ilike(f"%{marketplace}%")) if shop_name: query = query.filter(MarketplaceImportJob.shop_name.ilike(f"%{shop_name}%")) # Order by creation date (newest first) and apply pagination jobs = query.order_by(MarketplaceImportJob.created_at.desc()).offset(skip).limit(limit).all() return jobs def update_job_status( self, db: Session, job_id: int, status: str, **kwargs ) -> MarketplaceImportJob: """Update marketplace import job status and other fields""" job = db.query(MarketplaceImportJob).filter(MarketplaceImportJob.id == job_id).first() if not job: raise ValueError("Marketplace import job not found") job.status = status # Update optional fields if provided if 'imported_count' in kwargs: job.imported_count = kwargs['imported_count'] if 'updated_count' in kwargs: job.updated_count = kwargs['updated_count'] if 'total_processed' in kwargs: job.total_processed = kwargs['total_processed'] if 'error_count' in kwargs: job.error_count = kwargs['error_count'] if 'error_message' in kwargs: job.error_message = kwargs['error_message'] if 'started_at' in kwargs: job.started_at = kwargs['started_at'] if 'completed_at' in kwargs: job.completed_at = kwargs['completed_at'] db.commit() db.refresh(job) logger.info(f"Updated marketplace import job {job_id} status to {status}") return job def get_job_stats(self, db: Session, user: User) -> dict: """Get statistics about marketplace import jobs for a user""" query = db.query(MarketplaceImportJob) # Users can only see their own jobs, admins can see all if user.role != "admin": query = query.filter(MarketplaceImportJob.user_id == user.id) total_jobs = query.count() pending_jobs = query.filter(MarketplaceImportJob.status == "pending").count() running_jobs = query.filter(MarketplaceImportJob.status == "running").count() completed_jobs = query.filter(MarketplaceImportJob.status == "completed").count() failed_jobs = query.filter(MarketplaceImportJob.status == "failed").count() return { "total_jobs": total_jobs, "pending_jobs": pending_jobs, "running_jobs": running_jobs, "completed_jobs": completed_jobs, "failed_jobs": failed_jobs } def convert_to_response_model(self, job: MarketplaceImportJob) -> MarketplaceImportJobResponse: """Convert database model to API response model""" return MarketplaceImportJobResponse( job_id=job.id, status=job.status, marketplace=job.marketplace, shop_id=job.shop_id, shop_code=job.shop.shop_code if job.shop else None, # Add this optional field via relationship shop_name=job.shop_name, imported=job.imported_count or 0, updated=job.updated_count or 0, total_processed=job.total_processed or 0, error_count=job.error_count or 0, error_message=job.error_message, created_at=job.created_at, started_at=job.started_at, completed_at=job.completed_at ) def cancel_import_job(self, db: Session, job_id: int, user: User) -> MarketplaceImportJob: """Cancel a pending or running import job""" job = self.get_import_job_by_id(db, job_id, user) if job.status not in ["pending", "running"]: raise ValueError(f"Cannot cancel job with status: {job.status}") job.status = "cancelled" job.completed_at = datetime.utcnow() db.commit() db.refresh(job) logger.info(f"Cancelled marketplace import job {job_id}") return job def delete_import_job(self, db: Session, job_id: int, user: User) -> bool: """Delete a marketplace import job""" job = self.get_import_job_by_id(db, job_id, user) # Only allow deletion of completed, failed, or cancelled jobs if job.status in ["pending", "running"]: raise ValueError(f"Cannot delete job with status: {job.status}. Cancel it first.") db.delete(job) db.commit() logger.info(f"Deleted marketplace import job {job_id}") return True # Create service instance marketplace_service = MarketplaceService()