refactor: fix architecture violations with provider patterns and dependency inversion

Major changes:
- Add AuditProvider protocol for cross-module audit logging
- Move customer order operations to orders module (dependency inversion)
- Add customer order metrics via MetricsProvider pattern
- Fix missing db parameter in get_admin_context() calls
- Move ProductMedia relationship to catalog module (proper ownership)
- Add marketplace breakdown stats to marketplace_widgets

New files:
- contracts/audit.py - AuditProviderProtocol
- core/services/audit_aggregator.py - Aggregates audit providers
- monitoring/services/audit_provider.py - Monitoring audit implementation
- orders/services/customer_order_service.py - Customer order operations
- orders/routes/api/vendor_customer_orders.py - Customer order endpoints
- catalog/services/product_media_service.py - Product media service
- Architecture documentation for patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 21:32:32 +01:00
parent bd43e21940
commit 39dff4ab7d
34 changed files with 2751 additions and 407 deletions

View File

@@ -9,11 +9,12 @@ by the global exception handler.
import logging
from fastapi import APIRouter, Body, Depends, Path, Query
from fastapi import APIRouter, Body, Depends, Path, Query, Request
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api
from app.core.database import get_db
from app.modules.core.services.stats_aggregator import stats_aggregator
from app.modules.tenancy.services.admin_service import admin_service
from models.schema.auth import UserContext
from models.schema.auth import (
@@ -104,25 +105,60 @@ def create_user(
)
def _get_platform_id(request: Request, current_admin: UserContext) -> int:
"""Get platform_id from available sources."""
# From JWT token
if current_admin.token_platform_id:
return current_admin.token_platform_id
# From request state
platform = getattr(request.state, "platform", None)
if platform:
return platform.id
# First accessible platform
if current_admin.accessible_platform_ids:
return current_admin.accessible_platform_ids[0]
# Fallback
return 1
@admin_platform_users_router.get("/stats")
def get_user_statistics(
request: Request,
db: Session = Depends(get_db),
current_admin: UserContext = Depends(get_current_admin_api),
):
"""Get user statistics for admin dashboard (Admin only)."""
try:
from app.modules.analytics.services.stats_service import stats_service
"""Get user statistics for admin dashboard (Admin only).
return stats_service.get_user_statistics(db)
except ImportError:
# Analytics module not available - return empty stats
logger.warning("Analytics module not available for user statistics")
return {
"total_users": 0,
"active_users": 0,
"inactive_users": 0,
"admin_users": 0,
}
Uses the stats_aggregator to get user metrics from the tenancy module's
MetricsProvider, ensuring no cross-module import violations.
"""
platform_id = _get_platform_id(request, current_admin)
# Get metrics from stats_aggregator (tenancy module provides user stats)
metrics = stats_aggregator.get_admin_dashboard_stats(db=db, platform_id=platform_id)
# Extract user stats from tenancy metrics
tenancy_metrics = metrics.get("tenancy", [])
# Build response from metric values
stats = {
"total_users": 0,
"active_users": 0,
"inactive_users": 0,
"admin_users": 0,
}
for metric in tenancy_metrics:
if metric.key == "tenancy.total_users":
stats["total_users"] = int(metric.value)
elif metric.key == "tenancy.active_users":
stats["active_users"] = int(metric.value)
elif metric.key == "tenancy.inactive_users":
stats["inactive_users"] = int(metric.value)
elif metric.key == "tenancy.admin_users":
stats["admin_users"] = int(metric.value)
return stats
@admin_platform_users_router.get("/search", response_model=UserSearchResponse)