refactor: modernize code quality tooling with Ruff

- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter)
- Add comprehensive pyproject.toml configuration
- Simplify Makefile code quality targets
- Configure exclusions for venv/.venv in pyproject.toml
- Auto-fix 1,359 linting issues across codebase

Benefits:
- Much faster builds (Ruff is written in Rust)
- Single tool replaces multiple tools
- More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q)
- All configuration centralized in pyproject.toml
- Better import sorting and formatting consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 19:37:38 +01:00
parent 21c13ca39b
commit 238c1ec9b8
169 changed files with 2183 additions and 1784 deletions

View File

@@ -175,24 +175,24 @@ test-inventory:
# ============================================================================= # =============================================================================
format: format:
@echo "Running black..." @echo "Formatting code with ruff..."
$(PYTHON) -m black . --exclude '/(\.)?venv/' $(PYTHON) -m ruff format .
@echo "Running isort..."
$(PYTHON) -m isort . --skip venv --skip .venv
lint: lint:
@echo "Running linting..." @echo "Linting code with ruff..."
$(PYTHON) -m ruff check . --exclude venv --exclude .venv $(PYTHON) -m ruff check . --fix
$(PYTHON) -m mypy . --ignore-missing-imports --exclude '.*(\.)?venv.*' @echo "Type checking with mypy..."
$(PYTHON) -m mypy .
lint-flake8: lint-strict:
@echo "Running linting..." @echo "Linting (no auto-fix)..."
$(PYTHON) -m flake8 . --max-line-length=120 --extend-ignore=E203,W503,I201,I100 --exclude=venv,.venv,__pycache__,.git $(PYTHON) -m ruff check .
$(PYTHON) -m mypy . --ignore-missing-imports --exclude '.*(\.)?venv.*' @echo "Type checking with mypy..."
$(PYTHON) -m mypy .
check: format lint check: format lint
ci: format lint test-coverage ci: lint-strict test-coverage
qa: format lint test-coverage docs-check qa: format lint test-coverage docs-check
@echo "Quality assurance checks completed!" @echo "Quality assurance checks completed!"
@@ -330,10 +330,11 @@ help:
@echo " test-fast - Run fast tests only" @echo " test-fast - Run fast tests only"
@echo "" @echo ""
@echo "=== CODE QUALITY ===" @echo "=== CODE QUALITY ==="
@echo " format - Format code (black + isort)" @echo " format - Format code with ruff"
@echo " lint - Run linting (ruff + mypy)" @echo " lint - Lint and auto-fix with ruff + mypy"
@echo " lint-strict - Lint without auto-fix + mypy"
@echo " check - Format + lint" @echo " check - Format + lint"
@echo " ci - Full CI pipeline" @echo " ci - Full CI pipeline (strict)"
@echo " qa - Quality assurance" @echo " qa - Quality assurance"
@echo "" @echo ""
@echo "=== DOCUMENTATION ===" @echo "=== DOCUMENTATION ==="

View File

@@ -40,9 +40,13 @@ print("=" * 70)
# ADMIN MODELS # ADMIN MODELS
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
try: try:
from models.database.admin import (AdminAuditLog, AdminNotification, from models.database.admin import (
AdminSession, AdminSetting, AdminAuditLog,
PlatformAlert) AdminNotification,
AdminSession,
AdminSetting,
PlatformAlert,
)
print(" ✓ Admin models imported (5 models)") print(" ✓ Admin models imported (5 models)")
print(" - AdminAuditLog") print(" - AdminAuditLog")
@@ -176,8 +180,8 @@ except ImportError as e:
# SUMMARY # SUMMARY
# ============================================================================ # ============================================================================
print("=" * 70) print("=" * 70)
print(f"[ALEMBIC] Model import completed") print("[ALEMBIC] Model import completed")
print(f"[ALEMBIC] Tables detected in metadata:") print("[ALEMBIC] Tables detected in metadata:")
print("=" * 70) print("=" * 70)
if Base.metadata.tables: if Base.metadata.tables:

View File

