Refactoring code for modular approach

This commit is contained in:
2025-09-09 23:03:08 +02:00
parent 8fbe64687a
commit 1fc8810242
11 changed files with 74 additions and 84 deletions

9
.env
View File

@@ -1,4 +1,9 @@
# .env.example # .env.example
PROJECT_NAME: str = "Ecommerce Backend API with Marketplace Support"
DESCRIPTION: str = "Advanced product management system with JWT authentication"
VERSION: str = "0.0.1"
# Database Configuration # Database Configuration
# DATABASE_URL=postgresql://username:password@localhost:5432/ecommerce_db # DATABASE_URL=postgresql://username:password@localhost:5432/ecommerce_db
# For development, you can use SQLite: # For development, you can use SQLite:
@@ -16,8 +21,8 @@ DEBUG=False
# Rate Limiting # Rate Limiting
RATE_LIMIT_ENABLED=True RATE_LIMIT_ENABLED=True
DEFAULT_RATE_LIMIT=100 RATE_LIMIT_REQUESTS=100
DEFAULT_WINDOW_SECONDS=3600 RATE_LIMIT_WINDOW=3600
# Logging # Logging
LOG_LEVEL=INFO LOG_LEVEL=INFO

View File

@@ -3,10 +3,10 @@ from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.api.deps import get_current_user from app.api.deps import get_current_user, get_current_admin_user
from app.tasks.background_tasks import process_marketplace_import from app.tasks.background_tasks import process_marketplace_import
from middleware.decorators import rate_limit from middleware.decorators import rate_limit
from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest, UserResponse, ShopListResponse
from models.database_models import User, MarketplaceImportJob, Shop from models.database_models import User, MarketplaceImportJob, Shop
from datetime import datetime from datetime import datetime
import logging import logging

View File

@@ -8,7 +8,7 @@ from app.tasks.background_tasks import process_marketplace_import
from middleware.decorators import rate_limit from middleware.decorators import rate_limit
from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest
from models.database_models import User from models.database_models import User
from marketplace_service import marketplace_service from app.services.marketplace_service import MarketplaceService
import logging import logging
router = APIRouter() router = APIRouter()
@@ -30,7 +30,7 @@ async def import_products_from_marketplace(
f"Starting marketplace import: {request.marketplace} -> {request.shop_code} by user {current_user.username}") f"Starting marketplace import: {request.marketplace} -> {request.shop_code} by user {current_user.username}")
# Create import job through service # Create import job through service
import_job = marketplace_service.create_import_job(db, request, current_user) import_job = MarketplaceService.create_import_job(db, request, current_user)
# Process in background # Process in background
background_tasks.add_task( background_tasks.add_task(
@@ -68,8 +68,8 @@ def get_marketplace_import_status(
): ):
"""Get status of marketplace import job (Protected)""" """Get status of marketplace import job (Protected)"""
try: try:
job = marketplace_service.get_import_job_by_id(db, job_id, current_user) job = MarketplaceService.get_import_job_by_id(db, job_id, current_user)
return marketplace_service.convert_to_response_model(job) return MarketplaceService.convert_to_response_model(job)
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=str(e))
@@ -91,7 +91,7 @@ def get_marketplace_import_jobs(
): ):
"""Get marketplace import jobs with filtering (Protected)""" """Get marketplace import jobs with filtering (Protected)"""
try: try:
jobs = marketplace_service.get_import_jobs( jobs = MarketplaceService.get_import_jobs(
db=db, db=db,
user=current_user, user=current_user,
marketplace=marketplace, marketplace=marketplace,
@@ -100,7 +100,7 @@ def get_marketplace_import_jobs(
limit=limit limit=limit
) )
return [marketplace_service.convert_to_response_model(job) for job in jobs] return [MarketplaceService.convert_to_response_model(job) for job in jobs]
except Exception as e: except Exception as e:
logger.error(f"Error getting import jobs: {str(e)}") logger.error(f"Error getting import jobs: {str(e)}")
@@ -114,7 +114,7 @@ def get_marketplace_import_stats(
): ):
"""Get statistics about marketplace import jobs (Protected)""" """Get statistics about marketplace import jobs (Protected)"""
try: try:
stats = marketplace_service.get_job_stats(db, current_user) stats = MarketplaceService.get_job_stats(db, current_user)
return stats return stats
except Exception as e: except Exception as e:
@@ -130,8 +130,8 @@ def cancel_marketplace_import_job(
): ):
"""Cancel a pending or running marketplace import job (Protected)""" """Cancel a pending or running marketplace import job (Protected)"""
try: try:
job = marketplace_service.cancel_import_job(db, job_id, current_user) job = MarketplaceService.cancel_import_job(db, job_id, current_user)
return marketplace_service.convert_to_response_model(job) return MarketplaceService.convert_to_response_model(job)
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@@ -150,7 +150,7 @@ def delete_marketplace_import_job(
): ):
"""Delete a completed marketplace import job (Protected)""" """Delete a completed marketplace import job (Protected)"""
try: try:
marketplace_service.delete_import_job(db, job_id, current_user) MarketplaceService.delete_import_job(db, job_id, current_user)
return {"message": "Marketplace import job deleted successfully"} return {"message": "Marketplace import job deleted successfully"}
except ValueError as e: except ValueError as e:

