refactor: modernize code quality tooling with Ruff

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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