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:
31
Makefile
31
Makefile
@@ -175,24 +175,24 @@ test-inventory:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@echo "Running black..."
|
@echo "Formatting code with ruff..."
|
||||||
$(PYTHON) -m black . --exclude '/(\.)?venv/'
|
$(PYTHON) -m ruff format .
|
||||||
@echo "Running isort..."
|
|
||||||
$(PYTHON) -m isort . --skip venv --skip .venv
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@echo "Running linting..."
|
@echo "Linting code with ruff..."
|
||||||
$(PYTHON) -m ruff check . --exclude venv --exclude .venv
|
$(PYTHON) -m ruff check . --fix
|
||||||
$(PYTHON) -m mypy . --ignore-missing-imports --exclude '.*(\.)?venv.*'
|
@echo "Type checking with mypy..."
|
||||||
|
$(PYTHON) -m mypy .
|
||||||
|
|
||||||
lint-flake8:
|
lint-strict:
|
||||||
@echo "Running linting..."
|
@echo "Linting (no auto-fix)..."
|
||||||
$(PYTHON) -m flake8 . --max-line-length=120 --extend-ignore=E203,W503,I201,I100 --exclude=venv,.venv,__pycache__,.git
|
$(PYTHON) -m ruff check .
|
||||||
$(PYTHON) -m mypy . --ignore-missing-imports --exclude '.*(\.)?venv.*'
|
@echo "Type checking with mypy..."
|
||||||
|
$(PYTHON) -m mypy .
|
||||||
|
|
||||||
check: format lint
|
check: format lint
|
||||||
|
|
||||||
ci: format lint test-coverage
|
ci: lint-strict test-coverage
|
||||||
|
|
||||||
qa: format lint test-coverage docs-check
|
qa: format lint test-coverage docs-check
|
||||||
@echo "Quality assurance checks completed!"
|
@echo "Quality assurance checks completed!"
|
||||||
@@ -330,10 +330,11 @@ help:
|
|||||||
@echo " test-fast - Run fast tests only"
|
@echo " test-fast - Run fast tests only"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "=== CODE QUALITY ==="
|
@echo "=== CODE QUALITY ==="
|
||||||
@echo " format - Format code (black + isort)"
|
@echo " format - Format code with ruff"
|
||||||
@echo " lint - Run linting (ruff + mypy)"
|
@echo " lint - Lint and auto-fix with ruff + mypy"
|
||||||
|
@echo " lint-strict - Lint without auto-fix + mypy"
|
||||||
@echo " check - Format + lint"
|
@echo " check - Format + lint"
|
||||||
@echo " ci - Full CI pipeline"
|
@echo " ci - Full CI pipeline (strict)"
|
||||||
@echo " qa - Quality assurance"
|
@echo " qa - Quality assurance"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "=== DOCUMENTATION ==="
|
@echo "=== DOCUMENTATION ==="
|
||||||
|
|||||||
@@ -40,9 +40,13 @@ print("=" * 70)
|
|||||||
# ADMIN MODELS
|
# ADMIN MODELS
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
try:
|
try:
|
||||||
from models.database.admin import (AdminAuditLog, AdminNotification,
|
from models.database.admin import (
|
||||||
AdminSession, AdminSetting,
|
AdminAuditLog,
|
||||||
PlatformAlert)
|
AdminNotification,
|
||||||
|
AdminSession,
|
||||||
|
AdminSetting,
|
||||||
|
PlatformAlert,
|
||||||
|
)
|
||||||
|
|
||||||
print(" ✓ Admin models imported (5 models)")
|
print(" ✓ Admin models imported (5 models)")
|
||||||
print(" - AdminAuditLog")
|
print(" - AdminAuditLog")
|
||||||
@@ -176,8 +180,8 @@ except ImportError as e:
|
|||||||
# SUMMARY
|
# SUMMARY
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
print("=" * 70)
|
print("=" * 70)
|
||||||
print(f"[ALEMBIC] Model import completed")
|
print("[ALEMBIC] Model import completed")
|
||||||
print(f"[ALEMBIC] Tables detected in metadata:")
|
print("[ALEMBIC] Tables detected in metadata:")
|
||||||
print("=" * 70)
|
print("=" * 70)
|
||||||
|
|
||||||
if Base.metadata.tables:
|
if Base.metadata.tables:
|
||||||
|
|||||||
@@ -32,18 +32,20 @@ The cookie path restrictions prevent cross-context cookie leakage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from datetime import UTC
|
||||||
|
|
||||||
from fastapi import Cookie, Depends, Request
|
from fastapi import Cookie, Depends, Request
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.exceptions import (AdminRequiredException,
|
from app.exceptions import (
|
||||||
InsufficientPermissionsException,
|
AdminRequiredException,
|
||||||
InvalidTokenException,
|
InsufficientPermissionsException,
|
||||||
UnauthorizedVendorAccessException,
|
InvalidTokenException,
|
||||||
VendorNotFoundException)
|
UnauthorizedVendorAccessException,
|
||||||
|
VendorNotFoundException,
|
||||||
|
)
|
||||||
from middleware.auth import AuthManager
|
from middleware.auth import AuthManager
|
||||||
from middleware.rate_limiter import RateLimiter
|
from middleware.rate_limiter import RateLimiter
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
@@ -62,11 +64,11 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def _get_token_from_request(
|
def _get_token_from_request(
|
||||||
credentials: Optional[HTTPAuthorizationCredentials],
|
credentials: HTTPAuthorizationCredentials | None,
|
||||||
cookie_value: Optional[str],
|
cookie_value: str | None,
|
||||||
cookie_name: str,
|
cookie_name: str,
|
||||||
request_path: str,
|
request_path: str,
|
||||||
) -> tuple[Optional[str], Optional[str]]:
|
) -> tuple[str | None, str | None]:
|
||||||
"""
|
"""
|
||||||
Extract token from Authorization header or cookie.
|
Extract token from Authorization header or cookie.
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ def _get_token_from_request(
|
|||||||
if credentials:
|
if credentials:
|
||||||
logger.debug(f"Token found in Authorization header for {request_path}")
|
logger.debug(f"Token found in Authorization header for {request_path}")
|
||||||
return credentials.credentials, "header"
|
return credentials.credentials, "header"
|
||||||
elif cookie_value:
|
if cookie_value:
|
||||||
logger.debug(f"Token found in {cookie_name} cookie for {request_path}")
|
logger.debug(f"Token found in {cookie_name} cookie for {request_path}")
|
||||||
return cookie_value, "cookie"
|
return cookie_value, "cookie"
|
||||||
|
|
||||||
@@ -118,8 +120,8 @@ def _validate_user_token(token: str, db: Session) -> User:
|
|||||||
|
|
||||||
def get_current_admin_from_cookie_or_header(
|
def get_current_admin_from_cookie_or_header(
|
||||||
request: Request,
|
request: Request,
|
||||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||||
admin_token: Optional[str] = Cookie(None),
|
admin_token: str | None = Cookie(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> User:
|
) -> User:
|
||||||
"""
|
"""
|
||||||
@@ -205,8 +207,8 @@ def get_current_admin_api(
|
|||||||
|
|
||||||
def get_current_vendor_from_cookie_or_header(
|
def get_current_vendor_from_cookie_or_header(
|
||||||
request: Request,
|
request: Request,
|
||||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||||
vendor_token: Optional[str] = Cookie(None),
|
vendor_token: str | None = Cookie(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> User:
|
) -> User:
|
||||||
"""
|
"""
|
||||||
@@ -305,8 +307,8 @@ def get_current_vendor_api(
|
|||||||
|
|
||||||
def get_current_customer_from_cookie_or_header(
|
def get_current_customer_from_cookie_or_header(
|
||||||
request: Request,
|
request: Request,
|
||||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||||
customer_token: Optional[str] = Cookie(None),
|
customer_token: str | None = Cookie(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -331,7 +333,7 @@ def get_current_customer_from_cookie_or_header(
|
|||||||
Raises:
|
Raises:
|
||||||
InvalidTokenException: If no token or invalid token
|
InvalidTokenException: If no token or invalid token
|
||||||
"""
|
"""
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime
|
||||||
|
|
||||||
from jose import JWTError, jwt
|
from jose import JWTError, jwt
|
||||||
|
|
||||||
@@ -365,8 +367,8 @@ def get_current_customer_from_cookie_or_header(
|
|||||||
|
|
||||||
# Verify token hasn't expired
|
# Verify token hasn't expired
|
||||||
exp = payload.get("exp")
|
exp = payload.get("exp")
|
||||||
if exp and datetime.fromtimestamp(exp, tz=timezone.utc) < datetime.now(
|
if exp and datetime.fromtimestamp(exp, tz=UTC) < datetime.now(
|
||||||
timezone.utc
|
UTC
|
||||||
):
|
):
|
||||||
logger.warning(f"Expired customer token for customer_id={customer_id}")
|
logger.warning(f"Expired customer token for customer_id={customer_id}")
|
||||||
raise InvalidTokenException("Token has expired")
|
raise InvalidTokenException("Token has expired")
|
||||||
@@ -694,10 +696,10 @@ def get_user_permissions(
|
|||||||
|
|
||||||
def get_current_admin_optional(
|
def get_current_admin_optional(
|
||||||
request: Request,
|
request: Request,
|
||||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||||
admin_token: Optional[str] = Cookie(None),
|
admin_token: str | None = Cookie(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
"""
|
"""
|
||||||
Get current admin user from admin_token cookie or Authorization header.
|
Get current admin user from admin_token cookie or Authorization header.
|
||||||
|
|
||||||
@@ -741,10 +743,10 @@ def get_current_admin_optional(
|
|||||||
|
|
||||||
def get_current_vendor_optional(
|
def get_current_vendor_optional(
|
||||||
request: Request,
|
request: Request,
|
||||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||||
vendor_token: Optional[str] = Cookie(None),
|
vendor_token: str | None = Cookie(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
"""
|
"""
|
||||||
Get current vendor user from vendor_token cookie or Authorization header.
|
Get current vendor user from vendor_token cookie or Authorization header.
|
||||||
|
|
||||||
@@ -788,10 +790,10 @@ def get_current_vendor_optional(
|
|||||||
|
|
||||||
def get_current_customer_optional(
|
def get_current_customer_optional(
|
||||||
request: Request,
|
request: Request,
|
||||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||||
customer_token: Optional[str] = Cookie(None),
|
customer_token: str | None = Cookie(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
"""
|
"""
|
||||||
Get current customer user from customer_token cookie or Authorization header.
|
Get current customer user from customer_token cookie or Authorization header.
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,21 @@ IMPORTANT:
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
# Import all admin routers
|
# Import all admin routers
|
||||||
from . import (audit, auth, code_quality, content_pages, dashboard,
|
from . import (
|
||||||
marketplace, monitoring, notifications, settings, users,
|
audit,
|
||||||
vendor_domains, vendor_themes, vendors)
|
auth,
|
||||||
|
code_quality,
|
||||||
|
content_pages,
|
||||||
|
dashboard,
|
||||||
|
marketplace,
|
||||||
|
monitoring,
|
||||||
|
notifications,
|
||||||
|
settings,
|
||||||
|
users,
|
||||||
|
vendor_domains,
|
||||||
|
vendor_themes,
|
||||||
|
vendors,
|
||||||
|
)
|
||||||
|
|
||||||
# Create admin router
|
# Create admin router
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ Provides endpoints for:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -19,9 +18,11 @@ from app.api.deps import get_current_admin_api
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.services.admin_audit_service import admin_audit_service
|
from app.services.admin_audit_service import admin_audit_service
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.schema.admin import (AdminAuditLogFilters,
|
from models.schema.admin import (
|
||||||
AdminAuditLogListResponse,
|
AdminAuditLogFilters,
|
||||||
AdminAuditLogResponse)
|
AdminAuditLogListResponse,
|
||||||
|
AdminAuditLogResponse,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/audit")
|
router = APIRouter(prefix="/audit")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -29,11 +30,11 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@router.get("/logs", response_model=AdminAuditLogListResponse)
|
@router.get("/logs", response_model=AdminAuditLogListResponse)
|
||||||
def get_audit_logs(
|
def get_audit_logs(
|
||||||
admin_user_id: Optional[int] = Query(None, description="Filter by admin user"),
|
admin_user_id: int | None = Query(None, description="Filter by admin user"),
|
||||||
action: Optional[str] = Query(None, description="Filter by action type"),
|
action: str | None = Query(None, description="Filter by action type"),
|
||||||
target_type: Optional[str] = Query(None, description="Filter by target type"),
|
target_type: str | None = Query(None, description="Filter by target type"),
|
||||||
date_from: Optional[datetime] = Query(None, description="Filter from date"),
|
date_from: datetime | None = Query(None, description="Filter from date"),
|
||||||
date_to: Optional[datetime] = Query(None, description="Filter to date"),
|
date_to: datetime | None = Query(None, description="Filter to date"),
|
||||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||||
limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"),
|
limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ RESTful API for architecture validation and violation management
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -32,7 +31,7 @@ class ScanResponse(BaseModel):
|
|||||||
warnings: int
|
warnings: int
|
||||||
duration_seconds: float
|
duration_seconds: float
|
||||||
triggered_by: str
|
triggered_by: str
|
||||||
git_commit_hash: Optional[str]
|
git_commit_hash: str | None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
@@ -49,13 +48,13 @@ class ViolationResponse(BaseModel):
|
|||||||
file_path: str
|
file_path: str
|
||||||
line_number: int
|
line_number: int
|
||||||
message: str
|
message: str
|
||||||
context: Optional[str]
|
context: str | None
|
||||||
suggestion: Optional[str]
|
suggestion: str | None
|
||||||
status: str
|
status: str
|
||||||
assigned_to: Optional[int]
|
assigned_to: int | None
|
||||||
resolved_at: Optional[str]
|
resolved_at: str | None
|
||||||
resolved_by: Optional[int]
|
resolved_by: int | None
|
||||||
resolution_note: Optional[str]
|
resolution_note: str | None
|
||||||
created_at: str
|
created_at: str
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@@ -83,7 +82,7 @@ class AssignViolationRequest(BaseModel):
|
|||||||
"""Request model for assigning a violation"""
|
"""Request model for assigning a violation"""
|
||||||
|
|
||||||
user_id: int = Field(..., description="User ID to assign to")
|
user_id: int = Field(..., description="User ID to assign to")
|
||||||
due_date: Optional[datetime] = Field(None, description="Due date for resolution")
|
due_date: datetime | None = Field(None, description="Due date for resolution")
|
||||||
priority: str = Field(
|
priority: str = Field(
|
||||||
"medium", description="Priority level (low, medium, high, critical)"
|
"medium", description="Priority level (low, medium, high, critical)"
|
||||||
)
|
)
|
||||||
@@ -123,7 +122,7 @@ class DashboardStatsResponse(BaseModel):
|
|||||||
by_rule: dict
|
by_rule: dict
|
||||||
by_module: dict
|
by_module: dict
|
||||||
top_files: list
|
top_files: list
|
||||||
last_scan: Optional[str]
|
last_scan: str | None
|
||||||
|
|
||||||
|
|
||||||
# API Endpoints
|
# API Endpoints
|
||||||
@@ -189,17 +188,17 @@ async def list_scans(
|
|||||||
|
|
||||||
@router.get("/violations", response_model=ViolationListResponse)
|
@router.get("/violations", response_model=ViolationListResponse)
|
||||||
async def list_violations(
|
async def list_violations(
|
||||||
scan_id: Optional[int] = Query(
|
scan_id: int | None = Query(
|
||||||
None, description="Filter by scan ID (defaults to latest)"
|
None, description="Filter by scan ID (defaults to latest)"
|
||||||
),
|
),
|
||||||
severity: Optional[str] = Query(
|
severity: str | None = Query(
|
||||||
None, description="Filter by severity (error, warning)"
|
None, description="Filter by severity (error, warning)"
|
||||||
),
|
),
|
||||||
status: Optional[str] = Query(
|
status: str | None = Query(
|
||||||
None, description="Filter by status (open, assigned, resolved, ignored)"
|
None, description="Filter by status (open, assigned, resolved, ignored)"
|
||||||
),
|
),
|
||||||
rule_id: Optional[str] = Query(None, description="Filter by rule ID"),
|
rule_id: str | None = Query(None, description="Filter by rule ID"),
|
||||||
file_path: Optional[str] = Query(
|
file_path: str | None = Query(
|
||||||
None, description="Filter by file path (partial match)"
|
None, description="Filter by file path (partial match)"
|
||||||
),
|
),
|
||||||
page: int = Query(1, ge=1, description="Page number"),
|
page: int = Query(1, ge=1, description="Page number"),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ Platform administrators can:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -46,17 +45,17 @@ class ContentPageCreate(BaseModel):
|
|||||||
max_length=50,
|
max_length=50,
|
||||||
description="Template name (default, minimal, modern)",
|
description="Template name (default, minimal, modern)",
|
||||||
)
|
)
|
||||||
meta_description: Optional[str] = Field(
|
meta_description: str | None = Field(
|
||||||
None, max_length=300, description="SEO meta description"
|
None, max_length=300, description="SEO meta description"
|
||||||
)
|
)
|
||||||
meta_keywords: Optional[str] = Field(
|
meta_keywords: str | None = Field(
|
||||||
None, max_length=300, description="SEO keywords"
|
None, max_length=300, description="SEO keywords"
|
||||||
)
|
)
|
||||||
is_published: bool = Field(default=False, description="Publish immediately")
|
is_published: bool = Field(default=False, description="Publish immediately")
|
||||||
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
|
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
|
||||||
show_in_header: bool = Field(default=False, description="Show in header navigation")
|
show_in_header: bool = Field(default=False, description="Show in header navigation")
|
||||||
display_order: int = Field(default=0, description="Display order (lower = first)")
|
display_order: int = Field(default=0, description="Display order (lower = first)")
|
||||||
vendor_id: Optional[int] = Field(
|
vendor_id: int | None = Field(
|
||||||
None, description="Vendor ID (None for platform default)"
|
None, description="Vendor ID (None for platform default)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,32 +63,32 @@ class ContentPageCreate(BaseModel):
|
|||||||
class ContentPageUpdate(BaseModel):
|
class ContentPageUpdate(BaseModel):
|
||||||
"""Schema for updating a content page."""
|
"""Schema for updating a content page."""
|
||||||
|
|
||||||
title: Optional[str] = Field(None, max_length=200)
|
title: str | None = Field(None, max_length=200)
|
||||||
content: Optional[str] = None
|
content: str | None = None
|
||||||
content_format: Optional[str] = None
|
content_format: str | None = None
|
||||||
template: Optional[str] = Field(None, max_length=50)
|
template: str | None = Field(None, max_length=50)
|
||||||
meta_description: Optional[str] = Field(None, max_length=300)
|
meta_description: str | None = Field(None, max_length=300)
|
||||||
meta_keywords: Optional[str] = Field(None, max_length=300)
|
meta_keywords: str | None = Field(None, max_length=300)
|
||||||
is_published: Optional[bool] = None
|
is_published: bool | None = None
|
||||||
show_in_footer: Optional[bool] = None
|
show_in_footer: bool | None = None
|
||||||
show_in_header: Optional[bool] = None
|
show_in_header: bool | None = None
|
||||||
display_order: Optional[int] = None
|
display_order: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class ContentPageResponse(BaseModel):
|
class ContentPageResponse(BaseModel):
|
||||||
"""Schema for content page response."""
|
"""Schema for content page response."""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
vendor_id: Optional[int]
|
vendor_id: int | None
|
||||||
vendor_name: Optional[str]
|
vendor_name: str | None
|
||||||
slug: str
|
slug: str
|
||||||
title: str
|
title: str
|
||||||
content: str
|
content: str
|
||||||
content_format: str
|
content_format: str
|
||||||
meta_description: Optional[str]
|
meta_description: str | None
|
||||||
meta_keywords: Optional[str]
|
meta_keywords: str | None
|
||||||
is_published: bool
|
is_published: bool
|
||||||
published_at: Optional[str]
|
published_at: str | None
|
||||||
display_order: int
|
display_order: int
|
||||||
show_in_footer: bool
|
show_in_footer: bool
|
||||||
show_in_header: bool
|
show_in_header: bool
|
||||||
@@ -97,8 +96,8 @@ class ContentPageResponse(BaseModel):
|
|||||||
is_vendor_override: bool
|
is_vendor_override: bool
|
||||||
created_at: str
|
created_at: str
|
||||||
updated_at: str
|
updated_at: str
|
||||||
created_by: Optional[int]
|
created_by: int | None
|
||||||
updated_by: Optional[int]
|
updated_by: int | None
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -106,7 +105,7 @@ class ContentPageResponse(BaseModel):
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
@router.get("/platform", response_model=List[ContentPageResponse])
|
@router.get("/platform", response_model=list[ContentPageResponse])
|
||||||
def list_platform_pages(
|
def list_platform_pages(
|
||||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||||
current_user: User = Depends(get_current_admin_api),
|
current_user: User = Depends(get_current_admin_api),
|
||||||
@@ -161,9 +160,9 @@ def create_platform_page(
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[ContentPageResponse])
|
@router.get("/", response_model=list[ContentPageResponse])
|
||||||
def list_all_pages(
|
def list_all_pages(
|
||||||
vendor_id: Optional[int] = Query(None, description="Filter by vendor ID"),
|
vendor_id: int | None = Query(None, description="Filter by vendor ID"),
|
||||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||||
current_user: User = Depends(get_current_admin_api),
|
current_user: User = Depends(get_current_admin_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -256,4 +255,4 @@ def delete_page(
|
|||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(status_code=404, detail="Content page not found")
|
raise HTTPException(status_code=404, detail="Content page not found")
|
||||||
|
|
||||||
return None
|
return
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ Admin dashboard and statistics endpoints.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -57,7 +56,7 @@ def get_comprehensive_stats(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/stats/marketplace", response_model=List[MarketplaceStatsResponse])
|
@router.get("/stats/marketplace", response_model=list[MarketplaceStatsResponse])
|
||||||
def get_marketplace_stats(
|
def get_marketplace_stats(
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_admin: User = Depends(get_current_admin_api),
|
current_admin: User = Depends(get_current_admin_api),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ Marketplace import job monitoring endpoints for admin.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -20,11 +19,11 @@ router = APIRouter(prefix="/marketplace-import-jobs")
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=List[MarketplaceImportJobResponse])
|
@router.get("", response_model=list[MarketplaceImportJobResponse])
|
||||||
def get_all_marketplace_import_jobs(
|
def get_all_marketplace_import_jobs(
|
||||||
marketplace: Optional[str] = Query(None),
|
marketplace: str | None = Query(None),
|
||||||
vendor_name: Optional[str] = Query(None),
|
vendor_name: str | None = Query(None),
|
||||||
status: Optional[str] = Query(None),
|
status: str | None = Query(None),
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=100),
|
limit: int = Query(100, ge=1, le=100),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ Provides endpoints for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -17,12 +16,13 @@ from sqlalchemy.orm import Session
|
|||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.schema.admin import (AdminNotificationCreate,
|
from models.schema.admin import (
|
||||||
AdminNotificationListResponse,
|
AdminNotificationListResponse,
|
||||||
AdminNotificationResponse,
|
PlatformAlertCreate,
|
||||||
PlatformAlertCreate,
|
PlatformAlertListResponse,
|
||||||
PlatformAlertListResponse,
|
PlatformAlertResolve,
|
||||||
PlatformAlertResolve, PlatformAlertResponse)
|
PlatformAlertResponse,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/notifications")
|
router = APIRouter(prefix="/notifications")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -35,8 +35,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@router.get("", response_model=AdminNotificationListResponse)
|
@router.get("", response_model=AdminNotificationListResponse)
|
||||||
def get_notifications(
|
def get_notifications(
|
||||||
priority: Optional[str] = Query(None, description="Filter by priority"),
|
priority: str | None = Query(None, description="Filter by priority"),
|
||||||
is_read: Optional[bool] = Query(None, description="Filter by read status"),
|
is_read: bool | None = Query(None, description="Filter by read status"),
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(50, ge=1, le=100),
|
limit: int = Query(50, ge=1, le=100),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -87,8 +87,8 @@ def mark_all_as_read(
|
|||||||
|
|
||||||
@router.get("/alerts", response_model=PlatformAlertListResponse)
|
@router.get("/alerts", response_model=PlatformAlertListResponse)
|
||||||
def get_platform_alerts(
|
def get_platform_alerts(
|
||||||
severity: Optional[str] = Query(None, description="Filter by severity"),
|
severity: str | None = Query(None, description="Filter by severity"),
|
||||||
is_resolved: Optional[bool] = Query(
|
is_resolved: bool | None = Query(
|
||||||
None, description="Filter by resolution status"
|
None, description="Filter by resolution status"
|
||||||
),
|
),
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ Provides endpoints for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -19,8 +18,12 @@ from app.core.database import get_db
|
|||||||
from app.services.admin_audit_service import admin_audit_service
|
from app.services.admin_audit_service import admin_audit_service
|
||||||
from app.services.admin_settings_service import admin_settings_service
|
from app.services.admin_settings_service import admin_settings_service
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.schema.admin import (AdminSettingCreate, AdminSettingListResponse,
|
from models.schema.admin import (
|
||||||
AdminSettingResponse, AdminSettingUpdate)
|
AdminSettingCreate,
|
||||||
|
AdminSettingListResponse,
|
||||||
|
AdminSettingResponse,
|
||||||
|
AdminSettingUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/settings")
|
router = APIRouter(prefix="/settings")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -28,8 +31,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@router.get("", response_model=AdminSettingListResponse)
|
@router.get("", response_model=AdminSettingListResponse)
|
||||||
def get_all_settings(
|
def get_all_settings(
|
||||||
category: Optional[str] = Query(None, description="Filter by category"),
|
category: str | None = Query(None, description="Filter by category"),
|
||||||
is_public: Optional[bool] = Query(None, description="Filter by public flag"),
|
is_public: bool | None = Query(None, description="Filter by public flag"),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_admin: User = Depends(get_current_admin_api),
|
current_admin: User = Depends(get_current_admin_api),
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ User management endpoints for admin.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -20,7 +19,7 @@ router = APIRouter(prefix="/users")
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=List[UserResponse])
|
@router.get("", response_model=list[UserResponse])
|
||||||
def get_all_users(
|
def get_all_users(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ Follows the architecture pattern:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
from fastapi import APIRouter, Body, Depends, Path
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
@@ -21,13 +20,15 @@ from app.exceptions import VendorNotFoundException
|
|||||||
from app.services.vendor_domain_service import vendor_domain_service
|
from app.services.vendor_domain_service import vendor_domain_service
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.vendor_domain import (DomainDeletionResponse,
|
from models.schema.vendor_domain import (
|
||||||
DomainVerificationInstructions,
|
DomainDeletionResponse,
|
||||||
DomainVerificationResponse,
|
DomainVerificationInstructions,
|
||||||
VendorDomainCreate,
|
DomainVerificationResponse,
|
||||||
VendorDomainListResponse,
|
VendorDomainCreate,
|
||||||
VendorDomainResponse,
|
VendorDomainListResponse,
|
||||||
VendorDomainUpdate)
|
VendorDomainResponse,
|
||||||
|
VendorDomainUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/vendors")
|
router = APIRouter(prefix="/vendors")
|
||||||
logger = logging.getLogger(__name__)
|
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.api.deps import get_current_admin_api, get_db
|
||||||
from app.services.vendor_theme_service import vendor_theme_service
|
from app.services.vendor_theme_service import vendor_theme_service
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.schema.vendor_theme import (ThemePresetListResponse,
|
from models.schema.vendor_theme import (
|
||||||
VendorThemeResponse, VendorThemeUpdate)
|
ThemePresetListResponse,
|
||||||
|
VendorThemeResponse,
|
||||||
|
VendorThemeUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/vendor-themes")
|
router = APIRouter(prefix="/vendor-themes")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Vendor management endpoints for admin.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from datetime import UTC
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
@@ -12,18 +12,21 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.exceptions import (ConfirmationRequiredException,
|
from app.exceptions import ConfirmationRequiredException, VendorNotFoundException
|
||||||
VendorNotFoundException)
|
|
||||||
from app.services.admin_service import admin_service
|
from app.services.admin_service import admin_service
|
||||||
from app.services.stats_service import stats_service
|
from app.services.stats_service import stats_service
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.stats import VendorStatsResponse
|
from models.schema.stats import VendorStatsResponse
|
||||||
from models.schema.vendor import (VendorCreate, VendorCreateResponse,
|
from models.schema.vendor import (
|
||||||
VendorDetailResponse, VendorListResponse,
|
VendorCreate,
|
||||||
VendorResponse, VendorTransferOwnership,
|
VendorCreateResponse,
|
||||||
VendorTransferOwnershipResponse,
|
VendorDetailResponse,
|
||||||
VendorUpdate)
|
VendorListResponse,
|
||||||
|
VendorTransferOwnership,
|
||||||
|
VendorTransferOwnershipResponse,
|
||||||
|
VendorUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/vendors")
|
router = APIRouter(prefix="/vendors")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -126,9 +129,9 @@ def create_vendor_with_owner(
|
|||||||
def get_all_vendors_admin(
|
def get_all_vendors_admin(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
search: Optional[str] = Query(None, description="Search by name or vendor code"),
|
search: str | None = Query(None, description="Search by name or vendor code"),
|
||||||
is_active: Optional[bool] = Query(None),
|
is_active: bool | None = Query(None),
|
||||||
is_verified: Optional[bool] = Query(None),
|
is_verified: bool | None = Query(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_admin: User = Depends(get_current_admin_api),
|
current_admin: User = Depends(get_current_admin_api),
|
||||||
):
|
):
|
||||||
@@ -282,7 +285,7 @@ def transfer_vendor_ownership(
|
|||||||
- `confirm_transfer`: Must be true
|
- `confirm_transfer`: Must be true
|
||||||
- `transfer_reason`: Optional reason for audit trail
|
- `transfer_reason`: Optional reason for audit trail
|
||||||
"""
|
"""
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime
|
||||||
|
|
||||||
vendor = _get_vendor_by_identifier(db, vendor_identifier)
|
vendor = _get_vendor_by_identifier(db, vendor_identifier)
|
||||||
vendor, old_owner, new_owner = admin_service.transfer_vendor_ownership(
|
vendor, old_owner, new_owner = admin_service.transfer_vendor_ownership(
|
||||||
@@ -304,7 +307,7 @@ def transfer_vendor_ownership(
|
|||||||
"username": new_owner.username,
|
"username": new_owner.username,
|
||||||
"email": new_owner.email,
|
"email": new_owner.email,
|
||||||
},
|
},
|
||||||
transferred_at=datetime.now(timezone.utc),
|
transferred_at=datetime.now(UTC),
|
||||||
transfer_reason=transfer_data.transfer_reason,
|
transfer_reason=transfer_data.transfer_reason,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,13 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.services.cart_service import cart_service
|
from app.services.cart_service import cart_service
|
||||||
from models.schema.cart import (AddToCartRequest, CartOperationResponse,
|
from models.schema.cart import (
|
||||||
CartResponse, ClearCartResponse,
|
AddToCartRequest,
|
||||||
UpdateCartItemRequest)
|
CartOperationResponse,
|
||||||
|
CartResponse,
|
||||||
|
ClearCartResponse,
|
||||||
|
UpdateCartItemRequest,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ No authentication required.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -52,7 +51,7 @@ class ContentPageListItem(BaseModel):
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
@router.get("/navigation", response_model=List[ContentPageListItem])
|
@router.get("/navigation", response_model=list[ContentPageListItem])
|
||||||
def get_navigation_pages(request: Request, db: Session = Depends(get_db)):
|
def get_navigation_pages(request: Request, db: Session = Depends(get_db)):
|
||||||
"""
|
"""
|
||||||
Get list of content pages for navigation (footer/header).
|
Get list of content pages for navigation (footer/header).
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ Requires customer authentication for most operations.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -19,8 +18,12 @@ from app.services.customer_service import customer_service
|
|||||||
from app.services.order_service import order_service
|
from app.services.order_service import order_service
|
||||||
from models.database.customer import Customer
|
from models.database.customer import Customer
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.schema.order import (OrderCreate, OrderDetailResponse,
|
from models.schema.order import (
|
||||||
OrderListResponse, OrderResponse)
|
OrderCreate,
|
||||||
|
OrderDetailResponse,
|
||||||
|
OrderListResponse,
|
||||||
|
OrderResponse,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ No authentication required.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.services.product_service import product_service
|
from app.services.product_service import product_service
|
||||||
from models.schema.product import (ProductDetailResponse, ProductListResponse,
|
from models.schema.product import (
|
||||||
ProductResponse)
|
ProductDetailResponse,
|
||||||
|
ProductListResponse,
|
||||||
|
ProductResponse,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -27,8 +29,8 @@ def get_product_catalog(
|
|||||||
request: Request,
|
request: Request,
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
search: Optional[str] = Query(None, description="Search products by name"),
|
search: str | None = Query(None, description="Search products by name"),
|
||||||
is_featured: Optional[bool] = Query(
|
is_featured: bool | None = Query(
|
||||||
None, description="Filter by featured products"
|
None, description="Filter by featured products"
|
||||||
),
|
),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
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
|
from fastapi import APIRouter
|
||||||
|
|
||||||
# Import all sub-routers (JSON API only)
|
# Import all sub-routers (JSON API only)
|
||||||
from . import (analytics, auth, content_pages, customers, dashboard, info,
|
from . import (
|
||||||
inventory, marketplace, media, notifications, orders, payments,
|
analytics,
|
||||||
products, profile, settings, team)
|
auth,
|
||||||
|
content_pages,
|
||||||
|
customers,
|
||||||
|
dashboard,
|
||||||
|
info,
|
||||||
|
inventory,
|
||||||
|
marketplace,
|
||||||
|
media,
|
||||||
|
notifications,
|
||||||
|
orders,
|
||||||
|
payments,
|
||||||
|
products,
|
||||||
|
profile,
|
||||||
|
settings,
|
||||||
|
team,
|
||||||
|
)
|
||||||
|
|
||||||
# Create vendor router
|
# Create vendor router
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -41,10 +40,10 @@ class VendorContentPageCreate(BaseModel):
|
|||||||
content_format: str = Field(
|
content_format: str = Field(
|
||||||
default="html", description="Content format: html or markdown"
|
default="html", description="Content format: html or markdown"
|
||||||
)
|
)
|
||||||
meta_description: Optional[str] = Field(
|
meta_description: str | None = Field(
|
||||||
None, max_length=300, description="SEO meta description"
|
None, max_length=300, description="SEO meta description"
|
||||||
)
|
)
|
||||||
meta_keywords: Optional[str] = Field(
|
meta_keywords: str | None = Field(
|
||||||
None, max_length=300, description="SEO keywords"
|
None, max_length=300, description="SEO keywords"
|
||||||
)
|
)
|
||||||
is_published: bool = Field(default=False, description="Publish immediately")
|
is_published: bool = Field(default=False, description="Publish immediately")
|
||||||
@@ -56,31 +55,31 @@ class VendorContentPageCreate(BaseModel):
|
|||||||
class VendorContentPageUpdate(BaseModel):
|
class VendorContentPageUpdate(BaseModel):
|
||||||
"""Schema for updating a vendor content page."""
|
"""Schema for updating a vendor content page."""
|
||||||
|
|
||||||
title: Optional[str] = Field(None, max_length=200)
|
title: str | None = Field(None, max_length=200)
|
||||||
content: Optional[str] = None
|
content: str | None = None
|
||||||
content_format: Optional[str] = None
|
content_format: str | None = None
|
||||||
meta_description: Optional[str] = Field(None, max_length=300)
|
meta_description: str | None = Field(None, max_length=300)
|
||||||
meta_keywords: Optional[str] = Field(None, max_length=300)
|
meta_keywords: str | None = Field(None, max_length=300)
|
||||||
is_published: Optional[bool] = None
|
is_published: bool | None = None
|
||||||
show_in_footer: Optional[bool] = None
|
show_in_footer: bool | None = None
|
||||||
show_in_header: Optional[bool] = None
|
show_in_header: bool | None = None
|
||||||
display_order: Optional[int] = None
|
display_order: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class ContentPageResponse(BaseModel):
|
class ContentPageResponse(BaseModel):
|
||||||
"""Schema for content page response."""
|
"""Schema for content page response."""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
vendor_id: Optional[int]
|
vendor_id: int | None
|
||||||
vendor_name: Optional[str]
|
vendor_name: str | None
|
||||||
slug: str
|
slug: str
|
||||||
title: str
|
title: str
|
||||||
content: str
|
content: str
|
||||||
content_format: str
|
content_format: str
|
||||||
meta_description: Optional[str]
|
meta_description: str | None
|
||||||
meta_keywords: Optional[str]
|
meta_keywords: str | None
|
||||||
is_published: bool
|
is_published: bool
|
||||||
published_at: Optional[str]
|
published_at: str | None
|
||||||
display_order: int
|
display_order: int
|
||||||
show_in_footer: bool
|
show_in_footer: bool
|
||||||
show_in_header: bool
|
show_in_header: bool
|
||||||
@@ -88,8 +87,8 @@ class ContentPageResponse(BaseModel):
|
|||||||
is_vendor_override: bool
|
is_vendor_override: bool
|
||||||
created_at: str
|
created_at: str
|
||||||
updated_at: str
|
updated_at: str
|
||||||
created_by: Optional[int]
|
created_by: int | None
|
||||||
updated_by: Optional[int]
|
updated_by: int | None
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -97,7 +96,7 @@ class ContentPageResponse(BaseModel):
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[ContentPageResponse])
|
@router.get("/", response_model=list[ContentPageResponse])
|
||||||
def list_vendor_pages(
|
def list_vendor_pages(
|
||||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
@@ -120,7 +119,7 @@ def list_vendor_pages(
|
|||||||
return [page.to_dict() for page in pages]
|
return [page.to_dict() for page in pages]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/overrides", response_model=List[ContentPageResponse])
|
@router.get("/overrides", response_model=list[ContentPageResponse])
|
||||||
def list_vendor_overrides(
|
def list_vendor_overrides(
|
||||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
@@ -284,4 +283,4 @@ def delete_vendor_page(
|
|||||||
# Delete
|
# Delete
|
||||||
content_page_service.delete_page(db, page_id)
|
content_page_service.delete_page(db, page_id)
|
||||||
|
|
||||||
return None
|
return
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -24,8 +23,8 @@ logger = logging.getLogger(__name__)
|
|||||||
def get_vendor_customers(
|
def get_vendor_customers(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
search: Optional[str] = Query(None),
|
search: str | None = Query(None),
|
||||||
is_active: Optional[bool] = Query(None),
|
is_active: bool | None = Query(None),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
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.api.deps import get_current_vendor_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.services.stats_service import stats_service
|
from app.services.stats_service import stats_service
|
||||||
from middleware.vendor_context import require_vendor_context
|
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/dashboard")
|
router = APIRouter(prefix="/dashboard")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
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.core.database import get_db
|
||||||
from app.exceptions import VendorNotFoundException
|
from app.exceptions import VendorNotFoundException
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.vendor import VendorDetailResponse, VendorResponse
|
from models.schema.vendor import VendorDetailResponse
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
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
|
# app/api/v1/vendor/inventory.py
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -11,10 +10,15 @@ from app.services.inventory_service import inventory_service
|
|||||||
from middleware.vendor_context import require_vendor_context
|
from middleware.vendor_context import require_vendor_context
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.inventory import (InventoryAdjust, InventoryCreate,
|
from models.schema.inventory import (
|
||||||
InventoryListResponse, InventoryReserve,
|
InventoryAdjust,
|
||||||
InventoryResponse, InventoryUpdate,
|
InventoryCreate,
|
||||||
ProductInventorySummary)
|
InventoryListResponse,
|
||||||
|
InventoryReserve,
|
||||||
|
InventoryResponse,
|
||||||
|
InventoryUpdate,
|
||||||
|
ProductInventorySummary,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -90,8 +94,8 @@ def get_product_inventory(
|
|||||||
def get_vendor_inventory(
|
def get_vendor_inventory(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
location: Optional[str] = Query(None),
|
location: str | None = Query(None),
|
||||||
low_stock: Optional[int] = Query(None, ge=0),
|
low_stock: int | None = Query(None, ge=0),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, Query
|
from fastapi import APIRouter, BackgroundTasks, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import get_current_vendor_api
|
from app.api.deps import get_current_vendor_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.services.marketplace_import_job_service import \
|
from app.services.marketplace_import_job_service import marketplace_import_job_service
|
||||||
marketplace_import_job_service
|
|
||||||
from app.tasks.background_tasks import process_marketplace_import
|
from app.tasks.background_tasks import process_marketplace_import
|
||||||
from middleware.decorators import rate_limit
|
from middleware.decorators import rate_limit
|
||||||
from middleware.vendor_context import require_vendor_context # IMPORTANT
|
from middleware.vendor_context import require_vendor_context # IMPORTANT
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest,
|
from models.schema.marketplace_import_job import (
|
||||||
MarketplaceImportJobResponse)
|
MarketplaceImportJobRequest,
|
||||||
|
MarketplaceImportJobResponse,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -93,9 +93,9 @@ def get_marketplace_import_status(
|
|||||||
return marketplace_import_job_service.convert_to_response_model(job)
|
return marketplace_import_job_service.convert_to_response_model(job)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/imports", response_model=List[MarketplaceImportJobResponse])
|
@router.get("/imports", response_model=list[MarketplaceImportJobResponse])
|
||||||
def get_marketplace_import_jobs(
|
def get_marketplace_import_jobs(
|
||||||
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
|
marketplace: str | None = Query(None, description="Filter by marketplace"),
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(50, ge=1, le=100),
|
limit: int = Query(50, ge=1, le=100),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, File, Query, UploadFile
|
from fastapi import APIRouter, Depends, File, Query, UploadFile
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -24,8 +23,8 @@ logger = logging.getLogger(__name__)
|
|||||||
def get_media_library(
|
def get_media_library(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
media_type: Optional[str] = Query(None, description="image, video, document"),
|
media_type: str | None = Query(None, description="image, video, document"),
|
||||||
search: Optional[str] = Query(None),
|
search: str | None = Query(None),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -52,7 +51,7 @@ def get_media_library(
|
|||||||
@router.post("/upload")
|
@router.post("/upload")
|
||||||
async def upload_media(
|
async def upload_media(
|
||||||
file: UploadFile = File(...),
|
file: UploadFile = File(...),
|
||||||
folder: Optional[str] = Query(None, description="products, general, etc."),
|
folder: str | None = Query(None, description="products, general, etc."),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -78,7 +77,7 @@ async def upload_media(
|
|||||||
@router.post("/upload/multiple")
|
@router.post("/upload/multiple")
|
||||||
async def upload_multiple_media(
|
async def upload_multiple_media(
|
||||||
files: list[UploadFile] = File(...),
|
files: list[UploadFile] = File(...),
|
||||||
folder: Optional[str] = Query(None),
|
folder: str | None = Query(None),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -24,7 +23,7 @@ logger = logging.getLogger(__name__)
|
|||||||
def get_notifications(
|
def get_notifications(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(50, ge=1, le=100),
|
limit: int = Query(50, ge=1, le=100),
|
||||||
unread_only: Optional[bool] = Query(False),
|
unread_only: bool | None = Query(False),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import get_current_vendor_api
|
from app.api.deps import get_current_vendor_api
|
||||||
@@ -14,9 +13,13 @@ from app.core.database import get_db
|
|||||||
from app.services.order_service import order_service
|
from app.services.order_service import order_service
|
||||||
from middleware.vendor_context import require_vendor_context
|
from middleware.vendor_context import require_vendor_context
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor, VendorUser
|
from models.database.vendor import Vendor
|
||||||
from models.schema.order import (OrderDetailResponse, OrderListResponse,
|
from models.schema.order import (
|
||||||
OrderResponse, OrderUpdate)
|
OrderDetailResponse,
|
||||||
|
OrderListResponse,
|
||||||
|
OrderResponse,
|
||||||
|
OrderUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/orders")
|
router = APIRouter(prefix="/orders")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -26,8 +29,8 @@ logger = logging.getLogger(__name__)
|
|||||||
def get_vendor_orders(
|
def get_vendor_orders(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
status: Optional[str] = Query(None, description="Filter by order status"),
|
status: str | None = Query(None, description="Filter by order status"),
|
||||||
customer_id: Optional[int] = Query(None, description="Filter by customer"),
|
customer_id: int | None = Query(None, description="Filter by customer"),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -15,9 +14,13 @@ from app.services.product_service import product_service
|
|||||||
from middleware.vendor_context import require_vendor_context
|
from middleware.vendor_context import require_vendor_context
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.product import (ProductCreate, ProductDetailResponse,
|
from models.schema.product import (
|
||||||
ProductListResponse, ProductResponse,
|
ProductCreate,
|
||||||
ProductUpdate)
|
ProductDetailResponse,
|
||||||
|
ProductListResponse,
|
||||||
|
ProductResponse,
|
||||||
|
ProductUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/products")
|
router = APIRouter(prefix="/products")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -27,8 +30,8 @@ logger = logging.getLogger(__name__)
|
|||||||
def get_vendor_products(
|
def get_vendor_products(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
is_active: Optional[bool] = Query(None),
|
is_active: bool | None = Query(None),
|
||||||
is_featured: Optional[bool] = Query(None),
|
is_featured: bool | None = Query(None),
|
||||||
vendor: Vendor = Depends(require_vendor_context()),
|
vendor: Vendor = Depends(require_vendor_context()),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
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
|
import logging
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import (get_current_vendor_api, get_user_permissions,
|
from app.api.deps import (
|
||||||
require_vendor_owner, require_vendor_permission)
|
get_current_vendor_api,
|
||||||
|
get_user_permissions,
|
||||||
|
require_vendor_owner,
|
||||||
|
require_vendor_permission,
|
||||||
|
)
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.core.permissions import VendorPermissions
|
from app.core.permissions import VendorPermissions
|
||||||
from app.services.vendor_team_service import vendor_team_service
|
from app.services.vendor_team_service import vendor_team_service
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
from models.schema.team import (
|
||||||
from models.schema.team import (BulkRemoveRequest, BulkRemoveResponse,
|
BulkRemoveRequest,
|
||||||
InvitationAccept, InvitationAcceptResponse,
|
BulkRemoveResponse,
|
||||||
InvitationResponse, RoleListResponse,
|
InvitationAccept,
|
||||||
RoleResponse, TeamMemberInvite,
|
InvitationAcceptResponse,
|
||||||
TeamMemberListResponse, TeamMemberResponse,
|
InvitationResponse,
|
||||||
TeamMemberUpdate, TeamStatistics,
|
RoleListResponse,
|
||||||
UserPermissionsResponse)
|
TeamMemberInvite,
|
||||||
|
TeamMemberListResponse,
|
||||||
|
TeamMemberResponse,
|
||||||
|
TeamMemberUpdate,
|
||||||
|
TeamStatistics,
|
||||||
|
UserPermissionsResponse,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/team")
|
router = APIRouter(prefix="/team")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -382,7 +391,7 @@ def list_roles(
|
|||||||
@router.get("/me/permissions", response_model=UserPermissionsResponse)
|
@router.get("/me/permissions", response_model=UserPermissionsResponse)
|
||||||
def get_my_permissions(
|
def get_my_permissions(
|
||||||
request: Request,
|
request: Request,
|
||||||
permissions: List[str] = Depends(get_user_permissions),
|
permissions: list[str] = Depends(get_user_permissions),
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ Note: Environment detection is handled by app.core.environment module.
|
|||||||
This module focuses purely on configuration storage and validation.
|
This module focuses purely on configuration storage and validation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ class Settings(BaseSettings):
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# MIDDLEWARE & SECURITY
|
# MIDDLEWARE & SECURITY
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
allowed_hosts: List[str] = ["*"] # Configure for production
|
allowed_hosts: list[str] = ["*"] # Configure for production
|
||||||
|
|
||||||
# Rate Limiting
|
# Rate Limiting
|
||||||
rate_limit_enabled: bool = True
|
rate_limit_enabled: bool = True
|
||||||
@@ -93,7 +92,7 @@ class Settings(BaseSettings):
|
|||||||
# LOGGING
|
# LOGGING
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
log_file: Optional[str] = None
|
log_file: str | None = None
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# PLATFORM DOMAIN CONFIGURATION
|
# PLATFORM DOMAIN CONFIGURATION
|
||||||
@@ -138,9 +137,13 @@ settings = Settings()
|
|||||||
# ENVIRONMENT UTILITIES - Module-level functions
|
# ENVIRONMENT UTILITIES - Module-level functions
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Import environment detection utilities
|
# Import environment detection utilities
|
||||||
from app.core.environment import (get_environment, is_development,
|
from app.core.environment import (
|
||||||
is_production, is_staging,
|
get_environment,
|
||||||
should_use_secure_cookies)
|
is_development,
|
||||||
|
is_production,
|
||||||
|
is_staging,
|
||||||
|
should_use_secure_cookies,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_current_environment() -> str:
|
def get_current_environment() -> str:
|
||||||
@@ -188,7 +191,7 @@ def is_staging_environment() -> bool:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
def validate_production_settings() -> List[str]:
|
def validate_production_settings() -> list[str]:
|
||||||
"""
|
"""
|
||||||
Validate settings for production environment.
|
Validate settings for production environment.
|
||||||
|
|
||||||
|
|||||||
@@ -31,18 +31,18 @@ def get_environment() -> EnvironmentType:
|
|||||||
env = os.getenv("ENV", "").lower()
|
env = os.getenv("ENV", "").lower()
|
||||||
if env in ["development", "dev", "local"]:
|
if env in ["development", "dev", "local"]:
|
||||||
return "development"
|
return "development"
|
||||||
elif env in ["staging", "stage"]:
|
if env in ["staging", "stage"]:
|
||||||
return "staging"
|
return "staging"
|
||||||
elif env in ["production", "prod"]:
|
if env in ["production", "prod"]:
|
||||||
return "production"
|
return "production"
|
||||||
|
|
||||||
# Priority 2: ENVIRONMENT variable
|
# Priority 2: ENVIRONMENT variable
|
||||||
env = os.getenv("ENVIRONMENT", "").lower()
|
env = os.getenv("ENVIRONMENT", "").lower()
|
||||||
if env in ["development", "dev", "local"]:
|
if env in ["development", "dev", "local"]:
|
||||||
return "development"
|
return "development"
|
||||||
elif env in ["staging", "stage"]:
|
if env in ["staging", "stage"]:
|
||||||
return "staging"
|
return "staging"
|
||||||
elif env in ["production", "prod"]:
|
if env in ["production", "prod"]:
|
||||||
return "production"
|
return "production"
|
||||||
|
|
||||||
# Priority 3: Auto-detect from common indicators
|
# Priority 3: Auto-detect from common indicators
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from sqlalchemy import text
|
|||||||
|
|
||||||
from middleware.auth import AuthManager
|
from middleware.auth import AuthManager
|
||||||
|
|
||||||
from .database import SessionLocal, engine
|
from .database import engine
|
||||||
from .logging import setup_logging
|
from .logging import setup_logging
|
||||||
|
|
||||||
# Remove this import if not needed: from models.database.base import Base
|
# Remove this import if not needed: from models.database.base import Base
|
||||||
@@ -60,7 +60,6 @@ def check_database_ready():
|
|||||||
def get_migration_status():
|
def get_migration_status():
|
||||||
"""Get current Alembic migration status."""
|
"""Get current Alembic migration status."""
|
||||||
try:
|
try:
|
||||||
from alembic import command
|
|
||||||
from alembic.config import Config
|
from alembic.config import Config
|
||||||
|
|
||||||
alembic_cfg = Config("alembic.ini")
|
alembic_cfg = Config("alembic.ini")
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ This module defines:
|
|||||||
- Permission groups (for easier role creation)
|
- Permission groups (for easier role creation)
|
||||||
- Permission checking utilities
|
- Permission checking utilities
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Set
|
|
||||||
|
|
||||||
|
|
||||||
class VendorPermissions(str, Enum):
|
class VendorPermissions(str, Enum):
|
||||||
@@ -78,10 +78,10 @@ class PermissionGroups:
|
|||||||
"""Pre-defined permission groups for common roles."""
|
"""Pre-defined permission groups for common roles."""
|
||||||
|
|
||||||
# Full access (for owners)
|
# Full access (for owners)
|
||||||
OWNER: Set[str] = set(p.value for p in VendorPermissions)
|
OWNER: set[str] = set(p.value for p in VendorPermissions)
|
||||||
|
|
||||||
# Manager - Can do most things except team management and critical settings
|
# Manager - Can do most things except team management and critical settings
|
||||||
MANAGER: Set[str] = {
|
MANAGER: set[str] = {
|
||||||
VendorPermissions.DASHBOARD_VIEW.value,
|
VendorPermissions.DASHBOARD_VIEW.value,
|
||||||
VendorPermissions.PRODUCTS_VIEW.value,
|
VendorPermissions.PRODUCTS_VIEW.value,
|
||||||
VendorPermissions.PRODUCTS_CREATE.value,
|
VendorPermissions.PRODUCTS_CREATE.value,
|
||||||
@@ -113,7 +113,7 @@ class PermissionGroups:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Staff - Can view and edit products/orders but limited access
|
# Staff - Can view and edit products/orders but limited access
|
||||||
STAFF: Set[str] = {
|
STAFF: set[str] = {
|
||||||
VendorPermissions.DASHBOARD_VIEW.value,
|
VendorPermissions.DASHBOARD_VIEW.value,
|
||||||
VendorPermissions.PRODUCTS_VIEW.value,
|
VendorPermissions.PRODUCTS_VIEW.value,
|
||||||
VendorPermissions.PRODUCTS_CREATE.value,
|
VendorPermissions.PRODUCTS_CREATE.value,
|
||||||
@@ -127,7 +127,7 @@ class PermissionGroups:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Support - Can view and assist with orders/customers
|
# Support - Can view and assist with orders/customers
|
||||||
SUPPORT: Set[str] = {
|
SUPPORT: set[str] = {
|
||||||
VendorPermissions.DASHBOARD_VIEW.value,
|
VendorPermissions.DASHBOARD_VIEW.value,
|
||||||
VendorPermissions.PRODUCTS_VIEW.value,
|
VendorPermissions.PRODUCTS_VIEW.value,
|
||||||
VendorPermissions.ORDERS_VIEW.value,
|
VendorPermissions.ORDERS_VIEW.value,
|
||||||
@@ -137,7 +137,7 @@ class PermissionGroups:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Viewer - Read-only access
|
# Viewer - Read-only access
|
||||||
VIEWER: Set[str] = {
|
VIEWER: set[str] = {
|
||||||
VendorPermissions.DASHBOARD_VIEW.value,
|
VendorPermissions.DASHBOARD_VIEW.value,
|
||||||
VendorPermissions.PRODUCTS_VIEW.value,
|
VendorPermissions.PRODUCTS_VIEW.value,
|
||||||
VendorPermissions.STOCK_VIEW.value,
|
VendorPermissions.STOCK_VIEW.value,
|
||||||
@@ -147,7 +147,7 @@ class PermissionGroups:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Marketing - Focused on marketing and customer communication
|
# Marketing - Focused on marketing and customer communication
|
||||||
MARKETING: Set[str] = {
|
MARKETING: set[str] = {
|
||||||
VendorPermissions.DASHBOARD_VIEW.value,
|
VendorPermissions.DASHBOARD_VIEW.value,
|
||||||
VendorPermissions.CUSTOMERS_VIEW.value,
|
VendorPermissions.CUSTOMERS_VIEW.value,
|
||||||
VendorPermissions.CUSTOMERS_EXPORT.value,
|
VendorPermissions.CUSTOMERS_EXPORT.value,
|
||||||
@@ -162,34 +162,34 @@ class PermissionChecker:
|
|||||||
"""Utility class for permission checking."""
|
"""Utility class for permission checking."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_permission(permissions: List[str], required_permission: str) -> bool:
|
def has_permission(permissions: list[str], required_permission: str) -> bool:
|
||||||
"""Check if a permission list contains a required permission."""
|
"""Check if a permission list contains a required permission."""
|
||||||
return required_permission in permissions
|
return required_permission in permissions
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_any_permission(
|
def has_any_permission(
|
||||||
permissions: List[str], required_permissions: List[str]
|
permissions: list[str], required_permissions: list[str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check if a permission list contains ANY of the required permissions."""
|
"""Check if a permission list contains ANY of the required permissions."""
|
||||||
return any(perm in permissions for perm in required_permissions)
|
return any(perm in permissions for perm in required_permissions)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_all_permissions(
|
def has_all_permissions(
|
||||||
permissions: List[str], required_permissions: List[str]
|
permissions: list[str], required_permissions: list[str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check if a permission list contains ALL of the required permissions."""
|
"""Check if a permission list contains ALL of the required permissions."""
|
||||||
return all(perm in permissions for perm in required_permissions)
|
return all(perm in permissions for perm in required_permissions)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_missing_permissions(
|
def get_missing_permissions(
|
||||||
permissions: List[str], required_permissions: List[str]
|
permissions: list[str], required_permissions: list[str]
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
"""Get list of missing permissions."""
|
"""Get list of missing permissions."""
|
||||||
return [perm for perm in required_permissions if perm not in permissions]
|
return [perm for perm in required_permissions if perm not in permissions]
|
||||||
|
|
||||||
|
|
||||||
# Helper function to get permissions for a role preset
|
# Helper function to get permissions for a role preset
|
||||||
def get_preset_permissions(preset_name: str) -> Set[str]:
|
def get_preset_permissions(preset_name: str) -> set[str]:
|
||||||
"""
|
"""
|
||||||
Get permissions for a preset role.
|
Get permissions for a preset role.
|
||||||
|
|
||||||
|
|||||||
@@ -7,108 +7,178 @@ messages, and HTTP status mappings.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Admin exceptions
|
# Admin exceptions
|
||||||
from .admin import (AdminOperationException, BulkOperationException,
|
from .admin import (
|
||||||
CannotModifyAdminException, CannotModifySelfException,
|
AdminOperationException,
|
||||||
ConfirmationRequiredException, InvalidAdminActionException,
|
BulkOperationException,
|
||||||
UserNotFoundException, UserStatusChangeException,
|
CannotModifyAdminException,
|
||||||
VendorVerificationException)
|
CannotModifySelfException,
|
||||||
|
ConfirmationRequiredException,
|
||||||
|
InvalidAdminActionException,
|
||||||
|
UserNotFoundException,
|
||||||
|
UserStatusChangeException,
|
||||||
|
VendorVerificationException,
|
||||||
|
)
|
||||||
|
|
||||||
# Authentication exceptions
|
# Authentication exceptions
|
||||||
from .auth import (AdminRequiredException, InsufficientPermissionsException,
|
from .auth import (
|
||||||
InvalidCredentialsException, InvalidTokenException,
|
AdminRequiredException,
|
||||||
TokenExpiredException, UserAlreadyExistsException,
|
InsufficientPermissionsException,
|
||||||
UserNotActiveException)
|
InvalidCredentialsException,
|
||||||
|
InvalidTokenException,
|
||||||
|
TokenExpiredException,
|
||||||
|
UserAlreadyExistsException,
|
||||||
|
UserNotActiveException,
|
||||||
|
)
|
||||||
|
|
||||||
# Base exceptions
|
# Base exceptions
|
||||||
from .base import (AuthenticationException, AuthorizationException,
|
from .base import (
|
||||||
BusinessLogicException, ConflictException,
|
AuthenticationException,
|
||||||
ExternalServiceException, RateLimitException,
|
AuthorizationException,
|
||||||
ResourceNotFoundException, ServiceUnavailableException,
|
BusinessLogicException,
|
||||||
ValidationException, WizamartException)
|
ConflictException,
|
||||||
|
ExternalServiceException,
|
||||||
|
RateLimitException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ServiceUnavailableException,
|
||||||
|
ValidationException,
|
||||||
|
WizamartException,
|
||||||
|
)
|
||||||
|
|
||||||
# Cart exceptions
|
# Cart exceptions
|
||||||
from .cart import (CartItemNotFoundException, CartValidationException,
|
from .cart import (
|
||||||
EmptyCartException, InsufficientInventoryForCartException,
|
CartItemNotFoundException,
|
||||||
InvalidCartQuantityException,
|
CartValidationException,
|
||||||
ProductNotAvailableForCartException)
|
EmptyCartException,
|
||||||
|
InsufficientInventoryForCartException,
|
||||||
|
InvalidCartQuantityException,
|
||||||
|
ProductNotAvailableForCartException,
|
||||||
|
)
|
||||||
|
|
||||||
# Customer exceptions
|
# Customer exceptions
|
||||||
from .customer import (CustomerAlreadyExistsException,
|
from .customer import (
|
||||||
CustomerAuthorizationException,
|
CustomerAlreadyExistsException,
|
||||||
CustomerNotActiveException, CustomerNotFoundException,
|
CustomerAuthorizationException,
|
||||||
CustomerValidationException,
|
CustomerNotActiveException,
|
||||||
DuplicateCustomerEmailException,
|
CustomerNotFoundException,
|
||||||
InvalidCustomerCredentialsException)
|
CustomerValidationException,
|
||||||
|
DuplicateCustomerEmailException,
|
||||||
|
InvalidCustomerCredentialsException,
|
||||||
|
)
|
||||||
|
|
||||||
# Inventory exceptions
|
# Inventory exceptions
|
||||||
from .inventory import (InsufficientInventoryException,
|
from .inventory import (
|
||||||
InvalidInventoryOperationException,
|
InsufficientInventoryException,
|
||||||
InvalidQuantityException, InventoryNotFoundException,
|
InvalidInventoryOperationException,
|
||||||
InventoryValidationException,
|
InvalidQuantityException,
|
||||||
LocationNotFoundException, NegativeInventoryException)
|
InventoryNotFoundException,
|
||||||
|
InventoryValidationException,
|
||||||
|
LocationNotFoundException,
|
||||||
|
NegativeInventoryException,
|
||||||
|
)
|
||||||
|
|
||||||
# Marketplace import job exceptions
|
# Marketplace import job exceptions
|
||||||
from .marketplace_import_job import (ImportJobAlreadyProcessingException,
|
from .marketplace_import_job import (
|
||||||
ImportJobCannotBeCancelledException,
|
ImportJobAlreadyProcessingException,
|
||||||
ImportJobCannotBeDeletedException,
|
ImportJobCannotBeCancelledException,
|
||||||
ImportJobNotFoundException,
|
ImportJobCannotBeDeletedException,
|
||||||
ImportJobNotOwnedException,
|
ImportJobNotFoundException,
|
||||||
ImportRateLimitException,
|
ImportJobNotOwnedException,
|
||||||
InvalidImportDataException,
|
ImportRateLimitException,
|
||||||
InvalidMarketplaceException,
|
InvalidImportDataException,
|
||||||
MarketplaceConnectionException,
|
InvalidMarketplaceException,
|
||||||
MarketplaceDataParsingException,
|
MarketplaceConnectionException,
|
||||||
MarketplaceImportException)
|
MarketplaceDataParsingException,
|
||||||
|
MarketplaceImportException,
|
||||||
|
)
|
||||||
|
|
||||||
# Marketplace product exceptions
|
# Marketplace product exceptions
|
||||||
from .marketplace_product import (InvalidGTINException,
|
from .marketplace_product import (
|
||||||
InvalidMarketplaceProductDataException,
|
InvalidGTINException,
|
||||||
MarketplaceProductAlreadyExistsException,
|
InvalidMarketplaceProductDataException,
|
||||||
MarketplaceProductCSVImportException,
|
MarketplaceProductAlreadyExistsException,
|
||||||
MarketplaceProductNotFoundException,
|
MarketplaceProductCSVImportException,
|
||||||
MarketplaceProductValidationException)
|
MarketplaceProductNotFoundException,
|
||||||
|
MarketplaceProductValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
# Order exceptions
|
# Order exceptions
|
||||||
from .order import (InvalidOrderStatusException, OrderAlreadyExistsException,
|
from .order import (
|
||||||
OrderCannotBeCancelledException, OrderNotFoundException,
|
InvalidOrderStatusException,
|
||||||
OrderValidationException)
|
OrderAlreadyExistsException,
|
||||||
|
OrderCannotBeCancelledException,
|
||||||
|
OrderNotFoundException,
|
||||||
|
OrderValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
# Product exceptions
|
# Product exceptions
|
||||||
from .product import (CannotDeleteProductWithInventoryException,
|
from .product import (
|
||||||
CannotDeleteProductWithOrdersException,
|
CannotDeleteProductWithInventoryException,
|
||||||
InvalidProductDataException,
|
CannotDeleteProductWithOrdersException,
|
||||||
ProductAlreadyExistsException, ProductNotActiveException,
|
InvalidProductDataException,
|
||||||
ProductNotFoundException, ProductNotInCatalogException,
|
ProductAlreadyExistsException,
|
||||||
ProductValidationException)
|
ProductNotActiveException,
|
||||||
|
ProductNotFoundException,
|
||||||
|
ProductNotInCatalogException,
|
||||||
|
ProductValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
# Team exceptions
|
# Team exceptions
|
||||||
from .team import (CannotModifyOwnRoleException, CannotRemoveOwnerException,
|
from .team import (
|
||||||
InsufficientTeamPermissionsException,
|
CannotModifyOwnRoleException,
|
||||||
InvalidInvitationDataException,
|
CannotRemoveOwnerException,
|
||||||
InvalidInvitationTokenException, InvalidRoleException,
|
InsufficientTeamPermissionsException,
|
||||||
MaxTeamMembersReachedException, RoleNotFoundException,
|
InvalidInvitationDataException,
|
||||||
TeamInvitationAlreadyAcceptedException,
|
InvalidInvitationTokenException,
|
||||||
TeamInvitationExpiredException,
|
InvalidRoleException,
|
||||||
TeamInvitationNotFoundException,
|
MaxTeamMembersReachedException,
|
||||||
TeamMemberAlreadyExistsException,
|
RoleNotFoundException,
|
||||||
TeamMemberNotFoundException, TeamValidationException,
|
TeamInvitationAlreadyAcceptedException,
|
||||||
UnauthorizedTeamActionException)
|
TeamInvitationExpiredException,
|
||||||
|
TeamInvitationNotFoundException,
|
||||||
|
TeamMemberAlreadyExistsException,
|
||||||
|
TeamMemberNotFoundException,
|
||||||
|
TeamValidationException,
|
||||||
|
UnauthorizedTeamActionException,
|
||||||
|
)
|
||||||
|
|
||||||
# Vendor exceptions
|
# Vendor exceptions
|
||||||
from .vendor import (InvalidVendorDataException, MaxVendorsReachedException,
|
from .vendor import (
|
||||||
UnauthorizedVendorAccessException,
|
InvalidVendorDataException,
|
||||||
VendorAlreadyExistsException, VendorNotActiveException,
|
MaxVendorsReachedException,
|
||||||
VendorNotFoundException, VendorNotVerifiedException,
|
UnauthorizedVendorAccessException,
|
||||||
VendorValidationException)
|
VendorAlreadyExistsException,
|
||||||
|
VendorNotActiveException,
|
||||||
|
VendorNotFoundException,
|
||||||
|
VendorNotVerifiedException,
|
||||||
|
VendorValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
# Vendor domain exceptions
|
# Vendor domain exceptions
|
||||||
from .vendor_domain import (DNSVerificationException,
|
from .vendor_domain import (
|
||||||
DomainAlreadyVerifiedException,
|
DNSVerificationException,
|
||||||
DomainNotVerifiedException,
|
DomainAlreadyVerifiedException,
|
||||||
DomainVerificationFailedException,
|
DomainNotVerifiedException,
|
||||||
InvalidDomainFormatException,
|
DomainVerificationFailedException,
|
||||||
MaxDomainsReachedException,
|
InvalidDomainFormatException,
|
||||||
MultiplePrimaryDomainsException,
|
MaxDomainsReachedException,
|
||||||
ReservedDomainException,
|
MultiplePrimaryDomainsException,
|
||||||
UnauthorizedDomainAccessException,
|
ReservedDomainException,
|
||||||
VendorDomainAlreadyExistsException,
|
UnauthorizedDomainAccessException,
|
||||||
VendorDomainNotFoundException)
|
VendorDomainAlreadyExistsException,
|
||||||
|
VendorDomainNotFoundException,
|
||||||
|
)
|
||||||
|
|
||||||
# Vendor theme exceptions
|
# Vendor theme exceptions
|
||||||
from .vendor_theme import (InvalidColorFormatException,
|
from .vendor_theme import (
|
||||||
InvalidFontFamilyException,
|
InvalidColorFormatException,
|
||||||
InvalidThemeDataException, ThemeOperationException,
|
InvalidFontFamilyException,
|
||||||
ThemePresetAlreadyAppliedException,
|
InvalidThemeDataException,
|
||||||
ThemePresetNotFoundException,
|
ThemeOperationException,
|
||||||
ThemeValidationException,
|
ThemePresetAlreadyAppliedException,
|
||||||
VendorThemeNotFoundException)
|
ThemePresetNotFoundException,
|
||||||
|
ThemeValidationException,
|
||||||
|
VendorThemeNotFoundException,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Base exceptions
|
# Base exceptions
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
Admin operations specific exceptions.
|
Admin operations specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (AuthorizationException, BusinessLogicException,
|
from .base import (
|
||||||
ResourceNotFoundException, ValidationException)
|
AuthorizationException,
|
||||||
|
BusinessLogicException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserNotFoundException(ResourceNotFoundException):
|
class UserNotFoundException(ResourceNotFoundException):
|
||||||
@@ -36,7 +40,7 @@ class UserStatusChangeException(BusinessLogicException):
|
|||||||
user_id: int,
|
user_id: int,
|
||||||
current_status: str,
|
current_status: str,
|
||||||
attempted_action: str,
|
attempted_action: str,
|
||||||
reason: Optional[str] = None,
|
reason: str | None = None,
|
||||||
):
|
):
|
||||||
message = f"Cannot {attempted_action} user {user_id} (current status: {current_status})"
|
message = f"Cannot {attempted_action} user {user_id} (current status: {current_status})"
|
||||||
if reason:
|
if reason:
|
||||||
@@ -61,7 +65,7 @@ class ShopVerificationException(BusinessLogicException):
|
|||||||
self,
|
self,
|
||||||
shop_id: int,
|
shop_id: int,
|
||||||
reason: str,
|
reason: str,
|
||||||
current_verification_status: Optional[bool] = None,
|
current_verification_status: bool | None = None,
|
||||||
):
|
):
|
||||||
details = {
|
details = {
|
||||||
"shop_id": shop_id,
|
"shop_id": shop_id,
|
||||||
@@ -85,8 +89,8 @@ class AdminOperationException(BusinessLogicException):
|
|||||||
self,
|
self,
|
||||||
operation: str,
|
operation: str,
|
||||||
reason: str,
|
reason: str,
|
||||||
target_type: Optional[str] = None,
|
target_type: str | None = None,
|
||||||
target_id: Optional[str] = None,
|
target_id: str | None = None,
|
||||||
):
|
):
|
||||||
message = f"Admin operation '{operation}' failed: {reason}"
|
message = f"Admin operation '{operation}' failed: {reason}"
|
||||||
|
|
||||||
@@ -142,7 +146,7 @@ class InvalidAdminActionException(ValidationException):
|
|||||||
self,
|
self,
|
||||||
action: str,
|
action: str,
|
||||||
reason: str,
|
reason: str,
|
||||||
valid_actions: Optional[list] = None,
|
valid_actions: list | None = None,
|
||||||
):
|
):
|
||||||
details = {
|
details = {
|
||||||
"action": action,
|
"action": action,
|
||||||
@@ -167,7 +171,7 @@ class BulkOperationException(BusinessLogicException):
|
|||||||
operation: str,
|
operation: str,
|
||||||
total_items: int,
|
total_items: int,
|
||||||
failed_items: int,
|
failed_items: int,
|
||||||
errors: Optional[Dict[str, Any]] = None,
|
errors: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
message = f"Bulk {operation} completed with errors: {failed_items}/{total_items} failed"
|
message = f"Bulk {operation} completed with errors: {failed_items}/{total_items} failed"
|
||||||
|
|
||||||
@@ -194,7 +198,7 @@ class ConfirmationRequiredException(BusinessLogicException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
operation: str,
|
operation: str,
|
||||||
message: Optional[str] = None,
|
message: str | None = None,
|
||||||
confirmation_param: str = "confirm",
|
confirmation_param: str = "confirm",
|
||||||
):
|
):
|
||||||
if not message:
|
if not message:
|
||||||
@@ -217,7 +221,7 @@ class VendorVerificationException(BusinessLogicException):
|
|||||||
self,
|
self,
|
||||||
vendor_id: int,
|
vendor_id: int,
|
||||||
reason: str,
|
reason: str,
|
||||||
current_verification_status: Optional[bool] = None,
|
current_verification_status: bool | None = None,
|
||||||
):
|
):
|
||||||
details = {
|
details = {
|
||||||
"vendor_id": vendor_id,
|
"vendor_id": vendor_id,
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
Authentication and authorization specific exceptions.
|
Authentication and authorization specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from .base import (AuthenticationException, AuthorizationException,
|
from .base import AuthenticationException, AuthorizationException, ConflictException
|
||||||
ConflictException)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidCredentialsException(AuthenticationException):
|
class InvalidCredentialsException(AuthenticationException):
|
||||||
@@ -45,7 +43,7 @@ class InsufficientPermissionsException(AuthorizationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Insufficient permissions for this action",
|
message: str = "Insufficient permissions for this action",
|
||||||
required_permission: Optional[str] = None,
|
required_permission: str | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if required_permission:
|
if required_permission:
|
||||||
@@ -84,7 +82,7 @@ class UserAlreadyExistsException(ConflictException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "User already exists",
|
message: str = "User already exists",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if field:
|
if field:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ This module provides classes and functions for:
|
|||||||
- Standardized error response structure
|
- Standardized error response structure
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class WizamartException(Exception):
|
class WizamartException(Exception):
|
||||||
@@ -19,7 +19,7 @@ class WizamartException(Exception):
|
|||||||
message: str,
|
message: str,
|
||||||
error_code: str,
|
error_code: str,
|
||||||
status_code: int = 400,
|
status_code: int = 400,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
self.message = message
|
self.message = message
|
||||||
self.error_code = error_code
|
self.error_code = error_code
|
||||||
@@ -27,7 +27,7 @@ class WizamartException(Exception):
|
|||||||
self.details = details or {}
|
self.details = details or {}
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
"""Convert exception to dictionary for JSON response."""
|
"""Convert exception to dictionary for JSON response."""
|
||||||
result = {
|
result = {
|
||||||
"error_code": self.error_code,
|
"error_code": self.error_code,
|
||||||
@@ -45,8 +45,8 @@ class ValidationException(WizamartException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str,
|
message: str,
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
validation_details = details or {}
|
validation_details = details or {}
|
||||||
if field:
|
if field:
|
||||||
@@ -67,7 +67,7 @@ class AuthenticationException(WizamartException):
|
|||||||
self,
|
self,
|
||||||
message: str = "Authentication failed",
|
message: str = "Authentication failed",
|
||||||
error_code: str = "AUTHENTICATION_FAILED",
|
error_code: str = "AUTHENTICATION_FAILED",
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -84,7 +84,7 @@ class AuthorizationException(WizamartException):
|
|||||||
self,
|
self,
|
||||||
message: str = "Access denied",
|
message: str = "Access denied",
|
||||||
error_code: str = "AUTHORIZATION_FAILED",
|
error_code: str = "AUTHORIZATION_FAILED",
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -101,8 +101,8 @@ class ResourceNotFoundException(WizamartException):
|
|||||||
self,
|
self,
|
||||||
resource_type: str,
|
resource_type: str,
|
||||||
identifier: str,
|
identifier: str,
|
||||||
message: Optional[str] = None,
|
message: str | None = None,
|
||||||
error_code: Optional[str] = None,
|
error_code: str | None = None,
|
||||||
):
|
):
|
||||||
if not message:
|
if not message:
|
||||||
message = f"{resource_type} with identifier '{identifier}' not found"
|
message = f"{resource_type} with identifier '{identifier}' not found"
|
||||||
@@ -127,7 +127,7 @@ class ConflictException(WizamartException):
|
|||||||
self,
|
self,
|
||||||
message: str,
|
message: str,
|
||||||
error_code: str = "RESOURCE_CONFLICT",
|
error_code: str = "RESOURCE_CONFLICT",
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -144,7 +144,7 @@ class BusinessLogicException(WizamartException):
|
|||||||
self,
|
self,
|
||||||
message: str,
|
message: str,
|
||||||
error_code: str,
|
error_code: str,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -162,7 +162,7 @@ class ExternalServiceException(WizamartException):
|
|||||||
message: str,
|
message: str,
|
||||||
service_name: str,
|
service_name: str,
|
||||||
error_code: str = "EXTERNAL_SERVICE_ERROR",
|
error_code: str = "EXTERNAL_SERVICE_ERROR",
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
service_details = details or {}
|
service_details = details or {}
|
||||||
service_details["service_name"] = service_name
|
service_details["service_name"] = service_name
|
||||||
@@ -181,8 +181,8 @@ class RateLimitException(WizamartException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Rate limit exceeded",
|
message: str = "Rate limit exceeded",
|
||||||
retry_after: Optional[int] = None,
|
retry_after: int | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
rate_limit_details = details or {}
|
rate_limit_details = details or {}
|
||||||
if retry_after:
|
if retry_after:
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
Shopping cart specific exceptions.
|
Shopping cart specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from .base import (BusinessLogicException, ResourceNotFoundException,
|
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||||
ValidationException)
|
|
||||||
|
|
||||||
|
|
||||||
class CartItemNotFoundException(ResourceNotFoundException):
|
class CartItemNotFoundException(ResourceNotFoundException):
|
||||||
@@ -36,8 +34,8 @@ class CartValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Cart validation failed",
|
message: str = "Cart validation failed",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[dict] = None,
|
details: dict | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -75,7 +73,7 @@ class InvalidCartQuantityException(ValidationException):
|
|||||||
"""Raised when cart quantity is invalid."""
|
"""Raised when cart quantity is invalid."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, quantity: int, min_quantity: int = 1, max_quantity: Optional[int] = None
|
self, quantity: int, min_quantity: int = 1, max_quantity: int | None = None
|
||||||
):
|
):
|
||||||
if quantity < min_quantity:
|
if quantity < min_quantity:
|
||||||
message = f"Quantity must be at least {min_quantity}"
|
message = f"Quantity must be at least {min_quantity}"
|
||||||
|
|||||||
@@ -3,11 +3,15 @@
|
|||||||
Customer management specific exceptions.
|
Customer management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (AuthenticationException, BusinessLogicException,
|
from .base import (
|
||||||
ConflictException, ResourceNotFoundException,
|
AuthenticationException,
|
||||||
ValidationException)
|
BusinessLogicException,
|
||||||
|
ConflictException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomerNotFoundException(ResourceNotFoundException):
|
class CustomerNotFoundException(ResourceNotFoundException):
|
||||||
@@ -71,8 +75,8 @@ class CustomerValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Customer validation failed",
|
message: str = "Customer validation failed",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(message=message, field=field, details=details)
|
super().__init__(message=message, field=field, details=details)
|
||||||
self.error_code = "CUSTOMER_VALIDATION_FAILED"
|
self.error_code = "CUSTOMER_VALIDATION_FAILED"
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ Error Page Renderer
|
|||||||
Renders context-aware error pages using Jinja2 templates.
|
Renders context-aware error pages using Jinja2 templates.
|
||||||
Handles fallback logic and context-specific customization.
|
Handles fallback logic and context-specific customization.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
@@ -60,7 +61,7 @@ class ErrorPageRenderer:
|
|||||||
status_code: int,
|
status_code: int,
|
||||||
error_code: str,
|
error_code: str,
|
||||||
message: str,
|
message: str,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
show_debug: bool = False,
|
show_debug: bool = False,
|
||||||
) -> HTMLResponse:
|
) -> HTMLResponse:
|
||||||
"""
|
"""
|
||||||
@@ -190,9 +191,9 @@ class ErrorPageRenderer:
|
|||||||
status_code: int,
|
status_code: int,
|
||||||
error_code: str,
|
error_code: str,
|
||||||
message: str,
|
message: str,
|
||||||
details: Optional[Dict[str, Any]],
|
details: dict[str, Any] | None,
|
||||||
show_debug: bool,
|
show_debug: bool,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Prepare data dictionary for error template."""
|
"""Prepare data dictionary for error template."""
|
||||||
# Get friendly status name
|
# Get friendly status name
|
||||||
status_name = ErrorPageRenderer.STATUS_CODE_NAMES.get(
|
status_name = ErrorPageRenderer.STATUS_CODE_NAMES.get(
|
||||||
@@ -229,7 +230,7 @@ class ErrorPageRenderer:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_context_data(
|
def _get_context_data(
|
||||||
request: Request, context_type: RequestContext
|
request: Request, context_type: RequestContext
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Get context-specific data for error templates."""
|
"""Get context-specific data for error templates."""
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ This module provides classes and functions for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from fastapi import HTTPException, Request
|
from fastapi import HTTPException, Request
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
@@ -280,7 +279,7 @@ def setup_exception_handlers(app):
|
|||||||
request=request,
|
request=request,
|
||||||
status_code=404,
|
status_code=404,
|
||||||
error_code="ENDPOINT_NOT_FOUND",
|
error_code="ENDPOINT_NOT_FOUND",
|
||||||
message=f"The page you're looking for doesn't exist or has been moved.",
|
message="The page you're looking for doesn't exist or has been moved.",
|
||||||
details={"path": request.url.path},
|
details={"path": request.url.path},
|
||||||
show_debug=True,
|
show_debug=True,
|
||||||
)
|
)
|
||||||
@@ -364,10 +363,10 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
|
|||||||
if context_type == RequestContext.ADMIN:
|
if context_type == RequestContext.ADMIN:
|
||||||
logger.debug("Redirecting to /admin/login")
|
logger.debug("Redirecting to /admin/login")
|
||||||
return RedirectResponse(url="/admin/login", status_code=302)
|
return RedirectResponse(url="/admin/login", status_code=302)
|
||||||
elif context_type == RequestContext.VENDOR_DASHBOARD:
|
if context_type == RequestContext.VENDOR_DASHBOARD:
|
||||||
logger.debug("Redirecting to /vendor/login")
|
logger.debug("Redirecting to /vendor/login")
|
||||||
return RedirectResponse(url="/vendor/login", status_code=302)
|
return RedirectResponse(url="/vendor/login", status_code=302)
|
||||||
elif context_type == RequestContext.SHOP:
|
if context_type == RequestContext.SHOP:
|
||||||
# For shop context, redirect to shop login (customer login)
|
# For shop context, redirect to shop login (customer login)
|
||||||
# Calculate base_url for proper routing (supports domain, subdomain, and path-based access)
|
# Calculate base_url for proper routing (supports domain, subdomain, and path-based access)
|
||||||
vendor = getattr(request.state, "vendor", None)
|
vendor = getattr(request.state, "vendor", None)
|
||||||
@@ -390,10 +389,9 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
|
|||||||
login_url = f"{base_url}shop/account/login"
|
login_url = f"{base_url}shop/account/login"
|
||||||
logger.debug(f"Redirecting to {login_url}")
|
logger.debug(f"Redirecting to {login_url}")
|
||||||
return RedirectResponse(url=login_url, status_code=302)
|
return RedirectResponse(url=login_url, status_code=302)
|
||||||
else:
|
# Fallback to root for unknown contexts
|
||||||
# Fallback to root for unknown contexts
|
logger.debug("Unknown context, redirecting to /")
|
||||||
logger.debug("Unknown context, redirecting to /")
|
return RedirectResponse(url="/", status_code=302)
|
||||||
return RedirectResponse(url="/", status_code=302)
|
|
||||||
|
|
||||||
|
|
||||||
# Utility functions for common exception scenarios
|
# Utility functions for common exception scenarios
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
Inventory management specific exceptions.
|
Inventory management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (BusinessLogicException, ResourceNotFoundException,
|
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||||
ValidationException)
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryNotFoundException(ResourceNotFoundException):
|
class InventoryNotFoundException(ResourceNotFoundException):
|
||||||
@@ -58,8 +57,8 @@ class InvalidInventoryOperationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str,
|
message: str,
|
||||||
operation: Optional[str] = None,
|
operation: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
if not details:
|
if not details:
|
||||||
details = {}
|
details = {}
|
||||||
@@ -80,8 +79,8 @@ class InventoryValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Inventory validation failed",
|
message: str = "Inventory validation failed",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
validation_errors: Optional[Dict[str, str]] = None,
|
validation_errors: dict[str, str] | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if validation_errors:
|
if validation_errors:
|
||||||
|
|||||||
@@ -3,11 +3,15 @@
|
|||||||
Marketplace import specific exceptions.
|
Marketplace import specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (AuthorizationException, BusinessLogicException,
|
from .base import (
|
||||||
ExternalServiceException, ResourceNotFoundException,
|
AuthorizationException,
|
||||||
ValidationException)
|
BusinessLogicException,
|
||||||
|
ExternalServiceException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MarketplaceImportException(BusinessLogicException):
|
class MarketplaceImportException(BusinessLogicException):
|
||||||
@@ -17,8 +21,8 @@ class MarketplaceImportException(BusinessLogicException):
|
|||||||
self,
|
self,
|
||||||
message: str,
|
message: str,
|
||||||
error_code: str = "MARKETPLACE_IMPORT_ERROR",
|
error_code: str = "MARKETPLACE_IMPORT_ERROR",
|
||||||
marketplace: Optional[str] = None,
|
marketplace: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
if not details:
|
if not details:
|
||||||
details = {}
|
details = {}
|
||||||
@@ -48,7 +52,7 @@ class ImportJobNotFoundException(ResourceNotFoundException):
|
|||||||
class ImportJobNotOwnedException(AuthorizationException):
|
class ImportJobNotOwnedException(AuthorizationException):
|
||||||
"""Raised when user tries to access import job they don't own."""
|
"""Raised when user tries to access import job they don't own."""
|
||||||
|
|
||||||
def __init__(self, job_id: int, user_id: Optional[int] = None):
|
def __init__(self, job_id: int, user_id: int | None = None):
|
||||||
details = {"job_id": job_id}
|
details = {"job_id": job_id}
|
||||||
if user_id:
|
if user_id:
|
||||||
details["user_id"] = user_id
|
details["user_id"] = user_id
|
||||||
@@ -66,9 +70,9 @@ class InvalidImportDataException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid import data",
|
message: str = "Invalid import data",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
row_number: Optional[int] = None,
|
row_number: int | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
if not details:
|
if not details:
|
||||||
details = {}
|
details = {}
|
||||||
@@ -132,7 +136,7 @@ class MarketplaceDataParsingException(ValidationException):
|
|||||||
self,
|
self,
|
||||||
marketplace: str,
|
marketplace: str,
|
||||||
message: str = "Failed to parse marketplace data",
|
message: str = "Failed to parse marketplace data",
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
if not details:
|
if not details:
|
||||||
details = {}
|
details = {}
|
||||||
@@ -152,7 +156,7 @@ class ImportRateLimitException(BusinessLogicException):
|
|||||||
self,
|
self,
|
||||||
max_imports: int,
|
max_imports: int,
|
||||||
time_window: str,
|
time_window: str,
|
||||||
retry_after: Optional[int] = None,
|
retry_after: int | None = None,
|
||||||
):
|
):
|
||||||
details = {
|
details = {
|
||||||
"max_imports": max_imports,
|
"max_imports": max_imports,
|
||||||
@@ -172,7 +176,7 @@ class ImportRateLimitException(BusinessLogicException):
|
|||||||
class InvalidMarketplaceException(ValidationException):
|
class InvalidMarketplaceException(ValidationException):
|
||||||
"""Raised when marketplace is not supported."""
|
"""Raised when marketplace is not supported."""
|
||||||
|
|
||||||
def __init__(self, marketplace: str, supported_marketplaces: Optional[list] = None):
|
def __init__(self, marketplace: str, supported_marketplaces: list | None = None):
|
||||||
details = {"marketplace": marketplace}
|
details = {"marketplace": marketplace}
|
||||||
if supported_marketplaces:
|
if supported_marketplaces:
|
||||||
details["supported_marketplaces"] = supported_marketplaces
|
details["supported_marketplaces"] = supported_marketplaces
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
MarketplaceProduct management specific exceptions.
|
MarketplaceProduct management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (BusinessLogicException, ConflictException,
|
from .base import (
|
||||||
ResourceNotFoundException, ValidationException)
|
BusinessLogicException,
|
||||||
|
ConflictException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MarketplaceProductNotFoundException(ResourceNotFoundException):
|
class MarketplaceProductNotFoundException(ResourceNotFoundException):
|
||||||
@@ -38,8 +42,8 @@ class InvalidMarketplaceProductDataException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid product data",
|
message: str = "Invalid product data",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -55,8 +59,8 @@ class MarketplaceProductValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str,
|
message: str,
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
validation_errors: Optional[Dict[str, str]] = None,
|
validation_errors: dict[str, str] | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if validation_errors:
|
if validation_errors:
|
||||||
@@ -88,8 +92,8 @@ class MarketplaceProductCSVImportException(BusinessLogicException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "MarketplaceProduct CSV import failed",
|
message: str = "MarketplaceProduct CSV import failed",
|
||||||
row_number: Optional[int] = None,
|
row_number: int | None = None,
|
||||||
errors: Optional[Dict[str, Any]] = None,
|
errors: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if row_number:
|
if row_number:
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
Order management specific exceptions.
|
Order management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from .base import (BusinessLogicException, ResourceNotFoundException,
|
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
||||||
ValidationException)
|
|
||||||
|
|
||||||
|
|
||||||
class OrderNotFoundException(ResourceNotFoundException):
|
class OrderNotFoundException(ResourceNotFoundException):
|
||||||
@@ -35,7 +33,7 @@ class OrderAlreadyExistsException(ValidationException):
|
|||||||
class OrderValidationException(ValidationException):
|
class OrderValidationException(ValidationException):
|
||||||
"""Raised when order data validation fails."""
|
"""Raised when order data validation fails."""
|
||||||
|
|
||||||
def __init__(self, message: str, details: Optional[dict] = None):
|
def __init__(self, message: str, details: dict | None = None):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message, error_code="ORDER_VALIDATION_FAILED", details=details
|
message=message, error_code="ORDER_VALIDATION_FAILED", details=details
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,16 +3,19 @@
|
|||||||
Product (vendor catalog) specific exceptions.
|
Product (vendor catalog) specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from .base import (BusinessLogicException, ConflictException,
|
from .base import (
|
||||||
ResourceNotFoundException, ValidationException)
|
BusinessLogicException,
|
||||||
|
ConflictException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProductNotFoundException(ResourceNotFoundException):
|
class ProductNotFoundException(ResourceNotFoundException):
|
||||||
"""Raised when a product is not found in vendor catalog."""
|
"""Raised when a product is not found in vendor catalog."""
|
||||||
|
|
||||||
def __init__(self, product_id: int, vendor_id: Optional[int] = None):
|
def __init__(self, product_id: int, vendor_id: int | None = None):
|
||||||
details = {"product_id": product_id}
|
details = {"product_id": product_id}
|
||||||
if vendor_id:
|
if vendor_id:
|
||||||
details["vendor_id"] = vendor_id
|
details["vendor_id"] = vendor_id
|
||||||
@@ -79,8 +82,8 @@ class InvalidProductDataException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid product data",
|
message: str = "Invalid product data",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[dict] = None,
|
details: dict | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -96,8 +99,8 @@ class ProductValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Product validation failed",
|
message: str = "Product validation failed",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
validation_errors: Optional[dict] = None,
|
validation_errors: dict | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if validation_errors:
|
if validation_errors:
|
||||||
|
|||||||
@@ -3,17 +3,21 @@
|
|||||||
Team management specific exceptions.
|
Team management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (AuthorizationException, BusinessLogicException,
|
from .base import (
|
||||||
ConflictException, ResourceNotFoundException,
|
AuthorizationException,
|
||||||
ValidationException)
|
BusinessLogicException,
|
||||||
|
ConflictException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TeamMemberNotFoundException(ResourceNotFoundException):
|
class TeamMemberNotFoundException(ResourceNotFoundException):
|
||||||
"""Raised when a team member is not found."""
|
"""Raised when a team member is not found."""
|
||||||
|
|
||||||
def __init__(self, user_id: int, vendor_id: Optional[int] = None):
|
def __init__(self, user_id: int, vendor_id: int | None = None):
|
||||||
details = {"user_id": user_id}
|
details = {"user_id": user_id}
|
||||||
if vendor_id:
|
if vendor_id:
|
||||||
details["vendor_id"] = vendor_id
|
details["vendor_id"] = vendor_id
|
||||||
@@ -63,7 +67,7 @@ class TeamInvitationExpiredException(BusinessLogicException):
|
|||||||
|
|
||||||
def __init__(self, invitation_token: str):
|
def __init__(self, invitation_token: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=f"Team invitation has expired",
|
message="Team invitation has expired",
|
||||||
error_code="TEAM_INVITATION_EXPIRED",
|
error_code="TEAM_INVITATION_EXPIRED",
|
||||||
details={"invitation_token": invitation_token},
|
details={"invitation_token": invitation_token},
|
||||||
)
|
)
|
||||||
@@ -86,8 +90,8 @@ class UnauthorizedTeamActionException(AuthorizationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
action: str,
|
action: str,
|
||||||
user_id: Optional[int] = None,
|
user_id: int | None = None,
|
||||||
required_permission: Optional[str] = None,
|
required_permission: str | None = None,
|
||||||
):
|
):
|
||||||
details = {"action": action}
|
details = {"action": action}
|
||||||
if user_id:
|
if user_id:
|
||||||
@@ -130,7 +134,7 @@ class CannotModifyOwnRoleException(BusinessLogicException):
|
|||||||
class RoleNotFoundException(ResourceNotFoundException):
|
class RoleNotFoundException(ResourceNotFoundException):
|
||||||
"""Raised when a role is not found."""
|
"""Raised when a role is not found."""
|
||||||
|
|
||||||
def __init__(self, role_id: int, vendor_id: Optional[int] = None):
|
def __init__(self, role_id: int, vendor_id: int | None = None):
|
||||||
details = {"role_id": role_id}
|
details = {"role_id": role_id}
|
||||||
if vendor_id:
|
if vendor_id:
|
||||||
details["vendor_id"] = vendor_id
|
details["vendor_id"] = vendor_id
|
||||||
@@ -153,8 +157,8 @@ class InvalidRoleException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid role data",
|
message: str = "Invalid role data",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -170,8 +174,8 @@ class InsufficientTeamPermissionsException(AuthorizationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
required_permission: str,
|
required_permission: str,
|
||||||
user_id: Optional[int] = None,
|
user_id: int | None = None,
|
||||||
action: Optional[str] = None,
|
action: str | None = None,
|
||||||
):
|
):
|
||||||
details = {"required_permission": required_permission}
|
details = {"required_permission": required_permission}
|
||||||
if user_id:
|
if user_id:
|
||||||
@@ -208,8 +212,8 @@ class TeamValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Team operation validation failed",
|
message: str = "Team operation validation failed",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
validation_errors: Optional[Dict[str, str]] = None,
|
validation_errors: dict[str, str] | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if validation_errors:
|
if validation_errors:
|
||||||
@@ -229,8 +233,8 @@ class InvalidInvitationDataException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid invitation data",
|
message: str = "Invalid invitation data",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -255,7 +259,7 @@ class InvalidInvitationTokenException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid or expired invitation token",
|
message: str = "Invalid or expired invitation token",
|
||||||
invitation_token: Optional[str] = None,
|
invitation_token: str | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if invitation_token:
|
if invitation_token:
|
||||||
|
|||||||
@@ -3,11 +3,15 @@
|
|||||||
Vendor management specific exceptions.
|
Vendor management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (AuthorizationException, BusinessLogicException,
|
from .base import (
|
||||||
ConflictException, ResourceNotFoundException,
|
AuthorizationException,
|
||||||
ValidationException)
|
BusinessLogicException,
|
||||||
|
ConflictException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VendorNotFoundException(ResourceNotFoundException):
|
class VendorNotFoundException(ResourceNotFoundException):
|
||||||
@@ -63,7 +67,7 @@ class VendorNotVerifiedException(BusinessLogicException):
|
|||||||
class UnauthorizedVendorAccessException(AuthorizationException):
|
class UnauthorizedVendorAccessException(AuthorizationException):
|
||||||
"""Raised when user tries to access vendor they don't own."""
|
"""Raised when user tries to access vendor they don't own."""
|
||||||
|
|
||||||
def __init__(self, vendor_code: str, user_id: Optional[int] = None):
|
def __init__(self, vendor_code: str, user_id: int | None = None):
|
||||||
details = {"vendor_code": vendor_code}
|
details = {"vendor_code": vendor_code}
|
||||||
if user_id:
|
if user_id:
|
||||||
details["user_id"] = user_id
|
details["user_id"] = user_id
|
||||||
@@ -81,8 +85,8 @@ class InvalidVendorDataException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid vendor data",
|
message: str = "Invalid vendor data",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -98,8 +102,8 @@ class VendorValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Vendor validation failed",
|
message: str = "Vendor validation failed",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
validation_errors: Optional[Dict[str, str]] = None,
|
validation_errors: dict[str, str] | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if validation_errors:
|
if validation_errors:
|
||||||
@@ -134,7 +138,7 @@ class IncompleteVendorDataException(ValidationException):
|
|||||||
class MaxVendorsReachedException(BusinessLogicException):
|
class MaxVendorsReachedException(BusinessLogicException):
|
||||||
"""Raised when user tries to create more vendors than allowed."""
|
"""Raised when user tries to create more vendors than allowed."""
|
||||||
|
|
||||||
def __init__(self, max_vendors: int, user_id: Optional[int] = None):
|
def __init__(self, max_vendors: int, user_id: int | None = None):
|
||||||
details = {"max_vendors": max_vendors}
|
details = {"max_vendors": max_vendors}
|
||||||
if user_id:
|
if user_id:
|
||||||
details["user_id"] = user_id
|
details["user_id"] = user_id
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
Vendor domain management specific exceptions.
|
Vendor domain management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
|
||||||
|
|
||||||
from .base import (BusinessLogicException, ConflictException,
|
from .base import (
|
||||||
ExternalServiceException, ResourceNotFoundException,
|
BusinessLogicException,
|
||||||
ValidationException)
|
ConflictException,
|
||||||
|
ExternalServiceException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VendorDomainNotFoundException(ResourceNotFoundException):
|
class VendorDomainNotFoundException(ResourceNotFoundException):
|
||||||
@@ -30,7 +33,7 @@ class VendorDomainNotFoundException(ResourceNotFoundException):
|
|||||||
class VendorDomainAlreadyExistsException(ConflictException):
|
class VendorDomainAlreadyExistsException(ConflictException):
|
||||||
"""Raised when trying to add a domain that already exists."""
|
"""Raised when trying to add a domain that already exists."""
|
||||||
|
|
||||||
def __init__(self, domain: str, existing_vendor_id: Optional[int] = None):
|
def __init__(self, domain: str, existing_vendor_id: int | None = None):
|
||||||
details = {"domain": domain}
|
details = {"domain": domain}
|
||||||
if existing_vendor_id:
|
if existing_vendor_id:
|
||||||
details["existing_vendor_id"] = existing_vendor_id
|
details["existing_vendor_id"] = existing_vendor_id
|
||||||
@@ -104,7 +107,7 @@ class MultiplePrimaryDomainsException(BusinessLogicException):
|
|||||||
|
|
||||||
def __init__(self, vendor_id: int):
|
def __init__(self, vendor_id: int):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=f"Vendor can only have one primary domain",
|
message="Vendor can only have one primary domain",
|
||||||
error_code="MULTIPLE_PRIMARY_DOMAINS",
|
error_code="MULTIPLE_PRIMARY_DOMAINS",
|
||||||
details={"vendor_id": vendor_id},
|
details={"vendor_id": vendor_id},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
Vendor theme management specific exceptions.
|
Vendor theme management specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .base import (BusinessLogicException, ConflictException,
|
from .base import (
|
||||||
ResourceNotFoundException, ValidationException)
|
BusinessLogicException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VendorThemeNotFoundException(ResourceNotFoundException):
|
class VendorThemeNotFoundException(ResourceNotFoundException):
|
||||||
@@ -27,8 +30,8 @@ class InvalidThemeDataException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Invalid theme data",
|
message: str = "Invalid theme data",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
message=message,
|
message=message,
|
||||||
@@ -41,7 +44,7 @@ class InvalidThemeDataException(ValidationException):
|
|||||||
class ThemePresetNotFoundException(ResourceNotFoundException):
|
class ThemePresetNotFoundException(ResourceNotFoundException):
|
||||||
"""Raised when a theme preset is not found."""
|
"""Raised when a theme preset is not found."""
|
||||||
|
|
||||||
def __init__(self, preset_name: str, available_presets: Optional[list] = None):
|
def __init__(self, preset_name: str, available_presets: list | None = None):
|
||||||
details = {"preset_name": preset_name}
|
details = {"preset_name": preset_name}
|
||||||
if available_presets:
|
if available_presets:
|
||||||
details["available_presets"] = available_presets
|
details["available_presets"] = available_presets
|
||||||
@@ -61,8 +64,8 @@ class ThemeValidationException(ValidationException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
message: str = "Theme validation failed",
|
message: str = "Theme validation failed",
|
||||||
field: Optional[str] = None,
|
field: str | None = None,
|
||||||
validation_errors: Optional[Dict[str, str]] = None,
|
validation_errors: dict[str, str] | None = None,
|
||||||
):
|
):
|
||||||
details = {}
|
details = {}
|
||||||
if validation_errors:
|
if validation_errors:
|
||||||
|
|||||||
@@ -3,8 +3,17 @@ Architecture Scan Models
|
|||||||
Database models for tracking code quality scans and violations
|
Database models for tracking code quality scans and violations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, Float, ForeignKey,
|
from sqlalchemy import (
|
||||||
Integer, String, Text)
|
JSON,
|
||||||
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
|
|||||||
@@ -30,15 +30,17 @@ Routes:
|
|||||||
- GET /code-quality/violations/{violation_id} → Violation details (auth required)
|
- GET /code-quality/violations/{violation_id} → Violation details (auth required)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Path, Request
|
from fastapi import APIRouter, Depends, Path, Request
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import (get_current_admin_from_cookie_or_header,
|
from app.api.deps import (
|
||||||
get_current_admin_optional, get_db)
|
get_current_admin_from_cookie_or_header,
|
||||||
|
get_current_admin_optional,
|
||||||
|
get_db,
|
||||||
|
)
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -52,7 +54,7 @@ templates = Jinja2Templates(directory="app/templates")
|
|||||||
|
|
||||||
@router.get("/", response_class=RedirectResponse, include_in_schema=False)
|
@router.get("/", response_class=RedirectResponse, include_in_schema=False)
|
||||||
async def admin_root(
|
async def admin_root(
|
||||||
current_user: Optional[User] = Depends(get_current_admin_optional),
|
current_user: User | None = Depends(get_current_admin_optional),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Redirect /admin/ based on authentication status.
|
Redirect /admin/ based on authentication status.
|
||||||
@@ -69,7 +71,7 @@ async def admin_root(
|
|||||||
|
|
||||||
@router.get("/login", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/login", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_login_page(
|
async def admin_login_page(
|
||||||
request: Request, current_user: Optional[User] = Depends(get_current_admin_optional)
|
request: Request, current_user: User | None = Depends(get_current_admin_optional)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Render admin login page.
|
Render admin login page.
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"[SHOP_CONTEXT] Failed to load navigation pages",
|
"[SHOP_CONTEXT] Failed to load navigation pages",
|
||||||
extra={"error": str(e), "vendor_id": vendor.id if vendor else None},
|
extra={"error": str(e), "vendor_id": vendor.id if vendor else None},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
|
|||||||
context.update(extra_context)
|
context.update(extra_context)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_CONTEXT] Context built",
|
"[SHOP_CONTEXT] Context built",
|
||||||
extra={
|
extra={
|
||||||
"vendor_id": vendor.id if vendor else None,
|
"vendor_id": vendor.id if vendor else None,
|
||||||
"vendor_name": vendor.name if vendor else None,
|
"vendor_name": vendor.name if vendor else None,
|
||||||
@@ -179,7 +179,7 @@ async def shop_products_page(request: Request, db: Session = Depends(get_db)):
|
|||||||
Shows featured products and categories.
|
Shows featured products and categories.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -203,7 +203,7 @@ async def shop_product_detail_page(
|
|||||||
Shows product information, images, reviews, and buy options.
|
Shows product information, images, reviews, and buy options.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -227,7 +227,7 @@ async def shop_category_page(
|
|||||||
Shows all products in a specific category.
|
Shows all products in a specific category.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -247,7 +247,7 @@ async def shop_cart_page(request: Request):
|
|||||||
Shows cart items and allows quantity updates.
|
Shows cart items and allows quantity updates.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -265,7 +265,7 @@ async def shop_checkout_page(request: Request):
|
|||||||
Handles shipping, payment, and order confirmation.
|
Handles shipping, payment, and order confirmation.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -283,7 +283,7 @@ async def shop_search_page(request: Request):
|
|||||||
Shows products matching search query.
|
Shows products matching search query.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -306,7 +306,7 @@ async def shop_register_page(request: Request):
|
|||||||
No authentication required.
|
No authentication required.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -326,7 +326,7 @@ async def shop_login_page(request: Request):
|
|||||||
No authentication required.
|
No authentication required.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -348,7 +348,7 @@ async def shop_forgot_password_page(request: Request):
|
|||||||
Allows customers to reset their password.
|
Allows customers to reset their password.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -373,7 +373,7 @@ async def shop_account_root(request: Request):
|
|||||||
Redirect /shop/account or /shop/account/ to dashboard.
|
Redirect /shop/account or /shop/account/ to dashboard.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -414,7 +414,7 @@ async def shop_account_dashboard_page(
|
|||||||
Requires customer authentication.
|
Requires customer authentication.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -439,7 +439,7 @@ async def shop_orders_page(
|
|||||||
Requires customer authentication.
|
Requires customer authentication.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -467,7 +467,7 @@ async def shop_order_detail_page(
|
|||||||
Requires customer authentication.
|
Requires customer authentication.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -493,7 +493,7 @@ async def shop_profile_page(
|
|||||||
Requires customer authentication.
|
Requires customer authentication.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -518,7 +518,7 @@ async def shop_addresses_page(
|
|||||||
Requires customer authentication.
|
Requires customer authentication.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -543,7 +543,7 @@ async def shop_wishlist_page(
|
|||||||
Requires customer authentication.
|
Requires customer authentication.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -568,7 +568,7 @@ async def shop_settings_page(
|
|||||||
Requires customer authentication.
|
Requires customer authentication.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
"[SHOP_HANDLER] shop_products_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
"vendor": getattr(request.state, "vendor", "NOT SET"),
|
||||||
@@ -609,7 +609,7 @@ async def generic_content_page(
|
|||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SHOP_HANDLER] generic_content_page REACHED",
|
"[SHOP_HANDLER] generic_content_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"slug": slug,
|
"slug": slug,
|
||||||
@@ -628,7 +628,7 @@ async def generic_content_page(
|
|||||||
|
|
||||||
if not page:
|
if not page:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"[SHOP_HANDLER] Content page not found",
|
"[SHOP_HANDLER] Content page not found",
|
||||||
extra={
|
extra={
|
||||||
"slug": slug,
|
"slug": slug,
|
||||||
"vendor_id": vendor_id,
|
"vendor_id": vendor_id,
|
||||||
@@ -638,7 +638,7 @@ async def generic_content_page(
|
|||||||
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[SHOP_HANDLER] Content page found",
|
"[SHOP_HANDLER] Content page found",
|
||||||
extra={
|
extra={
|
||||||
"slug": slug,
|
"slug": slug,
|
||||||
"page_id": page.id,
|
"page_id": page.id,
|
||||||
@@ -709,14 +709,14 @@ async def debug_context(request: Request):
|
|||||||
<pre>{json.dumps(debug_info, indent=2)}</pre>
|
<pre>{json.dumps(debug_info, indent=2)}</pre>
|
||||||
|
|
||||||
<h2>Status</h2>
|
<h2>Status</h2>
|
||||||
<p class="{'good' if vendor else 'bad'}">
|
<p class="{"good" if vendor else "bad"}">
|
||||||
Vendor: {'✓ Found' if vendor else '✗ Not Found'}
|
Vendor: {"✓ Found" if vendor else "✗ Not Found"}
|
||||||
</p>
|
</p>
|
||||||
<p class="{'good' if theme else 'bad'}">
|
<p class="{"good" if theme else "bad"}">
|
||||||
Theme: {'✓ Found' if theme else '✗ Not Found'}
|
Theme: {"✓ Found" if theme else "✗ Not Found"}
|
||||||
</p>
|
</p>
|
||||||
<p class="{'good' if str(getattr(request.state, 'context_type', 'NOT SET')) == 'shop' else 'bad'}">
|
<p class="{"good" if str(getattr(request.state, "context_type", "NOT SET")) == "shop" else "bad"}">
|
||||||
Context Type: {str(getattr(request.state, 'context_type', 'NOT SET'))}
|
Context Type: {str(getattr(request.state, "context_type", "NOT SET"))}
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -22,15 +22,17 @@ Routes:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Path, Request
|
from fastapi import APIRouter, Depends, HTTPException, Path, Request
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import (get_current_vendor_from_cookie_or_header,
|
from app.api.deps import (
|
||||||
get_current_vendor_optional, get_db)
|
get_current_vendor_from_cookie_or_header,
|
||||||
|
get_current_vendor_optional,
|
||||||
|
get_db,
|
||||||
|
)
|
||||||
from app.services.content_page_service import content_page_service
|
from app.services.content_page_service import content_page_service
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ async def vendor_root_no_slash(vendor_code: str = Path(..., description="Vendor
|
|||||||
@router.get("/{vendor_code}/", response_class=RedirectResponse, include_in_schema=False)
|
@router.get("/{vendor_code}/", response_class=RedirectResponse, include_in_schema=False)
|
||||||
async def vendor_root(
|
async def vendor_root(
|
||||||
vendor_code: str = Path(..., description="Vendor code"),
|
vendor_code: str = Path(..., description="Vendor code"),
|
||||||
current_user: Optional[User] = Depends(get_current_vendor_optional),
|
current_user: User | None = Depends(get_current_vendor_optional),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Redirect /vendor/{code}/ based on authentication status.
|
Redirect /vendor/{code}/ based on authentication status.
|
||||||
@@ -78,7 +80,7 @@ async def vendor_root(
|
|||||||
async def vendor_login_page(
|
async def vendor_login_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
vendor_code: str = Path(..., description="Vendor code"),
|
vendor_code: str = Path(..., description="Vendor code"),
|
||||||
current_user: Optional[User] = Depends(get_current_vendor_optional),
|
current_user: User | None = Depends(get_current_vendor_optional),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Render vendor login page.
|
Render vendor login page.
|
||||||
@@ -374,7 +376,7 @@ async def vendor_content_page(
|
|||||||
shadowing other specific routes.
|
shadowing other specific routes.
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[VENDOR_HANDLER] vendor_content_page REACHED",
|
"[VENDOR_HANDLER] vendor_content_page REACHED",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"vendor_code": vendor_code,
|
"vendor_code": vendor_code,
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ This module provides functions for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from typing import Any
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import AdminOperationException
|
from app.exceptions import AdminOperationException
|
||||||
@@ -33,10 +32,10 @@ class AdminAuditService:
|
|||||||
action: str,
|
action: str,
|
||||||
target_type: str,
|
target_type: str,
|
||||||
target_id: str,
|
target_id: str,
|
||||||
details: Optional[Dict[str, Any]] = None,
|
details: dict[str, Any] | None = None,
|
||||||
ip_address: Optional[str] = None,
|
ip_address: str | None = None,
|
||||||
user_agent: Optional[str] = None,
|
user_agent: str | None = None,
|
||||||
request_id: Optional[str] = None,
|
request_id: str | None = None,
|
||||||
) -> AdminAuditLog:
|
) -> AdminAuditLog:
|
||||||
"""
|
"""
|
||||||
Log an admin action to the audit trail.
|
Log an admin action to the audit trail.
|
||||||
@@ -85,7 +84,7 @@ class AdminAuditService:
|
|||||||
|
|
||||||
def get_audit_logs(
|
def get_audit_logs(
|
||||||
self, db: Session, filters: AdminAuditLogFilters
|
self, db: Session, filters: AdminAuditLogFilters
|
||||||
) -> List[AdminAuditLogResponse]:
|
) -> list[AdminAuditLogResponse]:
|
||||||
"""
|
"""
|
||||||
Get filtered admin audit logs with pagination.
|
Get filtered admin audit logs with pagination.
|
||||||
|
|
||||||
@@ -187,14 +186,14 @@ class AdminAuditService:
|
|||||||
|
|
||||||
def get_recent_actions_by_admin(
|
def get_recent_actions_by_admin(
|
||||||
self, db: Session, admin_user_id: int, limit: int = 10
|
self, db: Session, admin_user_id: int, limit: int = 10
|
||||||
) -> List[AdminAuditLogResponse]:
|
) -> list[AdminAuditLogResponse]:
|
||||||
"""Get recent actions by a specific admin."""
|
"""Get recent actions by a specific admin."""
|
||||||
filters = AdminAuditLogFilters(admin_user_id=admin_user_id, limit=limit)
|
filters = AdminAuditLogFilters(admin_user_id=admin_user_id, limit=limit)
|
||||||
return self.get_audit_logs(db, filters)
|
return self.get_audit_logs(db, filters)
|
||||||
|
|
||||||
def get_actions_by_target(
|
def get_actions_by_target(
|
||||||
self, db: Session, target_type: str, target_id: str, limit: int = 50
|
self, db: Session, target_type: str, target_id: str, limit: int = 50
|
||||||
) -> List[AdminAuditLogResponse]:
|
) -> list[AdminAuditLogResponse]:
|
||||||
"""Get all actions performed on a specific target."""
|
"""Get all actions performed on a specific target."""
|
||||||
try:
|
try:
|
||||||
logs = (
|
logs = (
|
||||||
|
|||||||
@@ -13,17 +13,21 @@ This module provides classes and functions for:
|
|||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
from sqlalchemy import func, or_
|
from sqlalchemy import func, or_
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (AdminOperationException, CannotModifySelfException,
|
from app.exceptions import (
|
||||||
UserNotFoundException, UserStatusChangeException,
|
AdminOperationException,
|
||||||
ValidationException, VendorAlreadyExistsException,
|
CannotModifySelfException,
|
||||||
VendorNotFoundException,
|
UserNotFoundException,
|
||||||
VendorVerificationException)
|
UserStatusChangeException,
|
||||||
|
ValidationException,
|
||||||
|
VendorAlreadyExistsException,
|
||||||
|
VendorNotFoundException,
|
||||||
|
VendorVerificationException,
|
||||||
|
)
|
||||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Role, Vendor, VendorUser
|
from models.database.vendor import Role, Vendor, VendorUser
|
||||||
@@ -40,7 +44,7 @@ class AdminService:
|
|||||||
# USER MANAGEMENT
|
# USER MANAGEMENT
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> List[User]:
|
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> list[User]:
|
||||||
"""Get paginated list of all users."""
|
"""Get paginated list of all users."""
|
||||||
try:
|
try:
|
||||||
return db.query(User).offset(skip).limit(limit).all()
|
return db.query(User).offset(skip).limit(limit).all()
|
||||||
@@ -52,7 +56,7 @@ class AdminService:
|
|||||||
|
|
||||||
def toggle_user_status(
|
def toggle_user_status(
|
||||||
self, db: Session, user_id: int, current_admin_id: int
|
self, db: Session, user_id: int, current_admin_id: int
|
||||||
) -> Tuple[User, str]:
|
) -> tuple[User, str]:
|
||||||
"""Toggle user active status."""
|
"""Toggle user active status."""
|
||||||
user = self._get_user_by_id_or_raise(db, user_id)
|
user = self._get_user_by_id_or_raise(db, user_id)
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ class AdminService:
|
|||||||
try:
|
try:
|
||||||
original_status = user.is_active
|
original_status = user.is_active
|
||||||
user.is_active = not user.is_active
|
user.is_active = not user.is_active
|
||||||
user.updated_at = datetime.now(timezone.utc)
|
user.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(user)
|
db.refresh(user)
|
||||||
|
|
||||||
@@ -98,7 +102,7 @@ class AdminService:
|
|||||||
|
|
||||||
def create_vendor_with_owner(
|
def create_vendor_with_owner(
|
||||||
self, db: Session, vendor_data: VendorCreate
|
self, db: Session, vendor_data: VendorCreate
|
||||||
) -> Tuple[Vendor, User, str]:
|
) -> tuple[Vendor, User, str]:
|
||||||
"""
|
"""
|
||||||
Create vendor with owner user account.
|
Create vendor with owner user account.
|
||||||
|
|
||||||
@@ -222,10 +226,10 @@ class AdminService:
|
|||||||
db: Session,
|
db: Session,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
search: Optional[str] = None,
|
search: str | None = None,
|
||||||
is_active: Optional[bool] = None,
|
is_active: bool | None = None,
|
||||||
is_verified: Optional[bool] = None,
|
is_verified: bool | None = None,
|
||||||
) -> Tuple[List[Vendor], int]:
|
) -> tuple[list[Vendor], int]:
|
||||||
"""Get paginated list of all vendors with filtering."""
|
"""Get paginated list of all vendors with filtering."""
|
||||||
try:
|
try:
|
||||||
query = db.query(Vendor)
|
query = db.query(Vendor)
|
||||||
@@ -261,17 +265,17 @@ class AdminService:
|
|||||||
"""Get vendor by ID."""
|
"""Get vendor by ID."""
|
||||||
return self._get_vendor_by_id_or_raise(db, vendor_id)
|
return self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||||
|
|
||||||
def verify_vendor(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]:
|
def verify_vendor(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
|
||||||
"""Toggle vendor verification status."""
|
"""Toggle vendor verification status."""
|
||||||
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
original_status = vendor.is_verified
|
original_status = vendor.is_verified
|
||||||
vendor.is_verified = not vendor.is_verified
|
vendor.is_verified = not vendor.is_verified
|
||||||
vendor.updated_at = datetime.now(timezone.utc)
|
vendor.updated_at = datetime.now(UTC)
|
||||||
|
|
||||||
if vendor.is_verified:
|
if vendor.is_verified:
|
||||||
vendor.verified_at = datetime.now(timezone.utc)
|
vendor.verified_at = datetime.now(UTC)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(vendor)
|
db.refresh(vendor)
|
||||||
@@ -291,14 +295,14 @@ class AdminService:
|
|||||||
current_verification_status=original_status,
|
current_verification_status=original_status,
|
||||||
)
|
)
|
||||||
|
|
||||||
def toggle_vendor_status(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]:
|
def toggle_vendor_status(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
|
||||||
"""Toggle vendor active status."""
|
"""Toggle vendor active status."""
|
||||||
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
original_status = vendor.is_active
|
original_status = vendor.is_active
|
||||||
vendor.is_active = not vendor.is_active
|
vendor.is_active = not vendor.is_active
|
||||||
vendor.updated_at = datetime.now(timezone.utc)
|
vendor.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(vendor)
|
db.refresh(vendor)
|
||||||
|
|
||||||
@@ -347,7 +351,10 @@ class AdminService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update_vendor(
|
def update_vendor(
|
||||||
self, db: Session, vendor_id: int, vendor_update # VendorUpdate schema
|
self,
|
||||||
|
db: Session,
|
||||||
|
vendor_id: int,
|
||||||
|
vendor_update, # VendorUpdate schema
|
||||||
) -> Vendor:
|
) -> Vendor:
|
||||||
"""
|
"""
|
||||||
Update vendor information (Admin only).
|
Update vendor information (Admin only).
|
||||||
@@ -402,7 +409,7 @@ class AdminService:
|
|||||||
for field, value in update_data.items():
|
for field, value in update_data.items():
|
||||||
setattr(vendor, field, value)
|
setattr(vendor, field, value)
|
||||||
|
|
||||||
vendor.updated_at = datetime.now(timezone.utc)
|
vendor.updated_at = datetime.now(UTC)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(vendor)
|
db.refresh(vendor)
|
||||||
@@ -430,7 +437,7 @@ class AdminService:
|
|||||||
db: Session,
|
db: Session,
|
||||||
vendor_id: int,
|
vendor_id: int,
|
||||||
transfer_data, # VendorTransferOwnership schema
|
transfer_data, # VendorTransferOwnership schema
|
||||||
) -> Tuple[Vendor, User, User]:
|
) -> tuple[Vendor, User, User]:
|
||||||
"""
|
"""
|
||||||
Transfer vendor ownership to another user.
|
Transfer vendor ownership to another user.
|
||||||
|
|
||||||
@@ -556,7 +563,7 @@ class AdminService:
|
|||||||
|
|
||||||
# Update vendor owner_user_id
|
# Update vendor owner_user_id
|
||||||
vendor.owner_user_id = new_owner.id
|
vendor.owner_user_id = new_owner.id
|
||||||
vendor.updated_at = datetime.now(timezone.utc)
|
vendor.updated_at = datetime.now(UTC)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(vendor)
|
db.refresh(vendor)
|
||||||
@@ -593,12 +600,12 @@ class AdminService:
|
|||||||
def get_marketplace_import_jobs(
|
def get_marketplace_import_jobs(
|
||||||
self,
|
self,
|
||||||
db: Session,
|
db: Session,
|
||||||
marketplace: Optional[str] = None,
|
marketplace: str | None = None,
|
||||||
vendor_name: Optional[str] = None,
|
vendor_name: str | None = None,
|
||||||
status: Optional[str] = None,
|
status: str | None = None,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
) -> List[MarketplaceImportJobResponse]:
|
) -> list[MarketplaceImportJobResponse]:
|
||||||
"""Get filtered and paginated marketplace import jobs."""
|
"""Get filtered and paginated marketplace import jobs."""
|
||||||
try:
|
try:
|
||||||
query = db.query(MarketplaceImportJob)
|
query = db.query(MarketplaceImportJob)
|
||||||
@@ -633,7 +640,7 @@ class AdminService:
|
|||||||
# STATISTICS
|
# STATISTICS
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def get_recent_vendors(self, db: Session, limit: int = 5) -> List[dict]:
|
def get_recent_vendors(self, db: Session, limit: int = 5) -> list[dict]:
|
||||||
"""Get recently created vendors."""
|
"""Get recently created vendors."""
|
||||||
try:
|
try:
|
||||||
vendors = (
|
vendors = (
|
||||||
@@ -656,7 +663,7 @@ class AdminService:
|
|||||||
logger.error(f"Failed to get recent vendors: {str(e)}")
|
logger.error(f"Failed to get recent vendors: {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_recent_import_jobs(self, db: Session, limit: int = 10) -> List[dict]:
|
def get_recent_import_jobs(self, db: Session, limit: int = 10) -> list[dict]:
|
||||||
"""Get recent marketplace import jobs."""
|
"""Get recent marketplace import jobs."""
|
||||||
try:
|
try:
|
||||||
jobs = (
|
jobs = (
|
||||||
|
|||||||
@@ -10,17 +10,23 @@ This module provides functions for:
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (AdminOperationException, ResourceNotFoundException,
|
from app.exceptions import (
|
||||||
ValidationException)
|
AdminOperationException,
|
||||||
|
ResourceNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
from models.database.admin import AdminSetting
|
from models.database.admin import AdminSetting
|
||||||
from models.schema.admin import (AdminSettingCreate, AdminSettingResponse,
|
from models.schema.admin import (
|
||||||
AdminSettingUpdate)
|
AdminSettingCreate,
|
||||||
|
AdminSettingResponse,
|
||||||
|
AdminSettingUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -28,7 +34,7 @@ logger = logging.getLogger(__name__)
|
|||||||
class AdminSettingsService:
|
class AdminSettingsService:
|
||||||
"""Service for managing platform-wide settings."""
|
"""Service for managing platform-wide settings."""
|
||||||
|
|
||||||
def get_setting_by_key(self, db: Session, key: str) -> Optional[AdminSetting]:
|
def get_setting_by_key(self, db: Session, key: str) -> AdminSetting | None:
|
||||||
"""Get setting by key."""
|
"""Get setting by key."""
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
@@ -60,14 +66,13 @@ class AdminSettingsService:
|
|||||||
try:
|
try:
|
||||||
if setting.value_type == "integer":
|
if setting.value_type == "integer":
|
||||||
return int(setting.value)
|
return int(setting.value)
|
||||||
elif setting.value_type == "float":
|
if setting.value_type == "float":
|
||||||
return float(setting.value)
|
return float(setting.value)
|
||||||
elif setting.value_type == "boolean":
|
if setting.value_type == "boolean":
|
||||||
return setting.value.lower() in ("true", "1", "yes")
|
return setting.value.lower() in ("true", "1", "yes")
|
||||||
elif setting.value_type == "json":
|
if setting.value_type == "json":
|
||||||
return json.loads(setting.value)
|
return json.loads(setting.value)
|
||||||
else:
|
return setting.value
|
||||||
return setting.value
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to convert setting {key} value: {str(e)}")
|
logger.error(f"Failed to convert setting {key} value: {str(e)}")
|
||||||
return default
|
return default
|
||||||
@@ -75,9 +80,9 @@ class AdminSettingsService:
|
|||||||
def get_all_settings(
|
def get_all_settings(
|
||||||
self,
|
self,
|
||||||
db: Session,
|
db: Session,
|
||||||
category: Optional[str] = None,
|
category: str | None = None,
|
||||||
is_public: Optional[bool] = None,
|
is_public: bool | None = None,
|
||||||
) -> List[AdminSettingResponse]:
|
) -> list[AdminSettingResponse]:
|
||||||
"""Get all settings with optional filtering."""
|
"""Get all settings with optional filtering."""
|
||||||
try:
|
try:
|
||||||
query = db.query(AdminSetting)
|
query = db.query(AdminSetting)
|
||||||
@@ -100,7 +105,7 @@ class AdminSettingsService:
|
|||||||
operation="get_all_settings", reason="Database query failed"
|
operation="get_all_settings", reason="Database query failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_settings_by_category(self, db: Session, category: str) -> Dict[str, Any]:
|
def get_settings_by_category(self, db: Session, category: str) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get all settings in a category as a dictionary.
|
Get all settings in a category as a dictionary.
|
||||||
|
|
||||||
@@ -198,7 +203,7 @@ class AdminSettingsService:
|
|||||||
if update_data.description is not None:
|
if update_data.description is not None:
|
||||||
setting.description = update_data.description
|
setting.description = update_data.description
|
||||||
setting.last_modified_by_user_id = admin_user_id
|
setting.last_modified_by_user_id = admin_user_id
|
||||||
setting.updated_at = datetime.now(timezone.utc)
|
setting.updated_at = datetime.now(UTC)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(setting)
|
db.refresh(setting)
|
||||||
@@ -228,8 +233,7 @@ class AdminSettingsService:
|
|||||||
value=setting_data.value, description=setting_data.description
|
value=setting_data.value, description=setting_data.description
|
||||||
)
|
)
|
||||||
return self.update_setting(db, setting_data.key, update_data, admin_user_id)
|
return self.update_setting(db, setting_data.key, update_data, admin_user_id)
|
||||||
else:
|
return self.create_setting(db, setting_data, admin_user_id)
|
||||||
return self.create_setting(db, setting_data, admin_user_id)
|
|
||||||
|
|
||||||
def delete_setting(self, db: Session, key: str, admin_user_id: int) -> str:
|
def delete_setting(self, db: Session, key: str, admin_user_id: int) -> str:
|
||||||
"""Delete setting."""
|
"""Delete setting."""
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ This module provides classes and functions for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Optional
|
from datetime import UTC
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (InvalidCredentialsException,
|
from app.exceptions import (
|
||||||
UserAlreadyExistsException, UserNotActiveException,
|
InvalidCredentialsException,
|
||||||
ValidationException)
|
UserAlreadyExistsException,
|
||||||
|
UserNotActiveException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
from middleware.auth import AuthManager
|
from middleware.auth import AuthManager
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.schema.auth import UserLogin, UserRegister
|
from models.schema.auth import UserLogin, UserRegister
|
||||||
@@ -82,7 +86,7 @@ class AuthService:
|
|||||||
logger.error(f"Error registering user: {str(e)}")
|
logger.error(f"Error registering user: {str(e)}")
|
||||||
raise ValidationException("Registration failed")
|
raise ValidationException("Registration failed")
|
||||||
|
|
||||||
def login_user(self, db: Session, user_credentials: UserLogin) -> Dict[str, Any]:
|
def login_user(self, db: Session, user_credentials: UserLogin) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Login user and return JWT token with user data.
|
Login user and return JWT token with user data.
|
||||||
|
|
||||||
@@ -120,7 +124,7 @@ class AuthService:
|
|||||||
logger.error(f"Error during login: {str(e)}")
|
logger.error(f"Error during login: {str(e)}")
|
||||||
raise InvalidCredentialsException()
|
raise InvalidCredentialsException()
|
||||||
|
|
||||||
def get_user_by_email(self, db: Session, email: str) -> Optional[User]:
|
def get_user_by_email(self, db: Session, email: str) -> User | None:
|
||||||
"""Get user by email."""
|
"""Get user by email."""
|
||||||
try:
|
try:
|
||||||
return db.query(User).filter(User.email == email).first()
|
return db.query(User).filter(User.email == email).first()
|
||||||
@@ -128,7 +132,7 @@ class AuthService:
|
|||||||
logger.error(f"Error getting user by email: {str(e)}")
|
logger.error(f"Error getting user by email: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_user_by_username(self, db: Session, username: str) -> Optional[User]:
|
def get_user_by_username(self, db: Session, username: str) -> User | None:
|
||||||
"""Get user by username."""
|
"""Get user by username."""
|
||||||
try:
|
try:
|
||||||
return db.query(User).filter(User.username == username).first()
|
return db.query(User).filter(User.username == username).first()
|
||||||
@@ -138,7 +142,7 @@ class AuthService:
|
|||||||
|
|
||||||
def authenticate_user(
|
def authenticate_user(
|
||||||
self, db: Session, username: str, password: str
|
self, db: Session, username: str, password: str
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
"""Authenticate user with username/password."""
|
"""Authenticate user with username/password."""
|
||||||
try:
|
try:
|
||||||
return self.auth_manager.authenticate_user(db, username, password)
|
return self.auth_manager.authenticate_user(db, username, password)
|
||||||
@@ -146,7 +150,7 @@ class AuthService:
|
|||||||
logger.error(f"Error authenticating user: {str(e)}")
|
logger.error(f"Error authenticating user: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_access_token(self, user: User) -> Dict[str, Any]:
|
def create_access_token(self, user: User) -> dict[str, Any]:
|
||||||
"""Create access token for user."""
|
"""Create access token for user."""
|
||||||
try:
|
try:
|
||||||
return self.auth_manager.create_access_token(user)
|
return self.auth_manager.create_access_token(user)
|
||||||
@@ -182,7 +186,7 @@ class AuthService:
|
|||||||
Returns:
|
Returns:
|
||||||
Dictionary with access_token, token_type, and expires_in
|
Dictionary with access_token, token_type, and expires_in
|
||||||
"""
|
"""
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
|
|
||||||
@@ -190,13 +194,13 @@ class AuthService:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
expire = datetime.now(timezone.utc) + expires_delta
|
expire = datetime.now(UTC) + expires_delta
|
||||||
|
|
||||||
# Build payload with provided data
|
# Build payload with provided data
|
||||||
payload = {
|
payload = {
|
||||||
**data,
|
**data,
|
||||||
"exp": expire,
|
"exp": expire,
|
||||||
"iat": datetime.now(timezone.utc),
|
"iat": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
||||||
|
|||||||
@@ -9,20 +9,18 @@ This module provides:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (CartItemNotFoundException, CartValidationException,
|
from app.exceptions import (
|
||||||
InsufficientInventoryForCartException,
|
CartItemNotFoundException,
|
||||||
InvalidCartQuantityException,
|
InsufficientInventoryForCartException,
|
||||||
ProductNotAvailableForCartException,
|
InvalidCartQuantityException,
|
||||||
ProductNotFoundException)
|
ProductNotFoundException,
|
||||||
|
)
|
||||||
from models.database.cart import CartItem
|
from models.database.cart import CartItem
|
||||||
from models.database.product import Product
|
from models.database.product import Product
|
||||||
from models.database.vendor import Vendor
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|||||||
class CartService:
|
class CartService:
|
||||||
"""Service for managing shopping carts."""
|
"""Service for managing shopping carts."""
|
||||||
|
|
||||||
def get_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict:
|
def get_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
|
||||||
"""
|
"""
|
||||||
Get cart contents for a session.
|
Get cart contents for a session.
|
||||||
|
|
||||||
@@ -43,7 +41,7 @@ class CartService:
|
|||||||
Cart data with items and totals
|
Cart data with items and totals
|
||||||
"""
|
"""
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[CART_SERVICE] get_cart called",
|
"[CART_SERVICE] get_cart called",
|
||||||
extra={
|
extra={
|
||||||
"vendor_id": vendor_id,
|
"vendor_id": vendor_id,
|
||||||
"session_id": session_id,
|
"session_id": session_id,
|
||||||
@@ -111,7 +109,7 @@ class CartService:
|
|||||||
session_id: str,
|
session_id: str,
|
||||||
product_id: int,
|
product_id: int,
|
||||||
quantity: int = 1,
|
quantity: int = 1,
|
||||||
) -> Dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Add product to cart.
|
Add product to cart.
|
||||||
|
|
||||||
@@ -130,7 +128,7 @@ class CartService:
|
|||||||
InsufficientInventoryException: If not enough inventory
|
InsufficientInventoryException: If not enough inventory
|
||||||
"""
|
"""
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[CART_SERVICE] add_to_cart called",
|
"[CART_SERVICE] add_to_cart called",
|
||||||
extra={
|
extra={
|
||||||
"vendor_id": vendor_id,
|
"vendor_id": vendor_id,
|
||||||
"session_id": session_id,
|
"session_id": session_id,
|
||||||
@@ -154,7 +152,7 @@ class CartService:
|
|||||||
|
|
||||||
if not product:
|
if not product:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"[CART_SERVICE] Product not found",
|
"[CART_SERVICE] Product not found",
|
||||||
extra={"product_id": product_id, "vendor_id": vendor_id},
|
extra={"product_id": product_id, "vendor_id": vendor_id},
|
||||||
)
|
)
|
||||||
raise ProductNotFoundException(product_id=product_id, vendor_id=vendor_id)
|
raise ProductNotFoundException(product_id=product_id, vendor_id=vendor_id)
|
||||||
@@ -191,7 +189,7 @@ class CartService:
|
|||||||
# Check inventory for new total quantity
|
# Check inventory for new total quantity
|
||||||
if product.available_inventory < new_quantity:
|
if product.available_inventory < new_quantity:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"[CART_SERVICE] Insufficient inventory for update",
|
"[CART_SERVICE] Insufficient inventory for update",
|
||||||
extra={
|
extra={
|
||||||
"product_id": product_id,
|
"product_id": product_id,
|
||||||
"current_in_cart": existing_item.quantity,
|
"current_in_cart": existing_item.quantity,
|
||||||
@@ -212,7 +210,7 @@ class CartService:
|
|||||||
db.refresh(existing_item)
|
db.refresh(existing_item)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[CART_SERVICE] Updated existing cart item",
|
"[CART_SERVICE] Updated existing cart item",
|
||||||
extra={"cart_item_id": existing_item.id, "new_quantity": new_quantity},
|
extra={"cart_item_id": existing_item.id, "new_quantity": new_quantity},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -221,50 +219,49 @@ class CartService:
|
|||||||
"product_id": product_id,
|
"product_id": product_id,
|
||||||
"quantity": new_quantity,
|
"quantity": new_quantity,
|
||||||
}
|
}
|
||||||
else:
|
# Check inventory for new item
|
||||||
# Check inventory for new item
|
if product.available_inventory < quantity:
|
||||||
if product.available_inventory < quantity:
|
logger.warning(
|
||||||
logger.warning(
|
"[CART_SERVICE] Insufficient inventory",
|
||||||
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",
|
|
||||||
extra={
|
extra={
|
||||||
"cart_item_id": cart_item.id,
|
"product_id": product_id,
|
||||||
"quantity": quantity,
|
"requested": quantity,
|
||||||
"price": current_price,
|
"available": product.available_inventory,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
raise InsufficientInventoryForCartException(
|
||||||
|
product_id=product_id,
|
||||||
|
product_name=product.marketplace_product.title,
|
||||||
|
requested=quantity,
|
||||||
|
available=product.available_inventory,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
# Create new cart item
|
||||||
"message": "Product added to cart",
|
cart_item = CartItem(
|
||||||
"product_id": product_id,
|
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,
|
"quantity": quantity,
|
||||||
}
|
"price": current_price,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "Product added to cart",
|
||||||
|
"product_id": product_id,
|
||||||
|
"quantity": quantity,
|
||||||
|
}
|
||||||
|
|
||||||
def update_cart_item(
|
def update_cart_item(
|
||||||
self,
|
self,
|
||||||
@@ -273,7 +270,7 @@ class CartService:
|
|||||||
session_id: str,
|
session_id: str,
|
||||||
product_id: int,
|
product_id: int,
|
||||||
quantity: int,
|
quantity: int,
|
||||||
) -> Dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Update quantity of item in cart.
|
Update quantity of item in cart.
|
||||||
|
|
||||||
@@ -344,7 +341,7 @@ class CartService:
|
|||||||
db.refresh(cart_item)
|
db.refresh(cart_item)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[CART_SERVICE] Updated cart item quantity",
|
"[CART_SERVICE] Updated cart item quantity",
|
||||||
extra={
|
extra={
|
||||||
"cart_item_id": cart_item.id,
|
"cart_item_id": cart_item.id,
|
||||||
"product_id": product_id,
|
"product_id": product_id,
|
||||||
@@ -360,7 +357,7 @@ class CartService:
|
|||||||
|
|
||||||
def remove_from_cart(
|
def remove_from_cart(
|
||||||
self, db: Session, vendor_id: int, session_id: str, product_id: int
|
self, db: Session, vendor_id: int, session_id: str, product_id: int
|
||||||
) -> Dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Remove item from cart.
|
Remove item from cart.
|
||||||
|
|
||||||
@@ -398,7 +395,7 @@ class CartService:
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[CART_SERVICE] Removed item from cart",
|
"[CART_SERVICE] Removed item from cart",
|
||||||
extra={
|
extra={
|
||||||
"cart_item_id": cart_item.id,
|
"cart_item_id": cart_item.id,
|
||||||
"product_id": product_id,
|
"product_id": product_id,
|
||||||
@@ -408,7 +405,7 @@ class CartService:
|
|||||||
|
|
||||||
return {"message": "Item removed from cart", "product_id": product_id}
|
return {"message": "Item removed from cart", "product_id": product_id}
|
||||||
|
|
||||||
def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict:
|
def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
|
||||||
"""
|
"""
|
||||||
Clear all items from cart.
|
Clear all items from cart.
|
||||||
|
|
||||||
@@ -432,7 +429,7 @@ class CartService:
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[CART_SERVICE] Cleared cart",
|
"[CART_SERVICE] Cleared cart",
|
||||||
extra={
|
extra={
|
||||||
"session_id": session_id,
|
"session_id": session_id,
|
||||||
"vendor_id": vendor_id,
|
"vendor_id": vendor_id,
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
from sqlalchemy import desc, func
|
from sqlalchemy import desc, func
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.models.architecture_scan import (ArchitectureRule, ArchitectureScan,
|
from app.models.architecture_scan import (
|
||||||
ArchitectureViolation,
|
ArchitectureScan,
|
||||||
ViolationAssignment,
|
ArchitectureViolation,
|
||||||
ViolationComment)
|
ViolationAssignment,
|
||||||
|
ViolationComment,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ class CodeQualityService:
|
|||||||
logger.info(f"Scan completed: {scan.total_violations} violations found")
|
logger.info(f"Scan completed: {scan.total_violations} violations found")
|
||||||
return scan
|
return scan
|
||||||
|
|
||||||
def get_latest_scan(self, db: Session) -> Optional[ArchitectureScan]:
|
def get_latest_scan(self, db: Session) -> ArchitectureScan | None:
|
||||||
"""Get the most recent scan"""
|
"""Get the most recent scan"""
|
||||||
return (
|
return (
|
||||||
db.query(ArchitectureScan)
|
db.query(ArchitectureScan)
|
||||||
@@ -126,11 +126,11 @@ class CodeQualityService:
|
|||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_scan_by_id(self, db: Session, scan_id: int) -> Optional[ArchitectureScan]:
|
def get_scan_by_id(self, db: Session, scan_id: int) -> ArchitectureScan | None:
|
||||||
"""Get scan by ID"""
|
"""Get scan by ID"""
|
||||||
return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first()
|
return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first()
|
||||||
|
|
||||||
def get_scan_history(self, db: Session, limit: int = 30) -> List[ArchitectureScan]:
|
def get_scan_history(self, db: Session, limit: int = 30) -> list[ArchitectureScan]:
|
||||||
"""
|
"""
|
||||||
Get scan history for trend graphs
|
Get scan history for trend graphs
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ class CodeQualityService:
|
|||||||
file_path: str = None,
|
file_path: str = None,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
) -> Tuple[List[ArchitectureViolation], int]:
|
) -> tuple[list[ArchitectureViolation], int]:
|
||||||
"""
|
"""
|
||||||
Get violations with filtering and pagination
|
Get violations with filtering and pagination
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ class CodeQualityService:
|
|||||||
|
|
||||||
def get_violation_by_id(
|
def get_violation_by_id(
|
||||||
self, db: Session, violation_id: int
|
self, db: Session, violation_id: int
|
||||||
) -> Optional[ArchitectureViolation]:
|
) -> ArchitectureViolation | None:
|
||||||
"""Get single violation with details"""
|
"""Get single violation with details"""
|
||||||
return (
|
return (
|
||||||
db.query(ArchitectureViolation)
|
db.query(ArchitectureViolation)
|
||||||
@@ -348,7 +348,7 @@ class CodeQualityService:
|
|||||||
logger.info(f"Comment added to violation {violation_id} by user {user_id}")
|
logger.info(f"Comment added to violation {violation_id} by user {user_id}")
|
||||||
return comment_obj
|
return comment_obj
|
||||||
|
|
||||||
def get_dashboard_stats(self, db: Session) -> Dict:
|
def get_dashboard_stats(self, db: Session) -> dict:
|
||||||
"""
|
"""
|
||||||
Get statistics for dashboard
|
Get statistics for dashboard
|
||||||
|
|
||||||
@@ -507,7 +507,7 @@ class CodeQualityService:
|
|||||||
score = 100 - (scan.errors * 0.5 + scan.warnings * 0.05)
|
score = 100 - (scan.errors * 0.5 + scan.warnings * 0.05)
|
||||||
return max(0, min(100, int(score))) # Clamp to 0-100
|
return max(0, min(100, int(score))) # Clamp to 0-100
|
||||||
|
|
||||||
def _get_git_commit_hash(self) -> Optional[str]:
|
def _get_git_commit_hash(self) -> str | None:
|
||||||
"""Get current git commit hash"""
|
"""Get current git commit hash"""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ This allows:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from models.database.content_page import ContentPage
|
from models.database.content_page import ContentPage
|
||||||
@@ -35,9 +34,9 @@ class ContentPageService:
|
|||||||
def get_page_for_vendor(
|
def get_page_for_vendor(
|
||||||
db: Session,
|
db: Session,
|
||||||
slug: str,
|
slug: str,
|
||||||
vendor_id: Optional[int] = None,
|
vendor_id: int | None = None,
|
||||||
include_unpublished: bool = False,
|
include_unpublished: bool = False,
|
||||||
) -> Optional[ContentPage]:
|
) -> ContentPage | None:
|
||||||
"""
|
"""
|
||||||
Get content page for a vendor with fallback to platform default.
|
Get content page for a vendor with fallback to platform default.
|
||||||
|
|
||||||
@@ -90,11 +89,11 @@ class ContentPageService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def list_pages_for_vendor(
|
def list_pages_for_vendor(
|
||||||
db: Session,
|
db: Session,
|
||||||
vendor_id: Optional[int] = None,
|
vendor_id: int | None = None,
|
||||||
include_unpublished: bool = False,
|
include_unpublished: bool = False,
|
||||||
footer_only: bool = False,
|
footer_only: bool = False,
|
||||||
header_only: bool = False,
|
header_only: bool = False,
|
||||||
) -> List[ContentPage]:
|
) -> list[ContentPage]:
|
||||||
"""
|
"""
|
||||||
List all available pages for a vendor (includes vendor overrides + platform defaults).
|
List all available pages for a vendor (includes vendor overrides + platform defaults).
|
||||||
|
|
||||||
@@ -156,16 +155,16 @@ class ContentPageService:
|
|||||||
slug: str,
|
slug: str,
|
||||||
title: str,
|
title: str,
|
||||||
content: str,
|
content: str,
|
||||||
vendor_id: Optional[int] = None,
|
vendor_id: int | None = None,
|
||||||
content_format: str = "html",
|
content_format: str = "html",
|
||||||
template: str = "default",
|
template: str = "default",
|
||||||
meta_description: Optional[str] = None,
|
meta_description: str | None = None,
|
||||||
meta_keywords: Optional[str] = None,
|
meta_keywords: str | None = None,
|
||||||
is_published: bool = False,
|
is_published: bool = False,
|
||||||
show_in_footer: bool = True,
|
show_in_footer: bool = True,
|
||||||
show_in_header: bool = False,
|
show_in_header: bool = False,
|
||||||
display_order: int = 0,
|
display_order: int = 0,
|
||||||
created_by: Optional[int] = None,
|
created_by: int | None = None,
|
||||||
) -> ContentPage:
|
) -> ContentPage:
|
||||||
"""
|
"""
|
||||||
Create a new content page.
|
Create a new content page.
|
||||||
@@ -199,7 +198,7 @@ class ContentPageService:
|
|||||||
meta_description=meta_description,
|
meta_description=meta_description,
|
||||||
meta_keywords=meta_keywords,
|
meta_keywords=meta_keywords,
|
||||||
is_published=is_published,
|
is_published=is_published,
|
||||||
published_at=datetime.now(timezone.utc) if is_published else None,
|
published_at=datetime.now(UTC) if is_published else None,
|
||||||
show_in_footer=show_in_footer,
|
show_in_footer=show_in_footer,
|
||||||
show_in_header=show_in_header,
|
show_in_header=show_in_header,
|
||||||
display_order=display_order,
|
display_order=display_order,
|
||||||
@@ -220,18 +219,18 @@ class ContentPageService:
|
|||||||
def update_page(
|
def update_page(
|
||||||
db: Session,
|
db: Session,
|
||||||
page_id: int,
|
page_id: int,
|
||||||
title: Optional[str] = None,
|
title: str | None = None,
|
||||||
content: Optional[str] = None,
|
content: str | None = None,
|
||||||
content_format: Optional[str] = None,
|
content_format: str | None = None,
|
||||||
template: Optional[str] = None,
|
template: str | None = None,
|
||||||
meta_description: Optional[str] = None,
|
meta_description: str | None = None,
|
||||||
meta_keywords: Optional[str] = None,
|
meta_keywords: str | None = None,
|
||||||
is_published: Optional[bool] = None,
|
is_published: bool | None = None,
|
||||||
show_in_footer: Optional[bool] = None,
|
show_in_footer: bool | None = None,
|
||||||
show_in_header: Optional[bool] = None,
|
show_in_header: bool | None = None,
|
||||||
display_order: Optional[int] = None,
|
display_order: int | None = None,
|
||||||
updated_by: Optional[int] = None,
|
updated_by: int | None = None,
|
||||||
) -> Optional[ContentPage]:
|
) -> ContentPage | None:
|
||||||
"""
|
"""
|
||||||
Update an existing content page.
|
Update an existing content page.
|
||||||
|
|
||||||
@@ -275,7 +274,7 @@ class ContentPageService:
|
|||||||
if is_published is not None:
|
if is_published is not None:
|
||||||
page.is_published = is_published
|
page.is_published = is_published
|
||||||
if is_published and not page.published_at:
|
if is_published and not page.published_at:
|
||||||
page.published_at = datetime.now(timezone.utc)
|
page.published_at = datetime.now(UTC)
|
||||||
if show_in_footer is not None:
|
if show_in_footer is not None:
|
||||||
page.show_in_footer = show_in_footer
|
page.show_in_footer = show_in_footer
|
||||||
if show_in_header is not None:
|
if show_in_header is not None:
|
||||||
@@ -316,14 +315,14 @@ class ContentPageService:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_page_by_id(db: Session, page_id: int) -> Optional[ContentPage]:
|
def get_page_by_id(db: Session, page_id: int) -> ContentPage | None:
|
||||||
"""Get content page by ID."""
|
"""Get content page by ID."""
|
||||||
return db.query(ContentPage).filter(ContentPage.id == page_id).first()
|
return db.query(ContentPage).filter(ContentPage.id == page_id).first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list_all_vendor_pages(
|
def list_all_vendor_pages(
|
||||||
db: Session, vendor_id: int, include_unpublished: bool = False
|
db: Session, vendor_id: int, include_unpublished: bool = False
|
||||||
) -> List[ContentPage]:
|
) -> list[ContentPage]:
|
||||||
"""
|
"""
|
||||||
List only vendor-specific pages (no platform defaults).
|
List only vendor-specific pages (no platform defaults).
|
||||||
|
|
||||||
@@ -350,7 +349,7 @@ class ContentPageService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def list_all_platform_pages(
|
def list_all_platform_pages(
|
||||||
db: Session, include_unpublished: bool = False
|
db: Session, include_unpublished: bool = False
|
||||||
) -> List[ContentPage]:
|
) -> list[ContentPage]:
|
||||||
"""
|
"""
|
||||||
List only platform default pages.
|
List only platform default pages.
|
||||||
|
|
||||||
|
|||||||
@@ -7,22 +7,22 @@ with complete vendor isolation.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions.customer import (CustomerAlreadyExistsException,
|
from app.exceptions.customer import (
|
||||||
CustomerNotActiveException,
|
CustomerNotActiveException,
|
||||||
CustomerNotFoundException,
|
CustomerNotFoundException,
|
||||||
CustomerValidationException,
|
CustomerValidationException,
|
||||||
DuplicateCustomerEmailException,
|
DuplicateCustomerEmailException,
|
||||||
InvalidCustomerCredentialsException)
|
InvalidCustomerCredentialsException,
|
||||||
from app.exceptions.vendor import (VendorNotActiveException,
|
)
|
||||||
VendorNotFoundException)
|
from app.exceptions.vendor import VendorNotActiveException, VendorNotFoundException
|
||||||
from app.services.auth_service import AuthService
|
from app.services.auth_service import AuthService
|
||||||
from models.database.customer import Customer, CustomerAddress
|
from models.database.customer import Customer
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.auth import UserLogin
|
from models.schema.auth import UserLogin
|
||||||
from models.schema.customer import CustomerRegister, CustomerUpdate
|
from models.schema.customer import CustomerRegister, CustomerUpdate
|
||||||
@@ -128,7 +128,7 @@ class CustomerService:
|
|||||||
|
|
||||||
def login_customer(
|
def login_customer(
|
||||||
self, db: Session, vendor_id: int, credentials: UserLogin
|
self, db: Session, vendor_id: int, credentials: UserLogin
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Authenticate customer and generate JWT token.
|
Authenticate customer and generate JWT token.
|
||||||
|
|
||||||
@@ -177,13 +177,13 @@ class CustomerService:
|
|||||||
|
|
||||||
# Generate JWT token with customer context
|
# Generate JWT token with customer context
|
||||||
# Use auth_manager directly since Customer is not a User model
|
# Use auth_manager directly since Customer is not a User model
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime
|
||||||
|
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
|
|
||||||
auth_manager = self.auth_service.auth_manager
|
auth_manager = self.auth_service.auth_manager
|
||||||
expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
|
expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
|
||||||
expire = datetime.now(timezone.utc) + expires_delta
|
expire = datetime.now(UTC) + expires_delta
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"sub": str(customer.id),
|
"sub": str(customer.id),
|
||||||
@@ -191,7 +191,7 @@ class CustomerService:
|
|||||||
"vendor_id": vendor_id,
|
"vendor_id": vendor_id,
|
||||||
"type": "customer",
|
"type": "customer",
|
||||||
"exp": expire,
|
"exp": expire,
|
||||||
"iat": datetime.now(timezone.utc),
|
"iat": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
token = jwt.encode(
|
token = jwt.encode(
|
||||||
@@ -239,7 +239,7 @@ class CustomerService:
|
|||||||
|
|
||||||
def get_customer_by_email(
|
def get_customer_by_email(
|
||||||
self, db: Session, vendor_id: int, email: str
|
self, db: Session, vendor_id: int, email: str
|
||||||
) -> Optional[Customer]:
|
) -> Customer | None:
|
||||||
"""
|
"""
|
||||||
Get customer by email (vendor-scoped).
|
Get customer by email (vendor-scoped).
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
# app/services/inventory_service.py
|
# app/services/inventory_service.py
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (InsufficientInventoryException,
|
from app.exceptions import (
|
||||||
InvalidInventoryOperationException,
|
InsufficientInventoryException,
|
||||||
InvalidQuantityException,
|
InvalidQuantityException,
|
||||||
InventoryNotFoundException,
|
InventoryNotFoundException,
|
||||||
InventoryValidationException,
|
InventoryValidationException,
|
||||||
NegativeInventoryException,
|
ProductNotFoundException,
|
||||||
ProductNotFoundException, ValidationException)
|
ValidationException,
|
||||||
|
)
|
||||||
from models.database.inventory import Inventory
|
from models.database.inventory import Inventory
|
||||||
from models.database.product import Product
|
from models.database.product import Product
|
||||||
from models.database.vendor import Vendor
|
from models.schema.inventory import (
|
||||||
from models.schema.inventory import (InventoryAdjust, InventoryCreate,
|
InventoryAdjust,
|
||||||
InventoryLocationResponse,
|
InventoryCreate,
|
||||||
InventoryReserve, InventoryUpdate,
|
InventoryLocationResponse,
|
||||||
ProductInventorySummary)
|
InventoryReserve,
|
||||||
|
InventoryUpdate,
|
||||||
|
ProductInventorySummary,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ class InventoryService:
|
|||||||
if existing:
|
if existing:
|
||||||
old_qty = existing.quantity
|
old_qty = existing.quantity
|
||||||
existing.quantity = inventory_data.quantity
|
existing.quantity = inventory_data.quantity
|
||||||
existing.updated_at = datetime.now(timezone.utc)
|
existing.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(existing)
|
db.refresh(existing)
|
||||||
|
|
||||||
@@ -67,24 +70,23 @@ class InventoryService:
|
|||||||
f"{old_qty} → {inventory_data.quantity}"
|
f"{old_qty} → {inventory_data.quantity}"
|
||||||
)
|
)
|
||||||
return existing
|
return existing
|
||||||
else:
|
# Create new inventory entry
|
||||||
# Create new inventory entry
|
new_inventory = Inventory(
|
||||||
new_inventory = Inventory(
|
product_id=inventory_data.product_id,
|
||||||
product_id=inventory_data.product_id,
|
vendor_id=vendor_id,
|
||||||
vendor_id=vendor_id,
|
location=location,
|
||||||
location=location,
|
quantity=inventory_data.quantity,
|
||||||
quantity=inventory_data.quantity,
|
gtin=product.marketplace_product.gtin, # Optional reference
|
||||||
gtin=product.marketplace_product.gtin, # Optional reference
|
)
|
||||||
)
|
db.add(new_inventory)
|
||||||
db.add(new_inventory)
|
db.commit()
|
||||||
db.commit()
|
db.refresh(new_inventory)
|
||||||
db.refresh(new_inventory)
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Created inventory for product {inventory_data.product_id} at {location}: "
|
f"Created inventory for product {inventory_data.product_id} at {location}: "
|
||||||
f"{inventory_data.quantity}"
|
f"{inventory_data.quantity}"
|
||||||
)
|
)
|
||||||
return new_inventory
|
return new_inventory
|
||||||
|
|
||||||
except (
|
except (
|
||||||
ProductNotFoundException,
|
ProductNotFoundException,
|
||||||
@@ -162,7 +164,7 @@ class InventoryService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
existing.quantity = new_qty
|
existing.quantity = new_qty
|
||||||
existing.updated_at = datetime.now(timezone.utc)
|
existing.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(existing)
|
db.refresh(existing)
|
||||||
|
|
||||||
@@ -224,7 +226,7 @@ class InventoryService:
|
|||||||
|
|
||||||
# Reserve inventory
|
# Reserve inventory
|
||||||
inventory.reserved_quantity += reserve_data.quantity
|
inventory.reserved_quantity += reserve_data.quantity
|
||||||
inventory.updated_at = datetime.now(timezone.utc)
|
inventory.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(inventory)
|
db.refresh(inventory)
|
||||||
|
|
||||||
@@ -284,7 +286,7 @@ class InventoryService:
|
|||||||
else:
|
else:
|
||||||
inventory.reserved_quantity -= reserve_data.quantity
|
inventory.reserved_quantity -= reserve_data.quantity
|
||||||
|
|
||||||
inventory.updated_at = datetime.now(timezone.utc)
|
inventory.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(inventory)
|
db.refresh(inventory)
|
||||||
|
|
||||||
@@ -350,7 +352,7 @@ class InventoryService:
|
|||||||
inventory.reserved_quantity = max(
|
inventory.reserved_quantity = max(
|
||||||
0, inventory.reserved_quantity - reserve_data.quantity
|
0, inventory.reserved_quantity - reserve_data.quantity
|
||||||
)
|
)
|
||||||
inventory.updated_at = datetime.now(timezone.utc)
|
inventory.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(inventory)
|
db.refresh(inventory)
|
||||||
|
|
||||||
@@ -443,9 +445,9 @@ class InventoryService:
|
|||||||
vendor_id: int,
|
vendor_id: int,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
location: Optional[str] = None,
|
location: str | None = None,
|
||||||
low_stock_threshold: Optional[int] = None,
|
low_stock_threshold: int | None = None,
|
||||||
) -> List[Inventory]:
|
) -> list[Inventory]:
|
||||||
"""
|
"""
|
||||||
Get all inventory for a vendor with filtering.
|
Get all inventory for a vendor with filtering.
|
||||||
|
|
||||||
@@ -504,7 +506,7 @@ class InventoryService:
|
|||||||
if inventory_update.location:
|
if inventory_update.location:
|
||||||
inventory.location = self._validate_location(inventory_update.location)
|
inventory.location = self._validate_location(inventory_update.location)
|
||||||
|
|
||||||
inventory.updated_at = datetime.now(timezone.utc)
|
inventory.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(inventory)
|
db.refresh(inventory)
|
||||||
|
|
||||||
@@ -565,7 +567,7 @@ class InventoryService:
|
|||||||
|
|
||||||
def _get_inventory_entry(
|
def _get_inventory_entry(
|
||||||
self, db: Session, product_id: int, location: str
|
self, db: Session, product_id: int, location: str
|
||||||
) -> Optional[Inventory]:
|
) -> Inventory | None:
|
||||||
"""Get inventory entry by product and location."""
|
"""Get inventory entry by product and location."""
|
||||||
return (
|
return (
|
||||||
db.query(Inventory)
|
db.query(Inventory)
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
# app/services/marketplace_import_job_service.py
|
# app/services/marketplace_import_job_service.py
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (ImportJobCannotBeCancelledException,
|
from app.exceptions import (
|
||||||
ImportJobCannotBeDeletedException,
|
ImportJobNotFoundException,
|
||||||
ImportJobNotFoundException,
|
ImportJobNotOwnedException,
|
||||||
ImportJobNotOwnedException, ValidationException)
|
ValidationException,
|
||||||
|
)
|
||||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest,
|
from models.schema.marketplace_import_job import (
|
||||||
MarketplaceImportJobResponse)
|
MarketplaceImportJobRequest,
|
||||||
|
MarketplaceImportJobResponse,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -98,10 +99,10 @@ class MarketplaceImportJobService:
|
|||||||
db: Session,
|
db: Session,
|
||||||
vendor: Vendor, # ADDED: Vendor filter
|
vendor: Vendor, # ADDED: Vendor filter
|
||||||
user: User,
|
user: User,
|
||||||
marketplace: Optional[str] = None,
|
marketplace: str | None = None,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 50,
|
limit: int = 50,
|
||||||
) -> List[MarketplaceImportJob]:
|
) -> list[MarketplaceImportJob]:
|
||||||
"""Get marketplace import jobs for a specific vendor."""
|
"""Get marketplace import jobs for a specific vendor."""
|
||||||
try:
|
try:
|
||||||
query = db.query(MarketplaceImportJob).filter(
|
query = db.query(MarketplaceImportJob).filter(
|
||||||
|
|||||||
@@ -8,29 +8,31 @@ This module provides classes and functions for:
|
|||||||
- Inventory information integration
|
- Inventory information integration
|
||||||
- CSV export functionality
|
- CSV export functionality
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from collections.abc import Generator
|
||||||
|
from datetime import UTC, datetime
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Generator, List, Optional, Tuple
|
|
||||||
|
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (InvalidMarketplaceProductDataException,
|
from app.exceptions import (
|
||||||
MarketplaceProductAlreadyExistsException,
|
InvalidMarketplaceProductDataException,
|
||||||
MarketplaceProductNotFoundException,
|
MarketplaceProductAlreadyExistsException,
|
||||||
MarketplaceProductValidationException,
|
MarketplaceProductNotFoundException,
|
||||||
ValidationException)
|
MarketplaceProductValidationException,
|
||||||
from app.services.marketplace_import_job_service import \
|
ValidationException,
|
||||||
marketplace_import_job_service
|
)
|
||||||
from app.utils.data_processing import GTINProcessor, PriceProcessor
|
from app.utils.data_processing import GTINProcessor, PriceProcessor
|
||||||
from models.database.inventory import Inventory
|
from models.database.inventory import Inventory
|
||||||
from models.database.marketplace_product import MarketplaceProduct
|
from models.database.marketplace_product import MarketplaceProduct
|
||||||
from models.schema.inventory import (InventoryLocationResponse,
|
from models.schema.inventory import InventoryLocationResponse, InventorySummaryResponse
|
||||||
InventorySummaryResponse)
|
from models.schema.marketplace_product import (
|
||||||
from models.schema.marketplace_product import (MarketplaceProductCreate,
|
MarketplaceProductCreate,
|
||||||
MarketplaceProductUpdate)
|
MarketplaceProductUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -109,10 +111,9 @@ class MarketplaceProductService:
|
|||||||
raise MarketplaceProductAlreadyExistsException(
|
raise MarketplaceProductAlreadyExistsException(
|
||||||
product_data.marketplace_product_id
|
product_data.marketplace_product_id
|
||||||
)
|
)
|
||||||
else:
|
raise MarketplaceProductValidationException(
|
||||||
raise MarketplaceProductValidationException(
|
"Data integrity constraint violation"
|
||||||
"Data integrity constraint violation"
|
)
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
logger.error(f"Error creating product: {str(e)}")
|
logger.error(f"Error creating product: {str(e)}")
|
||||||
@@ -120,7 +121,7 @@ class MarketplaceProductService:
|
|||||||
|
|
||||||
def get_product_by_id(
|
def get_product_by_id(
|
||||||
self, db: Session, marketplace_product_id: str
|
self, db: Session, marketplace_product_id: str
|
||||||
) -> Optional[MarketplaceProduct]:
|
) -> MarketplaceProduct | None:
|
||||||
"""Get a product by its ID."""
|
"""Get a product by its ID."""
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
@@ -160,13 +161,13 @@ class MarketplaceProductService:
|
|||||||
db: Session,
|
db: Session,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
brand: Optional[str] = None,
|
brand: str | None = None,
|
||||||
category: Optional[str] = None,
|
category: str | None = None,
|
||||||
availability: Optional[str] = None,
|
availability: str | None = None,
|
||||||
marketplace: Optional[str] = None,
|
marketplace: str | None = None,
|
||||||
vendor_name: Optional[str] = None,
|
vendor_name: str | None = None,
|
||||||
search: Optional[str] = None,
|
search: str | None = None,
|
||||||
) -> Tuple[List[MarketplaceProduct], int]:
|
) -> tuple[list[MarketplaceProduct], int]:
|
||||||
"""
|
"""
|
||||||
Get products with filtering and pagination.
|
Get products with filtering and pagination.
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ class MarketplaceProductService:
|
|||||||
for key, value in update_data.items():
|
for key, value in update_data.items():
|
||||||
setattr(product, key, value)
|
setattr(product, key, value)
|
||||||
|
|
||||||
product.updated_at = datetime.now(timezone.utc)
|
product.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(product)
|
db.refresh(product)
|
||||||
|
|
||||||
@@ -324,7 +325,7 @@ class MarketplaceProductService:
|
|||||||
|
|
||||||
def get_inventory_info(
|
def get_inventory_info(
|
||||||
self, db: Session, gtin: str
|
self, db: Session, gtin: str
|
||||||
) -> Optional[InventorySummaryResponse]:
|
) -> InventorySummaryResponse | None:
|
||||||
"""
|
"""
|
||||||
Get inventory information for a product by GTIN.
|
Get inventory information for a product by GTIN.
|
||||||
|
|
||||||
@@ -358,15 +359,14 @@ class MarketplaceProductService:
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Generator, Optional
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
def generate_csv_export(
|
def generate_csv_export(
|
||||||
self,
|
self,
|
||||||
db: Session,
|
db: Session,
|
||||||
marketplace: Optional[str] = None,
|
marketplace: str | None = None,
|
||||||
vendor_name: Optional[str] = None,
|
vendor_name: str | None = None,
|
||||||
) -> Generator[str, None, None]:
|
) -> Generator[str, None, None]:
|
||||||
"""
|
"""
|
||||||
Generate CSV export with streaming for memory efficiency and proper CSV escaping.
|
Generate CSV export with streaming for memory efficiency and proper CSV escaping.
|
||||||
|
|||||||
@@ -11,15 +11,17 @@ This module provides:
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (CustomerNotFoundException,
|
from app.exceptions import (
|
||||||
InsufficientInventoryException,
|
CustomerNotFoundException,
|
||||||
OrderNotFoundException, ValidationException)
|
InsufficientInventoryException,
|
||||||
|
OrderNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
from models.database.customer import Customer, CustomerAddress
|
from models.database.customer import Customer, CustomerAddress
|
||||||
from models.database.order import Order, OrderItem
|
from models.database.order import Order, OrderItem
|
||||||
from models.database.product import Product
|
from models.database.product import Product
|
||||||
@@ -38,7 +40,7 @@ class OrderService:
|
|||||||
Format: ORD-{VENDOR_ID}-{TIMESTAMP}-{RANDOM}
|
Format: ORD-{VENDOR_ID}-{TIMESTAMP}-{RANDOM}
|
||||||
Example: ORD-1-20250110-A1B2C3
|
Example: ORD-1-20250110-A1B2C3
|
||||||
"""
|
"""
|
||||||
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d")
|
timestamp = datetime.now(UTC).strftime("%Y%m%d")
|
||||||
random_suffix = "".join(
|
random_suffix = "".join(
|
||||||
random.choices(string.ascii_uppercase + string.digits, k=6)
|
random.choices(string.ascii_uppercase + string.digits, k=6)
|
||||||
)
|
)
|
||||||
@@ -266,9 +268,9 @@ class OrderService:
|
|||||||
vendor_id: int,
|
vendor_id: int,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
status: Optional[str] = None,
|
status: str | None = None,
|
||||||
customer_id: Optional[int] = None,
|
customer_id: int | None = None,
|
||||||
) -> Tuple[List[Order], int]:
|
) -> tuple[list[Order], int]:
|
||||||
"""
|
"""
|
||||||
Get orders for vendor with filtering.
|
Get orders for vendor with filtering.
|
||||||
|
|
||||||
@@ -306,7 +308,7 @@ class OrderService:
|
|||||||
customer_id: int,
|
customer_id: int,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
) -> Tuple[List[Order], int]:
|
) -> tuple[list[Order], int]:
|
||||||
"""Get orders for a specific customer."""
|
"""Get orders for a specific customer."""
|
||||||
return self.get_vendor_orders(
|
return self.get_vendor_orders(
|
||||||
db=db, vendor_id=vendor_id, skip=skip, limit=limit, customer_id=customer_id
|
db=db, vendor_id=vendor_id, skip=skip, limit=limit, customer_id=customer_id
|
||||||
@@ -335,7 +337,7 @@ class OrderService:
|
|||||||
order.status = order_update.status
|
order.status = order_update.status
|
||||||
|
|
||||||
# Update timestamp based on status
|
# Update timestamp based on status
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(UTC)
|
||||||
if order_update.status == "shipped" and not order.shipped_at:
|
if order_update.status == "shipped" and not order.shipped_at:
|
||||||
order.shipped_at = now
|
order.shipped_at = now
|
||||||
elif order_update.status == "delivered" and not order.delivered_at:
|
elif order_update.status == "delivered" and not order.delivered_at:
|
||||||
@@ -351,7 +353,7 @@ class OrderService:
|
|||||||
if order_update.internal_notes:
|
if order_update.internal_notes:
|
||||||
order.internal_notes = order_update.internal_notes
|
order.internal_notes = order_update.internal_notes
|
||||||
|
|
||||||
order.updated_at = datetime.now(timezone.utc)
|
order.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(order)
|
db.refresh(order)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ This module provides:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (ProductAlreadyExistsException,
|
from app.exceptions import (
|
||||||
ProductNotFoundException, ValidationException)
|
ProductAlreadyExistsException,
|
||||||
|
ProductNotFoundException,
|
||||||
|
ValidationException,
|
||||||
|
)
|
||||||
from models.database.marketplace_product import MarketplaceProduct
|
from models.database.marketplace_product import MarketplaceProduct
|
||||||
from models.database.product import Product
|
from models.database.product import Product
|
||||||
from models.schema.product import ProductCreate, ProductUpdate
|
from models.schema.product import ProductCreate, ProductUpdate
|
||||||
@@ -106,7 +108,7 @@ class ProductService:
|
|||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
raise ProductAlreadyExistsException(
|
raise ProductAlreadyExistsException(
|
||||||
f"Product already exists in catalog"
|
"Product already exists in catalog"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create product
|
# Create product
|
||||||
@@ -167,7 +169,7 @@ class ProductService:
|
|||||||
for key, value in update_data.items():
|
for key, value in update_data.items():
|
||||||
setattr(product, key, value)
|
setattr(product, key, value)
|
||||||
|
|
||||||
product.updated_at = datetime.now(timezone.utc)
|
product.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(product)
|
db.refresh(product)
|
||||||
|
|
||||||
@@ -216,9 +218,9 @@ class ProductService:
|
|||||||
vendor_id: int,
|
vendor_id: int,
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
is_active: Optional[bool] = None,
|
is_active: bool | None = None,
|
||||||
is_featured: Optional[bool] = None,
|
is_featured: bool | None = None,
|
||||||
) -> Tuple[List[Product], int]:
|
) -> tuple[list[Product], int]:
|
||||||
"""
|
"""
|
||||||
Get products in vendor catalog with filtering.
|
Get products in vendor catalog with filtering.
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ This module provides:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -36,7 +36,7 @@ class StatsService:
|
|||||||
# VENDOR-SPECIFIC STATISTICS
|
# VENDOR-SPECIFIC STATISTICS
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
def get_vendor_stats(self, db: Session, vendor_id: int) -> Dict[str, Any]:
|
def get_vendor_stats(self, db: Session, vendor_id: int) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get statistics for a specific vendor.
|
Get statistics for a specific vendor.
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ class StatsService:
|
|||||||
|
|
||||||
def get_vendor_analytics(
|
def get_vendor_analytics(
|
||||||
self, db: Session, vendor_id: int, period: str = "30d"
|
self, db: Session, vendor_id: int, period: str = "30d"
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get a specific vendor analytics for a time period.
|
Get a specific vendor analytics for a time period.
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ class StatsService:
|
|||||||
# SYSTEM-WIDE STATISTICS (ADMIN)
|
# SYSTEM-WIDE STATISTICS (ADMIN)
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
def get_comprehensive_stats(self, db: Session) -> Dict[str, Any]:
|
def get_comprehensive_stats(self, db: Session) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get comprehensive system statistics for admin dashboard.
|
Get comprehensive system statistics for admin dashboard.
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class StatsService:
|
|||||||
reason=f"Database query failed: {str(e)}",
|
reason=f"Database query failed: {str(e)}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_marketplace_breakdown_stats(self, db: Session) -> List[Dict[str, Any]]:
|
def get_marketplace_breakdown_stats(self, db: Session) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Get statistics broken down by marketplace.
|
Get statistics broken down by marketplace.
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ class StatsService:
|
|||||||
reason=f"Database query failed: {str(e)}",
|
reason=f"Database query failed: {str(e)}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_user_statistics(self, db: Session) -> Dict[str, Any]:
|
def get_user_statistics(self, db: Session) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get user statistics for admin dashboard.
|
Get user statistics for admin dashboard.
|
||||||
|
|
||||||
@@ -416,7 +416,7 @@ class StatsService:
|
|||||||
operation="get_user_statistics", reason="Database query failed"
|
operation="get_user_statistics", reason="Database query failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_import_statistics(self, db: Session) -> Dict[str, Any]:
|
def get_import_statistics(self, db: Session) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get import job statistics.
|
Get import job statistics.
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ class StatsService:
|
|||||||
"success_rate": 0,
|
"success_rate": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_order_statistics(self, db: Session) -> Dict[str, Any]:
|
def get_order_statistics(self, db: Session) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get order statistics.
|
Get order statistics.
|
||||||
|
|
||||||
@@ -472,7 +472,7 @@ class StatsService:
|
|||||||
"""
|
"""
|
||||||
return {"total_orders": 0, "pending_orders": 0, "completed_orders": 0}
|
return {"total_orders": 0, "pending_orders": 0, "completed_orders": 0}
|
||||||
|
|
||||||
def get_product_statistics(self, db: Session) -> Dict[str, Any]:
|
def get_product_statistics(self, db: Session) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get product statistics.
|
Get product statistics.
|
||||||
|
|
||||||
@@ -548,7 +548,7 @@ class StatsService:
|
|||||||
.count()
|
.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_inventory_statistics(self, db: Session) -> Dict[str, int]:
|
def _get_inventory_statistics(self, db: Session) -> dict[str, int]:
|
||||||
"""
|
"""
|
||||||
Get inventory-related statistics.
|
Get inventory-related statistics.
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ This module provides:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (UnauthorizedVendorAccessException,
|
from app.exceptions import ValidationException
|
||||||
ValidationException)
|
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Role, VendorUser
|
from models.database.vendor import Role, VendorUser
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ class TeamService:
|
|||||||
|
|
||||||
def get_team_members(
|
def get_team_members(
|
||||||
self, db: Session, vendor_id: int, current_user: User
|
self, db: Session, vendor_id: int, current_user: User
|
||||||
) -> List[Dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Get all team members for vendor.
|
Get all team members for vendor.
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ class TeamService:
|
|||||||
|
|
||||||
def invite_team_member(
|
def invite_team_member(
|
||||||
self, db: Session, vendor_id: int, invitation_data: dict, current_user: User
|
self, db: Session, vendor_id: int, invitation_data: dict, current_user: User
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Invite a new team member.
|
Invite a new team member.
|
||||||
|
|
||||||
@@ -102,7 +101,7 @@ class TeamService:
|
|||||||
user_id: int,
|
user_id: int,
|
||||||
update_data: dict,
|
update_data: dict,
|
||||||
current_user: User,
|
current_user: User,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Update team member role or status.
|
Update team member role or status.
|
||||||
|
|
||||||
@@ -135,7 +134,7 @@ class TeamService:
|
|||||||
if "is_active" in update_data:
|
if "is_active" in update_data:
|
||||||
vendor_user.is_active = update_data["is_active"]
|
vendor_user.is_active = update_data["is_active"]
|
||||||
|
|
||||||
vendor_user.updated_at = datetime.now(timezone.utc)
|
vendor_user.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(vendor_user)
|
db.refresh(vendor_user)
|
||||||
|
|
||||||
@@ -178,7 +177,7 @@ class TeamService:
|
|||||||
|
|
||||||
# Soft delete
|
# Soft delete
|
||||||
vendor_user.is_active = False
|
vendor_user.is_active = False
|
||||||
vendor_user.updated_at = datetime.now(timezone.utc)
|
vendor_user.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
logger.info(f"Removed user {user_id} from vendor {vendor_id}")
|
logger.info(f"Removed user {user_id} from vendor {vendor_id}")
|
||||||
@@ -189,7 +188,7 @@ class TeamService:
|
|||||||
logger.error(f"Error removing team member: {str(e)}")
|
logger.error(f"Error removing team member: {str(e)}")
|
||||||
raise ValidationException("Failed to remove team member")
|
raise ValidationException("Failed to remove team member")
|
||||||
|
|
||||||
def get_vendor_roles(self, db: Session, vendor_id: int) -> List[Dict[str, Any]]:
|
def get_vendor_roles(self, db: Session, vendor_id: int) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Get available roles for vendor.
|
Get available roles for vendor.
|
||||||
|
|
||||||
|
|||||||
@@ -12,25 +12,23 @@ This module provides classes and functions for:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
from sqlalchemy import and_
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (DNSVerificationException,
|
from app.exceptions import (
|
||||||
DomainAlreadyVerifiedException,
|
DNSVerificationException,
|
||||||
DomainNotVerifiedException,
|
DomainAlreadyVerifiedException,
|
||||||
DomainVerificationFailedException,
|
DomainNotVerifiedException,
|
||||||
InvalidDomainFormatException,
|
DomainVerificationFailedException,
|
||||||
MaxDomainsReachedException,
|
InvalidDomainFormatException,
|
||||||
MultiplePrimaryDomainsException,
|
MaxDomainsReachedException,
|
||||||
ReservedDomainException,
|
ReservedDomainException,
|
||||||
UnauthorizedDomainAccessException,
|
ValidationException,
|
||||||
ValidationException,
|
VendorDomainAlreadyExistsException,
|
||||||
VendorDomainAlreadyExistsException,
|
VendorDomainNotFoundException,
|
||||||
VendorDomainNotFoundException,
|
VendorNotFoundException,
|
||||||
VendorNotFoundException)
|
)
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.database.vendor_domain import VendorDomain
|
from models.database.vendor_domain import VendorDomain
|
||||||
from models.schema.vendor_domain import VendorDomainCreate, VendorDomainUpdate
|
from models.schema.vendor_domain import VendorDomainCreate, VendorDomainUpdate
|
||||||
@@ -135,7 +133,7 @@ class VendorDomainService:
|
|||||||
logger.error(f"Error adding domain: {str(e)}")
|
logger.error(f"Error adding domain: {str(e)}")
|
||||||
raise ValidationException("Failed to add domain")
|
raise ValidationException("Failed to add domain")
|
||||||
|
|
||||||
def get_vendor_domains(self, db: Session, vendor_id: int) -> List[VendorDomain]:
|
def get_vendor_domains(self, db: Session, vendor_id: int) -> list[VendorDomain]:
|
||||||
"""
|
"""
|
||||||
Get all domains for a vendor.
|
Get all domains for a vendor.
|
||||||
|
|
||||||
@@ -272,7 +270,7 @@ class VendorDomainService:
|
|||||||
logger.error(f"Error deleting domain: {str(e)}")
|
logger.error(f"Error deleting domain: {str(e)}")
|
||||||
raise ValidationException("Failed to delete domain")
|
raise ValidationException("Failed to delete domain")
|
||||||
|
|
||||||
def verify_domain(self, db: Session, domain_id: int) -> Tuple[VendorDomain, str]:
|
def verify_domain(self, db: Session, domain_id: int) -> tuple[VendorDomain, str]:
|
||||||
"""
|
"""
|
||||||
Verify domain ownership via DNS TXT record.
|
Verify domain ownership via DNS TXT record.
|
||||||
|
|
||||||
@@ -313,7 +311,7 @@ class VendorDomainService:
|
|||||||
if txt_value == domain.verification_token:
|
if txt_value == domain.verification_token:
|
||||||
# Verification successful
|
# Verification successful
|
||||||
domain.is_verified = True
|
domain.is_verified = True
|
||||||
domain.verified_at = datetime.now(timezone.utc)
|
domain.verified_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(domain)
|
db.refresh(domain)
|
||||||
|
|
||||||
@@ -419,7 +417,7 @@ class VendorDomainService:
|
|||||||
raise ReservedDomainException(domain, first_part)
|
raise ReservedDomainException(domain, first_part)
|
||||||
|
|
||||||
def _unset_primary_domains(
|
def _unset_primary_domains(
|
||||||
self, db: Session, vendor_id: int, exclude_domain_id: Optional[int] = None
|
self, db: Session, vendor_id: int, exclude_domain_id: int | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Unset all primary domains for vendor."""
|
"""Unset all primary domains for vendor."""
|
||||||
query = db.query(VendorDomain).filter(
|
query = db.query(VendorDomain).filter(
|
||||||
|
|||||||
@@ -10,18 +10,20 @@ This module provides classes and functions for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (InvalidVendorDataException,
|
from app.exceptions import (
|
||||||
MarketplaceProductNotFoundException,
|
InvalidVendorDataException,
|
||||||
MaxVendorsReachedException,
|
MarketplaceProductNotFoundException,
|
||||||
ProductAlreadyExistsException,
|
MaxVendorsReachedException,
|
||||||
UnauthorizedVendorAccessException,
|
ProductAlreadyExistsException,
|
||||||
ValidationException, VendorAlreadyExistsException,
|
UnauthorizedVendorAccessException,
|
||||||
VendorNotFoundException)
|
ValidationException,
|
||||||
|
VendorAlreadyExistsException,
|
||||||
|
VendorNotFoundException,
|
||||||
|
)
|
||||||
from models.database.marketplace_product import MarketplaceProduct
|
from models.database.marketplace_product import MarketplaceProduct
|
||||||
from models.database.product import Product
|
from models.database.product import Product
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
@@ -108,7 +110,7 @@ class VendorService:
|
|||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
active_only: bool = True,
|
active_only: bool = True,
|
||||||
verified_only: bool = False,
|
verified_only: bool = False,
|
||||||
) -> Tuple[List[Vendor], int]:
|
) -> tuple[list[Vendor], int]:
|
||||||
"""
|
"""
|
||||||
Get vendors with filtering.
|
Get vendors with filtering.
|
||||||
|
|
||||||
@@ -257,7 +259,7 @@ class VendorService:
|
|||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
active_only: bool = True,
|
active_only: bool = True,
|
||||||
featured_only: bool = False,
|
featured_only: bool = False,
|
||||||
) -> Tuple[List[Product], int]:
|
) -> tuple[list[Product], int]:
|
||||||
"""
|
"""
|
||||||
Get products in vendor catalog with filtering.
|
Get products in vendor catalog with filtering.
|
||||||
|
|
||||||
|
|||||||
@@ -8,20 +8,23 @@ Handles:
|
|||||||
- Role assignment
|
- Role assignment
|
||||||
- Permission management
|
- Permission management
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.core.permissions import get_preset_permissions
|
from app.core.permissions import get_preset_permissions
|
||||||
from app.exceptions import (CannotRemoveOwnerException,
|
from app.exceptions import (
|
||||||
InvalidInvitationTokenException,
|
CannotRemoveOwnerException,
|
||||||
MaxTeamMembersReachedException,
|
InvalidInvitationTokenException,
|
||||||
TeamInvitationAlreadyAcceptedException,
|
MaxTeamMembersReachedException,
|
||||||
TeamMemberAlreadyExistsException,
|
TeamInvitationAlreadyAcceptedException,
|
||||||
UserNotFoundException, VendorNotFoundException)
|
TeamMemberAlreadyExistsException,
|
||||||
|
UserNotFoundException,
|
||||||
|
)
|
||||||
from middleware.auth import AuthManager
|
from middleware.auth import AuthManager
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
from models.database.vendor import Role, Vendor, VendorUser, VendorUserType
|
from models.database.vendor import Role, Vendor, VendorUser, VendorUserType
|
||||||
@@ -43,8 +46,8 @@ class VendorTeamService:
|
|||||||
inviter: User,
|
inviter: User,
|
||||||
email: str,
|
email: str,
|
||||||
role_name: str,
|
role_name: str,
|
||||||
custom_permissions: Optional[List[str]] = None,
|
custom_permissions: list[str] | None = None,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Invite a new team member to a vendor.
|
Invite a new team member to a vendor.
|
||||||
|
|
||||||
@@ -196,9 +199,9 @@ class VendorTeamService:
|
|||||||
db: Session,
|
db: Session,
|
||||||
invitation_token: str,
|
invitation_token: str,
|
||||||
password: str,
|
password: str,
|
||||||
first_name: Optional[str] = None,
|
first_name: str | None = None,
|
||||||
last_name: Optional[str] = None,
|
last_name: str | None = None,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Accept a team invitation and activate account.
|
Accept a team invitation and activate account.
|
||||||
|
|
||||||
@@ -330,7 +333,7 @@ class VendorTeamService:
|
|||||||
vendor: Vendor,
|
vendor: Vendor,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
new_role_name: str,
|
new_role_name: str,
|
||||||
custom_permissions: Optional[List[str]] = None,
|
custom_permissions: list[str] | None = None,
|
||||||
) -> VendorUser:
|
) -> VendorUser:
|
||||||
"""
|
"""
|
||||||
Update a team member's role.
|
Update a team member's role.
|
||||||
@@ -392,7 +395,7 @@ class VendorTeamService:
|
|||||||
db: Session,
|
db: Session,
|
||||||
vendor: Vendor,
|
vendor: Vendor,
|
||||||
include_inactive: bool = False,
|
include_inactive: bool = False,
|
||||||
) -> List[Dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Get all team members for a vendor.
|
Get all team members for a vendor.
|
||||||
|
|
||||||
@@ -445,7 +448,7 @@ class VendorTeamService:
|
|||||||
db: Session,
|
db: Session,
|
||||||
vendor: Vendor,
|
vendor: Vendor,
|
||||||
role_name: str,
|
role_name: str,
|
||||||
custom_permissions: Optional[List[str]] = None,
|
custom_permissions: list[str] | None = None,
|
||||||
) -> Role:
|
) -> Role:
|
||||||
"""Get existing role or create new one with preset/custom permissions."""
|
"""Get existing role or create new one with preset/custom permissions."""
|
||||||
# Try to find existing role with same name
|
# Try to find existing role with same name
|
||||||
@@ -492,7 +495,6 @@ class VendorTeamService:
|
|||||||
# - Vendor name
|
# - Vendor name
|
||||||
# - Inviter name
|
# - Inviter name
|
||||||
# - Expiry date
|
# - Expiry date
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Create service instance
|
# Create service instance
|
||||||
|
|||||||
@@ -8,21 +8,24 @@ Handles theme CRUD operations, preset application, and validation.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.core.theme_presets import (THEME_PRESETS, apply_preset,
|
from app.core.theme_presets import (
|
||||||
get_available_presets, get_preset_preview)
|
THEME_PRESETS,
|
||||||
|
apply_preset,
|
||||||
|
get_available_presets,
|
||||||
|
get_preset_preview,
|
||||||
|
)
|
||||||
from app.exceptions.vendor import VendorNotFoundException
|
from app.exceptions.vendor import VendorNotFoundException
|
||||||
from app.exceptions.vendor_theme import (InvalidColorFormatException,
|
from app.exceptions.vendor_theme import (
|
||||||
InvalidFontFamilyException,
|
InvalidColorFormatException,
|
||||||
InvalidThemeDataException,
|
InvalidFontFamilyException,
|
||||||
ThemeOperationException,
|
ThemeOperationException,
|
||||||
ThemePresetAlreadyAppliedException,
|
ThemePresetNotFoundException,
|
||||||
ThemePresetNotFoundException,
|
ThemeValidationException,
|
||||||
ThemeValidationException,
|
VendorThemeNotFoundException,
|
||||||
VendorThemeNotFoundException)
|
)
|
||||||
from models.database.vendor import Vendor
|
from models.database.vendor import Vendor
|
||||||
from models.database.vendor_theme import VendorTheme
|
from models.database.vendor_theme import VendorTheme
|
||||||
from models.schema.vendor_theme import ThemePresetPreview, VendorThemeUpdate
|
from models.schema.vendor_theme import ThemePresetPreview, VendorThemeUpdate
|
||||||
@@ -77,7 +80,7 @@ class VendorThemeService:
|
|||||||
# THEME RETRIEVAL
|
# THEME RETRIEVAL
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def get_theme(self, db: Session, vendor_code: str) -> Dict:
|
def get_theme(self, db: Session, vendor_code: str) -> dict:
|
||||||
"""
|
"""
|
||||||
Get theme for vendor. Returns default if no custom theme exists.
|
Get theme for vendor. Returns default if no custom theme exists.
|
||||||
|
|
||||||
@@ -107,7 +110,7 @@ class VendorThemeService:
|
|||||||
|
|
||||||
return theme.to_dict()
|
return theme.to_dict()
|
||||||
|
|
||||||
def _get_default_theme(self) -> Dict:
|
def _get_default_theme(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Get default theme configuration.
|
Get default theme configuration.
|
||||||
|
|
||||||
@@ -329,7 +332,7 @@ class VendorThemeService:
|
|||||||
operation="apply_preset", vendor_code=vendor_code, reason=str(e)
|
operation="apply_preset", vendor_code=vendor_code, reason=str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_available_presets(self) -> List[ThemePresetPreview]:
|
def get_available_presets(self) -> list[ThemePresetPreview]:
|
||||||
"""
|
"""
|
||||||
Get list of available theme presets.
|
Get list of available theme presets.
|
||||||
|
|
||||||
@@ -351,7 +354,7 @@ class VendorThemeService:
|
|||||||
# THEME DELETION
|
# THEME DELETION
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def delete_theme(self, db: Session, vendor_code: str) -> Dict:
|
def delete_theme(self, db: Session, vendor_code: str) -> dict:
|
||||||
"""
|
"""
|
||||||
Delete custom theme for vendor (reverts to default).
|
Delete custom theme for vendor (reverts to default).
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# app/tasks/background_tasks.py
|
# app/tasks/background_tasks.py
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from app.core.database import SessionLocal
|
from app.core.database import SessionLocal
|
||||||
from app.utils.csv_processor import CSVProcessor
|
from app.utils.csv_processor import CSVProcessor
|
||||||
@@ -39,13 +39,13 @@ async def process_marketplace_import(
|
|||||||
logger.error(f"Vendor {vendor_id} not found for import job {job_id}")
|
logger.error(f"Vendor {vendor_id} not found for import job {job_id}")
|
||||||
job.status = "failed"
|
job.status = "failed"
|
||||||
job.error_message = f"Vendor {vendor_id} not found"
|
job.error_message = f"Vendor {vendor_id} not found"
|
||||||
job.completed_at = datetime.now(timezone.utc)
|
job.completed_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update job status
|
# Update job status
|
||||||
job.status = "processing"
|
job.status = "processing"
|
||||||
job.started_at = datetime.now(timezone.utc)
|
job.started_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -64,7 +64,7 @@ async def process_marketplace_import(
|
|||||||
|
|
||||||
# Update job with results
|
# Update job with results
|
||||||
job.status = "completed"
|
job.status = "completed"
|
||||||
job.completed_at = datetime.now(timezone.utc)
|
job.completed_at = datetime.now(UTC)
|
||||||
job.imported_count = result["imported"]
|
job.imported_count = result["imported"]
|
||||||
job.updated_count = result["updated"]
|
job.updated_count = result["updated"]
|
||||||
job.error_count = result.get("errors", 0)
|
job.error_count = result.get("errors", 0)
|
||||||
@@ -87,13 +87,13 @@ async def process_marketplace_import(
|
|||||||
try:
|
try:
|
||||||
job.status = "failed"
|
job.status = "failed"
|
||||||
job.error_message = str(e)
|
job.error_message = str(e)
|
||||||
job.completed_at = datetime.now(timezone.utc)
|
job.completed_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as commit_error:
|
except Exception as commit_error:
|
||||||
logger.error(f"Failed to update job status: {commit_error}")
|
logger.error(f"Failed to update job status: {commit_error}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
finally:
|
finally:
|
||||||
if hasattr(db, "close") and callable(getattr(db, "close")):
|
if hasattr(db, "close") and callable(db.close):
|
||||||
try:
|
try:
|
||||||
db.close()
|
db.close()
|
||||||
except Exception as close_error:
|
except Exception as close_error:
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ This module provides classes and functions for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests
|
import requests
|
||||||
@@ -145,7 +145,7 @@ class CSVProcessor:
|
|||||||
logger.info(f"Normalized columns: {list(df.columns)}")
|
logger.info(f"Normalized columns: {list(df.columns)}")
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def _clean_row_data(self, row_data: Dict[str, Any]) -> Dict[str, Any]:
|
def _clean_row_data(self, row_data: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Process a single row with data normalization."""
|
"""Process a single row with data normalization."""
|
||||||
# Handle NaN values
|
# Handle NaN values
|
||||||
processed_data = {k: (v if pd.notna(v) else None) for k, v in row_data.items()}
|
processed_data = {k: (v if pd.notna(v) else None) for k, v in row_data.items()}
|
||||||
@@ -188,7 +188,7 @@ class CSVProcessor:
|
|||||||
|
|
||||||
async def process_marketplace_csv_from_url(
|
async def process_marketplace_csv_from_url(
|
||||||
self, url: str, marketplace: str, vendor_name: str, batch_size: int, db: Session
|
self, url: str, marketplace: str, vendor_name: str, batch_size: int, db: Session
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Process CSV from URL with marketplace and vendor information.
|
Process CSV from URL with marketplace and vendor information.
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ class CSVProcessor:
|
|||||||
vendor_name: str,
|
vendor_name: str,
|
||||||
db: Session,
|
db: Session,
|
||||||
batch_num: int,
|
batch_num: int,
|
||||||
) -> Dict[str, int]:
|
) -> dict[str, int]:
|
||||||
"""Process a batch of CSV rows with marketplace information."""
|
"""Process a batch of CSV rows with marketplace information."""
|
||||||
imported = 0
|
imported = 0
|
||||||
updated = 0
|
updated = 0
|
||||||
@@ -295,7 +295,7 @@ class CSVProcessor:
|
|||||||
existing_product, key
|
existing_product, key
|
||||||
):
|
):
|
||||||
setattr(existing_product, key, value)
|
setattr(existing_product, key, value)
|
||||||
existing_product.updated_at = datetime.now(timezone.utc)
|
existing_product.updated_at = datetime.now(UTC)
|
||||||
updated += 1
|
updated += 1
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Updated product {product_data['marketplace_product_id']} for "
|
f"Updated product {product_data['marketplace_product_id']} for "
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ This module provides classes and functions for:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Optional, Tuple
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ class GTINProcessor:
|
|||||||
|
|
||||||
VALID_LENGTHS = [8, 12, 13, 14] # List of valid GTIN lengths
|
VALID_LENGTHS = [8, 12, 13, 14] # List of valid GTIN lengths
|
||||||
|
|
||||||
def normalize(self, gtin_value: any) -> Optional[str]:
|
def normalize(self, gtin_value: any) -> str | None:
|
||||||
"""
|
"""
|
||||||
Normalize GTIN to proper format.
|
Normalize GTIN to proper format.
|
||||||
|
|
||||||
@@ -55,11 +54,11 @@ class GTINProcessor:
|
|||||||
# Standard lengths - pad appropriately
|
# Standard lengths - pad appropriately
|
||||||
if length == 8:
|
if length == 8:
|
||||||
return gtin_clean.zfill(8) # EAN-8
|
return gtin_clean.zfill(8) # EAN-8
|
||||||
elif length == 12:
|
if length == 12:
|
||||||
return gtin_clean.zfill(12) # UPC-A
|
return gtin_clean.zfill(12) # UPC-A
|
||||||
elif length == 13:
|
if length == 13:
|
||||||
return gtin_clean.zfill(13) # EAN-13
|
return gtin_clean.zfill(13) # EAN-13
|
||||||
elif length == 14:
|
if length == 14:
|
||||||
return gtin_clean.zfill(14) # GTIN-14
|
return gtin_clean.zfill(14) # GTIN-14
|
||||||
|
|
||||||
elif length > 14:
|
elif length > 14:
|
||||||
@@ -111,7 +110,7 @@ class PriceProcessor:
|
|||||||
|
|
||||||
def parse_price_currency(
|
def parse_price_currency(
|
||||||
self, price_str: any
|
self, price_str: any
|
||||||
) -> Tuple[Optional[str], Optional[str]]:
|
) -> tuple[str | None, str | None]:
|
||||||
"""
|
"""
|
||||||
Parse a price string to extract the numeric value and currency.
|
Parse a price string to extract the numeric value and currency.
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def get_db_engine(database_url: str):
|
|||||||
echo=False,
|
echo=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Database engine created for: " f"{database_url.split('@')[0]}@...")
|
logger.info(f"Database engine created for: {database_url.split('@')[0]}@...")
|
||||||
return engine
|
return engine
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
80
main.py
80
main.py
@@ -18,7 +18,7 @@ if sys.platform == "win32":
|
|||||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Request, Response
|
from fastapi import Depends, FastAPI, HTTPException, Request, Response
|
||||||
@@ -35,11 +35,13 @@ from app.core.database import get_db
|
|||||||
from app.core.lifespan import lifespan
|
from app.core.lifespan import lifespan
|
||||||
from app.exceptions import ServiceUnavailableException
|
from app.exceptions import ServiceUnavailableException
|
||||||
from app.exceptions.handler import setup_exception_handlers
|
from app.exceptions.handler import setup_exception_handlers
|
||||||
|
|
||||||
# Import page routers
|
# Import page routers
|
||||||
from app.routes import admin_pages, shop_pages, vendor_pages
|
from app.routes import admin_pages, shop_pages, vendor_pages
|
||||||
from middleware.context import ContextMiddleware
|
from middleware.context import ContextMiddleware
|
||||||
from middleware.logging import LoggingMiddleware
|
from middleware.logging import LoggingMiddleware
|
||||||
from middleware.theme_context import ThemeContextMiddleware
|
from middleware.theme_context import ThemeContextMiddleware
|
||||||
|
|
||||||
# Import REFACTORED class-based middleware
|
# Import REFACTORED class-based middleware
|
||||||
from middleware.vendor_context import VendorContextMiddleware
|
from middleware.vendor_context import VendorContextMiddleware
|
||||||
|
|
||||||
@@ -259,9 +261,8 @@ async def vendor_root_path(
|
|||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
template_path, get_shop_context(request, db=db, page=landing_page)
|
template_path, get_shop_context(request, db=db, page=landing_page)
|
||||||
)
|
)
|
||||||
else:
|
# No landing page - redirect to shop
|
||||||
# No landing page - redirect to shop
|
return RedirectResponse(url=f"/vendors/{vendor_code}/shop/", status_code=302)
|
||||||
return RedirectResponse(url=f"/vendors/{vendor_code}/shop/", status_code=302)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -317,18 +318,17 @@ async def platform_homepage(request: Request, db: Session = Depends(get_db)):
|
|||||||
"footer_pages": footer_pages,
|
"footer_pages": footer_pages,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
# Fallback to default static template
|
||||||
# Fallback to default static template
|
logger.info("[PLATFORM] No CMS homepage found, using default template")
|
||||||
logger.info("[PLATFORM] No CMS homepage found, using default template")
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"platform/homepage-default.html",
|
"platform/homepage-default.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"header_pages": header_pages,
|
"header_pages": header_pages,
|
||||||
"footer_pages": footer_pages,
|
"footer_pages": footer_pages,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/{slug}", response_class=HTMLResponse, include_in_schema=False)
|
@app.get("/{slug}", response_class=HTMLResponse, include_in_schema=False)
|
||||||
@@ -349,7 +349,10 @@ async def platform_content_page(
|
|||||||
|
|
||||||
# Load page from CMS
|
# Load page from CMS
|
||||||
page = content_page_service.get_page_for_vendor(
|
page = content_page_service.get_page_for_vendor(
|
||||||
db, slug=slug, vendor_id=None, include_unpublished=False # Platform pages only
|
db,
|
||||||
|
slug=slug,
|
||||||
|
vendor_id=None,
|
||||||
|
include_unpublished=False, # Platform pages only
|
||||||
)
|
)
|
||||||
|
|
||||||
if not page:
|
if not page:
|
||||||
@@ -430,30 +433,27 @@ async def root(request: Request, db: Session = Depends(get_db)):
|
|||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
template_path, get_shop_context(request, db=db, page=landing_page)
|
template_path, get_shop_context(request, db=db, page=landing_page)
|
||||||
)
|
)
|
||||||
else:
|
# No landing page - redirect to shop
|
||||||
# No landing page - redirect to shop
|
vendor_context = getattr(request.state, "vendor_context", None)
|
||||||
vendor_context = getattr(request.state, "vendor_context", None)
|
access_method = (
|
||||||
access_method = (
|
vendor_context.get("detection_method", "unknown")
|
||||||
vendor_context.get("detection_method", "unknown")
|
if vendor_context
|
||||||
if vendor_context
|
else "unknown"
|
||||||
else "unknown"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if access_method == "path":
|
if access_method == "path":
|
||||||
full_prefix = (
|
full_prefix = (
|
||||||
vendor_context.get("full_prefix", "/vendor/")
|
vendor_context.get("full_prefix", "/vendor/")
|
||||||
if vendor_context
|
if vendor_context
|
||||||
else "/vendor/"
|
else "/vendor/"
|
||||||
)
|
)
|
||||||
return RedirectResponse(
|
return RedirectResponse(
|
||||||
url=f"{full_prefix}{vendor.subdomain}/shop/", status_code=302
|
url=f"{full_prefix}{vendor.subdomain}/shop/", status_code=302
|
||||||
)
|
)
|
||||||
else:
|
# Domain/subdomain
|
||||||
# Domain/subdomain
|
return RedirectResponse(url="/shop/", status_code=302)
|
||||||
return RedirectResponse(url="/shop/", status_code=302)
|
# No vendor - platform root
|
||||||
else:
|
return RedirectResponse(url="/documentation")
|
||||||
# No vendor - platform root
|
|
||||||
return RedirectResponse(url="/documentation")
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
@@ -464,7 +464,7 @@ def health_check(db: Session = Depends(get_db)):
|
|||||||
db.execute(text("SELECT 1"))
|
db.execute(text("SELECT 1"))
|
||||||
return {
|
return {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"timestamp": datetime.now(timezone.utc),
|
"timestamp": datetime.now(UTC),
|
||||||
"message": f"{settings.project_name} v{settings.version}",
|
"message": f"{settings.project_name} v{settings.version}",
|
||||||
"docs": {
|
"docs": {
|
||||||
"swagger": "/docs",
|
"swagger": "/docs",
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ The module uses the following technologies:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta, timezone
|
from collections.abc import Callable
|
||||||
from typing import Any, Callable, Dict, Optional
|
from datetime import UTC, datetime, timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from fastapi.security import HTTPAuthorizationCredentials
|
from fastapi.security import HTTPAuthorizationCredentials
|
||||||
@@ -26,10 +27,14 @@ from jose import jwt
|
|||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.exceptions import (AdminRequiredException,
|
from app.exceptions import (
|
||||||
InsufficientPermissionsException,
|
AdminRequiredException,
|
||||||
InvalidCredentialsException, InvalidTokenException,
|
InsufficientPermissionsException,
|
||||||
TokenExpiredException, UserNotActiveException)
|
InvalidCredentialsException,
|
||||||
|
InvalidTokenException,
|
||||||
|
TokenExpiredException,
|
||||||
|
UserNotActiveException,
|
||||||
|
)
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -97,7 +102,7 @@ class AuthManager:
|
|||||||
|
|
||||||
def authenticate_user(
|
def authenticate_user(
|
||||||
self, db: Session, username: str, password: str
|
self, db: Session, username: str, password: str
|
||||||
) -> Optional[User]:
|
) -> User | None:
|
||||||
"""Authenticate user credentials against the database.
|
"""Authenticate user credentials against the database.
|
||||||
|
|
||||||
Supports authentication using either username or email address.
|
Supports authentication using either username or email address.
|
||||||
@@ -129,7 +134,7 @@ class AuthManager:
|
|||||||
# Authentication successful, return user object
|
# Authentication successful, return user object
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def create_access_token(self, user: User) -> Dict[str, Any]:
|
def create_access_token(self, user: User) -> dict[str, Any]:
|
||||||
"""Create a JWT access token for an authenticated user.
|
"""Create a JWT access token for an authenticated user.
|
||||||
|
|
||||||
The token includes user identity and role information in the payload.
|
The token includes user identity and role information in the payload.
|
||||||
@@ -146,7 +151,7 @@ class AuthManager:
|
|||||||
"""
|
"""
|
||||||
# Calculate token expiration time
|
# Calculate token expiration time
|
||||||
expires_delta = timedelta(minutes=self.token_expire_minutes)
|
expires_delta = timedelta(minutes=self.token_expire_minutes)
|
||||||
expire = datetime.now(timezone.utc) + expires_delta
|
expire = datetime.now(UTC) + expires_delta
|
||||||
|
|
||||||
# Build JWT payload with user information
|
# Build JWT payload with user information
|
||||||
payload = {
|
payload = {
|
||||||
@@ -155,7 +160,7 @@ class AuthManager:
|
|||||||
"email": user.email, # User email address
|
"email": user.email, # User email address
|
||||||
"role": user.role, # User role for authorization
|
"role": user.role, # User role for authorization
|
||||||
"exp": expire, # Expiration time (JWT standard claim)
|
"exp": expire, # Expiration time (JWT standard claim)
|
||||||
"iat": datetime.now(timezone.utc), # Issued at time (JWT standard claim)
|
"iat": datetime.now(UTC), # Issued at time (JWT standard claim)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Encode the payload into a JWT token
|
# Encode the payload into a JWT token
|
||||||
@@ -168,7 +173,7 @@ class AuthManager:
|
|||||||
"expires_in": self.token_expire_minutes * 60, # Convert minutes to seconds
|
"expires_in": self.token_expire_minutes * 60, # Convert minutes to seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
def verify_token(self, token: str) -> Dict[str, Any]:
|
def verify_token(self, token: str) -> dict[str, Any]:
|
||||||
"""Verify and decode a JWT token, returning the user data.
|
"""Verify and decode a JWT token, returning the user data.
|
||||||
|
|
||||||
Validates the token signature, expiration, and required claims.
|
Validates the token signature, expiration, and required claims.
|
||||||
@@ -199,8 +204,8 @@ class AuthManager:
|
|||||||
raise InvalidTokenException("Token missing expiration")
|
raise InvalidTokenException("Token missing expiration")
|
||||||
|
|
||||||
# Check if token has expired (additional check beyond jwt.decode)
|
# Check if token has expired (additional check beyond jwt.decode)
|
||||||
if datetime.now(timezone.utc) > datetime.fromtimestamp(
|
if datetime.now(UTC) > datetime.fromtimestamp(
|
||||||
exp, tz=timezone.utc
|
exp, tz=UTC
|
||||||
):
|
):
|
||||||
raise TokenExpiredException()
|
raise TokenExpiredException()
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ContextManager:
|
|||||||
host = host.split(":")[0]
|
host = host.split(":")[0]
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[CONTEXT] Detecting context",
|
"[CONTEXT] Detecting context",
|
||||||
extra={
|
extra={
|
||||||
"original_path": request.url.path,
|
"original_path": request.url.path,
|
||||||
"clean_path": getattr(request.state, "clean_path", "NOT SET"),
|
"clean_path": getattr(request.state, "clean_path", "NOT SET"),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ This module provides classes and functions for:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
from fastapi import Request, Response
|
from fastapi import Request, Response
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ This module provides classes and functions for:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import UTC, datetime, timedelta
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -21,9 +20,9 @@ class RateLimiter:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Class constructor."""
|
"""Class constructor."""
|
||||||
# Dictionary to store request timestamps for each client
|
# Dictionary to store request timestamps for each client
|
||||||
self.clients: Dict[str, deque] = defaultdict(lambda: deque())
|
self.clients: dict[str, deque] = defaultdict(lambda: deque())
|
||||||
self.cleanup_interval = 3600 # Clean up old entries every hour
|
self.cleanup_interval = 3600 # Clean up old entries every hour
|
||||||
self.last_cleanup = datetime.now(timezone.utc)
|
self.last_cleanup = datetime.now(UTC)
|
||||||
|
|
||||||
def allow_request(
|
def allow_request(
|
||||||
self, client_id: str, max_requests: int, window_seconds: int
|
self, client_id: str, max_requests: int, window_seconds: int
|
||||||
@@ -33,7 +32,7 @@ class RateLimiter:
|
|||||||
|
|
||||||
Uses sliding window algorithm
|
Uses sliding window algorithm
|
||||||
"""
|
"""
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(UTC)
|
||||||
window_start = now - timedelta(seconds=window_seconds)
|
window_start = now - timedelta(seconds=window_seconds)
|
||||||
|
|
||||||
# Clean up old entries periodically
|
# Clean up old entries periodically
|
||||||
@@ -60,7 +59,7 @@ class RateLimiter:
|
|||||||
|
|
||||||
def _cleanup_old_entries(self):
|
def _cleanup_old_entries(self):
|
||||||
"""Clean up old entries to prevent memory leaks."""
|
"""Clean up old entries to prevent memory leaks."""
|
||||||
cutoff_time = datetime.now(timezone.utc) - timedelta(hours=24)
|
cutoff_time = datetime.now(UTC) - timedelta(hours=24)
|
||||||
|
|
||||||
clients_to_remove = []
|
clients_to_remove = []
|
||||||
for client_id, requests in self.clients.items():
|
for client_id, requests in self.clients.items():
|
||||||
@@ -80,11 +79,11 @@ class RateLimiter:
|
|||||||
f"Rate limiter cleanup completed. Removed {len(clients_to_remove)} inactive clients"
|
f"Rate limiter cleanup completed. Removed {len(clients_to_remove)} inactive clients"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_client_stats(self, client_id: str) -> Dict[str, int]:
|
def get_client_stats(self, client_id: str) -> dict[str, int]:
|
||||||
"""Get statistics for a specific client."""
|
"""Get statistics for a specific client."""
|
||||||
client_requests = self.clients.get(client_id, deque())
|
client_requests = self.clients.get(client_id, deque())
|
||||||
|
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(UTC)
|
||||||
hour_ago = now - timedelta(hours=1)
|
hour_ago = now - timedelta(hours=1)
|
||||||
day_ago = now - timedelta(days=1)
|
day_ago = now - timedelta(days=1)
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class ThemeContextMiddleware(BaseHTTPMiddleware):
|
|||||||
request.state.theme = theme
|
request.state.theme = theme
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[THEME] Theme loaded for vendor",
|
"[THEME] Theme loaded for vendor",
|
||||||
extra={
|
extra={
|
||||||
"vendor_id": vendor.id,
|
"vendor_id": vendor.id,
|
||||||
"vendor_name": vendor.name,
|
"vendor_name": vendor.name,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ Also extracts clean_path for nested routing patterns.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
@@ -31,7 +30,7 @@ class VendorContextManager:
|
|||||||
"""Manages vendor context detection for multi-tenant routing."""
|
"""Manages vendor context detection for multi-tenant routing."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def detect_vendor_context(request: Request) -> Optional[dict]:
|
def detect_vendor_context(request: Request) -> dict | None:
|
||||||
"""
|
"""
|
||||||
Detect vendor context from request.
|
Detect vendor context from request.
|
||||||
|
|
||||||
@@ -106,7 +105,7 @@ class VendorContextManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_vendor_from_context(db: Session, context: dict) -> Optional[Vendor]:
|
def get_vendor_from_context(db: Session, context: dict) -> Vendor | None:
|
||||||
"""
|
"""
|
||||||
Get vendor from database using context information.
|
Get vendor from database using context information.
|
||||||
|
|
||||||
@@ -142,11 +141,10 @@ class VendorContextManager:
|
|||||||
f"[OK] Vendor found via custom domain: {domain} → {vendor.name}"
|
f"[OK] Vendor found via custom domain: {domain} → {vendor.name}"
|
||||||
)
|
)
|
||||||
return vendor
|
return vendor
|
||||||
else:
|
logger.warning(
|
||||||
logger.warning(
|
f"No active vendor found for custom domain: {domain}"
|
||||||
f"No active vendor found for custom domain: {domain}"
|
)
|
||||||
)
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
# Method 2 & 3: Subdomain or path-based lookup
|
# Method 2 & 3: Subdomain or path-based lookup
|
||||||
if "subdomain" in context:
|
if "subdomain" in context:
|
||||||
@@ -169,7 +167,7 @@ class VendorContextManager:
|
|||||||
return vendor
|
return vendor
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def extract_clean_path(request: Request, vendor_context: Optional[dict]) -> str:
|
def extract_clean_path(request: Request, vendor_context: dict | None) -> str:
|
||||||
"""
|
"""
|
||||||
Extract clean path without vendor prefix for routing.
|
Extract clean path without vendor prefix for routing.
|
||||||
|
|
||||||
@@ -217,7 +215,7 @@ class VendorContextManager:
|
|||||||
return request.url.path.startswith("/api/v1/shop/")
|
return request.url.path.startswith("/api/v1/shop/")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def extract_vendor_from_referer(request: Request) -> Optional[dict]:
|
def extract_vendor_from_referer(request: Request) -> dict | None:
|
||||||
"""
|
"""
|
||||||
Extract vendor context from Referer header.
|
Extract vendor context from Referer header.
|
||||||
|
|
||||||
@@ -250,7 +248,7 @@ class VendorContextManager:
|
|||||||
referer_host = referer_host.split(":")[0]
|
referer_host = referer_host.split(":")[0]
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[VENDOR] Extracting vendor from Referer",
|
"[VENDOR] Extracting vendor from Referer",
|
||||||
extra={
|
extra={
|
||||||
"referer": referer,
|
"referer": referer,
|
||||||
"referer_host": referer_host,
|
"referer_host": referer_host,
|
||||||
@@ -449,7 +447,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
request.state.clean_path = request.url.path
|
request.state.clean_path = request.url.path
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[VENDOR_CONTEXT] Vendor detected from Referer for shop API",
|
"[VENDOR_CONTEXT] Vendor detected from Referer for shop API",
|
||||||
extra={
|
extra={
|
||||||
"vendor_id": vendor.id,
|
"vendor_id": vendor.id,
|
||||||
"vendor_name": vendor.name,
|
"vendor_name": vendor.name,
|
||||||
@@ -463,7 +461,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"[WARNING] Vendor context from Referer but vendor not found",
|
"[WARNING] Vendor context from Referer but vendor not found",
|
||||||
extra={
|
extra={
|
||||||
"context": vendor_context,
|
"context": vendor_context,
|
||||||
"detection_method": vendor_context.get(
|
"detection_method": vendor_context.get(
|
||||||
@@ -479,7 +477,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
db.close()
|
db.close()
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"[VENDOR] Shop API request without Referer header",
|
"[VENDOR] Shop API request without Referer header",
|
||||||
extra={"path": request.url.path},
|
extra={"path": request.url.path},
|
||||||
)
|
)
|
||||||
request.state.vendor = None
|
request.state.vendor = None
|
||||||
@@ -518,7 +516,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[VENDOR_CONTEXT] Vendor detected",
|
"[VENDOR_CONTEXT] Vendor detected",
|
||||||
extra={
|
extra={
|
||||||
"vendor_id": vendor.id,
|
"vendor_id": vendor.id,
|
||||||
"vendor_name": vendor.name,
|
"vendor_name": vendor.name,
|
||||||
@@ -530,7 +528,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"[WARNING] Vendor context detected but vendor not found",
|
"[WARNING] Vendor context detected but vendor not found",
|
||||||
extra={
|
extra={
|
||||||
"context": vendor_context,
|
"context": vendor_context,
|
||||||
"detection_method": vendor_context.get("detection_method"),
|
"detection_method": vendor_context.get("detection_method"),
|
||||||
@@ -543,7 +541,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
db.close()
|
db.close()
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[VENDOR] No vendor context detected",
|
"[VENDOR] No vendor context detected",
|
||||||
extra={
|
extra={
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"host": request.headers.get("host", ""),
|
"host": request.headers.get("host", ""),
|
||||||
@@ -557,7 +555,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
|
|
||||||
def get_current_vendor(request: Request) -> Optional[Vendor]:
|
def get_current_vendor(request: Request) -> Vendor | None:
|
||||||
"""Helper function to get current vendor from request state."""
|
"""Helper function to get current vendor from request state."""
|
||||||
return getattr(request.state, "vendor", None)
|
return getattr(request.state, "vendor", None)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
# API models (Pydantic) - import the modules, not all classes
|
# API models (Pydantic) - import the modules, not all classes
|
||||||
from . import schema
|
from . import schema
|
||||||
|
|
||||||
# Database models (SQLAlchemy)
|
# Database models (SQLAlchemy)
|
||||||
from .database.base import Base
|
from .database.base import Base
|
||||||
from .database.inventory import Inventory
|
from .database.inventory import Inventory
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
# models/database/__init__.py
|
# models/database/__init__.py
|
||||||
"""Database models package."""
|
"""Database models package."""
|
||||||
from .admin import (AdminAuditLog, AdminNotification, AdminSession,
|
|
||||||
AdminSetting, PlatformAlert)
|
from .admin import (
|
||||||
|
AdminAuditLog,
|
||||||
|
AdminNotification,
|
||||||
|
AdminSession,
|
||||||
|
AdminSetting,
|
||||||
|
PlatformAlert,
|
||||||
|
)
|
||||||
from .base import Base
|
from .base import Base
|
||||||
from .customer import Customer, CustomerAddress
|
from .customer import Customer, CustomerAddress
|
||||||
from .inventory import Inventory
|
from .inventory import Inventory
|
||||||
|
|||||||
@@ -10,8 +10,16 @@ This module provides models for:
|
|||||||
- Platform alerts (system-wide issues)
|
- Platform alerts (system-wide issues)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer,
|
from sqlalchemy import (
|
||||||
String, Text)
|
JSON,
|
||||||
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from sqlalchemy import Column, DateTime
|
from sqlalchemy import Column, DateTime
|
||||||
|
|
||||||
from app.core.database import Base
|
|
||||||
|
|
||||||
|
|
||||||
class TimestampMixin:
|
class TimestampMixin:
|
||||||
"""Mixin to add created_at and updated_at timestamps to models"""
|
"""Mixin to add created_at and updated_at timestamps to models"""
|
||||||
|
|
||||||
created_at = Column(DateTime, default=datetime.now(timezone.utc), nullable=False)
|
created_at = Column(DateTime, default=datetime.now(UTC), nullable=False)
|
||||||
updated_at = Column(
|
updated_at = Column(
|
||||||
DateTime,
|
DateTime,
|
||||||
default=datetime.now(timezone.utc),
|
default=datetime.now(UTC),
|
||||||
onupdate=datetime.now(timezone.utc),
|
onupdate=datetime.now(UTC),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
# models/database/cart.py
|
# models/database/cart.py
|
||||||
"""Cart item database model."""
|
"""Cart item database model."""
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import (Column, Float, ForeignKey, Index, Integer, String,
|
|
||||||
UniqueConstraint)
|
from sqlalchemy import (
|
||||||
|
Column,
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
Index,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
UniqueConstraint,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -14,10 +14,19 @@ Features:
|
|||||||
- Version history support
|
- Version history support
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Index, Integer,
|
from sqlalchemy import (
|
||||||
String, Text, UniqueConstraint)
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
Index,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
UniqueConstraint,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
@@ -77,13 +86,13 @@ class ContentPage(Base):
|
|||||||
# Timestamps
|
# Timestamps
|
||||||
created_at = Column(
|
created_at = Column(
|
||||||
DateTime(timezone=True),
|
DateTime(timezone=True),
|
||||||
default=lambda: datetime.now(timezone.utc),
|
default=lambda: datetime.now(UTC),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
updated_at = Column(
|
updated_at = Column(
|
||||||
DateTime(timezone=True),
|
DateTime(timezone=True),
|
||||||
default=lambda: datetime.now(timezone.utc),
|
default=lambda: datetime.now(UTC),
|
||||||
onupdate=lambda: datetime.now(timezone.utc),
|
onupdate=lambda: datetime.now(UTC),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
from datetime import datetime
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer,
|
from sqlalchemy import (
|
||||||
Numeric, String, Text)
|
JSON,
|
||||||
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
Numeric,
|
||||||
|
String,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# models/database/inventory.py
|
# models/database/inventory.py
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import (Column, ForeignKey, Index, Integer, String,
|
from sqlalchemy import Column, ForeignKey, Index, Integer, String, UniqueConstraint
|
||||||
UniqueConstraint)
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
from sqlalchemy import (Column, DateTime, ForeignKey, Index, Integer, String,
|
from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Text
|
||||||
Text)
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index,
|
from sqlalchemy import (
|
||||||
Integer, String, Text, UniqueConstraint)
|
Column,
|
||||||
|
Index,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
# Import Base from the central database module instead of creating a new one
|
# Import Base from the central database module instead of creating a new one
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
# models/database/order.py
|
# models/database/order.py
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Integer,
|
from sqlalchemy import (
|
||||||
String, Text)
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
# models/database/product.py
|
# models/database/product.py
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import (Boolean, Column, Float, ForeignKey, Index, Integer,
|
from sqlalchemy import (
|
||||||
String, UniqueConstraint)
|
Boolean,
|
||||||
|
Column,
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
Index,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
UniqueConstraint,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ ROLE CLARIFICATION:
|
|||||||
- Vendor-specific roles (manager, staff, etc.) are stored in VendorUser.role
|
- Vendor-specific roles (manager, staff, etc.) are stored in VendorUser.role
|
||||||
- Customers are NOT in the User table - they use the Customer model
|
- Customers are NOT in the User table - they use the Customer model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, DateTime, Enum, Integer, String
|
from sqlalchemy import Boolean, Column, DateTime, Integer, String
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
|
|||||||
@@ -5,13 +5,23 @@ Vendor model representing entities that sell products or services.
|
|||||||
This module defines the Vendor model along with its relationships to
|
This module defines the Vendor model along with its relationships to
|
||||||
other models such as User (owner), Product, Customer, Order, and MarketplaceImportJob.
|
other models such as User (owner), Product, Customer, Order, and MarketplaceImportJob.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer,
|
from sqlalchemy import (
|
||||||
String, Text)
|
JSON,
|
||||||
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
# Import Base from the central database module instead of creating a new one
|
# Import Base from the central database module instead of creating a new one
|
||||||
from app.core.database import Base
|
from app.core.database import Base
|
||||||
from models.database.base import TimestampMixin
|
from models.database.base import TimestampMixin
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user