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:
@@ -32,18 +32,20 @@ The cookie path restrictions prevent cross-context cookie leakage:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
from datetime import UTC
|
||||
|
||||
from fastapi import Cookie, Depends, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import (AdminRequiredException,
|
||||
InsufficientPermissionsException,
|
||||
InvalidTokenException,
|
||||
UnauthorizedVendorAccessException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions import (
|
||||
AdminRequiredException,
|
||||
InsufficientPermissionsException,
|
||||
InvalidTokenException,
|
||||
UnauthorizedVendorAccessException,
|
||||
VendorNotFoundException,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
from middleware.rate_limiter import RateLimiter
|
||||
from models.database.user import User
|
||||
@@ -62,11 +64,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_token_from_request(
|
||||
credentials: Optional[HTTPAuthorizationCredentials],
|
||||
cookie_value: Optional[str],
|
||||
credentials: HTTPAuthorizationCredentials | None,
|
||||
cookie_value: str | None,
|
||||
cookie_name: str,
|
||||
request_path: str,
|
||||
) -> tuple[Optional[str], Optional[str]]:
|
||||
) -> tuple[str | None, str | None]:
|
||||
"""
|
||||
Extract token from Authorization header or cookie.
|
||||
|
||||
@@ -86,7 +88,7 @@ def _get_token_from_request(
|
||||
if credentials:
|
||||
logger.debug(f"Token found in Authorization header for {request_path}")
|
||||
return credentials.credentials, "header"
|
||||
elif cookie_value:
|
||||
if cookie_value:
|
||||
logger.debug(f"Token found in {cookie_name} cookie for {request_path}")
|
||||
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(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
admin_token: Optional[str] = Cookie(None),
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
admin_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> User:
|
||||
"""
|
||||
@@ -205,8 +207,8 @@ def get_current_admin_api(
|
||||
|
||||
def get_current_vendor_from_cookie_or_header(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
vendor_token: Optional[str] = Cookie(None),
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
vendor_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> User:
|
||||
"""
|
||||
@@ -305,8 +307,8 @@ def get_current_vendor_api(
|
||||
|
||||
def get_current_customer_from_cookie_or_header(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
customer_token: Optional[str] = Cookie(None),
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
customer_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
@@ -331,7 +333,7 @@ def get_current_customer_from_cookie_or_header(
|
||||
Raises:
|
||||
InvalidTokenException: If no token or invalid token
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime
|
||||
|
||||
from jose import JWTError, jwt
|
||||
|
||||
@@ -365,8 +367,8 @@ def get_current_customer_from_cookie_or_header(
|
||||
|
||||
# Verify token hasn't expired
|
||||
exp = payload.get("exp")
|
||||
if exp and datetime.fromtimestamp(exp, tz=timezone.utc) < datetime.now(
|
||||
timezone.utc
|
||||
if exp and datetime.fromtimestamp(exp, tz=UTC) < datetime.now(
|
||||
UTC
|
||||
):
|
||||
logger.warning(f"Expired customer token for customer_id={customer_id}")
|
||||
raise InvalidTokenException("Token has expired")
|
||||
@@ -694,10 +696,10 @@ def get_user_permissions(
|
||||
|
||||
def get_current_admin_optional(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
admin_token: Optional[str] = Cookie(None),
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
admin_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
"""
|
||||
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(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
vendor_token: Optional[str] = Cookie(None),
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
vendor_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
"""
|
||||
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(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
customer_token: Optional[str] = Cookie(None),
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
customer_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
"""
|
||||
Get current customer user from customer_token cookie or Authorization header.
|
||||
|
||||
|
||||
@@ -24,9 +24,21 @@ IMPORTANT:
|
||||
from fastapi import APIRouter
|
||||
|
||||
# Import all admin routers
|
||||
from . import (audit, auth, code_quality, content_pages, dashboard,
|
||||
marketplace, monitoring, notifications, settings, users,
|
||||
vendor_domains, vendor_themes, vendors)
|
||||
from . import (
|
||||
audit,
|
||||
auth,
|
||||
code_quality,
|
||||
content_pages,
|
||||
dashboard,
|
||||
marketplace,
|
||||
monitoring,
|
||||
notifications,
|
||||
settings,
|
||||
users,
|
||||
vendor_domains,
|
||||
vendor_themes,
|
||||
vendors,
|
||||
)
|
||||
|
||||
# Create admin router
|
||||
router = APIRouter()
|
||||
|
||||
@@ -10,7 +10,6 @@ Provides endpoints for:
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
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.services.admin_audit_service import admin_audit_service
|
||||
from models.database.user import User
|
||||
from models.schema.admin import (AdminAuditLogFilters,
|
||||
AdminAuditLogListResponse,
|
||||
AdminAuditLogResponse)
|
||||
from models.schema.admin import (
|
||||
AdminAuditLogFilters,
|
||||
AdminAuditLogListResponse,
|
||||
AdminAuditLogResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/audit")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -29,11 +30,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("/logs", response_model=AdminAuditLogListResponse)
|
||||
def get_audit_logs(
|
||||
admin_user_id: Optional[int] = Query(None, description="Filter by admin user"),
|
||||
action: Optional[str] = Query(None, description="Filter by action type"),
|
||||
target_type: Optional[str] = Query(None, description="Filter by target type"),
|
||||
date_from: Optional[datetime] = Query(None, description="Filter from date"),
|
||||
date_to: Optional[datetime] = Query(None, description="Filter to date"),
|
||||
admin_user_id: int | None = Query(None, description="Filter by admin user"),
|
||||
action: str | None = Query(None, description="Filter by action type"),
|
||||
target_type: str | None = Query(None, description="Filter by target type"),
|
||||
date_from: datetime | None = Query(None, description="Filter from date"),
|
||||
date_to: datetime | None = Query(None, description="Filter to date"),
|
||||
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"),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
@@ -4,7 +4,6 @@ RESTful API for architecture validation and violation management
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -32,7 +31,7 @@ class ScanResponse(BaseModel):
|
||||
warnings: int
|
||||
duration_seconds: float
|
||||
triggered_by: str
|
||||
git_commit_hash: Optional[str]
|
||||
git_commit_hash: str | None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -49,13 +48,13 @@ class ViolationResponse(BaseModel):
|
||||
file_path: str
|
||||
line_number: int
|
||||
message: str
|
||||
context: Optional[str]
|
||||
suggestion: Optional[str]
|
||||
context: str | None
|
||||
suggestion: str | None
|
||||
status: str
|
||||
assigned_to: Optional[int]
|
||||
resolved_at: Optional[str]
|
||||
resolved_by: Optional[int]
|
||||
resolution_note: Optional[str]
|
||||
assigned_to: int | None
|
||||
resolved_at: str | None
|
||||
resolved_by: int | None
|
||||
resolution_note: str | None
|
||||
created_at: str
|
||||
|
||||
class Config:
|
||||
@@ -83,7 +82,7 @@ class AssignViolationRequest(BaseModel):
|
||||
"""Request model for assigning a violation"""
|
||||
|
||||
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(
|
||||
"medium", description="Priority level (low, medium, high, critical)"
|
||||
)
|
||||
@@ -123,7 +122,7 @@ class DashboardStatsResponse(BaseModel):
|
||||
by_rule: dict
|
||||
by_module: dict
|
||||
top_files: list
|
||||
last_scan: Optional[str]
|
||||
last_scan: str | None
|
||||
|
||||
|
||||
# API Endpoints
|
||||
@@ -189,17 +188,17 @@ async def list_scans(
|
||||
|
||||
@router.get("/violations", response_model=ViolationListResponse)
|
||||
async def list_violations(
|
||||
scan_id: Optional[int] = Query(
|
||||
scan_id: int | None = Query(
|
||||
None, description="Filter by scan ID (defaults to latest)"
|
||||
),
|
||||
severity: Optional[str] = Query(
|
||||
severity: str | None = Query(
|
||||
None, description="Filter by severity (error, warning)"
|
||||
),
|
||||
status: Optional[str] = Query(
|
||||
status: str | None = Query(
|
||||
None, description="Filter by status (open, assigned, resolved, ignored)"
|
||||
),
|
||||
rule_id: Optional[str] = Query(None, description="Filter by rule ID"),
|
||||
file_path: Optional[str] = Query(
|
||||
rule_id: str | None = Query(None, description="Filter by rule ID"),
|
||||
file_path: str | None = Query(
|
||||
None, description="Filter by file path (partial match)"
|
||||
),
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
|
||||
@@ -9,7 +9,6 @@ Platform administrators can:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -46,17 +45,17 @@ class ContentPageCreate(BaseModel):
|
||||
max_length=50,
|
||||
description="Template name (default, minimal, modern)",
|
||||
)
|
||||
meta_description: Optional[str] = Field(
|
||||
meta_description: str | None = Field(
|
||||
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"
|
||||
)
|
||||
is_published: bool = Field(default=False, description="Publish immediately")
|
||||
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
|
||||
show_in_header: bool = Field(default=False, description="Show in header navigation")
|
||||
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)"
|
||||
)
|
||||
|
||||
@@ -64,32 +63,32 @@ class ContentPageCreate(BaseModel):
|
||||
class ContentPageUpdate(BaseModel):
|
||||
"""Schema for updating a content page."""
|
||||
|
||||
title: Optional[str] = Field(None, max_length=200)
|
||||
content: Optional[str] = None
|
||||
content_format: Optional[str] = None
|
||||
template: Optional[str] = Field(None, max_length=50)
|
||||
meta_description: Optional[str] = Field(None, max_length=300)
|
||||
meta_keywords: Optional[str] = Field(None, max_length=300)
|
||||
is_published: Optional[bool] = None
|
||||
show_in_footer: Optional[bool] = None
|
||||
show_in_header: Optional[bool] = None
|
||||
display_order: Optional[int] = None
|
||||
title: str | None = Field(None, max_length=200)
|
||||
content: str | None = None
|
||||
content_format: str | None = None
|
||||
template: str | None = Field(None, max_length=50)
|
||||
meta_description: str | None = Field(None, max_length=300)
|
||||
meta_keywords: str | None = Field(None, max_length=300)
|
||||
is_published: bool | None = None
|
||||
show_in_footer: bool | None = None
|
||||
show_in_header: bool | None = None
|
||||
display_order: int | None = None
|
||||
|
||||
|
||||
class ContentPageResponse(BaseModel):
|
||||
"""Schema for content page response."""
|
||||
|
||||
id: int
|
||||
vendor_id: Optional[int]
|
||||
vendor_name: Optional[str]
|
||||
vendor_id: int | None
|
||||
vendor_name: str | None
|
||||
slug: str
|
||||
title: str
|
||||
content: str
|
||||
content_format: str
|
||||
meta_description: Optional[str]
|
||||
meta_keywords: Optional[str]
|
||||
meta_description: str | None
|
||||
meta_keywords: str | None
|
||||
is_published: bool
|
||||
published_at: Optional[str]
|
||||
published_at: str | None
|
||||
display_order: int
|
||||
show_in_footer: bool
|
||||
show_in_header: bool
|
||||
@@ -97,8 +96,8 @@ class ContentPageResponse(BaseModel):
|
||||
is_vendor_override: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
created_by: Optional[int]
|
||||
updated_by: Optional[int]
|
||||
created_by: int | None
|
||||
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(
|
||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||
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(
|
||||
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"),
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -256,4 +255,4 @@ def delete_page(
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Content page not found")
|
||||
|
||||
return None
|
||||
return
|
||||
|
||||
@@ -4,7 +4,6 @@ Admin dashboard and statistics endpoints.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
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(
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
|
||||
@@ -4,7 +4,6 @@ Marketplace import job monitoring endpoints for admin.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -20,11 +19,11 @@ router = APIRouter(prefix="/marketplace-import-jobs")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("", response_model=List[MarketplaceImportJobResponse])
|
||||
@router.get("", response_model=list[MarketplaceImportJobResponse])
|
||||
def get_all_marketplace_import_jobs(
|
||||
marketplace: Optional[str] = Query(None),
|
||||
vendor_name: Optional[str] = Query(None),
|
||||
status: Optional[str] = Query(None),
|
||||
marketplace: str | None = Query(None),
|
||||
vendor_name: str | None = Query(None),
|
||||
status: str | None = Query(None),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
@@ -9,7 +9,6 @@ Provides endpoints for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
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.core.database import get_db
|
||||
from models.database.user import User
|
||||
from models.schema.admin import (AdminNotificationCreate,
|
||||
AdminNotificationListResponse,
|
||||
AdminNotificationResponse,
|
||||
PlatformAlertCreate,
|
||||
PlatformAlertListResponse,
|
||||
PlatformAlertResolve, PlatformAlertResponse)
|
||||
from models.schema.admin import (
|
||||
AdminNotificationListResponse,
|
||||
PlatformAlertCreate,
|
||||
PlatformAlertListResponse,
|
||||
PlatformAlertResolve,
|
||||
PlatformAlertResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/notifications")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -35,8 +35,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("", response_model=AdminNotificationListResponse)
|
||||
def get_notifications(
|
||||
priority: Optional[str] = Query(None, description="Filter by priority"),
|
||||
is_read: Optional[bool] = Query(None, description="Filter by read status"),
|
||||
priority: str | None = Query(None, description="Filter by priority"),
|
||||
is_read: bool | None = Query(None, description="Filter by read status"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -87,8 +87,8 @@ def mark_all_as_read(
|
||||
|
||||
@router.get("/alerts", response_model=PlatformAlertListResponse)
|
||||
def get_platform_alerts(
|
||||
severity: Optional[str] = Query(None, description="Filter by severity"),
|
||||
is_resolved: Optional[bool] = Query(
|
||||
severity: str | None = Query(None, description="Filter by severity"),
|
||||
is_resolved: bool | None = Query(
|
||||
None, description="Filter by resolution status"
|
||||
),
|
||||
skip: int = Query(0, ge=0),
|
||||
|
||||
@@ -9,7 +9,6 @@ Provides endpoints for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
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_settings_service import admin_settings_service
|
||||
from models.database.user import User
|
||||
from models.schema.admin import (AdminSettingCreate, AdminSettingListResponse,
|
||||
AdminSettingResponse, AdminSettingUpdate)
|
||||
from models.schema.admin import (
|
||||
AdminSettingCreate,
|
||||
AdminSettingListResponse,
|
||||
AdminSettingResponse,
|
||||
AdminSettingUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/settings")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -28,8 +31,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("", response_model=AdminSettingListResponse)
|
||||
def get_all_settings(
|
||||
category: Optional[str] = Query(None, description="Filter by category"),
|
||||
is_public: Optional[bool] = Query(None, description="Filter by public flag"),
|
||||
category: str | None = Query(None, description="Filter by category"),
|
||||
is_public: bool | None = Query(None, description="Filter by public flag"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
|
||||
@@ -4,7 +4,6 @@ User management endpoints for admin.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -20,7 +19,7 @@ router = APIRouter(prefix="/users")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("", response_model=List[UserResponse])
|
||||
@router.get("", response_model=list[UserResponse])
|
||||
def get_all_users(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
|
||||
@@ -10,9 +10,8 @@ Follows the architecture pattern:
|
||||
"""
|
||||
|
||||
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 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 models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.vendor_domain import (DomainDeletionResponse,
|
||||
DomainVerificationInstructions,
|
||||
DomainVerificationResponse,
|
||||
VendorDomainCreate,
|
||||
VendorDomainListResponse,
|
||||
VendorDomainResponse,
|
||||
VendorDomainUpdate)
|
||||
from models.schema.vendor_domain import (
|
||||
DomainDeletionResponse,
|
||||
DomainVerificationInstructions,
|
||||
DomainVerificationResponse,
|
||||
VendorDomainCreate,
|
||||
VendorDomainListResponse,
|
||||
VendorDomainResponse,
|
||||
VendorDomainUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/vendors")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -20,8 +20,11 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_admin_api, get_db
|
||||
from app.services.vendor_theme_service import vendor_theme_service
|
||||
from models.database.user import User
|
||||
from models.schema.vendor_theme import (ThemePresetListResponse,
|
||||
VendorThemeResponse, VendorThemeUpdate)
|
||||
from models.schema.vendor_theme import (
|
||||
ThemePresetListResponse,
|
||||
VendorThemeResponse,
|
||||
VendorThemeUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/vendor-themes")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -4,7 +4,7 @@ Vendor management endpoints for admin.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
from datetime import UTC
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
from sqlalchemy import func
|
||||
@@ -12,18 +12,21 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import (ConfirmationRequiredException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions import ConfirmationRequiredException, VendorNotFoundException
|
||||
from app.services.admin_service import admin_service
|
||||
from app.services.stats_service import stats_service
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.stats import VendorStatsResponse
|
||||
from models.schema.vendor import (VendorCreate, VendorCreateResponse,
|
||||
VendorDetailResponse, VendorListResponse,
|
||||
VendorResponse, VendorTransferOwnership,
|
||||
VendorTransferOwnershipResponse,
|
||||
VendorUpdate)
|
||||
from models.schema.vendor import (
|
||||
VendorCreate,
|
||||
VendorCreateResponse,
|
||||
VendorDetailResponse,
|
||||
VendorListResponse,
|
||||
VendorTransferOwnership,
|
||||
VendorTransferOwnershipResponse,
|
||||
VendorUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/vendors")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -126,9 +129,9 @@ def create_vendor_with_owner(
|
||||
def get_all_vendors_admin(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
search: Optional[str] = Query(None, description="Search by name or vendor code"),
|
||||
is_active: Optional[bool] = Query(None),
|
||||
is_verified: Optional[bool] = Query(None),
|
||||
search: str | None = Query(None, description="Search by name or vendor code"),
|
||||
is_active: bool | None = Query(None),
|
||||
is_verified: bool | None = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
@@ -282,7 +285,7 @@ def transfer_vendor_ownership(
|
||||
- `confirm_transfer`: Must be true
|
||||
- `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, old_owner, new_owner = admin_service.transfer_vendor_ownership(
|
||||
@@ -304,7 +307,7 @@ def transfer_vendor_ownership(
|
||||
"username": new_owner.username,
|
||||
"email": new_owner.email,
|
||||
},
|
||||
transferred_at=datetime.now(timezone.utc),
|
||||
transferred_at=datetime.now(UTC),
|
||||
transfer_reason=transfer_data.transfer_reason,
|
||||
)
|
||||
|
||||
|
||||
@@ -14,9 +14,13 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.services.cart_service import cart_service
|
||||
from models.schema.cart import (AddToCartRequest, CartOperationResponse,
|
||||
CartResponse, ClearCartResponse,
|
||||
UpdateCartItemRequest)
|
||||
from models.schema.cart import (
|
||||
AddToCartRequest,
|
||||
CartOperationResponse,
|
||||
CartResponse,
|
||||
ClearCartResponse,
|
||||
UpdateCartItemRequest,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -7,7 +7,6 @@ No authentication required.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
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)):
|
||||
"""
|
||||
Get list of content pages for navigation (footer/header).
|
||||
|
||||
@@ -8,7 +8,6 @@ Requires customer authentication for most operations.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||
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 models.database.customer import Customer
|
||||
from models.database.user import User
|
||||
from models.schema.order import (OrderCreate, OrderDetailResponse,
|
||||
OrderListResponse, OrderResponse)
|
||||
from models.schema.order import (
|
||||
OrderCreate,
|
||||
OrderDetailResponse,
|
||||
OrderListResponse,
|
||||
OrderResponse,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -8,15 +8,17 @@ No authentication required.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.services.product_service import product_service
|
||||
from models.schema.product import (ProductDetailResponse, ProductListResponse,
|
||||
ProductResponse)
|
||||
from models.schema.product import (
|
||||
ProductDetailResponse,
|
||||
ProductListResponse,
|
||||
ProductResponse,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -27,8 +29,8 @@ def get_product_catalog(
|
||||
request: Request,
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
search: Optional[str] = Query(None, description="Search products by name"),
|
||||
is_featured: Optional[bool] = Query(
|
||||
search: str | None = Query(None, description="Search products by name"),
|
||||
is_featured: bool | None = Query(
|
||||
None, description="Filter by featured products"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
21
app/api/v1/vendor/__init__.py
vendored
21
app/api/v1/vendor/__init__.py
vendored
@@ -13,9 +13,24 @@ IMPORTANT:
|
||||
from fastapi import APIRouter
|
||||
|
||||
# Import all sub-routers (JSON API only)
|
||||
from . import (analytics, auth, content_pages, customers, dashboard, info,
|
||||
inventory, marketplace, media, notifications, orders, payments,
|
||||
products, profile, settings, team)
|
||||
from . import (
|
||||
analytics,
|
||||
auth,
|
||||
content_pages,
|
||||
customers,
|
||||
dashboard,
|
||||
info,
|
||||
inventory,
|
||||
marketplace,
|
||||
media,
|
||||
notifications,
|
||||
orders,
|
||||
payments,
|
||||
products,
|
||||
profile,
|
||||
settings,
|
||||
team,
|
||||
)
|
||||
|
||||
# Create vendor router
|
||||
router = APIRouter()
|
||||
|
||||
43
app/api/v1/vendor/content_pages.py
vendored
43
app/api/v1/vendor/content_pages.py
vendored
@@ -9,7 +9,6 @@ Vendors can:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -41,10 +40,10 @@ class VendorContentPageCreate(BaseModel):
|
||||
content_format: str = Field(
|
||||
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"
|
||||
)
|
||||
meta_keywords: Optional[str] = Field(
|
||||
meta_keywords: str | None = Field(
|
||||
None, max_length=300, description="SEO keywords"
|
||||
)
|
||||
is_published: bool = Field(default=False, description="Publish immediately")
|
||||
@@ -56,31 +55,31 @@ class VendorContentPageCreate(BaseModel):
|
||||
class VendorContentPageUpdate(BaseModel):
|
||||
"""Schema for updating a vendor content page."""
|
||||
|
||||
title: Optional[str] = Field(None, max_length=200)
|
||||
content: Optional[str] = None
|
||||
content_format: Optional[str] = None
|
||||
meta_description: Optional[str] = Field(None, max_length=300)
|
||||
meta_keywords: Optional[str] = Field(None, max_length=300)
|
||||
is_published: Optional[bool] = None
|
||||
show_in_footer: Optional[bool] = None
|
||||
show_in_header: Optional[bool] = None
|
||||
display_order: Optional[int] = None
|
||||
title: str | None = Field(None, max_length=200)
|
||||
content: str | None = None
|
||||
content_format: str | None = None
|
||||
meta_description: str | None = Field(None, max_length=300)
|
||||
meta_keywords: str | None = Field(None, max_length=300)
|
||||
is_published: bool | None = None
|
||||
show_in_footer: bool | None = None
|
||||
show_in_header: bool | None = None
|
||||
display_order: int | None = None
|
||||
|
||||
|
||||
class ContentPageResponse(BaseModel):
|
||||
"""Schema for content page response."""
|
||||
|
||||
id: int
|
||||
vendor_id: Optional[int]
|
||||
vendor_name: Optional[str]
|
||||
vendor_id: int | None
|
||||
vendor_name: str | None
|
||||
slug: str
|
||||
title: str
|
||||
content: str
|
||||
content_format: str
|
||||
meta_description: Optional[str]
|
||||
meta_keywords: Optional[str]
|
||||
meta_description: str | None
|
||||
meta_keywords: str | None
|
||||
is_published: bool
|
||||
published_at: Optional[str]
|
||||
published_at: str | None
|
||||
display_order: int
|
||||
show_in_footer: bool
|
||||
show_in_header: bool
|
||||
@@ -88,8 +87,8 @@ class ContentPageResponse(BaseModel):
|
||||
is_vendor_override: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
created_by: Optional[int]
|
||||
updated_by: Optional[int]
|
||||
created_by: int | None
|
||||
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(
|
||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
@@ -120,7 +119,7 @@ def list_vendor_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(
|
||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
@@ -284,4 +283,4 @@ def delete_vendor_page(
|
||||
# Delete
|
||||
content_page_service.delete_page(db, page_id)
|
||||
|
||||
return None
|
||||
return
|
||||
|
||||
5
app/api/v1/vendor/customers.py
vendored
5
app/api/v1/vendor/customers.py
vendored
@@ -5,7 +5,6 @@ Vendor customer management endpoints.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -24,8 +23,8 @@ logger = logging.getLogger(__name__)
|
||||
def get_vendor_customers(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
search: Optional[str] = Query(None),
|
||||
is_active: Optional[bool] = Query(None),
|
||||
search: str | None = Query(None),
|
||||
is_active: bool | None = Query(None),
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
2
app/api/v1/vendor/dashboard.py
vendored
2
app/api/v1/vendor/dashboard.py
vendored
@@ -11,9 +11,7 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_vendor_api
|
||||
from app.core.database import get_db
|
||||
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.vendor import Vendor
|
||||
|
||||
router = APIRouter(prefix="/dashboard")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
2
app/api/v1/vendor/info.py
vendored
2
app/api/v1/vendor/info.py
vendored
@@ -16,7 +16,7 @@ from sqlalchemy.orm import Session
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import VendorNotFoundException
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.vendor import VendorDetailResponse, VendorResponse
|
||||
from models.schema.vendor import VendorDetailResponse
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
18
app/api/v1/vendor/inventory.py
vendored
18
app/api/v1/vendor/inventory.py
vendored
@@ -1,6 +1,5 @@
|
||||
# app/api/v1/vendor/inventory.py
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
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 models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.inventory import (InventoryAdjust, InventoryCreate,
|
||||
InventoryListResponse, InventoryReserve,
|
||||
InventoryResponse, InventoryUpdate,
|
||||
ProductInventorySummary)
|
||||
from models.schema.inventory import (
|
||||
InventoryAdjust,
|
||||
InventoryCreate,
|
||||
InventoryListResponse,
|
||||
InventoryReserve,
|
||||
InventoryResponse,
|
||||
InventoryUpdate,
|
||||
ProductInventorySummary,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -90,8 +94,8 @@ def get_product_inventory(
|
||||
def get_vendor_inventory(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
location: Optional[str] = Query(None),
|
||||
low_stock: Optional[int] = Query(None, ge=0),
|
||||
location: str | None = Query(None),
|
||||
low_stock: int | None = Query(None, ge=0),
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
14
app/api/v1/vendor/marketplace.py
vendored
14
app/api/v1/vendor/marketplace.py
vendored
@@ -5,22 +5,22 @@ Vendor context is automatically injected by middleware.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_vendor_api
|
||||
from app.core.database import get_db
|
||||
from app.services.marketplace_import_job_service import \
|
||||
marketplace_import_job_service
|
||||
from app.services.marketplace_import_job_service import marketplace_import_job_service
|
||||
from app.tasks.background_tasks import process_marketplace_import
|
||||
from middleware.decorators import rate_limit
|
||||
from middleware.vendor_context import require_vendor_context # IMPORTANT
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse)
|
||||
from models.schema.marketplace_import_job import (
|
||||
MarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -93,9 +93,9 @@ def get_marketplace_import_status(
|
||||
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(
|
||||
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
|
||||
marketplace: str | None = Query(None, description="Filter by marketplace"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
|
||||
9
app/api/v1/vendor/media.py
vendored
9
app/api/v1/vendor/media.py
vendored
@@ -5,7 +5,6 @@ Vendor media and file management endpoints.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Query, UploadFile
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -24,8 +23,8 @@ logger = logging.getLogger(__name__)
|
||||
def get_media_library(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
media_type: Optional[str] = Query(None, description="image, video, document"),
|
||||
search: Optional[str] = Query(None),
|
||||
media_type: str | None = Query(None, description="image, video, document"),
|
||||
search: str | None = Query(None),
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -52,7 +51,7 @@ def get_media_library(
|
||||
@router.post("/upload")
|
||||
async def upload_media(
|
||||
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()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -78,7 +77,7 @@ async def upload_media(
|
||||
@router.post("/upload/multiple")
|
||||
async def upload_multiple_media(
|
||||
files: list[UploadFile] = File(...),
|
||||
folder: Optional[str] = Query(None),
|
||||
folder: str | None = Query(None),
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
3
app/api/v1/vendor/notifications.py
vendored
3
app/api/v1/vendor/notifications.py
vendored
@@ -5,7 +5,6 @@ Vendor notification management endpoints.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -24,7 +23,7 @@ logger = logging.getLogger(__name__)
|
||||
def get_notifications(
|
||||
skip: int = Query(0, ge=0),
|
||||
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()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
17
app/api/v1/vendor/orders.py
vendored
17
app/api/v1/vendor/orders.py
vendored
@@ -4,9 +4,8 @@ Vendor order management endpoints.
|
||||
"""
|
||||
|
||||
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 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 middleware.vendor_context import require_vendor_context
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
from models.schema.order import (OrderDetailResponse, OrderListResponse,
|
||||
OrderResponse, OrderUpdate)
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.order import (
|
||||
OrderDetailResponse,
|
||||
OrderListResponse,
|
||||
OrderResponse,
|
||||
OrderUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/orders")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -26,8 +29,8 @@ logger = logging.getLogger(__name__)
|
||||
def get_vendor_orders(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
status: Optional[str] = Query(None, description="Filter by order status"),
|
||||
customer_id: Optional[int] = Query(None, description="Filter by customer"),
|
||||
status: str | None = Query(None, description="Filter by order status"),
|
||||
customer_id: int | None = Query(None, description="Filter by customer"),
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
15
app/api/v1/vendor/products.py
vendored
15
app/api/v1/vendor/products.py
vendored
@@ -4,7 +4,6 @@ Vendor product catalog management endpoints.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
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 models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.product import (ProductCreate, ProductDetailResponse,
|
||||
ProductListResponse, ProductResponse,
|
||||
ProductUpdate)
|
||||
from models.schema.product import (
|
||||
ProductCreate,
|
||||
ProductDetailResponse,
|
||||
ProductListResponse,
|
||||
ProductResponse,
|
||||
ProductUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/products")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -27,8 +30,8 @@ logger = logging.getLogger(__name__)
|
||||
def get_vendor_products(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
is_active: Optional[bool] = Query(None),
|
||||
is_featured: Optional[bool] = Query(None),
|
||||
is_active: bool | None = Query(None),
|
||||
is_featured: bool | None = Query(None),
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
33
app/api/v1/vendor/team.py
vendored
33
app/api/v1/vendor/team.py
vendored
@@ -11,25 +11,34 @@ Implements complete team management with:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import (get_current_vendor_api, get_user_permissions,
|
||||
require_vendor_owner, require_vendor_permission)
|
||||
from app.api.deps import (
|
||||
get_current_vendor_api,
|
||||
get_user_permissions,
|
||||
require_vendor_owner,
|
||||
require_vendor_permission,
|
||||
)
|
||||
from app.core.database import get_db
|
||||
from app.core.permissions import VendorPermissions
|
||||
from app.services.vendor_team_service import vendor_team_service
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.team import (BulkRemoveRequest, BulkRemoveResponse,
|
||||
InvitationAccept, InvitationAcceptResponse,
|
||||
InvitationResponse, RoleListResponse,
|
||||
RoleResponse, TeamMemberInvite,
|
||||
TeamMemberListResponse, TeamMemberResponse,
|
||||
TeamMemberUpdate, TeamStatistics,
|
||||
UserPermissionsResponse)
|
||||
from models.schema.team import (
|
||||
BulkRemoveRequest,
|
||||
BulkRemoveResponse,
|
||||
InvitationAccept,
|
||||
InvitationAcceptResponse,
|
||||
InvitationResponse,
|
||||
RoleListResponse,
|
||||
TeamMemberInvite,
|
||||
TeamMemberListResponse,
|
||||
TeamMemberResponse,
|
||||
TeamMemberUpdate,
|
||||
TeamStatistics,
|
||||
UserPermissionsResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/team")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -382,7 +391,7 @@ def list_roles(
|
||||
@router.get("/me/permissions", response_model=UserPermissionsResponse)
|
||||
def get_my_permissions(
|
||||
request: Request,
|
||||
permissions: List[str] = Depends(get_user_permissions),
|
||||
permissions: list[str] = Depends(get_user_permissions),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
):
|
||||
"""
|
||||
|
||||
@@ -13,7 +13,6 @@ Note: Environment detection is handled by app.core.environment module.
|
||||
This module focuses purely on configuration storage and validation.
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
@@ -82,7 +81,7 @@ class Settings(BaseSettings):
|
||||
# =============================================================================
|
||||
# MIDDLEWARE & SECURITY
|
||||
# =============================================================================
|
||||
allowed_hosts: List[str] = ["*"] # Configure for production
|
||||
allowed_hosts: list[str] = ["*"] # Configure for production
|
||||
|
||||
# Rate Limiting
|
||||
rate_limit_enabled: bool = True
|
||||
@@ -93,7 +92,7 @@ class Settings(BaseSettings):
|
||||
# LOGGING
|
||||
# =============================================================================
|
||||
log_level: str = "INFO"
|
||||
log_file: Optional[str] = None
|
||||
log_file: str | None = None
|
||||
|
||||
# =============================================================================
|
||||
# PLATFORM DOMAIN CONFIGURATION
|
||||
@@ -138,9 +137,13 @@ settings = Settings()
|
||||
# ENVIRONMENT UTILITIES - Module-level functions
|
||||
# =============================================================================
|
||||
# Import environment detection utilities
|
||||
from app.core.environment import (get_environment, is_development,
|
||||
is_production, is_staging,
|
||||
should_use_secure_cookies)
|
||||
from app.core.environment import (
|
||||
get_environment,
|
||||
is_development,
|
||||
is_production,
|
||||
is_staging,
|
||||
should_use_secure_cookies,
|
||||
)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -31,18 +31,18 @@ def get_environment() -> EnvironmentType:
|
||||
env = os.getenv("ENV", "").lower()
|
||||
if env in ["development", "dev", "local"]:
|
||||
return "development"
|
||||
elif env in ["staging", "stage"]:
|
||||
if env in ["staging", "stage"]:
|
||||
return "staging"
|
||||
elif env in ["production", "prod"]:
|
||||
if env in ["production", "prod"]:
|
||||
return "production"
|
||||
|
||||
# Priority 2: ENVIRONMENT variable
|
||||
env = os.getenv("ENVIRONMENT", "").lower()
|
||||
if env in ["development", "dev", "local"]:
|
||||
return "development"
|
||||
elif env in ["staging", "stage"]:
|
||||
if env in ["staging", "stage"]:
|
||||
return "staging"
|
||||
elif env in ["production", "prod"]:
|
||||
if env in ["production", "prod"]:
|
||||
return "production"
|
||||
|
||||
# Priority 3: Auto-detect from common indicators
|
||||
|
||||
@@ -16,7 +16,7 @@ from sqlalchemy import text
|
||||
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
from .database import SessionLocal, engine
|
||||
from .database import engine
|
||||
from .logging import setup_logging
|
||||
|
||||
# Remove this import if not needed: from models.database.base import Base
|
||||
@@ -60,7 +60,6 @@ def check_database_ready():
|
||||
def get_migration_status():
|
||||
"""Get current Alembic migration status."""
|
||||
try:
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
|
||||
alembic_cfg = Config("alembic.ini")
|
||||
|
||||
@@ -7,8 +7,8 @@ This module defines:
|
||||
- Permission groups (for easier role creation)
|
||||
- Permission checking utilities
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from typing import List, Set
|
||||
|
||||
|
||||
class VendorPermissions(str, Enum):
|
||||
@@ -78,10 +78,10 @@ class PermissionGroups:
|
||||
"""Pre-defined permission groups for common roles."""
|
||||
|
||||
# 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: Set[str] = {
|
||||
MANAGER: set[str] = {
|
||||
VendorPermissions.DASHBOARD_VIEW.value,
|
||||
VendorPermissions.PRODUCTS_VIEW.value,
|
||||
VendorPermissions.PRODUCTS_CREATE.value,
|
||||
@@ -113,7 +113,7 @@ class PermissionGroups:
|
||||
}
|
||||
|
||||
# Staff - Can view and edit products/orders but limited access
|
||||
STAFF: Set[str] = {
|
||||
STAFF: set[str] = {
|
||||
VendorPermissions.DASHBOARD_VIEW.value,
|
||||
VendorPermissions.PRODUCTS_VIEW.value,
|
||||
VendorPermissions.PRODUCTS_CREATE.value,
|
||||
@@ -127,7 +127,7 @@ class PermissionGroups:
|
||||
}
|
||||
|
||||
# Support - Can view and assist with orders/customers
|
||||
SUPPORT: Set[str] = {
|
||||
SUPPORT: set[str] = {
|
||||
VendorPermissions.DASHBOARD_VIEW.value,
|
||||
VendorPermissions.PRODUCTS_VIEW.value,
|
||||
VendorPermissions.ORDERS_VIEW.value,
|
||||
@@ -137,7 +137,7 @@ class PermissionGroups:
|
||||
}
|
||||
|
||||
# Viewer - Read-only access
|
||||
VIEWER: Set[str] = {
|
||||
VIEWER: set[str] = {
|
||||
VendorPermissions.DASHBOARD_VIEW.value,
|
||||
VendorPermissions.PRODUCTS_VIEW.value,
|
||||
VendorPermissions.STOCK_VIEW.value,
|
||||
@@ -147,7 +147,7 @@ class PermissionGroups:
|
||||
}
|
||||
|
||||
# Marketing - Focused on marketing and customer communication
|
||||
MARKETING: Set[str] = {
|
||||
MARKETING: set[str] = {
|
||||
VendorPermissions.DASHBOARD_VIEW.value,
|
||||
VendorPermissions.CUSTOMERS_VIEW.value,
|
||||
VendorPermissions.CUSTOMERS_EXPORT.value,
|
||||
@@ -162,34 +162,34 @@ class PermissionChecker:
|
||||
"""Utility class for permission checking."""
|
||||
|
||||
@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."""
|
||||
return required_permission in permissions
|
||||
|
||||
@staticmethod
|
||||
def has_any_permission(
|
||||
permissions: List[str], required_permissions: List[str]
|
||||
permissions: list[str], required_permissions: list[str]
|
||||
) -> bool:
|
||||
"""Check if a permission list contains ANY of the required permissions."""
|
||||
return any(perm in permissions for perm in required_permissions)
|
||||
|
||||
@staticmethod
|
||||
def has_all_permissions(
|
||||
permissions: List[str], required_permissions: List[str]
|
||||
permissions: list[str], required_permissions: list[str]
|
||||
) -> bool:
|
||||
"""Check if a permission list contains ALL of the required permissions."""
|
||||
return all(perm in permissions for perm in required_permissions)
|
||||
|
||||
@staticmethod
|
||||
def get_missing_permissions(
|
||||
permissions: List[str], required_permissions: List[str]
|
||||
) -> List[str]:
|
||||
permissions: list[str], required_permissions: list[str]
|
||||
) -> list[str]:
|
||||
"""Get list of missing permissions."""
|
||||
return [perm for perm in required_permissions if perm not in permissions]
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
@@ -7,108 +7,178 @@ messages, and HTTP status mappings.
|
||||
"""
|
||||
|
||||
# Admin exceptions
|
||||
from .admin import (AdminOperationException, BulkOperationException,
|
||||
CannotModifyAdminException, CannotModifySelfException,
|
||||
ConfirmationRequiredException, InvalidAdminActionException,
|
||||
UserNotFoundException, UserStatusChangeException,
|
||||
VendorVerificationException)
|
||||
from .admin import (
|
||||
AdminOperationException,
|
||||
BulkOperationException,
|
||||
CannotModifyAdminException,
|
||||
CannotModifySelfException,
|
||||
ConfirmationRequiredException,
|
||||
InvalidAdminActionException,
|
||||
UserNotFoundException,
|
||||
UserStatusChangeException,
|
||||
VendorVerificationException,
|
||||
)
|
||||
|
||||
# Authentication exceptions
|
||||
from .auth import (AdminRequiredException, InsufficientPermissionsException,
|
||||
InvalidCredentialsException, InvalidTokenException,
|
||||
TokenExpiredException, UserAlreadyExistsException,
|
||||
UserNotActiveException)
|
||||
from .auth import (
|
||||
AdminRequiredException,
|
||||
InsufficientPermissionsException,
|
||||
InvalidCredentialsException,
|
||||
InvalidTokenException,
|
||||
TokenExpiredException,
|
||||
UserAlreadyExistsException,
|
||||
UserNotActiveException,
|
||||
)
|
||||
|
||||
# Base exceptions
|
||||
from .base import (AuthenticationException, AuthorizationException,
|
||||
BusinessLogicException, ConflictException,
|
||||
ExternalServiceException, RateLimitException,
|
||||
ResourceNotFoundException, ServiceUnavailableException,
|
||||
ValidationException, WizamartException)
|
||||
from .base import (
|
||||
AuthenticationException,
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ExternalServiceException,
|
||||
RateLimitException,
|
||||
ResourceNotFoundException,
|
||||
ServiceUnavailableException,
|
||||
ValidationException,
|
||||
WizamartException,
|
||||
)
|
||||
|
||||
# Cart exceptions
|
||||
from .cart import (CartItemNotFoundException, CartValidationException,
|
||||
EmptyCartException, InsufficientInventoryForCartException,
|
||||
InvalidCartQuantityException,
|
||||
ProductNotAvailableForCartException)
|
||||
from .cart import (
|
||||
CartItemNotFoundException,
|
||||
CartValidationException,
|
||||
EmptyCartException,
|
||||
InsufficientInventoryForCartException,
|
||||
InvalidCartQuantityException,
|
||||
ProductNotAvailableForCartException,
|
||||
)
|
||||
|
||||
# Customer exceptions
|
||||
from .customer import (CustomerAlreadyExistsException,
|
||||
CustomerAuthorizationException,
|
||||
CustomerNotActiveException, CustomerNotFoundException,
|
||||
CustomerValidationException,
|
||||
DuplicateCustomerEmailException,
|
||||
InvalidCustomerCredentialsException)
|
||||
from .customer import (
|
||||
CustomerAlreadyExistsException,
|
||||
CustomerAuthorizationException,
|
||||
CustomerNotActiveException,
|
||||
CustomerNotFoundException,
|
||||
CustomerValidationException,
|
||||
DuplicateCustomerEmailException,
|
||||
InvalidCustomerCredentialsException,
|
||||
)
|
||||
|
||||
# Inventory exceptions
|
||||
from .inventory import (InsufficientInventoryException,
|
||||
InvalidInventoryOperationException,
|
||||
InvalidQuantityException, InventoryNotFoundException,
|
||||
InventoryValidationException,
|
||||
LocationNotFoundException, NegativeInventoryException)
|
||||
from .inventory import (
|
||||
InsufficientInventoryException,
|
||||
InvalidInventoryOperationException,
|
||||
InvalidQuantityException,
|
||||
InventoryNotFoundException,
|
||||
InventoryValidationException,
|
||||
LocationNotFoundException,
|
||||
NegativeInventoryException,
|
||||
)
|
||||
|
||||
# Marketplace import job exceptions
|
||||
from .marketplace_import_job import (ImportJobAlreadyProcessingException,
|
||||
ImportJobCannotBeCancelledException,
|
||||
ImportJobCannotBeDeletedException,
|
||||
ImportJobNotFoundException,
|
||||
ImportJobNotOwnedException,
|
||||
ImportRateLimitException,
|
||||
InvalidImportDataException,
|
||||
InvalidMarketplaceException,
|
||||
MarketplaceConnectionException,
|
||||
MarketplaceDataParsingException,
|
||||
MarketplaceImportException)
|
||||
from .marketplace_import_job import (
|
||||
ImportJobAlreadyProcessingException,
|
||||
ImportJobCannotBeCancelledException,
|
||||
ImportJobCannotBeDeletedException,
|
||||
ImportJobNotFoundException,
|
||||
ImportJobNotOwnedException,
|
||||
ImportRateLimitException,
|
||||
InvalidImportDataException,
|
||||
InvalidMarketplaceException,
|
||||
MarketplaceConnectionException,
|
||||
MarketplaceDataParsingException,
|
||||
MarketplaceImportException,
|
||||
)
|
||||
|
||||
# Marketplace product exceptions
|
||||
from .marketplace_product import (InvalidGTINException,
|
||||
InvalidMarketplaceProductDataException,
|
||||
MarketplaceProductAlreadyExistsException,
|
||||
MarketplaceProductCSVImportException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MarketplaceProductValidationException)
|
||||
from .marketplace_product import (
|
||||
InvalidGTINException,
|
||||
InvalidMarketplaceProductDataException,
|
||||
MarketplaceProductAlreadyExistsException,
|
||||
MarketplaceProductCSVImportException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MarketplaceProductValidationException,
|
||||
)
|
||||
|
||||
# Order exceptions
|
||||
from .order import (InvalidOrderStatusException, OrderAlreadyExistsException,
|
||||
OrderCannotBeCancelledException, OrderNotFoundException,
|
||||
OrderValidationException)
|
||||
from .order import (
|
||||
InvalidOrderStatusException,
|
||||
OrderAlreadyExistsException,
|
||||
OrderCannotBeCancelledException,
|
||||
OrderNotFoundException,
|
||||
OrderValidationException,
|
||||
)
|
||||
|
||||
# Product exceptions
|
||||
from .product import (CannotDeleteProductWithInventoryException,
|
||||
CannotDeleteProductWithOrdersException,
|
||||
InvalidProductDataException,
|
||||
ProductAlreadyExistsException, ProductNotActiveException,
|
||||
ProductNotFoundException, ProductNotInCatalogException,
|
||||
ProductValidationException)
|
||||
from .product import (
|
||||
CannotDeleteProductWithInventoryException,
|
||||
CannotDeleteProductWithOrdersException,
|
||||
InvalidProductDataException,
|
||||
ProductAlreadyExistsException,
|
||||
ProductNotActiveException,
|
||||
ProductNotFoundException,
|
||||
ProductNotInCatalogException,
|
||||
ProductValidationException,
|
||||
)
|
||||
|
||||
# Team exceptions
|
||||
from .team import (CannotModifyOwnRoleException, CannotRemoveOwnerException,
|
||||
InsufficientTeamPermissionsException,
|
||||
InvalidInvitationDataException,
|
||||
InvalidInvitationTokenException, InvalidRoleException,
|
||||
MaxTeamMembersReachedException, RoleNotFoundException,
|
||||
TeamInvitationAlreadyAcceptedException,
|
||||
TeamInvitationExpiredException,
|
||||
TeamInvitationNotFoundException,
|
||||
TeamMemberAlreadyExistsException,
|
||||
TeamMemberNotFoundException, TeamValidationException,
|
||||
UnauthorizedTeamActionException)
|
||||
from .team import (
|
||||
CannotModifyOwnRoleException,
|
||||
CannotRemoveOwnerException,
|
||||
InsufficientTeamPermissionsException,
|
||||
InvalidInvitationDataException,
|
||||
InvalidInvitationTokenException,
|
||||
InvalidRoleException,
|
||||
MaxTeamMembersReachedException,
|
||||
RoleNotFoundException,
|
||||
TeamInvitationAlreadyAcceptedException,
|
||||
TeamInvitationExpiredException,
|
||||
TeamInvitationNotFoundException,
|
||||
TeamMemberAlreadyExistsException,
|
||||
TeamMemberNotFoundException,
|
||||
TeamValidationException,
|
||||
UnauthorizedTeamActionException,
|
||||
)
|
||||
|
||||
# Vendor exceptions
|
||||
from .vendor import (InvalidVendorDataException, MaxVendorsReachedException,
|
||||
UnauthorizedVendorAccessException,
|
||||
VendorAlreadyExistsException, VendorNotActiveException,
|
||||
VendorNotFoundException, VendorNotVerifiedException,
|
||||
VendorValidationException)
|
||||
from .vendor import (
|
||||
InvalidVendorDataException,
|
||||
MaxVendorsReachedException,
|
||||
UnauthorizedVendorAccessException,
|
||||
VendorAlreadyExistsException,
|
||||
VendorNotActiveException,
|
||||
VendorNotFoundException,
|
||||
VendorNotVerifiedException,
|
||||
VendorValidationException,
|
||||
)
|
||||
|
||||
# Vendor domain exceptions
|
||||
from .vendor_domain import (DNSVerificationException,
|
||||
DomainAlreadyVerifiedException,
|
||||
DomainNotVerifiedException,
|
||||
DomainVerificationFailedException,
|
||||
InvalidDomainFormatException,
|
||||
MaxDomainsReachedException,
|
||||
MultiplePrimaryDomainsException,
|
||||
ReservedDomainException,
|
||||
UnauthorizedDomainAccessException,
|
||||
VendorDomainAlreadyExistsException,
|
||||
VendorDomainNotFoundException)
|
||||
from .vendor_domain import (
|
||||
DNSVerificationException,
|
||||
DomainAlreadyVerifiedException,
|
||||
DomainNotVerifiedException,
|
||||
DomainVerificationFailedException,
|
||||
InvalidDomainFormatException,
|
||||
MaxDomainsReachedException,
|
||||
MultiplePrimaryDomainsException,
|
||||
ReservedDomainException,
|
||||
UnauthorizedDomainAccessException,
|
||||
VendorDomainAlreadyExistsException,
|
||||
VendorDomainNotFoundException,
|
||||
)
|
||||
|
||||
# Vendor theme exceptions
|
||||
from .vendor_theme import (InvalidColorFormatException,
|
||||
InvalidFontFamilyException,
|
||||
InvalidThemeDataException, ThemeOperationException,
|
||||
ThemePresetAlreadyAppliedException,
|
||||
ThemePresetNotFoundException,
|
||||
ThemeValidationException,
|
||||
VendorThemeNotFoundException)
|
||||
from .vendor_theme import (
|
||||
InvalidColorFormatException,
|
||||
InvalidFontFamilyException,
|
||||
InvalidThemeDataException,
|
||||
ThemeOperationException,
|
||||
ThemePresetAlreadyAppliedException,
|
||||
ThemePresetNotFoundException,
|
||||
ThemeValidationException,
|
||||
VendorThemeNotFoundException,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base exceptions
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
Admin operations specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (AuthorizationException, BusinessLogicException,
|
||||
ResourceNotFoundException, ValidationException)
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class UserNotFoundException(ResourceNotFoundException):
|
||||
@@ -36,7 +40,7 @@ class UserStatusChangeException(BusinessLogicException):
|
||||
user_id: int,
|
||||
current_status: str,
|
||||
attempted_action: str,
|
||||
reason: Optional[str] = None,
|
||||
reason: str | None = None,
|
||||
):
|
||||
message = f"Cannot {attempted_action} user {user_id} (current status: {current_status})"
|
||||
if reason:
|
||||
@@ -61,7 +65,7 @@ class ShopVerificationException(BusinessLogicException):
|
||||
self,
|
||||
shop_id: int,
|
||||
reason: str,
|
||||
current_verification_status: Optional[bool] = None,
|
||||
current_verification_status: bool | None = None,
|
||||
):
|
||||
details = {
|
||||
"shop_id": shop_id,
|
||||
@@ -85,8 +89,8 @@ class AdminOperationException(BusinessLogicException):
|
||||
self,
|
||||
operation: str,
|
||||
reason: str,
|
||||
target_type: Optional[str] = None,
|
||||
target_id: Optional[str] = None,
|
||||
target_type: str | None = None,
|
||||
target_id: str | None = None,
|
||||
):
|
||||
message = f"Admin operation '{operation}' failed: {reason}"
|
||||
|
||||
@@ -142,7 +146,7 @@ class InvalidAdminActionException(ValidationException):
|
||||
self,
|
||||
action: str,
|
||||
reason: str,
|
||||
valid_actions: Optional[list] = None,
|
||||
valid_actions: list | None = None,
|
||||
):
|
||||
details = {
|
||||
"action": action,
|
||||
@@ -167,7 +171,7 @@ class BulkOperationException(BusinessLogicException):
|
||||
operation: str,
|
||||
total_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"
|
||||
|
||||
@@ -194,7 +198,7 @@ class ConfirmationRequiredException(BusinessLogicException):
|
||||
def __init__(
|
||||
self,
|
||||
operation: str,
|
||||
message: Optional[str] = None,
|
||||
message: str | None = None,
|
||||
confirmation_param: str = "confirm",
|
||||
):
|
||||
if not message:
|
||||
@@ -217,7 +221,7 @@ class VendorVerificationException(BusinessLogicException):
|
||||
self,
|
||||
vendor_id: int,
|
||||
reason: str,
|
||||
current_verification_status: Optional[bool] = None,
|
||||
current_verification_status: bool | None = None,
|
||||
):
|
||||
details = {
|
||||
"vendor_id": vendor_id,
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
Authentication and authorization specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import (AuthenticationException, AuthorizationException,
|
||||
ConflictException)
|
||||
from .base import AuthenticationException, AuthorizationException, ConflictException
|
||||
|
||||
|
||||
class InvalidCredentialsException(AuthenticationException):
|
||||
@@ -45,7 +43,7 @@ class InsufficientPermissionsException(AuthorizationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Insufficient permissions for this action",
|
||||
required_permission: Optional[str] = None,
|
||||
required_permission: str | None = None,
|
||||
):
|
||||
details = {}
|
||||
if required_permission:
|
||||
@@ -84,7 +82,7 @@ class UserAlreadyExistsException(ConflictException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "User already exists",
|
||||
field: Optional[str] = None,
|
||||
field: str | None = None,
|
||||
):
|
||||
details = {}
|
||||
if field:
|
||||
|
||||
@@ -8,7 +8,7 @@ This module provides classes and functions for:
|
||||
- Standardized error response structure
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
|
||||
class WizamartException(Exception):
|
||||
@@ -19,7 +19,7 @@ class WizamartException(Exception):
|
||||
message: str,
|
||||
error_code: str,
|
||||
status_code: int = 400,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
self.message = message
|
||||
self.error_code = error_code
|
||||
@@ -27,7 +27,7 @@ class WizamartException(Exception):
|
||||
self.details = details or {}
|
||||
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."""
|
||||
result = {
|
||||
"error_code": self.error_code,
|
||||
@@ -45,8 +45,8 @@ class ValidationException(WizamartException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
field: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
validation_details = details or {}
|
||||
if field:
|
||||
@@ -67,7 +67,7 @@ class AuthenticationException(WizamartException):
|
||||
self,
|
||||
message: str = "Authentication failed",
|
||||
error_code: str = "AUTHENTICATION_FAILED",
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -84,7 +84,7 @@ class AuthorizationException(WizamartException):
|
||||
self,
|
||||
message: str = "Access denied",
|
||||
error_code: str = "AUTHORIZATION_FAILED",
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -101,8 +101,8 @@ class ResourceNotFoundException(WizamartException):
|
||||
self,
|
||||
resource_type: str,
|
||||
identifier: str,
|
||||
message: Optional[str] = None,
|
||||
error_code: Optional[str] = None,
|
||||
message: str | None = None,
|
||||
error_code: str | None = None,
|
||||
):
|
||||
if not message:
|
||||
message = f"{resource_type} with identifier '{identifier}' not found"
|
||||
@@ -127,7 +127,7 @@ class ConflictException(WizamartException):
|
||||
self,
|
||||
message: str,
|
||||
error_code: str = "RESOURCE_CONFLICT",
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -144,7 +144,7 @@ class BusinessLogicException(WizamartException):
|
||||
self,
|
||||
message: str,
|
||||
error_code: str,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -162,7 +162,7 @@ class ExternalServiceException(WizamartException):
|
||||
message: str,
|
||||
service_name: str,
|
||||
error_code: str = "EXTERNAL_SERVICE_ERROR",
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
service_details = details or {}
|
||||
service_details["service_name"] = service_name
|
||||
@@ -181,8 +181,8 @@ class RateLimitException(WizamartException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Rate limit exceeded",
|
||||
retry_after: Optional[int] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
retry_after: int | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
rate_limit_details = details or {}
|
||||
if retry_after:
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
Shopping cart specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import (BusinessLogicException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class CartItemNotFoundException(ResourceNotFoundException):
|
||||
@@ -36,8 +34,8 @@ class CartValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Cart validation failed",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[dict] = None,
|
||||
field: str | None = None,
|
||||
details: dict | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -75,7 +73,7 @@ class InvalidCartQuantityException(ValidationException):
|
||||
"""Raised when cart quantity is invalid."""
|
||||
|
||||
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:
|
||||
message = f"Quantity must be at least {min_quantity}"
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
Customer management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (AuthenticationException, BusinessLogicException,
|
||||
ConflictException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import (
|
||||
AuthenticationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class CustomerNotFoundException(ResourceNotFoundException):
|
||||
@@ -71,8 +75,8 @@ class CustomerValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Customer validation failed",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(message=message, field=field, details=details)
|
||||
self.error_code = "CUSTOMER_VALIDATION_FAILED"
|
||||
|
||||
@@ -5,9 +5,10 @@ Error Page Renderer
|
||||
Renders context-aware error pages using Jinja2 templates.
|
||||
Handles fallback logic and context-specific customization.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
@@ -60,7 +61,7 @@ class ErrorPageRenderer:
|
||||
status_code: int,
|
||||
error_code: str,
|
||||
message: str,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
show_debug: bool = False,
|
||||
) -> HTMLResponse:
|
||||
"""
|
||||
@@ -190,9 +191,9 @@ class ErrorPageRenderer:
|
||||
status_code: int,
|
||||
error_code: str,
|
||||
message: str,
|
||||
details: Optional[Dict[str, Any]],
|
||||
details: dict[str, Any] | None,
|
||||
show_debug: bool,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""Prepare data dictionary for error template."""
|
||||
# Get friendly status name
|
||||
status_name = ErrorPageRenderer.STATUS_CODE_NAMES.get(
|
||||
@@ -229,7 +230,7 @@ class ErrorPageRenderer:
|
||||
@staticmethod
|
||||
def _get_context_data(
|
||||
request: Request, context_type: RequestContext
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""Get context-specific data for error templates."""
|
||||
data = {}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ This module provides classes and functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from fastapi import HTTPException, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
@@ -280,7 +279,7 @@ def setup_exception_handlers(app):
|
||||
request=request,
|
||||
status_code=404,
|
||||
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},
|
||||
show_debug=True,
|
||||
)
|
||||
@@ -364,10 +363,10 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
|
||||
if context_type == RequestContext.ADMIN:
|
||||
logger.debug("Redirecting to /admin/login")
|
||||
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")
|
||||
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)
|
||||
# Calculate base_url for proper routing (supports domain, subdomain, and path-based access)
|
||||
vendor = getattr(request.state, "vendor", None)
|
||||
@@ -390,10 +389,9 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
|
||||
login_url = f"{base_url}shop/account/login"
|
||||
logger.debug(f"Redirecting to {login_url}")
|
||||
return RedirectResponse(url=login_url, status_code=302)
|
||||
else:
|
||||
# Fallback to root for unknown contexts
|
||||
logger.debug("Unknown context, redirecting to /")
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
# Fallback to root for unknown contexts
|
||||
logger.debug("Unknown context, redirecting to /")
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
|
||||
|
||||
# Utility functions for common exception scenarios
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
Inventory management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (BusinessLogicException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class InventoryNotFoundException(ResourceNotFoundException):
|
||||
@@ -58,8 +57,8 @@ class InvalidInventoryOperationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
operation: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
operation: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
@@ -80,8 +79,8 @@ class InventoryValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Inventory validation failed",
|
||||
field: Optional[str] = None,
|
||||
validation_errors: Optional[Dict[str, str]] = None,
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
Marketplace import specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (AuthorizationException, BusinessLogicException,
|
||||
ExternalServiceException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ExternalServiceException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceImportException(BusinessLogicException):
|
||||
@@ -17,8 +21,8 @@ class MarketplaceImportException(BusinessLogicException):
|
||||
self,
|
||||
message: str,
|
||||
error_code: str = "MARKETPLACE_IMPORT_ERROR",
|
||||
marketplace: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
marketplace: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
@@ -48,7 +52,7 @@ class ImportJobNotFoundException(ResourceNotFoundException):
|
||||
class ImportJobNotOwnedException(AuthorizationException):
|
||||
"""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}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
@@ -66,9 +70,9 @@ class InvalidImportDataException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid import data",
|
||||
field: Optional[str] = None,
|
||||
row_number: Optional[int] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
row_number: int | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
@@ -132,7 +136,7 @@ class MarketplaceDataParsingException(ValidationException):
|
||||
self,
|
||||
marketplace: str,
|
||||
message: str = "Failed to parse marketplace data",
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
if not details:
|
||||
details = {}
|
||||
@@ -152,7 +156,7 @@ class ImportRateLimitException(BusinessLogicException):
|
||||
self,
|
||||
max_imports: int,
|
||||
time_window: str,
|
||||
retry_after: Optional[int] = None,
|
||||
retry_after: int | None = None,
|
||||
):
|
||||
details = {
|
||||
"max_imports": max_imports,
|
||||
@@ -172,7 +176,7 @@ class ImportRateLimitException(BusinessLogicException):
|
||||
class InvalidMarketplaceException(ValidationException):
|
||||
"""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}
|
||||
if supported_marketplaces:
|
||||
details["supported_marketplaces"] = supported_marketplaces
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
MarketplaceProduct management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (BusinessLogicException, ConflictException,
|
||||
ResourceNotFoundException, ValidationException)
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceProductNotFoundException(ResourceNotFoundException):
|
||||
@@ -38,8 +42,8 @@ class InvalidMarketplaceProductDataException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid product data",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -55,8 +59,8 @@ class MarketplaceProductValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
field: Optional[str] = None,
|
||||
validation_errors: Optional[Dict[str, str]] = None,
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
@@ -88,8 +92,8 @@ class MarketplaceProductCSVImportException(BusinessLogicException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "MarketplaceProduct CSV import failed",
|
||||
row_number: Optional[int] = None,
|
||||
errors: Optional[Dict[str, Any]] = None,
|
||||
row_number: int | None = None,
|
||||
errors: dict[str, Any] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if row_number:
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
Order management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import (BusinessLogicException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class OrderNotFoundException(ResourceNotFoundException):
|
||||
@@ -35,7 +33,7 @@ class OrderAlreadyExistsException(ValidationException):
|
||||
class OrderValidationException(ValidationException):
|
||||
"""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__(
|
||||
message=message, error_code="ORDER_VALIDATION_FAILED", details=details
|
||||
)
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
Product (vendor catalog) specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import (BusinessLogicException, ConflictException,
|
||||
ResourceNotFoundException, ValidationException)
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class ProductNotFoundException(ResourceNotFoundException):
|
||||
"""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}
|
||||
if vendor_id:
|
||||
details["vendor_id"] = vendor_id
|
||||
@@ -79,8 +82,8 @@ class InvalidProductDataException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid product data",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[dict] = None,
|
||||
field: str | None = None,
|
||||
details: dict | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -96,8 +99,8 @@ class ProductValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Product validation failed",
|
||||
field: Optional[str] = None,
|
||||
validation_errors: Optional[dict] = None,
|
||||
field: str | None = None,
|
||||
validation_errors: dict | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
|
||||
@@ -3,17 +3,21 @@
|
||||
Team management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (AuthorizationException, BusinessLogicException,
|
||||
ConflictException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class TeamMemberNotFoundException(ResourceNotFoundException):
|
||||
"""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}
|
||||
if vendor_id:
|
||||
details["vendor_id"] = vendor_id
|
||||
@@ -63,7 +67,7 @@ class TeamInvitationExpiredException(BusinessLogicException):
|
||||
|
||||
def __init__(self, invitation_token: str):
|
||||
super().__init__(
|
||||
message=f"Team invitation has expired",
|
||||
message="Team invitation has expired",
|
||||
error_code="TEAM_INVITATION_EXPIRED",
|
||||
details={"invitation_token": invitation_token},
|
||||
)
|
||||
@@ -86,8 +90,8 @@ class UnauthorizedTeamActionException(AuthorizationException):
|
||||
def __init__(
|
||||
self,
|
||||
action: str,
|
||||
user_id: Optional[int] = None,
|
||||
required_permission: Optional[str] = None,
|
||||
user_id: int | None = None,
|
||||
required_permission: str | None = None,
|
||||
):
|
||||
details = {"action": action}
|
||||
if user_id:
|
||||
@@ -130,7 +134,7 @@ class CannotModifyOwnRoleException(BusinessLogicException):
|
||||
class RoleNotFoundException(ResourceNotFoundException):
|
||||
"""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}
|
||||
if vendor_id:
|
||||
details["vendor_id"] = vendor_id
|
||||
@@ -153,8 +157,8 @@ class InvalidRoleException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid role data",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -170,8 +174,8 @@ class InsufficientTeamPermissionsException(AuthorizationException):
|
||||
def __init__(
|
||||
self,
|
||||
required_permission: str,
|
||||
user_id: Optional[int] = None,
|
||||
action: Optional[str] = None,
|
||||
user_id: int | None = None,
|
||||
action: str | None = None,
|
||||
):
|
||||
details = {"required_permission": required_permission}
|
||||
if user_id:
|
||||
@@ -208,8 +212,8 @@ class TeamValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Team operation validation failed",
|
||||
field: Optional[str] = None,
|
||||
validation_errors: Optional[Dict[str, str]] = None,
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
@@ -229,8 +233,8 @@ class InvalidInvitationDataException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid invitation data",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -255,7 +259,7 @@ class InvalidInvitationTokenException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid or expired invitation token",
|
||||
invitation_token: Optional[str] = None,
|
||||
invitation_token: str | None = None,
|
||||
):
|
||||
details = {}
|
||||
if invitation_token:
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
Vendor management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (AuthorizationException, BusinessLogicException,
|
||||
ConflictException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import (
|
||||
AuthorizationException,
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class VendorNotFoundException(ResourceNotFoundException):
|
||||
@@ -63,7 +67,7 @@ class VendorNotVerifiedException(BusinessLogicException):
|
||||
class UnauthorizedVendorAccessException(AuthorizationException):
|
||||
"""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}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
@@ -81,8 +85,8 @@ class InvalidVendorDataException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid vendor data",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -98,8 +102,8 @@ class VendorValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Vendor validation failed",
|
||||
field: Optional[str] = None,
|
||||
validation_errors: Optional[Dict[str, str]] = None,
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
@@ -134,7 +138,7 @@ class IncompleteVendorDataException(ValidationException):
|
||||
class MaxVendorsReachedException(BusinessLogicException):
|
||||
"""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}
|
||||
if user_id:
|
||||
details["user_id"] = user_id
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
Vendor domain management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from .base import (BusinessLogicException, ConflictException,
|
||||
ExternalServiceException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ExternalServiceException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class VendorDomainNotFoundException(ResourceNotFoundException):
|
||||
@@ -30,7 +33,7 @@ class VendorDomainNotFoundException(ResourceNotFoundException):
|
||||
class VendorDomainAlreadyExistsException(ConflictException):
|
||||
"""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}
|
||||
if existing_vendor_id:
|
||||
details["existing_vendor_id"] = existing_vendor_id
|
||||
@@ -104,7 +107,7 @@ class MultiplePrimaryDomainsException(BusinessLogicException):
|
||||
|
||||
def __init__(self, vendor_id: int):
|
||||
super().__init__(
|
||||
message=f"Vendor can only have one primary domain",
|
||||
message="Vendor can only have one primary domain",
|
||||
error_code="MULTIPLE_PRIMARY_DOMAINS",
|
||||
details={"vendor_id": vendor_id},
|
||||
)
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
Vendor theme management specific exceptions.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from .base import (BusinessLogicException, ConflictException,
|
||||
ResourceNotFoundException, ValidationException)
|
||||
from .base import (
|
||||
BusinessLogicException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
|
||||
|
||||
class VendorThemeNotFoundException(ResourceNotFoundException):
|
||||
@@ -27,8 +30,8 @@ class InvalidThemeDataException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Invalid theme data",
|
||||
field: Optional[str] = None,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
field: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
@@ -41,7 +44,7 @@ class InvalidThemeDataException(ValidationException):
|
||||
class ThemePresetNotFoundException(ResourceNotFoundException):
|
||||
"""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}
|
||||
if available_presets:
|
||||
details["available_presets"] = available_presets
|
||||
@@ -61,8 +64,8 @@ class ThemeValidationException(ValidationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Theme validation failed",
|
||||
field: Optional[str] = None,
|
||||
validation_errors: Optional[Dict[str, str]] = None,
|
||||
field: str | None = None,
|
||||
validation_errors: dict[str, str] | None = None,
|
||||
):
|
||||
details = {}
|
||||
if validation_errors:
|
||||
|
||||
@@ -3,8 +3,17 @@ Architecture Scan Models
|
||||
Database models for tracking code quality scans and violations
|
||||
"""
|
||||
|
||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, Float, ForeignKey,
|
||||
Integer, String, Text)
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
|
||||
@@ -30,15 +30,17 @@ Routes:
|
||||
- GET /code-quality/violations/{violation_id} → Violation details (auth required)
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Path, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import (get_current_admin_from_cookie_or_header,
|
||||
get_current_admin_optional, get_db)
|
||||
from app.api.deps import (
|
||||
get_current_admin_from_cookie_or_header,
|
||||
get_current_admin_optional,
|
||||
get_db,
|
||||
)
|
||||
from models.database.user import User
|
||||
|
||||
router = APIRouter()
|
||||
@@ -52,7 +54,7 @@ templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
@router.get("/", response_class=RedirectResponse, include_in_schema=False)
|
||||
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.
|
||||
@@ -69,7 +71,7 @@ async def admin_root(
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse, include_in_schema=False)
|
||||
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.
|
||||
|
||||
@@ -129,7 +129,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
|
||||
)
|
||||
except Exception as e:
|
||||
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},
|
||||
)
|
||||
|
||||
@@ -149,7 +149,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
|
||||
context.update(extra_context)
|
||||
|
||||
logger.debug(
|
||||
f"[SHOP_CONTEXT] Context built",
|
||||
"[SHOP_CONTEXT] Context built",
|
||||
extra={
|
||||
"vendor_id": vendor.id 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.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"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.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -227,7 +227,7 @@ async def shop_category_page(
|
||||
Shows all products in a specific category.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"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.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -265,7 +265,7 @@ async def shop_checkout_page(request: Request):
|
||||
Handles shipping, payment, and order confirmation.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -283,7 +283,7 @@ async def shop_search_page(request: Request):
|
||||
Shows products matching search query.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -306,7 +306,7 @@ async def shop_register_page(request: Request):
|
||||
No authentication required.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -326,7 +326,7 @@ async def shop_login_page(request: Request):
|
||||
No authentication required.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"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.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"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.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -414,7 +414,7 @@ async def shop_account_dashboard_page(
|
||||
Requires customer authentication.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -439,7 +439,7 @@ async def shop_orders_page(
|
||||
Requires customer authentication.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -467,7 +467,7 @@ async def shop_order_detail_page(
|
||||
Requires customer authentication.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -493,7 +493,7 @@ async def shop_profile_page(
|
||||
Requires customer authentication.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -518,7 +518,7 @@ async def shop_addresses_page(
|
||||
Requires customer authentication.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -543,7 +543,7 @@ async def shop_wishlist_page(
|
||||
Requires customer authentication.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -568,7 +568,7 @@ async def shop_settings_page(
|
||||
Requires customer authentication.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||
@@ -609,7 +609,7 @@ async def generic_content_page(
|
||||
from fastapi import HTTPException
|
||||
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] generic_content_page REACHED",
|
||||
"[SHOP_HANDLER] generic_content_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"slug": slug,
|
||||
@@ -628,7 +628,7 @@ async def generic_content_page(
|
||||
|
||||
if not page:
|
||||
logger.warning(
|
||||
f"[SHOP_HANDLER] Content page not found",
|
||||
"[SHOP_HANDLER] Content page not found",
|
||||
extra={
|
||||
"slug": slug,
|
||||
"vendor_id": vendor_id,
|
||||
@@ -638,7 +638,7 @@ async def generic_content_page(
|
||||
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
||||
|
||||
logger.info(
|
||||
f"[SHOP_HANDLER] Content page found",
|
||||
"[SHOP_HANDLER] Content page found",
|
||||
extra={
|
||||
"slug": slug,
|
||||
"page_id": page.id,
|
||||
@@ -709,14 +709,14 @@ async def debug_context(request: Request):
|
||||
<pre>{json.dumps(debug_info, indent=2)}</pre>
|
||||
|
||||
<h2>Status</h2>
|
||||
<p class="{'good' if vendor else 'bad'}">
|
||||
Vendor: {'✓ Found' if vendor else '✗ Not Found'}
|
||||
<p class="{"good" if vendor else "bad"}">
|
||||
Vendor: {"✓ Found" if vendor else "✗ Not Found"}
|
||||
</p>
|
||||
<p class="{'good' if theme else 'bad'}">
|
||||
Theme: {'✓ Found' if theme else '✗ Not Found'}
|
||||
<p class="{"good" if theme else "bad"}">
|
||||
Theme: {"✓ Found" if theme else "✗ Not Found"}
|
||||
</p>
|
||||
<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'))}
|
||||
<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"))}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -22,15 +22,17 @@ Routes:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import (get_current_vendor_from_cookie_or_header,
|
||||
get_current_vendor_optional, get_db)
|
||||
from app.api.deps import (
|
||||
get_current_vendor_from_cookie_or_header,
|
||||
get_current_vendor_optional,
|
||||
get_db,
|
||||
)
|
||||
from app.services.content_page_service import content_page_service
|
||||
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)
|
||||
async def vendor_root(
|
||||
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.
|
||||
@@ -78,7 +80,7 @@ async def vendor_root(
|
||||
async def vendor_login_page(
|
||||
request: Request,
|
||||
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.
|
||||
@@ -374,7 +376,7 @@ async def vendor_content_page(
|
||||
shadowing other specific routes.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[VENDOR_HANDLER] vendor_content_page REACHED",
|
||||
"[VENDOR_HANDLER] vendor_content_page REACHED",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"vendor_code": vendor_code,
|
||||
|
||||
@@ -9,10 +9,9 @@ This module provides functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import AdminOperationException
|
||||
@@ -33,10 +32,10 @@ class AdminAuditService:
|
||||
action: str,
|
||||
target_type: str,
|
||||
target_id: str,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
ip_address: Optional[str] = None,
|
||||
user_agent: Optional[str] = None,
|
||||
request_id: Optional[str] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
ip_address: str | None = None,
|
||||
user_agent: str | None = None,
|
||||
request_id: str | None = None,
|
||||
) -> AdminAuditLog:
|
||||
"""
|
||||
Log an admin action to the audit trail.
|
||||
@@ -85,7 +84,7 @@ class AdminAuditService:
|
||||
|
||||
def get_audit_logs(
|
||||
self, db: Session, filters: AdminAuditLogFilters
|
||||
) -> List[AdminAuditLogResponse]:
|
||||
) -> list[AdminAuditLogResponse]:
|
||||
"""
|
||||
Get filtered admin audit logs with pagination.
|
||||
|
||||
@@ -187,14 +186,14 @@ class AdminAuditService:
|
||||
|
||||
def get_recent_actions_by_admin(
|
||||
self, db: Session, admin_user_id: int, limit: int = 10
|
||||
) -> List[AdminAuditLogResponse]:
|
||||
) -> list[AdminAuditLogResponse]:
|
||||
"""Get recent actions by a specific admin."""
|
||||
filters = AdminAuditLogFilters(admin_user_id=admin_user_id, limit=limit)
|
||||
return self.get_audit_logs(db, filters)
|
||||
|
||||
def get_actions_by_target(
|
||||
self, db: Session, target_type: str, target_id: str, limit: int = 50
|
||||
) -> List[AdminAuditLogResponse]:
|
||||
) -> list[AdminAuditLogResponse]:
|
||||
"""Get all actions performed on a specific target."""
|
||||
try:
|
||||
logs = (
|
||||
|
||||
@@ -13,17 +13,21 @@ This module provides classes and functions for:
|
||||
import logging
|
||||
import secrets
|
||||
import string
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (AdminOperationException, CannotModifySelfException,
|
||||
UserNotFoundException, UserStatusChangeException,
|
||||
ValidationException, VendorAlreadyExistsException,
|
||||
VendorNotFoundException,
|
||||
VendorVerificationException)
|
||||
from app.exceptions import (
|
||||
AdminOperationException,
|
||||
CannotModifySelfException,
|
||||
UserNotFoundException,
|
||||
UserStatusChangeException,
|
||||
ValidationException,
|
||||
VendorAlreadyExistsException,
|
||||
VendorNotFoundException,
|
||||
VendorVerificationException,
|
||||
)
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Role, Vendor, VendorUser
|
||||
@@ -40,7 +44,7 @@ class AdminService:
|
||||
# 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."""
|
||||
try:
|
||||
return db.query(User).offset(skip).limit(limit).all()
|
||||
@@ -52,7 +56,7 @@ class AdminService:
|
||||
|
||||
def toggle_user_status(
|
||||
self, db: Session, user_id: int, current_admin_id: int
|
||||
) -> Tuple[User, str]:
|
||||
) -> tuple[User, str]:
|
||||
"""Toggle user active status."""
|
||||
user = self._get_user_by_id_or_raise(db, user_id)
|
||||
|
||||
@@ -72,7 +76,7 @@ class AdminService:
|
||||
try:
|
||||
original_status = 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.refresh(user)
|
||||
|
||||
@@ -98,7 +102,7 @@ class AdminService:
|
||||
|
||||
def create_vendor_with_owner(
|
||||
self, db: Session, vendor_data: VendorCreate
|
||||
) -> Tuple[Vendor, User, str]:
|
||||
) -> tuple[Vendor, User, str]:
|
||||
"""
|
||||
Create vendor with owner user account.
|
||||
|
||||
@@ -222,10 +226,10 @@ class AdminService:
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
search: Optional[str] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
is_verified: Optional[bool] = None,
|
||||
) -> Tuple[List[Vendor], int]:
|
||||
search: str | None = None,
|
||||
is_active: bool | None = None,
|
||||
is_verified: bool | None = None,
|
||||
) -> tuple[list[Vendor], int]:
|
||||
"""Get paginated list of all vendors with filtering."""
|
||||
try:
|
||||
query = db.query(Vendor)
|
||||
@@ -261,17 +265,17 @@ class AdminService:
|
||||
"""Get vendor by 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."""
|
||||
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||
|
||||
try:
|
||||
original_status = 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:
|
||||
vendor.verified_at = datetime.now(timezone.utc)
|
||||
vendor.verified_at = datetime.now(UTC)
|
||||
|
||||
db.commit()
|
||||
db.refresh(vendor)
|
||||
@@ -291,14 +295,14 @@ class AdminService:
|
||||
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."""
|
||||
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||
|
||||
try:
|
||||
original_status = 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.refresh(vendor)
|
||||
|
||||
@@ -347,7 +351,10 @@ class AdminService:
|
||||
)
|
||||
|
||||
def update_vendor(
|
||||
self, db: Session, vendor_id: int, vendor_update # VendorUpdate schema
|
||||
self,
|
||||
db: Session,
|
||||
vendor_id: int,
|
||||
vendor_update, # VendorUpdate schema
|
||||
) -> Vendor:
|
||||
"""
|
||||
Update vendor information (Admin only).
|
||||
@@ -402,7 +409,7 @@ class AdminService:
|
||||
for field, value in update_data.items():
|
||||
setattr(vendor, field, value)
|
||||
|
||||
vendor.updated_at = datetime.now(timezone.utc)
|
||||
vendor.updated_at = datetime.now(UTC)
|
||||
|
||||
db.commit()
|
||||
db.refresh(vendor)
|
||||
@@ -430,7 +437,7 @@ class AdminService:
|
||||
db: Session,
|
||||
vendor_id: int,
|
||||
transfer_data, # VendorTransferOwnership schema
|
||||
) -> Tuple[Vendor, User, User]:
|
||||
) -> tuple[Vendor, User, User]:
|
||||
"""
|
||||
Transfer vendor ownership to another user.
|
||||
|
||||
@@ -556,7 +563,7 @@ class AdminService:
|
||||
|
||||
# Update vendor owner_user_id
|
||||
vendor.owner_user_id = new_owner.id
|
||||
vendor.updated_at = datetime.now(timezone.utc)
|
||||
vendor.updated_at = datetime.now(UTC)
|
||||
|
||||
db.commit()
|
||||
db.refresh(vendor)
|
||||
@@ -593,12 +600,12 @@ class AdminService:
|
||||
def get_marketplace_import_jobs(
|
||||
self,
|
||||
db: Session,
|
||||
marketplace: Optional[str] = None,
|
||||
vendor_name: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
marketplace: str | None = None,
|
||||
vendor_name: str | None = None,
|
||||
status: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> List[MarketplaceImportJobResponse]:
|
||||
) -> list[MarketplaceImportJobResponse]:
|
||||
"""Get filtered and paginated marketplace import jobs."""
|
||||
try:
|
||||
query = db.query(MarketplaceImportJob)
|
||||
@@ -633,7 +640,7 @@ class AdminService:
|
||||
# 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."""
|
||||
try:
|
||||
vendors = (
|
||||
@@ -656,7 +663,7 @@ class AdminService:
|
||||
logger.error(f"Failed to get recent vendors: {str(e)}")
|
||||
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."""
|
||||
try:
|
||||
jobs = (
|
||||
|
||||
@@ -10,17 +10,23 @@ This module provides functions for:
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (AdminOperationException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from app.exceptions import (
|
||||
AdminOperationException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.admin import AdminSetting
|
||||
from models.schema.admin import (AdminSettingCreate, AdminSettingResponse,
|
||||
AdminSettingUpdate)
|
||||
from models.schema.admin import (
|
||||
AdminSettingCreate,
|
||||
AdminSettingResponse,
|
||||
AdminSettingUpdate,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,7 +34,7 @@ logger = logging.getLogger(__name__)
|
||||
class AdminSettingsService:
|
||||
"""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."""
|
||||
try:
|
||||
return (
|
||||
@@ -60,14 +66,13 @@ class AdminSettingsService:
|
||||
try:
|
||||
if setting.value_type == "integer":
|
||||
return int(setting.value)
|
||||
elif setting.value_type == "float":
|
||||
if setting.value_type == "float":
|
||||
return float(setting.value)
|
||||
elif setting.value_type == "boolean":
|
||||
if setting.value_type == "boolean":
|
||||
return setting.value.lower() in ("true", "1", "yes")
|
||||
elif setting.value_type == "json":
|
||||
if setting.value_type == "json":
|
||||
return json.loads(setting.value)
|
||||
else:
|
||||
return setting.value
|
||||
return setting.value
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to convert setting {key} value: {str(e)}")
|
||||
return default
|
||||
@@ -75,9 +80,9 @@ class AdminSettingsService:
|
||||
def get_all_settings(
|
||||
self,
|
||||
db: Session,
|
||||
category: Optional[str] = None,
|
||||
is_public: Optional[bool] = None,
|
||||
) -> List[AdminSettingResponse]:
|
||||
category: str | None = None,
|
||||
is_public: bool | None = None,
|
||||
) -> list[AdminSettingResponse]:
|
||||
"""Get all settings with optional filtering."""
|
||||
try:
|
||||
query = db.query(AdminSetting)
|
||||
@@ -100,7 +105,7 @@ class AdminSettingsService:
|
||||
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.
|
||||
|
||||
@@ -198,7 +203,7 @@ class AdminSettingsService:
|
||||
if update_data.description is not None:
|
||||
setting.description = update_data.description
|
||||
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.refresh(setting)
|
||||
@@ -228,8 +233,7 @@ class AdminSettingsService:
|
||||
value=setting_data.value, description=setting_data.description
|
||||
)
|
||||
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:
|
||||
"""Delete setting."""
|
||||
|
||||
@@ -9,13 +9,17 @@ This module provides classes and functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
from datetime import UTC
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InvalidCredentialsException,
|
||||
UserAlreadyExistsException, UserNotActiveException,
|
||||
ValidationException)
|
||||
from app.exceptions import (
|
||||
InvalidCredentialsException,
|
||||
UserAlreadyExistsException,
|
||||
UserNotActiveException,
|
||||
ValidationException,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
from models.database.user import User
|
||||
from models.schema.auth import UserLogin, UserRegister
|
||||
@@ -82,7 +86,7 @@ class AuthService:
|
||||
logger.error(f"Error registering user: {str(e)}")
|
||||
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.
|
||||
|
||||
@@ -120,7 +124,7 @@ class AuthService:
|
||||
logger.error(f"Error during login: {str(e)}")
|
||||
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."""
|
||||
try:
|
||||
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)}")
|
||||
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."""
|
||||
try:
|
||||
return db.query(User).filter(User.username == username).first()
|
||||
@@ -138,7 +142,7 @@ class AuthService:
|
||||
|
||||
def authenticate_user(
|
||||
self, db: Session, username: str, password: str
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
"""Authenticate user with username/password."""
|
||||
try:
|
||||
return self.auth_manager.authenticate_user(db, username, password)
|
||||
@@ -146,7 +150,7 @@ class AuthService:
|
||||
logger.error(f"Error authenticating user: {str(e)}")
|
||||
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."""
|
||||
try:
|
||||
return self.auth_manager.create_access_token(user)
|
||||
@@ -182,7 +186,7 @@ class AuthService:
|
||||
Returns:
|
||||
Dictionary with access_token, token_type, and expires_in
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from jose import jwt
|
||||
|
||||
@@ -190,13 +194,13 @@ class AuthService:
|
||||
|
||||
try:
|
||||
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
|
||||
payload = {
|
||||
**data,
|
||||
"exp": expire,
|
||||
"iat": datetime.now(timezone.utc),
|
||||
"iat": datetime.now(UTC),
|
||||
}
|
||||
|
||||
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
||||
|
||||
@@ -9,20 +9,18 @@ This module provides:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (CartItemNotFoundException, CartValidationException,
|
||||
InsufficientInventoryForCartException,
|
||||
InvalidCartQuantityException,
|
||||
ProductNotAvailableForCartException,
|
||||
ProductNotFoundException)
|
||||
from app.exceptions import (
|
||||
CartItemNotFoundException,
|
||||
InsufficientInventoryForCartException,
|
||||
InvalidCartQuantityException,
|
||||
ProductNotFoundException,
|
||||
)
|
||||
from models.database.cart import CartItem
|
||||
from models.database.product import Product
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,7 +28,7 @@ logger = logging.getLogger(__name__)
|
||||
class CartService:
|
||||
"""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.
|
||||
|
||||
@@ -43,7 +41,7 @@ class CartService:
|
||||
Cart data with items and totals
|
||||
"""
|
||||
logger.info(
|
||||
f"[CART_SERVICE] get_cart called",
|
||||
"[CART_SERVICE] get_cart called",
|
||||
extra={
|
||||
"vendor_id": vendor_id,
|
||||
"session_id": session_id,
|
||||
@@ -111,7 +109,7 @@ class CartService:
|
||||
session_id: str,
|
||||
product_id: int,
|
||||
quantity: int = 1,
|
||||
) -> Dict:
|
||||
) -> dict:
|
||||
"""
|
||||
Add product to cart.
|
||||
|
||||
@@ -130,7 +128,7 @@ class CartService:
|
||||
InsufficientInventoryException: If not enough inventory
|
||||
"""
|
||||
logger.info(
|
||||
f"[CART_SERVICE] add_to_cart called",
|
||||
"[CART_SERVICE] add_to_cart called",
|
||||
extra={
|
||||
"vendor_id": vendor_id,
|
||||
"session_id": session_id,
|
||||
@@ -154,7 +152,7 @@ class CartService:
|
||||
|
||||
if not product:
|
||||
logger.error(
|
||||
f"[CART_SERVICE] Product not found",
|
||||
"[CART_SERVICE] Product not found",
|
||||
extra={"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
|
||||
if product.available_inventory < new_quantity:
|
||||
logger.warning(
|
||||
f"[CART_SERVICE] Insufficient inventory for update",
|
||||
"[CART_SERVICE] Insufficient inventory for update",
|
||||
extra={
|
||||
"product_id": product_id,
|
||||
"current_in_cart": existing_item.quantity,
|
||||
@@ -212,7 +210,7 @@ class CartService:
|
||||
db.refresh(existing_item)
|
||||
|
||||
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},
|
||||
)
|
||||
|
||||
@@ -221,50 +219,49 @@ class CartService:
|
||||
"product_id": product_id,
|
||||
"quantity": new_quantity,
|
||||
}
|
||||
else:
|
||||
# Check inventory for new item
|
||||
if product.available_inventory < quantity:
|
||||
logger.warning(
|
||||
f"[CART_SERVICE] Insufficient inventory",
|
||||
extra={
|
||||
"product_id": product_id,
|
||||
"requested": quantity,
|
||||
"available": product.available_inventory,
|
||||
},
|
||||
)
|
||||
raise InsufficientInventoryForCartException(
|
||||
product_id=product_id,
|
||||
product_name=product.marketplace_product.title,
|
||||
requested=quantity,
|
||||
available=product.available_inventory,
|
||||
)
|
||||
|
||||
# Create new cart item
|
||||
cart_item = CartItem(
|
||||
vendor_id=vendor_id,
|
||||
session_id=session_id,
|
||||
product_id=product_id,
|
||||
quantity=quantity,
|
||||
price_at_add=current_price,
|
||||
)
|
||||
db.add(cart_item)
|
||||
db.commit()
|
||||
db.refresh(cart_item)
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Created new cart item",
|
||||
# Check inventory for new item
|
||||
if product.available_inventory < quantity:
|
||||
logger.warning(
|
||||
"[CART_SERVICE] Insufficient inventory",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"quantity": quantity,
|
||||
"price": current_price,
|
||||
"product_id": product_id,
|
||||
"requested": quantity,
|
||||
"available": product.available_inventory,
|
||||
},
|
||||
)
|
||||
raise InsufficientInventoryForCartException(
|
||||
product_id=product_id,
|
||||
product_name=product.marketplace_product.title,
|
||||
requested=quantity,
|
||||
available=product.available_inventory,
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Product added to cart",
|
||||
"product_id": product_id,
|
||||
# Create new cart item
|
||||
cart_item = CartItem(
|
||||
vendor_id=vendor_id,
|
||||
session_id=session_id,
|
||||
product_id=product_id,
|
||||
quantity=quantity,
|
||||
price_at_add=current_price,
|
||||
)
|
||||
db.add(cart_item)
|
||||
db.commit()
|
||||
db.refresh(cart_item)
|
||||
|
||||
logger.info(
|
||||
"[CART_SERVICE] Created new cart item",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"quantity": quantity,
|
||||
}
|
||||
"price": current_price,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Product added to cart",
|
||||
"product_id": product_id,
|
||||
"quantity": quantity,
|
||||
}
|
||||
|
||||
def update_cart_item(
|
||||
self,
|
||||
@@ -273,7 +270,7 @@ class CartService:
|
||||
session_id: str,
|
||||
product_id: int,
|
||||
quantity: int,
|
||||
) -> Dict:
|
||||
) -> dict:
|
||||
"""
|
||||
Update quantity of item in cart.
|
||||
|
||||
@@ -344,7 +341,7 @@ class CartService:
|
||||
db.refresh(cart_item)
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Updated cart item quantity",
|
||||
"[CART_SERVICE] Updated cart item quantity",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"product_id": product_id,
|
||||
@@ -360,7 +357,7 @@ class CartService:
|
||||
|
||||
def remove_from_cart(
|
||||
self, db: Session, vendor_id: int, session_id: str, product_id: int
|
||||
) -> Dict:
|
||||
) -> dict:
|
||||
"""
|
||||
Remove item from cart.
|
||||
|
||||
@@ -398,7 +395,7 @@ class CartService:
|
||||
db.commit()
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Removed item from cart",
|
||||
"[CART_SERVICE] Removed item from cart",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"product_id": product_id,
|
||||
@@ -408,7 +405,7 @@ class CartService:
|
||||
|
||||
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.
|
||||
|
||||
@@ -432,7 +429,7 @@ class CartService:
|
||||
db.commit()
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Cleared cart",
|
||||
"[CART_SERVICE] Cleared cart",
|
||||
extra={
|
||||
"session_id": session_id,
|
||||
"vendor_id": vendor_id,
|
||||
|
||||
@@ -7,16 +7,16 @@ import json
|
||||
import logging
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import desc, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.architecture_scan import (ArchitectureRule, ArchitectureScan,
|
||||
ArchitectureViolation,
|
||||
ViolationAssignment,
|
||||
ViolationComment)
|
||||
from app.models.architecture_scan import (
|
||||
ArchitectureScan,
|
||||
ArchitectureViolation,
|
||||
ViolationAssignment,
|
||||
ViolationComment,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -118,7 +118,7 @@ class CodeQualityService:
|
||||
logger.info(f"Scan completed: {scan.total_violations} violations found")
|
||||
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"""
|
||||
return (
|
||||
db.query(ArchitectureScan)
|
||||
@@ -126,11 +126,11 @@ class CodeQualityService:
|
||||
.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"""
|
||||
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
|
||||
|
||||
@@ -158,7 +158,7 @@ class CodeQualityService:
|
||||
file_path: str = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
) -> Tuple[List[ArchitectureViolation], int]:
|
||||
) -> tuple[list[ArchitectureViolation], int]:
|
||||
"""
|
||||
Get violations with filtering and pagination
|
||||
|
||||
@@ -217,7 +217,7 @@ class CodeQualityService:
|
||||
|
||||
def get_violation_by_id(
|
||||
self, db: Session, violation_id: int
|
||||
) -> Optional[ArchitectureViolation]:
|
||||
) -> ArchitectureViolation | None:
|
||||
"""Get single violation with details"""
|
||||
return (
|
||||
db.query(ArchitectureViolation)
|
||||
@@ -348,7 +348,7 @@ class CodeQualityService:
|
||||
logger.info(f"Comment added to violation {violation_id} by user {user_id}")
|
||||
return comment_obj
|
||||
|
||||
def get_dashboard_stats(self, db: Session) -> Dict:
|
||||
def get_dashboard_stats(self, db: Session) -> dict:
|
||||
"""
|
||||
Get statistics for dashboard
|
||||
|
||||
@@ -507,7 +507,7 @@ class CodeQualityService:
|
||||
score = 100 - (scan.errors * 0.5 + scan.warnings * 0.05)
|
||||
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"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
|
||||
@@ -17,10 +17,9 @@ This allows:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from models.database.content_page import ContentPage
|
||||
@@ -35,9 +34,9 @@ class ContentPageService:
|
||||
def get_page_for_vendor(
|
||||
db: Session,
|
||||
slug: str,
|
||||
vendor_id: Optional[int] = None,
|
||||
vendor_id: int | None = None,
|
||||
include_unpublished: bool = False,
|
||||
) -> Optional[ContentPage]:
|
||||
) -> ContentPage | None:
|
||||
"""
|
||||
Get content page for a vendor with fallback to platform default.
|
||||
|
||||
@@ -90,11 +89,11 @@ class ContentPageService:
|
||||
@staticmethod
|
||||
def list_pages_for_vendor(
|
||||
db: Session,
|
||||
vendor_id: Optional[int] = None,
|
||||
vendor_id: int | None = None,
|
||||
include_unpublished: bool = False,
|
||||
footer_only: bool = False,
|
||||
header_only: bool = False,
|
||||
) -> List[ContentPage]:
|
||||
) -> list[ContentPage]:
|
||||
"""
|
||||
List all available pages for a vendor (includes vendor overrides + platform defaults).
|
||||
|
||||
@@ -156,16 +155,16 @@ class ContentPageService:
|
||||
slug: str,
|
||||
title: str,
|
||||
content: str,
|
||||
vendor_id: Optional[int] = None,
|
||||
vendor_id: int | None = None,
|
||||
content_format: str = "html",
|
||||
template: str = "default",
|
||||
meta_description: Optional[str] = None,
|
||||
meta_keywords: Optional[str] = None,
|
||||
meta_description: str | None = None,
|
||||
meta_keywords: str | None = None,
|
||||
is_published: bool = False,
|
||||
show_in_footer: bool = True,
|
||||
show_in_header: bool = False,
|
||||
display_order: int = 0,
|
||||
created_by: Optional[int] = None,
|
||||
created_by: int | None = None,
|
||||
) -> ContentPage:
|
||||
"""
|
||||
Create a new content page.
|
||||
@@ -199,7 +198,7 @@ class ContentPageService:
|
||||
meta_description=meta_description,
|
||||
meta_keywords=meta_keywords,
|
||||
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_header=show_in_header,
|
||||
display_order=display_order,
|
||||
@@ -220,18 +219,18 @@ class ContentPageService:
|
||||
def update_page(
|
||||
db: Session,
|
||||
page_id: int,
|
||||
title: Optional[str] = None,
|
||||
content: Optional[str] = None,
|
||||
content_format: Optional[str] = None,
|
||||
template: Optional[str] = None,
|
||||
meta_description: Optional[str] = None,
|
||||
meta_keywords: Optional[str] = None,
|
||||
is_published: Optional[bool] = None,
|
||||
show_in_footer: Optional[bool] = None,
|
||||
show_in_header: Optional[bool] = None,
|
||||
display_order: Optional[int] = None,
|
||||
updated_by: Optional[int] = None,
|
||||
) -> Optional[ContentPage]:
|
||||
title: str | None = None,
|
||||
content: str | None = None,
|
||||
content_format: str | None = None,
|
||||
template: str | None = None,
|
||||
meta_description: str | None = None,
|
||||
meta_keywords: str | None = None,
|
||||
is_published: bool | None = None,
|
||||
show_in_footer: bool | None = None,
|
||||
show_in_header: bool | None = None,
|
||||
display_order: int | None = None,
|
||||
updated_by: int | None = None,
|
||||
) -> ContentPage | None:
|
||||
"""
|
||||
Update an existing content page.
|
||||
|
||||
@@ -275,7 +274,7 @@ class ContentPageService:
|
||||
if is_published is not None:
|
||||
page.is_published = is_published
|
||||
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:
|
||||
page.show_in_footer = show_in_footer
|
||||
if show_in_header is not None:
|
||||
@@ -316,14 +315,14 @@ class ContentPageService:
|
||||
return True
|
||||
|
||||
@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."""
|
||||
return db.query(ContentPage).filter(ContentPage.id == page_id).first()
|
||||
|
||||
@staticmethod
|
||||
def list_all_vendor_pages(
|
||||
db: Session, vendor_id: int, include_unpublished: bool = False
|
||||
) -> List[ContentPage]:
|
||||
) -> list[ContentPage]:
|
||||
"""
|
||||
List only vendor-specific pages (no platform defaults).
|
||||
|
||||
@@ -350,7 +349,7 @@ class ContentPageService:
|
||||
@staticmethod
|
||||
def list_all_platform_pages(
|
||||
db: Session, include_unpublished: bool = False
|
||||
) -> List[ContentPage]:
|
||||
) -> list[ContentPage]:
|
||||
"""
|
||||
List only platform default pages.
|
||||
|
||||
|
||||
@@ -7,22 +7,22 @@ with complete vendor isolation.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, Optional
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions.customer import (CustomerAlreadyExistsException,
|
||||
CustomerNotActiveException,
|
||||
CustomerNotFoundException,
|
||||
CustomerValidationException,
|
||||
DuplicateCustomerEmailException,
|
||||
InvalidCustomerCredentialsException)
|
||||
from app.exceptions.vendor import (VendorNotActiveException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions.customer import (
|
||||
CustomerNotActiveException,
|
||||
CustomerNotFoundException,
|
||||
CustomerValidationException,
|
||||
DuplicateCustomerEmailException,
|
||||
InvalidCustomerCredentialsException,
|
||||
)
|
||||
from app.exceptions.vendor import VendorNotActiveException, VendorNotFoundException
|
||||
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.schema.auth import UserLogin
|
||||
from models.schema.customer import CustomerRegister, CustomerUpdate
|
||||
@@ -128,7 +128,7 @@ class CustomerService:
|
||||
|
||||
def login_customer(
|
||||
self, db: Session, vendor_id: int, credentials: UserLogin
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Authenticate customer and generate JWT token.
|
||||
|
||||
@@ -177,13 +177,13 @@ class CustomerService:
|
||||
|
||||
# Generate JWT token with customer context
|
||||
# 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
|
||||
|
||||
auth_manager = self.auth_service.auth_manager
|
||||
expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
expire = datetime.now(UTC) + expires_delta
|
||||
|
||||
payload = {
|
||||
"sub": str(customer.id),
|
||||
@@ -191,7 +191,7 @@ class CustomerService:
|
||||
"vendor_id": vendor_id,
|
||||
"type": "customer",
|
||||
"exp": expire,
|
||||
"iat": datetime.now(timezone.utc),
|
||||
"iat": datetime.now(UTC),
|
||||
}
|
||||
|
||||
token = jwt.encode(
|
||||
@@ -239,7 +239,7 @@ class CustomerService:
|
||||
|
||||
def get_customer_by_email(
|
||||
self, db: Session, vendor_id: int, email: str
|
||||
) -> Optional[Customer]:
|
||||
) -> Customer | None:
|
||||
"""
|
||||
Get customer by email (vendor-scoped).
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
# app/services/inventory_service.py
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InsufficientInventoryException,
|
||||
InvalidInventoryOperationException,
|
||||
InvalidQuantityException,
|
||||
InventoryNotFoundException,
|
||||
InventoryValidationException,
|
||||
NegativeInventoryException,
|
||||
ProductNotFoundException, ValidationException)
|
||||
from app.exceptions import (
|
||||
InsufficientInventoryException,
|
||||
InvalidQuantityException,
|
||||
InventoryNotFoundException,
|
||||
InventoryValidationException,
|
||||
ProductNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.inventory import Inventory
|
||||
from models.database.product import Product
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.inventory import (InventoryAdjust, InventoryCreate,
|
||||
InventoryLocationResponse,
|
||||
InventoryReserve, InventoryUpdate,
|
||||
ProductInventorySummary)
|
||||
from models.schema.inventory import (
|
||||
InventoryAdjust,
|
||||
InventoryCreate,
|
||||
InventoryLocationResponse,
|
||||
InventoryReserve,
|
||||
InventoryUpdate,
|
||||
ProductInventorySummary,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -58,7 +61,7 @@ class InventoryService:
|
||||
if existing:
|
||||
old_qty = existing.quantity
|
||||
existing.quantity = inventory_data.quantity
|
||||
existing.updated_at = datetime.now(timezone.utc)
|
||||
existing.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
|
||||
@@ -67,24 +70,23 @@ class InventoryService:
|
||||
f"{old_qty} → {inventory_data.quantity}"
|
||||
)
|
||||
return existing
|
||||
else:
|
||||
# Create new inventory entry
|
||||
new_inventory = Inventory(
|
||||
product_id=inventory_data.product_id,
|
||||
vendor_id=vendor_id,
|
||||
location=location,
|
||||
quantity=inventory_data.quantity,
|
||||
gtin=product.marketplace_product.gtin, # Optional reference
|
||||
)
|
||||
db.add(new_inventory)
|
||||
db.commit()
|
||||
db.refresh(new_inventory)
|
||||
# Create new inventory entry
|
||||
new_inventory = Inventory(
|
||||
product_id=inventory_data.product_id,
|
||||
vendor_id=vendor_id,
|
||||
location=location,
|
||||
quantity=inventory_data.quantity,
|
||||
gtin=product.marketplace_product.gtin, # Optional reference
|
||||
)
|
||||
db.add(new_inventory)
|
||||
db.commit()
|
||||
db.refresh(new_inventory)
|
||||
|
||||
logger.info(
|
||||
f"Created inventory for product {inventory_data.product_id} at {location}: "
|
||||
f"{inventory_data.quantity}"
|
||||
)
|
||||
return new_inventory
|
||||
logger.info(
|
||||
f"Created inventory for product {inventory_data.product_id} at {location}: "
|
||||
f"{inventory_data.quantity}"
|
||||
)
|
||||
return new_inventory
|
||||
|
||||
except (
|
||||
ProductNotFoundException,
|
||||
@@ -162,7 +164,7 @@ class InventoryService:
|
||||
)
|
||||
|
||||
existing.quantity = new_qty
|
||||
existing.updated_at = datetime.now(timezone.utc)
|
||||
existing.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
|
||||
@@ -224,7 +226,7 @@ class InventoryService:
|
||||
|
||||
# Reserve inventory
|
||||
inventory.reserved_quantity += reserve_data.quantity
|
||||
inventory.updated_at = datetime.now(timezone.utc)
|
||||
inventory.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(inventory)
|
||||
|
||||
@@ -284,7 +286,7 @@ class InventoryService:
|
||||
else:
|
||||
inventory.reserved_quantity -= reserve_data.quantity
|
||||
|
||||
inventory.updated_at = datetime.now(timezone.utc)
|
||||
inventory.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(inventory)
|
||||
|
||||
@@ -350,7 +352,7 @@ class InventoryService:
|
||||
inventory.reserved_quantity = max(
|
||||
0, inventory.reserved_quantity - reserve_data.quantity
|
||||
)
|
||||
inventory.updated_at = datetime.now(timezone.utc)
|
||||
inventory.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(inventory)
|
||||
|
||||
@@ -443,9 +445,9 @@ class InventoryService:
|
||||
vendor_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
location: Optional[str] = None,
|
||||
low_stock_threshold: Optional[int] = None,
|
||||
) -> List[Inventory]:
|
||||
location: str | None = None,
|
||||
low_stock_threshold: int | None = None,
|
||||
) -> list[Inventory]:
|
||||
"""
|
||||
Get all inventory for a vendor with filtering.
|
||||
|
||||
@@ -504,7 +506,7 @@ class InventoryService:
|
||||
if 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.refresh(inventory)
|
||||
|
||||
@@ -565,7 +567,7 @@ class InventoryService:
|
||||
|
||||
def _get_inventory_entry(
|
||||
self, db: Session, product_id: int, location: str
|
||||
) -> Optional[Inventory]:
|
||||
) -> Inventory | None:
|
||||
"""Get inventory entry by product and location."""
|
||||
return (
|
||||
db.query(Inventory)
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
# app/services/marketplace_import_job_service.py
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (ImportJobCannotBeCancelledException,
|
||||
ImportJobCannotBeDeletedException,
|
||||
ImportJobNotFoundException,
|
||||
ImportJobNotOwnedException, ValidationException)
|
||||
from app.exceptions import (
|
||||
ImportJobNotFoundException,
|
||||
ImportJobNotOwnedException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse)
|
||||
from models.schema.marketplace_import_job import (
|
||||
MarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -98,10 +99,10 @@ class MarketplaceImportJobService:
|
||||
db: Session,
|
||||
vendor: Vendor, # ADDED: Vendor filter
|
||||
user: User,
|
||||
marketplace: Optional[str] = None,
|
||||
marketplace: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
) -> List[MarketplaceImportJob]:
|
||||
) -> list[MarketplaceImportJob]:
|
||||
"""Get marketplace import jobs for a specific vendor."""
|
||||
try:
|
||||
query = db.query(MarketplaceImportJob).filter(
|
||||
|
||||
@@ -8,29 +8,31 @@ This module provides classes and functions for:
|
||||
- Inventory information integration
|
||||
- CSV export functionality
|
||||
"""
|
||||
|
||||
import csv
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from collections.abc import Generator
|
||||
from datetime import UTC, datetime
|
||||
from io import StringIO
|
||||
from typing import Generator, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InvalidMarketplaceProductDataException,
|
||||
MarketplaceProductAlreadyExistsException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MarketplaceProductValidationException,
|
||||
ValidationException)
|
||||
from app.services.marketplace_import_job_service import \
|
||||
marketplace_import_job_service
|
||||
from app.exceptions import (
|
||||
InvalidMarketplaceProductDataException,
|
||||
MarketplaceProductAlreadyExistsException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MarketplaceProductValidationException,
|
||||
ValidationException,
|
||||
)
|
||||
from app.utils.data_processing import GTINProcessor, PriceProcessor
|
||||
from models.database.inventory import Inventory
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.schema.inventory import (InventoryLocationResponse,
|
||||
InventorySummaryResponse)
|
||||
from models.schema.marketplace_product import (MarketplaceProductCreate,
|
||||
MarketplaceProductUpdate)
|
||||
from models.schema.inventory import InventoryLocationResponse, InventorySummaryResponse
|
||||
from models.schema.marketplace_product import (
|
||||
MarketplaceProductCreate,
|
||||
MarketplaceProductUpdate,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -109,10 +111,9 @@ class MarketplaceProductService:
|
||||
raise MarketplaceProductAlreadyExistsException(
|
||||
product_data.marketplace_product_id
|
||||
)
|
||||
else:
|
||||
raise MarketplaceProductValidationException(
|
||||
"Data integrity constraint violation"
|
||||
)
|
||||
raise MarketplaceProductValidationException(
|
||||
"Data integrity constraint violation"
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error creating product: {str(e)}")
|
||||
@@ -120,7 +121,7 @@ class MarketplaceProductService:
|
||||
|
||||
def get_product_by_id(
|
||||
self, db: Session, marketplace_product_id: str
|
||||
) -> Optional[MarketplaceProduct]:
|
||||
) -> MarketplaceProduct | None:
|
||||
"""Get a product by its ID."""
|
||||
try:
|
||||
return (
|
||||
@@ -160,13 +161,13 @@ class MarketplaceProductService:
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
brand: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
availability: Optional[str] = None,
|
||||
marketplace: Optional[str] = None,
|
||||
vendor_name: Optional[str] = None,
|
||||
search: Optional[str] = None,
|
||||
) -> Tuple[List[MarketplaceProduct], int]:
|
||||
brand: str | None = None,
|
||||
category: str | None = None,
|
||||
availability: str | None = None,
|
||||
marketplace: str | None = None,
|
||||
vendor_name: str | None = None,
|
||||
search: str | None = None,
|
||||
) -> tuple[list[MarketplaceProduct], int]:
|
||||
"""
|
||||
Get products with filtering and pagination.
|
||||
|
||||
@@ -269,7 +270,7 @@ class MarketplaceProductService:
|
||||
for key, value in update_data.items():
|
||||
setattr(product, key, value)
|
||||
|
||||
product.updated_at = datetime.now(timezone.utc)
|
||||
product.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
@@ -324,7 +325,7 @@ class MarketplaceProductService:
|
||||
|
||||
def get_inventory_info(
|
||||
self, db: Session, gtin: str
|
||||
) -> Optional[InventorySummaryResponse]:
|
||||
) -> InventorySummaryResponse | None:
|
||||
"""
|
||||
Get inventory information for a product by GTIN.
|
||||
|
||||
@@ -358,15 +359,14 @@ class MarketplaceProductService:
|
||||
|
||||
import csv
|
||||
from io import StringIO
|
||||
from typing import Generator, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
def generate_csv_export(
|
||||
self,
|
||||
db: Session,
|
||||
marketplace: Optional[str] = None,
|
||||
vendor_name: Optional[str] = None,
|
||||
marketplace: str | None = None,
|
||||
vendor_name: str | None = None,
|
||||
) -> Generator[str, None, None]:
|
||||
"""
|
||||
Generate CSV export with streaming for memory efficiency and proper CSV escaping.
|
||||
|
||||
@@ -11,15 +11,17 @@ This module provides:
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (CustomerNotFoundException,
|
||||
InsufficientInventoryException,
|
||||
OrderNotFoundException, ValidationException)
|
||||
from app.exceptions import (
|
||||
CustomerNotFoundException,
|
||||
InsufficientInventoryException,
|
||||
OrderNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.customer import Customer, CustomerAddress
|
||||
from models.database.order import Order, OrderItem
|
||||
from models.database.product import Product
|
||||
@@ -38,7 +40,7 @@ class OrderService:
|
||||
Format: ORD-{VENDOR_ID}-{TIMESTAMP}-{RANDOM}
|
||||
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.choices(string.ascii_uppercase + string.digits, k=6)
|
||||
)
|
||||
@@ -266,9 +268,9 @@ class OrderService:
|
||||
vendor_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status: Optional[str] = None,
|
||||
customer_id: Optional[int] = None,
|
||||
) -> Tuple[List[Order], int]:
|
||||
status: str | None = None,
|
||||
customer_id: int | None = None,
|
||||
) -> tuple[list[Order], int]:
|
||||
"""
|
||||
Get orders for vendor with filtering.
|
||||
|
||||
@@ -306,7 +308,7 @@ class OrderService:
|
||||
customer_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> Tuple[List[Order], int]:
|
||||
) -> tuple[list[Order], int]:
|
||||
"""Get orders for a specific customer."""
|
||||
return self.get_vendor_orders(
|
||||
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
|
||||
|
||||
# Update timestamp based on status
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
if order_update.status == "shipped" and not order.shipped_at:
|
||||
order.shipped_at = now
|
||||
elif order_update.status == "delivered" and not order.delivered_at:
|
||||
@@ -351,7 +353,7 @@ class OrderService:
|
||||
if 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.refresh(order)
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ This module provides:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (ProductAlreadyExistsException,
|
||||
ProductNotFoundException, ValidationException)
|
||||
from app.exceptions import (
|
||||
ProductAlreadyExistsException,
|
||||
ProductNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.product import Product
|
||||
from models.schema.product import ProductCreate, ProductUpdate
|
||||
@@ -106,7 +108,7 @@ class ProductService:
|
||||
|
||||
if existing:
|
||||
raise ProductAlreadyExistsException(
|
||||
f"Product already exists in catalog"
|
||||
"Product already exists in catalog"
|
||||
)
|
||||
|
||||
# Create product
|
||||
@@ -167,7 +169,7 @@ class ProductService:
|
||||
for key, value in update_data.items():
|
||||
setattr(product, key, value)
|
||||
|
||||
product.updated_at = datetime.now(timezone.utc)
|
||||
product.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
@@ -216,9 +218,9 @@ class ProductService:
|
||||
vendor_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
is_active: Optional[bool] = None,
|
||||
is_featured: Optional[bool] = None,
|
||||
) -> Tuple[List[Product], int]:
|
||||
is_active: bool | None = None,
|
||||
is_featured: bool | None = None,
|
||||
) -> tuple[list[Product], int]:
|
||||
"""
|
||||
Get products in vendor catalog with filtering.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ This module provides:
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -36,7 +36,7 @@ class StatsService:
|
||||
# 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.
|
||||
|
||||
@@ -177,7 +177,7 @@ class StatsService:
|
||||
|
||||
def get_vendor_analytics(
|
||||
self, db: Session, vendor_id: int, period: str = "30d"
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Get a specific vendor analytics for a time period.
|
||||
|
||||
@@ -283,7 +283,7 @@ class StatsService:
|
||||
# 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.
|
||||
|
||||
@@ -333,7 +333,7 @@ class StatsService:
|
||||
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.
|
||||
|
||||
@@ -382,7 +382,7 @@ class StatsService:
|
||||
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.
|
||||
|
||||
@@ -416,7 +416,7 @@ class StatsService:
|
||||
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.
|
||||
|
||||
@@ -457,7 +457,7 @@ class StatsService:
|
||||
"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.
|
||||
|
||||
@@ -472,7 +472,7 @@ class StatsService:
|
||||
"""
|
||||
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.
|
||||
|
||||
@@ -548,7 +548,7 @@ class StatsService:
|
||||
.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.
|
||||
|
||||
|
||||
@@ -9,13 +9,12 @@ This module provides:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (UnauthorizedVendorAccessException,
|
||||
ValidationException)
|
||||
from app.exceptions import ValidationException
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Role, VendorUser
|
||||
|
||||
@@ -27,7 +26,7 @@ class TeamService:
|
||||
|
||||
def get_team_members(
|
||||
self, db: Session, vendor_id: int, current_user: User
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all team members for vendor.
|
||||
|
||||
@@ -69,7 +68,7 @@ class TeamService:
|
||||
|
||||
def invite_team_member(
|
||||
self, db: Session, vendor_id: int, invitation_data: dict, current_user: User
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Invite a new team member.
|
||||
|
||||
@@ -102,7 +101,7 @@ class TeamService:
|
||||
user_id: int,
|
||||
update_data: dict,
|
||||
current_user: User,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Update team member role or status.
|
||||
|
||||
@@ -135,7 +134,7 @@ class TeamService:
|
||||
if "is_active" in update_data:
|
||||
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.refresh(vendor_user)
|
||||
|
||||
@@ -178,7 +177,7 @@ class TeamService:
|
||||
|
||||
# Soft delete
|
||||
vendor_user.is_active = False
|
||||
vendor_user.updated_at = datetime.now(timezone.utc)
|
||||
vendor_user.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
|
||||
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)}")
|
||||
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.
|
||||
|
||||
|
||||
@@ -12,25 +12,23 @@ This module provides classes and functions for:
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (DNSVerificationException,
|
||||
DomainAlreadyVerifiedException,
|
||||
DomainNotVerifiedException,
|
||||
DomainVerificationFailedException,
|
||||
InvalidDomainFormatException,
|
||||
MaxDomainsReachedException,
|
||||
MultiplePrimaryDomainsException,
|
||||
ReservedDomainException,
|
||||
UnauthorizedDomainAccessException,
|
||||
ValidationException,
|
||||
VendorDomainAlreadyExistsException,
|
||||
VendorDomainNotFoundException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions import (
|
||||
DNSVerificationException,
|
||||
DomainAlreadyVerifiedException,
|
||||
DomainNotVerifiedException,
|
||||
DomainVerificationFailedException,
|
||||
InvalidDomainFormatException,
|
||||
MaxDomainsReachedException,
|
||||
ReservedDomainException,
|
||||
ValidationException,
|
||||
VendorDomainAlreadyExistsException,
|
||||
VendorDomainNotFoundException,
|
||||
VendorNotFoundException,
|
||||
)
|
||||
from models.database.vendor import Vendor
|
||||
from models.database.vendor_domain import VendorDomain
|
||||
from models.schema.vendor_domain import VendorDomainCreate, VendorDomainUpdate
|
||||
@@ -135,7 +133,7 @@ class VendorDomainService:
|
||||
logger.error(f"Error adding domain: {str(e)}")
|
||||
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.
|
||||
|
||||
@@ -272,7 +270,7 @@ class VendorDomainService:
|
||||
logger.error(f"Error deleting domain: {str(e)}")
|
||||
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.
|
||||
|
||||
@@ -313,7 +311,7 @@ class VendorDomainService:
|
||||
if txt_value == domain.verification_token:
|
||||
# Verification successful
|
||||
domain.is_verified = True
|
||||
domain.verified_at = datetime.now(timezone.utc)
|
||||
domain.verified_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(domain)
|
||||
|
||||
@@ -419,7 +417,7 @@ class VendorDomainService:
|
||||
raise ReservedDomainException(domain, first_part)
|
||||
|
||||
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:
|
||||
"""Unset all primary domains for vendor."""
|
||||
query = db.query(VendorDomain).filter(
|
||||
|
||||
@@ -10,18 +10,20 @@ This module provides classes and functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InvalidVendorDataException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MaxVendorsReachedException,
|
||||
ProductAlreadyExistsException,
|
||||
UnauthorizedVendorAccessException,
|
||||
ValidationException, VendorAlreadyExistsException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions import (
|
||||
InvalidVendorDataException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MaxVendorsReachedException,
|
||||
ProductAlreadyExistsException,
|
||||
UnauthorizedVendorAccessException,
|
||||
ValidationException,
|
||||
VendorAlreadyExistsException,
|
||||
VendorNotFoundException,
|
||||
)
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.product import Product
|
||||
from models.database.user import User
|
||||
@@ -108,7 +110,7 @@ class VendorService:
|
||||
limit: int = 100,
|
||||
active_only: bool = True,
|
||||
verified_only: bool = False,
|
||||
) -> Tuple[List[Vendor], int]:
|
||||
) -> tuple[list[Vendor], int]:
|
||||
"""
|
||||
Get vendors with filtering.
|
||||
|
||||
@@ -257,7 +259,7 @@ class VendorService:
|
||||
limit: int = 100,
|
||||
active_only: bool = True,
|
||||
featured_only: bool = False,
|
||||
) -> Tuple[List[Product], int]:
|
||||
) -> tuple[list[Product], int]:
|
||||
"""
|
||||
Get products in vendor catalog with filtering.
|
||||
|
||||
|
||||
@@ -8,20 +8,23 @@ Handles:
|
||||
- Role assignment
|
||||
- Permission management
|
||||
"""
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.permissions import get_preset_permissions
|
||||
from app.exceptions import (CannotRemoveOwnerException,
|
||||
InvalidInvitationTokenException,
|
||||
MaxTeamMembersReachedException,
|
||||
TeamInvitationAlreadyAcceptedException,
|
||||
TeamMemberAlreadyExistsException,
|
||||
UserNotFoundException, VendorNotFoundException)
|
||||
from app.exceptions import (
|
||||
CannotRemoveOwnerException,
|
||||
InvalidInvitationTokenException,
|
||||
MaxTeamMembersReachedException,
|
||||
TeamInvitationAlreadyAcceptedException,
|
||||
TeamMemberAlreadyExistsException,
|
||||
UserNotFoundException,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Role, Vendor, VendorUser, VendorUserType
|
||||
@@ -43,8 +46,8 @@ class VendorTeamService:
|
||||
inviter: User,
|
||||
email: str,
|
||||
role_name: str,
|
||||
custom_permissions: Optional[List[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
custom_permissions: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Invite a new team member to a vendor.
|
||||
|
||||
@@ -196,9 +199,9 @@ class VendorTeamService:
|
||||
db: Session,
|
||||
invitation_token: str,
|
||||
password: str,
|
||||
first_name: Optional[str] = None,
|
||||
last_name: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
first_name: str | None = None,
|
||||
last_name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Accept a team invitation and activate account.
|
||||
|
||||
@@ -330,7 +333,7 @@ class VendorTeamService:
|
||||
vendor: Vendor,
|
||||
user_id: int,
|
||||
new_role_name: str,
|
||||
custom_permissions: Optional[List[str]] = None,
|
||||
custom_permissions: list[str] | None = None,
|
||||
) -> VendorUser:
|
||||
"""
|
||||
Update a team member's role.
|
||||
@@ -392,7 +395,7 @@ class VendorTeamService:
|
||||
db: Session,
|
||||
vendor: Vendor,
|
||||
include_inactive: bool = False,
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all team members for a vendor.
|
||||
|
||||
@@ -445,7 +448,7 @@ class VendorTeamService:
|
||||
db: Session,
|
||||
vendor: Vendor,
|
||||
role_name: str,
|
||||
custom_permissions: Optional[List[str]] = None,
|
||||
custom_permissions: list[str] | None = None,
|
||||
) -> Role:
|
||||
"""Get existing role or create new one with preset/custom permissions."""
|
||||
# Try to find existing role with same name
|
||||
@@ -492,7 +495,6 @@ class VendorTeamService:
|
||||
# - Vendor name
|
||||
# - Inviter name
|
||||
# - Expiry date
|
||||
pass
|
||||
|
||||
|
||||
# Create service instance
|
||||
|
||||
@@ -8,21 +8,24 @@ Handles theme CRUD operations, preset application, and validation.
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.theme_presets import (THEME_PRESETS, apply_preset,
|
||||
get_available_presets, get_preset_preview)
|
||||
from app.core.theme_presets import (
|
||||
THEME_PRESETS,
|
||||
apply_preset,
|
||||
get_available_presets,
|
||||
get_preset_preview,
|
||||
)
|
||||
from app.exceptions.vendor import VendorNotFoundException
|
||||
from app.exceptions.vendor_theme import (InvalidColorFormatException,
|
||||
InvalidFontFamilyException,
|
||||
InvalidThemeDataException,
|
||||
ThemeOperationException,
|
||||
ThemePresetAlreadyAppliedException,
|
||||
ThemePresetNotFoundException,
|
||||
ThemeValidationException,
|
||||
VendorThemeNotFoundException)
|
||||
from app.exceptions.vendor_theme import (
|
||||
InvalidColorFormatException,
|
||||
InvalidFontFamilyException,
|
||||
ThemeOperationException,
|
||||
ThemePresetNotFoundException,
|
||||
ThemeValidationException,
|
||||
VendorThemeNotFoundException,
|
||||
)
|
||||
from models.database.vendor import Vendor
|
||||
from models.database.vendor_theme import VendorTheme
|
||||
from models.schema.vendor_theme import ThemePresetPreview, VendorThemeUpdate
|
||||
@@ -77,7 +80,7 @@ class VendorThemeService:
|
||||
# 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.
|
||||
|
||||
@@ -107,7 +110,7 @@ class VendorThemeService:
|
||||
|
||||
return theme.to_dict()
|
||||
|
||||
def _get_default_theme(self) -> Dict:
|
||||
def _get_default_theme(self) -> dict:
|
||||
"""
|
||||
Get default theme configuration.
|
||||
|
||||
@@ -329,7 +332,7 @@ class VendorThemeService:
|
||||
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.
|
||||
|
||||
@@ -351,7 +354,7 @@ class VendorThemeService:
|
||||
# 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).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# app/tasks/background_tasks.py
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
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}")
|
||||
job.status = "failed"
|
||||
job.error_message = f"Vendor {vendor_id} not found"
|
||||
job.completed_at = datetime.now(timezone.utc)
|
||||
job.completed_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
return
|
||||
|
||||
# Update job status
|
||||
job.status = "processing"
|
||||
job.started_at = datetime.now(timezone.utc)
|
||||
job.started_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
|
||||
logger.info(
|
||||
@@ -64,7 +64,7 @@ async def process_marketplace_import(
|
||||
|
||||
# Update job with results
|
||||
job.status = "completed"
|
||||
job.completed_at = datetime.now(timezone.utc)
|
||||
job.completed_at = datetime.now(UTC)
|
||||
job.imported_count = result["imported"]
|
||||
job.updated_count = result["updated"]
|
||||
job.error_count = result.get("errors", 0)
|
||||
@@ -87,13 +87,13 @@ async def process_marketplace_import(
|
||||
try:
|
||||
job.status = "failed"
|
||||
job.error_message = str(e)
|
||||
job.completed_at = datetime.now(timezone.utc)
|
||||
job.completed_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
except Exception as commit_error:
|
||||
logger.error(f"Failed to update job status: {commit_error}")
|
||||
db.rollback()
|
||||
finally:
|
||||
if hasattr(db, "close") and callable(getattr(db, "close")):
|
||||
if hasattr(db, "close") and callable(db.close):
|
||||
try:
|
||||
db.close()
|
||||
except Exception as close_error:
|
||||
|
||||
@@ -8,9 +8,9 @@ This module provides classes and functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from io import StringIO
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
import requests
|
||||
@@ -145,7 +145,7 @@ class CSVProcessor:
|
||||
logger.info(f"Normalized columns: {list(df.columns)}")
|
||||
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."""
|
||||
# Handle NaN values
|
||||
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(
|
||||
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.
|
||||
|
||||
@@ -245,7 +245,7 @@ class CSVProcessor:
|
||||
vendor_name: str,
|
||||
db: Session,
|
||||
batch_num: int,
|
||||
) -> Dict[str, int]:
|
||||
) -> dict[str, int]:
|
||||
"""Process a batch of CSV rows with marketplace information."""
|
||||
imported = 0
|
||||
updated = 0
|
||||
@@ -295,7 +295,7 @@ class CSVProcessor:
|
||||
existing_product, key
|
||||
):
|
||||
setattr(existing_product, key, value)
|
||||
existing_product.updated_at = datetime.now(timezone.utc)
|
||||
existing_product.updated_at = datetime.now(UTC)
|
||||
updated += 1
|
||||
logger.debug(
|
||||
f"Updated product {product_data['marketplace_product_id']} for "
|
||||
|
||||
@@ -9,7 +9,6 @@ This module provides classes and functions for:
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
|
||||
@@ -21,7 +20,7 @@ class GTINProcessor:
|
||||
|
||||
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.
|
||||
|
||||
@@ -55,11 +54,11 @@ class GTINProcessor:
|
||||
# Standard lengths - pad appropriately
|
||||
if length == 8:
|
||||
return gtin_clean.zfill(8) # EAN-8
|
||||
elif length == 12:
|
||||
if length == 12:
|
||||
return gtin_clean.zfill(12) # UPC-A
|
||||
elif length == 13:
|
||||
if length == 13:
|
||||
return gtin_clean.zfill(13) # EAN-13
|
||||
elif length == 14:
|
||||
if length == 14:
|
||||
return gtin_clean.zfill(14) # GTIN-14
|
||||
|
||||
elif length > 14:
|
||||
@@ -111,7 +110,7 @@ class PriceProcessor:
|
||||
|
||||
def parse_price_currency(
|
||||
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.
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ def get_db_engine(database_url: str):
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user