View File

@@ -3,11 +3,12 @@ from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.api.deps import get_current_user from app.api.deps import get_current_user, get_user_shop
from app.tasks.background_tasks import process_marketplace_import from app.tasks.background_tasks import process_marketplace_import
from middleware.decorators import rate_limit from middleware.decorators import rate_limit
from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest, ShopResponse, ShopCreate, \
from models.database_models import User, MarketplaceImportJob, Shop ShopListResponse, ShopProductResponse, ShopProductCreate
from models.database_models import User, MarketplaceImportJob, Shop, Product, ShopProduct
from datetime import datetime from datetime import datetime
import logging import logging

View File

@@ -1,13 +1,15 @@
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
from sqlalchemy import func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.api.deps import get_current_user from app.api.deps import get_current_user
from app.tasks.background_tasks import process_marketplace_import from app.tasks.background_tasks import process_marketplace_import
from middleware.decorators import rate_limit from middleware.decorators import rate_limit
from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest from models.api_models import MarketplaceImportJobResponse, MarketplaceImportRequest, StatsResponse, \
from models.database_models import User, MarketplaceImportJob, Shop MarketplaceStatsResponse
from models.database_models import User, MarketplaceImportJob, Shop, Product, Stock
from datetime import datetime from datetime import datetime
import logging import logging

View File

@@ -9,7 +9,7 @@ from middleware.decorators import rate_limit
from models.api_models import (MarketplaceImportJobResponse, MarketplaceImportRequest, StockResponse, from models.api_models import (MarketplaceImportJobResponse, MarketplaceImportRequest, StockResponse,
StockSummaryResponse, StockCreate, StockAdd, StockUpdate) StockSummaryResponse, StockCreate, StockAdd, StockUpdate)
from models.database_models import User, MarketplaceImportJob, Shop from models.database_models import User, MarketplaceImportJob, Shop
from stock_service import stock_service from app.services.stock_service import StockService
import logging import logging
router = APIRouter() router = APIRouter()
@@ -26,7 +26,7 @@ def set_stock(
): ):
"""Set exact stock quantity for a GTIN at a specific location (replaces existing quantity)""" """Set exact stock quantity for a GTIN at a specific location (replaces existing quantity)"""
try: try:
result = stock_service.set_stock(db, stock) result = StockService.set_stock(db, stock)
return result return result
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@@ -43,7 +43,7 @@ def add_stock(
): ):
"""Add quantity to existing stock for a GTIN at a specific location (adds to existing quantity)""" """Add quantity to existing stock for a GTIN at a specific location (adds to existing quantity)"""
try: try:
result = stock_service.add_stock(db, stock) result = StockService.add_stock(db, stock)
return result return result
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@@ -60,7 +60,7 @@ def remove_stock(
): ):
"""Remove quantity from existing stock for a GTIN at a specific location""" """Remove quantity from existing stock for a GTIN at a specific location"""
try: try:
result = stock_service.remove_stock(db, stock) result = StockService.remove_stock(db, stock)
return result return result
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@@ -77,7 +77,7 @@ def get_stock_by_gtin(
): ):
"""Get all stock locations and total quantity for a specific GTIN""" """Get all stock locations and total quantity for a specific GTIN"""
try: try:
result = stock_service.get_stock_by_gtin(db, gtin) result = StockService.get_stock_by_gtin(db, gtin)
return result return result
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=str(e))
@@ -94,7 +94,7 @@ def get_total_stock(
): ):
"""Get total quantity in stock for a specific GTIN""" """Get total quantity in stock for a specific GTIN"""
try: try:
result = stock_service.get_total_stock(db, gtin) result = StockService.get_total_stock(db, gtin)
return result return result
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@@ -114,7 +114,7 @@ def get_all_stock(
): ):
"""Get all stock entries with optional filtering""" """Get all stock entries with optional filtering"""
try: try:
result = stock_service.get_all_stock( result = StockService.get_all_stock(
db=db, db=db,
skip=skip, skip=skip,
limit=limit, limit=limit,
@@ -136,7 +136,7 @@ def update_stock(
): ):
"""Update stock quantity for a specific stock entry""" """Update stock quantity for a specific stock entry"""
try: try:
result = stock_service.update_stock(db, stock_id, stock_update) result = StockService.update_stock(db, stock_id, stock_update)
return result return result
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=str(e))
@@ -153,7 +153,7 @@ def delete_stock(
): ):
"""Delete a stock entry""" """Delete a stock entry"""
try: try:
stock_service.delete_stock(db, stock_id) StockService.delete_stock(db, stock_id)
return {"message": "Stock entry deleted successfully"} return {"message": "Stock entry deleted successfully"}
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=str(e))

