Refactoring code for modular approach

This commit is contained in:
2025-09-11 20:59:40 +02:00
parent fca389cff4
commit 900229d452
17 changed files with 850 additions and 125 deletions

View File

@@ -1,13 +1,13 @@
from fastapi import APIRouter
from app.api.v1 import auth, products, stock, shops, marketplace, admin, stats
from app.api.v1 import auth, product, stock, shop, marketplace, admin, stats
api_router = APIRouter()
# Include all route modules
api_router.include_router(auth.router, prefix="/auth", tags=["authentication"])
api_router.include_router(products.router, prefix="/products", tags=["products"])
api_router.include_router(product.router, prefix="/product", tags=["product"])
api_router.include_router(stock.router, prefix="/stock", tags=["stock"])
api_router.include_router(shops.router, prefix="/shops", tags=["shops"])
api_router.include_router(shop.router, prefix="/shop", tags=["shop"])
api_router.include_router(marketplace.router, prefix="/marketplace", tags=["marketplace"])
api_router.include_router(admin.router, prefix="/admin", tags=["admin"])
api_router.include_router(stats.router, prefix="/stats", tags=["statistics"])

View File

@@ -1,14 +1,13 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.api.deps import get_current_user, get_current_admin_user
from app.tasks.background_tasks import process_marketplace_import
from app.api.deps import get_current_admin_user
from app.services.admin_service import admin_service
from middleware.decorators import rate_limit
from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest, UserResponse, ShopListResponse
from models.database_models import User, MarketplaceImportJob, Shop
from datetime import datetime
from models.api_models import MarketplaceImportJobResponse, UserResponse, ShopListResponse
from models.database_models import User
import logging
router = APIRouter()
@@ -24,8 +23,12 @@ def get_all_users(
current_admin: User = Depends(get_current_admin_user)
):
"""Get all users (Admin only)"""
users = db.query(User).offset(skip).limit(limit).all()
return [UserResponse.model_validate(user) for user in users]
try:
users = admin_service.get_all_users(db=db, skip=skip, limit=limit)
return [UserResponse.model_validate(user) for user in users]
except Exception as e:
logger.error(f"Error getting users: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/admin/users/{user_id}/status")
@@ -35,20 +38,14 @@ def toggle_user_status(
current_admin: User = Depends(get_current_admin_user)
):
"""Toggle user active status (Admin only)"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
if user.id == current_admin.id:
raise HTTPException(status_code=400, detail="Cannot deactivate your own account")
user.is_active = not user.is_active
user.updated_at = datetime.utcnow()
db.commit()
db.refresh(user)
status = "activated" if user.is_active else "deactivated"
return {"message": f"User {user.username} has been {status}"}
try:
user, message = admin_service.toggle_user_status(db, user_id, current_admin.id)
return {"message": message}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error toggling user {user_id} status: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/admin/shops", response_model=ShopListResponse)
@@ -59,15 +56,18 @@ def get_all_shops_admin(
current_admin: User = Depends(get_current_admin_user)
):
"""Get all shops with admin view (Admin only)"""
total = db.query(Shop).count()
shops = db.query(Shop).offset(skip).limit(limit).all()
try:
shops, total = admin_service.get_all_shops(db=db, skip=skip, limit=limit)
return ShopListResponse(
shops=shops,
total=total,
skip=skip,
limit=limit
)
return ShopListResponse(
shops=shops,
total=total,
skip=skip,
limit=limit
)
except Exception as e:
logger.error(f"Error getting shops: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/admin/shops/{shop_id}/verify")
@@ -77,17 +77,14 @@ def verify_shop(
current_admin: User = Depends(get_current_admin_user)
):
"""Verify/unverify shop (Admin only)"""
shop = db.query(Shop).filter(Shop.id == shop_id).first()
if not shop:
raise HTTPException(status_code=404, detail="Shop not found")
shop.is_verified = not shop.is_verified
shop.updated_at = datetime.utcnow()
db.commit()
db.refresh(shop)
status = "verified" if shop.is_verified else "unverified"
return {"message": f"Shop {shop.shop_code} has been {status}"}
try:
shop, message = admin_service.verify_shop(db, shop_id)
return {"message": message}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error verifying shop {shop_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/admin/shops/{shop_id}/status")
@@ -97,17 +94,14 @@ def toggle_shop_status(
current_admin: User = Depends(get_current_admin_user)
):
"""Toggle shop active status (Admin only)"""
shop = db.query(Shop).filter(Shop.id == shop_id).first()
if not shop:
raise HTTPException(status_code=404, detail="Shop not found")
shop.is_active = not shop.is_active
shop.updated_at = datetime.utcnow()
db.commit()
db.refresh(shop)
status = "activated" if shop.is_active else "deactivated"
return {"message": f"Shop {shop.shop_code} has been {status}"}
try:
shop, message = admin_service.toggle_shop_status(db, shop_id)
return {"message": message}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error toggling shop {shop_id} status: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/admin/marketplace-import-jobs", response_model=List[MarketplaceImportJobResponse])
@@ -121,33 +115,15 @@ def get_all_marketplace_import_jobs(
current_admin: User = Depends(get_current_admin_user)
):
"""Get all marketplace import jobs (Admin only)"""
query = db.query(MarketplaceImportJob)
# 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}%"))
if status:
query = query.filter(MarketplaceImportJob.status == status)
# Order by creation date and apply pagination
jobs = query.order_by(MarketplaceImportJob.created_at.desc()).offset(skip).limit(limit).all()
return [
MarketplaceImportJobResponse(
job_id=job.id,
status=job.status,
marketplace=job.marketplace,
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
) for job in jobs
]
try:
return admin_service.get_marketplace_import_jobs(
db=db,
marketplace=marketplace,
shop_name=shop_name,
status=status,
skip=skip,
limit=limit
)
except Exception as e:
logger.error(f"Error getting marketplace import jobs: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
# Enhanced Product Routes with Marketplace Support
@router.get("/products", response_model=ProductListResponse)
@router.get("/product", response_model=ProductListResponse)
def get_products(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
@@ -58,7 +58,7 @@ def get_products(
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/products", response_model=ProductResponse)
@router.post("/product", response_model=ProductResponse)
def create_product(
product: ProductCreate,
db: Session = Depends(get_db),
@@ -86,7 +86,7 @@ def create_product(
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/products/{product_id}", response_model=ProductDetailResponse)
@router.get("/product/{product_id}", response_model=ProductDetailResponse)
def get_product(
product_id: str,
db: Session = Depends(get_db),
@@ -116,7 +116,7 @@ def get_product(
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/products/{product_id}", response_model=ProductResponse)
@router.put("/product/{product_id}", response_model=ProductResponse)
def update_product(
product_id: str,
product_update: ProductUpdate,
@@ -142,7 +142,7 @@ def update_product(
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/products/{product_id}")
@router.delete("/product/{product_id}")
def delete_product(
product_id: str,
db: Session = Depends(get_db),

View File

@@ -2,18 +2,21 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy import text
from .logging import setup_logging
from .database import engine
from .database import engine, SessionLocal
from models.database_models import Base
import logging
from middleware.auth import AuthManager
logger = logging.getLogger(__name__)
auth_manager = AuthManager()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan events"""
# Startup
logger = setup_logging() # Configure logging first
logger.info("Starting up ecommerce API")
app_logger = setup_logging() # Configure logging first
app_logger.info("Starting up ecommerce API")
# Create tables
Base.metadata.create_all(bind=engine)
@@ -21,10 +24,19 @@ async def lifespan(app: FastAPI):
# Create indexes
create_indexes()
# Create default admin user
db = SessionLocal()
try:
auth_manager.create_default_admin_user(db)
except Exception as e:
logger.error(f"Failed to create default admin user: {e}")
finally:
db.close()
yield
# Shutdown
logger.info("Shutting down ecommerce API")
app_logger.info("Shutting down ecommerce API")
def create_indexes():

