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:
2026-02-07 21:17:11 +01:00
parent 4cb2bda575
commit 2250054ba2
30 changed files with 1220 additions and 805 deletions

View File

@@ -43,6 +43,13 @@ def _get_feature_provider():
return catalog_feature_provider
def _get_media_usage_provider():
"""Lazy import of media usage provider to avoid circular imports."""
from app.modules.catalog.services.product_media_service import product_media_service
return product_media_service
# Catalog module definition
catalog_module = ModuleDefinition(
code="catalog",
@@ -122,6 +129,7 @@ catalog_module = ModuleDefinition(
# Metrics provider for dashboard statistics
metrics_provider=_get_metrics_provider,
feature_provider=_get_feature_provider,
media_usage_provider=_get_media_usage_provider,
)

View File

@@ -231,6 +231,26 @@ class ProductMediaService:
"total_count": len(associations),
}
def get_media_usage(self, db: Session, media_id: int) -> list[dict]:
"""Implement MediaUsageProviderProtocol.
Returns list of usage records for a given media file across products.
"""
rows = (
db.query(ProductMedia)
.filter(ProductMedia.media_id == media_id)
.all()
)
return [
{
"entity_type": "product",
"entity_id": r.product_id,
"entity_name": r.product.name if r.product else "Unknown",
"usage_type": r.usage_type,
}
for r in rows
]
def set_main_image(
self,
db: Session,