@@ -32,18 +32,20 @@ The cookie path restrictions prevent cross-context cookie leakage:
""" """
import logging import logging
from typing import Optional from datetime import UTC
from fastapi import Cookie, Depends, Request from fastapi import Cookie, Depends, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.exceptions import (AdminRequiredException, from app.exceptions import (
AdminRequiredException,
InsufficientPermissionsException, InsufficientPermissionsException,
InvalidTokenException, InvalidTokenException,
UnauthorizedVendorAccessException, UnauthorizedVendorAccessException,
VendorNotFoundException) VendorNotFoundException,
)
from middleware.auth import AuthManager from middleware.auth import AuthManager
from middleware.rate_limiter import RateLimiter from middleware.rate_limiter import RateLimiter
from models.database.user import User from models.database.user import User
@@ -62,11 +64,11 @@ logger = logging.getLogger(__name__)
def _get_token_from_request( def _get_token_from_request(
credentials: Optional[HTTPAuthorizationCredentials], credentials: HTTPAuthorizationCredentials | None,
cookie_value: Optional[str], cookie_value: str | None,
cookie_name: str, cookie_name: str,
request_path: str, request_path: str,
) -> tuple[Optional[str], Optional[str]]: ) -> tuple[str | None, str | None]:
""" """
Extract token from Authorization header or cookie. Extract token from Authorization header or cookie.
@@ -86,7 +88,7 @@ def _get_token_from_request(
if credentials: if credentials:
logger.debug(f"Token found in Authorization header for {request_path}") logger.debug(f"Token found in Authorization header for {request_path}")
return credentials.credentials, "header" return credentials.credentials, "header"
elif cookie_value: if cookie_value:
logger.debug(f"Token found in {cookie_name} cookie for {request_path}") logger.debug(f"Token found in {cookie_name} cookie for {request_path}")
return cookie_value, "cookie" return cookie_value, "cookie"
@@ -118,8 +120,8 @@ def _validate_user_token(token: str, db: Session) -> User:
def get_current_admin_from_cookie_or_header( def get_current_admin_from_cookie_or_header(
request: Request, request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), credentials: HTTPAuthorizationCredentials | None = Depends(security),
admin_token: Optional[str] = Cookie(None), admin_token: str | None = Cookie(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> User: ) -> User:
""" """
@@ -205,8 +207,8 @@ def get_current_admin_api(
def get_current_vendor_from_cookie_or_header( def get_current_vendor_from_cookie_or_header(
request: Request, request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), credentials: HTTPAuthorizationCredentials | None = Depends(security),
vendor_token: Optional[str] = Cookie(None), vendor_token: str | None = Cookie(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> User: ) -> User:
""" """
@@ -305,8 +307,8 @@ def get_current_vendor_api(
def get_current_customer_from_cookie_or_header( def get_current_customer_from_cookie_or_header(
request: Request, request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), credentials: HTTPAuthorizationCredentials | None = Depends(security),
customer_token: Optional[str] = Cookie(None), customer_token: str | None = Cookie(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
""" """
@@ -331,7 +333,7 @@ def get_current_customer_from_cookie_or_header(
Raises: Raises:
InvalidTokenException: If no token or invalid token InvalidTokenException: If no token or invalid token
""" """
from datetime import datetime, timezone from datetime import datetime
from jose import JWTError, jwt from jose import JWTError, jwt
@@ -365,8 +367,8 @@ def get_current_customer_from_cookie_or_header(
# Verify token hasn't expired # Verify token hasn't expired
exp = payload.get("exp") exp = payload.get("exp")
if exp and datetime.fromtimestamp(exp, tz=timezone.utc) < datetime.now( if exp and datetime.fromtimestamp(exp, tz=UTC) < datetime.now(
timezone.utc UTC
): ):
logger.warning(f"Expired customer token for customer_id={customer_id}") logger.warning(f"Expired customer token for customer_id={customer_id}")
raise InvalidTokenException("Token has expired") raise InvalidTokenException("Token has expired")
@@ -694,10 +696,10 @@ def get_user_permissions(
def get_current_admin_optional( def get_current_admin_optional(
request: Request, request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), credentials: HTTPAuthorizationCredentials | None = Depends(security),
admin_token: Optional[str] = Cookie(None), admin_token: str | None = Cookie(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> Optional[User]: ) -> User | None:
""" """
Get current admin user from admin_token cookie or Authorization header. Get current admin user from admin_token cookie or Authorization header.
@@ -741,10 +743,10 @@ def get_current_admin_optional(
def get_current_vendor_optional( def get_current_vendor_optional(
request: Request, request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), credentials: HTTPAuthorizationCredentials | None = Depends(security),
vendor_token: Optional[str] = Cookie(None), vendor_token: str | None = Cookie(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> Optional[User]: ) -> User | None:
""" """
Get current vendor user from vendor_token cookie or Authorization header. Get current vendor user from vendor_token cookie or Authorization header.
@@ -788,10 +790,10 @@ def get_current_vendor_optional(
def get_current_customer_optional( def get_current_customer_optional(
request: Request, request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), credentials: HTTPAuthorizationCredentials | None = Depends(security),
customer_token: Optional[str] = Cookie(None), customer_token: str | None = Cookie(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> Optional[User]: ) -> User | None:
""" """
Get current customer user from customer_token cookie or Authorization header. Get current customer user from customer_token cookie or Authorization header.

View File

@@ -24,9 +24,21 @@ IMPORTANT:
from fastapi import APIRouter from fastapi import APIRouter
# Import all admin routers # Import all admin routers
from . import (audit, auth, code_quality, content_pages, dashboard, from . import (
marketplace, monitoring, notifications, settings, users, audit,
vendor_domains, vendor_themes, vendors) auth,
code_quality,
content_pages,
dashboard,
marketplace,
monitoring,
notifications,
settings,
users,
vendor_domains,
vendor_themes,
vendors,
)
# Create admin router # Create admin router
router = APIRouter() router = APIRouter()

View File

@@ -10,7 +10,6 @@ Provides endpoints for:
import logging import logging
from datetime import datetime from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -19,9 +18,11 @@ from app.api.deps import get_current_admin_api
from app.core.database import get_db from app.core.database import get_db
from app.services.admin_audit_service import admin_audit_service from app.services.admin_audit_service import admin_audit_service
from models.database.user import User from models.database.user import User
from models.schema.admin import (AdminAuditLogFilters, from models.schema.admin import (
AdminAuditLogFilters,
AdminAuditLogListResponse, AdminAuditLogListResponse,
AdminAuditLogResponse) AdminAuditLogResponse,
)
router = APIRouter(prefix="/audit") router = APIRouter(prefix="/audit")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -29,11 +30,11 @@ logger = logging.getLogger(__name__)
@router.get("/logs", response_model=AdminAuditLogListResponse) @router.get("/logs", response_model=AdminAuditLogListResponse)
def get_audit_logs( def get_audit_logs(
admin_user_id: Optional[int] = Query(None, description="Filter by admin user"), admin_user_id: int | None = Query(None, description="Filter by admin user"),
action: Optional[str] = Query(None, description="Filter by action type"), action: str | None = Query(None, description="Filter by action type"),
target_type: Optional[str] = Query(None, description="Filter by target type"), target_type: str | None = Query(None, description="Filter by target type"),
date_from: Optional[datetime] = Query(None, description="Filter from date"), date_from: datetime | None = Query(None, description="Filter from date"),
date_to: Optional[datetime] = Query(None, description="Filter to date"), date_to: datetime | None = Query(None, description="Filter to date"),
skip: int = Query(0, ge=0, description="Number of records to skip"), skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"), limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -4,7 +4,6 @@ RESTful API for architecture validation and violation management
""" """
from datetime import datetime from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -32,7 +31,7 @@ class ScanResponse(BaseModel):
warnings: int warnings: int
duration_seconds: float duration_seconds: float
triggered_by: str triggered_by: str
git_commit_hash: Optional[str] git_commit_hash: str | None
class Config: class Config:
from_attributes = True from_attributes = True
@@ -49,13 +48,13 @@ class ViolationResponse(BaseModel):
file_path: str file_path: str
line_number: int line_number: int
message: str message: str
context: Optional[str] context: str | None
suggestion: Optional[str] suggestion: str | None
status: str status: str
assigned_to: Optional[int] assigned_to: int | None
resolved_at: Optional[str] resolved_at: str | None
resolved_by: Optional[int] resolved_by: int | None
resolution_note: Optional[str] resolution_note: str | None
created_at: str created_at: str
class Config: class Config:
@@ -83,7 +82,7 @@ class AssignViolationRequest(BaseModel):
"""Request model for assigning a violation""" """Request model for assigning a violation"""
user_id: int = Field(..., description="User ID to assign to") user_id: int = Field(..., description="User ID to assign to")
due_date: Optional[datetime] = Field(None, description="Due date for resolution") due_date: datetime | None = Field(None, description="Due date for resolution")
priority: str = Field( priority: str = Field(
"medium", description="Priority level (low, medium, high, critical)" "medium", description="Priority level (low, medium, high, critical)"
) )
@@ -123,7 +122,7 @@ class DashboardStatsResponse(BaseModel):
by_rule: dict by_rule: dict
by_module: dict by_module: dict
top_files: list top_files: list
last_scan: Optional[str] last_scan: str | None
# API Endpoints # API Endpoints
@@ -189,17 +188,17 @@ async def list_scans(
@router.get("/violations", response_model=ViolationListResponse) @router.get("/violations", response_model=ViolationListResponse)
async def list_violations( async def list_violations(
scan_id: Optional[int] = Query( scan_id: int | None = Query(
None, description="Filter by scan ID (defaults to latest)" None, description="Filter by scan ID (defaults to latest)"
), ),
severity: Optional[str] = Query( severity: str | None = Query(
None, description="Filter by severity (error, warning)" None, description="Filter by severity (error, warning)"
), ),
status: Optional[str] = Query( status: str | None = Query(
None, description="Filter by status (open, assigned, resolved, ignored)" None, description="Filter by status (open, assigned, resolved, ignored)"
), ),
rule_id: Optional[str] = Query(None, description="Filter by rule ID"), rule_id: str | None = Query(None, description="Filter by rule ID"),
file_path: Optional[str] = Query( file_path: str | None = Query(
None, description="Filter by file path (partial match)" None, description="Filter by file path (partial match)"
), ),
page: int = Query(1, ge=1, description="Page number"), page: int = Query(1, ge=1, description="Page number"),

View File

@@ -9,7 +9,6 @@ Platform administrators can:
""" """
import logging import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -46,17 +45,17 @@ class ContentPageCreate(BaseModel):
max_length=50, max_length=50,
description="Template name (default, minimal, modern)", description="Template name (default, minimal, modern)",
) )
meta_description: Optional[str] = Field( meta_description: str | None = Field(
None, max_length=300, description="SEO meta description" None, max_length=300, description="SEO meta description"
) )
meta_keywords: Optional[str] = Field( meta_keywords: str | None = Field(
None, max_length=300, description="SEO keywords" None, max_length=300, description="SEO keywords"
) )
is_published: bool = Field(default=False, description="Publish immediately") is_published: bool = Field(default=False, description="Publish immediately")
show_in_footer: bool = Field(default=True, description="Show in footer navigation") show_in_footer: bool = Field(default=True, description="Show in footer navigation")
show_in_header: bool = Field(default=False, description="Show in header navigation") show_in_header: bool = Field(default=False, description="Show in header navigation")
display_order: int = Field(default=0, description="Display order (lower = first)") display_order: int = Field(default=0, description="Display order (lower = first)")
vendor_id: Optional[int] = Field( vendor_id: int | None = Field(
None, description="Vendor ID (None for platform default)" None, description="Vendor ID (None for platform default)"
) )
@@ -64,32 +63,32 @@ class ContentPageCreate(BaseModel):
class ContentPageUpdate(BaseModel): class ContentPageUpdate(BaseModel):
"""Schema for updating a content page.""" """Schema for updating a content page."""
title: Optional[str] = Field(None, max_length=200) title: str | None = Field(None, max_length=200)
content: Optional[str] = None content: str | None = None
content_format: Optional[str] = None content_format: str | None = None
template: Optional[str] = Field(None, max_length=50) template: str | None = Field(None, max_length=50)
meta_description: Optional[str] = Field(None, max_length=300) meta_description: str | None = Field(None, max_length=300)
meta_keywords: Optional[str] = Field(None, max_length=300) meta_keywords: str | None = Field(None, max_length=300)
is_published: Optional[bool] = None is_published: bool | None = None
show_in_footer: Optional[bool] = None show_in_footer: bool | None = None
show_in_header: Optional[bool] = None show_in_header: bool | None = None
display_order: Optional[int] = None display_order: int | None = None
class ContentPageResponse(BaseModel): class ContentPageResponse(BaseModel):
"""Schema for content page response.""" """Schema for content page response."""
id: int id: int
vendor_id: Optional[int] vendor_id: int | None
vendor_name: Optional[str] vendor_name: str | None
slug: str slug: str
title: str title: str
content: str content: str
content_format: str content_format: str
meta_description: Optional[str] meta_description: str | None
meta_keywords: Optional[str] meta_keywords: str | None
is_published: bool is_published: bool
published_at: Optional[str] published_at: str | None
display_order: int display_order: int
show_in_footer: bool show_in_footer: bool
show_in_header: bool show_in_header: bool
@@ -97,8 +96,8 @@ class ContentPageResponse(BaseModel):
is_vendor_override: bool is_vendor_override: bool
created_at: str created_at: str
updated_at: str updated_at: str
created_by: Optional[int] created_by: int | None
updated_by: Optional[int] updated_by: int | None
# ============================================================================ # ============================================================================
@@ -106,7 +105,7 @@ class ContentPageResponse(BaseModel):
# ============================================================================ # ============================================================================
@router.get("/platform", response_model=List[ContentPageResponse]) @router.get("/platform", response_model=list[ContentPageResponse])
def list_platform_pages( def list_platform_pages(
include_unpublished: bool = Query(False, description="Include draft pages"), include_unpublished: bool = Query(False, description="Include draft pages"),
current_user: User = Depends(get_current_admin_api), current_user: User = Depends(get_current_admin_api),
@@ -161,9 +160,9 @@ def create_platform_page(
# ============================================================================ # ============================================================================
@router.get("/", response_model=List[ContentPageResponse]) @router.get("/", response_model=list[ContentPageResponse])
def list_all_pages( def list_all_pages(
vendor_id: Optional[int] = Query(None, description="Filter by vendor ID"), vendor_id: int | None = Query(None, description="Filter by vendor ID"),
include_unpublished: bool = Query(False, description="Include draft pages"), include_unpublished: bool = Query(False, description="Include draft pages"),
current_user: User = Depends(get_current_admin_api), current_user: User = Depends(get_current_admin_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),
@@ -256,4 +255,4 @@ def delete_page(
if not success: if not success:
raise HTTPException(status_code=404, detail="Content page not found") raise HTTPException(status_code=404, detail="Content page not found")
return None return

View File

@@ -4,7 +4,6 @@ Admin dashboard and statistics endpoints.
""" """
import logging import logging
from typing import List
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -57,7 +56,7 @@ def get_comprehensive_stats(
) )
@router.get("/stats/marketplace", response_model=List[MarketplaceStatsResponse]) @router.get("/stats/marketplace", response_model=list[MarketplaceStatsResponse])
def get_marketplace_stats( def get_marketplace_stats(
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api), current_admin: User = Depends(get_current_admin_api),

View File

@@ -4,7 +4,6 @@ Marketplace import job monitoring endpoints for admin.
""" """
import logging import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -20,11 +19,11 @@ router = APIRouter(prefix="/marketplace-import-jobs")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@router.get("", response_model=List[MarketplaceImportJobResponse]) @router.get("", response_model=list[MarketplaceImportJobResponse])
def get_all_marketplace_import_jobs( def get_all_marketplace_import_jobs(
marketplace: Optional[str] = Query(None), marketplace: str | None = Query(None),
vendor_name: Optional[str] = Query(None), vendor_name: str | None = Query(None),
status: Optional[str] = Query(None), status: str | None = Query(None),
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=100), limit: int = Query(100, ge=1, le=100),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -9,7 +9,6 @@ Provides endpoints for:
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -17,12 +16,13 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api from app.api.deps import get_current_admin_api
from app.core.database import get_db from app.core.database import get_db
from models.database.user import User from models.database.user import User
from models.schema.admin import (AdminNotificationCreate, from models.schema.admin import (
AdminNotificationListResponse, AdminNotificationListResponse,
AdminNotificationResponse,
PlatformAlertCreate, PlatformAlertCreate,
PlatformAlertListResponse, PlatformAlertListResponse,
PlatformAlertResolve, PlatformAlertResponse) PlatformAlertResolve,
PlatformAlertResponse,
)
router = APIRouter(prefix="/notifications") router = APIRouter(prefix="/notifications")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -35,8 +35,8 @@ logger = logging.getLogger(__name__)
@router.get("", response_model=AdminNotificationListResponse) @router.get("", response_model=AdminNotificationListResponse)
def get_notifications( def get_notifications(
priority: Optional[str] = Query(None, description="Filter by priority"), priority: str | None = Query(None, description="Filter by priority"),
is_read: Optional[bool] = Query(None, description="Filter by read status"), is_read: bool | None = Query(None, description="Filter by read status"),
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100), limit: int = Query(50, ge=1, le=100),
db: Session = Depends(get_db), db: Session = Depends(get_db),
@@ -87,8 +87,8 @@ def mark_all_as_read(
@router.get("/alerts", response_model=PlatformAlertListResponse) @router.get("/alerts", response_model=PlatformAlertListResponse)
def get_platform_alerts( def get_platform_alerts(
severity: Optional[str] = Query(None, description="Filter by severity"), severity: str | None = Query(None, description="Filter by severity"),
is_resolved: Optional[bool] = Query( is_resolved: bool | None = Query(
None, description="Filter by resolution status" None, description="Filter by resolution status"
), ),
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),

View File

@@ -9,7 +9,6 @@ Provides endpoints for:
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -19,8 +18,12 @@ from app.core.database import get_db
from app.services.admin_audit_service import admin_audit_service from app.services.admin_audit_service import admin_audit_service
from app.services.admin_settings_service import admin_settings_service from app.services.admin_settings_service import admin_settings_service
from models.database.user import User from models.database.user import User
from models.schema.admin import (AdminSettingCreate, AdminSettingListResponse, from models.schema.admin import (
AdminSettingResponse, AdminSettingUpdate) AdminSettingCreate,
AdminSettingListResponse,
AdminSettingResponse,
AdminSettingUpdate,
)
router = APIRouter(prefix="/settings") router = APIRouter(prefix="/settings")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,8 +31,8 @@ logger = logging.getLogger(__name__)
@router.get("", response_model=AdminSettingListResponse) @router.get("", response_model=AdminSettingListResponse)
def get_all_settings( def get_all_settings(
category: Optional[str] = Query(None, description="Filter by category"), category: str | None = Query(None, description="Filter by category"),
is_public: Optional[bool] = Query(None, description="Filter by public flag"), is_public: bool | None = Query(None, description="Filter by public flag"),
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api), current_admin: User = Depends(get_current_admin_api),
): ):

View File

@@ -4,7 +4,6 @@ User management endpoints for admin.
""" """
import logging import logging
from typing import List
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -20,7 +19,7 @@ router = APIRouter(prefix="/users")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@router.get("", response_model=List[UserResponse]) @router.get("", response_model=list[UserResponse])
def get_all_users( def get_all_users(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),

View File

@@ -10,9 +10,8 @@ Follows the architecture pattern:
""" """
import logging import logging
from typing import List
from fastapi import APIRouter, Body, Depends, Path, Query from fastapi import APIRouter, Body, Depends, Path
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api from app.api.deps import get_current_admin_api
@@ -21,13 +20,15 @@ from app.exceptions import VendorNotFoundException
from app.services.vendor_domain_service import vendor_domain_service from app.services.vendor_domain_service import vendor_domain_service
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.vendor_domain import (DomainDeletionResponse, from models.schema.vendor_domain import (
DomainDeletionResponse,
DomainVerificationInstructions, DomainVerificationInstructions,
DomainVerificationResponse, DomainVerificationResponse,
VendorDomainCreate, VendorDomainCreate,
VendorDomainListResponse, VendorDomainListResponse,
VendorDomainResponse, VendorDomainResponse,
VendorDomainUpdate) VendorDomainUpdate,
)
router = APIRouter(prefix="/vendors") router = APIRouter(prefix="/vendors")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -20,8 +20,11 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api, get_db from app.api.deps import get_current_admin_api, get_db
from app.services.vendor_theme_service import vendor_theme_service from app.services.vendor_theme_service import vendor_theme_service
from models.database.user import User from models.database.user import User
from models.schema.vendor_theme import (ThemePresetListResponse, from models.schema.vendor_theme import (
VendorThemeResponse, VendorThemeUpdate) ThemePresetListResponse,
VendorThemeResponse,
VendorThemeUpdate,
)
router = APIRouter(prefix="/vendor-themes") router = APIRouter(prefix="/vendor-themes")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -4,7 +4,7 @@ Vendor management endpoints for admin.
""" """
import logging import logging
from typing import Optional from datetime import UTC
from fastapi import APIRouter, Body, Depends, Path, Query from fastapi import APIRouter, Body, Depends, Path, Query
from sqlalchemy import func from sqlalchemy import func
@@ -12,18 +12,21 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api from app.api.deps import get_current_admin_api
from app.core.database import get_db from app.core.database import get_db
from app.exceptions import (ConfirmationRequiredException, from app.exceptions import ConfirmationRequiredException, VendorNotFoundException
VendorNotFoundException)
from app.services.admin_service import admin_service from app.services.admin_service import admin_service
from app.services.stats_service import stats_service from app.services.stats_service import stats_service
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.stats import VendorStatsResponse from models.schema.stats import VendorStatsResponse
from models.schema.vendor import (VendorCreate, VendorCreateResponse, from models.schema.vendor import (
VendorDetailResponse, VendorListResponse, VendorCreate,
VendorResponse, VendorTransferOwnership, VendorCreateResponse,
VendorDetailResponse,
VendorListResponse,
VendorTransferOwnership,
VendorTransferOwnershipResponse, VendorTransferOwnershipResponse,
VendorUpdate) VendorUpdate,
)
router = APIRouter(prefix="/vendors") router = APIRouter(prefix="/vendors")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -126,9 +129,9 @@ def create_vendor_with_owner(
def get_all_vendors_admin( def get_all_vendors_admin(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
search: Optional[str] = Query(None, description="Search by name or vendor code"), search: str | None = Query(None, description="Search by name or vendor code"),
is_active: Optional[bool] = Query(None), is_active: bool | None = Query(None),
is_verified: Optional[bool] = Query(None), is_verified: bool | None = Query(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api), current_admin: User = Depends(get_current_admin_api),
): ):
@@ -282,7 +285,7 @@ def transfer_vendor_ownership(
- `confirm_transfer`: Must be true - `confirm_transfer`: Must be true
- `transfer_reason`: Optional reason for audit trail - `transfer_reason`: Optional reason for audit trail
""" """
from datetime import datetime, timezone from datetime import datetime
vendor = _get_vendor_by_identifier(db, vendor_identifier) vendor = _get_vendor_by_identifier(db, vendor_identifier)
vendor, old_owner, new_owner = admin_service.transfer_vendor_ownership( vendor, old_owner, new_owner = admin_service.transfer_vendor_ownership(
@@ -304,7 +307,7 @@ def transfer_vendor_ownership(
"username": new_owner.username, "username": new_owner.username,
"email": new_owner.email, "email": new_owner.email,
}, },
transferred_at=datetime.now(timezone.utc), transferred_at=datetime.now(UTC),
transfer_reason=transfer_data.transfer_reason, transfer_reason=transfer_data.transfer_reason,
) )

View File

@@ -14,9 +14,13 @@ from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.services.cart_service import cart_service from app.services.cart_service import cart_service
from models.schema.cart import (AddToCartRequest, CartOperationResponse, from models.schema.cart import (
CartResponse, ClearCartResponse, AddToCartRequest,
UpdateCartItemRequest) CartOperationResponse,
CartResponse,
ClearCartResponse,
UpdateCartItemRequest,
)
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -7,7 +7,6 @@ No authentication required.
""" """
import logging import logging
from typing import List
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel from pydantic import BaseModel
@@ -52,7 +51,7 @@ class ContentPageListItem(BaseModel):
# ============================================================================ # ============================================================================
@router.get("/navigation", response_model=List[ContentPageListItem]) @router.get("/navigation", response_model=list[ContentPageListItem])
def get_navigation_pages(request: Request, db: Session = Depends(get_db)): def get_navigation_pages(request: Request, db: Session = Depends(get_db)):
""" """
Get list of content pages for navigation (footer/header). Get list of content pages for navigation (footer/header).

View File

@@ -8,7 +8,6 @@ Requires customer authentication for most operations.
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -19,8 +18,12 @@ from app.services.customer_service import customer_service
from app.services.order_service import order_service from app.services.order_service import order_service
from models.database.customer import Customer from models.database.customer import Customer
from models.database.user import User from models.database.user import User
from models.schema.order import (OrderCreate, OrderDetailResponse, from models.schema.order import (
OrderListResponse, OrderResponse) OrderCreate,
OrderDetailResponse,
OrderListResponse,
OrderResponse,
)
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -8,15 +8,17 @@ No authentication required.
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.services.product_service import product_service from app.services.product_service import product_service
from models.schema.product import (ProductDetailResponse, ProductListResponse, from models.schema.product import (
ProductResponse) ProductDetailResponse,
ProductListResponse,
ProductResponse,
)
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -27,8 +29,8 @@ def get_product_catalog(
request: Request, request: Request,
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
search: Optional[str] = Query(None, description="Search products by name"), search: str | None = Query(None, description="Search products by name"),
is_featured: Optional[bool] = Query( is_featured: bool | None = Query(
None, description="Filter by featured products" None, description="Filter by featured products"
), ),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -13,9 +13,24 @@ IMPORTANT:
from fastapi import APIRouter from fastapi import APIRouter
# Import all sub-routers (JSON API only) # Import all sub-routers (JSON API only)
from . import (analytics, auth, content_pages, customers, dashboard, info, from . import (
inventory, marketplace, media, notifications, orders, payments, analytics,
products, profile, settings, team) auth,
content_pages,
customers,
dashboard,
info,
inventory,
marketplace,
media,
notifications,
orders,
payments,
products,
profile,
settings,
team,
)
# Create vendor router # Create vendor router
router = APIRouter() router = APIRouter()

View File

@@ -9,7 +9,6 @@ Vendors can:
""" """
import logging import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -41,10 +40,10 @@ class VendorContentPageCreate(BaseModel):
content_format: str = Field( content_format: str = Field(
default="html", description="Content format: html or markdown" default="html", description="Content format: html or markdown"
) )
meta_description: Optional[str] = Field( meta_description: str | None = Field(
None, max_length=300, description="SEO meta description" None, max_length=300, description="SEO meta description"
) )
meta_keywords: Optional[str] = Field( meta_keywords: str | None = Field(
None, max_length=300, description="SEO keywords" None, max_length=300, description="SEO keywords"
) )
is_published: bool = Field(default=False, description="Publish immediately") is_published: bool = Field(default=False, description="Publish immediately")
@@ -56,31 +55,31 @@ class VendorContentPageCreate(BaseModel):
class VendorContentPageUpdate(BaseModel): class VendorContentPageUpdate(BaseModel):
"""Schema for updating a vendor content page.""" """Schema for updating a vendor content page."""
title: Optional[str] = Field(None, max_length=200) title: str | None = Field(None, max_length=200)
content: Optional[str] = None content: str | None = None
content_format: Optional[str] = None content_format: str | None = None
meta_description: Optional[str] = Field(None, max_length=300) meta_description: str | None = Field(None, max_length=300)
meta_keywords: Optional[str] = Field(None, max_length=300) meta_keywords: str | None = Field(None, max_length=300)
is_published: Optional[bool] = None is_published: bool | None = None
show_in_footer: Optional[bool] = None show_in_footer: bool | None = None
show_in_header: Optional[bool] = None show_in_header: bool | None = None
display_order: Optional[int] = None display_order: int | None = None
class ContentPageResponse(BaseModel): class ContentPageResponse(BaseModel):
"""Schema for content page response.""" """Schema for content page response."""
id: int id: int
vendor_id: Optional[int] vendor_id: int | None
vendor_name: Optional[str] vendor_name: str | None
slug: str slug: str
title: str title: str
content: str content: str
content_format: str content_format: str
meta_description: Optional[str] meta_description: str | None
meta_keywords: Optional[str] meta_keywords: str | None
is_published: bool is_published: bool
published_at: Optional[str] published_at: str | None
display_order: int display_order: int
show_in_footer: bool show_in_footer: bool
show_in_header: bool show_in_header: bool
@@ -88,8 +87,8 @@ class ContentPageResponse(BaseModel):
is_vendor_override: bool is_vendor_override: bool
created_at: str created_at: str
updated_at: str updated_at: str
created_by: Optional[int] created_by: int | None
updated_by: Optional[int] updated_by: int | None
# ============================================================================ # ============================================================================
@@ -97,7 +96,7 @@ class ContentPageResponse(BaseModel):
# ============================================================================ # ============================================================================
@router.get("/", response_model=List[ContentPageResponse]) @router.get("/", response_model=list[ContentPageResponse])
def list_vendor_pages( def list_vendor_pages(
include_unpublished: bool = Query(False, description="Include draft pages"), include_unpublished: bool = Query(False, description="Include draft pages"),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
@@ -120,7 +119,7 @@ def list_vendor_pages(
return [page.to_dict() for page in pages] return [page.to_dict() for page in pages]
@router.get("/overrides", response_model=List[ContentPageResponse]) @router.get("/overrides", response_model=list[ContentPageResponse])
def list_vendor_overrides( def list_vendor_overrides(
include_unpublished: bool = Query(False, description="Include draft pages"), include_unpublished: bool = Query(False, description="Include draft pages"),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
@@ -284,4 +283,4 @@ def delete_vendor_page(
# Delete # Delete
content_page_service.delete_page(db, page_id) content_page_service.delete_page(db, page_id)
return None return

View File

@@ -5,7 +5,6 @@ Vendor customer management endpoints.
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -24,8 +23,8 @@ logger = logging.getLogger(__name__)
def get_vendor_customers( def get_vendor_customers(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
search: Optional[str] = Query(None), search: str | None = Query(None),
is_active: Optional[bool] = Query(None), is_active: bool | None = Query(None),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -11,9 +11,7 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api from app.api.deps import get_current_vendor_api
from app.core.database import get_db from app.core.database import get_db
from app.services.stats_service import stats_service from app.services.stats_service import stats_service
from middleware.vendor_context import require_vendor_context
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/dashboard") router = APIRouter(prefix="/dashboard")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -16,7 +16,7 @@ from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.exceptions import VendorNotFoundException from app.exceptions import VendorNotFoundException
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.vendor import VendorDetailResponse, VendorResponse from models.schema.vendor import VendorDetailResponse
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -1,6 +1,5 @@
# app/api/v1/vendor/inventory.py # app/api/v1/vendor/inventory.py
import logging import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -11,10 +10,15 @@ from app.services.inventory_service import inventory_service
from middleware.vendor_context import require_vendor_context from middleware.vendor_context import require_vendor_context
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.inventory import (InventoryAdjust, InventoryCreate, from models.schema.inventory import (
InventoryListResponse, InventoryReserve, InventoryAdjust,
InventoryResponse, InventoryUpdate, InventoryCreate,
ProductInventorySummary) InventoryListResponse,
InventoryReserve,
InventoryResponse,
InventoryUpdate,
ProductInventorySummary,
)
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -90,8 +94,8 @@ def get_product_inventory(
def get_vendor_inventory( def get_vendor_inventory(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
location: Optional[str] = Query(None), location: str | None = Query(None),
low_stock: Optional[int] = Query(None, ge=0), low_stock: int | None = Query(None, ge=0),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -5,22 +5,22 @@ Vendor context is automatically injected by middleware.
""" """
import logging import logging
from typing import List, Optional
from fastapi import APIRouter, BackgroundTasks, Depends, Query from fastapi import APIRouter, BackgroundTasks, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api from app.api.deps import get_current_vendor_api
from app.core.database import get_db from app.core.database import get_db
from app.services.marketplace_import_job_service import \ from app.services.marketplace_import_job_service import marketplace_import_job_service
marketplace_import_job_service
from app.tasks.background_tasks import process_marketplace_import from app.tasks.background_tasks import process_marketplace_import
from middleware.decorators import rate_limit from middleware.decorators import rate_limit
from middleware.vendor_context import require_vendor_context # IMPORTANT from middleware.vendor_context import require_vendor_context # IMPORTANT
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest, from models.schema.marketplace_import_job import (
MarketplaceImportJobResponse) MarketplaceImportJobRequest,
MarketplaceImportJobResponse,
)
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -93,9 +93,9 @@ def get_marketplace_import_status(
return marketplace_import_job_service.convert_to_response_model(job) return marketplace_import_job_service.convert_to_response_model(job)
@router.get("/imports", response_model=List[MarketplaceImportJobResponse]) @router.get("/imports", response_model=list[MarketplaceImportJobResponse])
def get_marketplace_import_jobs( def get_marketplace_import_jobs(
marketplace: Optional[str] = Query(None, description="Filter by marketplace"), marketplace: str | None = Query(None, description="Filter by marketplace"),
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100), limit: int = Query(50, ge=1, le=100),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),

View File

@@ -5,7 +5,6 @@ Vendor media and file management endpoints.
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, File, Query, UploadFile from fastapi import APIRouter, Depends, File, Query, UploadFile
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -24,8 +23,8 @@ logger = logging.getLogger(__name__)
def get_media_library( def get_media_library(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
media_type: Optional[str] = Query(None, description="image, video, document"), media_type: str | None = Query(None, description="image, video, document"),
search: Optional[str] = Query(None), search: str | None = Query(None),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),
@@ -52,7 +51,7 @@ def get_media_library(
@router.post("/upload") @router.post("/upload")
async def upload_media( async def upload_media(
file: UploadFile = File(...), file: UploadFile = File(...),
folder: Optional[str] = Query(None, description="products, general, etc."), folder: str | None = Query(None, description="products, general, etc."),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),
@@ -78,7 +77,7 @@ async def upload_media(
@router.post("/upload/multiple") @router.post("/upload/multiple")
async def upload_multiple_media( async def upload_multiple_media(
files: list[UploadFile] = File(...), files: list[UploadFile] = File(...),
folder: Optional[str] = Query(None), folder: str | None = Query(None),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -5,7 +5,6 @@ Vendor notification management endpoints.
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -24,7 +23,7 @@ logger = logging.getLogger(__name__)
def get_notifications( def get_notifications(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100), limit: int = Query(50, ge=1, le=100),
unread_only: Optional[bool] = Query(False), unread_only: bool | None = Query(False),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -4,9 +4,8 @@ Vendor order management endpoints.
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api from app.api.deps import get_current_vendor_api
@@ -14,9 +13,13 @@ from app.core.database import get_db
from app.services.order_service import order_service from app.services.order_service import order_service
from middleware.vendor_context import require_vendor_context from middleware.vendor_context import require_vendor_context
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor, VendorUser from models.database.vendor import Vendor
from models.schema.order import (OrderDetailResponse, OrderListResponse, from models.schema.order import (
OrderResponse, OrderUpdate) OrderDetailResponse,
OrderListResponse,
OrderResponse,
OrderUpdate,
)
router = APIRouter(prefix="/orders") router = APIRouter(prefix="/orders")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -26,8 +29,8 @@ logger = logging.getLogger(__name__)
def get_vendor_orders( def get_vendor_orders(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
status: Optional[str] = Query(None, description="Filter by order status"), status: str | None = Query(None, description="Filter by order status"),
customer_id: Optional[int] = Query(None, description="Filter by customer"), customer_id: int | None = Query(None, description="Filter by customer"),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -4,7 +4,6 @@ Vendor product catalog management endpoints.
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -15,9 +14,13 @@ from app.services.product_service import product_service
from middleware.vendor_context import require_vendor_context from middleware.vendor_context import require_vendor_context
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.product import (ProductCreate, ProductDetailResponse, from models.schema.product import (
ProductListResponse, ProductResponse, ProductCreate,
ProductUpdate) ProductDetailResponse,
ProductListResponse,
ProductResponse,
ProductUpdate,
)
router = APIRouter(prefix="/products") router = APIRouter(prefix="/products")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -27,8 +30,8 @@ logger = logging.getLogger(__name__)
def get_vendor_products( def get_vendor_products(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
is_active: Optional[bool] = Query(None), is_active: bool | None = Query(None),
is_featured: Optional[bool] = Query(None), is_featured: bool | None = Query(None),
vendor: Vendor = Depends(require_vendor_context()), vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@@ -11,25 +11,34 @@ Implements complete team management with:
""" """
import logging import logging
from typing import List
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.api.deps import (get_current_vendor_api, get_user_permissions, from app.api.deps import (
require_vendor_owner, require_vendor_permission) get_current_vendor_api,
get_user_permissions,
require_vendor_owner,
require_vendor_permission,
)
from app.core.database import get_db from app.core.database import get_db
from app.core.permissions import VendorPermissions from app.core.permissions import VendorPermissions
from app.services.vendor_team_service import vendor_team_service from app.services.vendor_team_service import vendor_team_service
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor from models.schema.team import (
from models.schema.team import (BulkRemoveRequest, BulkRemoveResponse, BulkRemoveRequest,
InvitationAccept, InvitationAcceptResponse, BulkRemoveResponse,
InvitationResponse, RoleListResponse, InvitationAccept,
RoleResponse, TeamMemberInvite, InvitationAcceptResponse,
TeamMemberListResponse, TeamMemberResponse, InvitationResponse,
TeamMemberUpdate, TeamStatistics, RoleListResponse,
UserPermissionsResponse) TeamMemberInvite,
TeamMemberListResponse,
TeamMemberResponse,
TeamMemberUpdate,
TeamStatistics,
UserPermissionsResponse,
)
router = APIRouter(prefix="/team") router = APIRouter(prefix="/team")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -382,7 +391,7 @@ def list_roles(
@router.get("/me/permissions", response_model=UserPermissionsResponse) @router.get("/me/permissions", response_model=UserPermissionsResponse)
def get_my_permissions( def get_my_permissions(
request: Request, request: Request,
permissions: List[str] = Depends(get_user_permissions), permissions: list[str] = Depends(get_user_permissions),
current_user: User = Depends(get_current_vendor_api), current_user: User = Depends(get_current_vendor_api),
): ):
""" """

View File

@@ -13,7 +13,6 @@ Note: Environment detection is handled by app.core.environment module.
This module focuses purely on configuration storage and validation. This module focuses purely on configuration storage and validation.
""" """
from typing import List, Optional
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
@@ -82,7 +81,7 @@ class Settings(BaseSettings):
# ============================================================================= # =============================================================================
# MIDDLEWARE & SECURITY # MIDDLEWARE & SECURITY
# ============================================================================= # =============================================================================
allowed_hosts: List[str] = ["*"] # Configure for production allowed_hosts: list[str] = ["*"] # Configure for production
# Rate Limiting # Rate Limiting
rate_limit_enabled: bool = True rate_limit_enabled: bool = True
@@ -93,7 +92,7 @@ class Settings(BaseSettings):
# LOGGING # LOGGING
# ============================================================================= # =============================================================================
log_level: str = "INFO" log_level: str = "INFO"
log_file: Optional[str] = None log_file: str | None = None
# ============================================================================= # =============================================================================
# PLATFORM DOMAIN CONFIGURATION # PLATFORM DOMAIN CONFIGURATION
@@ -138,9 +137,13 @@ settings = Settings()
# ENVIRONMENT UTILITIES - Module-level functions # ENVIRONMENT UTILITIES - Module-level functions
# ============================================================================= # =============================================================================
# Import environment detection utilities # Import environment detection utilities
from app.core.environment import (get_environment, is_development, from app.core.environment import (
is_production, is_staging, get_environment,
should_use_secure_cookies) is_development,
is_production,
is_staging,
should_use_secure_cookies,
)
def get_current_environment() -> str: def get_current_environment() -> str:
@@ -188,7 +191,7 @@ def is_staging_environment() -> bool:
# ============================================================================= # =============================================================================
def validate_production_settings() -> List[str]: def validate_production_settings() -> list[str]:
""" """
Validate settings for production environment. Validate settings for production environment.

View File

@@ -31,18 +31,18 @@ def get_environment() -> EnvironmentType:
env = os.getenv("ENV", "").lower() env = os.getenv("ENV", "").lower()
if env in ["development", "dev", "local"]: if env in ["development", "dev", "local"]:
return "development" return "development"
elif env in ["staging", "stage"]: if env in ["staging", "stage"]:
return "staging" return "staging"
elif env in ["production", "prod"]: if env in ["production", "prod"]:
return "production" return "production"
# Priority 2: ENVIRONMENT variable # Priority 2: ENVIRONMENT variable
env = os.getenv("ENVIRONMENT", "").lower() env = os.getenv("ENVIRONMENT", "").lower()
if env in ["development", "dev", "local"]: if env in ["development", "dev", "local"]:
return "development" return "development"
elif env in ["staging", "stage"]: if env in ["staging", "stage"]:
return "staging" return "staging"
elif env in ["production", "prod"]: if env in ["production", "prod"]:
return "production" return "production"
# Priority 3: Auto-detect from common indicators # Priority 3: Auto-detect from common indicators

View File

@@ -16,7 +16,7 @@ from sqlalchemy import text
from middleware.auth import AuthManager from middleware.auth import AuthManager
from .database import SessionLocal, engine from .database import engine
from .logging import setup_logging from .logging import setup_logging
# Remove this import if not needed: from models.database.base import Base # Remove this import if not needed: from models.database.base import Base
@@ -60,7 +60,6 @@ def check_database_ready():
def get_migration_status(): def get_migration_status():
"""Get current Alembic migration status.""" """Get current Alembic migration status."""
try: try:
from alembic import command
from alembic.config import Config from alembic.config import Config
alembic_cfg = Config("alembic.ini") alembic_cfg = Config("alembic.ini")

View File

@@ -7,8 +7,8 @@ This module defines:
- Permission groups (for easier role creation) - Permission groups (for easier role creation)
- Permission checking utilities - Permission checking utilities
""" """
from enum import Enum from enum import Enum
from typing import List, Set
class VendorPermissions(str, Enum): class VendorPermissions(str, Enum):
@@ -78,10 +78,10 @@ class PermissionGroups:
"""Pre-defined permission groups for common roles.""" """Pre-defined permission groups for common roles."""
# Full access (for owners) # Full access (for owners)
OWNER: Set[str] = set(p.value for p in VendorPermissions) OWNER: set[str] = set(p.value for p in VendorPermissions)
# Manager - Can do most things except team management and critical settings # Manager - Can do most things except team management and critical settings
MANAGER: Set[str] = { MANAGER: set[str] = {
VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.DASHBOARD_VIEW.value,
VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value,
VendorPermissions.PRODUCTS_CREATE.value, VendorPermissions.PRODUCTS_CREATE.value,
@@ -113,7 +113,7 @@ class PermissionGroups:
} }
# Staff - Can view and edit products/orders but limited access # Staff - Can view and edit products/orders but limited access
STAFF: Set[str] = { STAFF: set[str] = {
VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.DASHBOARD_VIEW.value,
VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value,
VendorPermissions.PRODUCTS_CREATE.value, VendorPermissions.PRODUCTS_CREATE.value,
@@ -127,7 +127,7 @@ class PermissionGroups:
} }
# Support - Can view and assist with orders/customers # Support - Can view and assist with orders/customers
SUPPORT: Set[str] = { SUPPORT: set[str] = {
VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.DASHBOARD_VIEW.value,
VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value,
VendorPermissions.ORDERS_VIEW.value, VendorPermissions.ORDERS_VIEW.value,
@@ -137,7 +137,7 @@ class PermissionGroups:
} }
# Viewer - Read-only access # Viewer - Read-only access
VIEWER: Set[str] = { VIEWER: set[str] = {
VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.DASHBOARD_VIEW.value,
VendorPermissions.PRODUCTS_VIEW.value, VendorPermissions.PRODUCTS_VIEW.value,
VendorPermissions.STOCK_VIEW.value, VendorPermissions.STOCK_VIEW.value,
@@ -147,7 +147,7 @@ class PermissionGroups:
} }
# Marketing - Focused on marketing and customer communication # Marketing - Focused on marketing and customer communication
MARKETING: Set[str] = { MARKETING: set[str] = {
VendorPermissions.DASHBOARD_VIEW.value, VendorPermissions.DASHBOARD_VIEW.value,
VendorPermissions.CUSTOMERS_VIEW.value, VendorPermissions.CUSTOMERS_VIEW.value,
VendorPermissions.CUSTOMERS_EXPORT.value, VendorPermissions.CUSTOMERS_EXPORT.value,
@@ -162,34 +162,34 @@ class PermissionChecker:
"""Utility class for permission checking.""" """Utility class for permission checking."""
@staticmethod @staticmethod
def has_permission(permissions: List[str], required_permission: str) -> bool: def has_permission(permissions: list[str], required_permission: str) -> bool:
"""Check if a permission list contains a required permission.""" """Check if a permission list contains a required permission."""
return required_permission in permissions return required_permission in permissions
@staticmethod @staticmethod
def has_any_permission( def has_any_permission(
permissions: List[str], required_permissions: List[str] permissions: list[str], required_permissions: list[str]
) -> bool: ) -> bool:
"""Check if a permission list contains ANY of the required permissions.""" """Check if a permission list contains ANY of the required permissions."""
return any(perm in permissions for perm in required_permissions) return any(perm in permissions for perm in required_permissions)
@staticmethod @staticmethod
def has_all_permissions( def has_all_permissions(
permissions: List[str], required_permissions: List[str] permissions: list[str], required_permissions: list[str]
) -> bool: ) -> bool:
"""Check if a permission list contains ALL of the required permissions.""" """Check if a permission list contains ALL of the required permissions."""
return all(perm in permissions for perm in required_permissions) return all(perm in permissions for perm in required_permissions)
@staticmethod @staticmethod
def get_missing_permissions( def get_missing_permissions(
permissions: List[str], required_permissions: List[str] permissions: list[str], required_permissions: list[str]
) -> List[str]: ) -> list[str]:
"""Get list of missing permissions.""" """Get list of missing permissions."""
return [perm for perm in required_permissions if perm not in permissions] return [perm for perm in required_permissions if perm not in permissions]
# Helper function to get permissions for a role preset # Helper function to get permissions for a role preset
def get_preset_permissions(preset_name: str) -> Set[str]: def get_preset_permissions(preset_name: str) -> set[str]:
""" """
Get permissions for a preset role. Get permissions for a preset role.

View File

@@ -7,42 +7,78 @@ messages, and HTTP status mappings.
""" """
# Admin exceptions # Admin exceptions
from .admin import (AdminOperationException, BulkOperationException, from .admin import (
CannotModifyAdminException, CannotModifySelfException, AdminOperationException,
ConfirmationRequiredException, InvalidAdminActionException, BulkOperationException,
UserNotFoundException, UserStatusChangeException, CannotModifyAdminException,
VendorVerificationException) CannotModifySelfException,
ConfirmationRequiredException,
InvalidAdminActionException,
UserNotFoundException,
UserStatusChangeException,
VendorVerificationException,
)
# Authentication exceptions # Authentication exceptions
from .auth import (AdminRequiredException, InsufficientPermissionsException, from .auth import (
InvalidCredentialsException, InvalidTokenException, AdminRequiredException,
TokenExpiredException, UserAlreadyExistsException, InsufficientPermissionsException,
UserNotActiveException) InvalidCredentialsException,
InvalidTokenException,
TokenExpiredException,
UserAlreadyExistsException,
UserNotActiveException,
)
# Base exceptions # Base exceptions
from .base import (AuthenticationException, AuthorizationException, from .base import (
BusinessLogicException, ConflictException, AuthenticationException,
ExternalServiceException, RateLimitException, AuthorizationException,
ResourceNotFoundException, ServiceUnavailableException, BusinessLogicException,
ValidationException, WizamartException) ConflictException,
ExternalServiceException,
RateLimitException,
ResourceNotFoundException,
ServiceUnavailableException,
ValidationException,
WizamartException,
)
# Cart exceptions # Cart exceptions
from .cart import (CartItemNotFoundException, CartValidationException, from .cart import (
EmptyCartException, InsufficientInventoryForCartException, CartItemNotFoundException,
CartValidationException,
EmptyCartException,
InsufficientInventoryForCartException,
InvalidCartQuantityException, InvalidCartQuantityException,
ProductNotAvailableForCartException) ProductNotAvailableForCartException,
)
# Customer exceptions # Customer exceptions
from .customer import (CustomerAlreadyExistsException, from .customer import (
CustomerAlreadyExistsException,
CustomerAuthorizationException, CustomerAuthorizationException,
CustomerNotActiveException, CustomerNotFoundException, CustomerNotActiveException,
CustomerNotFoundException,
CustomerValidationException, CustomerValidationException,
DuplicateCustomerEmailException, DuplicateCustomerEmailException,
InvalidCustomerCredentialsException) InvalidCustomerCredentialsException,
)
# Inventory exceptions # Inventory exceptions
from .inventory import (InsufficientInventoryException, from .inventory import (
InsufficientInventoryException,
InvalidInventoryOperationException, InvalidInventoryOperationException,
InvalidQuantityException, InventoryNotFoundException, InvalidQuantityException,
InventoryNotFoundException,
InventoryValidationException, InventoryValidationException,
LocationNotFoundException, NegativeInventoryException) LocationNotFoundException,
NegativeInventoryException,
)
# Marketplace import job exceptions # Marketplace import job exceptions
from .marketplace_import_job import (ImportJobAlreadyProcessingException, from .marketplace_import_job import (
ImportJobAlreadyProcessingException,
ImportJobCannotBeCancelledException, ImportJobCannotBeCancelledException,
ImportJobCannotBeDeletedException, ImportJobCannotBeDeletedException,
ImportJobNotFoundException, ImportJobNotFoundException,
@@ -52,45 +88,74 @@ from .marketplace_import_job import (ImportJobAlreadyProcessingException,
InvalidMarketplaceException, InvalidMarketplaceException,
MarketplaceConnectionException, MarketplaceConnectionException,
MarketplaceDataParsingException, MarketplaceDataParsingException,
MarketplaceImportException) MarketplaceImportException,
)
# Marketplace product exceptions # Marketplace product exceptions
from .marketplace_product import (InvalidGTINException, from .marketplace_product import (
InvalidGTINException,
InvalidMarketplaceProductDataException, InvalidMarketplaceProductDataException,
MarketplaceProductAlreadyExistsException, MarketplaceProductAlreadyExistsException,
MarketplaceProductCSVImportException, MarketplaceProductCSVImportException,
MarketplaceProductNotFoundException, MarketplaceProductNotFoundException,
MarketplaceProductValidationException) MarketplaceProductValidationException,
)
# Order exceptions # Order exceptions
from .order import (InvalidOrderStatusException, OrderAlreadyExistsException, from .order import (
OrderCannotBeCancelledException, OrderNotFoundException, InvalidOrderStatusException,
OrderValidationException) OrderAlreadyExistsException,
OrderCannotBeCancelledException,
OrderNotFoundException,
OrderValidationException,
)
# Product exceptions # Product exceptions
from .product import (CannotDeleteProductWithInventoryException, from .product import (
CannotDeleteProductWithInventoryException,
CannotDeleteProductWithOrdersException, CannotDeleteProductWithOrdersException,
InvalidProductDataException, InvalidProductDataException,
ProductAlreadyExistsException, ProductNotActiveException, ProductAlreadyExistsException,
ProductNotFoundException, ProductNotInCatalogException, ProductNotActiveException,
ProductValidationException) ProductNotFoundException,
ProductNotInCatalogException,
ProductValidationException,
)
# Team exceptions # Team exceptions
from .team import (CannotModifyOwnRoleException, CannotRemoveOwnerException, from .team import (
CannotModifyOwnRoleException,
CannotRemoveOwnerException,
InsufficientTeamPermissionsException, InsufficientTeamPermissionsException,
InvalidInvitationDataException, InvalidInvitationDataException,
InvalidInvitationTokenException, InvalidRoleException, InvalidInvitationTokenException,
MaxTeamMembersReachedException, RoleNotFoundException, InvalidRoleException,
MaxTeamMembersReachedException,
RoleNotFoundException,
TeamInvitationAlreadyAcceptedException, TeamInvitationAlreadyAcceptedException,
TeamInvitationExpiredException, TeamInvitationExpiredException,
TeamInvitationNotFoundException, TeamInvitationNotFoundException,
TeamMemberAlreadyExistsException, TeamMemberAlreadyExistsException,
TeamMemberNotFoundException, TeamValidationException, TeamMemberNotFoundException,
UnauthorizedTeamActionException) TeamValidationException,
UnauthorizedTeamActionException,
)
# Vendor exceptions # Vendor exceptions
from .vendor import (InvalidVendorDataException, MaxVendorsReachedException, from .vendor import (
InvalidVendorDataException,
MaxVendorsReachedException,
UnauthorizedVendorAccessException, UnauthorizedVendorAccessException,
VendorAlreadyExistsException, VendorNotActiveException, VendorAlreadyExistsException,
VendorNotFoundException, VendorNotVerifiedException, VendorNotActiveException,
VendorValidationException) VendorNotFoundException,
VendorNotVerifiedException,
VendorValidationException,
)
# Vendor domain exceptions # Vendor domain exceptions
from .vendor_domain import (DNSVerificationException, from .vendor_domain import (
DNSVerificationException,
DomainAlreadyVerifiedException, DomainAlreadyVerifiedException,
DomainNotVerifiedException, DomainNotVerifiedException,
DomainVerificationFailedException, DomainVerificationFailedException,
@@ -100,15 +165,20 @@ from .vendor_domain import (DNSVerificationException,
ReservedDomainException, ReservedDomainException,
UnauthorizedDomainAccessException, UnauthorizedDomainAccessException,
VendorDomainAlreadyExistsException, VendorDomainAlreadyExistsException,
VendorDomainNotFoundException) VendorDomainNotFoundException,
)
# Vendor theme exceptions # Vendor theme exceptions
from .vendor_theme import (InvalidColorFormatException, from .vendor_theme import (
InvalidColorFormatException,
InvalidFontFamilyException, InvalidFontFamilyException,
InvalidThemeDataException, ThemeOperationException, InvalidThemeDataException,
ThemeOperationException,
ThemePresetAlreadyAppliedException, ThemePresetAlreadyAppliedException,
ThemePresetNotFoundException, ThemePresetNotFoundException,
ThemeValidationException, ThemeValidationException,
VendorThemeNotFoundException) VendorThemeNotFoundException,
)
__all__ = [ __all__ = [
# Base exceptions # Base exceptions

View File

@@ -3,10 +3,14 @@
Admin operations specific exceptions. Admin operations specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (AuthorizationException, BusinessLogicException, from .base import (
ResourceNotFoundException, ValidationException) AuthorizationException,
BusinessLogicException,
ResourceNotFoundException,
ValidationException,
)
class UserNotFoundException(ResourceNotFoundException): class UserNotFoundException(ResourceNotFoundException):
@@ -36,7 +40,7 @@ class UserStatusChangeException(BusinessLogicException):
user_id: int, user_id: int,
current_status: str, current_status: str,
attempted_action: str, attempted_action: str,
reason: Optional[str] = None, reason: str | None = None,
): ):
message = f"Cannot {attempted_action} user {user_id} (current status: {current_status})" message = f"Cannot {attempted_action} user {user_id} (current status: {current_status})"
if reason: if reason:
@@ -61,7 +65,7 @@ class ShopVerificationException(BusinessLogicException):
self, self,
shop_id: int, shop_id: int,
reason: str, reason: str,
current_verification_status: Optional[bool] = None, current_verification_status: bool | None = None,
): ):
details = { details = {
"shop_id": shop_id, "shop_id": shop_id,
@@ -85,8 +89,8 @@ class AdminOperationException(BusinessLogicException):
self, self,
operation: str, operation: str,
reason: str, reason: str,
target_type: Optional[str] = None, target_type: str | None = None,
target_id: Optional[str] = None, target_id: str | None = None,
): ):
message = f"Admin operation '{operation}' failed: {reason}" message = f"Admin operation '{operation}' failed: {reason}"
@@ -142,7 +146,7 @@ class InvalidAdminActionException(ValidationException):
self, self,
action: str, action: str,
reason: str, reason: str,
valid_actions: Optional[list] = None, valid_actions: list | None = None,
): ):
details = { details = {
"action": action, "action": action,
@@ -167,7 +171,7 @@ class BulkOperationException(BusinessLogicException):
operation: str, operation: str,
total_items: int, total_items: int,
failed_items: int, failed_items: int,
errors: Optional[Dict[str, Any]] = None, errors: dict[str, Any] | None = None,
): ):
message = f"Bulk {operation} completed with errors: {failed_items}/{total_items} failed" message = f"Bulk {operation} completed with errors: {failed_items}/{total_items} failed"
@@ -194,7 +198,7 @@ class ConfirmationRequiredException(BusinessLogicException):
def __init__( def __init__(
self, self,
operation: str, operation: str,
message: Optional[str] = None, message: str | None = None,
confirmation_param: str = "confirm", confirmation_param: str = "confirm",
): ):
if not message: if not message:
@@ -217,7 +221,7 @@ class VendorVerificationException(BusinessLogicException):
self, self,
vendor_id: int, vendor_id: int,
reason: str, reason: str,
current_verification_status: Optional[bool] = None, current_verification_status: bool | None = None,
): ):
details = { details = {
"vendor_id": vendor_id, "vendor_id": vendor_id,

View File

@@ -3,10 +3,8 @@
Authentication and authorization specific exceptions. Authentication and authorization specific exceptions.
""" """
from typing import Optional
from .base import (AuthenticationException, AuthorizationException, from .base import AuthenticationException, AuthorizationException, ConflictException
ConflictException)
class InvalidCredentialsException(AuthenticationException): class InvalidCredentialsException(AuthenticationException):
@@ -45,7 +43,7 @@ class InsufficientPermissionsException(AuthorizationException):
def __init__( def __init__(
self, self,
message: str = "Insufficient permissions for this action", message: str = "Insufficient permissions for this action",
required_permission: Optional[str] = None, required_permission: str | None = None,
): ):
details = {} details = {}
if required_permission: if required_permission:
@@ -84,7 +82,7 @@ class UserAlreadyExistsException(ConflictException):
def __init__( def __init__(
self, self,
message: str = "User already exists", message: str = "User already exists",
field: Optional[str] = None, field: str | None = None,
): ):
details = {} details = {}
if field: if field:

View File

@@ -8,7 +8,7 @@ This module provides classes and functions for:
- Standardized error response structure - Standardized error response structure
""" """
from typing import Any, Dict, Optional from typing import Any
class WizamartException(Exception): class WizamartException(Exception):
@@ -19,7 +19,7 @@ class WizamartException(Exception):
message: str, message: str,
error_code: str, error_code: str,
status_code: int = 400, status_code: int = 400,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
self.message = message self.message = message
self.error_code = error_code self.error_code = error_code
@@ -27,7 +27,7 @@ class WizamartException(Exception):
self.details = details or {} self.details = details or {}
super().__init__(self.message) super().__init__(self.message)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> dict[str, Any]:
"""Convert exception to dictionary for JSON response.""" """Convert exception to dictionary for JSON response."""
result = { result = {
"error_code": self.error_code, "error_code": self.error_code,
@@ -45,8 +45,8 @@ class ValidationException(WizamartException):
def __init__( def __init__(
self, self,
message: str, message: str,
field: Optional[str] = None, field: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
validation_details = details or {} validation_details = details or {}
if field: if field:
@@ -67,7 +67,7 @@ class AuthenticationException(WizamartException):
self, self,
message: str = "Authentication failed", message: str = "Authentication failed",
error_code: str = "AUTHENTICATION_FAILED", error_code: str = "AUTHENTICATION_FAILED",
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -84,7 +84,7 @@ class AuthorizationException(WizamartException):
self, self,
message: str = "Access denied", message: str = "Access denied",
error_code: str = "AUTHORIZATION_FAILED", error_code: str = "AUTHORIZATION_FAILED",
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -101,8 +101,8 @@ class ResourceNotFoundException(WizamartException):
self, self,
resource_type: str, resource_type: str,
identifier: str, identifier: str,
message: Optional[str] = None, message: str | None = None,
error_code: Optional[str] = None, error_code: str | None = None,
): ):
if not message: if not message:
message = f"{resource_type} with identifier '{identifier}' not found" message = f"{resource_type} with identifier '{identifier}' not found"
@@ -127,7 +127,7 @@ class ConflictException(WizamartException):
self, self,
message: str, message: str,
error_code: str = "RESOURCE_CONFLICT", error_code: str = "RESOURCE_CONFLICT",
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -144,7 +144,7 @@ class BusinessLogicException(WizamartException):
self, self,
message: str, message: str,
error_code: str, error_code: str,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -162,7 +162,7 @@ class ExternalServiceException(WizamartException):
message: str, message: str,
service_name: str, service_name: str,
error_code: str = "EXTERNAL_SERVICE_ERROR", error_code: str = "EXTERNAL_SERVICE_ERROR",
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
service_details = details or {} service_details = details or {}
service_details["service_name"] = service_name service_details["service_name"] = service_name
@@ -181,8 +181,8 @@ class RateLimitException(WizamartException):
def __init__( def __init__(
self, self,
message: str = "Rate limit exceeded", message: str = "Rate limit exceeded",
retry_after: Optional[int] = None, retry_after: int | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
rate_limit_details = details or {} rate_limit_details = details or {}
if retry_after: if retry_after:

View File

@@ -3,10 +3,8 @@
Shopping cart specific exceptions. Shopping cart specific exceptions.
""" """
from typing import Optional
from .base import (BusinessLogicException, ResourceNotFoundException, from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
ValidationException)
class CartItemNotFoundException(ResourceNotFoundException): class CartItemNotFoundException(ResourceNotFoundException):
@@ -36,8 +34,8 @@ class CartValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Cart validation failed", message: str = "Cart validation failed",
field: Optional[str] = None, field: str | None = None,
details: Optional[dict] = None, details: dict | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -75,7 +73,7 @@ class InvalidCartQuantityException(ValidationException):
"""Raised when cart quantity is invalid.""" """Raised when cart quantity is invalid."""
def __init__( def __init__(
self, quantity: int, min_quantity: int = 1, max_quantity: Optional[int] = None self, quantity: int, min_quantity: int = 1, max_quantity: int | None = None
): ):
if quantity < min_quantity: if quantity < min_quantity:
message = f"Quantity must be at least {min_quantity}" message = f"Quantity must be at least {min_quantity}"

View File

@@ -3,11 +3,15 @@
Customer management specific exceptions. Customer management specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (AuthenticationException, BusinessLogicException, from .base import (
ConflictException, ResourceNotFoundException, AuthenticationException,
ValidationException) BusinessLogicException,
ConflictException,
ResourceNotFoundException,
ValidationException,
)
class CustomerNotFoundException(ResourceNotFoundException): class CustomerNotFoundException(ResourceNotFoundException):
@@ -71,8 +75,8 @@ class CustomerValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Customer validation failed", message: str = "Customer validation failed",
field: Optional[str] = None, field: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__(message=message, field=field, details=details) super().__init__(message=message, field=field, details=details)
self.error_code = "CUSTOMER_VALIDATION_FAILED" self.error_code = "CUSTOMER_VALIDATION_FAILED"

View File

@@ -5,9 +5,10 @@ Error Page Renderer
Renders context-aware error pages using Jinja2 templates. Renders context-aware error pages using Jinja2 templates.
Handles fallback logic and context-specific customization. Handles fallback logic and context-specific customization.
""" """
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import Any
from fastapi import Request from fastapi import Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
@@ -60,7 +61,7 @@ class ErrorPageRenderer:
status_code: int, status_code: int,
error_code: str, error_code: str,
message: str, message: str,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
show_debug: bool = False, show_debug: bool = False,
) -> HTMLResponse: ) -> HTMLResponse:
""" """
@@ -190,9 +191,9 @@ class ErrorPageRenderer:
status_code: int, status_code: int,
error_code: str, error_code: str,
message: str, message: str,
details: Optional[Dict[str, Any]], details: dict[str, Any] | None,
show_debug: bool, show_debug: bool,
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Prepare data dictionary for error template.""" """Prepare data dictionary for error template."""
# Get friendly status name # Get friendly status name
status_name = ErrorPageRenderer.STATUS_CODE_NAMES.get( status_name = ErrorPageRenderer.STATUS_CODE_NAMES.get(
@@ -229,7 +230,7 @@ class ErrorPageRenderer:
@staticmethod @staticmethod
def _get_context_data( def _get_context_data(
request: Request, context_type: RequestContext request: Request, context_type: RequestContext
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Get context-specific data for error templates.""" """Get context-specific data for error templates."""
data = {} data = {}

View File

@@ -11,7 +11,6 @@ This module provides classes and functions for:
""" """
import logging import logging
from typing import Union
from fastapi import HTTPException, Request from fastapi import HTTPException, Request
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
@@ -280,7 +279,7 @@ def setup_exception_handlers(app):
request=request, request=request,
status_code=404, status_code=404,
error_code="ENDPOINT_NOT_FOUND", error_code="ENDPOINT_NOT_FOUND",
message=f"The page you're looking for doesn't exist or has been moved.", message="The page you're looking for doesn't exist or has been moved.",
details={"path": request.url.path}, details={"path": request.url.path},
show_debug=True, show_debug=True,
) )
@@ -364,10 +363,10 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
if context_type == RequestContext.ADMIN: if context_type == RequestContext.ADMIN:
logger.debug("Redirecting to /admin/login") logger.debug("Redirecting to /admin/login")
return RedirectResponse(url="/admin/login", status_code=302) return RedirectResponse(url="/admin/login", status_code=302)
elif context_type == RequestContext.VENDOR_DASHBOARD: if context_type == RequestContext.VENDOR_DASHBOARD:
logger.debug("Redirecting to /vendor/login") logger.debug("Redirecting to /vendor/login")
return RedirectResponse(url="/vendor/login", status_code=302) return RedirectResponse(url="/vendor/login", status_code=302)
elif context_type == RequestContext.SHOP: if context_type == RequestContext.SHOP:
# For shop context, redirect to shop login (customer login) # For shop context, redirect to shop login (customer login)
# Calculate base_url for proper routing (supports domain, subdomain, and path-based access) # Calculate base_url for proper routing (supports domain, subdomain, and path-based access)
vendor = getattr(request.state, "vendor", None) vendor = getattr(request.state, "vendor", None)
@@ -390,7 +389,6 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
login_url = f"{base_url}shop/account/login" login_url = f"{base_url}shop/account/login"
logger.debug(f"Redirecting to {login_url}") logger.debug(f"Redirecting to {login_url}")
return RedirectResponse(url=login_url, status_code=302) return RedirectResponse(url=login_url, status_code=302)
else:
# Fallback to root for unknown contexts # Fallback to root for unknown contexts
logger.debug("Unknown context, redirecting to /") logger.debug("Unknown context, redirecting to /")
return RedirectResponse(url="/", status_code=302) return RedirectResponse(url="/", status_code=302)

View File

@@ -3,10 +3,9 @@
Inventory management specific exceptions. Inventory management specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (BusinessLogicException, ResourceNotFoundException, from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
ValidationException)
class InventoryNotFoundException(ResourceNotFoundException): class InventoryNotFoundException(ResourceNotFoundException):
@@ -58,8 +57,8 @@ class InvalidInventoryOperationException(ValidationException):
def __init__( def __init__(
self, self,
message: str, message: str,
operation: Optional[str] = None, operation: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
if not details: if not details:
details = {} details = {}
@@ -80,8 +79,8 @@ class InventoryValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Inventory validation failed", message: str = "Inventory validation failed",
field: Optional[str] = None, field: str | None = None,
validation_errors: Optional[Dict[str, str]] = None, validation_errors: dict[str, str] | None = None,
): ):
details = {} details = {}
if validation_errors: if validation_errors:

View File

@@ -3,11 +3,15 @@
Marketplace import specific exceptions. Marketplace import specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (AuthorizationException, BusinessLogicException, from .base import (
ExternalServiceException, ResourceNotFoundException, AuthorizationException,
ValidationException) BusinessLogicException,
ExternalServiceException,
ResourceNotFoundException,
ValidationException,
)
class MarketplaceImportException(BusinessLogicException): class MarketplaceImportException(BusinessLogicException):
@@ -17,8 +21,8 @@ class MarketplaceImportException(BusinessLogicException):
self, self,
message: str, message: str,
error_code: str = "MARKETPLACE_IMPORT_ERROR", error_code: str = "MARKETPLACE_IMPORT_ERROR",
marketplace: Optional[str] = None, marketplace: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
if not details: if not details:
details = {} details = {}
@@ -48,7 +52,7 @@ class ImportJobNotFoundException(ResourceNotFoundException):
class ImportJobNotOwnedException(AuthorizationException): class ImportJobNotOwnedException(AuthorizationException):
"""Raised when user tries to access import job they don't own.""" """Raised when user tries to access import job they don't own."""
def __init__(self, job_id: int, user_id: Optional[int] = None): def __init__(self, job_id: int, user_id: int | None = None):
details = {"job_id": job_id} details = {"job_id": job_id}
if user_id: if user_id:
details["user_id"] = user_id details["user_id"] = user_id
@@ -66,9 +70,9 @@ class InvalidImportDataException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid import data", message: str = "Invalid import data",
field: Optional[str] = None, field: str | None = None,
row_number: Optional[int] = None, row_number: int | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
if not details: if not details:
details = {} details = {}
@@ -132,7 +136,7 @@ class MarketplaceDataParsingException(ValidationException):
self, self,
marketplace: str, marketplace: str,
message: str = "Failed to parse marketplace data", message: str = "Failed to parse marketplace data",
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
if not details: if not details:
details = {} details = {}
@@ -152,7 +156,7 @@ class ImportRateLimitException(BusinessLogicException):
self, self,
max_imports: int, max_imports: int,
time_window: str, time_window: str,
retry_after: Optional[int] = None, retry_after: int | None = None,
): ):
details = { details = {
"max_imports": max_imports, "max_imports": max_imports,
@@ -172,7 +176,7 @@ class ImportRateLimitException(BusinessLogicException):
class InvalidMarketplaceException(ValidationException): class InvalidMarketplaceException(ValidationException):
"""Raised when marketplace is not supported.""" """Raised when marketplace is not supported."""
def __init__(self, marketplace: str, supported_marketplaces: Optional[list] = None): def __init__(self, marketplace: str, supported_marketplaces: list | None = None):
details = {"marketplace": marketplace} details = {"marketplace": marketplace}
if supported_marketplaces: if supported_marketplaces:
details["supported_marketplaces"] = supported_marketplaces details["supported_marketplaces"] = supported_marketplaces

View File

@@ -3,10 +3,14 @@
MarketplaceProduct management specific exceptions. MarketplaceProduct management specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (BusinessLogicException, ConflictException, from .base import (
ResourceNotFoundException, ValidationException) BusinessLogicException,
ConflictException,
ResourceNotFoundException,
ValidationException,
)
class MarketplaceProductNotFoundException(ResourceNotFoundException): class MarketplaceProductNotFoundException(ResourceNotFoundException):
@@ -38,8 +42,8 @@ class InvalidMarketplaceProductDataException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid product data", message: str = "Invalid product data",
field: Optional[str] = None, field: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -55,8 +59,8 @@ class MarketplaceProductValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str, message: str,
field: Optional[str] = None, field: str | None = None,
validation_errors: Optional[Dict[str, str]] = None, validation_errors: dict[str, str] | None = None,
): ):
details = {} details = {}
if validation_errors: if validation_errors:
@@ -88,8 +92,8 @@ class MarketplaceProductCSVImportException(BusinessLogicException):
def __init__( def __init__(
self, self,
message: str = "MarketplaceProduct CSV import failed", message: str = "MarketplaceProduct CSV import failed",
row_number: Optional[int] = None, row_number: int | None = None,
errors: Optional[Dict[str, Any]] = None, errors: dict[str, Any] | None = None,
): ):
details = {} details = {}
if row_number: if row_number:

View File

@@ -3,10 +3,8 @@
Order management specific exceptions. Order management specific exceptions.
""" """
from typing import Optional
from .base import (BusinessLogicException, ResourceNotFoundException, from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
ValidationException)
class OrderNotFoundException(ResourceNotFoundException): class OrderNotFoundException(ResourceNotFoundException):
@@ -35,7 +33,7 @@ class OrderAlreadyExistsException(ValidationException):
class OrderValidationException(ValidationException): class OrderValidationException(ValidationException):
"""Raised when order data validation fails.""" """Raised when order data validation fails."""
def __init__(self, message: str, details: Optional[dict] = None): def __init__(self, message: str, details: dict | None = None):
super().__init__( super().__init__(
message=message, error_code="ORDER_VALIDATION_FAILED", details=details message=message, error_code="ORDER_VALIDATION_FAILED", details=details
) )

View File

@@ -3,16 +3,19 @@
Product (vendor catalog) specific exceptions. Product (vendor catalog) specific exceptions.
""" """
from typing import Optional
from .base import (BusinessLogicException, ConflictException, from .base import (
ResourceNotFoundException, ValidationException) BusinessLogicException,
ConflictException,
ResourceNotFoundException,
ValidationException,
)
class ProductNotFoundException(ResourceNotFoundException): class ProductNotFoundException(ResourceNotFoundException):
"""Raised when a product is not found in vendor catalog.""" """Raised when a product is not found in vendor catalog."""
def __init__(self, product_id: int, vendor_id: Optional[int] = None): def __init__(self, product_id: int, vendor_id: int | None = None):
details = {"product_id": product_id} details = {"product_id": product_id}
if vendor_id: if vendor_id:
details["vendor_id"] = vendor_id details["vendor_id"] = vendor_id
@@ -79,8 +82,8 @@ class InvalidProductDataException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid product data", message: str = "Invalid product data",
field: Optional[str] = None, field: str | None = None,
details: Optional[dict] = None, details: dict | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -96,8 +99,8 @@ class ProductValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Product validation failed", message: str = "Product validation failed",
field: Optional[str] = None, field: str | None = None,
validation_errors: Optional[dict] = None, validation_errors: dict | None = None,
): ):
details = {} details = {}
if validation_errors: if validation_errors:

View File

@@ -3,17 +3,21 @@
Team management specific exceptions. Team management specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (AuthorizationException, BusinessLogicException, from .base import (
ConflictException, ResourceNotFoundException, AuthorizationException,
ValidationException) BusinessLogicException,
ConflictException,
ResourceNotFoundException,
ValidationException,
)
class TeamMemberNotFoundException(ResourceNotFoundException): class TeamMemberNotFoundException(ResourceNotFoundException):
"""Raised when a team member is not found.""" """Raised when a team member is not found."""
def __init__(self, user_id: int, vendor_id: Optional[int] = None): def __init__(self, user_id: int, vendor_id: int | None = None):
details = {"user_id": user_id} details = {"user_id": user_id}
if vendor_id: if vendor_id:
details["vendor_id"] = vendor_id details["vendor_id"] = vendor_id
@@ -63,7 +67,7 @@ class TeamInvitationExpiredException(BusinessLogicException):
def __init__(self, invitation_token: str): def __init__(self, invitation_token: str):
super().__init__( super().__init__(
message=f"Team invitation has expired", message="Team invitation has expired",
error_code="TEAM_INVITATION_EXPIRED", error_code="TEAM_INVITATION_EXPIRED",
details={"invitation_token": invitation_token}, details={"invitation_token": invitation_token},
) )
@@ -86,8 +90,8 @@ class UnauthorizedTeamActionException(AuthorizationException):
def __init__( def __init__(
self, self,
action: str, action: str,
user_id: Optional[int] = None, user_id: int | None = None,
required_permission: Optional[str] = None, required_permission: str | None = None,
): ):
details = {"action": action} details = {"action": action}
if user_id: if user_id:
@@ -130,7 +134,7 @@ class CannotModifyOwnRoleException(BusinessLogicException):
class RoleNotFoundException(ResourceNotFoundException): class RoleNotFoundException(ResourceNotFoundException):
"""Raised when a role is not found.""" """Raised when a role is not found."""
def __init__(self, role_id: int, vendor_id: Optional[int] = None): def __init__(self, role_id: int, vendor_id: int | None = None):
details = {"role_id": role_id} details = {"role_id": role_id}
if vendor_id: if vendor_id:
details["vendor_id"] = vendor_id details["vendor_id"] = vendor_id
@@ -153,8 +157,8 @@ class InvalidRoleException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid role data", message: str = "Invalid role data",
field: Optional[str] = None, field: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -170,8 +174,8 @@ class InsufficientTeamPermissionsException(AuthorizationException):
def __init__( def __init__(
self, self,
required_permission: str, required_permission: str,
user_id: Optional[int] = None, user_id: int | None = None,
action: Optional[str] = None, action: str | None = None,
): ):
details = {"required_permission": required_permission} details = {"required_permission": required_permission}
if user_id: if user_id:
@@ -208,8 +212,8 @@ class TeamValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Team operation validation failed", message: str = "Team operation validation failed",
field: Optional[str] = None, field: str | None = None,
validation_errors: Optional[Dict[str, str]] = None, validation_errors: dict[str, str] | None = None,
): ):
details = {} details = {}
if validation_errors: if validation_errors:
@@ -229,8 +233,8 @@ class InvalidInvitationDataException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid invitation data", message: str = "Invalid invitation data",
field: Optional[str] = None, field: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -255,7 +259,7 @@ class InvalidInvitationTokenException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid or expired invitation token", message: str = "Invalid or expired invitation token",
invitation_token: Optional[str] = None, invitation_token: str | None = None,
): ):
details = {} details = {}
if invitation_token: if invitation_token:

View File

@@ -3,11 +3,15 @@
Vendor management specific exceptions. Vendor management specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (AuthorizationException, BusinessLogicException, from .base import (
ConflictException, ResourceNotFoundException, AuthorizationException,
ValidationException) BusinessLogicException,
ConflictException,
ResourceNotFoundException,
ValidationException,
)
class VendorNotFoundException(ResourceNotFoundException): class VendorNotFoundException(ResourceNotFoundException):
@@ -63,7 +67,7 @@ class VendorNotVerifiedException(BusinessLogicException):
class UnauthorizedVendorAccessException(AuthorizationException): class UnauthorizedVendorAccessException(AuthorizationException):
"""Raised when user tries to access vendor they don't own.""" """Raised when user tries to access vendor they don't own."""
def __init__(self, vendor_code: str, user_id: Optional[int] = None): def __init__(self, vendor_code: str, user_id: int | None = None):
details = {"vendor_code": vendor_code} details = {"vendor_code": vendor_code}
if user_id: if user_id:
details["user_id"] = user_id details["user_id"] = user_id
@@ -81,8 +85,8 @@ class InvalidVendorDataException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid vendor data", message: str = "Invalid vendor data",
field: Optional[str] = None, field: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -98,8 +102,8 @@ class VendorValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Vendor validation failed", message: str = "Vendor validation failed",
field: Optional[str] = None, field: str | None = None,
validation_errors: Optional[Dict[str, str]] = None, validation_errors: dict[str, str] | None = None,
): ):
details = {} details = {}
if validation_errors: if validation_errors:
@@ -134,7 +138,7 @@ class IncompleteVendorDataException(ValidationException):
class MaxVendorsReachedException(BusinessLogicException): class MaxVendorsReachedException(BusinessLogicException):
"""Raised when user tries to create more vendors than allowed.""" """Raised when user tries to create more vendors than allowed."""
def __init__(self, max_vendors: int, user_id: Optional[int] = None): def __init__(self, max_vendors: int, user_id: int | None = None):
details = {"max_vendors": max_vendors} details = {"max_vendors": max_vendors}
if user_id: if user_id:
details["user_id"] = user_id details["user_id"] = user_id

View File

@@ -3,11 +3,14 @@
Vendor domain management specific exceptions. Vendor domain management specific exceptions.
""" """
from typing import Any, Dict, Optional
from .base import (BusinessLogicException, ConflictException, from .base import (
ExternalServiceException, ResourceNotFoundException, BusinessLogicException,
ValidationException) ConflictException,
ExternalServiceException,
ResourceNotFoundException,
ValidationException,
)
class VendorDomainNotFoundException(ResourceNotFoundException): class VendorDomainNotFoundException(ResourceNotFoundException):
@@ -30,7 +33,7 @@ class VendorDomainNotFoundException(ResourceNotFoundException):
class VendorDomainAlreadyExistsException(ConflictException): class VendorDomainAlreadyExistsException(ConflictException):
"""Raised when trying to add a domain that already exists.""" """Raised when trying to add a domain that already exists."""
def __init__(self, domain: str, existing_vendor_id: Optional[int] = None): def __init__(self, domain: str, existing_vendor_id: int | None = None):
details = {"domain": domain} details = {"domain": domain}
if existing_vendor_id: if existing_vendor_id:
details["existing_vendor_id"] = existing_vendor_id details["existing_vendor_id"] = existing_vendor_id
@@ -104,7 +107,7 @@ class MultiplePrimaryDomainsException(BusinessLogicException):
def __init__(self, vendor_id: int): def __init__(self, vendor_id: int):
super().__init__( super().__init__(
message=f"Vendor can only have one primary domain", message="Vendor can only have one primary domain",
error_code="MULTIPLE_PRIMARY_DOMAINS", error_code="MULTIPLE_PRIMARY_DOMAINS",
details={"vendor_id": vendor_id}, details={"vendor_id": vendor_id},
) )

View File

@@ -3,10 +3,13 @@
Vendor theme management specific exceptions. Vendor theme management specific exceptions.
""" """
from typing import Any, Dict, Optional from typing import Any
from .base import (BusinessLogicException, ConflictException, from .base import (
ResourceNotFoundException, ValidationException) BusinessLogicException,
ResourceNotFoundException,
ValidationException,
)
class VendorThemeNotFoundException(ResourceNotFoundException): class VendorThemeNotFoundException(ResourceNotFoundException):
@@ -27,8 +30,8 @@ class InvalidThemeDataException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Invalid theme data", message: str = "Invalid theme data",
field: Optional[str] = None, field: str | None = None,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
): ):
super().__init__( super().__init__(
message=message, message=message,
@@ -41,7 +44,7 @@ class InvalidThemeDataException(ValidationException):
class ThemePresetNotFoundException(ResourceNotFoundException): class ThemePresetNotFoundException(ResourceNotFoundException):
"""Raised when a theme preset is not found.""" """Raised when a theme preset is not found."""
def __init__(self, preset_name: str, available_presets: Optional[list] = None): def __init__(self, preset_name: str, available_presets: list | None = None):
details = {"preset_name": preset_name} details = {"preset_name": preset_name}
if available_presets: if available_presets:
details["available_presets"] = available_presets details["available_presets"] = available_presets
@@ -61,8 +64,8 @@ class ThemeValidationException(ValidationException):
def __init__( def __init__(
self, self,
message: str = "Theme validation failed", message: str = "Theme validation failed",
field: Optional[str] = None, field: str | None = None,
validation_errors: Optional[Dict[str, str]] = None, validation_errors: dict[str, str] | None = None,
): ):
details = {} details = {}
if validation_errors: if validation_errors:

View File

@@ -3,8 +3,17 @@ Architecture Scan Models
Database models for tracking code quality scans and violations Database models for tracking code quality scans and violations
""" """
from sqlalchemy import (JSON, Boolean, Column, DateTime, Float, ForeignKey, from sqlalchemy import (
Integer, String, Text) JSON,
Boolean,
Column,
DateTime,
Float,
ForeignKey,
Integer,
String,
Text,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.sql import func from sqlalchemy.sql import func

View File

@@ -30,15 +30,17 @@ Routes:
- GET /code-quality/violations/{violation_id} → Violation details (auth required) - GET /code-quality/violations/{violation_id} → Violation details (auth required)
""" """
from typing import Optional
from fastapi import APIRouter, Depends, Path, Request from fastapi import APIRouter, Depends, Path, Request
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.api.deps import (get_current_admin_from_cookie_or_header, from app.api.deps import (
get_current_admin_optional, get_db) get_current_admin_from_cookie_or_header,
get_current_admin_optional,
get_db,
)
from models.database.user import User from models.database.user import User
router = APIRouter() router = APIRouter()
@@ -52,7 +54,7 @@ templates = Jinja2Templates(directory="app/templates")
@router.get("/", response_class=RedirectResponse, include_in_schema=False) @router.get("/", response_class=RedirectResponse, include_in_schema=False)
async def admin_root( async def admin_root(
current_user: Optional[User] = Depends(get_current_admin_optional), current_user: User | None = Depends(get_current_admin_optional),
): ):
""" """
Redirect /admin/ based on authentication status. Redirect /admin/ based on authentication status.
@@ -69,7 +71,7 @@ async def admin_root(
@router.get("/login", response_class=HTMLResponse, include_in_schema=False) @router.get("/login", response_class=HTMLResponse, include_in_schema=False)
async def admin_login_page( async def admin_login_page(
request: Request, current_user: Optional[User] = Depends(get_current_admin_optional) request: Request, current_user: User | None = Depends(get_current_admin_optional)
): ):
""" """
Render admin login page. Render admin login page.

View File

@@ -129,7 +129,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
) )
except Exception as e: except Exception as e:
logger.error( logger.error(
f"[SHOP_CONTEXT] Failed to load navigation pages", "[SHOP_CONTEXT] Failed to load navigation pages",
extra={"error": str(e), "vendor_id": vendor.id if vendor else None}, extra={"error": str(e), "vendor_id": vendor.id if vendor else None},
) )
@@ -149,7 +149,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
context.update(extra_context) context.update(extra_context)
logger.debug( logger.debug(
f"[SHOP_CONTEXT] Context built", "[SHOP_CONTEXT] Context built",
extra={ extra={
"vendor_id": vendor.id if vendor else None, "vendor_id": vendor.id if vendor else None,
"vendor_name": vendor.name if vendor else None, "vendor_name": vendor.name if vendor else None,
@@ -179,7 +179,7 @@ async def shop_products_page(request: Request, db: Session = Depends(get_db)):
Shows featured products and categories. Shows featured products and categories.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -203,7 +203,7 @@ async def shop_product_detail_page(
Shows product information, images, reviews, and buy options. Shows product information, images, reviews, and buy options.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -227,7 +227,7 @@ async def shop_category_page(
Shows all products in a specific category. Shows all products in a specific category.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -247,7 +247,7 @@ async def shop_cart_page(request: Request):
Shows cart items and allows quantity updates. Shows cart items and allows quantity updates.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -265,7 +265,7 @@ async def shop_checkout_page(request: Request):
Handles shipping, payment, and order confirmation. Handles shipping, payment, and order confirmation.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -283,7 +283,7 @@ async def shop_search_page(request: Request):
Shows products matching search query. Shows products matching search query.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -306,7 +306,7 @@ async def shop_register_page(request: Request):
No authentication required. No authentication required.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -326,7 +326,7 @@ async def shop_login_page(request: Request):
No authentication required. No authentication required.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -348,7 +348,7 @@ async def shop_forgot_password_page(request: Request):
Allows customers to reset their password. Allows customers to reset their password.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -373,7 +373,7 @@ async def shop_account_root(request: Request):
Redirect /shop/account or /shop/account/ to dashboard. Redirect /shop/account or /shop/account/ to dashboard.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -414,7 +414,7 @@ async def shop_account_dashboard_page(
Requires customer authentication. Requires customer authentication.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -439,7 +439,7 @@ async def shop_orders_page(
Requires customer authentication. Requires customer authentication.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -467,7 +467,7 @@ async def shop_order_detail_page(
Requires customer authentication. Requires customer authentication.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -493,7 +493,7 @@ async def shop_profile_page(
Requires customer authentication. Requires customer authentication.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -518,7 +518,7 @@ async def shop_addresses_page(
Requires customer authentication. Requires customer authentication.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -543,7 +543,7 @@ async def shop_wishlist_page(
Requires customer authentication. Requires customer authentication.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -568,7 +568,7 @@ async def shop_settings_page(
Requires customer authentication. Requires customer authentication.
""" """
logger.debug( logger.debug(
f"[SHOP_HANDLER] shop_products_page REACHED", "[SHOP_HANDLER] shop_products_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor": getattr(request.state, "vendor", "NOT SET"), "vendor": getattr(request.state, "vendor", "NOT SET"),
@@ -609,7 +609,7 @@ async def generic_content_page(
from fastapi import HTTPException from fastapi import HTTPException
logger.debug( logger.debug(
f"[SHOP_HANDLER] generic_content_page REACHED", "[SHOP_HANDLER] generic_content_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"slug": slug, "slug": slug,
@@ -628,7 +628,7 @@ async def generic_content_page(
if not page: if not page:
logger.warning( logger.warning(
f"[SHOP_HANDLER] Content page not found", "[SHOP_HANDLER] Content page not found",
extra={ extra={
"slug": slug, "slug": slug,
"vendor_id": vendor_id, "vendor_id": vendor_id,
@@ -638,7 +638,7 @@ async def generic_content_page(
raise HTTPException(status_code=404, detail=f"Page not found: {slug}") raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
logger.info( logger.info(
f"[SHOP_HANDLER] Content page found", "[SHOP_HANDLER] Content page found",
extra={ extra={
"slug": slug, "slug": slug,
"page_id": page.id, "page_id": page.id,
@@ -709,14 +709,14 @@ async def debug_context(request: Request):
<pre>{json.dumps(debug_info, indent=2)}</pre> <pre>{json.dumps(debug_info, indent=2)}</pre>
<h2>Status</h2> <h2>Status</h2>
<p class="{'good' if vendor else 'bad'}"> <p class="{"good" if vendor else "bad"}">
Vendor: {'✓ Found' if vendor else '✗ Not Found'} Vendor: {"✓ Found" if vendor else "✗ Not Found"}
</p> </p>
<p class="{'good' if theme else 'bad'}"> <p class="{"good" if theme else "bad"}">
Theme: {'✓ Found' if theme else '✗ Not Found'} Theme: {"✓ Found" if theme else "✗ Not Found"}
</p> </p>
<p class="{'good' if str(getattr(request.state, 'context_type', 'NOT SET')) == 'shop' else 'bad'}"> <p class="{"good" if str(getattr(request.state, "context_type", "NOT SET")) == "shop" else "bad"}">
Context Type: {str(getattr(request.state, 'context_type', 'NOT SET'))} Context Type: {str(getattr(request.state, "context_type", "NOT SET"))}
</p> </p>
</body> </body>
</html> </html>

View File

@@ -22,15 +22,17 @@ Routes:
""" """
import logging import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Path, Request from fastapi import APIRouter, Depends, HTTPException, Path, Request
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.api.deps import (get_current_vendor_from_cookie_or_header, from app.api.deps import (
get_current_vendor_optional, get_db) get_current_vendor_from_cookie_or_header,
get_current_vendor_optional,
get_db,
)
from app.services.content_page_service import content_page_service from app.services.content_page_service import content_page_service
from models.database.user import User from models.database.user import User
@@ -57,7 +59,7 @@ async def vendor_root_no_slash(vendor_code: str = Path(..., description="Vendor
@router.get("/{vendor_code}/", response_class=RedirectResponse, include_in_schema=False) @router.get("/{vendor_code}/", response_class=RedirectResponse, include_in_schema=False)
async def vendor_root( async def vendor_root(
vendor_code: str = Path(..., description="Vendor code"), vendor_code: str = Path(..., description="Vendor code"),
current_user: Optional[User] = Depends(get_current_vendor_optional), current_user: User | None = Depends(get_current_vendor_optional),
): ):
""" """
Redirect /vendor/{code}/ based on authentication status. Redirect /vendor/{code}/ based on authentication status.
@@ -78,7 +80,7 @@ async def vendor_root(
async def vendor_login_page( async def vendor_login_page(
request: Request, request: Request,
vendor_code: str = Path(..., description="Vendor code"), vendor_code: str = Path(..., description="Vendor code"),
current_user: Optional[User] = Depends(get_current_vendor_optional), current_user: User | None = Depends(get_current_vendor_optional),
): ):
""" """
Render vendor login page. Render vendor login page.
@@ -374,7 +376,7 @@ async def vendor_content_page(
shadowing other specific routes. shadowing other specific routes.
""" """
logger.debug( logger.debug(
f"[VENDOR_HANDLER] vendor_content_page REACHED", "[VENDOR_HANDLER] vendor_content_page REACHED",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"vendor_code": vendor_code, "vendor_code": vendor_code,

View File

@@ -9,10 +9,9 @@ This module provides functions for:
""" """
import logging import logging
from datetime import datetime, timezone from typing import Any
from typing import Any, Dict, List, Optional
from sqlalchemy import and_, or_ from sqlalchemy import and_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import AdminOperationException from app.exceptions import AdminOperationException
@@ -33,10 +32,10 @@ class AdminAuditService:
action: str, action: str,
target_type: str, target_type: str,
target_id: str, target_id: str,
details: Optional[Dict[str, Any]] = None, details: dict[str, Any] | None = None,
ip_address: Optional[str] = None, ip_address: str | None = None,
user_agent: Optional[str] = None, user_agent: str | None = None,
request_id: Optional[str] = None, request_id: str | None = None,
) -> AdminAuditLog: ) -> AdminAuditLog:
""" """
Log an admin action to the audit trail. Log an admin action to the audit trail.
@@ -85,7 +84,7 @@ class AdminAuditService:
def get_audit_logs( def get_audit_logs(
self, db: Session, filters: AdminAuditLogFilters self, db: Session, filters: AdminAuditLogFilters
) -> List[AdminAuditLogResponse]: ) -> list[AdminAuditLogResponse]:
""" """
Get filtered admin audit logs with pagination. Get filtered admin audit logs with pagination.
@@ -187,14 +186,14 @@ class AdminAuditService:
def get_recent_actions_by_admin( def get_recent_actions_by_admin(
self, db: Session, admin_user_id: int, limit: int = 10 self, db: Session, admin_user_id: int, limit: int = 10
) -> List[AdminAuditLogResponse]: ) -> list[AdminAuditLogResponse]:
"""Get recent actions by a specific admin.""" """Get recent actions by a specific admin."""
filters = AdminAuditLogFilters(admin_user_id=admin_user_id, limit=limit) filters = AdminAuditLogFilters(admin_user_id=admin_user_id, limit=limit)
return self.get_audit_logs(db, filters) return self.get_audit_logs(db, filters)
def get_actions_by_target( def get_actions_by_target(
self, db: Session, target_type: str, target_id: str, limit: int = 50 self, db: Session, target_type: str, target_id: str, limit: int = 50
) -> List[AdminAuditLogResponse]: ) -> list[AdminAuditLogResponse]:
"""Get all actions performed on a specific target.""" """Get all actions performed on a specific target."""
try: try:
logs = ( logs = (

View File

@@ -13,17 +13,21 @@ This module provides classes and functions for:
import logging import logging
import secrets import secrets
import string import string
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import List, Optional, Tuple
from sqlalchemy import func, or_ from sqlalchemy import func, or_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (AdminOperationException, CannotModifySelfException, from app.exceptions import (
UserNotFoundException, UserStatusChangeException, AdminOperationException,
ValidationException, VendorAlreadyExistsException, CannotModifySelfException,
UserNotFoundException,
UserStatusChangeException,
ValidationException,
VendorAlreadyExistsException,
VendorNotFoundException, VendorNotFoundException,
VendorVerificationException) VendorVerificationException,
)
from models.database.marketplace_import_job import MarketplaceImportJob from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.user import User from models.database.user import User
from models.database.vendor import Role, Vendor, VendorUser from models.database.vendor import Role, Vendor, VendorUser
@@ -40,7 +44,7 @@ class AdminService:
# USER MANAGEMENT # USER MANAGEMENT
# ============================================================================ # ============================================================================
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> List[User]: def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> list[User]:
"""Get paginated list of all users.""" """Get paginated list of all users."""
try: try:
return db.query(User).offset(skip).limit(limit).all() return db.query(User).offset(skip).limit(limit).all()
@@ -52,7 +56,7 @@ class AdminService:
def toggle_user_status( def toggle_user_status(
self, db: Session, user_id: int, current_admin_id: int self, db: Session, user_id: int, current_admin_id: int
) -> Tuple[User, str]: ) -> tuple[User, str]:
"""Toggle user active status.""" """Toggle user active status."""
user = self._get_user_by_id_or_raise(db, user_id) user = self._get_user_by_id_or_raise(db, user_id)
@@ -72,7 +76,7 @@ class AdminService:
try: try:
original_status = user.is_active original_status = user.is_active
user.is_active = not user.is_active user.is_active = not user.is_active
user.updated_at = datetime.now(timezone.utc) user.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(user) db.refresh(user)
@@ -98,7 +102,7 @@ class AdminService:
def create_vendor_with_owner( def create_vendor_with_owner(
self, db: Session, vendor_data: VendorCreate self, db: Session, vendor_data: VendorCreate
) -> Tuple[Vendor, User, str]: ) -> tuple[Vendor, User, str]:
""" """
Create vendor with owner user account. Create vendor with owner user account.
@@ -222,10 +226,10 @@ class AdminService:
db: Session, db: Session,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
search: Optional[str] = None, search: str | None = None,
is_active: Optional[bool] = None, is_active: bool | None = None,
is_verified: Optional[bool] = None, is_verified: bool | None = None,
) -> Tuple[List[Vendor], int]: ) -> tuple[list[Vendor], int]:
"""Get paginated list of all vendors with filtering.""" """Get paginated list of all vendors with filtering."""
try: try:
query = db.query(Vendor) query = db.query(Vendor)
@@ -261,17 +265,17 @@ class AdminService:
"""Get vendor by ID.""" """Get vendor by ID."""
return self._get_vendor_by_id_or_raise(db, vendor_id) return self._get_vendor_by_id_or_raise(db, vendor_id)
def verify_vendor(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]: def verify_vendor(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
"""Toggle vendor verification status.""" """Toggle vendor verification status."""
vendor = self._get_vendor_by_id_or_raise(db, vendor_id) vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
try: try:
original_status = vendor.is_verified original_status = vendor.is_verified
vendor.is_verified = not vendor.is_verified vendor.is_verified = not vendor.is_verified
vendor.updated_at = datetime.now(timezone.utc) vendor.updated_at = datetime.now(UTC)
if vendor.is_verified: if vendor.is_verified:
vendor.verified_at = datetime.now(timezone.utc) vendor.verified_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(vendor) db.refresh(vendor)
@@ -291,14 +295,14 @@ class AdminService:
current_verification_status=original_status, current_verification_status=original_status,
) )
def toggle_vendor_status(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]: def toggle_vendor_status(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
"""Toggle vendor active status.""" """Toggle vendor active status."""
vendor = self._get_vendor_by_id_or_raise(db, vendor_id) vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
try: try:
original_status = vendor.is_active original_status = vendor.is_active
vendor.is_active = not vendor.is_active vendor.is_active = not vendor.is_active
vendor.updated_at = datetime.now(timezone.utc) vendor.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(vendor) db.refresh(vendor)
@@ -347,7 +351,10 @@ class AdminService:
) )
def update_vendor( def update_vendor(
self, db: Session, vendor_id: int, vendor_update # VendorUpdate schema self,
db: Session,
vendor_id: int,
vendor_update, # VendorUpdate schema
) -> Vendor: ) -> Vendor:
""" """
Update vendor information (Admin only). Update vendor information (Admin only).
@@ -402,7 +409,7 @@ class AdminService:
for field, value in update_data.items(): for field, value in update_data.items():
setattr(vendor, field, value) setattr(vendor, field, value)
vendor.updated_at = datetime.now(timezone.utc) vendor.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(vendor) db.refresh(vendor)
@@ -430,7 +437,7 @@ class AdminService:
db: Session, db: Session,
vendor_id: int, vendor_id: int,
transfer_data, # VendorTransferOwnership schema transfer_data, # VendorTransferOwnership schema
) -> Tuple[Vendor, User, User]: ) -> tuple[Vendor, User, User]:
""" """
Transfer vendor ownership to another user. Transfer vendor ownership to another user.
@@ -556,7 +563,7 @@ class AdminService:
# Update vendor owner_user_id # Update vendor owner_user_id
vendor.owner_user_id = new_owner.id vendor.owner_user_id = new_owner.id
vendor.updated_at = datetime.now(timezone.utc) vendor.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(vendor) db.refresh(vendor)
@@ -593,12 +600,12 @@ class AdminService:
def get_marketplace_import_jobs( def get_marketplace_import_jobs(
self, self,
db: Session, db: Session,
marketplace: Optional[str] = None, marketplace: str | None = None,
vendor_name: Optional[str] = None, vendor_name: str | None = None,
status: Optional[str] = None, status: str | None = None,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
) -> List[MarketplaceImportJobResponse]: ) -> list[MarketplaceImportJobResponse]:
"""Get filtered and paginated marketplace import jobs.""" """Get filtered and paginated marketplace import jobs."""
try: try:
query = db.query(MarketplaceImportJob) query = db.query(MarketplaceImportJob)
@@ -633,7 +640,7 @@ class AdminService:
# STATISTICS # STATISTICS
# ============================================================================ # ============================================================================
def get_recent_vendors(self, db: Session, limit: int = 5) -> List[dict]: def get_recent_vendors(self, db: Session, limit: int = 5) -> list[dict]:
"""Get recently created vendors.""" """Get recently created vendors."""
try: try:
vendors = ( vendors = (
@@ -656,7 +663,7 @@ class AdminService:
logger.error(f"Failed to get recent vendors: {str(e)}") logger.error(f"Failed to get recent vendors: {str(e)}")
return [] return []
def get_recent_import_jobs(self, db: Session, limit: int = 10) -> List[dict]: def get_recent_import_jobs(self, db: Session, limit: int = 10) -> list[dict]:
"""Get recent marketplace import jobs.""" """Get recent marketplace import jobs."""
try: try:
jobs = ( jobs = (

View File

@@ -10,17 +10,23 @@ This module provides functions for:
import json import json
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import Any, Dict, List, Optional from typing import Any
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (AdminOperationException, ResourceNotFoundException, from app.exceptions import (
ValidationException) AdminOperationException,
ResourceNotFoundException,
ValidationException,
)
from models.database.admin import AdminSetting from models.database.admin import AdminSetting
from models.schema.admin import (AdminSettingCreate, AdminSettingResponse, from models.schema.admin import (
AdminSettingUpdate) AdminSettingCreate,
AdminSettingResponse,
AdminSettingUpdate,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,7 +34,7 @@ logger = logging.getLogger(__name__)
class AdminSettingsService: class AdminSettingsService:
"""Service for managing platform-wide settings.""" """Service for managing platform-wide settings."""
def get_setting_by_key(self, db: Session, key: str) -> Optional[AdminSetting]: def get_setting_by_key(self, db: Session, key: str) -> AdminSetting | None:
"""Get setting by key.""" """Get setting by key."""
try: try:
return ( return (
@@ -60,13 +66,12 @@ class AdminSettingsService:
try: try:
if setting.value_type == "integer": if setting.value_type == "integer":
return int(setting.value) return int(setting.value)
elif setting.value_type == "float": if setting.value_type == "float":
return float(setting.value) return float(setting.value)
elif setting.value_type == "boolean": if setting.value_type == "boolean":
return setting.value.lower() in ("true", "1", "yes") return setting.value.lower() in ("true", "1", "yes")
elif setting.value_type == "json": if setting.value_type == "json":
return json.loads(setting.value) return json.loads(setting.value)
else:
return setting.value return setting.value
except Exception as e: except Exception as e:
logger.error(f"Failed to convert setting {key} value: {str(e)}") logger.error(f"Failed to convert setting {key} value: {str(e)}")
@@ -75,9 +80,9 @@ class AdminSettingsService:
def get_all_settings( def get_all_settings(
self, self,
db: Session, db: Session,
category: Optional[str] = None, category: str | None = None,
is_public: Optional[bool] = None, is_public: bool | None = None,
) -> List[AdminSettingResponse]: ) -> list[AdminSettingResponse]:
"""Get all settings with optional filtering.""" """Get all settings with optional filtering."""
try: try:
query = db.query(AdminSetting) query = db.query(AdminSetting)
@@ -100,7 +105,7 @@ class AdminSettingsService:
operation="get_all_settings", reason="Database query failed" operation="get_all_settings", reason="Database query failed"
) )
def get_settings_by_category(self, db: Session, category: str) -> Dict[str, Any]: def get_settings_by_category(self, db: Session, category: str) -> dict[str, Any]:
""" """
Get all settings in a category as a dictionary. Get all settings in a category as a dictionary.
@@ -198,7 +203,7 @@ class AdminSettingsService:
if update_data.description is not None: if update_data.description is not None:
setting.description = update_data.description setting.description = update_data.description
setting.last_modified_by_user_id = admin_user_id setting.last_modified_by_user_id = admin_user_id
setting.updated_at = datetime.now(timezone.utc) setting.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(setting) db.refresh(setting)
@@ -228,7 +233,6 @@ class AdminSettingsService:
value=setting_data.value, description=setting_data.description value=setting_data.value, description=setting_data.description
) )
return self.update_setting(db, setting_data.key, update_data, admin_user_id) return self.update_setting(db, setting_data.key, update_data, admin_user_id)
else:
return self.create_setting(db, setting_data, admin_user_id) return self.create_setting(db, setting_data, admin_user_id)
def delete_setting(self, db: Session, key: str, admin_user_id: int) -> str: def delete_setting(self, db: Session, key: str, admin_user_id: int) -> str:

View File

@@ -9,13 +9,17 @@ This module provides classes and functions for:
""" """
import logging import logging
from typing import Any, Dict, Optional from datetime import UTC
from typing import Any
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (InvalidCredentialsException, from app.exceptions import (
UserAlreadyExistsException, UserNotActiveException, InvalidCredentialsException,
ValidationException) UserAlreadyExistsException,
UserNotActiveException,
ValidationException,
)
from middleware.auth import AuthManager from middleware.auth import AuthManager
from models.database.user import User from models.database.user import User
from models.schema.auth import UserLogin, UserRegister from models.schema.auth import UserLogin, UserRegister
@@ -82,7 +86,7 @@ class AuthService:
logger.error(f"Error registering user: {str(e)}") logger.error(f"Error registering user: {str(e)}")
raise ValidationException("Registration failed") raise ValidationException("Registration failed")
def login_user(self, db: Session, user_credentials: UserLogin) -> Dict[str, Any]: def login_user(self, db: Session, user_credentials: UserLogin) -> dict[str, Any]:
""" """
Login user and return JWT token with user data. Login user and return JWT token with user data.
@@ -120,7 +124,7 @@ class AuthService:
logger.error(f"Error during login: {str(e)}") logger.error(f"Error during login: {str(e)}")
raise InvalidCredentialsException() raise InvalidCredentialsException()
def get_user_by_email(self, db: Session, email: str) -> Optional[User]: def get_user_by_email(self, db: Session, email: str) -> User | None:
"""Get user by email.""" """Get user by email."""
try: try:
return db.query(User).filter(User.email == email).first() return db.query(User).filter(User.email == email).first()
@@ -128,7 +132,7 @@ class AuthService:
logger.error(f"Error getting user by email: {str(e)}") logger.error(f"Error getting user by email: {str(e)}")
return None return None
def get_user_by_username(self, db: Session, username: str) -> Optional[User]: def get_user_by_username(self, db: Session, username: str) -> User | None:
"""Get user by username.""" """Get user by username."""
try: try:
return db.query(User).filter(User.username == username).first() return db.query(User).filter(User.username == username).first()
@@ -138,7 +142,7 @@ class AuthService:
def authenticate_user( def authenticate_user(
self, db: Session, username: str, password: str self, db: Session, username: str, password: str
) -> Optional[User]: ) -> User | None:
"""Authenticate user with username/password.""" """Authenticate user with username/password."""
try: try:
return self.auth_manager.authenticate_user(db, username, password) return self.auth_manager.authenticate_user(db, username, password)
@@ -146,7 +150,7 @@ class AuthService:
logger.error(f"Error authenticating user: {str(e)}") logger.error(f"Error authenticating user: {str(e)}")
return None return None
def create_access_token(self, user: User) -> Dict[str, Any]: def create_access_token(self, user: User) -> dict[str, Any]:
"""Create access token for user.""" """Create access token for user."""
try: try:
return self.auth_manager.create_access_token(user) return self.auth_manager.create_access_token(user)
@@ -182,7 +186,7 @@ class AuthService:
Returns: Returns:
Dictionary with access_token, token_type, and expires_in Dictionary with access_token, token_type, and expires_in
""" """
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta
from jose import jwt from jose import jwt
@@ -190,13 +194,13 @@ class AuthService:
try: try:
expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
expire = datetime.now(timezone.utc) + expires_delta expire = datetime.now(UTC) + expires_delta
# Build payload with provided data # Build payload with provided data
payload = { payload = {
**data, **data,
"exp": expire, "exp": expire,
"iat": datetime.now(timezone.utc), "iat": datetime.now(UTC),
} }
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256") token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")

View File

@@ -9,20 +9,18 @@ This module provides:
""" """
import logging import logging
from datetime import datetime, timezone
from typing import Dict, List, Optional
from sqlalchemy import and_ from sqlalchemy import and_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (CartItemNotFoundException, CartValidationException, from app.exceptions import (
CartItemNotFoundException,
InsufficientInventoryForCartException, InsufficientInventoryForCartException,
InvalidCartQuantityException, InvalidCartQuantityException,
ProductNotAvailableForCartException, ProductNotFoundException,
ProductNotFoundException) )
from models.database.cart import CartItem from models.database.cart import CartItem
from models.database.product import Product from models.database.product import Product
from models.database.vendor import Vendor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -30,7 +28,7 @@ logger = logging.getLogger(__name__)
class CartService: class CartService:
"""Service for managing shopping carts.""" """Service for managing shopping carts."""
def get_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict: def get_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
""" """
Get cart contents for a session. Get cart contents for a session.
@@ -43,7 +41,7 @@ class CartService:
Cart data with items and totals Cart data with items and totals
""" """
logger.info( logger.info(
f"[CART_SERVICE] get_cart called", "[CART_SERVICE] get_cart called",
extra={ extra={
"vendor_id": vendor_id, "vendor_id": vendor_id,
"session_id": session_id, "session_id": session_id,
@@ -111,7 +109,7 @@ class CartService:
session_id: str, session_id: str,
product_id: int, product_id: int,
quantity: int = 1, quantity: int = 1,
) -> Dict: ) -> dict:
""" """
Add product to cart. Add product to cart.
@@ -130,7 +128,7 @@ class CartService:
InsufficientInventoryException: If not enough inventory InsufficientInventoryException: If not enough inventory
""" """
logger.info( logger.info(
f"[CART_SERVICE] add_to_cart called", "[CART_SERVICE] add_to_cart called",
extra={ extra={
"vendor_id": vendor_id, "vendor_id": vendor_id,
"session_id": session_id, "session_id": session_id,
@@ -154,7 +152,7 @@ class CartService:
if not product: if not product:
logger.error( logger.error(
f"[CART_SERVICE] Product not found", "[CART_SERVICE] Product not found",
extra={"product_id": product_id, "vendor_id": vendor_id}, extra={"product_id": product_id, "vendor_id": vendor_id},
) )
raise ProductNotFoundException(product_id=product_id, vendor_id=vendor_id) raise ProductNotFoundException(product_id=product_id, vendor_id=vendor_id)
@@ -191,7 +189,7 @@ class CartService:
# Check inventory for new total quantity # Check inventory for new total quantity
if product.available_inventory < new_quantity: if product.available_inventory < new_quantity:
logger.warning( logger.warning(
f"[CART_SERVICE] Insufficient inventory for update", "[CART_SERVICE] Insufficient inventory for update",
extra={ extra={
"product_id": product_id, "product_id": product_id,
"current_in_cart": existing_item.quantity, "current_in_cart": existing_item.quantity,
@@ -212,7 +210,7 @@ class CartService:
db.refresh(existing_item) db.refresh(existing_item)
logger.info( logger.info(
f"[CART_SERVICE] Updated existing cart item", "[CART_SERVICE] Updated existing cart item",
extra={"cart_item_id": existing_item.id, "new_quantity": new_quantity}, extra={"cart_item_id": existing_item.id, "new_quantity": new_quantity},
) )
@@ -221,11 +219,10 @@ class CartService:
"product_id": product_id, "product_id": product_id,
"quantity": new_quantity, "quantity": new_quantity,
} }
else:
# Check inventory for new item # Check inventory for new item
if product.available_inventory < quantity: if product.available_inventory < quantity:
logger.warning( logger.warning(
f"[CART_SERVICE] Insufficient inventory", "[CART_SERVICE] Insufficient inventory",
extra={ extra={
"product_id": product_id, "product_id": product_id,
"requested": quantity, "requested": quantity,
@@ -252,7 +249,7 @@ class CartService:
db.refresh(cart_item) db.refresh(cart_item)
logger.info( logger.info(
f"[CART_SERVICE] Created new cart item", "[CART_SERVICE] Created new cart item",
extra={ extra={
"cart_item_id": cart_item.id, "cart_item_id": cart_item.id,
"quantity": quantity, "quantity": quantity,
@@ -273,7 +270,7 @@ class CartService:
session_id: str, session_id: str,
product_id: int, product_id: int,
quantity: int, quantity: int,
) -> Dict: ) -> dict:
""" """
Update quantity of item in cart. Update quantity of item in cart.
@@ -344,7 +341,7 @@ class CartService:
db.refresh(cart_item) db.refresh(cart_item)
logger.info( logger.info(
f"[CART_SERVICE] Updated cart item quantity", "[CART_SERVICE] Updated cart item quantity",
extra={ extra={
"cart_item_id": cart_item.id, "cart_item_id": cart_item.id,
"product_id": product_id, "product_id": product_id,
@@ -360,7 +357,7 @@ class CartService:
def remove_from_cart( def remove_from_cart(
self, db: Session, vendor_id: int, session_id: str, product_id: int self, db: Session, vendor_id: int, session_id: str, product_id: int
) -> Dict: ) -> dict:
""" """
Remove item from cart. Remove item from cart.
@@ -398,7 +395,7 @@ class CartService:
db.commit() db.commit()
logger.info( logger.info(
f"[CART_SERVICE] Removed item from cart", "[CART_SERVICE] Removed item from cart",
extra={ extra={
"cart_item_id": cart_item.id, "cart_item_id": cart_item.id,
"product_id": product_id, "product_id": product_id,
@@ -408,7 +405,7 @@ class CartService:
return {"message": "Item removed from cart", "product_id": product_id} return {"message": "Item removed from cart", "product_id": product_id}
def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict: def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
""" """
Clear all items from cart. Clear all items from cart.
@@ -432,7 +429,7 @@ class CartService:
db.commit() db.commit()
logger.info( logger.info(
f"[CART_SERVICE] Cleared cart", "[CART_SERVICE] Cleared cart",
extra={ extra={
"session_id": session_id, "session_id": session_id,
"vendor_id": vendor_id, "vendor_id": vendor_id,

View File

@@ -7,16 +7,16 @@ import json
import logging import logging
import subprocess import subprocess
from datetime import datetime from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from sqlalchemy import desc, func from sqlalchemy import desc, func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.models.architecture_scan import (ArchitectureRule, ArchitectureScan, from app.models.architecture_scan import (
ArchitectureScan,
ArchitectureViolation, ArchitectureViolation,
ViolationAssignment, ViolationAssignment,
ViolationComment) ViolationComment,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -118,7 +118,7 @@ class CodeQualityService:
logger.info(f"Scan completed: {scan.total_violations} violations found") logger.info(f"Scan completed: {scan.total_violations} violations found")
return scan return scan
def get_latest_scan(self, db: Session) -> Optional[ArchitectureScan]: def get_latest_scan(self, db: Session) -> ArchitectureScan | None:
"""Get the most recent scan""" """Get the most recent scan"""
return ( return (
db.query(ArchitectureScan) db.query(ArchitectureScan)
@@ -126,11 +126,11 @@ class CodeQualityService:
.first() .first()
) )
def get_scan_by_id(self, db: Session, scan_id: int) -> Optional[ArchitectureScan]: def get_scan_by_id(self, db: Session, scan_id: int) -> ArchitectureScan | None:
"""Get scan by ID""" """Get scan by ID"""
return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first() return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first()
def get_scan_history(self, db: Session, limit: int = 30) -> List[ArchitectureScan]: def get_scan_history(self, db: Session, limit: int = 30) -> list[ArchitectureScan]:
""" """
Get scan history for trend graphs Get scan history for trend graphs
@@ -158,7 +158,7 @@ class CodeQualityService:
file_path: str = None, file_path: str = None,
limit: int = 100, limit: int = 100,
offset: int = 0, offset: int = 0,
) -> Tuple[List[ArchitectureViolation], int]: ) -> tuple[list[ArchitectureViolation], int]:
""" """
Get violations with filtering and pagination Get violations with filtering and pagination
@@ -217,7 +217,7 @@ class CodeQualityService:
def get_violation_by_id( def get_violation_by_id(
self, db: Session, violation_id: int self, db: Session, violation_id: int
) -> Optional[ArchitectureViolation]: ) -> ArchitectureViolation | None:
"""Get single violation with details""" """Get single violation with details"""
return ( return (
db.query(ArchitectureViolation) db.query(ArchitectureViolation)
@@ -348,7 +348,7 @@ class CodeQualityService:
logger.info(f"Comment added to violation {violation_id} by user {user_id}") logger.info(f"Comment added to violation {violation_id} by user {user_id}")
return comment_obj return comment_obj
def get_dashboard_stats(self, db: Session) -> Dict: def get_dashboard_stats(self, db: Session) -> dict:
""" """
Get statistics for dashboard Get statistics for dashboard
@@ -507,7 +507,7 @@ class CodeQualityService:
score = 100 - (scan.errors * 0.5 + scan.warnings * 0.05) score = 100 - (scan.errors * 0.5 + scan.warnings * 0.05)
return max(0, min(100, int(score))) # Clamp to 0-100 return max(0, min(100, int(score))) # Clamp to 0-100
def _get_git_commit_hash(self) -> Optional[str]: def _get_git_commit_hash(self) -> str | None:
"""Get current git commit hash""" """Get current git commit hash"""
try: try:
result = subprocess.run( result = subprocess.run(

View File

@@ -17,10 +17,9 @@ This allows:
""" """
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import List, Optional
from sqlalchemy import and_, or_ from sqlalchemy import and_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from models.database.content_page import ContentPage from models.database.content_page import ContentPage
@@ -35,9 +34,9 @@ class ContentPageService:
def get_page_for_vendor( def get_page_for_vendor(
db: Session, db: Session,
slug: str, slug: str,
vendor_id: Optional[int] = None, vendor_id: int | None = None,
include_unpublished: bool = False, include_unpublished: bool = False,
) -> Optional[ContentPage]: ) -> ContentPage | None:
""" """
Get content page for a vendor with fallback to platform default. Get content page for a vendor with fallback to platform default.
@@ -90,11 +89,11 @@ class ContentPageService:
@staticmethod @staticmethod
def list_pages_for_vendor( def list_pages_for_vendor(
db: Session, db: Session,
vendor_id: Optional[int] = None, vendor_id: int | None = None,
include_unpublished: bool = False, include_unpublished: bool = False,
footer_only: bool = False, footer_only: bool = False,
header_only: bool = False, header_only: bool = False,
) -> List[ContentPage]: ) -> list[ContentPage]:
""" """
List all available pages for a vendor (includes vendor overrides + platform defaults). List all available pages for a vendor (includes vendor overrides + platform defaults).
@@ -156,16 +155,16 @@ class ContentPageService:
slug: str, slug: str,
title: str, title: str,
content: str, content: str,
vendor_id: Optional[int] = None, vendor_id: int | None = None,
content_format: str = "html", content_format: str = "html",
template: str = "default", template: str = "default",
meta_description: Optional[str] = None, meta_description: str | None = None,
meta_keywords: Optional[str] = None, meta_keywords: str | None = None,
is_published: bool = False, is_published: bool = False,
show_in_footer: bool = True, show_in_footer: bool = True,
show_in_header: bool = False, show_in_header: bool = False,
display_order: int = 0, display_order: int = 0,
created_by: Optional[int] = None, created_by: int | None = None,
) -> ContentPage: ) -> ContentPage:
""" """
Create a new content page. Create a new content page.
@@ -199,7 +198,7 @@ class ContentPageService:
meta_description=meta_description, meta_description=meta_description,
meta_keywords=meta_keywords, meta_keywords=meta_keywords,
is_published=is_published, is_published=is_published,
published_at=datetime.now(timezone.utc) if is_published else None, published_at=datetime.now(UTC) if is_published else None,
show_in_footer=show_in_footer, show_in_footer=show_in_footer,
show_in_header=show_in_header, show_in_header=show_in_header,
display_order=display_order, display_order=display_order,
@@ -220,18 +219,18 @@ class ContentPageService:
def update_page( def update_page(
db: Session, db: Session,
page_id: int, page_id: int,
title: Optional[str] = None, title: str | None = None,
content: Optional[str] = None, content: str | None = None,
content_format: Optional[str] = None, content_format: str | None = None,
template: Optional[str] = None, template: str | None = None,
meta_description: Optional[str] = None, meta_description: str | None = None,
meta_keywords: Optional[str] = None, meta_keywords: str | None = None,
is_published: Optional[bool] = None, is_published: bool | None = None,
show_in_footer: Optional[bool] = None, show_in_footer: bool | None = None,
show_in_header: Optional[bool] = None, show_in_header: bool | None = None,
display_order: Optional[int] = None, display_order: int | None = None,
updated_by: Optional[int] = None, updated_by: int | None = None,
) -> Optional[ContentPage]: ) -> ContentPage | None:
""" """
Update an existing content page. Update an existing content page.
@@ -275,7 +274,7 @@ class ContentPageService:
if is_published is not None: if is_published is not None:
page.is_published = is_published page.is_published = is_published
if is_published and not page.published_at: if is_published and not page.published_at:
page.published_at = datetime.now(timezone.utc) page.published_at = datetime.now(UTC)
if show_in_footer is not None: if show_in_footer is not None:
page.show_in_footer = show_in_footer page.show_in_footer = show_in_footer
if show_in_header is not None: if show_in_header is not None:
@@ -316,14 +315,14 @@ class ContentPageService:
return True return True
@staticmethod @staticmethod
def get_page_by_id(db: Session, page_id: int) -> Optional[ContentPage]: def get_page_by_id(db: Session, page_id: int) -> ContentPage | None:
"""Get content page by ID.""" """Get content page by ID."""
return db.query(ContentPage).filter(ContentPage.id == page_id).first() return db.query(ContentPage).filter(ContentPage.id == page_id).first()
@staticmethod @staticmethod
def list_all_vendor_pages( def list_all_vendor_pages(
db: Session, vendor_id: int, include_unpublished: bool = False db: Session, vendor_id: int, include_unpublished: bool = False
) -> List[ContentPage]: ) -> list[ContentPage]:
""" """
List only vendor-specific pages (no platform defaults). List only vendor-specific pages (no platform defaults).
@@ -350,7 +349,7 @@ class ContentPageService:
@staticmethod @staticmethod
def list_all_platform_pages( def list_all_platform_pages(
db: Session, include_unpublished: bool = False db: Session, include_unpublished: bool = False
) -> List[ContentPage]: ) -> list[ContentPage]:
""" """
List only platform default pages. List only platform default pages.

View File

@@ -7,22 +7,22 @@ with complete vendor isolation.
""" """
import logging import logging
from datetime import datetime, timedelta from datetime import UTC, datetime, timedelta
from typing import Any, Dict, Optional from typing import Any
from sqlalchemy import and_ from sqlalchemy import and_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions.customer import (CustomerAlreadyExistsException, from app.exceptions.customer import (
CustomerNotActiveException, CustomerNotActiveException,
CustomerNotFoundException, CustomerNotFoundException,
CustomerValidationException, CustomerValidationException,
DuplicateCustomerEmailException, DuplicateCustomerEmailException,
InvalidCustomerCredentialsException) InvalidCustomerCredentialsException,
from app.exceptions.vendor import (VendorNotActiveException, )
VendorNotFoundException) from app.exceptions.vendor import VendorNotActiveException, VendorNotFoundException
from app.services.auth_service import AuthService from app.services.auth_service import AuthService
from models.database.customer import Customer, CustomerAddress from models.database.customer import Customer
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.auth import UserLogin from models.schema.auth import UserLogin
from models.schema.customer import CustomerRegister, CustomerUpdate from models.schema.customer import CustomerRegister, CustomerUpdate
@@ -128,7 +128,7 @@ class CustomerService:
def login_customer( def login_customer(
self, db: Session, vendor_id: int, credentials: UserLogin self, db: Session, vendor_id: int, credentials: UserLogin
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Authenticate customer and generate JWT token. Authenticate customer and generate JWT token.
@@ -177,13 +177,13 @@ class CustomerService:
# Generate JWT token with customer context # Generate JWT token with customer context
# Use auth_manager directly since Customer is not a User model # Use auth_manager directly since Customer is not a User model
from datetime import datetime, timedelta, timezone from datetime import datetime
from jose import jwt from jose import jwt
auth_manager = self.auth_service.auth_manager auth_manager = self.auth_service.auth_manager
expires_delta = timedelta(minutes=auth_manager.token_expire_minutes) expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
expire = datetime.now(timezone.utc) + expires_delta expire = datetime.now(UTC) + expires_delta
payload = { payload = {
"sub": str(customer.id), "sub": str(customer.id),
@@ -191,7 +191,7 @@ class CustomerService:
"vendor_id": vendor_id, "vendor_id": vendor_id,
"type": "customer", "type": "customer",
"exp": expire, "exp": expire,
"iat": datetime.now(timezone.utc), "iat": datetime.now(UTC),
} }
token = jwt.encode( token = jwt.encode(
@@ -239,7 +239,7 @@ class CustomerService:
def get_customer_by_email( def get_customer_by_email(
self, db: Session, vendor_id: int, email: str self, db: Session, vendor_id: int, email: str
) -> Optional[Customer]: ) -> Customer | None:
""" """
Get customer by email (vendor-scoped). Get customer by email (vendor-scoped).

View File

@@ -1,24 +1,27 @@
# app/services/inventory_service.py # app/services/inventory_service.py
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import List, Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (InsufficientInventoryException, from app.exceptions import (
InvalidInventoryOperationException, InsufficientInventoryException,
InvalidQuantityException, InvalidQuantityException,
InventoryNotFoundException, InventoryNotFoundException,
InventoryValidationException, InventoryValidationException,
NegativeInventoryException, ProductNotFoundException,
ProductNotFoundException, ValidationException) ValidationException,
)
from models.database.inventory import Inventory from models.database.inventory import Inventory
from models.database.product import Product from models.database.product import Product
from models.database.vendor import Vendor from models.schema.inventory import (
from models.schema.inventory import (InventoryAdjust, InventoryCreate, InventoryAdjust,
InventoryCreate,
InventoryLocationResponse, InventoryLocationResponse,
InventoryReserve, InventoryUpdate, InventoryReserve,
ProductInventorySummary) InventoryUpdate,
ProductInventorySummary,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -58,7 +61,7 @@ class InventoryService:
if existing: if existing:
old_qty = existing.quantity old_qty = existing.quantity
existing.quantity = inventory_data.quantity existing.quantity = inventory_data.quantity
existing.updated_at = datetime.now(timezone.utc) existing.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(existing) db.refresh(existing)
@@ -67,7 +70,6 @@ class InventoryService:
f"{old_qty}{inventory_data.quantity}" f"{old_qty}{inventory_data.quantity}"
) )
return existing return existing
else:
# Create new inventory entry # Create new inventory entry
new_inventory = Inventory( new_inventory = Inventory(
product_id=inventory_data.product_id, product_id=inventory_data.product_id,
@@ -162,7 +164,7 @@ class InventoryService:
) )
existing.quantity = new_qty existing.quantity = new_qty
existing.updated_at = datetime.now(timezone.utc) existing.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(existing) db.refresh(existing)
@@ -224,7 +226,7 @@ class InventoryService:
# Reserve inventory # Reserve inventory
inventory.reserved_quantity += reserve_data.quantity inventory.reserved_quantity += reserve_data.quantity
inventory.updated_at = datetime.now(timezone.utc) inventory.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(inventory) db.refresh(inventory)
@@ -284,7 +286,7 @@ class InventoryService:
else: else:
inventory.reserved_quantity -= reserve_data.quantity inventory.reserved_quantity -= reserve_data.quantity
inventory.updated_at = datetime.now(timezone.utc) inventory.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(inventory) db.refresh(inventory)
@@ -350,7 +352,7 @@ class InventoryService:
inventory.reserved_quantity = max( inventory.reserved_quantity = max(
0, inventory.reserved_quantity - reserve_data.quantity 0, inventory.reserved_quantity - reserve_data.quantity
) )
inventory.updated_at = datetime.now(timezone.utc) inventory.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(inventory) db.refresh(inventory)
@@ -443,9 +445,9 @@ class InventoryService:
vendor_id: int, vendor_id: int,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
location: Optional[str] = None, location: str | None = None,
low_stock_threshold: Optional[int] = None, low_stock_threshold: int | None = None,
) -> List[Inventory]: ) -> list[Inventory]:
""" """
Get all inventory for a vendor with filtering. Get all inventory for a vendor with filtering.
@@ -504,7 +506,7 @@ class InventoryService:
if inventory_update.location: if inventory_update.location:
inventory.location = self._validate_location(inventory_update.location) inventory.location = self._validate_location(inventory_update.location)
inventory.updated_at = datetime.now(timezone.utc) inventory.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(inventory) db.refresh(inventory)
@@ -565,7 +567,7 @@ class InventoryService:
def _get_inventory_entry( def _get_inventory_entry(
self, db: Session, product_id: int, location: str self, db: Session, product_id: int, location: str
) -> Optional[Inventory]: ) -> Inventory | None:
"""Get inventory entry by product and location.""" """Get inventory entry by product and location."""
return ( return (
db.query(Inventory) db.query(Inventory)

View File

@@ -1,19 +1,20 @@
# app/services/marketplace_import_job_service.py # app/services/marketplace_import_job_service.py
import logging import logging
from datetime import datetime, timezone
from typing import List, Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (ImportJobCannotBeCancelledException, from app.exceptions import (
ImportJobCannotBeDeletedException,
ImportJobNotFoundException, ImportJobNotFoundException,
ImportJobNotOwnedException, ValidationException) ImportJobNotOwnedException,
ValidationException,
)
from models.database.marketplace_import_job import MarketplaceImportJob from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest, from models.schema.marketplace_import_job import (
MarketplaceImportJobResponse) MarketplaceImportJobRequest,
MarketplaceImportJobResponse,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -98,10 +99,10 @@ class MarketplaceImportJobService:
db: Session, db: Session,
vendor: Vendor, # ADDED: Vendor filter vendor: Vendor, # ADDED: Vendor filter
user: User, user: User,
marketplace: Optional[str] = None, marketplace: str | None = None,
skip: int = 0, skip: int = 0,
limit: int = 50, limit: int = 50,
) -> List[MarketplaceImportJob]: ) -> list[MarketplaceImportJob]:
"""Get marketplace import jobs for a specific vendor.""" """Get marketplace import jobs for a specific vendor."""
try: try:
query = db.query(MarketplaceImportJob).filter( query = db.query(MarketplaceImportJob).filter(

View File

@@ -8,29 +8,31 @@ This module provides classes and functions for:
- Inventory information integration - Inventory information integration
- CSV export functionality - CSV export functionality
""" """
import csv import csv
import logging import logging
from datetime import datetime, timezone from collections.abc import Generator
from datetime import UTC, datetime
from io import StringIO from io import StringIO
from typing import Generator, List, Optional, Tuple
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (InvalidMarketplaceProductDataException, from app.exceptions import (
InvalidMarketplaceProductDataException,
MarketplaceProductAlreadyExistsException, MarketplaceProductAlreadyExistsException,
MarketplaceProductNotFoundException, MarketplaceProductNotFoundException,
MarketplaceProductValidationException, MarketplaceProductValidationException,
ValidationException) ValidationException,
from app.services.marketplace_import_job_service import \ )
marketplace_import_job_service
from app.utils.data_processing import GTINProcessor, PriceProcessor from app.utils.data_processing import GTINProcessor, PriceProcessor
from models.database.inventory import Inventory from models.database.inventory import Inventory
from models.database.marketplace_product import MarketplaceProduct from models.database.marketplace_product import MarketplaceProduct
from models.schema.inventory import (InventoryLocationResponse, from models.schema.inventory import InventoryLocationResponse, InventorySummaryResponse
InventorySummaryResponse) from models.schema.marketplace_product import (
from models.schema.marketplace_product import (MarketplaceProductCreate, MarketplaceProductCreate,
MarketplaceProductUpdate) MarketplaceProductUpdate,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -109,7 +111,6 @@ class MarketplaceProductService:
raise MarketplaceProductAlreadyExistsException( raise MarketplaceProductAlreadyExistsException(
product_data.marketplace_product_id product_data.marketplace_product_id
) )
else:
raise MarketplaceProductValidationException( raise MarketplaceProductValidationException(
"Data integrity constraint violation" "Data integrity constraint violation"
) )
@@ -120,7 +121,7 @@ class MarketplaceProductService:
def get_product_by_id( def get_product_by_id(
self, db: Session, marketplace_product_id: str self, db: Session, marketplace_product_id: str
) -> Optional[MarketplaceProduct]: ) -> MarketplaceProduct | None:
"""Get a product by its ID.""" """Get a product by its ID."""
try: try:
return ( return (
@@ -160,13 +161,13 @@ class MarketplaceProductService:
db: Session, db: Session,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
brand: Optional[str] = None, brand: str | None = None,
category: Optional[str] = None, category: str | None = None,
availability: Optional[str] = None, availability: str | None = None,
marketplace: Optional[str] = None, marketplace: str | None = None,
vendor_name: Optional[str] = None, vendor_name: str | None = None,
search: Optional[str] = None, search: str | None = None,
) -> Tuple[List[MarketplaceProduct], int]: ) -> tuple[list[MarketplaceProduct], int]:
""" """
Get products with filtering and pagination. Get products with filtering and pagination.
@@ -269,7 +270,7 @@ class MarketplaceProductService:
for key, value in update_data.items(): for key, value in update_data.items():
setattr(product, key, value) setattr(product, key, value)
product.updated_at = datetime.now(timezone.utc) product.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(product) db.refresh(product)
@@ -324,7 +325,7 @@ class MarketplaceProductService:
def get_inventory_info( def get_inventory_info(
self, db: Session, gtin: str self, db: Session, gtin: str
) -> Optional[InventorySummaryResponse]: ) -> InventorySummaryResponse | None:
""" """
Get inventory information for a product by GTIN. Get inventory information for a product by GTIN.
@@ -358,15 +359,14 @@ class MarketplaceProductService:
import csv import csv
from io import StringIO from io import StringIO
from typing import Generator, Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
def generate_csv_export( def generate_csv_export(
self, self,
db: Session, db: Session,
marketplace: Optional[str] = None, marketplace: str | None = None,
vendor_name: Optional[str] = None, vendor_name: str | None = None,
) -> Generator[str, None, None]: ) -> Generator[str, None, None]:
""" """
Generate CSV export with streaming for memory efficiency and proper CSV escaping. Generate CSV export with streaming for memory efficiency and proper CSV escaping.

View File

@@ -11,15 +11,17 @@ This module provides:
import logging import logging
import random import random
import string import string
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import List, Optional, Tuple
from sqlalchemy import and_, or_ from sqlalchemy import and_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (CustomerNotFoundException, from app.exceptions import (
CustomerNotFoundException,
InsufficientInventoryException, InsufficientInventoryException,
OrderNotFoundException, ValidationException) OrderNotFoundException,
ValidationException,
)
from models.database.customer import Customer, CustomerAddress from models.database.customer import Customer, CustomerAddress
from models.database.order import Order, OrderItem from models.database.order import Order, OrderItem
from models.database.product import Product from models.database.product import Product
@@ -38,7 +40,7 @@ class OrderService:
Format: ORD-{VENDOR_ID}-{TIMESTAMP}-{RANDOM} Format: ORD-{VENDOR_ID}-{TIMESTAMP}-{RANDOM}
Example: ORD-1-20250110-A1B2C3 Example: ORD-1-20250110-A1B2C3
""" """
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d") timestamp = datetime.now(UTC).strftime("%Y%m%d")
random_suffix = "".join( random_suffix = "".join(
random.choices(string.ascii_uppercase + string.digits, k=6) random.choices(string.ascii_uppercase + string.digits, k=6)
) )
@@ -266,9 +268,9 @@ class OrderService:
vendor_id: int, vendor_id: int,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
status: Optional[str] = None, status: str | None = None,
customer_id: Optional[int] = None, customer_id: int | None = None,
) -> Tuple[List[Order], int]: ) -> tuple[list[Order], int]:
""" """
Get orders for vendor with filtering. Get orders for vendor with filtering.
@@ -306,7 +308,7 @@ class OrderService:
customer_id: int, customer_id: int,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
) -> Tuple[List[Order], int]: ) -> tuple[list[Order], int]:
"""Get orders for a specific customer.""" """Get orders for a specific customer."""
return self.get_vendor_orders( return self.get_vendor_orders(
db=db, vendor_id=vendor_id, skip=skip, limit=limit, customer_id=customer_id db=db, vendor_id=vendor_id, skip=skip, limit=limit, customer_id=customer_id
@@ -335,7 +337,7 @@ class OrderService:
order.status = order_update.status order.status = order_update.status
# Update timestamp based on status # Update timestamp based on status
now = datetime.now(timezone.utc) now = datetime.now(UTC)
if order_update.status == "shipped" and not order.shipped_at: if order_update.status == "shipped" and not order.shipped_at:
order.shipped_at = now order.shipped_at = now
elif order_update.status == "delivered" and not order.delivered_at: elif order_update.status == "delivered" and not order.delivered_at:
@@ -351,7 +353,7 @@ class OrderService:
if order_update.internal_notes: if order_update.internal_notes:
order.internal_notes = order_update.internal_notes order.internal_notes = order_update.internal_notes
order.updated_at = datetime.now(timezone.utc) order.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(order) db.refresh(order)

View File

@@ -9,13 +9,15 @@ This module provides:
""" """
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import List, Optional, Tuple
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (ProductAlreadyExistsException, from app.exceptions import (
ProductNotFoundException, ValidationException) ProductAlreadyExistsException,
ProductNotFoundException,
ValidationException,
)
from models.database.marketplace_product import MarketplaceProduct from models.database.marketplace_product import MarketplaceProduct
from models.database.product import Product from models.database.product import Product
from models.schema.product import ProductCreate, ProductUpdate from models.schema.product import ProductCreate, ProductUpdate
@@ -106,7 +108,7 @@ class ProductService:
if existing: if existing:
raise ProductAlreadyExistsException( raise ProductAlreadyExistsException(
f"Product already exists in catalog" "Product already exists in catalog"
) )
# Create product # Create product
@@ -167,7 +169,7 @@ class ProductService:
for key, value in update_data.items(): for key, value in update_data.items():
setattr(product, key, value) setattr(product, key, value)
product.updated_at = datetime.now(timezone.utc) product.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(product) db.refresh(product)
@@ -216,9 +218,9 @@ class ProductService:
vendor_id: int, vendor_id: int,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
is_active: Optional[bool] = None, is_active: bool | None = None,
is_featured: Optional[bool] = None, is_featured: bool | None = None,
) -> Tuple[List[Product], int]: ) -> tuple[list[Product], int]:
""" """
Get products in vendor catalog with filtering. Get products in vendor catalog with filtering.

View File

@@ -11,7 +11,7 @@ This module provides:
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, List from typing import Any
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -36,7 +36,7 @@ class StatsService:
# VENDOR-SPECIFIC STATISTICS # VENDOR-SPECIFIC STATISTICS
# ======================================================================== # ========================================================================
def get_vendor_stats(self, db: Session, vendor_id: int) -> Dict[str, Any]: def get_vendor_stats(self, db: Session, vendor_id: int) -> dict[str, Any]:
""" """
Get statistics for a specific vendor. Get statistics for a specific vendor.
@@ -177,7 +177,7 @@ class StatsService:
def get_vendor_analytics( def get_vendor_analytics(
self, db: Session, vendor_id: int, period: str = "30d" self, db: Session, vendor_id: int, period: str = "30d"
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Get a specific vendor analytics for a time period. Get a specific vendor analytics for a time period.
@@ -283,7 +283,7 @@ class StatsService:
# SYSTEM-WIDE STATISTICS (ADMIN) # SYSTEM-WIDE STATISTICS (ADMIN)
# ======================================================================== # ========================================================================
def get_comprehensive_stats(self, db: Session) -> Dict[str, Any]: def get_comprehensive_stats(self, db: Session) -> dict[str, Any]:
""" """
Get comprehensive system statistics for admin dashboard. Get comprehensive system statistics for admin dashboard.
@@ -333,7 +333,7 @@ class StatsService:
reason=f"Database query failed: {str(e)}", reason=f"Database query failed: {str(e)}",
) )
def get_marketplace_breakdown_stats(self, db: Session) -> List[Dict[str, Any]]: def get_marketplace_breakdown_stats(self, db: Session) -> list[dict[str, Any]]:
""" """
Get statistics broken down by marketplace. Get statistics broken down by marketplace.
@@ -382,7 +382,7 @@ class StatsService:
reason=f"Database query failed: {str(e)}", reason=f"Database query failed: {str(e)}",
) )
def get_user_statistics(self, db: Session) -> Dict[str, Any]: def get_user_statistics(self, db: Session) -> dict[str, Any]:
""" """
Get user statistics for admin dashboard. Get user statistics for admin dashboard.
@@ -416,7 +416,7 @@ class StatsService:
operation="get_user_statistics", reason="Database query failed" operation="get_user_statistics", reason="Database query failed"
) )
def get_import_statistics(self, db: Session) -> Dict[str, Any]: def get_import_statistics(self, db: Session) -> dict[str, Any]:
""" """
Get import job statistics. Get import job statistics.
@@ -457,7 +457,7 @@ class StatsService:
"success_rate": 0, "success_rate": 0,
} }
def get_order_statistics(self, db: Session) -> Dict[str, Any]: def get_order_statistics(self, db: Session) -> dict[str, Any]:
""" """
Get order statistics. Get order statistics.
@@ -472,7 +472,7 @@ class StatsService:
""" """
return {"total_orders": 0, "pending_orders": 0, "completed_orders": 0} return {"total_orders": 0, "pending_orders": 0, "completed_orders": 0}
def get_product_statistics(self, db: Session) -> Dict[str, Any]: def get_product_statistics(self, db: Session) -> dict[str, Any]:
""" """
Get product statistics. Get product statistics.
@@ -548,7 +548,7 @@ class StatsService:
.count() .count()
) )
def _get_inventory_statistics(self, db: Session) -> Dict[str, int]: def _get_inventory_statistics(self, db: Session) -> dict[str, int]:
""" """
Get inventory-related statistics. Get inventory-related statistics.

View File

@@ -9,13 +9,12 @@ This module provides:
""" """
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import Any, Dict, List from typing import Any
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (UnauthorizedVendorAccessException, from app.exceptions import ValidationException
ValidationException)
from models.database.user import User from models.database.user import User
from models.database.vendor import Role, VendorUser from models.database.vendor import Role, VendorUser
@@ -27,7 +26,7 @@ class TeamService:
def get_team_members( def get_team_members(
self, db: Session, vendor_id: int, current_user: User self, db: Session, vendor_id: int, current_user: User
) -> List[Dict[str, Any]]: ) -> list[dict[str, Any]]:
""" """
Get all team members for vendor. Get all team members for vendor.
@@ -69,7 +68,7 @@ class TeamService:
def invite_team_member( def invite_team_member(
self, db: Session, vendor_id: int, invitation_data: dict, current_user: User self, db: Session, vendor_id: int, invitation_data: dict, current_user: User
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Invite a new team member. Invite a new team member.
@@ -102,7 +101,7 @@ class TeamService:
user_id: int, user_id: int,
update_data: dict, update_data: dict,
current_user: User, current_user: User,
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Update team member role or status. Update team member role or status.
@@ -135,7 +134,7 @@ class TeamService:
if "is_active" in update_data: if "is_active" in update_data:
vendor_user.is_active = update_data["is_active"] vendor_user.is_active = update_data["is_active"]
vendor_user.updated_at = datetime.now(timezone.utc) vendor_user.updated_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(vendor_user) db.refresh(vendor_user)
@@ -178,7 +177,7 @@ class TeamService:
# Soft delete # Soft delete
vendor_user.is_active = False vendor_user.is_active = False
vendor_user.updated_at = datetime.now(timezone.utc) vendor_user.updated_at = datetime.now(UTC)
db.commit() db.commit()
logger.info(f"Removed user {user_id} from vendor {vendor_id}") logger.info(f"Removed user {user_id} from vendor {vendor_id}")
@@ -189,7 +188,7 @@ class TeamService:
logger.error(f"Error removing team member: {str(e)}") logger.error(f"Error removing team member: {str(e)}")
raise ValidationException("Failed to remove team member") raise ValidationException("Failed to remove team member")
def get_vendor_roles(self, db: Session, vendor_id: int) -> List[Dict[str, Any]]: def get_vendor_roles(self, db: Session, vendor_id: int) -> list[dict[str, Any]]:
""" """
Get available roles for vendor. Get available roles for vendor.

View File

@@ -12,25 +12,23 @@ This module provides classes and functions for:
import logging import logging
import secrets import secrets
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import List, Optional, Tuple
from sqlalchemy import and_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (DNSVerificationException, from app.exceptions import (
DNSVerificationException,
DomainAlreadyVerifiedException, DomainAlreadyVerifiedException,
DomainNotVerifiedException, DomainNotVerifiedException,
DomainVerificationFailedException, DomainVerificationFailedException,
InvalidDomainFormatException, InvalidDomainFormatException,
MaxDomainsReachedException, MaxDomainsReachedException,
MultiplePrimaryDomainsException,
ReservedDomainException, ReservedDomainException,
UnauthorizedDomainAccessException,
ValidationException, ValidationException,
VendorDomainAlreadyExistsException, VendorDomainAlreadyExistsException,
VendorDomainNotFoundException, VendorDomainNotFoundException,
VendorNotFoundException) VendorNotFoundException,
)
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.database.vendor_domain import VendorDomain from models.database.vendor_domain import VendorDomain
from models.schema.vendor_domain import VendorDomainCreate, VendorDomainUpdate from models.schema.vendor_domain import VendorDomainCreate, VendorDomainUpdate
@@ -135,7 +133,7 @@ class VendorDomainService:
logger.error(f"Error adding domain: {str(e)}") logger.error(f"Error adding domain: {str(e)}")
raise ValidationException("Failed to add domain") raise ValidationException("Failed to add domain")
def get_vendor_domains(self, db: Session, vendor_id: int) -> List[VendorDomain]: def get_vendor_domains(self, db: Session, vendor_id: int) -> list[VendorDomain]:
""" """
Get all domains for a vendor. Get all domains for a vendor.
@@ -272,7 +270,7 @@ class VendorDomainService:
logger.error(f"Error deleting domain: {str(e)}") logger.error(f"Error deleting domain: {str(e)}")
raise ValidationException("Failed to delete domain") raise ValidationException("Failed to delete domain")
def verify_domain(self, db: Session, domain_id: int) -> Tuple[VendorDomain, str]: def verify_domain(self, db: Session, domain_id: int) -> tuple[VendorDomain, str]:
""" """
Verify domain ownership via DNS TXT record. Verify domain ownership via DNS TXT record.
@@ -313,7 +311,7 @@ class VendorDomainService:
if txt_value == domain.verification_token: if txt_value == domain.verification_token:
# Verification successful # Verification successful
domain.is_verified = True domain.is_verified = True
domain.verified_at = datetime.now(timezone.utc) domain.verified_at = datetime.now(UTC)
db.commit() db.commit()
db.refresh(domain) db.refresh(domain)
@@ -419,7 +417,7 @@ class VendorDomainService:
raise ReservedDomainException(domain, first_part) raise ReservedDomainException(domain, first_part)
def _unset_primary_domains( def _unset_primary_domains(
self, db: Session, vendor_id: int, exclude_domain_id: Optional[int] = None self, db: Session, vendor_id: int, exclude_domain_id: int | None = None
) -> None: ) -> None:
"""Unset all primary domains for vendor.""" """Unset all primary domains for vendor."""
query = db.query(VendorDomain).filter( query = db.query(VendorDomain).filter(

View File

@@ -10,18 +10,20 @@ This module provides classes and functions for:
""" """
import logging import logging
from typing import List, Optional, Tuple
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (InvalidVendorDataException, from app.exceptions import (
InvalidVendorDataException,
MarketplaceProductNotFoundException, MarketplaceProductNotFoundException,
MaxVendorsReachedException, MaxVendorsReachedException,
ProductAlreadyExistsException, ProductAlreadyExistsException,
UnauthorizedVendorAccessException, UnauthorizedVendorAccessException,
ValidationException, VendorAlreadyExistsException, ValidationException,
VendorNotFoundException) VendorAlreadyExistsException,
VendorNotFoundException,
)
from models.database.marketplace_product import MarketplaceProduct from models.database.marketplace_product import MarketplaceProduct
from models.database.product import Product from models.database.product import Product
from models.database.user import User from models.database.user import User
@@ -108,7 +110,7 @@ class VendorService:
limit: int = 100, limit: int = 100,
active_only: bool = True, active_only: bool = True,
verified_only: bool = False, verified_only: bool = False,
) -> Tuple[List[Vendor], int]: ) -> tuple[list[Vendor], int]:
""" """
Get vendors with filtering. Get vendors with filtering.
@@ -257,7 +259,7 @@ class VendorService:
limit: int = 100, limit: int = 100,
active_only: bool = True, active_only: bool = True,
featured_only: bool = False, featured_only: bool = False,
) -> Tuple[List[Product], int]: ) -> tuple[list[Product], int]:
""" """
Get products in vendor catalog with filtering. Get products in vendor catalog with filtering.

View File

@@ -8,20 +8,23 @@ Handles:
- Role assignment - Role assignment
- Permission management - Permission management
""" """
import logging import logging
import secrets import secrets
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional from typing import Any
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.permissions import get_preset_permissions from app.core.permissions import get_preset_permissions
from app.exceptions import (CannotRemoveOwnerException, from app.exceptions import (
CannotRemoveOwnerException,
InvalidInvitationTokenException, InvalidInvitationTokenException,
MaxTeamMembersReachedException, MaxTeamMembersReachedException,
TeamInvitationAlreadyAcceptedException, TeamInvitationAlreadyAcceptedException,
TeamMemberAlreadyExistsException, TeamMemberAlreadyExistsException,
UserNotFoundException, VendorNotFoundException) UserNotFoundException,
)
from middleware.auth import AuthManager from middleware.auth import AuthManager
from models.database.user import User from models.database.user import User
from models.database.vendor import Role, Vendor, VendorUser, VendorUserType from models.database.vendor import Role, Vendor, VendorUser, VendorUserType
@@ -43,8 +46,8 @@ class VendorTeamService:
inviter: User, inviter: User,
email: str, email: str,
role_name: str, role_name: str,
custom_permissions: Optional[List[str]] = None, custom_permissions: list[str] | None = None,
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Invite a new team member to a vendor. Invite a new team member to a vendor.
@@ -196,9 +199,9 @@ class VendorTeamService:
db: Session, db: Session,
invitation_token: str, invitation_token: str,
password: str, password: str,
first_name: Optional[str] = None, first_name: str | None = None,
last_name: Optional[str] = None, last_name: str | None = None,
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Accept a team invitation and activate account. Accept a team invitation and activate account.
@@ -330,7 +333,7 @@ class VendorTeamService:
vendor: Vendor, vendor: Vendor,
user_id: int, user_id: int,
new_role_name: str, new_role_name: str,
custom_permissions: Optional[List[str]] = None, custom_permissions: list[str] | None = None,
) -> VendorUser: ) -> VendorUser:
""" """
Update a team member's role. Update a team member's role.
@@ -392,7 +395,7 @@ class VendorTeamService:
db: Session, db: Session,
vendor: Vendor, vendor: Vendor,
include_inactive: bool = False, include_inactive: bool = False,
) -> List[Dict[str, Any]]: ) -> list[dict[str, Any]]:
""" """
Get all team members for a vendor. Get all team members for a vendor.
@@ -445,7 +448,7 @@ class VendorTeamService:
db: Session, db: Session,
vendor: Vendor, vendor: Vendor,
role_name: str, role_name: str,
custom_permissions: Optional[List[str]] = None, custom_permissions: list[str] | None = None,
) -> Role: ) -> Role:
"""Get existing role or create new one with preset/custom permissions.""" """Get existing role or create new one with preset/custom permissions."""
# Try to find existing role with same name # Try to find existing role with same name
@@ -492,7 +495,6 @@ class VendorTeamService:
# - Vendor name # - Vendor name
# - Inviter name # - Inviter name
# - Expiry date # - Expiry date
pass
# Create service instance # Create service instance

View File

@@ -8,21 +8,24 @@ Handles theme CRUD operations, preset application, and validation.
import logging import logging
import re import re
from typing import Dict, List, Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.theme_presets import (THEME_PRESETS, apply_preset, from app.core.theme_presets import (
get_available_presets, get_preset_preview) THEME_PRESETS,
apply_preset,
get_available_presets,
get_preset_preview,
)
from app.exceptions.vendor import VendorNotFoundException from app.exceptions.vendor import VendorNotFoundException
from app.exceptions.vendor_theme import (InvalidColorFormatException, from app.exceptions.vendor_theme import (
InvalidColorFormatException,
InvalidFontFamilyException, InvalidFontFamilyException,
InvalidThemeDataException,
ThemeOperationException, ThemeOperationException,
ThemePresetAlreadyAppliedException,
ThemePresetNotFoundException, ThemePresetNotFoundException,
ThemeValidationException, ThemeValidationException,
VendorThemeNotFoundException) VendorThemeNotFoundException,
)
from models.database.vendor import Vendor from models.database.vendor import Vendor
from models.database.vendor_theme import VendorTheme from models.database.vendor_theme import VendorTheme
from models.schema.vendor_theme import ThemePresetPreview, VendorThemeUpdate from models.schema.vendor_theme import ThemePresetPreview, VendorThemeUpdate
@@ -77,7 +80,7 @@ class VendorThemeService:
# THEME RETRIEVAL # THEME RETRIEVAL
# ============================================================================ # ============================================================================
def get_theme(self, db: Session, vendor_code: str) -> Dict: def get_theme(self, db: Session, vendor_code: str) -> dict:
""" """
Get theme for vendor. Returns default if no custom theme exists. Get theme for vendor. Returns default if no custom theme exists.
@@ -107,7 +110,7 @@ class VendorThemeService:
return theme.to_dict() return theme.to_dict()
def _get_default_theme(self) -> Dict: def _get_default_theme(self) -> dict:
""" """
Get default theme configuration. Get default theme configuration.
@@ -329,7 +332,7 @@ class VendorThemeService:
operation="apply_preset", vendor_code=vendor_code, reason=str(e) operation="apply_preset", vendor_code=vendor_code, reason=str(e)
) )
def get_available_presets(self) -> List[ThemePresetPreview]: def get_available_presets(self) -> list[ThemePresetPreview]:
""" """
Get list of available theme presets. Get list of available theme presets.
@@ -351,7 +354,7 @@ class VendorThemeService:
# THEME DELETION # THEME DELETION
# ============================================================================ # ============================================================================
def delete_theme(self, db: Session, vendor_code: str) -> Dict: def delete_theme(self, db: Session, vendor_code: str) -> dict:
""" """
Delete custom theme for vendor (reverts to default). Delete custom theme for vendor (reverts to default).

View File

@@ -1,6 +1,6 @@
# app/tasks/background_tasks.py # app/tasks/background_tasks.py
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from app.core.database import SessionLocal from app.core.database import SessionLocal
from app.utils.csv_processor import CSVProcessor from app.utils.csv_processor import CSVProcessor
@@ -39,13 +39,13 @@ async def process_marketplace_import(
logger.error(f"Vendor {vendor_id} not found for import job {job_id}") logger.error(f"Vendor {vendor_id} not found for import job {job_id}")
job.status = "failed" job.status = "failed"
job.error_message = f"Vendor {vendor_id} not found" job.error_message = f"Vendor {vendor_id} not found"
job.completed_at = datetime.now(timezone.utc) job.completed_at = datetime.now(UTC)
db.commit() db.commit()
return return
# Update job status # Update job status
job.status = "processing" job.status = "processing"
job.started_at = datetime.now(timezone.utc) job.started_at = datetime.now(UTC)
db.commit() db.commit()
logger.info( logger.info(
@@ -64,7 +64,7 @@ async def process_marketplace_import(
# Update job with results # Update job with results
job.status = "completed" job.status = "completed"
job.completed_at = datetime.now(timezone.utc) job.completed_at = datetime.now(UTC)
job.imported_count = result["imported"] job.imported_count = result["imported"]
job.updated_count = result["updated"] job.updated_count = result["updated"]
job.error_count = result.get("errors", 0) job.error_count = result.get("errors", 0)
@@ -87,13 +87,13 @@ async def process_marketplace_import(
try: try:
job.status = "failed" job.status = "failed"
job.error_message = str(e) job.error_message = str(e)
job.completed_at = datetime.now(timezone.utc) job.completed_at = datetime.now(UTC)
db.commit() db.commit()
except Exception as commit_error: except Exception as commit_error:
logger.error(f"Failed to update job status: {commit_error}") logger.error(f"Failed to update job status: {commit_error}")
db.rollback() db.rollback()
finally: finally:
if hasattr(db, "close") and callable(getattr(db, "close")): if hasattr(db, "close") and callable(db.close):
try: try:
db.close() db.close()
except Exception as close_error: except Exception as close_error:

View File

@@ -8,9 +8,9 @@ This module provides classes and functions for:
""" """
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from io import StringIO from io import StringIO
from typing import Any, Dict from typing import Any
import pandas as pd import pandas as pd
import requests import requests
@@ -145,7 +145,7 @@ class CSVProcessor:
logger.info(f"Normalized columns: {list(df.columns)}") logger.info(f"Normalized columns: {list(df.columns)}")
return df return df
def _clean_row_data(self, row_data: Dict[str, Any]) -> Dict[str, Any]: def _clean_row_data(self, row_data: dict[str, Any]) -> dict[str, Any]:
"""Process a single row with data normalization.""" """Process a single row with data normalization."""
# Handle NaN values # Handle NaN values
processed_data = {k: (v if pd.notna(v) else None) for k, v in row_data.items()} processed_data = {k: (v if pd.notna(v) else None) for k, v in row_data.items()}
@@ -188,7 +188,7 @@ class CSVProcessor:
async def process_marketplace_csv_from_url( async def process_marketplace_csv_from_url(
self, url: str, marketplace: str, vendor_name: str, batch_size: int, db: Session self, url: str, marketplace: str, vendor_name: str, batch_size: int, db: Session
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Process CSV from URL with marketplace and vendor information. Process CSV from URL with marketplace and vendor information.
@@ -245,7 +245,7 @@ class CSVProcessor:
vendor_name: str, vendor_name: str,
db: Session, db: Session,
batch_num: int, batch_num: int,
) -> Dict[str, int]: ) -> dict[str, int]:
"""Process a batch of CSV rows with marketplace information.""" """Process a batch of CSV rows with marketplace information."""
imported = 0 imported = 0
updated = 0 updated = 0
@@ -295,7 +295,7 @@ class CSVProcessor:
existing_product, key existing_product, key
): ):
setattr(existing_product, key, value) setattr(existing_product, key, value)
existing_product.updated_at = datetime.now(timezone.utc) existing_product.updated_at = datetime.now(UTC)
updated += 1 updated += 1
logger.debug( logger.debug(
f"Updated product {product_data['marketplace_product_id']} for " f"Updated product {product_data['marketplace_product_id']} for "

View File

@@ -9,7 +9,6 @@ This module provides classes and functions for:
import logging import logging
import re import re
from typing import Optional, Tuple
import pandas as pd import pandas as pd
@@ -21,7 +20,7 @@ class GTINProcessor:
VALID_LENGTHS = [8, 12, 13, 14] # List of valid GTIN lengths VALID_LENGTHS = [8, 12, 13, 14] # List of valid GTIN lengths
def normalize(self, gtin_value: any) -> Optional[str]: def normalize(self, gtin_value: any) -> str | None:
""" """
Normalize GTIN to proper format. Normalize GTIN to proper format.
@@ -55,11 +54,11 @@ class GTINProcessor:
# Standard lengths - pad appropriately # Standard lengths - pad appropriately
if length == 8: if length == 8:
return gtin_clean.zfill(8) # EAN-8 return gtin_clean.zfill(8) # EAN-8
elif length == 12: if length == 12:
return gtin_clean.zfill(12) # UPC-A return gtin_clean.zfill(12) # UPC-A
elif length == 13: if length == 13:
return gtin_clean.zfill(13) # EAN-13 return gtin_clean.zfill(13) # EAN-13
elif length == 14: if length == 14:
return gtin_clean.zfill(14) # GTIN-14 return gtin_clean.zfill(14) # GTIN-14
elif length > 14: elif length > 14:
@@ -111,7 +110,7 @@ class PriceProcessor:
def parse_price_currency( def parse_price_currency(
self, price_str: any self, price_str: any
) -> Tuple[Optional[str], Optional[str]]: ) -> tuple[str | None, str | None]:
""" """
Parse a price string to extract the numeric value and currency. Parse a price string to extract the numeric value and currency.

View File

@@ -40,7 +40,7 @@ def get_db_engine(database_url: str):
echo=False, echo=False,
) )
logger.info(f"Database engine created for: " f"{database_url.split('@')[0]}@...") logger.info(f"Database engine created for: {database_url.split('@')[0]}@...")
return engine return engine

16
main.py
View File

@@ -18,7 +18,7 @@ if sys.platform == "win32":
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8") sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
import logging import logging
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from fastapi import Depends, FastAPI, HTTPException, Request, Response from fastapi import Depends, FastAPI, HTTPException, Request, Response
@@ -35,11 +35,13 @@ from app.core.database import get_db
from app.core.lifespan import lifespan from app.core.lifespan import lifespan
from app.exceptions import ServiceUnavailableException from app.exceptions import ServiceUnavailableException
from app.exceptions.handler import setup_exception_handlers from app.exceptions.handler import setup_exception_handlers
# Import page routers # Import page routers
from app.routes import admin_pages, shop_pages, vendor_pages from app.routes import admin_pages, shop_pages, vendor_pages
from middleware.context import ContextMiddleware from middleware.context import ContextMiddleware
from middleware.logging import LoggingMiddleware from middleware.logging import LoggingMiddleware
from middleware.theme_context import ThemeContextMiddleware from middleware.theme_context import ThemeContextMiddleware
# Import REFACTORED class-based middleware # Import REFACTORED class-based middleware
from middleware.vendor_context import VendorContextMiddleware from middleware.vendor_context import VendorContextMiddleware
@@ -259,7 +261,6 @@ async def vendor_root_path(
return templates.TemplateResponse( return templates.TemplateResponse(
template_path, get_shop_context(request, db=db, page=landing_page) template_path, get_shop_context(request, db=db, page=landing_page)
) )
else:
# No landing page - redirect to shop # No landing page - redirect to shop
return RedirectResponse(url=f"/vendors/{vendor_code}/shop/", status_code=302) return RedirectResponse(url=f"/vendors/{vendor_code}/shop/", status_code=302)
@@ -317,7 +318,6 @@ async def platform_homepage(request: Request, db: Session = Depends(get_db)):
"footer_pages": footer_pages, "footer_pages": footer_pages,
}, },
) )
else:
# Fallback to default static template # Fallback to default static template
logger.info("[PLATFORM] No CMS homepage found, using default template") logger.info("[PLATFORM] No CMS homepage found, using default template")
@@ -349,7 +349,10 @@ async def platform_content_page(
# Load page from CMS # Load page from CMS
page = content_page_service.get_page_for_vendor( page = content_page_service.get_page_for_vendor(
db, slug=slug, vendor_id=None, include_unpublished=False # Platform pages only db,
slug=slug,
vendor_id=None,
include_unpublished=False, # Platform pages only
) )
if not page: if not page:
@@ -430,7 +433,6 @@ async def root(request: Request, db: Session = Depends(get_db)):
return templates.TemplateResponse( return templates.TemplateResponse(
template_path, get_shop_context(request, db=db, page=landing_page) template_path, get_shop_context(request, db=db, page=landing_page)
) )
else:
# No landing page - redirect to shop # No landing page - redirect to shop
vendor_context = getattr(request.state, "vendor_context", None) vendor_context = getattr(request.state, "vendor_context", None)
access_method = ( access_method = (
@@ -448,10 +450,8 @@ async def root(request: Request, db: Session = Depends(get_db)):
return RedirectResponse( return RedirectResponse(
url=f"{full_prefix}{vendor.subdomain}/shop/", status_code=302 url=f"{full_prefix}{vendor.subdomain}/shop/", status_code=302
) )
else:
# Domain/subdomain # Domain/subdomain
return RedirectResponse(url="/shop/", status_code=302) return RedirectResponse(url="/shop/", status_code=302)
else:
# No vendor - platform root # No vendor - platform root
return RedirectResponse(url="/documentation") return RedirectResponse(url="/documentation")
@@ -464,7 +464,7 @@ def health_check(db: Session = Depends(get_db)):
db.execute(text("SELECT 1")) db.execute(text("SELECT 1"))
return { return {
"status": "healthy", "status": "healthy",
"timestamp": datetime.now(timezone.utc), "timestamp": datetime.now(UTC),
"message": f"{settings.project_name} v{settings.version}", "message": f"{settings.project_name} v{settings.version}",
"docs": { "docs": {
"swagger": "/docs", "swagger": "/docs",

View File

@@ -17,8 +17,9 @@ The module uses the following technologies:
import logging import logging
import os import os
from datetime import datetime, timedelta, timezone from collections.abc import Callable
from typing import Any, Callable, Dict, Optional from datetime import UTC, datetime, timedelta
from typing import Any
from fastapi import HTTPException from fastapi import HTTPException
from fastapi.security import HTTPAuthorizationCredentials from fastapi.security import HTTPAuthorizationCredentials
@@ -26,10 +27,14 @@ from jose import jwt
from passlib.context import CryptContext from passlib.context import CryptContext
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.exceptions import (AdminRequiredException, from app.exceptions import (
AdminRequiredException,
InsufficientPermissionsException, InsufficientPermissionsException,
InvalidCredentialsException, InvalidTokenException, InvalidCredentialsException,
TokenExpiredException, UserNotActiveException) InvalidTokenException,
TokenExpiredException,
UserNotActiveException,
)
from models.database.user import User from models.database.user import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -97,7 +102,7 @@ class AuthManager:
def authenticate_user( def authenticate_user(
self, db: Session, username: str, password: str self, db: Session, username: str, password: str
) -> Optional[User]: ) -> User | None:
"""Authenticate user credentials against the database. """Authenticate user credentials against the database.
Supports authentication using either username or email address. Supports authentication using either username or email address.
@@ -129,7 +134,7 @@ class AuthManager:
# Authentication successful, return user object # Authentication successful, return user object
return user return user
def create_access_token(self, user: User) -> Dict[str, Any]: def create_access_token(self, user: User) -> dict[str, Any]:
"""Create a JWT access token for an authenticated user. """Create a JWT access token for an authenticated user.
The token includes user identity and role information in the payload. The token includes user identity and role information in the payload.
@@ -146,7 +151,7 @@ class AuthManager:
""" """
# Calculate token expiration time # Calculate token expiration time
expires_delta = timedelta(minutes=self.token_expire_minutes) expires_delta = timedelta(minutes=self.token_expire_minutes)
expire = datetime.now(timezone.utc) + expires_delta expire = datetime.now(UTC) + expires_delta
# Build JWT payload with user information # Build JWT payload with user information
payload = { payload = {
@@ -155,7 +160,7 @@ class AuthManager:
"email": user.email, # User email address "email": user.email, # User email address
"role": user.role, # User role for authorization "role": user.role, # User role for authorization
"exp": expire, # Expiration time (JWT standard claim) "exp": expire, # Expiration time (JWT standard claim)
"iat": datetime.now(timezone.utc), # Issued at time (JWT standard claim) "iat": datetime.now(UTC), # Issued at time (JWT standard claim)
} }
# Encode the payload into a JWT token # Encode the payload into a JWT token
@@ -168,7 +173,7 @@ class AuthManager:
"expires_in": self.token_expire_minutes * 60, # Convert minutes to seconds "expires_in": self.token_expire_minutes * 60, # Convert minutes to seconds
} }
def verify_token(self, token: str) -> Dict[str, Any]: def verify_token(self, token: str) -> dict[str, Any]:
"""Verify and decode a JWT token, returning the user data. """Verify and decode a JWT token, returning the user data.
Validates the token signature, expiration, and required claims. Validates the token signature, expiration, and required claims.
@@ -199,8 +204,8 @@ class AuthManager:
raise InvalidTokenException("Token missing expiration") raise InvalidTokenException("Token missing expiration")
# Check if token has expired (additional check beyond jwt.decode) # Check if token has expired (additional check beyond jwt.decode)
if datetime.now(timezone.utc) > datetime.fromtimestamp( if datetime.now(UTC) > datetime.fromtimestamp(
exp, tz=timezone.utc exp, tz=UTC
): ):
raise TokenExpiredException() raise TokenExpiredException()

View File

@@ -70,7 +70,7 @@ class ContextManager:
host = host.split(":")[0] host = host.split(":")[0]
logger.debug( logger.debug(
f"[CONTEXT] Detecting context", "[CONTEXT] Detecting context",
extra={ extra={
"original_path": request.url.path, "original_path": request.url.path,
"clean_path": getattr(request.state, "clean_path", "NOT SET"), "clean_path": getattr(request.state, "clean_path", "NOT SET"),

View File

@@ -9,7 +9,7 @@ This module provides classes and functions for:
import logging import logging
import time import time
from typing import Callable from collections.abc import Callable
from fastapi import Request, Response from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware

View File

@@ -9,8 +9,7 @@ This module provides classes and functions for:
import logging import logging
from collections import defaultdict, deque from collections import defaultdict, deque
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from typing import Dict
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -21,9 +20,9 @@ class RateLimiter:
def __init__(self): def __init__(self):
"""Class constructor.""" """Class constructor."""
# Dictionary to store request timestamps for each client # Dictionary to store request timestamps for each client
self.clients: Dict[str, deque] = defaultdict(lambda: deque()) self.clients: dict[str, deque] = defaultdict(lambda: deque())
self.cleanup_interval = 3600 # Clean up old entries every hour self.cleanup_interval = 3600 # Clean up old entries every hour
self.last_cleanup = datetime.now(timezone.utc) self.last_cleanup = datetime.now(UTC)
def allow_request( def allow_request(
self, client_id: str, max_requests: int, window_seconds: int self, client_id: str, max_requests: int, window_seconds: int
@@ -33,7 +32,7 @@ class RateLimiter:
Uses sliding window algorithm Uses sliding window algorithm
""" """
now = datetime.now(timezone.utc) now = datetime.now(UTC)
window_start = now - timedelta(seconds=window_seconds) window_start = now - timedelta(seconds=window_seconds)
# Clean up old entries periodically # Clean up old entries periodically
@@ -60,7 +59,7 @@ class RateLimiter:
def _cleanup_old_entries(self): def _cleanup_old_entries(self):
"""Clean up old entries to prevent memory leaks.""" """Clean up old entries to prevent memory leaks."""
cutoff_time = datetime.now(timezone.utc) - timedelta(hours=24) cutoff_time = datetime.now(UTC) - timedelta(hours=24)
clients_to_remove = [] clients_to_remove = []
for client_id, requests in self.clients.items(): for client_id, requests in self.clients.items():
@@ -80,11 +79,11 @@ class RateLimiter:
f"Rate limiter cleanup completed. Removed {len(clients_to_remove)} inactive clients" f"Rate limiter cleanup completed. Removed {len(clients_to_remove)} inactive clients"
) )
def get_client_stats(self, client_id: str) -> Dict[str, int]: def get_client_stats(self, client_id: str) -> dict[str, int]:
"""Get statistics for a specific client.""" """Get statistics for a specific client."""
client_requests = self.clients.get(client_id, deque()) client_requests = self.clients.get(client_id, deque())
now = datetime.now(timezone.utc) now = datetime.now(UTC)
hour_ago = now - timedelta(hours=1) hour_ago = now - timedelta(hours=1)
day_ago = now - timedelta(days=1) day_ago = now - timedelta(days=1)

View File

@@ -114,7 +114,7 @@ class ThemeContextMiddleware(BaseHTTPMiddleware):
request.state.theme = theme request.state.theme = theme
logger.debug( logger.debug(
f"[THEME] Theme loaded for vendor", "[THEME] Theme loaded for vendor",
extra={ extra={
"vendor_id": vendor.id, "vendor_id": vendor.id,
"vendor_name": vendor.name, "vendor_name": vendor.name,

View File

@@ -12,7 +12,6 @@ Also extracts clean_path for nested routing patterns.
""" """
import logging import logging
from typing import Optional
from fastapi import Request from fastapi import Request
from sqlalchemy import func from sqlalchemy import func
@@ -31,7 +30,7 @@ class VendorContextManager:
"""Manages vendor context detection for multi-tenant routing.""" """Manages vendor context detection for multi-tenant routing."""
@staticmethod @staticmethod
def detect_vendor_context(request: Request) -> Optional[dict]: def detect_vendor_context(request: Request) -> dict | None:
""" """
Detect vendor context from request. Detect vendor context from request.
@@ -106,7 +105,7 @@ class VendorContextManager:
return None return None
@staticmethod @staticmethod
def get_vendor_from_context(db: Session, context: dict) -> Optional[Vendor]: def get_vendor_from_context(db: Session, context: dict) -> Vendor | None:
""" """
Get vendor from database using context information. Get vendor from database using context information.
@@ -142,7 +141,6 @@ class VendorContextManager:
f"[OK] Vendor found via custom domain: {domain}{vendor.name}" f"[OK] Vendor found via custom domain: {domain}{vendor.name}"
) )
return vendor return vendor
else:
logger.warning( logger.warning(
f"No active vendor found for custom domain: {domain}" f"No active vendor found for custom domain: {domain}"
) )
@@ -169,7 +167,7 @@ class VendorContextManager:
return vendor return vendor
@staticmethod @staticmethod
def extract_clean_path(request: Request, vendor_context: Optional[dict]) -> str: def extract_clean_path(request: Request, vendor_context: dict | None) -> str:
""" """
Extract clean path without vendor prefix for routing. Extract clean path without vendor prefix for routing.
@@ -217,7 +215,7 @@ class VendorContextManager:
return request.url.path.startswith("/api/v1/shop/") return request.url.path.startswith("/api/v1/shop/")
@staticmethod @staticmethod
def extract_vendor_from_referer(request: Request) -> Optional[dict]: def extract_vendor_from_referer(request: Request) -> dict | None:
""" """
Extract vendor context from Referer header. Extract vendor context from Referer header.
@@ -250,7 +248,7 @@ class VendorContextManager:
referer_host = referer_host.split(":")[0] referer_host = referer_host.split(":")[0]
logger.debug( logger.debug(
f"[VENDOR] Extracting vendor from Referer", "[VENDOR] Extracting vendor from Referer",
extra={ extra={
"referer": referer, "referer": referer,
"referer_host": referer_host, "referer_host": referer_host,
@@ -449,7 +447,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
request.state.clean_path = request.url.path request.state.clean_path = request.url.path
logger.debug( logger.debug(
f"[VENDOR_CONTEXT] Vendor detected from Referer for shop API", "[VENDOR_CONTEXT] Vendor detected from Referer for shop API",
extra={ extra={
"vendor_id": vendor.id, "vendor_id": vendor.id,
"vendor_name": vendor.name, "vendor_name": vendor.name,
@@ -463,7 +461,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
) )
else: else:
logger.warning( logger.warning(
f"[WARNING] Vendor context from Referer but vendor not found", "[WARNING] Vendor context from Referer but vendor not found",
extra={ extra={
"context": vendor_context, "context": vendor_context,
"detection_method": vendor_context.get( "detection_method": vendor_context.get(
@@ -479,7 +477,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
db.close() db.close()
else: else:
logger.warning( logger.warning(
f"[VENDOR] Shop API request without Referer header", "[VENDOR] Shop API request without Referer header",
extra={"path": request.url.path}, extra={"path": request.url.path},
) )
request.state.vendor = None request.state.vendor = None
@@ -518,7 +516,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
) )
logger.debug( logger.debug(
f"[VENDOR_CONTEXT] Vendor detected", "[VENDOR_CONTEXT] Vendor detected",
extra={ extra={
"vendor_id": vendor.id, "vendor_id": vendor.id,
"vendor_name": vendor.name, "vendor_name": vendor.name,
@@ -530,7 +528,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
) )
else: else:
logger.warning( logger.warning(
f"[WARNING] Vendor context detected but vendor not found", "[WARNING] Vendor context detected but vendor not found",
extra={ extra={
"context": vendor_context, "context": vendor_context,
"detection_method": vendor_context.get("detection_method"), "detection_method": vendor_context.get("detection_method"),
@@ -543,7 +541,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
db.close() db.close()
else: else:
logger.debug( logger.debug(
f"[VENDOR] No vendor context detected", "[VENDOR] No vendor context detected",
extra={ extra={
"path": request.url.path, "path": request.url.path,
"host": request.headers.get("host", ""), "host": request.headers.get("host", ""),
@@ -557,7 +555,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
return await call_next(request) return await call_next(request)
def get_current_vendor(request: Request) -> Optional[Vendor]: def get_current_vendor(request: Request) -> Vendor | None:
"""Helper function to get current vendor from request state.""" """Helper function to get current vendor from request state."""
return getattr(request.state, "vendor", None) return getattr(request.state, "vendor", None)

View File

@@ -3,6 +3,7 @@
# API models (Pydantic) - import the modules, not all classes # API models (Pydantic) - import the modules, not all classes
from . import schema from . import schema
# Database models (SQLAlchemy) # Database models (SQLAlchemy)
from .database.base import Base from .database.base import Base
from .database.inventory import Inventory from .database.inventory import Inventory

View File

@@ -1,7 +1,13 @@
# models/database/__init__.py # models/database/__init__.py
"""Database models package.""" """Database models package."""
from .admin import (AdminAuditLog, AdminNotification, AdminSession,
AdminSetting, PlatformAlert) from .admin import (
AdminAuditLog,
AdminNotification,
AdminSession,
AdminSetting,
PlatformAlert,
)
from .base import Base from .base import Base
from .customer import Customer, CustomerAddress from .customer import Customer, CustomerAddress
from .inventory import Inventory from .inventory import Inventory

View File

@@ -10,8 +10,16 @@ This module provides models for:
- Platform alerts (system-wide issues) - Platform alerts (system-wide issues)
""" """
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer, from sqlalchemy import (
String, Text) JSON,
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
String,
Text,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -1,17 +1,15 @@
from datetime import datetime, timezone from datetime import UTC, datetime
from sqlalchemy import Column, DateTime from sqlalchemy import Column, DateTime
from app.core.database import Base
class TimestampMixin: class TimestampMixin:
"""Mixin to add created_at and updated_at timestamps to models""" """Mixin to add created_at and updated_at timestamps to models"""
created_at = Column(DateTime, default=datetime.now(timezone.utc), nullable=False) created_at = Column(DateTime, default=datetime.now(UTC), nullable=False)
updated_at = Column( updated_at = Column(
DateTime, DateTime,
default=datetime.now(timezone.utc), default=datetime.now(UTC),
onupdate=datetime.now(timezone.utc), onupdate=datetime.now(UTC),
nullable=False, nullable=False,
) )

View File

@@ -1,9 +1,16 @@
# models/database/cart.py # models/database/cart.py
"""Cart item database model.""" """Cart item database model."""
from datetime import datetime
from sqlalchemy import (Column, Float, ForeignKey, Index, Integer, String,
UniqueConstraint) from sqlalchemy import (
Column,
Float,
ForeignKey,
Index,
Integer,
String,
UniqueConstraint,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -14,10 +14,19 @@ Features:
- Version history support - Version history support
""" """
from datetime import datetime, timezone from datetime import UTC, datetime
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Index, Integer, from sqlalchemy import (
String, Text, UniqueConstraint) Boolean,
Column,
DateTime,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base
@@ -77,13 +86,13 @@ class ContentPage(Base):
# Timestamps # Timestamps
created_at = Column( created_at = Column(
DateTime(timezone=True), DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc), default=lambda: datetime.now(UTC),
nullable=False, nullable=False,
) )
updated_at = Column( updated_at = Column(
DateTime(timezone=True), DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc), default=lambda: datetime.now(UTC),
onupdate=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(UTC),
nullable=False, nullable=False,
) )

View File

@@ -1,8 +1,14 @@
from datetime import datetime
from decimal import Decimal
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer, from sqlalchemy import (
Numeric, String, Text) JSON,
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
Numeric,
String,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -1,8 +1,6 @@
# models/database/inventory.py # models/database/inventory.py
from datetime import datetime
from sqlalchemy import (Column, ForeignKey, Index, Integer, String, from sqlalchemy import Column, ForeignKey, Index, Integer, String, UniqueConstraint
UniqueConstraint)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -1,7 +1,5 @@
from datetime import datetime, timezone
from sqlalchemy import (Column, DateTime, ForeignKey, Index, Integer, String, from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Text
Text)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -1,7 +1,10 @@
from datetime import datetime, timezone
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index, from sqlalchemy import (
Integer, String, Text, UniqueConstraint) Column,
Index,
Integer,
String,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
# Import Base from the central database module instead of creating a new one # Import Base from the central database module instead of creating a new one

View File

@@ -1,8 +1,15 @@
# models/database/order.py # models/database/order.py
from datetime import datetime
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Integer, from sqlalchemy import (
String, Text) Boolean,
Column,
DateTime,
Float,
ForeignKey,
Integer,
String,
Text,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -1,8 +1,15 @@
# models/database/product.py # models/database/product.py
from datetime import datetime
from sqlalchemy import (Boolean, Column, Float, ForeignKey, Index, Integer, from sqlalchemy import (
String, UniqueConstraint) Boolean,
Column,
Float,
ForeignKey,
Index,
Integer,
String,
UniqueConstraint,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -10,9 +10,10 @@ ROLE CLARIFICATION:
- Vendor-specific roles (manager, staff, etc.) are stored in VendorUser.role - Vendor-specific roles (manager, staff, etc.) are stored in VendorUser.role
- Customers are NOT in the User table - they use the Customer model - Customers are NOT in the User table - they use the Customer model
""" """
import enum import enum
from sqlalchemy import Boolean, Column, DateTime, Enum, Integer, String from sqlalchemy import Boolean, Column, DateTime, Integer, String
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base

View File

@@ -5,13 +5,23 @@ Vendor model representing entities that sell products or services.
This module defines the Vendor model along with its relationships to This module defines the Vendor model along with its relationships to
other models such as User (owner), Product, Customer, Order, and MarketplaceImportJob. other models such as User (owner), Product, Customer, Order, and MarketplaceImportJob.
""" """
import enum import enum
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer, from sqlalchemy import (
String, Text) JSON,
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
String,
Text,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.config import settings from app.core.config import settings
# Import Base from the central database module instead of creating a new one # Import Base from the central database module instead of creating a new one
from app.core.database import Base from app.core.database import Base
from models.database.base import TimestampMixin from models.database.base import TimestampMixin

Some files were not shown because too many files have changed in this diff Show More