View File

@@ -0,0 +1,193 @@
from sqlalchemy.orm import Session
from sqlalchemy import func
from fastapi import HTTPException
from datetime import datetime
import logging
from typing import List, Optional, Tuple
from models.database_models import User, MarketplaceImportJob, Shop
from models.api_models import MarketplaceImportJobResponse
logger = logging.getLogger(__name__)
class AdminService:
"""Service class for admin operations following the application's service pattern"""
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> List[User]:
"""Get paginated list of all users"""
return db.query(User).offset(skip).limit(limit).all()
def toggle_user_status(self, db: Session, user_id: int, current_admin_id: int) -> Tuple[User, str]:
"""
Toggle user active status
Args:
db: Database session
user_id: ID of user to toggle
current_admin_id: ID of the admin performing the action
Returns:
Tuple of (updated_user, status_message)
Raises:
HTTPException: If user not found or trying to deactivate own account
"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
if user.id == current_admin_id:
raise HTTPException(status_code=400, detail="Cannot deactivate your own account")
user.is_active = not user.is_active
user.updated_at = datetime.utcnow()
db.commit()
db.refresh(user)
status = "activated" if user.is_active else "deactivated"
logger.info(f"User {user.username} has been {status} by admin {current_admin_id}")
return user, f"User {user.username} has been {status}"
def get_all_shops(self, db: Session, skip: int = 0, limit: int = 100) -> Tuple[List[Shop], int]:
"""
Get paginated list of all shops with total count
Args:
db: Database session
skip: Number of records to skip
limit: Maximum number of records to return
Returns:
Tuple of (shops_list, total_count)
"""
total = db.query(Shop).count()
shops = db.query(Shop).offset(skip).limit(limit).all()
return shops, total
def verify_shop(self, db: Session, shop_id: int) -> Tuple[Shop, str]:
"""
Toggle shop verification status
Args:
db: Database session
shop_id: ID of shop to verify/unverify
Returns:
Tuple of (updated_shop, status_message)
Raises:
HTTPException: If shop not found
"""
shop = db.query(Shop).filter(Shop.id == shop_id).first()
if not shop:
raise HTTPException(status_code=404, detail="Shop not found")
shop.is_verified = not shop.is_verified
shop.updated_at = datetime.utcnow()
db.commit()
db.refresh(shop)
status = "verified" if shop.is_verified else "unverified"
logger.info(f"Shop {shop.shop_code} has been {status}")
return shop, f"Shop {shop.shop_code} has been {status}"
def toggle_shop_status(self, db: Session, shop_id: int) -> Tuple[Shop, str]:
"""
Toggle shop active status
Args:
db: Database session
shop_id: ID of shop to activate/deactivate
Returns:
Tuple of (updated_shop, status_message)
Raises:
HTTPException: If shop not found
"""
shop = db.query(Shop).filter(Shop.id == shop_id).first()
if not shop:
raise HTTPException(status_code=404, detail="Shop not found")
shop.is_active = not shop.is_active
shop.updated_at = datetime.utcnow()
db.commit()
db.refresh(shop)
status = "activated" if shop.is_active else "deactivated"
logger.info(f"Shop {shop.shop_code} has been {status}")
return shop, f"Shop {shop.shop_code} has been {status}"
def get_marketplace_import_jobs(
self,
db: Session,
marketplace: Optional[str] = None,
shop_name: Optional[str] = None,
status: Optional[str] = None,
skip: int = 0,
limit: int = 100
) -> List[MarketplaceImportJobResponse]:
"""
Get filtered and paginated marketplace import jobs
Args:
db: Database session
marketplace: Filter by marketplace name (case-insensitive partial match)
shop_name: Filter by shop name (case-insensitive partial match)
status: Filter by exact status
skip: Number of records to skip
limit: Maximum number of records to return
Returns:
List of MarketplaceImportJobResponse objects
"""
query = db.query(MarketplaceImportJob)
# 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}%"))
if status:
query = query.filter(MarketplaceImportJob.status == status)
# Order by creation date and apply pagination
jobs = query.order_by(MarketplaceImportJob.created_at.desc()).offset(skip).limit(limit).all()
return [
MarketplaceImportJobResponse(
job_id=job.id,
status=job.status,
marketplace=job.marketplace,
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
) for job in jobs
]
def get_user_by_id(self, db: Session, user_id: int) -> Optional[User]:
"""Get user by ID"""
return db.query(User).filter(User.id == user_id).first()
def get_shop_by_id(self, db: Session, shop_id: int) -> Optional[Shop]:
"""Get shop by ID"""
return db.query(Shop).filter(Shop.id == shop_id).first()
def user_exists(self, db: Session, user_id: int) -> bool:
"""Check if user exists by ID"""
return db.query(User).filter(User.id == user_id).first() is not None
def shop_exists(self, db: Session, shop_id: int) -> bool:
"""Check if shop exists by ID"""
return db.query(Shop).filter(Shop.id == shop_id).first() is not None
# Create service instance following the same pattern as product_service
admin_service = AdminService()