style: apply black and isort formatting across entire codebase
- Standardize quote style (single to double quotes) - Reorder and group imports alphabetically - Fix line breaks and indentation for consistency - Apply PEP 8 formatting standards Also updated Makefile to exclude both venv and .venv from code quality checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
# models/schema/__init__.py
|
||||
"""API models package - Pydantic models for request/response validation."""
|
||||
|
||||
from . import auth
|
||||
# Import API model modules
|
||||
from . import base
|
||||
from . import marketplace_import_job
|
||||
from . import marketplace_product
|
||||
from . import stats
|
||||
from . import inventory
|
||||
from . import vendor
|
||||
from . import (auth, base, inventory, marketplace_import_job,
|
||||
marketplace_product, stats, vendor)
|
||||
# Common imports for convenience
|
||||
from .base import * # Base Pydantic models
|
||||
|
||||
|
||||
@@ -12,16 +12,18 @@ This module provides schemas for:
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
# ============================================================================
|
||||
# ADMIN AUDIT LOG SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class AdminAuditLogResponse(BaseModel):
|
||||
"""Response model for admin audit logs."""
|
||||
|
||||
id: int
|
||||
admin_user_id: int
|
||||
admin_username: Optional[str] = None
|
||||
@@ -39,6 +41,7 @@ class AdminAuditLogResponse(BaseModel):
|
||||
|
||||
class AdminAuditLogFilters(BaseModel):
|
||||
"""Filters for querying audit logs."""
|
||||
|
||||
admin_user_id: Optional[int] = None
|
||||
action: Optional[str] = None
|
||||
target_type: Optional[str] = None
|
||||
@@ -50,6 +53,7 @@ class AdminAuditLogFilters(BaseModel):
|
||||
|
||||
class AdminAuditLogListResponse(BaseModel):
|
||||
"""Paginated list of audit logs."""
|
||||
|
||||
logs: List[AdminAuditLogResponse]
|
||||
total: int
|
||||
skip: int
|
||||
@@ -60,8 +64,10 @@ class AdminAuditLogListResponse(BaseModel):
|
||||
# ADMIN NOTIFICATION SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class AdminNotificationCreate(BaseModel):
|
||||
"""Create admin notification."""
|
||||
|
||||
type: str = Field(..., max_length=50, description="Notification type")
|
||||
priority: str = Field(default="normal", description="Priority level")
|
||||
title: str = Field(..., max_length=200)
|
||||
@@ -70,10 +76,10 @@ class AdminNotificationCreate(BaseModel):
|
||||
action_url: Optional[str] = Field(None, max_length=500)
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
@field_validator('priority')
|
||||
@field_validator("priority")
|
||||
@classmethod
|
||||
def validate_priority(cls, v):
|
||||
allowed = ['low', 'normal', 'high', 'critical']
|
||||
allowed = ["low", "normal", "high", "critical"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Priority must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
@@ -81,6 +87,7 @@ class AdminNotificationCreate(BaseModel):
|
||||
|
||||
class AdminNotificationResponse(BaseModel):
|
||||
"""Admin notification response."""
|
||||
|
||||
id: int
|
||||
type: str
|
||||
priority: str
|
||||
@@ -99,11 +106,13 @@ class AdminNotificationResponse(BaseModel):
|
||||
|
||||
class AdminNotificationUpdate(BaseModel):
|
||||
"""Mark notification as read."""
|
||||
|
||||
is_read: bool = True
|
||||
|
||||
|
||||
class AdminNotificationListResponse(BaseModel):
|
||||
"""Paginated list of notifications."""
|
||||
|
||||
notifications: List[AdminNotificationResponse]
|
||||
total: int
|
||||
unread_count: int
|
||||
@@ -115,8 +124,10 @@ class AdminNotificationListResponse(BaseModel):
|
||||
# ADMIN SETTINGS SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class AdminSettingCreate(BaseModel):
|
||||
"""Create or update admin setting."""
|
||||
|
||||
key: str = Field(..., max_length=100, description="Unique setting key")
|
||||
value: str = Field(..., description="Setting value")
|
||||
value_type: str = Field(default="string", description="Data type")
|
||||
@@ -125,25 +136,28 @@ class AdminSettingCreate(BaseModel):
|
||||
is_encrypted: bool = Field(default=False)
|
||||
is_public: bool = Field(default=False, description="Can be exposed to frontend")
|
||||
|
||||
@field_validator('value_type')
|
||||
@field_validator("value_type")
|
||||
@classmethod
|
||||
def validate_value_type(cls, v):
|
||||
allowed = ['string', 'integer', 'boolean', 'json', 'float']
|
||||
allowed = ["string", "integer", "boolean", "json", "float"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Value type must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
@field_validator('key')
|
||||
@field_validator("key")
|
||||
@classmethod
|
||||
def validate_key_format(cls, v):
|
||||
# Setting keys should be lowercase with underscores
|
||||
if not v.replace('_', '').isalnum():
|
||||
raise ValueError("Setting key must contain only letters, numbers, and underscores")
|
||||
if not v.replace("_", "").isalnum():
|
||||
raise ValueError(
|
||||
"Setting key must contain only letters, numbers, and underscores"
|
||||
)
|
||||
return v.lower()
|
||||
|
||||
|
||||
class AdminSettingResponse(BaseModel):
|
||||
"""Admin setting response."""
|
||||
|
||||
id: int
|
||||
key: str
|
||||
value: str
|
||||
@@ -160,12 +174,14 @@ class AdminSettingResponse(BaseModel):
|
||||
|
||||
class AdminSettingUpdate(BaseModel):
|
||||
"""Update admin setting value."""
|
||||
|
||||
value: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class AdminSettingListResponse(BaseModel):
|
||||
"""List of settings by category."""
|
||||
|
||||
settings: List[AdminSettingResponse]
|
||||
total: int
|
||||
category: Optional[str] = None
|
||||
@@ -175,8 +191,10 @@ class AdminSettingListResponse(BaseModel):
|
||||
# PLATFORM ALERT SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class PlatformAlertCreate(BaseModel):
|
||||
"""Create platform alert."""
|
||||
|
||||
alert_type: str = Field(..., max_length=50)
|
||||
severity: str = Field(..., description="Alert severity")
|
||||
title: str = Field(..., max_length=200)
|
||||
@@ -185,18 +203,25 @@ class PlatformAlertCreate(BaseModel):
|
||||
affected_systems: Optional[List[str]] = None
|
||||
auto_generated: bool = Field(default=True)
|
||||
|
||||
@field_validator('severity')
|
||||
@field_validator("severity")
|
||||
@classmethod
|
||||
def validate_severity(cls, v):
|
||||
allowed = ['info', 'warning', 'error', 'critical']
|
||||
allowed = ["info", "warning", "error", "critical"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Severity must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
@field_validator('alert_type')
|
||||
@field_validator("alert_type")
|
||||
@classmethod
|
||||
def validate_alert_type(cls, v):
|
||||
allowed = ['security', 'performance', 'capacity', 'integration', 'database', 'system']
|
||||
allowed = [
|
||||
"security",
|
||||
"performance",
|
||||
"capacity",
|
||||
"integration",
|
||||
"database",
|
||||
"system",
|
||||
]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Alert type must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
@@ -204,6 +229,7 @@ class PlatformAlertCreate(BaseModel):
|
||||
|
||||
class PlatformAlertResponse(BaseModel):
|
||||
"""Platform alert response."""
|
||||
|
||||
id: int
|
||||
alert_type: str
|
||||
severity: str
|
||||
@@ -226,12 +252,14 @@ class PlatformAlertResponse(BaseModel):
|
||||
|
||||
class PlatformAlertResolve(BaseModel):
|
||||
"""Resolve platform alert."""
|
||||
|
||||
is_resolved: bool = True
|
||||
resolution_notes: Optional[str] = None
|
||||
|
||||
|
||||
class PlatformAlertListResponse(BaseModel):
|
||||
"""Paginated list of platform alerts."""
|
||||
|
||||
alerts: List[PlatformAlertResponse]
|
||||
total: int
|
||||
active_count: int
|
||||
@@ -244,17 +272,19 @@ class PlatformAlertListResponse(BaseModel):
|
||||
# BULK OPERATION SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class BulkVendorAction(BaseModel):
|
||||
"""Bulk actions on vendors."""
|
||||
|
||||
vendor_ids: List[int] = Field(..., min_length=1, max_length=100)
|
||||
action: str = Field(..., description="Action to perform")
|
||||
confirm: bool = Field(default=False, description="Required for destructive actions")
|
||||
reason: Optional[str] = Field(None, description="Reason for bulk action")
|
||||
|
||||
@field_validator('action')
|
||||
@field_validator("action")
|
||||
@classmethod
|
||||
def validate_action(cls, v):
|
||||
allowed = ['activate', 'deactivate', 'verify', 'unverify', 'delete']
|
||||
allowed = ["activate", "deactivate", "verify", "unverify", "delete"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Action must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
@@ -262,6 +292,7 @@ class BulkVendorAction(BaseModel):
|
||||
|
||||
class BulkVendorActionResponse(BaseModel):
|
||||
"""Response for bulk vendor actions."""
|
||||
|
||||
successful: List[int]
|
||||
failed: Dict[int, str] # vendor_id -> error_message
|
||||
total_processed: int
|
||||
@@ -271,15 +302,16 @@ class BulkVendorActionResponse(BaseModel):
|
||||
|
||||
class BulkUserAction(BaseModel):
|
||||
"""Bulk actions on users."""
|
||||
|
||||
user_ids: List[int] = Field(..., min_length=1, max_length=100)
|
||||
action: str = Field(..., description="Action to perform")
|
||||
confirm: bool = Field(default=False)
|
||||
reason: Optional[str] = None
|
||||
|
||||
@field_validator('action')
|
||||
@field_validator("action")
|
||||
@classmethod
|
||||
def validate_action(cls, v):
|
||||
allowed = ['activate', 'deactivate', 'delete']
|
||||
allowed = ["activate", "deactivate", "delete"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Action must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
@@ -287,6 +319,7 @@ class BulkUserAction(BaseModel):
|
||||
|
||||
class BulkUserActionResponse(BaseModel):
|
||||
"""Response for bulk user actions."""
|
||||
|
||||
successful: List[int]
|
||||
failed: Dict[int, str]
|
||||
total_processed: int
|
||||
@@ -298,8 +331,10 @@ class BulkUserActionResponse(BaseModel):
|
||||
# ADMIN DASHBOARD SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class AdminDashboardStats(BaseModel):
|
||||
"""Comprehensive admin dashboard statistics."""
|
||||
|
||||
platform: Dict[str, Any]
|
||||
users: Dict[str, Any]
|
||||
vendors: Dict[str, Any]
|
||||
@@ -317,8 +352,10 @@ class AdminDashboardStats(BaseModel):
|
||||
# SYSTEM HEALTH SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class ComponentHealthStatus(BaseModel):
|
||||
"""Health status for a system component."""
|
||||
|
||||
status: str # healthy, degraded, unhealthy
|
||||
response_time_ms: Optional[float] = None
|
||||
error_message: Optional[str] = None
|
||||
@@ -328,6 +365,7 @@ class ComponentHealthStatus(BaseModel):
|
||||
|
||||
class SystemHealthResponse(BaseModel):
|
||||
"""System health check response."""
|
||||
|
||||
overall_status: str # healthy, degraded, critical
|
||||
database: ComponentHealthStatus
|
||||
redis: ComponentHealthStatus
|
||||
@@ -342,8 +380,10 @@ class SystemHealthResponse(BaseModel):
|
||||
# ADMIN SESSION SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class AdminSessionResponse(BaseModel):
|
||||
"""Admin session information."""
|
||||
|
||||
id: int
|
||||
admin_user_id: int
|
||||
admin_username: Optional[str] = None
|
||||
@@ -360,6 +400,7 @@ class AdminSessionResponse(BaseModel):
|
||||
|
||||
class AdminSessionListResponse(BaseModel):
|
||||
"""List of admin sessions."""
|
||||
|
||||
sessions: List[AdminSessionResponse]
|
||||
total: int
|
||||
active_count: int
|
||||
|
||||
@@ -17,7 +17,9 @@ class UserRegister(BaseModel):
|
||||
@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")
|
||||
raise ValueError(
|
||||
"Username must contain only letters, numbers, or underscores"
|
||||
)
|
||||
return v.lower().strip()
|
||||
|
||||
@field_validator("password")
|
||||
@@ -31,7 +33,9 @@ class UserRegister(BaseModel):
|
||||
class UserLogin(BaseModel):
|
||||
email_or_username: str = Field(..., description="Username or email address")
|
||||
password: str = Field(..., description="Password")
|
||||
vendor_code: Optional[str] = Field(None, description="Optional vendor code for context")
|
||||
vendor_code: Optional[str] = Field(
|
||||
None, description="Optional vendor code for context"
|
||||
)
|
||||
|
||||
@field_validator("email_or_username")
|
||||
@classmethod
|
||||
|
||||
@@ -5,21 +5,24 @@ Pydantic schemas for shopping cart operations.
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
# ============================================================================
|
||||
# Request Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class AddToCartRequest(BaseModel):
|
||||
"""Request model for adding items to cart."""
|
||||
|
||||
product_id: int = Field(..., description="Product ID to add", gt=0)
|
||||
quantity: int = Field(1, ge=1, description="Quantity to add")
|
||||
|
||||
|
||||
class UpdateCartItemRequest(BaseModel):
|
||||
"""Request model for updating cart item quantity."""
|
||||
|
||||
quantity: int = Field(..., ge=1, description="New quantity (must be >= 1)")
|
||||
|
||||
|
||||
@@ -27,23 +30,30 @@ class UpdateCartItemRequest(BaseModel):
|
||||
# Response Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CartItemResponse(BaseModel):
|
||||
"""Response model for a single cart item."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
product_id: int = Field(..., description="Product ID")
|
||||
product_name: str = Field(..., description="Product name")
|
||||
quantity: int = Field(..., description="Quantity in cart")
|
||||
price: float = Field(..., description="Price per unit when added to cart")
|
||||
line_total: float = Field(..., description="Total price for this line (price * quantity)")
|
||||
line_total: float = Field(
|
||||
..., description="Total price for this line (price * quantity)"
|
||||
)
|
||||
image_url: Optional[str] = Field(None, description="Product image URL")
|
||||
|
||||
|
||||
class CartResponse(BaseModel):
|
||||
"""Response model for shopping cart."""
|
||||
|
||||
vendor_id: int = Field(..., description="Vendor ID")
|
||||
session_id: str = Field(..., description="Shopping session ID")
|
||||
items: List[CartItemResponse] = Field(default_factory=list, description="Cart items")
|
||||
items: List[CartItemResponse] = Field(
|
||||
default_factory=list, description="Cart items"
|
||||
)
|
||||
subtotal: float = Field(..., description="Subtotal of all items")
|
||||
total: float = Field(..., description="Total amount (currently same as subtotal)")
|
||||
item_count: int = Field(..., description="Total number of items in cart")
|
||||
@@ -63,18 +73,22 @@ class CartResponse(BaseModel):
|
||||
items=items,
|
||||
subtotal=cart_dict["subtotal"],
|
||||
total=cart_dict["total"],
|
||||
item_count=len(items)
|
||||
item_count=len(items),
|
||||
)
|
||||
|
||||
|
||||
class CartOperationResponse(BaseModel):
|
||||
"""Response model for cart operations (add, update, remove)."""
|
||||
|
||||
message: str = Field(..., description="Operation result message")
|
||||
product_id: int = Field(..., description="Product ID affected")
|
||||
quantity: Optional[int] = Field(None, description="New quantity (for add/update operations)")
|
||||
quantity: Optional[int] = Field(
|
||||
None, description="New quantity (for add/update operations)"
|
||||
)
|
||||
|
||||
|
||||
class ClearCartResponse(BaseModel):
|
||||
"""Response model for clearing cart."""
|
||||
|
||||
message: str = Field(..., description="Operation result message")
|
||||
items_removed: int = Field(..., description="Number of items removed from cart")
|
||||
|
||||
@@ -5,35 +5,34 @@ Pydantic schema for customer-related operations.
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
|
||||
# ============================================================================
|
||||
# Customer Registration & Authentication
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CustomerRegister(BaseModel):
|
||||
"""Schema for customer registration."""
|
||||
|
||||
email: EmailStr = Field(..., description="Customer email address")
|
||||
password: str = Field(
|
||||
...,
|
||||
min_length=8,
|
||||
description="Password (minimum 8 characters)"
|
||||
..., min_length=8, description="Password (minimum 8 characters)"
|
||||
)
|
||||
first_name: str = Field(..., min_length=1, max_length=100)
|
||||
last_name: str = Field(..., min_length=1, max_length=100)
|
||||
phone: Optional[str] = Field(None, max_length=50)
|
||||
marketing_consent: bool = Field(default=False)
|
||||
|
||||
@field_validator('email')
|
||||
@field_validator("email")
|
||||
@classmethod
|
||||
def email_lowercase(cls, v: str) -> str:
|
||||
"""Convert email to lowercase."""
|
||||
return v.lower()
|
||||
|
||||
@field_validator('password')
|
||||
@field_validator("password")
|
||||
@classmethod
|
||||
def password_strength(cls, v: str) -> str:
|
||||
"""Validate password strength."""
|
||||
@@ -55,7 +54,7 @@ class CustomerUpdate(BaseModel):
|
||||
phone: Optional[str] = Field(None, max_length=50)
|
||||
marketing_consent: Optional[bool] = None
|
||||
|
||||
@field_validator('email')
|
||||
@field_validator("email")
|
||||
@classmethod
|
||||
def email_lowercase(cls, v: Optional[str]) -> Optional[str]:
|
||||
"""Convert email to lowercase."""
|
||||
@@ -66,6 +65,7 @@ class CustomerUpdate(BaseModel):
|
||||
# Customer Response
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CustomerResponse(BaseModel):
|
||||
"""Schema for customer response (excludes password)."""
|
||||
|
||||
@@ -84,9 +84,7 @@ class CustomerResponse(BaseModel):
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {
|
||||
"from_attributes": True
|
||||
}
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
@@ -110,6 +108,7 @@ class CustomerListResponse(BaseModel):
|
||||
# Customer Address
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CustomerAddressCreate(BaseModel):
|
||||
"""Schema for creating customer address."""
|
||||
|
||||
@@ -159,14 +158,14 @@ class CustomerAddressResponse(BaseModel):
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {
|
||||
"from_attributes": True
|
||||
}
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Customer Preferences
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CustomerPreferencesUpdate(BaseModel):
|
||||
"""Schema for updating customer preferences."""
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# models/schema/inventory.py
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
@@ -11,16 +12,21 @@ class InventoryBase(BaseModel):
|
||||
|
||||
class InventoryCreate(InventoryBase):
|
||||
"""Set exact inventory quantity (replaces existing)."""
|
||||
|
||||
quantity: int = Field(..., description="Exact inventory quantity", ge=0)
|
||||
|
||||
|
||||
class InventoryAdjust(InventoryBase):
|
||||
"""Add or remove inventory quantity."""
|
||||
quantity: int = Field(..., description="Quantity to add (positive) or remove (negative)")
|
||||
|
||||
quantity: int = Field(
|
||||
..., description="Quantity to add (positive) or remove (negative)"
|
||||
)
|
||||
|
||||
|
||||
class InventoryUpdate(BaseModel):
|
||||
"""Update inventory fields."""
|
||||
|
||||
quantity: Optional[int] = Field(None, ge=0)
|
||||
reserved_quantity: Optional[int] = Field(None, ge=0)
|
||||
location: Optional[str] = None
|
||||
@@ -28,6 +34,7 @@ class InventoryUpdate(BaseModel):
|
||||
|
||||
class InventoryReserve(BaseModel):
|
||||
"""Reserve inventory for orders."""
|
||||
|
||||
product_id: int
|
||||
location: str
|
||||
quantity: int = Field(..., gt=0)
|
||||
@@ -60,6 +67,7 @@ class InventoryLocationResponse(BaseModel):
|
||||
|
||||
class ProductInventorySummary(BaseModel):
|
||||
"""Inventory summary for a product."""
|
||||
|
||||
product_id: int
|
||||
vendor_id: int
|
||||
product_sku: Optional[str]
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Marketplace import job models
|
||||
# Marketplace import job models
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
@@ -8,9 +9,12 @@ class MarketplaceImportJobRequest(BaseModel):
|
||||
|
||||
Note: vendor_id is injected by middleware, not from request body.
|
||||
"""
|
||||
|
||||
source_url: str = Field(..., description="URL to CSV file from marketplace")
|
||||
marketplace: str = Field(default="Letzshop", description="Marketplace name")
|
||||
batch_size: Optional[int] = Field(1000, description="Processing batch size", ge=100, le=10000)
|
||||
batch_size: Optional[int] = Field(
|
||||
1000, description="Processing batch size", ge=100, le=10000
|
||||
)
|
||||
|
||||
@field_validator("source_url")
|
||||
@classmethod
|
||||
@@ -28,6 +32,7 @@ class MarketplaceImportJobRequest(BaseModel):
|
||||
|
||||
class MarketplaceImportJobResponse(BaseModel):
|
||||
"""Response schema for marketplace import job."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
job_id: int
|
||||
@@ -55,6 +60,7 @@ class MarketplaceImportJobResponse(BaseModel):
|
||||
|
||||
class MarketplaceImportJobListResponse(BaseModel):
|
||||
"""Response schema for list of import jobs."""
|
||||
|
||||
jobs: list[MarketplaceImportJobResponse]
|
||||
total: int
|
||||
skip: int
|
||||
@@ -63,6 +69,7 @@ class MarketplaceImportJobListResponse(BaseModel):
|
||||
|
||||
class MarketplaceImportJobStatusUpdate(BaseModel):
|
||||
"""Schema for updating import job status (internal use)."""
|
||||
|
||||
status: str
|
||||
imported_count: Optional[int] = None
|
||||
updated_count: Optional[int] = None
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# models/schema/marketplace_products.py - Simplified validation
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from models.schema.inventory import ProductInventorySummary
|
||||
|
||||
|
||||
class MarketplaceProductBase(BaseModel):
|
||||
marketplace_product_id: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
@@ -45,27 +48,34 @@ class MarketplaceProductBase(BaseModel):
|
||||
marketplace: Optional[str] = None
|
||||
vendor_name: Optional[str] = None
|
||||
|
||||
|
||||
class MarketplaceProductCreate(MarketplaceProductBase):
|
||||
marketplace_product_id: str = Field(..., description="MarketplaceProduct identifier")
|
||||
marketplace_product_id: str = Field(
|
||||
..., description="MarketplaceProduct identifier"
|
||||
)
|
||||
title: str = Field(..., description="MarketplaceProduct title")
|
||||
# Removed: min_length constraints and custom validators
|
||||
# Service will handle empty string validation with proper domain exceptions
|
||||
|
||||
|
||||
class MarketplaceProductUpdate(MarketplaceProductBase):
|
||||
pass
|
||||
|
||||
|
||||
class MarketplaceProductResponse(MarketplaceProductBase):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class MarketplaceProductListResponse(BaseModel):
|
||||
products: List[MarketplaceProductResponse]
|
||||
total: int
|
||||
skip: int
|
||||
limit: int
|
||||
|
||||
|
||||
class MarketplaceProductDetailResponse(BaseModel):
|
||||
product: MarketplaceProductResponse
|
||||
inventory_info: Optional[ProductInventorySummary] = None
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Media/file management models
|
||||
# Media/file management models
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Monitoring models
|
||||
# Monitoring models
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Notification models
|
||||
# Notification models
|
||||
|
||||
@@ -4,23 +4,26 @@ Pydantic schema for order operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from decimal import Decimal
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
# ============================================================================
|
||||
# Order Item Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class OrderItemCreate(BaseModel):
|
||||
"""Schema for creating an order item."""
|
||||
|
||||
product_id: int
|
||||
quantity: int = Field(..., ge=1)
|
||||
|
||||
|
||||
class OrderItemResponse(BaseModel):
|
||||
"""Schema for order item response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
@@ -41,8 +44,10 @@ class OrderItemResponse(BaseModel):
|
||||
# Order Address Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class OrderAddressCreate(BaseModel):
|
||||
"""Schema for order address (shipping/billing)."""
|
||||
|
||||
first_name: str = Field(..., min_length=1, max_length=100)
|
||||
last_name: str = Field(..., min_length=1, max_length=100)
|
||||
company: Optional[str] = Field(None, max_length=200)
|
||||
@@ -55,6 +60,7 @@ class OrderAddressCreate(BaseModel):
|
||||
|
||||
class OrderAddressResponse(BaseModel):
|
||||
"""Schema for order address response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
@@ -73,8 +79,10 @@ class OrderAddressResponse(BaseModel):
|
||||
# Order Create/Update Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class OrderCreate(BaseModel):
|
||||
"""Schema for creating an order."""
|
||||
|
||||
customer_id: Optional[int] = None # Optional for guest checkout
|
||||
items: List[OrderItemCreate] = Field(..., min_length=1)
|
||||
|
||||
@@ -92,9 +100,9 @@ class OrderCreate(BaseModel):
|
||||
|
||||
class OrderUpdate(BaseModel):
|
||||
"""Schema for updating order status."""
|
||||
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
pattern="^(pending|processing|shipped|delivered|cancelled|refunded)$"
|
||||
None, pattern="^(pending|processing|shipped|delivered|cancelled|refunded)$"
|
||||
)
|
||||
tracking_number: Optional[str] = None
|
||||
internal_notes: Optional[str] = None
|
||||
@@ -104,8 +112,10 @@ class OrderUpdate(BaseModel):
|
||||
# Order Response Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class OrderResponse(BaseModel):
|
||||
"""Schema for order response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
@@ -141,6 +151,7 @@ class OrderResponse(BaseModel):
|
||||
|
||||
class OrderDetailResponse(OrderResponse):
|
||||
"""Schema for detailed order response with items and addresses."""
|
||||
|
||||
items: List[OrderItemResponse]
|
||||
shipping_address: OrderAddressResponse
|
||||
billing_address: OrderAddressResponse
|
||||
@@ -148,6 +159,7 @@ class OrderDetailResponse(OrderResponse):
|
||||
|
||||
class OrderListResponse(BaseModel):
|
||||
"""Schema for paginated order list."""
|
||||
|
||||
orders: List[OrderResponse]
|
||||
total: int
|
||||
skip: int
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Payment models
|
||||
# Payment models
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
# models/schema/product.py
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from models.schema.marketplace_product import MarketplaceProductResponse
|
||||
|
||||
from models.schema.inventory import InventoryLocationResponse
|
||||
from models.schema.marketplace_product import MarketplaceProductResponse
|
||||
|
||||
|
||||
class ProductCreate(BaseModel):
|
||||
marketplace_product_id: int = Field(..., description="MarketplaceProduct ID to add to vendor catalog")
|
||||
product_id: Optional[str] = Field(None, description="Vendor's internal SKU/product ID")
|
||||
marketplace_product_id: int = Field(
|
||||
..., description="MarketplaceProduct ID to add to vendor catalog"
|
||||
)
|
||||
product_id: Optional[str] = Field(
|
||||
None, description="Vendor's internal SKU/product ID"
|
||||
)
|
||||
price: Optional[float] = Field(None, ge=0)
|
||||
sale_price: Optional[float] = Field(None, ge=0)
|
||||
currency: Optional[str] = None
|
||||
@@ -59,6 +65,7 @@ class ProductResponse(BaseModel):
|
||||
|
||||
class ProductDetailResponse(ProductResponse):
|
||||
"""Product with full inventory details."""
|
||||
|
||||
inventory_locations: List[InventoryLocationResponse] = []
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Search models
|
||||
# Search models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
|
||||
@@ -22,10 +22,12 @@ class MarketplaceStatsResponse(BaseModel):
|
||||
unique_vendors: int
|
||||
unique_brands: int
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Customer Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CustomerStatsResponse(BaseModel):
|
||||
"""Schema for customer statistics."""
|
||||
|
||||
@@ -42,8 +44,10 @@ class CustomerStatsResponse(BaseModel):
|
||||
# Order Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class OrderStatsResponse(BaseModel):
|
||||
"""Schema for order statistics."""
|
||||
|
||||
total_orders: int
|
||||
pending_orders: int
|
||||
processing_orders: int
|
||||
@@ -53,13 +57,16 @@ class OrderStatsResponse(BaseModel):
|
||||
total_revenue: Decimal
|
||||
average_order_value: Decimal
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Vendor Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class VendorStatsResponse(BaseModel):
|
||||
"""Vendor statistics response schema."""
|
||||
|
||||
total: int = Field(..., description="Total number of vendors")
|
||||
verified: int = Field(..., description="Number of verified vendors")
|
||||
pending: int = Field(..., description="Number of pending verification vendors")
|
||||
inactive: int = Field(..., description="Number of inactive vendors")
|
||||
inactive: int = Field(..., description="Number of inactive vendors")
|
||||
|
||||
@@ -10,33 +10,40 @@ This module defines request/response schemas for:
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
|
||||
# ============================================================================
|
||||
# Role Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class RoleBase(BaseModel):
|
||||
"""Base role schema."""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=100, description="Role name")
|
||||
permissions: List[str] = Field(default_factory=list, description="List of permission strings")
|
||||
permissions: List[str] = Field(
|
||||
default_factory=list, description="List of permission strings"
|
||||
)
|
||||
|
||||
|
||||
class RoleCreate(RoleBase):
|
||||
"""Schema for creating a role."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RoleUpdate(BaseModel):
|
||||
"""Schema for updating a role."""
|
||||
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100)
|
||||
permissions: Optional[List[str]] = None
|
||||
|
||||
|
||||
class RoleResponse(RoleBase):
|
||||
"""Schema for role response."""
|
||||
|
||||
id: int
|
||||
vendor_id: int
|
||||
created_at: datetime
|
||||
@@ -48,6 +55,7 @@ class RoleResponse(RoleBase):
|
||||
|
||||
class RoleListResponse(BaseModel):
|
||||
"""Schema for role list response."""
|
||||
|
||||
roles: List[RoleResponse]
|
||||
total: int
|
||||
|
||||
@@ -56,8 +64,10 @@ class RoleListResponse(BaseModel):
|
||||
# Team Member Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TeamMemberBase(BaseModel):
|
||||
"""Base team member schema."""
|
||||
|
||||
email: EmailStr = Field(..., description="Team member email address")
|
||||
first_name: Optional[str] = Field(None, max_length=100)
|
||||
last_name: Optional[str] = Field(None, max_length=100)
|
||||
@@ -65,30 +75,34 @@ class TeamMemberBase(BaseModel):
|
||||
|
||||
class TeamMemberInvite(TeamMemberBase):
|
||||
"""Schema for inviting a team member."""
|
||||
role_id: Optional[int] = Field(None, description="Role ID to assign (for preset roles)")
|
||||
role_name: Optional[str] = Field(None, description="Role name (manager, staff, support, etc.)")
|
||||
|
||||
role_id: Optional[int] = Field(
|
||||
None, description="Role ID to assign (for preset roles)"
|
||||
)
|
||||
role_name: Optional[str] = Field(
|
||||
None, description="Role name (manager, staff, support, etc.)"
|
||||
)
|
||||
custom_permissions: Optional[List[str]] = Field(
|
||||
None,
|
||||
description="Custom permissions (overrides role preset)"
|
||||
None, description="Custom permissions (overrides role preset)"
|
||||
)
|
||||
|
||||
@field_validator('role_name')
|
||||
@field_validator("role_name")
|
||||
def validate_role_name(cls, v):
|
||||
"""Validate role name is in allowed presets."""
|
||||
if v is not None:
|
||||
allowed_roles = ['manager', 'staff', 'support', 'viewer', 'marketing']
|
||||
allowed_roles = ["manager", "staff", "support", "viewer", "marketing"]
|
||||
if v.lower() not in allowed_roles:
|
||||
raise ValueError(
|
||||
f"Role name must be one of: {', '.join(allowed_roles)}"
|
||||
)
|
||||
return v.lower() if v else v
|
||||
|
||||
@field_validator('custom_permissions')
|
||||
@field_validator("custom_permissions")
|
||||
def validate_custom_permissions(cls, v, values):
|
||||
"""Ensure either role_id/role_name OR custom_permissions is provided."""
|
||||
if v is not None and len(v) > 0:
|
||||
# If custom permissions provided, role_name should be provided too
|
||||
if 'role_name' not in values or not values['role_name']:
|
||||
if "role_name" not in values or not values["role_name"]:
|
||||
raise ValueError(
|
||||
"role_name is required when providing custom_permissions"
|
||||
)
|
||||
@@ -97,12 +111,14 @@ class TeamMemberInvite(TeamMemberBase):
|
||||
|
||||
class TeamMemberUpdate(BaseModel):
|
||||
"""Schema for updating a team member."""
|
||||
|
||||
role_id: Optional[int] = Field(None, description="New role ID")
|
||||
is_active: Optional[bool] = Field(None, description="Active status")
|
||||
|
||||
|
||||
class TeamMemberResponse(BaseModel):
|
||||
"""Schema for team member response."""
|
||||
|
||||
id: int = Field(..., description="User ID")
|
||||
email: EmailStr
|
||||
username: str
|
||||
@@ -112,15 +128,18 @@ class TeamMemberResponse(BaseModel):
|
||||
user_type: str = Field(..., description="'owner' or 'member'")
|
||||
role_name: str = Field(..., description="Role name")
|
||||
role_id: Optional[int]
|
||||
permissions: List[str] = Field(default_factory=list, description="User's permissions")
|
||||
permissions: List[str] = Field(
|
||||
default_factory=list, description="User's permissions"
|
||||
)
|
||||
is_active: bool
|
||||
is_owner: bool
|
||||
invitation_pending: bool = Field(
|
||||
default=False,
|
||||
description="True if invitation not yet accepted"
|
||||
default=False, description="True if invitation not yet accepted"
|
||||
)
|
||||
invited_at: Optional[datetime] = Field(None, description="When invitation was sent")
|
||||
accepted_at: Optional[datetime] = Field(None, description="When invitation was accepted")
|
||||
accepted_at: Optional[datetime] = Field(
|
||||
None, description="When invitation was accepted"
|
||||
)
|
||||
joined_at: datetime = Field(..., description="When user joined vendor")
|
||||
|
||||
class Config:
|
||||
@@ -129,6 +148,7 @@ class TeamMemberResponse(BaseModel):
|
||||
|
||||
class TeamMemberListResponse(BaseModel):
|
||||
"""Schema for team member list response."""
|
||||
|
||||
members: List[TeamMemberResponse]
|
||||
total: int
|
||||
active_count: int
|
||||
@@ -139,19 +159,20 @@ class TeamMemberListResponse(BaseModel):
|
||||
# Invitation Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class InvitationAccept(BaseModel):
|
||||
"""Schema for accepting a team invitation."""
|
||||
invitation_token: str = Field(..., min_length=32, description="Invitation token from email")
|
||||
|
||||
invitation_token: str = Field(
|
||||
..., min_length=32, description="Invitation token from email"
|
||||
)
|
||||
password: str = Field(
|
||||
...,
|
||||
min_length=8,
|
||||
max_length=128,
|
||||
description="Password for new account"
|
||||
..., min_length=8, max_length=128, description="Password for new account"
|
||||
)
|
||||
first_name: str = Field(..., min_length=1, max_length=100)
|
||||
last_name: str = Field(..., min_length=1, max_length=100)
|
||||
|
||||
@field_validator('password')
|
||||
@field_validator("password")
|
||||
def validate_password_strength(cls, v):
|
||||
"""Validate password meets minimum requirements."""
|
||||
if len(v) < 8:
|
||||
@@ -172,18 +193,19 @@ class InvitationAccept(BaseModel):
|
||||
|
||||
class InvitationResponse(BaseModel):
|
||||
"""Schema for invitation response."""
|
||||
|
||||
message: str
|
||||
email: EmailStr
|
||||
role: str
|
||||
invitation_token: Optional[str] = Field(
|
||||
None,
|
||||
description="Token (only returned in dev/test environments)"
|
||||
None, description="Token (only returned in dev/test environments)"
|
||||
)
|
||||
invitation_sent: bool = Field(default=True)
|
||||
|
||||
|
||||
class InvitationAcceptResponse(BaseModel):
|
||||
"""Schema for invitation acceptance response."""
|
||||
|
||||
message: str
|
||||
vendor: dict = Field(..., description="Vendor information")
|
||||
user: dict = Field(..., description="User information")
|
||||
@@ -194,8 +216,10 @@ class InvitationAcceptResponse(BaseModel):
|
||||
# Team Statistics Schema
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TeamStatistics(BaseModel):
|
||||
"""Schema for team statistics."""
|
||||
|
||||
total_members: int
|
||||
active_members: int
|
||||
inactive_members: int
|
||||
@@ -203,8 +227,7 @@ class TeamStatistics(BaseModel):
|
||||
owners: int
|
||||
team_members: int
|
||||
roles_breakdown: dict = Field(
|
||||
default_factory=dict,
|
||||
description="Count of members per role"
|
||||
default_factory=dict, description="Count of members per role"
|
||||
)
|
||||
|
||||
|
||||
@@ -212,13 +235,18 @@ class TeamStatistics(BaseModel):
|
||||
# Bulk Operations Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class BulkRemoveRequest(BaseModel):
|
||||
"""Schema for bulk removing team members."""
|
||||
user_ids: List[int] = Field(..., min_items=1, description="List of user IDs to remove")
|
||||
|
||||
user_ids: List[int] = Field(
|
||||
..., min_items=1, description="List of user IDs to remove"
|
||||
)
|
||||
|
||||
|
||||
class BulkRemoveResponse(BaseModel):
|
||||
"""Schema for bulk remove response."""
|
||||
|
||||
success_count: int
|
||||
failed_count: int
|
||||
errors: List[dict] = Field(default_factory=list)
|
||||
@@ -228,21 +256,27 @@ class BulkRemoveResponse(BaseModel):
|
||||
# Permission Check Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class PermissionCheckRequest(BaseModel):
|
||||
"""Schema for checking permissions."""
|
||||
|
||||
permissions: List[str] = Field(..., min_items=1, description="Permissions to check")
|
||||
|
||||
|
||||
class PermissionCheckResponse(BaseModel):
|
||||
"""Schema for permission check response."""
|
||||
|
||||
has_all: bool = Field(..., description="True if user has all permissions")
|
||||
has_any: bool = Field(..., description="True if user has any permission")
|
||||
granted: List[str] = Field(default_factory=list, description="Permissions user has")
|
||||
denied: List[str] = Field(default_factory=list, description="Permissions user lacks")
|
||||
denied: List[str] = Field(
|
||||
default_factory=list, description="Permissions user lacks"
|
||||
)
|
||||
|
||||
|
||||
class UserPermissionsResponse(BaseModel):
|
||||
"""Schema for user's permissions response."""
|
||||
|
||||
permissions: List[str] = Field(default_factory=list)
|
||||
permission_count: int
|
||||
is_owner: bool
|
||||
@@ -253,8 +287,10 @@ class UserPermissionsResponse(BaseModel):
|
||||
# Error Response Schema
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TeamErrorResponse(BaseModel):
|
||||
"""Schema for team operation errors."""
|
||||
|
||||
error_code: str
|
||||
message: str
|
||||
details: Optional[dict] = None
|
||||
|
||||
@@ -15,7 +15,8 @@ Schemas include:
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
@@ -27,32 +28,26 @@ class VendorCreate(BaseModel):
|
||||
...,
|
||||
description="Unique vendor identifier (e.g., TECHSTORE)",
|
||||
min_length=2,
|
||||
max_length=50
|
||||
max_length=50,
|
||||
)
|
||||
subdomain: str = Field(
|
||||
...,
|
||||
description="Unique subdomain for the vendor",
|
||||
min_length=2,
|
||||
max_length=100
|
||||
..., description="Unique subdomain for the vendor", min_length=2, max_length=100
|
||||
)
|
||||
name: str = Field(
|
||||
...,
|
||||
description="Display name of the vendor",
|
||||
min_length=2,
|
||||
max_length=255
|
||||
..., description="Display name of the vendor", min_length=2, max_length=255
|
||||
)
|
||||
description: Optional[str] = Field(None, description="Vendor description")
|
||||
|
||||
# Owner Information (Creates User Account)
|
||||
owner_email: str = Field(
|
||||
...,
|
||||
description="Email for the vendor owner (used for login and authentication)"
|
||||
description="Email for the vendor owner (used for login and authentication)",
|
||||
)
|
||||
|
||||
# Business Contact Information (Vendor Fields)
|
||||
contact_email: Optional[str] = Field(
|
||||
None,
|
||||
description="Public business contact email (defaults to owner_email if not provided)"
|
||||
description="Public business contact email (defaults to owner_email if not provided)",
|
||||
)
|
||||
contact_phone: Optional[str] = Field(None, description="Contact phone number")
|
||||
website: Optional[str] = Field(None, description="Website URL")
|
||||
@@ -78,8 +73,10 @@ class VendorCreate(BaseModel):
|
||||
@classmethod
|
||||
def validate_subdomain(cls, v):
|
||||
"""Validate subdomain format: lowercase alphanumeric with hyphens."""
|
||||
if v and not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$', v):
|
||||
raise ValueError("Subdomain must contain only lowercase letters, numbers, and hyphens")
|
||||
if v and not re.match(r"^[a-z0-9][a-z0-9-]*[a-z0-9]$", v):
|
||||
raise ValueError(
|
||||
"Subdomain must contain only lowercase letters, numbers, and hyphens"
|
||||
)
|
||||
return v.lower() if v else v
|
||||
|
||||
@field_validator("vendor_code")
|
||||
@@ -104,8 +101,7 @@ class VendorUpdate(BaseModel):
|
||||
|
||||
# Business Contact Information (Vendor Fields)
|
||||
contact_email: Optional[str] = Field(
|
||||
None,
|
||||
description="Public business contact email"
|
||||
None, description="Public business contact email"
|
||||
)
|
||||
contact_phone: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
@@ -142,6 +138,7 @@ class VendorUpdate(BaseModel):
|
||||
|
||||
class VendorResponse(BaseModel):
|
||||
"""Standard schema for vendor response data."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
@@ -184,13 +181,9 @@ class VendorDetailResponse(VendorResponse):
|
||||
"""
|
||||
|
||||
owner_email: str = Field(
|
||||
...,
|
||||
description="Email of the vendor owner (for login/authentication)"
|
||||
)
|
||||
owner_username: str = Field(
|
||||
...,
|
||||
description="Username of the vendor owner"
|
||||
..., description="Email of the vendor owner (for login/authentication)"
|
||||
)
|
||||
owner_username: str = Field(..., description="Username of the vendor owner")
|
||||
|
||||
|
||||
class VendorCreateResponse(VendorDetailResponse):
|
||||
@@ -201,17 +194,14 @@ class VendorCreateResponse(VendorDetailResponse):
|
||||
"""
|
||||
|
||||
temporary_password: str = Field(
|
||||
...,
|
||||
description="Temporary password for owner (SHOWN ONLY ONCE)"
|
||||
)
|
||||
login_url: Optional[str] = Field(
|
||||
None,
|
||||
description="URL for vendor owner to login"
|
||||
..., description="Temporary password for owner (SHOWN ONLY ONCE)"
|
||||
)
|
||||
login_url: Optional[str] = Field(None, description="URL for vendor owner to login")
|
||||
|
||||
|
||||
class VendorListResponse(BaseModel):
|
||||
"""Schema for paginated vendor list."""
|
||||
|
||||
vendors: List[VendorResponse]
|
||||
total: int
|
||||
skip: int
|
||||
@@ -220,6 +210,7 @@ class VendorListResponse(BaseModel):
|
||||
|
||||
class VendorSummary(BaseModel):
|
||||
"""Lightweight vendor summary for dropdowns and quick references."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
@@ -239,20 +230,17 @@ class VendorTransferOwnership(BaseModel):
|
||||
"""
|
||||
|
||||
new_owner_user_id: int = Field(
|
||||
...,
|
||||
description="ID of the user who will become the new owner",
|
||||
gt=0
|
||||
..., description="ID of the user who will become the new owner", gt=0
|
||||
)
|
||||
|
||||
confirm_transfer: bool = Field(
|
||||
...,
|
||||
description="Must be true to confirm ownership transfer"
|
||||
..., description="Must be true to confirm ownership transfer"
|
||||
)
|
||||
|
||||
transfer_reason: Optional[str] = Field(
|
||||
None,
|
||||
max_length=500,
|
||||
description="Reason for ownership transfer (for audit logs)"
|
||||
description="Reason for ownership transfer (for audit logs)",
|
||||
)
|
||||
|
||||
@field_validator("confirm_transfer")
|
||||
@@ -273,12 +261,10 @@ class VendorTransferOwnershipResponse(BaseModel):
|
||||
vendor_name: str
|
||||
|
||||
old_owner: Dict[str, Any] = Field(
|
||||
...,
|
||||
description="Information about the previous owner"
|
||||
..., description="Information about the previous owner"
|
||||
)
|
||||
new_owner: Dict[str, Any] = Field(
|
||||
...,
|
||||
description="Information about the new owner"
|
||||
..., description="Information about the new owner"
|
||||
)
|
||||
|
||||
transferred_at: datetime
|
||||
|
||||
@@ -12,7 +12,8 @@ Schemas include:
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
@@ -23,14 +24,13 @@ class VendorDomainCreate(BaseModel):
|
||||
...,
|
||||
description="Custom domain (e.g., myshop.com or shop.mybrand.com)",
|
||||
min_length=3,
|
||||
max_length=255
|
||||
max_length=255,
|
||||
)
|
||||
is_primary: bool = Field(
|
||||
default=False,
|
||||
description="Set as primary domain for the vendor"
|
||||
default=False, description="Set as primary domain for the vendor"
|
||||
)
|
||||
|
||||
@field_validator('domain')
|
||||
@field_validator("domain")
|
||||
@classmethod
|
||||
def validate_domain(cls, v: str) -> str:
|
||||
"""Validate and normalize domain."""
|
||||
@@ -44,20 +44,22 @@ class VendorDomainCreate(BaseModel):
|
||||
domain = domain.lower().strip()
|
||||
|
||||
# Basic validation
|
||||
if not domain or '/' in domain:
|
||||
if not domain or "/" in domain:
|
||||
raise ValueError("Invalid domain format")
|
||||
|
||||
if '.' not in domain:
|
||||
if "." not in domain:
|
||||
raise ValueError("Domain must have at least one dot")
|
||||
|
||||
# Check for reserved subdomains
|
||||
reserved = ['www', 'admin', 'api', 'mail', 'smtp', 'ftp', 'cpanel', 'webmail']
|
||||
first_part = domain.split('.')[0]
|
||||
reserved = ["www", "admin", "api", "mail", "smtp", "ftp", "cpanel", "webmail"]
|
||||
first_part = domain.split(".")[0]
|
||||
if first_part in reserved:
|
||||
raise ValueError(f"Domain cannot start with reserved subdomain: {first_part}")
|
||||
raise ValueError(
|
||||
f"Domain cannot start with reserved subdomain: {first_part}"
|
||||
)
|
||||
|
||||
# Validate domain format (basic regex)
|
||||
domain_pattern = r'^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$'
|
||||
domain_pattern = r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$"
|
||||
if not re.match(domain_pattern, domain):
|
||||
raise ValueError("Invalid domain format")
|
||||
|
||||
@@ -75,6 +77,7 @@ class VendorDomainUpdate(BaseModel):
|
||||
|
||||
class VendorDomainResponse(BaseModel):
|
||||
"""Standard schema for vendor domain response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
Pydantic schemas for vendor theme operations.
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional, List
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class VendorThemeColors(BaseModel):
|
||||
"""Color scheme for vendor theme."""
|
||||
|
||||
primary: Optional[str] = Field(None, description="Primary brand color")
|
||||
secondary: Optional[str] = Field(None, description="Secondary color")
|
||||
accent: Optional[str] = Field(None, description="Accent/CTA color")
|
||||
@@ -19,12 +21,14 @@ class VendorThemeColors(BaseModel):
|
||||
|
||||
class VendorThemeFonts(BaseModel):
|
||||
"""Typography settings for vendor theme."""
|
||||
|
||||
heading: Optional[str] = Field(None, description="Font for headings")
|
||||
body: Optional[str] = Field(None, description="Font for body text")
|
||||
|
||||
|
||||
class VendorThemeBranding(BaseModel):
|
||||
"""Branding assets for vendor theme."""
|
||||
|
||||
logo: Optional[str] = Field(None, description="Logo URL")
|
||||
logo_dark: Optional[str] = Field(None, description="Dark mode logo URL")
|
||||
favicon: Optional[str] = Field(None, description="Favicon URL")
|
||||
@@ -33,36 +37,54 @@ class VendorThemeBranding(BaseModel):
|
||||
|
||||
class VendorThemeLayout(BaseModel):
|
||||
"""Layout settings for vendor theme."""
|
||||
style: Optional[str] = Field(None, description="Product layout style (grid, list, masonry)")
|
||||
header: Optional[str] = Field(None, description="Header style (fixed, static, transparent)")
|
||||
product_card: Optional[str] = Field(None, description="Product card style (modern, classic, minimal)")
|
||||
|
||||
style: Optional[str] = Field(
|
||||
None, description="Product layout style (grid, list, masonry)"
|
||||
)
|
||||
header: Optional[str] = Field(
|
||||
None, description="Header style (fixed, static, transparent)"
|
||||
)
|
||||
product_card: Optional[str] = Field(
|
||||
None, description="Product card style (modern, classic, minimal)"
|
||||
)
|
||||
|
||||
|
||||
class VendorThemeUpdate(BaseModel):
|
||||
"""Schema for updating vendor theme (partial updates allowed)."""
|
||||
|
||||
theme_name: Optional[str] = Field(None, description="Theme preset name")
|
||||
colors: Optional[Dict[str, str]] = Field(None, description="Color scheme")
|
||||
fonts: Optional[Dict[str, str]] = Field(None, description="Font settings")
|
||||
branding: Optional[Dict[str, Optional[str]]] = Field(None, description="Branding assets")
|
||||
branding: Optional[Dict[str, Optional[str]]] = Field(
|
||||
None, description="Branding assets"
|
||||
)
|
||||
layout: Optional[Dict[str, str]] = Field(None, description="Layout settings")
|
||||
custom_css: Optional[str] = Field(None, description="Custom CSS rules")
|
||||
social_links: Optional[Dict[str, str]] = Field(None, description="Social media links")
|
||||
social_links: Optional[Dict[str, str]] = Field(
|
||||
None, description="Social media links"
|
||||
)
|
||||
|
||||
|
||||
class VendorThemeResponse(BaseModel):
|
||||
"""Schema for vendor theme response."""
|
||||
|
||||
theme_name: str = Field(..., description="Theme name")
|
||||
colors: Dict[str, str] = Field(..., description="Color scheme")
|
||||
fonts: Dict[str, str] = Field(..., description="Font settings")
|
||||
branding: Dict[str, Optional[str]] = Field(..., description="Branding assets")
|
||||
layout: Dict[str, str] = Field(..., description="Layout settings")
|
||||
social_links: Optional[Dict[str, str]] = Field(default_factory=dict, description="Social links")
|
||||
social_links: Optional[Dict[str, str]] = Field(
|
||||
default_factory=dict, description="Social links"
|
||||
)
|
||||
custom_css: Optional[str] = Field(None, description="Custom CSS")
|
||||
css_variables: Optional[Dict[str, str]] = Field(None, description="CSS custom properties")
|
||||
css_variables: Optional[Dict[str, str]] = Field(
|
||||
None, description="CSS custom properties"
|
||||
)
|
||||
|
||||
|
||||
class ThemePresetPreview(BaseModel):
|
||||
"""Preview information for a theme preset."""
|
||||
|
||||
name: str = Field(..., description="Preset name")
|
||||
description: str = Field(..., description="Preset description")
|
||||
primary_color: str = Field(..., description="Primary color")
|
||||
@@ -75,10 +97,12 @@ class ThemePresetPreview(BaseModel):
|
||||
|
||||
class ThemePresetResponse(BaseModel):
|
||||
"""Response after applying a preset."""
|
||||
|
||||
message: str = Field(..., description="Success message")
|
||||
theme: VendorThemeResponse = Field(..., description="Applied theme")
|
||||
|
||||
|
||||
class ThemePresetListResponse(BaseModel):
|
||||
"""List of available theme presets."""
|
||||
|
||||
presets: List[ThemePresetPreview] = Field(..., description="Available presets")
|
||||
|
||||
Reference in New Issue
Block a user