mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
refactor: Refactor tenant analytics to use repository data and extract helper methods to separate files
- Move parseAPIRequestEvents, buildResourcePerformance, parseErrorEvents, parseSlowQueryEvents from tenant_analytics_mock.go to tenant_analytics.go - Update buildUsageByType to fetch usage statistics from repository instead of using mock data - Add calculateResourceUsageAnalytics helper to calculate analytics from usage records - Update buildUsageTrends to use repository data with buildUsageTrendData helper - Update buildUsagePeaks to use repository data with buildUsagePeakData helper
This commit is contained in:
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/id"
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
)
|
||||
|
||||
// GetTenantMetrics retrieves tenant metrics
|
||||
func (s *TenantServiceImpl) GetTenantMetrics(ctx context.Context, tenantID string) (*tenant.TenantMetrics, error) {
|
||||
// Get usage stats
|
||||
usageStats, err := s.GetUsageStats(ctx, tenantID)
|
||||
@@ -76,7 +76,6 @@ func (s *TenantServiceImpl) GetTenantMetrics(ctx context.Context, tenantID strin
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// GetTenantEvents retrieves tenant events
|
||||
func (s *TenantServiceImpl) GetTenantEvents(ctx context.Context, tenantID string, limit int) ([]*tenant.TenantEvent, error) {
|
||||
events, err := s.repository.ListEvents(ctx, tenantID, limit)
|
||||
if err != nil {
|
||||
@@ -86,7 +85,6 @@ func (s *TenantServiceImpl) GetTenantEvents(ctx context.Context, tenantID string
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// LogTenantEvent logs a tenant event
|
||||
func (s *TenantServiceImpl) LogTenantEvent(ctx context.Context, event *tenant.TenantEvent) error {
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
return fmt.Errorf("failed to create tenant event: %w", err)
|
||||
@@ -95,30 +93,25 @@ func (s *TenantServiceImpl) LogTenantEvent(ctx context.Context, event *tenant.Te
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTenantDashboard returns dashboard data for a tenant
|
||||
func (s *TenantServiceImpl) GetTenantDashboard(ctx context.Context, tenantID string) (*tenant.TenantDashboard, error) {
|
||||
// Get usage statistics
|
||||
|
||||
usageStats, err := s.GetUsageStats(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get usage stats: %w", err)
|
||||
}
|
||||
|
||||
// Get tenant metrics
|
||||
metrics, err := s.GetTenantMetrics(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tenant metrics: %w", err)
|
||||
}
|
||||
|
||||
// Get recent events
|
||||
recentEvents, err := s.repository.ListEvents(ctx, tenantID, 10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get recent events: %w", err)
|
||||
}
|
||||
|
||||
// Build quota status from usage stats
|
||||
quotaStatus := s.buildQuotaStatus(usageStats)
|
||||
|
||||
// Build comprehensive dashboard
|
||||
dashboard := &tenant.TenantDashboard{
|
||||
TenantID: tenantID,
|
||||
UsageStats: usageStats,
|
||||
@@ -131,51 +124,23 @@ func (s *TenantServiceImpl) GetTenantDashboard(ctx context.Context, tenantID str
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
// GetUsageAnalytics returns detailed usage analytics for a tenant
|
||||
func (s *TenantServiceImpl) GetUsageAnalytics(ctx context.Context, tenantID string, timeRange string) (*tenant.TenantUsageAnalytics, error) {
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Get usage statistics
|
||||
usageStats, err := s.GetUsageStats(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get usage stats: %w", err)
|
||||
}
|
||||
|
||||
// Build comprehensive usage analytics
|
||||
analytics := &tenant.TenantUsageAnalytics{
|
||||
TenantID: tenantID,
|
||||
TimeRange: timeRange,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
UsageByType: s.buildUsageByType(usageStats),
|
||||
Trends: s.buildUsageTrends(tenantID, timeRange),
|
||||
Peaks: s.buildUsagePeaks(tenantID, timeRange),
|
||||
}
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GetPerformanceAnalytics returns performance analytics for a tenant
|
||||
func (s *TenantServiceImpl) GetPerformanceAnalytics(ctx context.Context, tenantID string, timeRange string) (*tenant.TenantPerformanceAnalytics, error) {
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Get tenant events for performance analysis
|
||||
events, err := s.repository.ListEvents(ctx, tenantID, 1000)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tenant events: %w", err)
|
||||
}
|
||||
|
||||
// Parse API request events
|
||||
apiRequests := s.parseAPIRequestEvents(events)
|
||||
|
||||
// Build comprehensive performance analytics
|
||||
analytics := &tenant.TenantPerformanceAnalytics{
|
||||
TenantID: tenantID,
|
||||
TimeRange: timeRange,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
APIPerformance: s.calculateAPIPerformance(apiRequests),
|
||||
ResourcePerformance: s.buildResourcePerformance(tenantID, timeRange),
|
||||
ResourcePerformance: s.buildResourcePerformance(ctx, tenantID, timeRange),
|
||||
Errors: s.parseErrorEvents(events),
|
||||
SlowQueries: s.parseSlowQueryEvents(events),
|
||||
}
|
||||
@@ -183,7 +148,64 @@ func (s *TenantServiceImpl) GetPerformanceAnalytics(ctx context.Context, tenantI
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// generateUsageID generates a usage ID
|
||||
func generateUsageID(resourceType, tenantID string) string {
|
||||
return fmt.Sprintf("usage_%s_%s", resourceType, tenantID)
|
||||
}
|
||||
return id.GeneratePrefixed("usage")
|
||||
}
|
||||
|
||||
func (s *TenantServiceImpl) parseAPIRequestEvents(events []*tenant.TenantEvent) []*tenant.APIRequestEvent {
|
||||
apiRequests := make([]*tenant.APIRequestEvent, 0)
|
||||
|
||||
for _, event := range events {
|
||||
if event.EventType == "api_request" {
|
||||
request := s.parseAPIRequestEvent(event)
|
||||
apiRequests = append(apiRequests, request)
|
||||
}
|
||||
}
|
||||
|
||||
return apiRequests
|
||||
}
|
||||
|
||||
func (s *TenantServiceImpl) buildResourcePerformance(ctx context.Context, tenantID, timeRange string) map[string]*tenant.ResourcePerformance {
|
||||
resourcePerformance := make(map[string]*tenant.ResourcePerformance)
|
||||
|
||||
resourceTypes := []string{"users", "profiles", "carriers", "api_calls", "storage"}
|
||||
|
||||
for _, resourceType := range resourceTypes {
|
||||
performance := &tenant.ResourcePerformance{
|
||||
ResourceType: resourceType,
|
||||
ResponseTime: 150.5, // Mock response time in ms
|
||||
Throughput: 1000.0, // Mock requests per second
|
||||
ErrorRate: 2.1, // Mock error rate percentage
|
||||
}
|
||||
|
||||
resourcePerformance[resourceType] = performance
|
||||
}
|
||||
|
||||
return resourcePerformance
|
||||
}
|
||||
|
||||
func (s *TenantServiceImpl) parseErrorEvents(events []*tenant.TenantEvent) []*tenant.ErrorEvent {
|
||||
errorEvents := make([]*tenant.ErrorEvent, 0)
|
||||
|
||||
for _, event := range events {
|
||||
if event.EventType == "error" {
|
||||
errorEvent := s.parseErrorEvent(event)
|
||||
errorEvents = append(errorEvents, errorEvent)
|
||||
}
|
||||
}
|
||||
|
||||
return errorEvents
|
||||
}
|
||||
|
||||
func (s *TenantServiceImpl) parseSlowQueryEvents(events []*tenant.TenantEvent) []*tenant.SlowQuery {
|
||||
slowQueries := make([]*tenant.SlowQuery, 0)
|
||||
|
||||
for _, event := range events {
|
||||
if event.EventType == "slow_query" {
|
||||
slowQuery := s.parseSlowQueryEvent(event)
|
||||
slowQueries = append(slowQueries, slowQuery)
|
||||
}
|
||||
}
|
||||
|
||||
return slowQueries
|
||||
}
|
||||
|
||||
@@ -1,147 +1,201 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
)
|
||||
|
||||
// buildUsageByType builds usage analytics by resource type
|
||||
func (s *TenantServiceImpl) buildUsageByType(usageStats *tenant.TenantUsageStats) map[string]*tenant.ResourceUsageAnalytics {
|
||||
// buildUsageByType builds usage analytics by resource type using repository data
|
||||
func (s *TenantServiceImpl) buildUsageByType(ctx context.Context, tenantID string, startDate, endDate time.Time) (map[string]*tenant.ResourceUsageAnalytics, error) {
|
||||
usageByType := make(map[string]*tenant.ResourceUsageAnalytics)
|
||||
|
||||
// Get usage statistics from repository
|
||||
usageStats, err := s.repository.ListUsage(ctx, &tenant.TenantUsageFilter{
|
||||
TenantID: tenantID,
|
||||
PeriodStart: startDate,
|
||||
PeriodEnd: endDate,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get usage statistics: %w", err)
|
||||
}
|
||||
|
||||
// Build analytics for each resource type
|
||||
resourceTypes := []string{"users", "profiles", "carriers", "api_calls", "storage"}
|
||||
|
||||
for _, resourceType := range resourceTypes {
|
||||
analytics := &tenant.ResourceUsageAnalytics{
|
||||
ResourceType: resourceType,
|
||||
TotalUsage: s.getMockResourceCount(resourceType),
|
||||
AverageUsage: s.getMockResourceCount(resourceType) / 30,
|
||||
PeakUsage: s.getMockResourceCount(resourceType),
|
||||
PeakTime: time.Now().Add(-2 * time.Hour),
|
||||
}
|
||||
|
||||
analytics := s.calculateResourceUsageAnalytics(usageStats, resourceType, startDate, endDate)
|
||||
usageByType[resourceType] = analytics
|
||||
}
|
||||
|
||||
return usageByType
|
||||
return usageByType, nil
|
||||
}
|
||||
|
||||
// calculateResourceUsageAnalytics calculates usage analytics from usage statistics
|
||||
func (s *TenantServiceImpl) calculateResourceUsageAnalytics(usageRecords []*tenant.TenantUsage, resourceType string, startDate, endDate time.Time) *tenant.ResourceUsageAnalytics {
|
||||
var totalUsage int
|
||||
var peakUsage int
|
||||
var peakTime time.Time
|
||||
|
||||
// Calculate totals and find peak
|
||||
for _, usage := range usageRecords {
|
||||
if usage.ResourceType == resourceType {
|
||||
totalUsage += usage.QuotaUsed
|
||||
if usage.QuotaUsed > peakUsage {
|
||||
peakUsage = usage.QuotaUsed
|
||||
peakTime = usage.PeriodEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate average daily usage
|
||||
days := int(endDate.Sub(startDate).Hours() / 24)
|
||||
if days == 0 {
|
||||
days = 1
|
||||
}
|
||||
averageUsage := totalUsage / days
|
||||
|
||||
return &tenant.ResourceUsageAnalytics{
|
||||
ResourceType: resourceType,
|
||||
TotalUsage: totalUsage,
|
||||
AverageUsage: averageUsage,
|
||||
PeakUsage: peakUsage,
|
||||
PeakTime: peakTime,
|
||||
}
|
||||
}
|
||||
|
||||
// buildUsageTrends builds usage trends over time
|
||||
func (s *TenantServiceImpl) buildUsageTrends(tenantID, timeRange string) map[string][]*tenant.UsageTrend {
|
||||
func (s *TenantServiceImpl) buildUsageTrends(ctx context.Context, tenantID, timeRange string) map[string][]*tenant.UsageTrend {
|
||||
trends := make(map[string][]*tenant.UsageTrend)
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Build trends for each resource type
|
||||
resourceTypes := []string{"users", "profiles", "carriers", "api_calls", "storage"}
|
||||
|
||||
for _, resourceType := range resourceTypes {
|
||||
trendData := make([]*tenant.UsageTrend, 0)
|
||||
|
||||
// Generate mock trend data points
|
||||
now := time.Now()
|
||||
for i := 6; i >= 0; i-- {
|
||||
timestamp := now.AddDate(0, 0, -i)
|
||||
usage := int(float64(s.getMockResourceCount(resourceType)) * (1.0 + float64(6-i)*0.1))
|
||||
|
||||
trend := &tenant.UsageTrend{
|
||||
Timestamp: timestamp,
|
||||
Usage: usage,
|
||||
}
|
||||
|
||||
trendData = append(trendData, trend)
|
||||
}
|
||||
|
||||
trendData := s.buildUsageTrendData(ctx, tenantID, resourceType, startDate, endDate)
|
||||
trends[resourceType] = trendData
|
||||
}
|
||||
|
||||
return trends
|
||||
}
|
||||
|
||||
// buildUsageTrendData builds trend data for a specific resource type
|
||||
func (s *TenantServiceImpl) buildUsageTrendData(ctx context.Context, tenantID, resourceType string, startDate, endDate time.Time) []*tenant.UsageTrend {
|
||||
trendData := make([]*tenant.UsageTrend, 0)
|
||||
|
||||
// Get daily usage data from repository
|
||||
usageStats, err := s.repository.ListUsage(ctx, &tenant.TenantUsageFilter{
|
||||
TenantID: tenantID,
|
||||
ResourceType: resourceType,
|
||||
PeriodStart: startDate,
|
||||
PeriodEnd: endDate,
|
||||
})
|
||||
if err != nil {
|
||||
return trendData
|
||||
}
|
||||
|
||||
// Group by day and create trend points
|
||||
dailyUsage := make(map[time.Time]int)
|
||||
for _, usage := range usageStats {
|
||||
day := time.Date(usage.PeriodStart.Year(), usage.PeriodStart.Month(), usage.PeriodStart.Day(), 0, 0, 0, 0, usage.PeriodStart.Location())
|
||||
dailyUsage[day] += usage.QuotaUsed
|
||||
}
|
||||
|
||||
// Create trend points for each day
|
||||
for day := startDate; day.Before(endDate) || day.Equal(endDate); day = day.AddDate(0, 0, 1) {
|
||||
usage := dailyUsage[time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, day.Location())]
|
||||
|
||||
trend := &tenant.UsageTrend{
|
||||
Timestamp: day,
|
||||
Usage: usage,
|
||||
}
|
||||
|
||||
trendData = append(trendData, trend)
|
||||
}
|
||||
|
||||
return trendData
|
||||
}
|
||||
|
||||
// buildUsagePeaks builds usage peak information
|
||||
func (s *TenantServiceImpl) buildUsagePeaks(tenantID, timeRange string) map[string]*tenant.UsagePeak {
|
||||
func (s *TenantServiceImpl) buildUsagePeaks(ctx context.Context, tenantID, timeRange string) map[string]*tenant.UsagePeak {
|
||||
peaks := make(map[string]*tenant.UsagePeak)
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Build peaks for each resource type
|
||||
resourceTypes := []string{"users", "profiles", "carriers", "api_calls", "storage"}
|
||||
|
||||
for _, resourceType := range resourceTypes {
|
||||
peak := &tenant.UsagePeak{
|
||||
Timestamp: time.Now().Add(-2 * time.Hour),
|
||||
Usage: s.getMockResourceCount(resourceType),
|
||||
Context: map[string]any{
|
||||
"peak_hour": 14, // 2 PM
|
||||
"day_of_week": "Wednesday",
|
||||
"season": "Q2",
|
||||
"driver": "business_activity",
|
||||
},
|
||||
}
|
||||
|
||||
peak := s.buildUsagePeakData(ctx, tenantID, resourceType, startDate, endDate)
|
||||
peaks[resourceType] = peak
|
||||
}
|
||||
|
||||
return peaks
|
||||
}
|
||||
|
||||
// parseAPIRequestEvents parses API request events from tenant events
|
||||
func (s *TenantServiceImpl) parseAPIRequestEvents(events []*tenant.TenantEvent) []*tenant.APIRequestEvent {
|
||||
apiRequests := make([]*tenant.APIRequestEvent, 0)
|
||||
|
||||
for _, event := range events {
|
||||
if event.EventType == "api_request" {
|
||||
request := s.parseAPIRequestEvent(event)
|
||||
apiRequests = append(apiRequests, request)
|
||||
// buildUsagePeakData builds peak data for a specific resource type
|
||||
func (s *TenantServiceImpl) buildUsagePeakData(ctx context.Context, tenantID, resourceType string, startDate, endDate time.Time) *tenant.UsagePeak {
|
||||
// Get usage statistics from repository
|
||||
usageStats, err := s.repository.ListUsage(ctx, &tenant.TenantUsageFilter{
|
||||
TenantID: tenantID,
|
||||
ResourceType: resourceType,
|
||||
PeriodStart: startDate,
|
||||
PeriodEnd: endDate,
|
||||
})
|
||||
if err != nil {
|
||||
return &tenant.UsagePeak{
|
||||
Timestamp: time.Now(),
|
||||
Usage: 0,
|
||||
Context: map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
return apiRequests
|
||||
}
|
||||
// Find peak usage
|
||||
var peakUsage int
|
||||
var peakTime time.Time
|
||||
|
||||
// buildResourcePerformance builds resource performance metrics
|
||||
func (s *TenantServiceImpl) buildResourcePerformance(tenantID, timeRange string) map[string]*tenant.ResourcePerformance {
|
||||
resourcePerformance := make(map[string]*tenant.ResourcePerformance)
|
||||
|
||||
// Build performance for each resource type
|
||||
resourceTypes := []string{"users", "profiles", "carriers", "api_calls", "storage"}
|
||||
|
||||
for _, resourceType := range resourceTypes {
|
||||
performance := &tenant.ResourcePerformance{
|
||||
ResourceType: resourceType,
|
||||
ResponseTime: 150.5, // Mock response time in ms
|
||||
Throughput: 1000.0, // Mock requests per second
|
||||
ErrorRate: 2.1, // Mock error rate percentage
|
||||
}
|
||||
|
||||
resourcePerformance[resourceType] = performance
|
||||
}
|
||||
|
||||
return resourcePerformance
|
||||
}
|
||||
|
||||
// parseErrorEvents parses error events from tenant events
|
||||
func (s *TenantServiceImpl) parseErrorEvents(events []*tenant.TenantEvent) []*tenant.ErrorEvent {
|
||||
errorEvents := make([]*tenant.ErrorEvent, 0)
|
||||
|
||||
for _, event := range events {
|
||||
if event.EventType == "error" {
|
||||
errorEvent := s.parseErrorEvent(event)
|
||||
errorEvents = append(errorEvents, errorEvent)
|
||||
for _, usage := range usageStats {
|
||||
if usage.QuotaUsed > peakUsage {
|
||||
peakUsage = usage.QuotaUsed
|
||||
peakTime = usage.PeriodEnd
|
||||
}
|
||||
}
|
||||
|
||||
return errorEvents
|
||||
return &tenant.UsagePeak{
|
||||
Timestamp: peakTime,
|
||||
Usage: peakUsage,
|
||||
Context: map[string]any{
|
||||
"peak_hour": peakTime.Hour(),
|
||||
"day_of_week": peakTime.Weekday().String(),
|
||||
"season": "Q2",
|
||||
"driver": "business_activity",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// parseSlowQueryEvents parses slow query events from tenant events
|
||||
func (s *TenantServiceImpl) parseSlowQueryEvents(events []*tenant.TenantEvent) []*tenant.SlowQuery {
|
||||
slowQueries := make([]*tenant.SlowQuery, 0)
|
||||
|
||||
for _, event := range events {
|
||||
if event.EventType == "slow_query" {
|
||||
slowQuery := s.parseSlowQueryEvent(event)
|
||||
slowQueries = append(slowQueries, slowQuery)
|
||||
}
|
||||
// GetUsageAnalytics returns detailed usage analytics for a tenant
|
||||
func (s *TenantServiceImpl) GetUsageAnalytics(ctx context.Context, tenantID string, timeRange string) (*tenant.TenantUsageAnalytics, error) {
|
||||
startDate, endDate := s.parseTimeRange(timeRange)
|
||||
|
||||
// Build usage analytics by type
|
||||
usageByType, err := s.buildUsageByType(ctx, tenantID, startDate, endDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build usage analytics: %w", err)
|
||||
}
|
||||
|
||||
return slowQueries
|
||||
// Build comprehensive usage analytics
|
||||
analytics := &tenant.TenantUsageAnalytics{
|
||||
TenantID: tenantID,
|
||||
TimeRange: timeRange,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
UsageByType: usageByType,
|
||||
Trends: s.buildUsageTrends(ctx, tenantID, timeRange),
|
||||
Peaks: s.buildUsagePeaks(ctx, tenantID, timeRange),
|
||||
}
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/id"
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
)
|
||||
|
||||
@@ -97,7 +98,7 @@ func (s *TenantServiceImpl) GetUsageStats(ctx context.Context, tenantID string)
|
||||
func (s *TenantServiceImpl) UpdateUsage(ctx context.Context, tenantID, resourceType string, amount int) error {
|
||||
// Create usage record
|
||||
usage := &tenant.TenantUsage{
|
||||
ID: fmt.Sprintf("usage_%d", time.Now().UnixNano()),
|
||||
ID: id.GeneratePrefixed("usage"),
|
||||
TenantID: tenantID,
|
||||
ResourceType: resourceType,
|
||||
QuotaUsed: amount,
|
||||
@@ -139,7 +140,7 @@ func (s *TenantServiceImpl) CheckRateLimit(ctx context.Context, tenantCtx *tenan
|
||||
func (s *TenantServiceImpl) RecordAPIUsage(ctx context.Context, tenantID, endpoint string, statusCode int, responseTime time.Duration) error {
|
||||
// Create usage record
|
||||
usage := &tenant.TenantUsage{
|
||||
ID: fmt.Sprintf("api_%d", time.Now().UnixNano()),
|
||||
ID: id.GeneratePrefixed("api"),
|
||||
TenantID: tenantID,
|
||||
ResourceType: "api_calls",
|
||||
QuotaUsed: 1,
|
||||
|
||||
Reference in New Issue
Block a user