diff --git a/README.md b/README.md index d61b4111..7e39e00a 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ import sys # Add your project directory to the Python path sys.path.append(os.path.dirname(os.path.dirname(__file__))) -from models.database_models import Base +from models.database import Base from config.settings import settings # Alembic Config object diff --git a/alembic-env.py b/alembic-env.py index 7d751343..524c71f2 100644 --- a/alembic-env.py +++ b/alembic-env.py @@ -10,7 +10,7 @@ from alembic import context sys.path.append(os.path.dirname(os.path.dirname(__file__))) from app.core.config import settings -from models.database_models import Base +from models.database import Base # Alembic Config object config = context.config diff --git a/alembic/env.py b/alembic/env.py index 7d751343..e9959486 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -10,7 +10,7 @@ from alembic import context sys.path.append(os.path.dirname(os.path.dirname(__file__))) from app.core.config import settings -from models.database_models import Base +from models.database.base import Base # Alembic Config object config = context.config diff --git a/app/api/deps.py b/app/api/deps.py index e8ba57ac..2fcc7ed9 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -14,7 +14,8 @@ from sqlalchemy.orm import Session from app.core.database import get_db from middleware.auth import AuthManager from middleware.rate_limiter import RateLimiter -from models.database_models import Shop, User +from models.database.user import User +from models.database.shop import Shop # Set auto_error=False to prevent automatic 403 responses security = HTTPBearer(auto_error=False) diff --git a/app/api/v1/admin.py b/app/api/v1/admin.py index 3da3c782..a968eb0f 100644 --- a/app/api/v1/admin.py +++ b/app/api/v1/admin.py @@ -16,9 +16,10 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_admin_user from app.core.database import get_db from app.services.admin_service import admin_service -from models.api_models import (MarketplaceImportJobResponse, ShopListResponse, - UserResponse) -from models.database_models import User +from models.api.marketplace import MarketplaceImportJobResponse +from models.api.shop import ShopListResponse +from models.api.auth import UserResponse +from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/auth.py b/app/api/v1/auth.py index a9ed4271..96c60b3f 100644 --- a/app/api/v1/auth.py +++ b/app/api/v1/auth.py @@ -15,9 +15,9 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.auth_service import auth_service -from models.api_models import (LoginResponse, UserLogin, UserRegister, +from models.api.auth import (LoginResponse, UserLogin, UserRegister, UserResponse) -from models.database_models import User +from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/marketplace.py b/app/api/v1/marketplace.py index 7881b846..4560648a 100644 --- a/app/api/v1/marketplace.py +++ b/app/api/v1/marketplace.py @@ -18,9 +18,9 @@ from app.core.database import get_db from app.services.marketplace_service import marketplace_service from app.tasks.background_tasks import process_marketplace_import from middleware.decorators import rate_limit -from models.api_models import (MarketplaceImportJobResponse, +from models.api.marketplace import (MarketplaceImportJobResponse, MarketplaceImportRequest) -from models.database_models import User +from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/product.py b/app/api/v1/product.py index 52979989..92f8bcfe 100644 --- a/app/api/v1/product.py +++ b/app/api/v1/product.py @@ -17,10 +17,10 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.product_service import product_service -from models.api_models import (ProductCreate, ProductDetailResponse, +from models.api.product import (ProductCreate, ProductDetailResponse, ProductListResponse, ProductResponse, ProductUpdate) -from models.database_models import User +from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/shop.py b/app/api/v1/shop.py index e0b32fe2..5051fe76 100644 --- a/app/api/v1/shop.py +++ b/app/api/v1/shop.py @@ -15,9 +15,9 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_user, get_user_shop from app.core.database import get_db from app.services.shop_service import shop_service -from models.api_models import (ShopCreate, ShopListResponse, ShopProductCreate, +from models.api.shop import (ShopCreate, ShopListResponse, ShopProductCreate, ShopProductResponse, ShopResponse) -from models.database_models import User +from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/stats.py b/app/api/v1/stats.py index ddcb3f6d..5486c3fa 100644 --- a/app/api/v1/stats.py +++ b/app/api/v1/stats.py @@ -16,8 +16,8 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.stats_service import stats_service -from models.api_models import MarketplaceStatsResponse, StatsResponse -from models.database_models import User +from models.api.stats import MarketplaceStatsResponse, StatsResponse +from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/api/v1/stock.py b/app/api/v1/stock.py index bd5a4eb1..c41b62ac 100644 --- a/app/api/v1/stock.py +++ b/app/api/v1/stock.py @@ -16,9 +16,9 @@ from sqlalchemy.orm import Session from app.api.deps import get_current_user from app.core.database import get_db from app.services.stock_service import stock_service -from models.api_models import (StockAdd, StockCreate, StockResponse, +from models.api.stock import (StockAdd, StockCreate, StockResponse, StockSummaryResponse, StockUpdate) -from models.database_models import User +from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) diff --git a/app/core/lifespan.py b/app/core/lifespan.py index 2233edd2..0962e666 100644 --- a/app/core/lifespan.py +++ b/app/core/lifespan.py @@ -14,7 +14,7 @@ from fastapi import FastAPI from sqlalchemy import text from middleware.auth import AuthManager -from models.database_models import Base +from models.database.base import Base from .database import SessionLocal, engine from .logging import setup_logging diff --git a/app/services/admin_service.py b/app/services/admin_service.py index 179e5245..8934631a 100644 --- a/app/services/admin_service.py +++ b/app/services/admin_service.py @@ -14,8 +14,10 @@ from typing import List, Optional, Tuple from fastapi import HTTPException from sqlalchemy.orm import Session -from models.api_models import MarketplaceImportJobResponse -from models.database_models import MarketplaceImportJob, Shop, User +from models.api.marketplace import MarketplaceImportJobResponse +from models.database.marketplace import MarketplaceImportJob +from models.database.shop import Shop +from models.database.user import User logger = logging.getLogger(__name__) diff --git a/app/services/auth_service.py b/app/services/auth_service.py index 4a264220..44f8b8fc 100644 --- a/app/services/auth_service.py +++ b/app/services/auth_service.py @@ -14,8 +14,8 @@ from fastapi import HTTPException from sqlalchemy.orm import Session from middleware.auth import AuthManager -from models.api_models import UserLogin, UserRegister -from models.database_models import User +from models.api.auth import UserLogin, UserRegister +from models.database.user import User logger = logging.getLogger(__name__) diff --git a/app/services/marketplace_service.py b/app/services/marketplace_service.py index 94daff7f..1746de0e 100644 --- a/app/services/marketplace_service.py +++ b/app/services/marketplace_service.py @@ -14,9 +14,11 @@ from typing import List, Optional from sqlalchemy import func from sqlalchemy.orm import Session -from models.api_models import (MarketplaceImportJobResponse, +from models.api.marketplace import (MarketplaceImportJobResponse, MarketplaceImportRequest) -from models.database_models import MarketplaceImportJob, Shop, User +from models.database.marketplace import MarketplaceImportJob +from models.database.shop import Shop +from models.database.user import User logger = logging.getLogger(__name__) diff --git a/app/services/product_service.py b/app/services/product_service.py index 80b6a935..290485ab 100644 --- a/app/services/product_service.py +++ b/app/services/product_service.py @@ -14,9 +14,10 @@ from typing import Generator, List, Optional from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session -from models.api_models import (ProductCreate, ProductUpdate, - StockLocationResponse, StockSummaryResponse) -from models.database_models import Product, Stock +from models.api.product import ProductCreate, ProductUpdate +from models.api.stock import StockLocationResponse, StockSummaryResponse +from models.database.product import Product +from models.database.stock import Stock from utils.data_processing import GTINProcessor, PriceProcessor logger = logging.getLogger(__name__) diff --git a/app/services/shop_service.py b/app/services/shop_service.py index fae9f1df..3f601853 100644 --- a/app/services/shop_service.py +++ b/app/services/shop_service.py @@ -14,8 +14,10 @@ from fastapi import HTTPException from sqlalchemy import func from sqlalchemy.orm import Session -from models.api_models import ShopCreate, ShopProductCreate -from models.database_models import Product, Shop, ShopProduct, User +from models.api.shop import ShopCreate, ShopProductCreate +from models.database.product import Product +from models.database.shop import Shop, ShopProduct +from models.database.user import User logger = logging.getLogger(__name__) diff --git a/app/services/stats_service.py b/app/services/stats_service.py index 13ec7b74..108abddc 100644 --- a/app/services/stats_service.py +++ b/app/services/stats_service.py @@ -13,7 +13,8 @@ from typing import Any, Dict, List from sqlalchemy import func from sqlalchemy.orm import Session -from models.database_models import Product, Stock +from models.database.product import Product +from models.database.stock import Stock logger = logging.getLogger(__name__) diff --git a/app/services/stock_service.py b/app/services/stock_service.py index 3c39d797..b362ee78 100644 --- a/app/services/stock_service.py +++ b/app/services/stock_service.py @@ -13,9 +13,10 @@ from typing import List, Optional from sqlalchemy.orm import Session -from models.api_models import (StockAdd, StockCreate, StockLocationResponse, +from models.api.stock import (StockAdd, StockCreate, StockLocationResponse, StockSummaryResponse, StockUpdate) -from models.database_models import Product, Stock +from models.database.product import Product +from models.database.stock import Stock from utils.data_processing import GTINProcessor logger = logging.getLogger(__name__) diff --git a/app/tasks/background_tasks.py b/app/tasks/background_tasks.py index fb2e55cd..47e9e813 100644 --- a/app/tasks/background_tasks.py +++ b/app/tasks/background_tasks.py @@ -11,7 +11,7 @@ import logging from datetime import datetime from app.core.database import SessionLocal -from models.database_models import MarketplaceImportJob +from models.database.marketplace import MarketplaceImportJob from utils.csv_processor import CSVProcessor logger = logging.getLogger(__name__) diff --git a/models/api_models.py b/backup/api_models.py similarity index 100% rename from models/api_models.py rename to backup/api_models.py diff --git a/models/database_models.py b/backup/database_models.py similarity index 100% rename from models/database_models.py rename to backup/database_models.py diff --git a/backup/test_background_tasks.py b/backup/test_background_tasks.py index 5edf0153..06dd3c22 100644 --- a/backup/test_background_tasks.py +++ b/backup/test_background_tasks.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, patch import pytest from app.tasks.background_tasks import process_marketplace_import -from models.database_models import MarketplaceImportJob +from models.database.marketplace import MarketplaceImportJob class TestBackgroundTasks: diff --git a/backup/test_export.py b/backup/test_export.py index a1dd0d89..fb3bc699 100644 --- a/backup/test_export.py +++ b/backup/test_export.py @@ -4,7 +4,7 @@ from io import StringIO import pytest -from models.database_models import Product +from models.database.product import Product class TestExportFunctionality: diff --git a/backup/test_filtering.py b/backup/test_filtering.py index fbfd8623..9c5f16a7 100644 --- a/backup/test_filtering.py +++ b/backup/test_filtering.py @@ -1,7 +1,7 @@ # tests/test_filtering.py import pytest -from models.database_models import Product +from models.database.product import Product class TestFiltering: diff --git a/middleware/auth.py b/middleware/auth.py index 34d8fb27..d916e6a2 100644 --- a/middleware/auth.py +++ b/middleware/auth.py @@ -18,7 +18,7 @@ from jose import jwt from passlib.context import CryptContext from sqlalchemy.orm import Session -from models.database_models import User +from models.database.user import User logger = logging.getLogger(__name__) diff --git a/models/api/__init__.py b/models/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/models/api/auth.py b/models/api/auth.py new file mode 100644 index 00000000..fe6201bd --- /dev/null +++ b/models/api/auth.py @@ -0,0 +1,62 @@ +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator + + +# User Authentication Models +class UserRegister(BaseModel): + email: EmailStr = Field(..., description="Valid email address") + username: str = Field( + ..., min_length=3, max_length=50, description="Username (3-50 characters)" + ) + password: str = Field( + ..., min_length=6, description="Password (minimum 6 characters)" + ) + + @field_validator("username") + @classmethod + def validate_username(cls, v): + if not re.match(r"^[a-zA-Z0-9_]+$", v): + raise ValueError( + "Username must contain only letters, numbers, or underscores" + ) + return v.lower().strip() + + @field_validator("password") + @classmethod + def validate_password(cls, v): + if len(v) < 6: + raise ValueError("Password must be at least 6 characters long") + return v + + +class UserLogin(BaseModel): + username: str = Field(..., description="Username") + password: str = Field(..., description="Password") + + @field_validator("username") + @classmethod + def validate_username(cls, v): + return v.strip() + + +class UserResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int + email: str + username: str + role: str + is_active: bool + last_login: Optional[datetime] = None + created_at: datetime + updated_at: datetime + + +class LoginResponse(BaseModel): + access_token: str + token_type: str = "bearer" + expires_in: int + user: UserResponse diff --git a/models/api/base.py b/models/api/base.py new file mode 100644 index 00000000..0b26ddd3 --- /dev/null +++ b/models/api/base.py @@ -0,0 +1,16 @@ +from typing import List, TypeVar, Generic +from pydantic import BaseModel + +T = TypeVar('T') + +class ListResponse(BaseModel, Generic[T]): + """Generic list response model""" + items: List[T] + total: int + skip: int + limit: int + +class StatusResponse(BaseModel): + """Generic status response""" + success: bool + message: str diff --git a/models/api/marketplace.py b/models/api/marketplace.py new file mode 100644 index 00000000..f071b190 --- /dev/null +++ b/models/api/marketplace.py @@ -0,0 +1,65 @@ +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator + + +# Marketplace Import Models +class MarketplaceImportRequest(BaseModel): + url: str = Field(..., description="URL to CSV file from marketplace") + marketplace: str = Field( + default="Letzshop", + description="Name of the marketplace (e.g., Letzshop, Amazon, eBay)", + ) + shop_code: str = Field(..., description="Shop code to associate products with") + batch_size: Optional[int] = Field( + 1000, gt=0, le=10000, description="Batch size for processing" + ) + + @field_validator("url") + @classmethod + def validate_url(cls, v): + if not v.startswith(("http://", "https://")): + raise ValueError("URL must start with http:// or https://") + return v + + @field_validator("marketplace") + @classmethod + def validate_marketplace(cls, v): + # You can add validation for supported marketplaces here + supported_marketplaces = [ + "Letzshop", + "Amazon", + "eBay", + "Etsy", + "Shopify", + "Other", + ] + if v not in supported_marketplaces: + # For now, allow any marketplace but log it + pass + return v.strip() + + @field_validator("shop_code") + @classmethod + def validate_shop_code(cls, v): + return v.upper().strip() + + +class MarketplaceImportJobResponse(BaseModel): + job_id: int + status: str + marketplace: str + shop_id: int + shop_code: Optional[str] = None # Will be populated from shop relationship + shop_name: str + message: Optional[str] = None + imported: Optional[int] = 0 + updated: Optional[int] = 0 + total_processed: Optional[int] = 0 + error_count: Optional[int] = 0 + error_message: Optional[str] = None + created_at: Optional[datetime] = None + started_at: Optional[datetime] = None + completed_at: Optional[datetime] = None diff --git a/models/api/product.py b/models/api/product.py new file mode 100644 index 00000000..d587be77 --- /dev/null +++ b/models/api/product.py @@ -0,0 +1,83 @@ +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator +from models.api.stock import StockSummaryResponse + +class ProductBase(BaseModel): + product_id: Optional[str] = None + title: Optional[str] = None + description: Optional[str] = None + link: Optional[str] = None + image_link: Optional[str] = None + availability: Optional[str] = None + price: Optional[str] = None + brand: Optional[str] = None + gtin: Optional[str] = None + mpn: Optional[str] = None + condition: Optional[str] = None + adult: Optional[str] = None + multipack: Optional[int] = None + is_bundle: Optional[str] = None + age_group: Optional[str] = None + color: Optional[str] = None + gender: Optional[str] = None + material: Optional[str] = None + pattern: Optional[str] = None + size: Optional[str] = None + size_type: Optional[str] = None + size_system: Optional[str] = None + item_group_id: Optional[str] = None + google_product_category: Optional[str] = None + product_type: Optional[str] = None + custom_label_0: Optional[str] = None + custom_label_1: Optional[str] = None + custom_label_2: Optional[str] = None + custom_label_3: Optional[str] = None + custom_label_4: Optional[str] = None + additional_image_link: Optional[str] = None + sale_price: Optional[str] = None + unit_pricing_measure: Optional[str] = None + unit_pricing_base_measure: Optional[str] = None + identifier_exists: Optional[str] = None + shipping: Optional[str] = None + currency: Optional[str] = None + # New marketplace fields + marketplace: Optional[str] = None + shop_name: Optional[str] = None + + +class ProductCreate(ProductBase): + product_id: str = Field(..., min_length=1, description="Product ID is required") + title: str = Field(..., min_length=1, description="Title is required") + + @field_validator("product_id", "title") + @classmethod + def validate_required_fields(cls, v): + if not v or not v.strip(): + raise ValueError("Field cannot be empty") + return v.strip() + + +class ProductUpdate(ProductBase): + pass + + +class ProductResponse(ProductBase): + model_config = ConfigDict(from_attributes=True) + + id: int + created_at: datetime + updated_at: datetime + +class ProductListResponse(BaseModel): + products: List[ProductResponse] + total: int + skip: int + limit: int + + +class ProductDetailResponse(BaseModel): + product: ProductResponse + stock_info: Optional[StockSummaryResponse] = None diff --git a/models/api/shop.py b/models/api/shop.py new file mode 100644 index 00000000..d27fd9b7 --- /dev/null +++ b/models/api/shop.py @@ -0,0 +1,125 @@ +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator +from models.api.product import ProductResponse + +class ShopCreate(BaseModel): + shop_code: str = Field( + ..., + min_length=3, + max_length=50, + description="Unique shop code (e.g., TECHSTORE)", + ) + shop_name: str = Field( + ..., min_length=1, max_length=200, description="Display name of the shop" + ) + description: Optional[str] = Field( + None, max_length=2000, description="Shop description" + ) + contact_email: Optional[str] = None + contact_phone: Optional[str] = None + website: Optional[str] = None + business_address: Optional[str] = None + tax_number: Optional[str] = None + + @field_validator("shop_code") + def validate_shop_code(cls, v): + # Convert to uppercase and check format + v = v.upper().strip() + if not v.replace("_", "").replace("-", "").isalnum(): + raise ValueError( + "Shop code must be alphanumeric (underscores and hyphens allowed)" + ) + return v + + @field_validator("contact_email") + def validate_contact_email(cls, v): + if v and ("@" not in v or "." not in v): + raise ValueError("Invalid email format") + return v.lower() if v else v + + +class ShopUpdate(BaseModel): + shop_name: Optional[str] = Field(None, min_length=1, max_length=200) + description: Optional[str] = Field(None, max_length=2000) + contact_email: Optional[str] = None + contact_phone: Optional[str] = None + website: Optional[str] = None + business_address: Optional[str] = None + tax_number: Optional[str] = None + + @field_validator("contact_email") + def validate_contact_email(cls, v): + if v and ("@" not in v or "." not in v): + raise ValueError("Invalid email format") + return v.lower() if v else v + + +class ShopResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int + shop_code: str + shop_name: str + description: Optional[str] + owner_id: int + contact_email: Optional[str] + contact_phone: Optional[str] + website: Optional[str] + business_address: Optional[str] + tax_number: Optional[str] + is_active: bool + is_verified: bool + created_at: datetime + updated_at: datetime + + +class ShopListResponse(BaseModel): + shops: List[ShopResponse] + total: int + skip: int + limit: int + +class ShopProductCreate(BaseModel): + product_id: str = Field(..., description="Product ID to add to shop") + shop_product_id: Optional[str] = Field( + None, description="Shop's internal product ID" + ) + shop_price: Optional[float] = Field( + None, ge=0, description="Shop-specific price override" + ) + shop_sale_price: Optional[float] = Field( + None, ge=0, description="Shop-specific sale price" + ) + shop_currency: Optional[str] = Field(None, description="Shop-specific currency") + shop_availability: Optional[str] = Field( + None, description="Shop-specific availability" + ) + shop_condition: Optional[str] = Field(None, description="Shop-specific condition") + is_featured: bool = Field(False, description="Featured product flag") + min_quantity: int = Field(1, ge=1, description="Minimum order quantity") + max_quantity: Optional[int] = Field( + None, ge=1, description="Maximum order quantity" + ) + + +class ShopProductResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int + shop_id: int + product: ProductResponse + shop_product_id: Optional[str] + shop_price: Optional[float] + shop_sale_price: Optional[float] + shop_currency: Optional[str] + shop_availability: Optional[str] + shop_condition: Optional[str] + is_featured: bool + is_active: bool + min_quantity: int + max_quantity: Optional[int] + created_at: datetime + updated_at: datetime diff --git a/models/api/stats.py b/models/api/stats.py new file mode 100644 index 00000000..9d09a208 --- /dev/null +++ b/models/api/stats.py @@ -0,0 +1,22 @@ +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator + + +class StatsResponse(BaseModel): + total_products: int + unique_brands: int + unique_categories: int + unique_marketplaces: int = 0 + unique_shops: int = 0 + total_stock_entries: int = 0 + total_inventory_quantity: int = 0 + + +class MarketplaceStatsResponse(BaseModel): + marketplace: str + total_products: int + unique_shops: int + unique_brands: int \ No newline at end of file diff --git a/models/api/stock.py b/models/api/stock.py new file mode 100644 index 00000000..f6d057bb --- /dev/null +++ b/models/api/stock.py @@ -0,0 +1,46 @@ +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator + + +# Stock Models +class StockBase(BaseModel): + gtin: str = Field(..., min_length=1, description="GTIN is required") + location: str = Field(..., min_length=1, description="Location is required") + + +class StockCreate(StockBase): + quantity: int = Field(ge=0, description="Quantity must be non-negative") + + +class StockAdd(StockBase): + quantity: int = Field(gt=0, description="Quantity to add must be positive") + + +class StockUpdate(BaseModel): + quantity: int = Field(ge=0, description="Quantity must be non-negative") + + +class StockResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int + gtin: str + location: str + quantity: int + created_at: datetime + updated_at: datetime + + +class StockLocationResponse(BaseModel): + location: str + quantity: int + + +class StockSummaryResponse(BaseModel): + gtin: str + total_quantity: int + locations: List[StockLocationResponse] + product_title: Optional[str] = None \ No newline at end of file diff --git a/models/database/__init__.py b/models/database/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/models/database/base.py b/models/database/base.py new file mode 100644 index 00000000..c7e48696 --- /dev/null +++ b/models/database/base.py @@ -0,0 +1,10 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime +from app.core.database import Base + +class TimestampMixin: + """Mixin to add created_at and updated_at timestamps to models""" + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column( + DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False + ) diff --git a/models/database/marketplace.py b/models/database/marketplace.py new file mode 100644 index 00000000..8c347da0 --- /dev/null +++ b/models/database/marketplace.py @@ -0,0 +1,61 @@ +from datetime import datetime + +from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index, + Integer, String, Text, UniqueConstraint) +from sqlalchemy.orm import relationship + +# Import Base from the central database module instead of creating a new one +from app.core.database import Base + + +class MarketplaceImportJob(Base): + __tablename__ = "marketplace_import_jobs" + + id = Column(Integer, primary_key=True, index=True) + status = Column( + String, nullable=False, default="pending" + ) # pending, processing, completed, failed, completed_with_errors + source_url = Column(String, nullable=False) + marketplace = Column( + String, nullable=False, index=True, default="Letzshop" + ) # Index for marketplace filtering + shop_name = Column(String, nullable=False, index=True) # Index for shop filtering + shop_id = Column( + Integer, ForeignKey("shops.id"), nullable=False + ) # Add proper foreign key + user_id = Column( + Integer, ForeignKey("users.id"), nullable=False + ) # Foreign key to users table + + # Results + imported_count = Column(Integer, default=0) + updated_count = Column(Integer, default=0) + error_count = Column(Integer, default=0) + total_processed = Column(Integer, default=0) + + # Error handling + error_message = Column(String) + + # Timestamps + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + started_at = Column(DateTime) + completed_at = Column(DateTime) + + # Relationship to user + user = relationship("User", foreign_keys=[user_id]) + shop = relationship("Shop", back_populates="marketplace_import_jobs") + + # Additional indexes for marketplace import job queries + __table_args__ = ( + Index( + "idx_marketplace_import_user_marketplace", "user_id", "marketplace" + ), # User's marketplace imports + Index("idx_marketplace_import_shop_status", "status"), # Shop import status + Index("idx_marketplace_import_shop_id", "shop_id"), + ) + + def __repr__(self): + return ( + f"" + ) diff --git a/models/database/product.py b/models/database/product.py new file mode 100644 index 00000000..5181d95e --- /dev/null +++ b/models/database/product.py @@ -0,0 +1,88 @@ +from datetime import datetime + +from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index, + Integer, String, Text, UniqueConstraint) +from sqlalchemy.orm import relationship + +# Import Base from the central database module instead of creating a new one +from app.core.database import Base + + +class Product(Base): + __tablename__ = "products" + + id = Column(Integer, primary_key=True, index=True) + product_id = Column(String, unique=True, index=True, nullable=False) + title = Column(String, nullable=False) + description = Column(String) + link = Column(String) + image_link = Column(String) + availability = Column(String, index=True) # Index for filtering + price = Column(String) + brand = Column(String, index=True) # Index for filtering + gtin = Column(String, index=True) # Index for stock lookups + mpn = Column(String) + condition = Column(String) + adult = Column(String) + multipack = Column(Integer) + is_bundle = Column(String) + age_group = Column(String) + color = Column(String) + gender = Column(String) + material = Column(String) + pattern = Column(String) + size = Column(String) + size_type = Column(String) + size_system = Column(String) + item_group_id = Column(String) + google_product_category = Column(String, index=True) # Index for filtering + product_type = Column(String) + custom_label_0 = Column(String) + custom_label_1 = Column(String) + custom_label_2 = Column(String) + custom_label_3 = Column(String) + custom_label_4 = Column(String) + additional_image_link = Column(String) + sale_price = Column(String) + unit_pricing_measure = Column(String) + unit_pricing_base_measure = Column(String) + identifier_exists = Column(String) + shipping = Column(String) + currency = Column(String) + + # New marketplace fields + marketplace = Column( + String, index=True, nullable=True, default="Letzshop" + ) # Index for marketplace filtering + shop_name = Column(String, index=True, nullable=True) # Index for shop filtering + + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column( + DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False + ) + + # Relationship to stock (one-to-many via GTIN) + stock_entries = relationship( + "Stock", + foreign_keys="Stock.gtin", + primaryjoin="Product.gtin == Stock.gtin", + viewonly=True, + ) + shop_products = relationship("ShopProduct", back_populates="product") + + # Additional indexes for marketplace queries + __table_args__ = ( + Index( + "idx_marketplace_shop", "marketplace", "shop_name" + ), # Composite index for marketplace+shop queries + Index( + "idx_marketplace_brand", "marketplace", "brand" + ), # Composite index for marketplace+brand queries + ) + + def __repr__(self): + return ( + f"" + ) + diff --git a/models/database/shop.py b/models/database/shop.py new file mode 100644 index 00000000..09ca6ffd --- /dev/null +++ b/models/database/shop.py @@ -0,0 +1,84 @@ +from datetime import datetime + +from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index, + Integer, String, Text, UniqueConstraint) +from sqlalchemy.orm import relationship + +# Import Base from the central database module instead of creating a new one +from app.core.database import Base + + +class Shop(Base): + __tablename__ = "shops" + + id = Column(Integer, primary_key=True, index=True) + shop_code = Column( + String, unique=True, index=True, nullable=False + ) # e.g., "TECHSTORE", "FASHIONHUB" + shop_name = Column(String, nullable=False) # Display name + description = Column(Text) + owner_id = Column(Integer, ForeignKey("users.id"), nullable=False) + + # Contact information + contact_email = Column(String) + contact_phone = Column(String) + website = Column(String) + + # Business information + business_address = Column(Text) + tax_number = Column(String) + + # Status + is_active = Column(Boolean, default=True) + is_verified = Column(Boolean, default=False) + + # Timestamps + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # Relationships + owner = relationship("User", back_populates="owned_shops") + shop_products = relationship("ShopProduct", back_populates="shop") + marketplace_import_jobs = relationship( + "MarketplaceImportJob", back_populates="shop" + ) + +class ShopProduct(Base): + __tablename__ = "shop_products" + + id = Column(Integer, primary_key=True, index=True) + shop_id = Column(Integer, ForeignKey("shops.id"), nullable=False) + product_id = Column(Integer, ForeignKey("products.id"), nullable=False) + + # Shop-specific overrides (can override the main product data) + shop_product_id = Column(String) # Shop's internal product ID + shop_price = Column(Float) # Override main product price + shop_sale_price = Column(Float) + shop_currency = Column(String) + shop_availability = Column(String) # Override availability + shop_condition = Column(String) + + # Shop-specific metadata + is_featured = Column(Boolean, default=False) + is_active = Column(Boolean, default=True) + display_order = Column(Integer, default=0) + + # Inventory management + min_quantity = Column(Integer, default=1) + max_quantity = Column(Integer) + + # Timestamps + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # Relationships + shop = relationship("Shop", back_populates="shop_products") + product = relationship("Product", back_populates="shop_products") + + # Constraints + __table_args__ = ( + UniqueConstraint("shop_id", "product_id", name="uq_shop_product"), + Index("idx_shop_product_active", "shop_id", "is_active"), + Index("idx_shop_product_featured", "shop_id", "is_featured"), + ) + diff --git a/models/database/stock.py b/models/database/stock.py new file mode 100644 index 00000000..ef66010d --- /dev/null +++ b/models/database/stock.py @@ -0,0 +1,40 @@ +from datetime import datetime + +from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index, + Integer, String, Text, UniqueConstraint) +from sqlalchemy.orm import relationship + +# Import Base from the central database module instead of creating a new one +from app.core.database import Base + + +class Stock(Base): + __tablename__ = "stock" + + id = Column(Integer, primary_key=True, index=True) + gtin = Column( + String, index=True, nullable=False + ) # Foreign key relationship would be ideal + location = Column(String, nullable=False, index=True) + quantity = Column(Integer, nullable=False, default=0) + reserved_quantity = Column(Integer, default=0) # For orders being processed + shop_id = Column(Integer, ForeignKey("shops.id")) # Optional: shop-specific stock + + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column( + DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False + ) + + # Relationships + shop = relationship("Shop") + + # Composite unique constraint to prevent duplicate GTIN-location combinations + __table_args__ = ( + UniqueConstraint("gtin", "location", name="uq_stock_gtin_location"), + Index( + "idx_stock_gtin_location", "gtin", "location" + ), # Composite index for efficient queries + ) + + def __repr__(self): + return f"" diff --git a/models/database/user.py b/models/database/user.py new file mode 100644 index 00000000..bb1ddf62 --- /dev/null +++ b/models/database/user.py @@ -0,0 +1,33 @@ +from datetime import datetime + +from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index, + Integer, String, Text, UniqueConstraint) +from sqlalchemy.orm import relationship + +# Import Base from the central database module instead of creating a new one +from app.core.database import Base + + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + email = Column(String, unique=True, index=True, nullable=False) + username = Column(String, unique=True, index=True, nullable=False) + hashed_password = Column(String, nullable=False) + role = Column(String, nullable=False, default="user") # user, admin, shop_owner + is_active = Column(Boolean, default=True, nullable=False) + last_login = Column(DateTime, nullable=True) + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column( + DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False + ) + + # Relationships + marketplace_import_jobs = relationship( + "MarketplaceImportJob", back_populates="user" + ) + owned_shops = relationship("Shop", back_populates="owner") + + def __repr__(self): + return f"" diff --git a/models_restructure.md b/models_restructure.md index 7ce5d2ff..bb0d832e 100644 --- a/models_restructure.md +++ b/models_restructure.md @@ -152,7 +152,7 @@ class StatusResponse(BaseModel): ```python # In route files -from models.database import User, Product, Stock +from models.database.user import User, Product, Stock from models.api.auth import UserLogin, UserResponse from models.api.product import ProductCreate, ProductListResponse diff --git a/models_restructure_script.ps1 b/models_restructure_script.ps1 index 2975e1db..a5431914 100644 --- a/models_restructure_script.ps1 +++ b/models_restructure_script.ps1 @@ -1,6 +1,6 @@ -# restructure_models.ps1 - PowerShell script to restructure models from single files to domain-organized structure +# restructure_models.ps1 - Final working script using temp files -Write-Host "๐Ÿ”„ Starting models restructure..." -ForegroundColor Cyan +Write-Host "๐Ÿ“„ Starting models restructure..." -ForegroundColor Cyan # Create new directory structure for models Write-Host "๐Ÿ“ Creating models directory structure..." -ForegroundColor Yellow @@ -10,13 +10,19 @@ $modelDirectories = @( ) foreach ($dir in $modelDirectories) { - New-Item -Path $dir -ItemType Directory -Force | Out-Null - Write-Host " Created: $dir" -ForegroundColor Gray + if (!(Test-Path $dir)) { + New-Item -Path $dir -ItemType Directory -Force | Out-Null + Write-Host " Created: $dir" -ForegroundColor Green + } else { + Write-Host " Exists: $dir" -ForegroundColor Gray + } } # Backup original model files Write-Host "๐Ÿ’พ Backing up original model files..." -ForegroundColor Yellow -New-Item -Path "models\backup" -ItemType Directory -Force | Out-Null +if (!(Test-Path "models\backup")) { + New-Item -Path "models\backup" -ItemType Directory -Force | Out-Null +} $originalFiles = @("models\database_models.py", "models\api_models.py") foreach ($file in $originalFiles) { @@ -26,11 +32,23 @@ foreach ($file in $originalFiles) { } } -# Create database models files Write-Host "๐Ÿ“„ Creating database model files..." -ForegroundColor Yellow -# models/database/__init__.py -$databaseInit = @" +# Function to create files using temp file approach +function New-PythonFile { + param( + [string]$Path, + [string]$TempContent + ) + + $tempFile = [System.IO.Path]::GetTempFileName() + $TempContent | Out-File $tempFile -Encoding UTF8 + Move-Item $tempFile $Path -Force +} + +# Create models/database/__init__.py +Write-Host " Creating models\database\__init__.py..." -ForegroundColor Gray +$initContent = @" # models/database/__init__.py from .user import User from .product import Product @@ -40,437 +58,104 @@ from .marketplace import MarketplaceImportJob __all__ = [ "User", - "Product", + "Product", "Shop", "ShopProduct", "Stock", "MarketplaceImportJob", ] "@ +New-PythonFile -Path "models\database\__init__.py" -TempContent $initContent -$databaseInit | Out-File -FilePath "models\database\__init__.py" -Encoding UTF8 +# Create models/database/base.py +Write-Host " Creating models\database\base.py..." -ForegroundColor Gray +$baseContent = 'LS0gLS0tQkVHSU4gUFlUSE9OIENPREUtLS0tLQojIG1vZGVscy9kYXRhYmFzZS9iYXNlLnB5CmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lCmZyb20gc3FsYWxjaGVteSBpbXBvcnQgQ29sdW1uLCBEYXRlVGltZQpmcm9tIGFwcC5jb3JlLmRhdGFiYXNlIGltcG9ydCBCYXNlCgoKY2xhc3MgVGltZXN0YW1wTWl4aW46CiAgICAiIiJNaXhpbiB0byBhZGQgY3JlYXRlZF9hdCBhbmQgdXBkYXRlZF9hdCB0aW1lc3RhbXBzIHRvIG1vZGVscyIiIgogICAgY3JlYXRlZF9hdCA9IENvbHVtbihEYXRlVGltZSwgZGVmYXVsdD1kYXRldGltZS51dGNub3csIG51bGxhYmxlPUZhbHNlKQogICAgdXBkYXRlZF9hdCA9IENvbHVtbigKICAgICAgICBEYXRlVGltZSwgZGVmYXVsdD1kYXRldGltZS51dGNub3csIG9udXBkYXRlPWRhdGV0aW1lLnV0Y25vdywgbnVsbGFibGU9RmFsc2UKICAgICkKLS0tLS1FTkQgUFlUSE9OIENPREUtLS0tLQo=' +$decodedBase = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($baseContent)) +$decodedBase = $decodedBase.Replace('-----BEGIN PYTHON CODE-----', '').Replace('-----END PYTHON CODE-----', '').Trim() +$decodedBase | Out-File "models\database\base.py" -Encoding UTF8 -# models/database/base.py -$databaseBase = @" -# models/database/base.py -from datetime import datetime -from sqlalchemy import Column, DateTime -from app.core.database import Base - - -class TimestampMixin: - """Mixin to add created_at and updated_at timestamps to models""" - created_at = Column(DateTime, default=datetime.utcnow, nullable=False) - updated_at = Column( - DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False - ) -"@ - -$databaseBase | Out-File -FilePath "models\database\base.py" -Encoding UTF8 - -# models/database/user.py -$userModel = @" -# models/database/user.py -from datetime import datetime -from sqlalchemy import Boolean, Column, DateTime, Integer, String -from sqlalchemy.orm import relationship -from app.core.database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True, index=True) - email = Column(String, unique=True, index=True, nullable=False) - username = Column(String, unique=True, index=True, nullable=False) - hashed_password = Column(String, nullable=False) - role = Column(String, nullable=False, default="user") # user, admin, shop_owner - is_active = Column(Boolean, default=True, nullable=False) - last_login = Column(DateTime, nullable=True) - created_at = Column(DateTime, default=datetime.utcnow, nullable=False) - updated_at = Column( - DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False - ) - - # Relationships - marketplace_import_jobs = relationship( - "MarketplaceImportJob", back_populates="user" - ) - owned_shops = relationship("Shop", back_populates="owner") - - def __repr__(self): - return f"" -"@ - -$userModel | Out-File -FilePath "models\database\user.py" -Encoding UTF8 - -# models/database/product.py -$productModel = @" -# models/database/product.py -from datetime import datetime -from sqlalchemy import Column, DateTime, Index, Integer, String -from sqlalchemy.orm import relationship -from app.core.database import Base - - -class Product(Base): - __tablename__ = "products" - - id = Column(Integer, primary_key=True, index=True) - product_id = Column(String, unique=True, index=True, nullable=False) - title = Column(String, nullable=False) - description = Column(String) - link = Column(String) - image_link = Column(String) - availability = Column(String, index=True) # Index for filtering - price = Column(String) - brand = Column(String, index=True) # Index for filtering - gtin = Column(String, index=True) # Index for stock lookups - mpn = Column(String) - condition = Column(String) - adult = Column(String) - multipack = Column(Integer) - is_bundle = Column(String) - age_group = Column(String) - color = Column(String) - gender = Column(String) - material = Column(String) - pattern = Column(String) - size = Column(String) - size_type = Column(String) - size_system = Column(String) - item_group_id = Column(String) - google_product_category = Column(String, index=True) # Index for filtering - product_type = Column(String) - custom_label_0 = Column(String) - custom_label_1 = Column(String) - custom_label_2 = Column(String) - custom_label_3 = Column(String) - custom_label_4 = Column(String) - additional_image_link = Column(String) - sale_price = Column(String) - unit_pricing_measure = Column(String) - unit_pricing_base_measure = Column(String) - identifier_exists = Column(String) - shipping = Column(String) - currency = Column(String) - - # New marketplace fields - marketplace = Column( - String, index=True, nullable=True, default="Letzshop" - ) # Index for marketplace filtering - shop_name = Column(String, index=True, nullable=True) # Index for shop filtering - - created_at = Column(DateTime, default=datetime.utcnow, nullable=False) - updated_at = Column( - DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False - ) - - # Relationship to stock (one-to-many via GTIN) - stock_entries = relationship( - "Stock", - foreign_keys="Stock.gtin", - primaryjoin="Product.gtin == Stock.gtin", - viewonly=True, - ) - shop_products = relationship("ShopProduct", back_populates="product") - - # Additional indexes for marketplace queries - __table_args__ = ( - Index( - "idx_marketplace_shop", "marketplace", "shop_name" - ), # Composite index for marketplace+shop queries - Index( - "idx_marketplace_brand", "marketplace", "brand" - ), # Composite index for marketplace+brand queries - ) - - def __repr__(self): - return ( - f"" - ) -"@ - -$productModel | Out-File -FilePath "models\database\product.py" -Encoding UTF8 - -# models/database/shop.py -$shopModel = @" -# models/database/shop.py -from datetime import datetime -from sqlalchemy import Boolean, Column, DateTime, Float, ForeignKey, Index, Integer, String, Text, UniqueConstraint -from sqlalchemy.orm import relationship -from app.core.database import Base - - -class Shop(Base): - __tablename__ = "shops" - - id = Column(Integer, primary_key=True, index=True) - shop_code = Column( - String, unique=True, index=True, nullable=False - ) # e.g., "TECHSTORE", "FASHIONHUB" - shop_name = Column(String, nullable=False) # Display name - description = Column(Text) - owner_id = Column(Integer, ForeignKey("users.id"), nullable=False) - - # Contact information - contact_email = Column(String) - contact_phone = Column(String) - website = Column(String) - - # Business information - business_address = Column(Text) - tax_number = Column(String) - - # Status - is_active = Column(Boolean, default=True) - is_verified = Column(Boolean, default=False) - - # Timestamps - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - # Relationships - owner = relationship("User", back_populates="owned_shops") - shop_products = relationship("ShopProduct", back_populates="shop") - marketplace_import_jobs = relationship( - "MarketplaceImportJob", back_populates="shop" - ) - - -class ShopProduct(Base): - __tablename__ = "shop_products" - - id = Column(Integer, primary_key=True, index=True) - shop_id = Column(Integer, ForeignKey("shops.id"), nullable=False) - product_id = Column(Integer, ForeignKey("products.id"), nullable=False) - - # Shop-specific overrides (can override the main product data) - shop_product_id = Column(String) # Shop's internal product ID - shop_price = Column(Float) # Override main product price - shop_sale_price = Column(Float) - shop_currency = Column(String) - shop_availability = Column(String) # Override availability - shop_condition = Column(String) - - # Shop-specific metadata - is_featured = Column(Boolean, default=False) - is_active = Column(Boolean, default=True) - display_order = Column(Integer, default=0) - - # Inventory management - min_quantity = Column(Integer, default=1) - max_quantity = Column(Integer) - - # Timestamps - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - # Relationships - shop = relationship("Shop", back_populates="shop_products") - product = relationship("Product", back_populates="shop_products") - - # Constraints - __table_args__ = ( - UniqueConstraint("shop_id", "product_id", name="uq_shop_product"), - Index("idx_shop_product_active", "shop_id", "is_active"), - Index("idx_shop_product_featured", "shop_id", "is_featured"), - ) -"@ - -$shopModel | Out-File -FilePath "models\database\shop.py" -Encoding UTF8 - -# models/database/stock.py -$stockModel = @" -# models/database/stock.py -from datetime import datetime -from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, UniqueConstraint -from sqlalchemy.orm import relationship -from app.core.database import Base - - -class Stock(Base): - __tablename__ = "stock" - - id = Column(Integer, primary_key=True, index=True) - gtin = Column( - String, index=True, nullable=False - ) # Foreign key relationship would be ideal - location = Column(String, nullable=False, index=True) - quantity = Column(Integer, nullable=False, default=0) - reserved_quantity = Column(Integer, default=0) # For orders being processed - shop_id = Column(Integer, ForeignKey("shops.id")) # Optional: shop-specific stock - - created_at = Column(DateTime, default=datetime.utcnow, nullable=False) - updated_at = Column( - DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False - ) - - # Relationships - shop = relationship("Shop") - - # Composite unique constraint to prevent duplicate GTIN-location combinations - __table_args__ = ( - UniqueConstraint("gtin", "location", name="uq_stock_gtin_location"), - Index( - "idx_stock_gtin_location", "gtin", "location" - ), # Composite index for efficient queries - ) - - def __repr__(self): - return f"" -"@ - -$stockModel | Out-File -FilePath "models\database\stock.py" -Encoding UTF8 - -# models/database/marketplace.py -$marketplaceModel = @" -# models/database/marketplace.py -from datetime import datetime -from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String -from sqlalchemy.orm import relationship -from app.core.database import Base - - -class MarketplaceImportJob(Base): - __tablename__ = "marketplace_import_jobs" - - id = Column(Integer, primary_key=True, index=True) - status = Column( - String, nullable=False, default="pending" - ) # pending, processing, completed, failed, completed_with_errors - source_url = Column(String, nullable=False) - marketplace = Column( - String, nullable=False, index=True, default="Letzshop" - ) # Index for marketplace filtering - shop_name = Column(String, nullable=False, index=True) # Index for shop filtering - shop_id = Column( - Integer, ForeignKey("shops.id"), nullable=False - ) # Add proper foreign key - user_id = Column( - Integer, ForeignKey("users.id"), nullable=False - ) # Foreign key to users table - - # Results - imported_count = Column(Integer, default=0) - updated_count = Column(Integer, default=0) - error_count = Column(Integer, default=0) - total_processed = Column(Integer, default=0) - - # Error handling - error_message = Column(String) - - # Timestamps - created_at = Column(DateTime, default=datetime.utcnow, nullable=False) - started_at = Column(DateTime) - completed_at = Column(DateTime) - - # Relationship to user - user = relationship("User", foreign_keys=[user_id]) - shop = relationship("Shop", back_populates="marketplace_import_jobs") - - # Additional indexes for marketplace import job queries - __table_args__ = ( - Index( - "idx_marketplace_import_user_marketplace", "user_id", "marketplace" - ), # User's marketplace imports - Index("idx_marketplace_import_shop_status", "status"), # Shop import status - Index("idx_marketplace_import_shop_id", "shop_id"), - ) - - def __repr__(self): - return ( - f"" - ) -"@ - -$marketplaceModel | Out-File -FilePath "models\database\marketplace.py" -Encoding UTF8 - -# Create API models files -Write-Host "๐Ÿ“„ Creating API model files..." -ForegroundColor Yellow +# Create models/database/user.py +Write-Host " Creating models\database\user.py..." -ForegroundColor Gray +$userContent = 'LS0tLS1CRUdJTiBQWVRIT04gQ09ERS0tLS0tCiMgbW9kZWxzL2RhdGFiYXNlL3VzZXIucHkKZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSBzcWxhbGNoZW15IGltcG9ydCBCb29sZWFuLCBDb2x1bW4sIERhdGVUaW1lLCBJbnRlZ2VyLCBTdHJpbmcKZnJvbSBzcWxhbGNoZW15Lm9ybSBpbXBvcnQgcmVsYXRpb25zaGlwCmZyb20gYXBwLmNvcmUuZGF0YWJhc2UgaW1wb3J0IEJhc2UKCgpjbGFzcyBVc2VyKEJhc2UpOgogICAgX190YWJsZW5hbWVfXyA9ICJ1c2VycyIKCiAgICBpZCA9IENvbHVtbihJbnRlZ2VyLCBwcmltYXJ5X2tleT1UcnVlLCBpbmRleD1UcnVlKQogICAgZW1haWwgPSBDb2x1bW4oU3RyaW5nLCB1bmlxdWU9VHJ1ZSwgaW5kZXg9VHJ1ZSwgbnVsbGFibGU9RmFsc2UpCiAgICB1c2VybmFtZSA9IENvbHVtbihTdHJpbmcsIHVuaXF1ZT1UcnVlLCBpbmRleD1UcnVlLCBudWxsYWJsZT1GYWxzZSkKICAgIGhhc2hlZF9wYXNzd29yZCA9IENvbHVtbihTdHJpbmcsIG51bGxhYmxlPUZhbHNlKQogICAgcm9sZSA9IENvbHVtbihTdHJpbmcsIG51bGxhYmxlPUZhbHNlLCBkZWZhdWx0PSJ1c2VyIikgICMgdXNlciwgYWRtaW4sIHNob3Bfb3duZXIKICAgIGlzX2FjdGl2ZSA9IENvbHVtbihCb29sZWFuLCBkZWZhdWx0PVRydWUsIG51bGxhYmxlPUZhbHNlKQogICAgbGFzdF9sb2dpbiA9IENvbHVtbihEYXRlVGltZSwgbnVsbGFibGU9VHJ1ZSkKICAgIGNyZWF0ZWRfYXQgPSBDb2x1bW4oRGF0ZVRpbWUsIGRlZmF1bHQ9ZGF0ZXRpbWUudXRjbm93LCBudWxsYWJsZT1GYWxzZSkKICAgIHVwZGF0ZWRfYXQgPSBDb2x1bW4oCiAgICAgICAgRGF0ZVRpbWUsIGRlZmF1bHQ9ZGF0ZXRpbWUudXRjbm93LCBvbnVwZGF0ZT1kYXRldGltZS51dGNub3csIG51bGxhYmxlPUZhbHNlCiAgICApCgogICAgIyBSZWxhdGlvbnNoaXBzCiAgICBtYXJrZXRwbGFjZV9pbXBvcnRfam9icyA9IHJlbGF0aW9uc2hpcCgKICAgICAgICAiTWFya2V0cGxhY2VJbXBvcnRKb2IiLCBiYWNrX3BvcHVsYXRlcz0idXNlciIKICAgICkKICAgIG93bmVkX3Nob3BzID0gcmVsYXRpb25zaGlwKCJTaG9wIiwgYmFja19wb3B1bGF0ZXM9Im93bmVyIikKCiAgICBkZWYgX19yZXByX18oc2VsZik6CiAgICAgICAgcmV0dXJuIGYiPFVzZXIodXNlcm5hbWU9J3tzZWxmLnVzZXJuYW1lfScsIGVtYWlsPSd7c2VsZi5lbWFpbH0nLCByb2xlPSd7c2VsZi5yb2xlfScpPiIKLS0tLS1FTkQgUFlUSE9OIENPREUtLS0tLQo=' +$decodedUser = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($userContent)) +$decodedUser = $decodedUser.Replace('-----BEGIN PYTHON CODE-----', '').Replace('-----END PYTHON CODE-----', '').Trim() +$decodedUser | Out-File "models\database\user.py" -Encoding UTF8 +# Create models/api/__init__.py +Write-Host " Creating models\api\__init__.py..." -ForegroundColor Gray +$apiInitContent = @" # models/api/__init__.py -$apiInit = @" -# models/api/__init__.py -from .auth import UserRegister, UserLogin, UserResponse, LoginResponse -from .product import ProductBase, ProductCreate, ProductUpdate, ProductResponse, ProductListResponse, ProductDetailResponse -from .shop import ShopCreate, ShopUpdate, ShopResponse, ShopListResponse, ShopProductCreate, ShopProductResponse -from .stock import StockBase, StockCreate, StockAdd, StockUpdate, StockResponse, StockLocationResponse, StockSummaryResponse -from .marketplace import MarketplaceImportRequest, MarketplaceImportJobResponse -from .stats import StatsResponse, MarketplaceStatsResponse +# Import statements will be added as you create the individual API model files +# Example: +# from .auth import UserRegister, UserLogin, UserResponse, LoginResponse __all__ = [ - # Auth models - "UserRegister", "UserLogin", "UserResponse", "LoginResponse", - # Product models - "ProductBase", "ProductCreate", "ProductUpdate", "ProductResponse", - "ProductListResponse", "ProductDetailResponse", - # Shop models - "ShopCreate", "ShopUpdate", "ShopResponse", "ShopListResponse", - "ShopProductCreate", "ShopProductResponse", - # Stock models - "StockBase", "StockCreate", "StockAdd", "StockUpdate", "StockResponse", - "StockLocationResponse", "StockSummaryResponse", - # Marketplace models - "MarketplaceImportRequest", "MarketplaceImportJobResponse", - # Stats models - "StatsResponse", "MarketplaceStatsResponse", + # Add your API model imports here as you create them ] "@ +New-PythonFile -Path "models\api\__init__.py" -TempContent $apiInitContent -$apiInit | Out-File -FilePath "models\api\__init__.py" -Encoding UTF8 +# Create models/api/base.py +Write-Host " Creating models\api\base.py..." -ForegroundColor Gray +$apiBaseContent = 'LS0tLS1CRUdJTiBQWVRIT04gQ09ERS0tLS0tCiMgbW9kZWxzL2FwaS9iYXNlLnB5CmZyb20gdHlwaW5nIGltcG9ydCBMaXN0LCBUeXBlVmFyLCBHZW5lcmljCmZyb20gcHlkYW50aWMgaW1wb3J0IEJhc2VNb2RlbAoKVCA9IFR5cGVWYXIoJ1QnKQoKY2xhc3MgTGlzdFJlc3BvbnNlKEJhc2VNb2RlbCwgR2VuZXJpY1tUXSk6CiAgICAiIiJHZW5lcmljIGxpc3QgcmVzcG9uc2UgbW9kZWwiIiIKICAgIGl0ZW1zOiBMaXN0W1RdCiAgICB0b3RhbDogaW50CiAgICBza2lwOiBpbnQKICAgIGxpbWl0OiBpbnQKCmNsYXNzIFN0YXR1c1Jlc3BvbnNlKEJhc2VNb2RlbCk6CiAgICAiIiJHZW5lcmljIHN0YXR1cyByZXNwb25zZSIiIgogICAgc3VjY2VzczogYm9vbAogICAgbWVzc2FnZTogc3RyCi0tLS0tRU5EIFBZVEhPTiBDT0RFLS0tLS0K' +$decodedApi = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($apiBaseContent)) +$decodedApi = $decodedApi.Replace('-----BEGIN PYTHON CODE-----', '').Replace('-----END PYTHON CODE-----', '').Trim() +$decodedApi | Out-File "models\api\base.py" -Encoding UTF8 -# models/api/base.py -$apiBase = @" -# models/api/base.py -from typing import List, TypeVar, Generic -from pydantic import BaseModel +Write-Host "โœ… Basic model structure created!" -ForegroundColor Green +Write-Host "" +Write-Host "๐Ÿ” Testing file creation..." -ForegroundColor Yellow -T = TypeVar('T') +# Check if files were created +$createdFiles = @( + "models\database\__init__.py", + "models\database\base.py", + "models\database\user.py", + "models\api\__init__.py", + "models\api\base.py" +) -class ListResponse(BaseModel, Generic[T]): - """Generic list response model""" - items: List[T] - total: int - skip: int - limit: int +foreach ($file in $createdFiles) { + if (Test-Path $file) { + $size = (Get-Item $file).Length + Write-Host "โœ… $file ($size bytes)" -ForegroundColor Green + } else { + Write-Host "โŒ $file (missing)" -ForegroundColor Red + } +} -class StatusResponse(BaseModel): - """Generic status response""" - success: bool - message: str -"@ +Write-Host "" +Write-Host "๐Ÿงช Testing Python imports..." -ForegroundColor Yellow +try { + $pythonTest = 'import sys; sys.path.append("."); from models.database.user import User; print("SUCCESS: Database models import working")' + $testResult = python -c $pythonTest 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host "โœ… Python import test passed!" -ForegroundColor Green + } else { + Write-Host "โš ๏ธ Python import test failed (may need dependencies)" -ForegroundColor Yellow + } +} catch { + Write-Host "โš ๏ธ Python not available for testing" -ForegroundColor Yellow +} -$apiBase | Out-File -FilePath "models\api\base.py" -Encoding UTF8 - -Write-Host "โœ… Models directory structure created!" -ForegroundColor Green Write-Host "" Write-Host "๐Ÿ“‹ Next steps:" -ForegroundColor Cyan -Write-Host "1. You'll need to manually extract the remaining API model classes from api_models.py:" -ForegroundColor White -Write-Host " - Auth models โ†’ models\api\auth.py" -ForegroundColor Gray -Write-Host " - Product models โ†’ models\api\product.py" -ForegroundColor Gray -Write-Host " - Shop models โ†’ models\api\shop.py" -ForegroundColor Gray -Write-Host " - Stock models โ†’ models\api\stock.py" -ForegroundColor Gray -Write-Host " - Marketplace models โ†’ models\api\marketplace.py" -ForegroundColor Gray -Write-Host " - Stats models โ†’ models\api\stats.py" -ForegroundColor Gray +Write-Host "1. Complete the remaining database models by copying from your original files:" -ForegroundColor White +Write-Host " - models\database\product.py" -ForegroundColor Gray +Write-Host " - models\database\shop.py" -ForegroundColor Gray +Write-Host " - models\database\stock.py" -ForegroundColor Gray +Write-Host " - models\database\marketplace.py" -ForegroundColor Gray Write-Host "" -Write-Host "2. Update imports throughout your application:" -ForegroundColor White -Write-Host " Old: from models.database_models import User, Product" -ForegroundColor Gray -Write-Host " New: from models.database import User, Product" -ForegroundColor Gray +Write-Host "2. Extract API model classes from your api_models.py to separate files:" -ForegroundColor White +Write-Host " - models\api\auth.py" -ForegroundColor Gray +Write-Host " - models\api\product.py" -ForegroundColor Gray +Write-Host " - models\api\shop.py" -ForegroundColor Gray +Write-Host " - models\api\stock.py" -ForegroundColor Gray +Write-Host " - models\api\marketplace.py" -ForegroundColor Gray +Write-Host " - models\api\stats.py" -ForegroundColor Gray Write-Host "" -Write-Host " Old: from models.api_models import UserCreate, ProductResponse" -ForegroundColor Gray -Write-Host " New: from models.api import UserRegister, ProductResponse" -ForegroundColor Gray +Write-Host "3. Update imports throughout your application:" -ForegroundColor White +Write-Host " Old: from models.database.user import User" -ForegroundColor Gray +Write-Host " New: from models.database.user import User" -ForegroundColor Gray Write-Host "" -Write-Host "3. Test imports after restructure:" -ForegroundColor White -Write-Host " python -c \"from models.database import User, Product, Shop\"" -ForegroundColor Yellow -Write-Host " python -c \"from models.api import UserRegister, ProductResponse\"" -ForegroundColor Yellow -Write-Host "" -Write-Host "4. Files to update (search and replace imports):" -ForegroundColor White -Write-Host " - All route files in api/v1/" -ForegroundColor Gray -Write-Host " - Service files in app/services/" -ForegroundColor Gray -Write-Host " - Test files" -ForegroundColor Gray -Write-Host " - Any utility files" -ForegroundColor Gray -Write-Host "" -Write-Host "โœจ Database models restructure completed!" -ForegroundColor Green -Write-Host "๐Ÿ“š Original files backed up in models\backup\" -ForegroundColor Green \ No newline at end of file +Write-Host "โœจ Foundation completed successfully!" -ForegroundColor Green +Write-Host "๐Ÿ“š Original files backed up in models\backup\" -ForegroundColor Green diff --git a/restructure-model.md b/restructure-model.md index 38644f19..1c428106 100644 --- a/restructure-model.md +++ b/restructure-model.md @@ -148,11 +148,11 @@ After running the script, update imports throughout your codebase: ```python # Old imports -from models.database_models import User, Product, Shop -from models.api_models import UserRegister, ProductResponse +from models.database.user import User, Product, Shop +from models.api import UserRegister, ProductResponse # New imports -from models.database import User, Product, Shop +from models.database.user import User, Product, Shop from models.api import UserRegister, ProductResponse ``` diff --git a/scripts/setup_dev.py b/scripts/setup_dev.py index a7be6017..e913e234 100644 --- a/scripts/setup_dev.py +++ b/scripts/setup_dev.py @@ -45,7 +45,7 @@ import sys # Add your project directory to the Python path sys.path.append(os.path.dirname(os.path.dirname(__file__))) -from models.database_models import Base +from models.database import Base from config.settings import settings # Alembic Config object diff --git a/tests/conftest.py b/tests/conftest.py index 91c1242d..4c2dd1f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,8 +8,11 @@ from sqlalchemy.pool import StaticPool from app.core.database import Base, get_db from main import app # Import all models to ensure they're registered with Base metadata -from models.database_models import (MarketplaceImportJob, Product, Shop, - ShopProduct, Stock, User) +from models.database.marketplace import MarketplaceImportJob +from models.database.product import Product +from models.database.shop import Shop,ShopProduct +from models.database.stock import Stock +from models.database.user import User # Use in-memory SQLite database for tests SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///:memory:" diff --git a/tests/fixtures/auth_fixtures.py b/tests/fixtures/auth_fixtures.py index 6a4dfe57..afc339c3 100644 --- a/tests/fixtures/auth_fixtures.py +++ b/tests/fixtures/auth_fixtures.py @@ -4,7 +4,7 @@ import uuid import pytest from middleware.auth import AuthManager -from models.database_models import User +from models.database.user import User @pytest.fixture(scope="session") diff --git a/tests/fixtures/marketplace_fixtures.py b/tests/fixtures/marketplace_fixtures.py index 9aa40720..ffe256be 100644 --- a/tests/fixtures/marketplace_fixtures.py +++ b/tests/fixtures/marketplace_fixtures.py @@ -1,7 +1,7 @@ # tests/fixtures/marketplace_fixtures.py import pytest -from models.database_models import MarketplaceImportJob +from models.database.marketplace import MarketplaceImportJob @pytest.fixture diff --git a/tests/fixtures/product_fixtures.py b/tests/fixtures/product_fixtures.py index a9bdb2d4..3efb8600 100644 --- a/tests/fixtures/product_fixtures.py +++ b/tests/fixtures/product_fixtures.py @@ -3,7 +3,7 @@ import uuid import pytest -from models.database_models import Product +from models.database.product import Product @pytest.fixture diff --git a/tests/fixtures/shop_fixtures.py b/tests/fixtures/shop_fixtures.py index 2c06f863..1f69596a 100644 --- a/tests/fixtures/shop_fixtures.py +++ b/tests/fixtures/shop_fixtures.py @@ -3,7 +3,8 @@ import uuid import pytest -from models.database_models import Shop, ShopProduct, Stock +from models.database.shop import Shop,ShopProduct +from models.database.stock import Stock @pytest.fixture diff --git a/tests/integration/api/v1/test_pagination.py b/tests/integration/api/v1/test_pagination.py index a0cc4207..8e6eb083 100644 --- a/tests/integration/api/v1/test_pagination.py +++ b/tests/integration/api/v1/test_pagination.py @@ -1,7 +1,8 @@ # tests/integration/api/v1/test_pagination.py import pytest -from models.database_models import Product, Shop +from models.database.product import Product +from models.database.shop import Shop @pytest.mark.integration diff --git a/tests/integration/api/v1/test_stock_endpoints.py b/tests/integration/api/v1/test_stock_endpoints.py index 5aa72de3..f355f66c 100644 --- a/tests/integration/api/v1/test_stock_endpoints.py +++ b/tests/integration/api/v1/test_stock_endpoints.py @@ -1,7 +1,7 @@ # tests/test_stock_endpoints.py import pytest -from models.database_models import Stock +from models.database.stock import Stock class TestStockAPI: diff --git a/tests/performance/test_api_performance.py b/tests/performance/test_api_performance.py index 5b0aad95..a8ad027e 100644 --- a/tests/performance/test_api_performance.py +++ b/tests/performance/test_api_performance.py @@ -3,7 +3,7 @@ import time import pytest -from models.database_models import Product +from models.database.product import Product @pytest.mark.performance diff --git a/tests/unit/models/test_database_models.py b/tests/unit/models/test_database_models.py index 7a9ba273..d59b9572 100644 --- a/tests/unit/models/test_database_models.py +++ b/tests/unit/models/test_database_models.py @@ -1,7 +1,10 @@ # tests/unit/models/test_database_models.py import pytest -from models.database_models import Product, Shop, Stock, User +from models.database.product import Product +from models.database.shop import Shop +from models.database.stock import Stock +from models.database.user import User @pytest.mark.unit diff --git a/tests/unit/services/test_admin_service.py b/tests/unit/services/test_admin_service.py index e73462bf..ed650f5d 100644 --- a/tests/unit/services/test_admin_service.py +++ b/tests/unit/services/test_admin_service.py @@ -3,7 +3,8 @@ import pytest from fastapi import HTTPException from app.services.admin_service import AdminService -from models.database_models import MarketplaceImportJob, Shop +from models.database.marketplace import MarketplaceImportJob +from models.database.shop import Shop @pytest.mark.unit diff --git a/tests/unit/services/test_auth_service.py b/tests/unit/services/test_auth_service.py index 0421e17e..b9a80c6c 100644 --- a/tests/unit/services/test_auth_service.py +++ b/tests/unit/services/test_auth_service.py @@ -3,8 +3,8 @@ import pytest from fastapi import HTTPException from app.services.auth_service import AuthService -from models.api_models import UserLogin, UserRegister -from models.database_models import User +from models.api.auth import UserLogin, UserRegister +from models.database.user import User @pytest.mark.unit @pytest.mark.auth diff --git a/tests/unit/services/test_marketplace_service.py b/tests/unit/services/test_marketplace_service.py index 3391c7cd..e2ba0cde 100644 --- a/tests/unit/services/test_marketplace_service.py +++ b/tests/unit/services/test_marketplace_service.py @@ -5,8 +5,10 @@ from datetime import datetime import pytest from app.services.marketplace_service import MarketplaceService -from models.api_models import MarketplaceImportRequest -from models.database_models import MarketplaceImportJob, Shop, User +from models.api.marketplace import MarketplaceImportRequest +from models.database.marketplace import MarketplaceImportJob +from models.database.shop import Shop +from models.database.user import User @pytest.mark.unit @pytest.mark.marketplace diff --git a/tests/unit/services/test_product_service.py b/tests/unit/services/test_product_service.py index c4cc706f..ec974d6b 100644 --- a/tests/unit/services/test_product_service.py +++ b/tests/unit/services/test_product_service.py @@ -2,8 +2,8 @@ import pytest from app.services.product_service import ProductService -from models.api_models import ProductCreate -from models.database_models import Product +from models.api.product import ProductCreate +from models.database.product import Product @pytest.mark.unit @pytest.mark.products diff --git a/tests/unit/services/test_shop_service.py b/tests/unit/services/test_shop_service.py index b890199b..1eb7ee73 100644 --- a/tests/unit/services/test_shop_service.py +++ b/tests/unit/services/test_shop_service.py @@ -3,7 +3,7 @@ import pytest from fastapi import HTTPException from app.services.shop_service import ShopService -from models.api_models import ShopCreate, ShopProductCreate +from models.api.shop import ShopCreate, ShopProductCreate @pytest.mark.unit @pytest.mark.shops diff --git a/tests/unit/services/test_stats_service.py b/tests/unit/services/test_stats_service.py index b6ccead0..6a7eb2a6 100644 --- a/tests/unit/services/test_stats_service.py +++ b/tests/unit/services/test_stats_service.py @@ -2,7 +2,8 @@ import pytest from app.services.stats_service import StatsService -from models.database_models import Product, Stock +from models.database.product import Product +from models.database.stock import Stock @pytest.mark.unit @pytest.mark.stats diff --git a/tests/unit/services/test_stock_service.py b/tests/unit/services/test_stock_service.py index 7bf5c5e3..8199f682 100644 --- a/tests/unit/services/test_stock_service.py +++ b/tests/unit/services/test_stock_service.py @@ -4,8 +4,9 @@ import uuid import pytest from app.services.stock_service import StockService -from models.api_models import StockAdd, StockCreate, StockUpdate -from models.database_models import Product, Stock +from models.api.stock import StockAdd, StockCreate, StockUpdate +from models.database.product import Product +from models.database.stock import Stock @pytest.mark.unit @pytest.mark.stock diff --git a/tests/unit/utils/test_csv_processor.py b/tests/unit/utils/test_csv_processor.py index faf64c83..98ba2806 100644 --- a/tests/unit/utils/test_csv_processor.py +++ b/tests/unit/utils/test_csv_processor.py @@ -83,7 +83,7 @@ class TestCSVProcessor: mock_get.return_value = mock_response with pytest.raises(Exception): - self.processor._download_csv("http://example.com/nonexistent.csv") + self.processor.download_csv("http://example.com/nonexistent.csv") def test_parse_csv_content(self): """Test CSV content parsing""" diff --git a/tests_restructure.md b/tests_restructure.md index 27edbc50..ca9c557a 100644 --- a/tests_restructure.md +++ b/tests_restructure.md @@ -116,7 +116,7 @@ tests/ ```python # tests/fixtures/auth_fixtures.py import pytest -from models.database import User +from models.database.user import User from utils.auth import create_access_token @pytest.fixture diff --git a/updated_readme_marketplace.md b/updated_readme_marketplace.md index 89c91d52..84d90013 100644 --- a/updated_readme_marketplace.md +++ b/updated_readme_marketplace.md @@ -603,7 +603,7 @@ volumes: ```python # Migrate existing products to demo shop -from models.database_models import Product, Shop, ShopProduct +from models.database import Product, Shop, ShopProduct from sqlalchemy.orm import Session def migrate_to_shops(db: Session): diff --git a/utils/csv_processor.py b/utils/csv_processor.py index 3c249382..93b21ddb 100644 --- a/utils/csv_processor.py +++ b/utils/csv_processor.py @@ -17,7 +17,7 @@ import requests from sqlalchemy import literal from sqlalchemy.orm import Session -from models.database_models import Product +from models.database.product import Product logger = logging.getLogger(__name__)