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

@@ -20,7 +20,7 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api
from app.core.database import get_db
from app.modules.contracts.widgets import ListWidget
from app.modules.contracts.widgets import BreakdownWidget, ListWidget
from app.modules.core.schemas.dashboard import (
AdminDashboardResponse,
ImportStatsResponse,
@@ -219,29 +219,37 @@ def get_comprehensive_stats(
"/stats/marketplace", response_model=list[MarketplaceStatsResponse]
)
def get_marketplace_stats(
request: Request,
db: Session = Depends(get_db),
current_admin: UserContext = Depends(get_current_admin_api),
):
"""Get statistics broken down by marketplace (Admin only)."""
# For detailed marketplace breakdown, we still use the analytics service
# as the MetricsProvider pattern is for aggregated stats
try:
from app.modules.analytics.services.stats_service import stats_service
"""Get statistics broken down by marketplace (Admin only).
marketplace_stats = stats_service.get_marketplace_breakdown_stats(db=db)
return [
MarketplaceStatsResponse(
marketplace=stat["marketplace"],
total_products=stat["total_products"],
unique_vendors=stat["unique_vendors"],
unique_brands=stat["unique_brands"],
)
for stat in marketplace_stats
]
except ImportError:
# Analytics module not available
logger.warning("Analytics module not available for marketplace breakdown stats")
return []
Uses the widget_aggregator to get marketplace breakdown data from the
marketplace module's WidgetProvider, avoiding cross-module violations.
"""
platform_id = _get_platform_id(request, current_admin)
# Get widgets from marketplace module via aggregator
widgets = widget_aggregator.get_admin_dashboard_widgets(db=db, platform_id=platform_id)
# Extract marketplace breakdown widget
marketplace_widgets = widgets.get("marketplace", [])
for widget in marketplace_widgets:
if widget.key == "marketplace.breakdown" and isinstance(widget.data, BreakdownWidget):
# Transform breakdown items to MarketplaceStatsResponse
return [
MarketplaceStatsResponse(
marketplace=item.label,
total_products=int(item.value),
unique_vendors=int(item.secondary_value or 0),
unique_brands=0, # Not included in breakdown widget
)
for item in widget.data.items
]
# No breakdown widget found
return []
@admin_dashboard_router.get("/stats/platform", response_model=PlatformStatsResponse)

View File

@@ -20,8 +20,8 @@ from app.core.config import settings as app_settings
from app.core.database import get_db
from app.exceptions import ResourceNotFoundException
from app.modules.tenancy.exceptions import ConfirmationRequiredException
from app.modules.monitoring.services.admin_audit_service import admin_audit_service
from app.modules.core.services.admin_settings_service import admin_settings_service
from app.modules.core.services.audit_aggregator import audit_aggregator
from models.schema.auth import UserContext
from app.modules.tenancy.schemas.admin import (
AdminSettingCreate,
@@ -116,7 +116,7 @@ def create_setting(
)
# Log action
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="create_setting",
@@ -147,7 +147,7 @@ def update_setting(
)
# Log action
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="update_setting",
@@ -176,7 +176,7 @@ def upsert_setting(
)
# Log action
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="upsert_setting",
@@ -233,7 +233,7 @@ def set_rows_per_page(
db=db, setting_data=setting_data, admin_user_id=current_admin.id
)
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="update_setting",
@@ -288,7 +288,7 @@ def delete_setting(
)
# Log action
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="delete_setting",
@@ -586,7 +586,7 @@ def update_email_settings(
updated_keys.append(field)
# Log action
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="update_email_settings",
@@ -625,7 +625,7 @@ def reset_email_settings(
deleted_count += 1
# Log action
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="reset_email_settings",
@@ -688,7 +688,7 @@ def send_test_email(
# Check if email was actually sent (send_raw returns EmailLog, not boolean)
if email_log.status == "sent":
# Log action
admin_audit_service.log_action(
audit_aggregator.log(
db=db,
admin_user_id=current_admin.id,
action="send_test_email",