View File

@@ -1,26 +1,40 @@
from pydantic_settings import BaseSettings # app/core/config.py
from typing import List from pydantic_settings import BaseSettings # This is the correct import for Pydantic v2
import os from typing import Optional, List
class Settings(BaseSettings): class Settings(BaseSettings):
PROJECT_NAME: str = "Ecommerce Backend API with Marketplace Support" # Project information
DESCRIPTION: str = "Advanced product management system with JWT authentication" project_name: str = "Ecommerce Backend API with Marketplace Support"
VERSION: str = "2.2.0" description: str = "Advanced product management system with JWT authentication"
version: str = "2.2.0"
DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql://user:password@localhost/ecommerce") # Database
SECRET_KEY: str = os.getenv("SECRET_KEY", "your-secret-key-change-in-production") database_url: str = "sqlite:///./ecommerce.db"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
ALLOWED_HOSTS: List[str] = ["*"] # Configure for production # JWT
jwt_secret_key: str = "change-this-in-production"
jwt_expire_hours: int = 24
jwt_expire_minutes: int = 30
# Rate limiting # Middleware
RATE_LIMIT_REQUESTS: int = 100 allowed_hosts: List[str] = ["*"] # Configure for production
RATE_LIMIT_WINDOW: int = 3600
class Config: # API
env_file = ".env" api_host: str = "0.0.0.0"
extra = "ignore" api_port: int = 8000
debug: bool = False
# Rate Limiting
rate_limit_enabled: bool = True
rate_limit_requests: int = 100
rate_limit_window: int = 3600
# Logging
log_level: str = "INFO"
log_file: Optional[str] = None
model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2
settings = Settings() settings = Settings()

View File

@@ -3,7 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session from sqlalchemy.orm import sessionmaker, Session
from .config import settings from .config import settings
engine = create_engine(settings.DATABASE_URL) engine = create_engine(settings.database_url)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() Base = declarative_base()

View File

@@ -9,12 +9,12 @@ def setup_logging():
"""Configure application logging with file and console handlers""" """Configure application logging with file and console handlers"""
# Create logs directory if it doesn't exist # Create logs directory if it doesn't exist
log_file = Path(settings.LOG_FILE) log_file = Path(settings.log_file)
log_file.parent.mkdir(parents=True, exist_ok=True) log_file.parent.mkdir(parents=True, exist_ok=True)
# Configure root logger # Configure root logger
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper())) logger.setLevel(getattr(logging, settings.log_level.upper()))
# Remove existing handlers # Remove existing handlers
for handler in logger.handlers[:]: for handler in logger.handlers[:]:

View File

@@ -1,32 +0,0 @@
# config/settings.py
from pydantic_settings import BaseSettings # This is the correct import for Pydantic v2
from typing import Optional
class Settings(BaseSettings):
# Database
database_url: str = "sqlite:///./ecommerce.db"
# JWT
jwt_secret_key: str = "change-this-in-production"
jwt_expire_hours: int = 24
jwt_expire_minutes: int = 30
# API
api_host: str = "0.0.0.0"
api_port: int = 8000
debug: bool = False
# Rate Limiting
rate_limit_enabled: bool = True
default_rate_limit: int = 100
default_window_seconds: int = 3600
# Logging
log_level: str = "INFO"
log_file: Optional[str] = None
model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2
settings = Settings()

10
main.py
View File

@@ -13,16 +13,16 @@ logger = logging.getLogger(__name__)
# FastAPI app with lifespan # FastAPI app with lifespan
app = FastAPI( app = FastAPI(
title=settings.PROJECT_NAME, title=settings.project_name,
description=settings.DESCRIPTION, description=settings.description,
version=settings.VERSION, version=settings.version,
lifespan=lifespan lifespan=lifespan
) )
# Add CORS middleware # Add CORS middleware
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=settings.ALLOWED_HOSTS, allow_origins=settings.allowed_hosts,
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
@@ -37,7 +37,7 @@ app.include_router(api_router, prefix="/api/v1")
@app.get("/") @app.get("/")
def root(): def root():
return { return {
"message": f"{settings.PROJECT_NAME} v{settings.VERSION}", "message": f"{settings.project_name} v{settings.version}",
"status": "operational", "status": "operational",
"docs": "/docs", "docs": "/docs",
"features": [ "features": [