package app import ( "context" "github.com/geo-platform/tenant-api/internal/shared/auth" "github.com/geo-platform/tenant-api/internal/shared/response" "github.com/geo-platform/tenant-api/internal/tenant/repository" ) type DashboardOverviewResponse struct { TotalSubscriberTenants int64 `json:"total_subscriber_tenants"` MonthNewSubscriptions int64 `json:"month_new_subscriptions"` TotalUsage int64 `json:"total_usage"` MonthUsage int64 `json:"month_usage"` FailureCount int64 `json:"failure_count"` FailureRate float64 `json:"failure_rate"` } type DashboardPackageStat struct { PackageID int64 `json:"package_id"` PackageName string `json:"package_name"` SubscriberTenants int64 `json:"subscriber_tenants"` UsageCount int64 `json:"usage_count"` FailureRate float64 `json:"failure_rate"` } type DashboardTrendPoint struct { Date string `json:"date"` Subscriptions int64 `json:"subscriptions"` Usages int64 `json:"usages"` } type KolDashboardService struct { profileSvc *KolProfileService pkgRepo repository.KolPackageRepository subRepo repository.KolSubscriptionRepository usageRepo repository.KolUsageRepository } func NewKolDashboardService( profileSvc *KolProfileService, pkgRepo repository.KolPackageRepository, subRepo repository.KolSubscriptionRepository, usageRepo repository.KolUsageRepository, ) *KolDashboardService { return &KolDashboardService{ profileSvc: profileSvc, pkgRepo: pkgRepo, subRepo: subRepo, usageRepo: usageRepo, } } func (s *KolDashboardService) Overview(ctx context.Context, actor auth.Actor) (*DashboardOverviewResponse, error) { profile, err := s.profileSvc.RequireActiveKol(ctx, actor) if err != nil { return nil, err } packageIDs, err := s.pkgRepo.ListPackageIDsByProfile(ctx, actor.TenantID, profile.ID) if err != nil { return nil, response.ErrInternal(50120, "kol_dashboard_package_query_failed", err.Error()) } if len(packageIDs) == 0 { return &DashboardOverviewResponse{}, nil } overview, err := s.usageRepo.DashboardOverview(ctx, packageIDs) if err != nil { return nil, response.ErrInternal(50121, "kol_dashboard_overview_query_failed", err.Error()) } totalUsage := maxDashboardBase(overview.TotalUsage) return &DashboardOverviewResponse{ TotalSubscriberTenants: overview.TotalSubs, MonthNewSubscriptions: overview.MonthSubs, TotalUsage: overview.TotalUsage, MonthUsage: overview.MonthUsage, FailureCount: overview.FailureCount, FailureRate: float64(overview.FailureCount) / float64(totalUsage), }, nil } func (s *KolDashboardService) PerPackage(ctx context.Context, actor auth.Actor) ([]DashboardPackageStat, error) { profile, err := s.profileSvc.RequireActiveKol(ctx, actor) if err != nil { return nil, err } packages, err := s.pkgRepo.ListByProfile(ctx, actor.TenantID, profile.ID) if err != nil { return nil, response.ErrInternal(50120, "kol_dashboard_package_query_failed", err.Error()) } if len(packages) == 0 { return []DashboardPackageStat{}, nil } packageIDs := make([]int64, 0, len(packages)) for _, pkg := range packages { packageIDs = append(packageIDs, pkg.ID) } usageStats, err := s.usageRepo.PerPackageStats(ctx, packageIDs) if err != nil { return nil, response.ErrInternal(50122, "kol_dashboard_package_stats_query_failed", err.Error()) } usageByPackage := make(map[int64]repository.KolUsagePackageStats, len(usageStats)) for _, stat := range usageStats { usageByPackage[stat.PackageID] = stat } result := make([]DashboardPackageStat, 0, len(packages)) for _, pkg := range packages { subscribers, err := s.subRepo.ListActiveSubscribersForPackage(ctx, pkg.ID) if err != nil { return nil, response.ErrInternal(50123, "kol_dashboard_subscriber_query_failed", err.Error()) } subscriberTenants := uniqueSubscriberTenants(subscribers) usage := usageByPackage[pkg.ID] result = append(result, DashboardPackageStat{ PackageID: pkg.ID, PackageName: pkg.Name, SubscriberTenants: subscriberTenants, UsageCount: usage.Total, FailureRate: float64(usage.Failed) / float64(maxDashboardBase(usage.Total)), }) } return result, nil } func (s *KolDashboardService) Trend(ctx context.Context, actor auth.Actor, periodDays int) ([]DashboardTrendPoint, error) { profile, err := s.profileSvc.RequireActiveKol(ctx, actor) if err != nil { return nil, err } packageIDs, err := s.pkgRepo.ListPackageIDsByProfile(ctx, actor.TenantID, profile.ID) if err != nil { return nil, response.ErrInternal(50120, "kol_dashboard_package_query_failed", err.Error()) } if len(packageIDs) == 0 { return []DashboardTrendPoint{}, nil } points, err := s.usageRepo.DashboardTrend(ctx, packageIDs, periodDays) if err != nil { return nil, response.ErrInternal(50124, "kol_dashboard_trend_query_failed", err.Error()) } result := make([]DashboardTrendPoint, 0, len(points)) for _, point := range points { result = append(result, DashboardTrendPoint{ Date: point.Day.Format("2006-01-02"), Subscriptions: point.Subscriptions, Usages: point.Usages, }) } return result, nil } func maxDashboardBase(total int64) int64 { if total <= 0 { return 1 } return total } func uniqueSubscriberTenants(subscribers []repository.KolSubscription) int64 { if len(subscribers) == 0 { return 0 } seen := make(map[int64]struct{}, len(subscribers)) for _, subscriber := range subscribers { seen[subscriber.TenantID] = struct{}{} } return int64(len(seen)) } func ValidateKolDashboardPeriodDays(periodDays int) error { switch periodDays { case 7, 30, 90: return nil default: return response.ErrBadRequest(40073, "kol_dashboard_period_invalid", "period_days must be one of 7, 30, 90") } }