Files
orion/app/modules/customers/routes/api/admin.py
Samir Boulahtit 9a0dd84035 fix: make FrontendType mandatory in require_module_access
The require_module_access dependency was using path-based detection to
determine admin vs vendor authentication, which failed for API routes
(/api/v1/admin/*) because it only checked for /admin/*.

Changes:
- Make frontend_type parameter mandatory (was optional with fallback)
- Remove path-based detection logic from require_module_access
- Update all 33 module route files to pass explicit FrontendType:
  - 15 admin routes use FrontendType.ADMIN
  - 18 vendor routes use FrontendType.VENDOR

This ensures authentication method is explicitly declared at route
definition time, making it independent of URL structure and future-proof
for API version changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 22:09:21 +01:00

124 lines
4.1 KiB
Python

# app/modules/customers/routes/api/admin.py
"""
Customer management endpoints for admin.
Provides admin-level access to customer data across all vendors.
"""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api, require_module_access
from app.core.database import get_db
from app.modules.customers.services import admin_customer_service
from app.modules.enums import FrontendType
from models.schema.auth import UserContext
from app.modules.customers.schemas import (
CustomerDetailResponse,
CustomerListResponse,
CustomerMessageResponse,
CustomerStatisticsResponse,
)
# Create module-aware router
admin_router = APIRouter(
prefix="/customers",
dependencies=[Depends(require_module_access("customers", FrontendType.ADMIN))],
)
# ============================================================================
# List Customers
# ============================================================================
@admin_router.get("", response_model=CustomerListResponse)
def list_customers(
vendor_id: int | None = Query(None, description="Filter by vendor ID"),
search: str = Query("", description="Search by email, name, or customer number"),
is_active: bool | None = Query(None, description="Filter by active status"),
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
db: Session = Depends(get_db),
current_admin: UserContext = Depends(get_current_admin_api),
) -> CustomerListResponse:
"""
Get paginated list of customers across all vendors.
Admin can filter by vendor, search, and active status.
"""
customers, total = admin_customer_service.list_customers(
db=db,
vendor_id=vendor_id,
search=search if search else None,
is_active=is_active,
skip=skip,
limit=limit,
)
# Calculate pagination values
page = (skip // limit) + 1 if limit > 0 else 1
per_page = limit
total_pages = (total + limit - 1) // limit if limit > 0 else 1
return CustomerListResponse(
customers=customers,
total=total,
page=page,
per_page=per_page,
total_pages=total_pages,
)
# ============================================================================
# Customer Statistics
# ============================================================================
@admin_router.get("/stats", response_model=CustomerStatisticsResponse)
def get_customer_stats(
vendor_id: int | None = Query(None, description="Filter by vendor ID"),
db: Session = Depends(get_db),
current_admin: UserContext = Depends(get_current_admin_api),
) -> CustomerStatisticsResponse:
"""Get customer statistics."""
stats = admin_customer_service.get_customer_stats(db=db, vendor_id=vendor_id)
return CustomerStatisticsResponse(**stats)
# ============================================================================
# Get Single Customer
# ============================================================================
@admin_router.get("/{customer_id}", response_model=CustomerDetailResponse)
def get_customer(
customer_id: int,
db: Session = Depends(get_db),
current_admin: UserContext = Depends(get_current_admin_api),
) -> CustomerDetailResponse:
"""Get customer details by ID."""
customer = admin_customer_service.get_customer(db=db, customer_id=customer_id)
return CustomerDetailResponse(**customer)
# ============================================================================
# Toggle Customer Status
# ============================================================================
@admin_router.patch("/{customer_id}/toggle-status", response_model=CustomerMessageResponse)
def toggle_customer_status(
customer_id: int,
db: Session = Depends(get_db),
current_admin: UserContext = Depends(get_current_admin_api),
) -> CustomerMessageResponse:
"""Toggle customer active status."""
result = admin_customer_service.toggle_customer_status(
db=db,
customer_id=customer_id,
admin_email=current_admin.email,
)
db.commit()
return CustomerMessageResponse(message=result["message"])