Files
orion/app/services/marketplace_service.py

252 lines
8.5 KiB
Python

# app/services/marketplace_service.py
"""Summary description ....
This module provides classes and functions for:
- ....
- ....
- ....
"""
import logging
from datetime import datetime
from typing import List, Optional
from sqlalchemy import func
from sqlalchemy.orm import Session
from models.api.marketplace import (MarketplaceImportJobResponse,
MarketplaceImportRequest)
from models.database.marketplace import MarketplaceImportJob
from models.database.shop import Shop
from models.database.user import User
logger = logging.getLogger(__name__)
class MarketplaceService:
"""Service class for Marketplace operations following the application's service pattern."""
def __init__(self):
"""Class constructor."""
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}: "
f"{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()