feat: consolidate media service, add merchant users page, fix metrics overlap
- Merge ImageService into MediaService with WebP variant generation, DB-backed storage stats, and module-driven media usage discovery via new MediaUsageProviderProtocol - Add merchant users admin page with scoped user listing, stats endpoint, template, JS, and i18n strings (de/en/fr/lb) - Fix merchant user metrics so Owners and Team Members are mutually exclusive (filter team_members on user_type="member" and exclude owner IDs) ensuring stat cards add up correctly - Update billing and monitoring services to use media_service - Update subscription-billing and feature-gating docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ def get_all_users(
|
||||
per_page: int = Query(10, ge=1, le=100),
|
||||
search: str = Query("", description="Search by username or email"),
|
||||
role: str = Query("", description="Filter by role"),
|
||||
scope: str = Query("", description="Filter scope: 'merchant' for merchant owners and team members"),
|
||||
is_active: str = Query("", description="Filter by active status"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: UserContext = Depends(get_current_admin_api),
|
||||
@@ -54,11 +55,35 @@ def get_all_users(
|
||||
per_page=per_page,
|
||||
search=search if search else None,
|
||||
role=role if role else None,
|
||||
scope=scope if scope else None,
|
||||
is_active=is_active_bool,
|
||||
)
|
||||
|
||||
if scope == "merchant":
|
||||
items = [
|
||||
UserDetailResponse(
|
||||
id=user.id,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
is_active=user.is_active,
|
||||
last_login=user.last_login,
|
||||
created_at=user.created_at,
|
||||
updated_at=user.updated_at,
|
||||
first_name=user.first_name,
|
||||
last_name=user.last_name,
|
||||
full_name=user.full_name,
|
||||
is_email_verified=user.is_email_verified,
|
||||
owned_merchants_count=len(user.owned_merchants) if user.owned_merchants else 0,
|
||||
store_memberships_count=len(user.store_memberships) if user.store_memberships else 0,
|
||||
)
|
||||
for user in users
|
||||
]
|
||||
else:
|
||||
items = [UserResponse.model_validate(user) for user in users]
|
||||
|
||||
return UserListResponse(
|
||||
items=[UserResponse.model_validate(user) for user in users],
|
||||
items=items,
|
||||
total=total,
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
@@ -161,6 +186,42 @@ def get_user_statistics(
|
||||
return stats
|
||||
|
||||
|
||||
@admin_platform_users_router.get("/merchant-stats")
|
||||
def get_merchant_user_statistics(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: UserContext = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get merchant user statistics for admin dashboard.
|
||||
|
||||
Uses the stats_aggregator to get merchant user metrics from the tenancy
|
||||
module's MetricsProvider.
|
||||
"""
|
||||
platform_id = _get_platform_id(request, current_admin)
|
||||
|
||||
metrics = stats_aggregator.get_admin_dashboard_stats(db=db, platform_id=platform_id)
|
||||
tenancy_metrics = metrics.get("tenancy", [])
|
||||
|
||||
stats = {
|
||||
"merchant_users_total": 0,
|
||||
"merchant_users_active": 0,
|
||||
"merchant_owners": 0,
|
||||
"merchant_team_members": 0,
|
||||
}
|
||||
|
||||
for metric in tenancy_metrics:
|
||||
if metric.key == "tenancy.merchant_users_total":
|
||||
stats["merchant_users_total"] = int(metric.value)
|
||||
elif metric.key == "tenancy.merchant_users_active":
|
||||
stats["merchant_users_active"] = int(metric.value)
|
||||
elif metric.key == "tenancy.merchant_owners":
|
||||
stats["merchant_owners"] = int(metric.value)
|
||||
elif metric.key == "tenancy.merchant_team_members":
|
||||
stats["merchant_team_members"] = int(metric.value)
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
@admin_platform_users_router.get("/search", response_model=UserSearchResponse)
|
||||
def search_users(
|
||||
q: str = Query(..., min_length=2, description="Search query (username or email)"),
|
||||
|
||||
Reference in New Issue
Block a user