diff --git a/README.md b/README.md index 03f9735..1ad16bb 100644 --- a/README.md +++ b/README.md @@ -70,16 +70,9 @@ The platform is built as a microservices architecture with the following core co **Developer Tools:** - **CLI**: Command-line interface for service orchestration, configuration, and health checks - **Web Dashboard**: Next.js-based management interface for network operations -- **Multi-Language SDKs**: Client libraries for Go, Python, TypeScript, Kotlin, Ruby, Swift, Rust, and Elixir +- **TypeScript SDK**: Client library for integrating with TaaS APIs - **Kubernetes Operators**: Custom resources for deploying and managing TaaS services -**Analytics & Intelligence:** -- **Churn Analysis**: ML-powered customer churn prediction with risk scoring and retention recommendations -- **Fraud Detection**: Real-time fraud detection for account takeover, subscription fraud, payment fraud, and SIM swap attacks -- **Market Analytics**: Market penetration analysis, competitor tracking, and growth opportunity identification -- **Predictive Maintenance**: Infrastructure health monitoring with failure prediction and maintenance scheduling -- **Pricing Optimization**: Dynamic pricing strategies for revenue maximization, market share, and churn reduction - ### Key Features **API Gateway & Security:** @@ -213,15 +206,6 @@ telecom-platform/ | |-- charging-engine/ # Rust: OCS Real-time Credit Control | |-- packet-gateway/ # Rust: eBPF UPF Data Plane | |-- web-dashboard/ # TypeScript: Next.js Frontend -|-- sdk/ -| |-- go/ # Go SDK -| |-- python/ # Python SDK -| |-- typescript/ # TypeScript SDK -| |-- kotlin/ # Kotlin SDK -| |-- ruby/ # Ruby SDK -| |-- swift/ # Swift SDK -| |-- rust/ # Rust SDK -| |-- elixir/ # Elixir SDK |-- libs/ | |-- shared-ts-sdk/ # TypeScript: Drop-in Widget SDK | |-- proto/ # Shared Protobufs @@ -232,7 +216,6 @@ telecom-platform/ | |-- traefik.yml # Static configuration | |-- dynamic/ # Dynamic middleware config |-- docs/ # Architecture & API docs -| |-- sdk-usage.md # Multi-language SDK documentation | |-- gateway-quickstart.md # API Gateway guide | |-- api-gateway.md # Gateway implementation details |-- scripts/ # Automation scripts @@ -287,7 +270,6 @@ pnpm dev ## Documentation - **[API Documentation](./docs/api-spec.yaml)**: OpenAPI 3.0 specification -- **[SDK Documentation](./docs/sdk-usage.md)**: Multi-language SDK usage guide - **[Building 5G Networks](./docs/building-5g-with-taas.md)**: Complete 5G deployment guide - **[Airalo & eSIM Analysis](./docs/airalo-esim-operator-analysis.md)**: Commercial use case analysis - **[Gateway Quickstart](./docs/gateway-quickstart.md)**: API Gateway setup and configuration @@ -348,172 +330,6 @@ The API Gateway provides: For detailed setup, see [Gateway Quickstart Guide](./docs/gateway-quickstart.md) -## Platform Architecture & Components - -### **API Gateway Layer** -- **Traefik API Gateway**: Centralized entry point providing SSL termination, rate limiting, authentication, and request routing -- **Unified HTTPS Endpoint**: All services accessible via `https://api.telecom.com` -- **Security Middleware**: JWT authentication, security headers, compression, and retry logic -- **Monitoring Dashboard**: Real-time metrics and service health visualization - -### **Core Network Services** - -#### **API Server (Go/Gin)** -- **Purpose**: Central BSS (Business Support System) API -- **Features**: Authentication, subscriber management, automation, plugin system -- **Architecture**: Microservices with Gin framework, PostgreSQL, Redis caching -- **Key Modules**: Handlers for analytics, payments, monitoring, RBAC, websockets - -#### **Carrier Connector (Go/Gin)** -- **Purpose**: ES2+ interface for eSIM profile management and carrier integration -- **Features**: Multi-carrier aggregation, GSMA ES2+ standards compliance, real-time eSIM provisioning -- **Architecture**: GORM for database, ES2+ client, message queue integration -- **Key Modules**: Pricing optimization, security (fraud detection), rate plans, MVNO support - -#### **Charging Engine (Rust/Axum)** -- **Purpose**: Real-time credit control, usage tracking, and billing -- **Features**: Redis-backed rate limiting, PostgreSQL for rate plans, circuit breakers -- **Architecture**: High-performance Rust with tokio async runtime -- **Key Modules**: Charging handlers, authentication, monitoring, rating plans - -#### **Packet Gateway (Rust/eBPF)** -- **Purpose**: High-performance packet processing for network traffic routing and QoS enforcement -- **Features**: eBPF-accelerated packet processing for line-rate throughput - -### **Supporting Infrastructure** -- **PostgreSQL**: Persistent data storage for subscribers, automations, configuration -- **Redis**: Distributed caching, rate limiting, session management -- **MongoDB**: Document storage for 5G core network data -- **RabbitMQ**: Asynchronous event-driven communication -- **Consul**: Service discovery and health checking -- **Vault**: Secure secret management - -### **Frontend Applications** - -#### **Web Dashboard (Next.js/TypeScript)** -- **Purpose**: Management interface for network operations -- **Features**: Real-time dashboard, subscriber management, analytics, pricing optimization -- **Architecture**: React components, Tailwind CSS, API integration -- **Key Pages**: Dashboard, analytics, pricing, subscribers, system health - -### **SDK Ecosystem** - -Multi-language SDKs for developer integration: -- **Swift**: iOS/macOS applications with async/await support -- **Python**: Backend integration and automation -- **TypeScript**: Web applications and Node.js backends -- **Go**: Microservices and CLI tools -- **Kotlin**: Android applications -- **Rust**: High-performance systems -- **Elixir**: Phoenix applications -- **Ruby**: Rails integration - -### **Analytics & Intelligence** - -#### **Advanced Analytics Modules** -1. **Churn Analysis**: ML-powered customer churn prediction with risk scoring -2. **Fraud Detection**: Real-time fraud detection (account takeover, subscription fraud, SIM swap attacks) -3. **Market Analytics**: Market penetration analysis, competitor tracking -4. **Predictive Maintenance**: Infrastructure health monitoring with failure prediction -5. **Pricing Optimization**: Dynamic pricing strategies with elasticity calculations - -#### **Pricing Optimization System** -- **Strategies**: Revenue maximization, market share, profit margin, competitive positioning, churn reduction -- **Advanced Calculations**: - - Dynamic elasticity based on rate plan characteristics - - Competitive index with seasonal market analysis - - ROI calculation with period-based adjustments -- **Implementation**: Go services with mathematical modeling and bounded realistic values - -### **Commercial Applications** - -#### **eSIM Operators (Airalo-style)** -- Multi-carrier aggregation across 400+ global carriers -- Real-time eSIM provisioning via GSMA ES2+ standards -- Usage-based billing with global rate plans -- B2B2C model for MVNO partnerships - -#### **Enterprise Private Networks** -- Industrial IoT and manufacturing connectivity -- Campus networks for universities and hospitals -- Critical infrastructure communications -- Secure data sovereignty deployments - -#### **Telecom Service Providers** -- MVNO enablement platform -- Network slicing as a service -- Edge computing integration -- 5G core network hosting - -### **Data Flow Architecture** - -``` -Client Applications → Traefik Gateway → API Services → Backend Services - ↓ - Authentication & Rate Limiting - ↓ - Message Queue (RabbitMQ) for Async Events - ↓ - Database Layer (PostgreSQL, Redis, MongoDB) -``` - -### **Key Features Summary** - -- **Sovereignty & Security**: Full data sovereignty, end-to-end encryption, RBAC -- **Performance**: eBPF-accelerated packet processing, Redis-backed caching -- **Scalability**: Microservices architecture, horizontal scaling -- **Developer Experience**: Multi-language SDKs, comprehensive documentation -- **Enterprise Ready**: Monitoring, backup, security, compliance features - -The platform represents a **complete telecom stack** for modern cellular network operations, combining carrier-grade reliability with cloud-native architecture and advanced analytics capabilities. - -## API Endpoints - -### Analytics API - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/analytics/churn/predict` | Predict churn for a profile | -| GET | `/api/v1/analytics/churn/metrics` | Get churn metrics | -| GET | `/api/v1/analytics/churn/at-risk` | Get at-risk customers | -| GET | `/api/v1/analytics/market/metrics` | Get market metrics | -| GET | `/api/v1/analytics/market/competitors` | Get competitor analysis | -| GET | `/api/v1/analytics/market/opportunities` | Get market opportunities | -| GET | `/api/v1/analytics/maintenance/metrics` | Get maintenance metrics | -| GET | `/api/v1/analytics/maintenance/assets` | Get assets health | -| GET | `/api/v1/analytics/maintenance/alerts` | Get maintenance alerts | -| POST | `/api/v1/analytics/maintenance/predict/:asset_id` | Predict asset failure | -| GET | `/api/v1/analytics/pricing/metrics` | Get pricing metrics | -| POST | `/api/v1/analytics/pricing/optimize` | Optimize pricing | -| GET | `/api/v1/analytics/pricing/elasticity` | Get price elasticity | - -### Security API - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/security/fraud/analyze` | Analyze transaction for fraud | -| POST | `/api/v1/security/fraud/alerts` | Get fraud alerts | -| PUT | `/api/v1/security/fraud/alerts/:id` | Update alert status | -| GET | `/api/v1/security/fraud/metrics` | Get fraud metrics | -| GET | `/api/v1/security/fraud/patterns` | Get fraud patterns | -| POST | `/api/v1/security/simswap/verify` | Verify SIM swap | -| GET | `/api/v1/security/simswap/history/:profile_id` | Get SIM swap history | - -### Currency & Billing API - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/currency/convert` | Convert currency | -| GET | `/api/v1/currency/exchange/:from/:to` | Get exchange rate | -| GET | `/api/v1/currency/exchange/:from/:to/history` | Get exchange rate history | -| GET | `/api/v1/currency/currencies` | List supported currencies | -| POST | `/api/v1/currency/exchange/refresh` | Refresh exchange rates | -| POST | `/api/v1/currency/billing` | Process billing | -| GET | `/api/v1/currency/billing/history/:profile_id` | Get billing history | -| GET | `/api/v1/currency/billing/summary/:profile_id` | Get billing summary | -| POST | `/api/v1/currency/billing/refund/:transaction_id` | Process refund | -| GET | `/api/v1/currency/billing/analytics` | Get billing analytics | - ## Environment Variables Create `.env` files in each service directory: diff --git a/apps/api-server/cmd/server_routes.go b/apps/api-server/cmd/server_routes.go index 6184404..37b4c0b 100644 --- a/apps/api-server/cmd/server_routes.go +++ b/apps/api-server/cmd/server_routes.go @@ -5,7 +5,6 @@ import ( swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" - "github.com/nutcas3/telecom-platform/apps/api-server/internal/handlers" "github.com/nutcas3/telecom-platform/apps/api-server/internal/middleware" "github.com/nutcas3/telecom-platform/apps/api-server/internal/rbac" ) @@ -74,9 +73,6 @@ func registerV1Routes(router *gin.Engine, d *serverDeps) { registerBillingRoutes(apiProtected, d) registerConfigRoutes(apiProtected, d) registerChaosRoutes(apiProtected, d) - registerAnalyticsRoutes(apiProtected) - registerSecurityRoutes(apiProtected) - registerCurrencyRoutes(apiProtected) } } @@ -199,68 +195,3 @@ func registerSubscriberRoutes(api *gin.RouterGroup, d *serverDeps) { w.POST("/:id/suspend", d.subscriberH.SuspendSubscriber) w.POST("/:id/terminate", d.subscriberH.TerminateSubscriber) } - -func registerAnalyticsRoutes(api *gin.RouterGroup) { - h := handlers.NewAnalyticsHandler() - analytics := api.Group("/analytics") - - // Churn Analysis - churn := analytics.Group("/churn") - churn.POST("/predict", h.PredictChurn) - churn.GET("/metrics", h.GetChurnMetrics) - churn.GET("/at-risk", h.GetAtRiskCustomers) - - // Market Analytics - market := analytics.Group("/market") - market.GET("/metrics", h.GetMarketMetrics) - market.GET("/competitors", h.GetCompetitors) - market.GET("/opportunities", h.GetMarketOpportunities) - - // Predictive Maintenance - maintenance := analytics.Group("/maintenance") - maintenance.GET("/metrics", h.GetMaintenanceMetrics) - maintenance.GET("/assets", h.GetAssetsHealth) - maintenance.GET("/alerts", h.GetMaintenanceAlerts) - maintenance.POST("/predict/:asset_id", h.PredictFailure) - - // Pricing Optimization - pricing := analytics.Group("/pricing") - pricing.GET("/metrics", h.GetPricingMetrics) - pricing.POST("/optimize", h.OptimizePricing) - pricing.GET("/elasticity", h.GetPriceElasticity) -} - -func registerSecurityRoutes(api *gin.RouterGroup) { - h := handlers.NewSecurityHandler() - security := api.Group("/security") - - // Fraud Detection - fraud := security.Group("/fraud") - fraud.POST("/analyze", h.AnalyzeTransaction) - fraud.POST("/alerts", h.GetFraudAlerts) - fraud.PUT("/alerts/:id", h.UpdateAlertStatus) - fraud.GET("/metrics", h.GetFraudMetrics) - fraud.GET("/patterns", h.GetFraudPatterns) - - // SIM Swap Protection - simswap := security.Group("/simswap") - simswap.POST("/verify", h.VerifySIMSwap) - simswap.GET("/history/:profile_id", h.GetSIMSwapHistory) -} - -func registerCurrencyRoutes(api *gin.RouterGroup) { - h := handlers.NewCurrencyHandler() - currencyGroup := api.Group("/currency") - - currencyGroup.POST("/convert", h.ConvertCurrency) - currencyGroup.GET("/exchange/:from/:to", h.GetExchangeRate) - currencyGroup.GET("/exchange/:from/:to/history", h.GetExchangeRateHistory) - currencyGroup.GET("/currencies", h.GetSupportedCurrencies) - currencyGroup.POST("/exchange/refresh", h.RefreshExchangeRates) - - currencyGroup.POST("/billing", h.ProcessBilling) - currencyGroup.GET("/billing/history/:profile_id", h.GetBillingHistory) - currencyGroup.GET("/billing/summary/:profile_id", h.GetBillingSummary) - currencyGroup.POST("/billing/refund/:transaction_id", h.ProcessRefund) - currencyGroup.GET("/billing/analytics", h.GetBillingAnalytics) -} diff --git a/apps/api-server/internal/handlers/analytics_handlers.go b/apps/api-server/internal/handlers/analytics_handlers.go deleted file mode 100644 index b34419d..0000000 --- a/apps/api-server/internal/handlers/analytics_handlers.go +++ /dev/null @@ -1,169 +0,0 @@ -package handlers - -import ( - "net/http" - "strconv" - "time" - - "github.com/gin-gonic/gin" -) - -// AnalyticsHandler handles analytics-related HTTP requests -type AnalyticsHandler struct{} - -// NewAnalyticsHandler creates a new analytics handler -func NewAnalyticsHandler() *AnalyticsHandler { - return &AnalyticsHandler{} -} - -// ChurnPrediction represents a churn prediction response -type ChurnPrediction struct { - ProfileID string `json:"profile_id"` - RiskLevel string `json:"risk_level"` - RiskScore float64 `json:"risk_score"` - PredictedChurnDate string `json:"predicted_churn_date,omitempty"` - Reasons []string `json:"reasons"` - Recommendations []string `json:"recommendations"` - LastUpdated time.Time `json:"last_updated"` -} - -// ChurnMetrics represents churn metrics -type ChurnMetrics struct { - Period string `json:"period"` - TotalSubscribers int64 `json:"total_subscribers"` - ChurnedSubscribers int64 `json:"churned_subscribers"` - ChurnRate float64 `json:"churn_rate_pct"` - MonthlyChurnRate float64 `json:"monthly_churn_rate_pct"` - AnnualChurnRate float64 `json:"annual_churn_rate_pct"` - AverageTenureDays float64 `json:"average_tenure_days"` - RiskDistribution map[string]int64 `json:"risk_distribution"` - GeneratedAt time.Time `json:"generated_at"` -} - -// PredictChurn predicts churn for a profile -func (h *AnalyticsHandler) PredictChurn(c *gin.Context) { - var req struct { - ProfileID string `json:"profile_id" binding:"required"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Simulated churn prediction - prediction := ChurnPrediction{ - ProfileID: req.ProfileID, - RiskLevel: "medium", - RiskScore: 45.5, - PredictedChurnDate: time.Now().AddDate(0, 2, 0).Format("2006-01-02"), - Reasons: []string{ - "Decreased usage over last 30 days", - "No recent plan upgrades", - "Support tickets increased", - }, - Recommendations: []string{ - "Offer loyalty discount", - "Proactive customer outreach", - "Personalized plan recommendation", - }, - LastUpdated: time.Now(), - } - - c.JSON(http.StatusOK, prediction) -} - -// GetAtRiskCustomers returns customers at risk of churning -func (h *AnalyticsHandler) GetAtRiskCustomers(c *gin.Context) { - riskLevel := c.DefaultQuery("risk_level", "high") - limitStr := c.DefaultQuery("limit", "100") - limit, _ := strconv.Atoi(limitStr) - if limit > 1000 { - limit = 1000 - } - - // Simulated at-risk customers - customers := make([]ChurnPrediction, 0) - for i := 0; i < min(limit, 10); i++ { - customers = append(customers, ChurnPrediction{ - ProfileID: "profile-" + strconv.Itoa(i+1), - RiskLevel: riskLevel, - RiskScore: 70.0 + float64(i)*2, - PredictedChurnDate: time.Now().AddDate(0, 0, 30+i*7).Format("2006-01-02"), - Reasons: []string{"Low engagement", "Billing issues"}, - Recommendations: []string{"Retention offer", "Account review"}, - LastUpdated: time.Now(), - }) - } - - c.JSON(http.StatusOK, customers) -} - -// MarketMetrics represents market analytics -type MarketMetrics struct { - Period string `json:"period"` - TotalMarketSize int64 `json:"total_market_size"` - OurSubscribers int64 `json:"our_subscribers"` - MarketShare float64 `json:"market_share_pct"` - GrowthRate float64 `json:"growth_rate_pct"` - ByCountry map[string]any `json:"by_country"` - GeneratedAt time.Time `json:"generated_at"` -} - -// GetMarketMetrics returns market penetration metrics -func (h *AnalyticsHandler) GetMarketMetrics(c *gin.Context) { - period := c.DefaultQuery("period", "monthly") - - metrics := MarketMetrics{ - Period: period, - TotalMarketSize: 5500000000, // Global mobile subscribers ~5.5B - OurSubscribers: 150000, - MarketShare: 0.0027, - GrowthRate: 12.5, - ByCountry: map[string]any{ - "US": map[string]any{ - "market_size": 330000000, - "our_subs": 45000, - "penetration": 0.014, - "growth_rate": 8.5, - }, - "UK": map[string]any{ - "market_size": 67000000, - "our_subs": 25000, - "penetration": 0.037, - "growth_rate": 15.2, - }, - "DE": map[string]any{ - "market_size": 83000000, - "our_subs": 30000, - "penetration": 0.036, - "growth_rate": 11.8, - }, - }, - GeneratedAt: time.Now(), - } - - c.JSON(http.StatusOK, metrics) -} - -// MaintenanceMetrics represents predictive maintenance metrics -type MaintenanceMetrics struct { - Period string `json:"period"` - TotalAssets int64 `json:"total_assets"` - HealthyAssets int64 `json:"healthy_assets"` - AssetsNeedingAttention int64 `json:"assets_needing_attention"` - Uptime float64 `json:"uptime_pct"` - MeanTimeToFailure float64 `json:"mean_time_to_failure_hours"` - MeanTimeToRepair float64 `json:"mean_time_to_repair_hours"` - GeneratedAt time.Time `json:"generated_at"` -} - -// PricingMetrics represents pricing optimization metrics -type PricingMetrics struct { - Period string `json:"period"` - TotalRevenue float64 `json:"total_revenue"` - ARPU float64 `json:"arpu"` - PriceElasticity float64 `json:"price_elasticity"` - CompetitiveIndex float64 `json:"competitive_index"` - OptimizationROI float64 `json:"optimization_roi_pct"` - GeneratedAt time.Time `json:"generated_at"` -} diff --git a/apps/api-server/internal/handlers/analytics_handlers_markets.go b/apps/api-server/internal/handlers/analytics_handlers_markets.go deleted file mode 100644 index 9214dbe..0000000 --- a/apps/api-server/internal/handlers/analytics_handlers_markets.go +++ /dev/null @@ -1,260 +0,0 @@ -package handlers - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func (h *AnalyticsHandler) GetChurnMetrics(c *gin.Context) { - period := c.DefaultQuery("period", "monthly") - - metrics := ChurnMetrics{ - Period: period, - TotalSubscribers: 150000, - ChurnedSubscribers: 2250, - ChurnRate: 1.5, - MonthlyChurnRate: 1.5, - AnnualChurnRate: 18.0, - AverageTenureDays: 425, - RiskDistribution: map[string]int64{ - "low": 120000, - "medium": 22500, - "high": 6000, - "critical": 1500, - }, - GeneratedAt: time.Now(), - } - - c.JSON(http.StatusOK, metrics) -} - -// GetCompetitors returns competitor analysis -func (h *AnalyticsHandler) GetCompetitors(c *gin.Context) { - competitors := []map[string]any{ - { - "name": "AT&T", - "market_share": 35.5, - "subscribers": 200000000, - "strengths": []string{"Network coverage", "Brand recognition", "Enterprise focus"}, - "weaknesses": []string{"High prices", "Customer service"}, - "threat_level": "high", - }, - { - "name": "Verizon", - "market_share": 32.0, - "subscribers": 180000000, - "strengths": []string{"Network quality", "5G leadership"}, - "weaknesses": []string{"Limited international", "Premium pricing"}, - "threat_level": "high", - }, - { - "name": "T-Mobile", - "market_share": 21.3, - "subscribers": 120000000, - "strengths": []string{"Competitive pricing", "Innovation", "Customer experience"}, - "weaknesses": []string{"Rural coverage"}, - "threat_level": "medium", - }, - } - - c.JSON(http.StatusOK, gin.H{"competitors": competitors}) -} - -// GetMarketOpportunities returns market opportunities -func (h *AnalyticsHandler) GetMarketOpportunities(c *gin.Context) { - opportunities := []map[string]any{ - { - "id": "opp-1", - "type": "5G Migration", - "country": "US", - "potential_subs": 50000000, - "required_investment": 1000000000, - "expected_roi": 25.0, - "time_to_market": 24, - "confidence": 85.0, - }, - { - "id": "opp-2", - "type": "IoT Services", - "country": "UK", - "potential_subs": 20000000, - "required_investment": 500000000, - "expected_roi": 30.0, - "time_to_market": 18, - "confidence": 78.0, - }, - { - "id": "opp-3", - "type": "Enterprise 5G", - "country": "DE", - "potential_subs": 15000000, - "required_investment": 750000000, - "expected_roi": 20.0, - "time_to_market": 30, - "confidence": 72.0, - }, - } - - c.JSON(http.StatusOK, gin.H{"opportunities": opportunities}) -} - -// GetMaintenanceMetrics returns predictive maintenance metrics -func (h *AnalyticsHandler) GetMaintenanceMetrics(c *gin.Context) { - period := c.DefaultQuery("period", "monthly") - - metrics := MaintenanceMetrics{ - Period: period, - TotalAssets: 1250, - HealthyAssets: 1180, - AssetsNeedingAttention: 70, - Uptime: 99.95, - MeanTimeToFailure: 8760, // 1 year - MeanTimeToRepair: 4, // 4 hours - GeneratedAt: time.Now(), - } - - c.JSON(http.StatusOK, metrics) -} - -// GetAssetsHealth returns assets health status -func (h *AnalyticsHandler) GetAssetsHealth(c *gin.Context) { - assets := []map[string]any{ - {"id": "server-1", "name": "Web Server 1", "type": "server", "health_score": 85.0, "status": "healthy"}, - {"id": "server-2", "name": "Web Server 2", "type": "server", "health_score": 92.0, "status": "healthy"}, - {"id": "db-1", "name": "Primary Database", "type": "database", "health_score": 78.0, "status": "warning"}, - {"id": "db-2", "name": "Replica Database", "type": "database", "health_score": 95.0, "status": "healthy"}, - {"id": "net-1", "name": "Core Switch", "type": "network", "health_score": 88.0, "status": "healthy"}, - } - - c.JSON(http.StatusOK, gin.H{"assets": assets}) -} - -// GetMaintenanceAlerts returns maintenance alerts -func (h *AnalyticsHandler) GetMaintenanceAlerts(c *gin.Context) { - alerts := []map[string]any{ - { - "id": "alert-1", - "asset_id": "db-1", - "type": "predictive", - "severity": "medium", - "title": "Database disk space warning", - "description": "Disk usage at 78%, predicted to reach 90% in 14 days", - "timestamp": time.Now().Add(-2 * time.Hour), - }, - { - "id": "alert-2", - "asset_id": "server-3", - "type": "preventive", - "severity": "low", - "title": "Scheduled maintenance due", - "description": "Server maintenance overdue by 7 days", - "timestamp": time.Now().Add(-24 * time.Hour), - }, - } - - c.JSON(http.StatusOK, gin.H{"alerts": alerts}) -} - -// PredictFailure predicts failure for an asset -func (h *AnalyticsHandler) PredictFailure(c *gin.Context) { - assetID := c.Param("asset_id") - - prediction := map[string]any{ - "asset_id": assetID, - "failure_probability": 0.15, - "predicted_failure": time.Now().AddDate(0, 3, 0).Format("2006-01-02"), - "confidence": 82.5, - "risk_factors": []string{ - "Age approaching end of lifecycle", - "Increased error rate", - "Temperature fluctuations", - }, - "recommendations": []string{ - "Schedule preventive maintenance", - "Monitor closely", - "Prepare replacement parts", - }, - } - - c.JSON(http.StatusOK, prediction) -} - -// GetPricingMetrics returns pricing optimization metrics -func (h *AnalyticsHandler) GetPricingMetrics(c *gin.Context) { - period := c.DefaultQuery("period", "monthly") - - metrics := PricingMetrics{ - Period: period, - TotalRevenue: 4500000, - ARPU: 30.0, - PriceElasticity: -1.2, - CompetitiveIndex: 75.0, - OptimizationROI: 15.5, - GeneratedAt: time.Now(), - } - - c.JSON(http.StatusOK, metrics) -} - -// OptimizePricing optimizes pricing for rate plans -func (h *AnalyticsHandler) OptimizePricing(c *gin.Context) { - var req struct { - RatePlanIDs []string `json:"rate_plan_ids"` - Strategy string `json:"strategy"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - results := make([]map[string]any, 0) - for _, planID := range req.RatePlanIDs { - results = append(results, map[string]any{ - "rate_plan_id": planID, - "strategy": req.Strategy, - "current_price": 29.99, - "optimal_price": 32.99, - "price_change_pct": 10.0, - "expected_revenue": 165000, - "expected_demand": 5000, - "confidence": 85.0, - "reasoning": []string{ - "Market analysis suggests price increase tolerance", - "Competitor prices are higher", - "Strong value proposition", - }, - "risks": []string{ - "Potential short-term churn increase", - }, - "recommendations": []string{ - "Implement gradually over 2 months", - "Monitor churn closely", - }, - }) - } - - c.JSON(http.StatusOK, gin.H{"results": results}) -} - -// GetPriceElasticity returns price elasticity data -func (h *AnalyticsHandler) GetPriceElasticity(c *gin.Context) { - elasticity := map[string]any{ - "overall_elasticity": -1.2, - "by_segment": map[string]float64{ - "enterprise": -0.8, - "smb": -1.1, - "consumer": -1.5, - }, - "by_price_range": map[string]float64{ - "0-20": -1.8, - "20-50": -1.2, - "50-100": -0.9, - "100+": -0.6, - }, - "generated_at": time.Now(), - } - - c.JSON(http.StatusOK, elasticity) -} diff --git a/apps/api-server/internal/handlers/currency_handlers.go b/apps/api-server/internal/handlers/currency_handlers.go deleted file mode 100644 index 6ada69c..0000000 --- a/apps/api-server/internal/handlers/currency_handlers.go +++ /dev/null @@ -1,293 +0,0 @@ -package handlers - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -// CurrencyHandler handles currency and billing HTTP requests -type CurrencyHandler struct{} - -// NewCurrencyHandler creates a new currency handler -func NewCurrencyHandler() *CurrencyHandler { - return &CurrencyHandler{} -} - -// ConvertCurrency converts between currencies -func (h *CurrencyHandler) ConvertCurrency(c *gin.Context) { - var req struct { - From string `json:"from" binding:"required"` - To string `json:"to" binding:"required"` - Amount float64 `json:"amount" binding:"required"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Simulated exchange rates - rates := map[string]float64{ - "USD": 1.0, - "EUR": 0.92, - "GBP": 0.79, - "JPY": 149.50, - "CAD": 1.36, - "AUD": 1.53, - "CHF": 0.88, - } - - fromRate := rates[req.From] - toRate := rates[req.To] - - if fromRate == 0 || toRate == 0 { - c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported currency"}) - return - } - - // Convert to USD first, then to target currency - usdAmount := req.Amount / fromRate - converted := usdAmount * toRate - rate := toRate / fromRate - - c.JSON(http.StatusOK, gin.H{ - "from": req.From, - "to": req.To, - "amount": req.Amount, - "converted": converted, - "rate": rate, - "timestamp": time.Now(), - }) -} - -// GetExchangeRate returns exchange rate between currencies -func (h *CurrencyHandler) GetExchangeRate(c *gin.Context) { - from := c.Param("from") - to := c.Param("to") - - rates := map[string]float64{ - "USD": 1.0, - "EUR": 0.92, - "GBP": 0.79, - "JPY": 149.50, - "CAD": 1.36, - "AUD": 1.53, - "CHF": 0.88, - } - - fromRate := rates[from] - toRate := rates[to] - - if fromRate == 0 || toRate == 0 { - c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported currency"}) - return - } - - rate := toRate / fromRate - - c.JSON(http.StatusOK, gin.H{ - "from": from, - "to": to, - "rate": rate, - "timestamp": time.Now(), - }) -} - -// GetExchangeRateHistory returns exchange rate history -func (h *CurrencyHandler) GetExchangeRateHistory(c *gin.Context) { - from := c.Param("from") - to := c.Param("to") - - // Simulated historical rates - history := make([]map[string]any, 0) - baseRate := 0.92 // EUR/USD base - - for i := 30; i >= 0; i-- { - date := time.Now().AddDate(0, 0, -i) - variation := (float64(i%5) - 2) * 0.005 // Small daily variation - history = append(history, map[string]any{ - "from": from, - "to": to, - "rate": baseRate + variation, - "timestamp": date.Format("2006-01-02"), - }) - } - - c.JSON(http.StatusOK, gin.H{"history": history}) -} - -// GetSupportedCurrencies returns list of supported currencies -func (h *CurrencyHandler) GetSupportedCurrencies(c *gin.Context) { - currencies := []map[string]any{ - {"code": "USD", "name": "US Dollar", "symbol": "$"}, - {"code": "EUR", "name": "Euro", "symbol": "€"}, - {"code": "GBP", "name": "British Pound", "symbol": "£"}, - {"code": "JPY", "name": "Japanese Yen", "symbol": "¥"}, - {"code": "CAD", "name": "Canadian Dollar", "symbol": "C$"}, - {"code": "AUD", "name": "Australian Dollar", "symbol": "A$"}, - {"code": "CHF", "name": "Swiss Franc", "symbol": "CHF"}, - {"code": "CNY", "name": "Chinese Yuan", "symbol": "¥"}, - {"code": "INR", "name": "Indian Rupee", "symbol": "₹"}, - {"code": "KRW", "name": "South Korean Won", "symbol": "₩"}, - } - - c.JSON(http.StatusOK, gin.H{"currencies": currencies}) -} - -// RefreshExchangeRates refreshes exchange rates from external sources -func (h *CurrencyHandler) RefreshExchangeRates(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "status": "refreshed", - "updated_at": time.Now(), - "source": "external_api", - "rates_count": 10, - }) -} - -// ProcessBilling processes a billing transaction -func (h *CurrencyHandler) ProcessBilling(c *gin.Context) { - var req struct { - ProfileID string `json:"profile_id" binding:"required"` - Amount float64 `json:"amount" binding:"required"` - Currency string `json:"currency" binding:"required"` - Description string `json:"description"` - Type string `json:"type"` // charge, credit, refund - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if req.Type == "" { - req.Type = "charge" - } - - transaction := map[string]any{ - "id": "txn-" + time.Now().Format("20060102150405"), - "profile_id": req.ProfileID, - "amount": req.Amount, - "currency": req.Currency, - "type": req.Type, - "description": req.Description, - "status": "completed", - "created_at": time.Now(), - } - - c.JSON(http.StatusCreated, transaction) -} - -// GetBillingHistory returns billing history for a profile -func (h *CurrencyHandler) GetBillingHistory(c *gin.Context) { - profileID := c.Param("profile_id") - - transactions := []map[string]any{ - { - "id": "txn-001", - "profile_id": profileID, - "amount": 29.99, - "currency": "USD", - "type": "charge", - "description": "Monthly subscription", - "status": "completed", - "created_at": time.Now().AddDate(0, 0, -1), - }, - { - "id": "txn-002", - "profile_id": profileID, - "amount": 15.00, - "currency": "USD", - "type": "charge", - "description": "Data top-up", - "status": "completed", - "created_at": time.Now().AddDate(0, 0, -15), - }, - { - "id": "txn-003", - "profile_id": profileID, - "amount": 29.99, - "currency": "USD", - "type": "charge", - "description": "Monthly subscription", - "status": "completed", - "created_at": time.Now().AddDate(0, -1, -1), - }, - } - - c.JSON(http.StatusOK, gin.H{"transactions": transactions}) -} - -// GetBillingSummary returns billing summary for a profile -func (h *CurrencyHandler) GetBillingSummary(c *gin.Context) { - profileID := c.Param("profile_id") - period := c.DefaultQuery("period", "monthly") - - summary := map[string]any{ - "profile_id": profileID, - "period": period, - "total_amount": 74.98, - "currency": "USD", - "transaction_count": 3, - "breakdown": map[string]float64{ - "subscription": 59.98, - "data_topup": 15.00, - }, - "average_transaction": 24.99, - "generated_at": time.Now(), - } - - c.JSON(http.StatusOK, summary) -} - -// ProcessRefund processes a refund for a transaction -func (h *CurrencyHandler) ProcessRefund(c *gin.Context) { - transactionID := c.Param("transaction_id") - - var req struct { - Reason string `json:"reason" binding:"required"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - refund := map[string]any{ - "id": "ref-" + time.Now().Format("20060102150405"), - "original_transaction": transactionID, - "amount": 29.99, - "currency": "USD", - "reason": req.Reason, - "status": "completed", - "created_at": time.Now(), - } - - c.JSON(http.StatusOK, refund) -} - -// GetBillingAnalytics returns billing analytics -func (h *CurrencyHandler) GetBillingAnalytics(c *gin.Context) { - period := c.DefaultQuery("period", "monthly") - - analytics := map[string]any{ - "period": period, - "total_revenue": 4500000, - "total_transactions": 150000, - "average_transaction": 30.0, - "by_type": map[string]float64{ - "subscription": 3500000, - "data_topup": 750000, - "voice_topup": 150000, - "other": 100000, - }, - "by_currency": map[string]float64{ - "USD": 3000000, - "EUR": 1000000, - "GBP": 500000, - }, - "growth_rate": 12.5, - "churn_impact": -150000, - "generated_at": time.Now(), - } - - c.JSON(http.StatusOK, analytics) -} diff --git a/apps/api-server/internal/handlers/security_handlers.go b/apps/api-server/internal/handlers/security_handlers.go deleted file mode 100644 index f428685..0000000 --- a/apps/api-server/internal/handlers/security_handlers.go +++ /dev/null @@ -1,314 +0,0 @@ -package handlers - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -// SecurityHandler handles security-related HTTP requests -type SecurityHandler struct{} - -// NewSecurityHandler creates a new security handler -func NewSecurityHandler() *SecurityHandler { - return &SecurityHandler{} -} - -// FraudAlert represents a fraud alert -type FraudAlert struct { - ID string `json:"id"` - Type string `json:"type"` - Severity string `json:"severity"` - ProfileID string `json:"profile_id"` - Description string `json:"description"` - RiskScore float64 `json:"risk_score"` - Evidence []string `json:"evidence"` - IPAddress string `json:"ip_address"` - Timestamp time.Time `json:"timestamp"` - Status string `json:"status"` - ActionsTaken []string `json:"actions_taken"` - Metadata map[string]any `json:"metadata"` -} - -// FraudMetrics represents fraud detection metrics -type FraudMetrics struct { - Period string `json:"period"` - TotalAlerts int64 `json:"total_alerts"` - ResolvedAlerts int64 `json:"resolved_alerts"` - FalsePositives int64 `json:"false_positives"` - ResolutionRate float64 `json:"resolution_rate_pct"` - FalsePositiveRate float64 `json:"false_positive_rate_pct"` - ByType map[string]int64 `json:"by_type"` - BySeverity map[string]int64 `json:"by_severity"` - GeneratedAt time.Time `json:"generated_at"` -} - -// AnalyzeTransaction analyzes a transaction for fraud -func (h *SecurityHandler) AnalyzeTransaction(c *gin.Context) { - var req map[string]any - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - profileID, _ := req["profile_id"].(string) - ipAddress, _ := req["ip_address"].(string) - - // Simulated fraud analysis - riskScore := 25.0 // Low risk by default - - // Check for suspicious patterns - if amount, ok := req["amount"].(float64); ok && amount > 1000 { - riskScore += 20 - } - - alert := FraudAlert{ - ID: "alert-" + time.Now().Format("20060102150405"), - Type: "transaction_analysis", - Severity: getSeverityFromScore(riskScore), - ProfileID: profileID, - Description: "Transaction analyzed for fraud indicators", - RiskScore: riskScore, - Evidence: []string{ - "Transaction amount within normal range", - "IP address matches historical pattern", - "Device fingerprint recognized", - }, - IPAddress: ipAddress, - Timestamp: time.Now(), - Status: "analyzed", - ActionsTaken: []string{"Logged for monitoring"}, - Metadata: req, - } - - c.JSON(http.StatusOK, alert) -} - -// GetFraudAlerts returns fraud alerts with filtering -func (h *SecurityHandler) GetFraudAlerts(c *gin.Context) { - var filter struct { - Type string `json:"type"` - Severity string `json:"severity"` - Status string `json:"status"` - Limit int `json:"limit"` - } - c.ShouldBindJSON(&filter) - - if filter.Limit == 0 || filter.Limit > 100 { - filter.Limit = 50 - } - - // Simulated alerts - alerts := []FraudAlert{ - { - ID: "alert-001", - Type: "account_takeover", - Severity: "high", - ProfileID: "profile-123", - Description: "Multiple failed login attempts from new IP", - RiskScore: 85.0, - Evidence: []string{"10 failed logins", "New IP address", "Different country"}, - IPAddress: "192.168.1.100", - Timestamp: time.Now().Add(-2 * time.Hour), - Status: "new", - }, - { - ID: "alert-002", - Type: "payment_fraud", - Severity: "medium", - ProfileID: "profile-456", - Description: "Unusual payment pattern detected", - RiskScore: 55.0, - Evidence: []string{"Large transaction", "New payment method"}, - IPAddress: "10.0.0.50", - Timestamp: time.Now().Add(-5 * time.Hour), - Status: "investigating", - }, - { - ID: "alert-003", - Type: "sim_swap", - Severity: "critical", - ProfileID: "profile-789", - Description: "SIM swap attempt detected", - RiskScore: 95.0, - Evidence: []string{"SIM change request", "No prior notification", "High-value account"}, - IPAddress: "172.16.0.25", - Timestamp: time.Now().Add(-30 * time.Minute), - Status: "blocked", - }, - } - - // Filter alerts - filtered := make([]FraudAlert, 0) - for _, alert := range alerts { - if filter.Type != "" && alert.Type != filter.Type { - continue - } - if filter.Severity != "" && alert.Severity != filter.Severity { - continue - } - if filter.Status != "" && alert.Status != filter.Status { - continue - } - filtered = append(filtered, alert) - if len(filtered) >= filter.Limit { - break - } - } - - c.JSON(http.StatusOK, filtered) -} - -// UpdateAlertStatus updates a fraud alert status -func (h *SecurityHandler) UpdateAlertStatus(c *gin.Context) { - alertID := c.Param("id") - - var req struct { - Status string `json:"status" binding:"required"` - Actions []string `json:"actions"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "id": alertID, - "status": req.Status, - "actions_taken": req.Actions, - "updated_at": time.Now(), - }) -} - -// GetFraudMetrics returns fraud detection metrics -func (h *SecurityHandler) GetFraudMetrics(c *gin.Context) { - period := c.DefaultQuery("period", "monthly") - - metrics := FraudMetrics{ - Period: period, - TotalAlerts: 1250, - ResolvedAlerts: 1100, - FalsePositives: 125, - ResolutionRate: 88.0, - FalsePositiveRate: 10.0, - ByType: map[string]int64{ - "account_takeover": 350, - "subscription_fraud": 200, - "payment_fraud": 400, - "usage_anomaly": 200, - "sim_swap": 100, - }, - BySeverity: map[string]int64{ - "low": 400, - "medium": 500, - "high": 250, - "critical": 100, - }, - GeneratedAt: time.Now(), - } - - c.JSON(http.StatusOK, metrics) -} - -// GetFraudPatterns returns detected fraud patterns -func (h *SecurityHandler) GetFraudPatterns(c *gin.Context) { - patterns := []map[string]any{ - { - "id": "pattern-1", - "name": "Velocity Attack", - "description": "Multiple rapid transactions from same source", - "frequency": "high", - "indicators": []string{"High transaction rate", "Same IP", "Different accounts"}, - "mitigation": "Rate limiting, IP blocking", - }, - { - "id": "pattern-2", - "name": "Account Enumeration", - "description": "Systematic probing of account existence", - "frequency": "medium", - "indicators": []string{"Sequential account checks", "Automated requests"}, - "mitigation": "CAPTCHA, rate limiting", - }, - { - "id": "pattern-3", - "name": "SIM Swap Attack", - "description": "Unauthorized SIM card replacement", - "frequency": "low", - "indicators": []string{"Sudden SIM change", "No customer contact", "High-value target"}, - "mitigation": "Multi-factor verification, cooling period", - }, - } - - c.JSON(http.StatusOK, gin.H{"patterns": patterns}) -} - -// VerifySIMSwap verifies a SIM swap request -func (h *SecurityHandler) VerifySIMSwap(c *gin.Context) { - var req struct { - ProfileID string `json:"profile_id" binding:"required"` - MSISDN string `json:"msisdn" binding:"required"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Simulated SIM swap verification - c.JSON(http.StatusOK, gin.H{ - "profile_id": req.ProfileID, - "msisdn": req.MSISDN, - "verified": true, - "risk_score": 15.0, - "risk_level": "low", - "last_sim_swap": time.Now().AddDate(-1, 0, 0).Format("2006-01-02"), - "checks_passed": []string{ - "Identity verified", - "No recent SIM changes", - "Account in good standing", - }, - }) -} - -// GetSIMSwapHistory returns SIM swap history for a profile -func (h *SecurityHandler) GetSIMSwapHistory(c *gin.Context) { - profileID := c.Param("profile_id") - - history := []map[string]any{ - { - "id": "swap-001", - "profile_id": profileID, - "old_iccid": "8901234567890123456", - "new_iccid": "8901234567890123457", - "timestamp": time.Now().AddDate(-1, 0, 0), - "reason": "Device upgrade", - "verified": true, - "status": "completed", - }, - { - "id": "swap-002", - "profile_id": profileID, - "old_iccid": "8901234567890123455", - "new_iccid": "8901234567890123456", - "timestamp": time.Now().AddDate(-2, 0, 0), - "reason": "Lost device", - "verified": true, - "status": "completed", - }, - } - - c.JSON(http.StatusOK, gin.H{"history": history}) -} - -func getSeverityFromScore(score float64) string { - switch { - case score >= 80: - return "critical" - case score >= 60: - return "high" - case score >= 40: - return "medium" - default: - return "low" - } -} diff --git a/apps/carrier-connector/internal/analytics/churn_service.go b/apps/carrier-connector/internal/analytics/churn_service.go deleted file mode 100644 index d237637..0000000 --- a/apps/carrier-connector/internal/analytics/churn_service.go +++ /dev/null @@ -1,207 +0,0 @@ -package analytics - -import ( - "context" - "fmt" - "time" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// ChurnAnalysisService provides churn analysis and prediction -type ChurnAnalysisService struct { - db *gorm.DB - logger *logrus.Logger -} - -// NewChurnAnalysisService creates a new churn analysis service -func NewChurnAnalysisService(db *gorm.DB, logger *logrus.Logger) *ChurnAnalysisService { - return &ChurnAnalysisService{db: db, logger: logger} -} - -// PredictChurn predicts churn risk for a specific profile -func (s *ChurnAnalysisService) PredictChurn(ctx context.Context, profileID string) (*ChurnPrediction, error) { - factors := s.analyzeFactors(ctx, profileID) - score := s.calculateScore(factors) - - prediction := &ChurnPrediction{ - ProfileID: profileID, - RiskScore: score, - RiskLevel: RiskLevelFromScore(score), - Reasons: s.generateReasons(factors), - Recommendations: RecommendationsForRisk(RiskLevelFromScore(score)), - LastUpdated: time.Now(), - } - - if prediction.RiskLevel == ChurnRiskHigh || prediction.RiskLevel == ChurnRiskCritical { - date := time.Now().AddDate(0, 0, int((100-score)*3)) - prediction.PredictedChurnDate = &date - } - - return prediction, nil -} - -// GetChurnMetrics calculates overall churn metrics -func (s *ChurnAnalysisService) GetChurnMetrics(ctx context.Context, period string) (*ChurnMetrics, error) { - start, end := periodDates(period) - metrics := &ChurnMetrics{Period: period, RiskDistribution: make(map[ChurnRiskLevel]int64), GeneratedAt: time.Now()} - - s.db.WithContext(ctx).Table("profiles").Where("created_at < ?", start).Count(&metrics.TotalSubscribers) - s.db.WithContext(ctx).Table("rate_plan_subscriptions").Where("ended_at BETWEEN ? AND ?", start, end).Count(&metrics.ChurnedSubscribers) - - if metrics.TotalSubscribers > 0 { - metrics.ChurnRate = float64(metrics.ChurnedSubscribers) / float64(metrics.TotalSubscribers) * 100 - metrics.MonthlyChurnRate = metrics.ChurnRate - metrics.AnnualChurnRate = metrics.ChurnRate * 12 - } - - s.db.WithContext(ctx).Table("rate_plan_subscriptions").Where("status = ?", "cancelled"). - Select("AVG(EXTRACT(EPOCH FROM (ended_at - started_at))/86400)").Scan(&metrics.AverageTenure) - - // Estimated distribution - metrics.RiskDistribution[ChurnRiskLow] = int64(float64(metrics.TotalSubscribers) * 0.6) - metrics.RiskDistribution[ChurnRiskMedium] = int64(float64(metrics.TotalSubscribers) * 0.25) - metrics.RiskDistribution[ChurnRiskHigh] = int64(float64(metrics.TotalSubscribers) * 0.12) - metrics.RiskDistribution[ChurnRiskCritical] = int64(float64(metrics.TotalSubscribers) * 0.03) - - return metrics, nil -} - -// GetChurnFactors returns the top factors contributing to churn -func (s *ChurnAnalysisService) GetChurnFactors(_ context.Context) ([]ChurnFactor, error) { - return DefaultChurnFactors(), nil -} - -// GetAtRiskCustomers returns customers at high risk of churn -func (s *ChurnAnalysisService) GetAtRiskCustomers(ctx context.Context, riskLevel ChurnRiskLevel, limit int) ([]*ChurnPrediction, error) { - var subs []struct{ ID string } - s.db.WithContext(ctx).Table("profiles").Where("status = ?", "active").Limit(limit).Find(&subs) - - var predictions []*ChurnPrediction - for _, sub := range subs { - pred, err := s.PredictChurn(ctx, sub.ID) - if err != nil { - continue - } - if pred.RiskLevel == riskLevel || (riskLevel == ChurnRiskHigh && pred.RiskLevel == ChurnRiskCritical) { - predictions = append(predictions, pred) - } - } - return predictions, nil -} - -func (s *ChurnAnalysisService) analyzeFactors(ctx context.Context, profileID string) []ChurnFactor { - factors := DefaultChurnFactors() - result := make([]ChurnFactor, 0, len(factors)) - - for _, f := range factors { - impact := s.factorImpact(ctx, profileID, f.Factor) - if impact > 0.1 { - result = append(result, ChurnFactor{Factor: f.Factor, Impact: impact, Description: f.Description, Weight: f.Weight}) - } - } - return result -} - -func (s *ChurnAnalysisService) factorImpact(ctx context.Context, profileID, factor string) float64 { - switch factor { - case "Low Usage": - return s.usageImpact(ctx, profileID) - case "Payment Issues": - return s.paymentImpact(ctx, profileID) - case "Price Sensitivity": - return s.priceImpact(ctx, profileID) - default: - return 0.4 - } -} - -func (s *ChurnAnalysisService) usageImpact(ctx context.Context, profileID string) float64 { - var total int64 - s.db.WithContext(ctx).Table("rate_plan_usage").Where("profile_id = ? AND created_at > ?", profileID, time.Now().AddDate(0, -1, 0)). - Select("COALESCE(SUM(data_used + voice_used + sms_used), 0)").Scan(&total) - - switch { - case total < 100: - return 0.8 - case total < 500: - return 0.6 - case total < 1000: - return 0.3 - default: - return 0.1 - } -} - -func (s *ChurnAnalysisService) paymentImpact(ctx context.Context, profileID string) float64 { - var createdAt time.Time - s.db.WithContext(ctx).Table("profiles").Where("id = ?", profileID).Select("created_at").Scan(&createdAt) - days := time.Since(createdAt).Hours() / 24 - if days < 30 { - return 0.3 - } else if days < 90 { - return 0.2 - } - return 0.1 -} - -func (s *ChurnAnalysisService) priceImpact(ctx context.Context, profileID string) float64 { - var price float64 - s.db.WithContext(ctx).Table("rate_plans rp").Joins("JOIN rate_plan_subscriptions rps ON rps.rate_plan_id = rp.id"). - Where("rps.profile_id = ? AND rps.status = ?", profileID, "active").Select("rp.base_price").Scan(&price) - - switch { - case price > 50: - return 0.6 - case price > 30: - return 0.4 - case price > 15: - return 0.2 - default: - return 0.1 - } -} - -func (s *ChurnAnalysisService) calculateScore(factors []ChurnFactor) float64 { - var total, weight float64 - for _, f := range factors { - total += f.Impact * f.Weight - weight += f.Weight - } - if weight == 0 { - return 0 - } - return (total / weight) * 100 -} - -func (s *ChurnAnalysisService) generateReasons(factors []ChurnFactor) []string { - var reasons []string - for _, f := range factors { - if f.Impact > 0.7 { - reasons = append(reasons, fmt.Sprintf("High %s detected", f.Factor)) - } else if f.Impact > 0.5 { - reasons = append(reasons, fmt.Sprintf("Moderate %s detected", f.Factor)) - } - } - if len(reasons) == 0 { - reasons = append(reasons, "Multiple minor risk factors detected") - } - return reasons -} - -func periodDates(period string) (time.Time, time.Time) { - now := time.Now() - switch period { - case "daily": - return now.Truncate(24 * time.Hour), now - case "weekly": - return now.AddDate(0, 0, -7), now - case "monthly": - return now.AddDate(0, -1, 0), now - case "quarterly": - return now.AddDate(0, -3, 0), now - default: - return now.AddDate(0, -1, 0), now - } -} diff --git a/apps/carrier-connector/internal/analytics/churn_types.go b/apps/carrier-connector/internal/analytics/churn_types.go deleted file mode 100644 index e6bbeed..0000000 --- a/apps/carrier-connector/internal/analytics/churn_types.go +++ /dev/null @@ -1,85 +0,0 @@ -package analytics - -import "time" - -// ChurnRiskLevel represents the risk level of customer churn -type ChurnRiskLevel string - -const ( - ChurnRiskLow ChurnRiskLevel = "low" - ChurnRiskMedium ChurnRiskLevel = "medium" - ChurnRiskHigh ChurnRiskLevel = "high" - ChurnRiskCritical ChurnRiskLevel = "critical" -) - -// ChurnPrediction represents a churn prediction for a customer -type ChurnPrediction struct { - ProfileID string `json:"profile_id"` - RiskLevel ChurnRiskLevel `json:"risk_level"` - RiskScore float64 `json:"risk_score"` - PredictedChurnDate *time.Time `json:"predicted_churn_date,omitempty"` - Reasons []string `json:"reasons"` - Recommendations []string `json:"recommendations"` - LastUpdated time.Time `json:"last_updated"` -} - -// ChurnMetrics represents churn analysis metrics -type ChurnMetrics struct { - Period string `json:"period"` - TotalSubscribers int64 `json:"total_subscribers"` - ChurnedSubscribers int64 `json:"churned_subscribers"` - ChurnRate float64 `json:"churn_rate"` - MonthlyChurnRate float64 `json:"monthly_churn_rate"` - AnnualChurnRate float64 `json:"annual_churn_rate"` - AverageTenure float64 `json:"average_tenure_days"` - RiskDistribution map[ChurnRiskLevel]int64 `json:"risk_distribution"` - GeneratedAt time.Time `json:"generated_at"` -} - -// ChurnFactor represents factors contributing to churn -type ChurnFactor struct { - Factor string `json:"factor"` - Impact float64 `json:"impact"` - Description string `json:"description"` - Weight float64 `json:"weight"` -} - -// DefaultChurnFactors returns standard churn factors -func DefaultChurnFactors() []ChurnFactor { - return []ChurnFactor{ - {Factor: "Low Usage", Impact: 0.85, Description: "Low data/voice usage increases churn risk", Weight: 0.25}, - {Factor: "Payment Issues", Impact: 0.92, Description: "Failed payments increase churn risk", Weight: 0.20}, - {Factor: "Poor Support", Impact: 0.78, Description: "High support ticket resolution time", Weight: 0.15}, - {Factor: "Network Quality", Impact: 0.70, Description: "Dropped calls and slow data speeds", Weight: 0.20}, - {Factor: "Price Sensitivity", Impact: 0.65, Description: "Higher-priced plans have higher churn", Weight: 0.10}, - {Factor: "Competitor Offers", Impact: 0.60, Description: "Better competitor deals", Weight: 0.10}, - } -} - -// RiskLevelFromScore determines risk level from score -func RiskLevelFromScore(score float64) ChurnRiskLevel { - switch { - case score >= 80: - return ChurnRiskCritical - case score >= 60: - return ChurnRiskHigh - case score >= 40: - return ChurnRiskMedium - default: - return ChurnRiskLow - } -} - -// RecommendationsForRisk returns recommendations based on risk level -func RecommendationsForRisk(level ChurnRiskLevel) []string { - switch level { - case ChurnRiskCritical: - return []string{"Immediate intervention required", "Offer retention discount", "Schedule proactive support call"} - case ChurnRiskHigh: - return []string{"Send personalized retention offer", "Review and address service issues"} - case ChurnRiskMedium: - return []string{"Monitor usage patterns closely", "Send value-add content"} - default: - return []string{"Continue standard engagement"} - } -} diff --git a/apps/carrier-connector/internal/analytics/market_service.go b/apps/carrier-connector/internal/analytics/market_service.go deleted file mode 100644 index 894fa92..0000000 --- a/apps/carrier-connector/internal/analytics/market_service.go +++ /dev/null @@ -1,591 +0,0 @@ -package analytics - -import ( - "context" - "time" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// MarketMetrics represents market penetration analysis -type MarketMetrics struct { - Period string `json:"period"` - TotalMarketSize int64 `json:"total_market_size"` - OurSubscribers int64 `json:"our_subscribers"` - MarketShare float64 `json:"market_share_pct"` - GrowthRate float64 `json:"growth_rate_pct"` - ByCountry map[string]CountryMetrics `json:"by_country"` - ByCarrier map[string]MarketCarrierMetrics `json:"by_carrier"` - ByDemographic map[string]DemoMetrics `json:"by_demographic"` - CompetitorAnalysis []CompetitorMetrics `json:"competitor_analysis"` - PenetrationOpportunities []OpportunityMetrics `json:"penetration_opportunities"` - GeneratedAt time.Time `json:"generated_at"` -} - -// CountryMetrics represents metrics by country -type CountryMetrics struct { - Country string `json:"country"` - TotalPopulation int64 `json:"total_population"` - ActiveSubscribers int64 `json:"active_subscribers"` - PenetrationRate float64 `json:"penetration_rate_pct"` - GrowthRate float64 `json:"growth_rate_pct"` - ARPU float64 `json:"arpu"` // Average Revenue Per User - MarketPotential int64 `json:"market_potential"` -} - -// MarketCarrierMetrics represents metrics by carrier for market analysis -type MarketCarrierMetrics struct { - CarrierName string `json:"carrier_name"` - Subscribers int64 `json:"subscribers"` - MarketShare float64 `json:"market_share_pct"` - ChurnRate float64 `json:"churn_rate_pct"` - ARPU float64 `json:"arpu"` - NetworkQuality float64 `json:"network_quality_score"` - Coverage float64 `json:"coverage_pct"` -} - -// DemoMetrics represents metrics by demographic -type DemoMetrics struct { - Segment string `json:"segment"` - Subscribers int64 `json:"subscribers"` - MarketShare float64 `json:"market_share_pct"` - ARPU float64 `json:"arpu"` - GrowthRate float64 `json:"growth_rate_pct"` -} - -// CompetitorMetrics represents competitor analysis -type CompetitorMetrics struct { - Name string `json:"name"` - EstimatedSubs int64 `json:"estimated_subscribers"` - MarketShare float64 `json:"market_share_pct"` - Strengths []string `json:"strengths"` - Weaknesses []string `json:"weaknesses"` - ThreatLevel string `json:"threat_level"` -} - -// OpportunityMetrics represents market opportunities -type OpportunityMetrics struct { - Country string `json:"country"` - OpportunityType string `json:"opportunity_type"` - PotentialSubs int64 `json:"potential_subscribers"` - RequiredInvestment float64 `json:"required_investment"` - ExpectedROI float64 `json:"expected_roi_pct"` - TimeToMarket int `json:"time_to_market_months"` -} - -// MarketAnalysisService provides market penetration analysis -type MarketAnalysisService struct { - db *gorm.DB - logger *logrus.Logger -} - -// NewMarketAnalysisService creates a new market analysis service -func NewMarketAnalysisService(db *gorm.DB, logger *logrus.Logger) *MarketAnalysisService { - return &MarketAnalysisService{ - db: db, - logger: logger, - } -} - -// GetMarketMetrics calculates market penetration metrics -func (s *MarketAnalysisService) GetMarketMetrics(ctx context.Context, period string) (*MarketMetrics, error) { - metrics := &MarketMetrics{ - Period: period, - ByCountry: make(map[string]CountryMetrics), - ByCarrier: make(map[string]MarketCarrierMetrics), - ByDemographic: make(map[string]DemoMetrics), - CompetitorAnalysis: make([]CompetitorMetrics, 0), - PenetrationOpportunities: make([]OpportunityMetrics, 0), - GeneratedAt: time.Now(), - } - - // Calculate overall market metrics - s.calculateOverallMetrics(ctx, metrics) - - // Calculate country-specific metrics - s.calculateCountryMetrics(ctx, metrics) - - // Calculate carrier metrics - s.calculateCarrierMetrics(ctx, metrics) - - // Calculate demographic metrics - s.calculateDemographicMetrics(ctx, metrics) - - // Analyze competitors - s.analyzeCompetitors(ctx, metrics) - - // Identify opportunities - s.identifyOpportunities(ctx, metrics) - - return metrics, nil -} - -// calculateOverallMetrics calculates overall market metrics -func (s *MarketAnalysisService) calculateOverallMetrics(ctx context.Context, metrics *MarketMetrics) { - // Get our subscriber count - var ourSubs int64 - s.db.WithContext(ctx).Table("profiles"). - Where("status = ?", "active"). - Count(&ourSubs) - metrics.OurSubscribers = ourSubs - - // Estimate total market size (simplified - would use external data) - metrics.TotalMarketSize = s.estimateTotalMarketSize() - - // Calculate market share - if metrics.TotalMarketSize > 0 { - metrics.MarketShare = float64(ourSubs) / float64(metrics.TotalMarketSize) * 100 - } - - // Calculate growth rate (compare to previous period) - metrics.GrowthRate = s.calculateGrowthRate(ctx, ourSubs) -} - -// calculateCountryMetrics calculates metrics by country -func (s *MarketAnalysisService) calculateCountryMetrics(ctx context.Context, metrics *MarketMetrics) { - countries := []string{"US", "UK", "DE", "FR", "JP", "AU", "CA", "SG"} - - for _, country := range countries { - var subs int64 - s.db.WithContext(ctx).Table("profiles"). - Where("country = ? AND status = ?", country, "active"). - Count(&subs) - - countryMetrics := CountryMetrics{ - Country: country, - ActiveSubscribers: subs, - TotalPopulation: s.getCountryPopulation(country), - PenetrationRate: s.calculatePenetrationRate(subs, country), - GrowthRate: s.calculateCountryGrowthRate(ctx, country), - ARPU: s.calculateCountryARPU(ctx, country), - MarketPotential: s.calculateMarketPotential(country), - } - - metrics.ByCountry[country] = countryMetrics - } -} - -// calculateCarrierMetrics calculates metrics by carrier -func (s *MarketAnalysisService) calculateCarrierMetrics(ctx context.Context, metrics *MarketMetrics) { - // This would analyze carrier partnerships and performance - carriers := []string{"AT&T", "Verizon", "T-Mobile", "Vodafone", "Orange", "Deutsche Telekom"} - - for _, carrier := range carriers { - var subs int64 - s.db.WithContext(ctx).Table("rate_plan_subscriptions rps"). - Joins("JOIN rate_plans rp ON rps.rate_plan_id = rp.id"). - Joins("JOIN profiles p ON rps.profile_id = p.id"). - Where("rp.carrier_name = ? AND rps.status = ?", carrier, "active"). - Count(&subs) - - carrierMetrics := MarketCarrierMetrics{ - CarrierName: carrier, - Subscribers: subs, - MarketShare: s.calculateCarrierMarketShare(ctx, carrier), - ChurnRate: s.calculateCarrierChurnRate(ctx, carrier), - ARPU: s.calculateCarrierARPU(ctx, carrier), - NetworkQuality: s.getCarrierNetworkQuality(carrier), - Coverage: s.getCarrierCoverage(carrier), - } - - metrics.ByCarrier[carrier] = carrierMetrics - } -} - -// calculateDemographicMetrics calculates metrics by demographic segments -func (s *MarketAnalysisService) calculateDemographicMetrics(ctx context.Context, metrics *MarketMetrics) { - segments := []string{"18-24", "25-34", "35-44", "45-54", "55+", "Business", "Student"} - - for _, segment := range segments { - var subs int64 - s.db.WithContext(ctx).Table("profiles"). - Where("demographic_segment = ? AND status = ?", segment, "active"). - Count(&subs) - - demoMetrics := DemoMetrics{ - Segment: segment, - Subscribers: subs, - MarketShare: s.calculateDemoMarketShare(ctx, segment), - ARPU: s.calculateDemoARPU(ctx, segment), - GrowthRate: s.calculateDemoGrowthRate(ctx, segment), - } - - metrics.ByDemographic[segment] = demoMetrics - } -} - -// analyzeCompetitors analyzes competitive landscape -func (s *MarketAnalysisService) analyzeCompetitors(_ context.Context, metrics *MarketMetrics) { - competitors := []struct { - name string - subs int64 - share float64 - strengths []string - weaknesses []string - threat string - }{ - { - name: "Airalo", - subs: 5000000, - share: 25.0, - strengths: []string{"Large market share", "Global presence", "Brand recognition"}, - weaknesses: []string{"Higher prices", "Limited customization"}, - threat: "high", - }, - { - name: "Truphone", - subs: 2000000, - share: 10.0, - strengths: []string{"Enterprise focus", "Quality network"}, - weaknesses: []string{"Limited consumer market", "Higher pricing"}, - threat: "medium", - }, - { - name: "Ubigi", - subs: 1500000, - share: 7.5, - strengths: []string{"Competitive pricing", "Good coverage"}, - weaknesses: []string{"Smaller market share", "Limited features"}, - threat: "medium", - }, - { - name: "Holafly", - subs: 1000000, - share: 5.0, - strengths: []string{"Simple pricing", "Good customer service"}, - weaknesses: []string{"Limited carrier partnerships", "Basic features"}, - threat: "low", - }, - } - - for _, comp := range competitors { - metrics.CompetitorAnalysis = append(metrics.CompetitorAnalysis, CompetitorMetrics{ - Name: comp.name, - EstimatedSubs: comp.subs, - MarketShare: comp.share, - Strengths: comp.strengths, - Weaknesses: comp.weaknesses, - ThreatLevel: comp.threat, - }) - } -} - -// identifyOpportunities identifies market penetration opportunities -func (s *MarketAnalysisService) identifyOpportunities(_ context.Context, metrics *MarketMetrics) { - opportunities := []OpportunityMetrics{ - { - Country: "India", - OpportunityType: "Emerging Market", - PotentialSubs: 10000000, - RequiredInvestment: 5000000, - ExpectedROI: 150.0, - TimeToMarket: 6, - }, - { - Country: "Brazil", - OpportunityType: "Latin America Expansion", - PotentialSubs: 5000000, - RequiredInvestment: 3000000, - ExpectedROI: 120.0, - TimeToMarket: 4, - }, - { - Country: "South Korea", - OpportunityType: "Tech-Savvy Market", - PotentialSubs: 2000000, - RequiredInvestment: 2000000, - ExpectedROI: 80.0, - TimeToMarket: 3, - }, - { - Country: "Nigeria", - OpportunityType: "African Market Entry", - PotentialSubs: 8000000, - RequiredInvestment: 4000000, - ExpectedROI: 180.0, - TimeToMarket: 8, - }, - } - - metrics.PenetrationOpportunities = opportunities -} - -// estimateTotalMarketSize estimates total addressable market -func (s *MarketAnalysisService) estimateTotalMarketSize() int64 { - // Simplified estimation - would use market research data - // Global mobile subscribers ~5.5B, eSIM adoption ~10% - return 550000000 // 550M eSIM users globally -} - -// calculateGrowthRate calculates growth rate compared to previous period -func (s *MarketAnalysisService) calculateGrowthRate(ctx context.Context, currentSubs int64) float64 { - // Get subscribers from previous month - var prevSubs int64 - s.db.WithContext(ctx).Table("profiles"). - Where("status = ? AND created_at < ?", "active", time.Now().AddDate(0, -1, 0)). - Count(&prevSubs) - - if prevSubs == 0 { - return 0 - } - - return float64(currentSubs-prevSubs) / float64(prevSubs) * 100 -} - -// getCountryPopulation returns population data for a country -func (s *MarketAnalysisService) getCountryPopulation(country string) int64 { - populations := map[string]int64{ - "US": 331000000, - "UK": 67000000, - "DE": 83000000, - "FR": 65000000, - "JP": 126000000, - "AU": 25000000, - "CA": 38000000, - "SG": 5800000, - } - return populations[country] -} - -// calculatePenetrationRate calculates penetration rate for a country -func (s *MarketAnalysisService) calculatePenetrationRate(subs int64, country string) float64 { - population := s.getCountryPopulation(country) - if population == 0 { - return 0 - } - return float64(subs) / float64(population) * 100 -} - -// calculateCountryGrowthRate calculates growth rate for a country -func (s *MarketAnalysisService) calculateCountryGrowthRate(ctx context.Context, country string) float64 { - var currentSubs, prevSubs int64 - - now := time.Now() - prevMonth := now.AddDate(0, -1, 0) - - s.db.WithContext(ctx).Table("profiles"). - Where("country = ? AND status = ?", country, "active"). - Count(¤tSubs) - - s.db.WithContext(ctx).Table("profiles"). - Where("country = ? AND status = ? AND created_at < ?", country, "active", prevMonth). - Count(&prevSubs) - - if prevSubs == 0 { - return 0 - } - - return float64(currentSubs-prevSubs) / float64(prevSubs) * 100 -} - -// calculateCountryARPU calculates ARPU for a country -func (s *MarketAnalysisService) calculateCountryARPU(ctx context.Context, country string) float64 { - var totalRevenue float64 - var subscriberCount int64 - - s.db.WithContext(ctx).Table("billing_transactions bt"). - Joins("JOIN profiles p ON bt.profile_id = p.id"). - Where("p.country = ? AND bt.status = ?", country, "completed"). - Select("COALESCE(SUM(bt.amount), 0)"). - Scan(&totalRevenue) - - s.db.WithContext(ctx).Table("profiles"). - Where("country = ? AND status = ?", country, "active"). - Count(&subscriberCount) - - if subscriberCount == 0 { - return 0 - } - - return totalRevenue / float64(subscriberCount) -} - -// calculateMarketPotential calculates market potential for a country -func (s *MarketAnalysisService) calculateMarketPotential(country string) int64 { - population := s.getCountryPopulation(country) - // Assume 20% of population could adopt eSIM in next 2 years - return int64(float64(population) * 0.2) -} - -// calculateCarrierMarketShare calculates market share for a carrier -func (s *MarketAnalysisService) calculateCarrierMarketShare(ctx context.Context, carrier string) float64 { - var carrierSubs, totalSubs int64 - - s.db.WithContext(ctx).Table("rate_plan_subscriptions rps"). - Joins("JOIN rate_plans rp ON rps.rate_plan_id = rp.id"). - Where("rp.carrier_name = ? AND rps.status = ?", carrier, "active"). - Count(&carrierSubs) - - s.db.WithContext(ctx).Table("rate_plan_subscriptions"). - Where("status = ?", "active"). - Count(&totalSubs) - - if totalSubs == 0 { - return 0 - } - - return float64(carrierSubs) / float64(totalSubs) * 100 -} - -// calculateCarrierChurnRate calculates churn rate for a carrier -func (s *MarketAnalysisService) calculateCarrierChurnRate(ctx context.Context, carrier string) float64 { - var totalSubs, churnedSubs int64 - - s.db.WithContext(ctx).Table("rate_plan_subscriptions rps"). - Joins("JOIN rate_plans rp ON rps.rate_plan_id = rp.id"). - Where("rp.carrier_name = ?", carrier). - Count(&totalSubs) - - s.db.WithContext(ctx).Table("rate_plan_subscriptions rps"). - Joins("JOIN rate_plans rp ON rps.rate_plan_id = rp.id"). - Where("rp.carrier_name = ? AND rps.status = ? AND rps.ended_at > ?", - carrier, "cancelled", time.Now().AddDate(0, -1, 0)). - Count(&churnedSubs) - - if totalSubs == 0 { - return 0 - } - - return float64(churnedSubs) / float64(totalSubs) * 100 -} - -// calculateCarrierARPU calculates ARPU for a carrier -func (s *MarketAnalysisService) calculateCarrierARPU(ctx context.Context, carrier string) float64 { - var totalRevenue float64 - var subscriberCount int64 - - s.db.WithContext(ctx).Table("billing_transactions bt"). - Joins("JOIN rate_plan_subscriptions rps ON bt.subscription_id = rps.id"). - Joins("JOIN rate_plans rp ON rps.rate_plan_id = rp.id"). - Where("rp.carrier_name = ? AND bt.status = ?", carrier, "completed"). - Select("COALESCE(SUM(bt.amount), 0)"). - Scan(&totalRevenue) - - s.db.WithContext(ctx).Table("rate_plan_subscriptions rps"). - Joins("JOIN rate_plans rp ON rps.rate_plan_id = rp.id"). - Where("rp.carrier_name = ? AND rps.status = ?", carrier, "active"). - Count(&subscriberCount) - - if subscriberCount == 0 { - return 0 - } - - return totalRevenue / float64(subscriberCount) -} - -// getCarrierNetworkQuality returns network quality score for carrier -func (s *MarketAnalysisService) getCarrierNetworkQuality(carrier string) float64 { - // Simplified network quality scores - quality := map[string]float64{ - "AT&T": 85.0, - "Verizon": 87.0, - "T-Mobile": 83.0, - "Vodafone": 80.0, - "Orange": 78.0, - "Deutsche Telekom": 82.0, - } - return quality[carrier] -} - -// getCarrierCoverage returns coverage percentage for carrier -func (s *MarketAnalysisService) getCarrierCoverage(carrier string) float64 { - // Simplified coverage percentages - coverage := map[string]float64{ - "AT&T": 95.0, - "Verizon": 96.0, - "T-Mobile": 94.0, - "Vodafone": 85.0, - "Orange": 88.0, - "Deutsche Telekom": 92.0, - } - return coverage[carrier] -} - -// calculateDemoMarketShare calculates market share for demographic segment -func (s *MarketAnalysisService) calculateDemoMarketShare(ctx context.Context, segment string) float64 { - var segmentSubs, totalSubs int64 - - s.db.WithContext(ctx).Table("profiles"). - Where("demographic_segment = ? AND status = ?", segment, "active"). - Count(&segmentSubs) - - s.db.WithContext(ctx).Table("profiles"). - Where("status = ?", "active"). - Count(&totalSubs) - - if totalSubs == 0 { - return 0 - } - - return float64(segmentSubs) / float64(totalSubs) * 100 -} - -// calculateDemoARPU calculates ARPU for demographic segment -func (s *MarketAnalysisService) calculateDemoARPU(ctx context.Context, segment string) float64 { - var totalRevenue float64 - var subscriberCount int64 - - s.db.WithContext(ctx).Table("billing_transactions bt"). - Joins("JOIN profiles p ON bt.profile_id = p.id"). - Where("p.demographic_segment = ? AND bt.status = ?", segment, "completed"). - Select("COALESCE(SUM(bt.amount), 0)"). - Scan(&totalRevenue) - - s.db.WithContext(ctx).Table("profiles"). - Where("demographic_segment = ? AND status = ?", segment, "active"). - Count(&subscriberCount) - - if subscriberCount == 0 { - return 0 - } - - return totalRevenue / float64(subscriberCount) -} - -// calculateDemoGrowthRate calculates growth rate for demographic segment -func (s *MarketAnalysisService) calculateDemoGrowthRate(ctx context.Context, segment string) float64 { - var currentSubs, prevSubs int64 - - now := time.Now() - prevMonth := now.AddDate(0, -1, 0) - - s.db.WithContext(ctx).Table("profiles"). - Where("demographic_segment = ? AND status = ?", segment, "active"). - Count(¤tSubs) - - s.db.WithContext(ctx).Table("profiles"). - Where("demographic_segment = ? AND status = ? AND created_at < ?", segment, "active", prevMonth). - Count(&prevSubs) - - if prevSubs == 0 { - return 0 - } - - return float64(currentSubs-prevSubs) / float64(prevSubs) * 100 -} - -// GetMarketTrends returns market penetration trends -func (s *MarketAnalysisService) GetMarketTrends(ctx context.Context, period string) ([]map[string]interface{}, error) { - trends := make([]map[string]interface{}, 0) - - // Generate trend data for the last 12 months - for i := 11; i >= 0; i-- { - date := time.Now().AddDate(0, -i, 0) - - var subs int64 - s.db.WithContext(ctx).Table("profiles"). - Where("status = ? AND created_at <= ?", "active", date). - Count(&subs) - - trend := map[string]interface{}{ - "month": date.Format("2006-01"), - "subscribers": subs, - "market_share": float64(subs) / 550000000 * 100, // vs total market - } - - trends = append(trends, trend) - } - - return trends, nil -} diff --git a/apps/carrier-connector/internal/analytics/service.go b/apps/carrier-connector/internal/analytics/service.go deleted file mode 100644 index 66af19d..0000000 --- a/apps/carrier-connector/internal/analytics/service.go +++ /dev/null @@ -1,183 +0,0 @@ -package analytics - -import ( - "context" - "fmt" - "time" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// Service provides analytics operations -type Service struct { - db *gorm.DB - logger *logrus.Logger -} - -// NewService creates a new analytics service -func NewService(db *gorm.DB, logger *logrus.Logger) *Service { - return &Service{db: db, logger: logger} -} - -// GetDashboard retrieves the main analytics dashboard -func (s *Service) GetDashboard(ctx context.Context, filter *AnalyticsFilter) (*DashboardMetrics, error) { - dashboard := &DashboardMetrics{ - TenantID: filter.TenantID, - Period: fmt.Sprintf("%s to %s", filter.StartDate.Format("2006-01-02"), filter.EndDate.Format("2006-01-02")), - GeneratedAt: time.Now(), - } - - var err error - dashboard.Revenue, err = s.getRevenueMetrics(ctx, filter) - if err != nil { - s.logger.WithError(err).Warn("Failed to get revenue metrics") - } - - dashboard.Subscribers, err = s.getSubscriberStats(ctx, filter) - if err != nil { - s.logger.WithError(err).Warn("Failed to get subscriber stats") - } - - dashboard.Usage, err = s.getUsageMetrics(ctx, filter) - if err != nil { - s.logger.WithError(err).Warn("Failed to get usage metrics") - } - - dashboard.Carriers, err = s.getCarrierMetrics(ctx, filter) - if err != nil { - s.logger.WithError(err).Warn("Failed to get carrier metrics") - } - - dashboard.Geographic, err = s.getGeoMetrics(ctx, filter) - if err != nil { - s.logger.WithError(err).Warn("Failed to get geo metrics") - } - - dashboard.Performance, err = s.getPerformanceStats(ctx, filter) - if err != nil { - s.logger.WithError(err).Warn("Failed to get performance stats") - } - - return dashboard, nil -} - -// GetRevenueAnalytics retrieves detailed revenue analytics -func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *AnalyticsFilter) (*RevenueMetrics, error) { - metrics, err := s.getRevenueMetrics(ctx, filter) - return &metrics, err -} - -func (s *Service) getRevenueMetrics(ctx context.Context, filter *AnalyticsFilter) (RevenueMetrics, error) { - metrics := RevenueMetrics{ - RevenueByCountry: make(map[string]float64), - RevenueByCarrier: make(map[string]float64), - RevenueByPlan: make(map[string]float64), - RevenueByCurrency: make(map[string]float64), - } - - // Query total revenue - var totalRevenue float64 - s.db.WithContext(ctx).Table("billing_transactions"). - Where("tenant_id = ? AND created_at BETWEEN ? AND ? AND status = ?", - filter.TenantID, filter.StartDate, filter.EndDate, "completed"). - Select("COALESCE(SUM(amount), 0)").Scan(&totalRevenue) - metrics.TotalRevenue = totalRevenue - - // Query revenue by country - type countryRevenue struct { - Country string - Total float64 - } - var byCountry []countryRevenue - s.db.WithContext(ctx).Table("billing_transactions"). - Select("country, SUM(amount) as total"). - Where("tenant_id = ? AND created_at BETWEEN ? AND ?", filter.TenantID, filter.StartDate, filter.EndDate). - Group("country").Scan(&byCountry) - for _, cr := range byCountry { - metrics.RevenueByCountry[cr.Country] = cr.Total - } - - return metrics, nil -} - -func (s *Service) getSubscriberStats(ctx context.Context, filter *AnalyticsFilter) (SubscriberStats, error) { - stats := SubscriberStats{ - ByCountry: make(map[string]int64), - ByPlan: make(map[string]int64), - ByStatus: make(map[string]int64), - } - - // Query active subscribers - s.db.WithContext(ctx).Table("profiles"). - Where("tenant_id = ? AND status = ?", filter.TenantID, "active"). - Count(&stats.TotalActive) - - // Query new subscribers in period - s.db.WithContext(ctx).Table("profiles"). - Where("tenant_id = ? AND created_at BETWEEN ? AND ?", filter.TenantID, filter.StartDate, filter.EndDate). - Count(&stats.NewThisPeriod) - - return stats, nil -} - -func (s *Service) getUsageMetrics(ctx context.Context, filter *AnalyticsFilter) (UsageMetrics, error) { - metrics := UsageMetrics{ - UsageByCountry: make(map[string]int64), - UsageByCarrier: make(map[string]int64), - } - - // Query total data usage - s.db.WithContext(ctx).Table("rate_plan_usage"). - Where("created_at BETWEEN ? AND ?", filter.StartDate, filter.EndDate). - Select("COALESCE(SUM(data_used), 0)").Scan(&metrics.TotalDataUsedMB) - - return metrics, nil -} - -func (s *Service) getCarrierMetrics(ctx context.Context, _ *AnalyticsFilter) (CarrierMetrics, error) { - metrics := CarrierMetrics{ - ByCarrier: make(map[string]CarrierStat), - FailuresByReason: make(map[string]int64), - } - - // Query carrier stats - var activeCount int64 - s.db.WithContext(ctx).Table("carriers"). - Where("is_active = ?", true). - Count(&activeCount) - metrics.ActiveCarriers = int(activeCount) - - return metrics, nil -} - -func (s *Service) getGeoMetrics(_ context.Context, _ *AnalyticsFilter) (GeoMetrics, error) { - metrics := GeoMetrics{ - RevenueByContinent: make(map[string]float64), - } - return metrics, nil -} - -func (s *Service) getPerformanceStats(_ context.Context, _ *AnalyticsFilter) (PerformanceStats, error) { - return PerformanceStats{ - Uptime: 99.9, - ErrorRate: 0.1, - }, nil -} - -// CreateScheduledReport creates a scheduled report -func (s *Service) CreateScheduledReport(ctx context.Context, report *ScheduledReport) error { - if err := s.db.WithContext(ctx).Create(report).Error; err != nil { - return fmt.Errorf("failed to create scheduled report: %w", err) - } - return nil -} - -// ListScheduledReports lists scheduled reports for a tenant -func (s *Service) ListScheduledReports(ctx context.Context, tenantID string) ([]*ScheduledReport, error) { - var reports []*ScheduledReport - if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).Find(&reports).Error; err != nil { - return nil, fmt.Errorf("failed to list reports: %w", err) - } - return reports, nil -} diff --git a/apps/carrier-connector/internal/analytics/types.go b/apps/carrier-connector/internal/analytics/types.go deleted file mode 100644 index d2e3232..0000000 --- a/apps/carrier-connector/internal/analytics/types.go +++ /dev/null @@ -1,163 +0,0 @@ -package analytics - -import "time" - -// DashboardMetrics represents the main analytics dashboard data -type DashboardMetrics struct { - TenantID string `json:"tenant_id"` - Period string `json:"period"` - GeneratedAt time.Time `json:"generated_at"` - Revenue RevenueMetrics `json:"revenue"` - Subscribers SubscriberStats `json:"subscribers"` - Usage UsageMetrics `json:"usage"` - Carriers CarrierMetrics `json:"carriers"` - Geographic GeoMetrics `json:"geographic"` - Performance PerformanceStats `json:"performance"` -} - -// RevenueMetrics contains revenue analytics -type RevenueMetrics struct { - TotalRevenue float64 `json:"total_revenue"` - RecurringRevenue float64 `json:"recurring_revenue"` - OneTimeRevenue float64 `json:"one_time_revenue"` - RefundsTotal float64 `json:"refunds_total"` - NetRevenue float64 `json:"net_revenue"` - GrowthRate float64 `json:"growth_rate_pct"` - ARPU float64 `json:"arpu"` - RevenueByCountry map[string]float64 `json:"revenue_by_country"` - RevenueByCarrier map[string]float64 `json:"revenue_by_carrier"` - RevenueByPlan map[string]float64 `json:"revenue_by_plan"` - RevenueByCurrency map[string]float64 `json:"revenue_by_currency"` - DailyRevenue []TimeSeriesPoint `json:"daily_revenue"` - MonthlyRevenue []TimeSeriesPoint `json:"monthly_revenue"` -} - -// SubscriberStats contains subscriber analytics -type SubscriberStats struct { - TotalActive int64 `json:"total_active"` - NewThisPeriod int64 `json:"new_this_period"` - ChurnedThisPeriod int64 `json:"churned_this_period"` - ChurnRate float64 `json:"churn_rate_pct"` - RetentionRate float64 `json:"retention_rate_pct"` - LifetimeValue float64 `json:"lifetime_value"` - ByCountry map[string]int64 `json:"by_country"` - ByPlan map[string]int64 `json:"by_plan"` - ByStatus map[string]int64 `json:"by_status"` - GrowthTrend []TimeSeriesPoint `json:"growth_trend"` -} - -// UsageMetrics contains usage analytics -type UsageMetrics struct { - TotalDataUsedMB int64 `json:"total_data_used_mb"` - TotalVoiceMinutes int64 `json:"total_voice_minutes"` - TotalSMSCount int64 `json:"total_sms_count"` - AverageDataPerUser float64 `json:"avg_data_per_user_mb"` - PeakUsageHour int `json:"peak_usage_hour"` - UsageByCountry map[string]int64 `json:"usage_by_country"` - UsageByCarrier map[string]int64 `json:"usage_by_carrier"` - DailyUsage []TimeSeriesPoint `json:"daily_usage"` -} - -// CarrierMetrics contains carrier performance analytics -type CarrierMetrics struct { - TotalCarriers int `json:"total_carriers"` - ActiveCarriers int `json:"active_carriers"` - AvgSuccessRate float64 `json:"avg_success_rate_pct"` - AvgResponseTime float64 `json:"avg_response_time_ms"` - ByCarrier map[string]CarrierStat `json:"by_carrier"` - FailuresByReason map[string]int64 `json:"failures_by_reason"` -} - -// CarrierStat contains per-carrier statistics -type CarrierStat struct { - CarrierID string `json:"carrier_id"` - CarrierName string `json:"carrier_name"` - TotalRequests int64 `json:"total_requests"` - SuccessRate float64 `json:"success_rate_pct"` - AvgResponseMs float64 `json:"avg_response_ms"` - Revenue float64 `json:"revenue"` - ActiveProfiles int64 `json:"active_profiles"` -} - -// GeoMetrics contains geographic analytics -type GeoMetrics struct { - TopCountries []CountryStat `json:"top_countries"` - TopRegions []RegionStat `json:"top_regions"` - CoverageCountries int `json:"coverage_countries"` - RevenueByContinent map[string]float64 `json:"revenue_by_continent"` -} - -// CountryStat contains per-country statistics -type CountryStat struct { - CountryCode string `json:"country_code"` - CountryName string `json:"country_name"` - Subscribers int64 `json:"subscribers"` - Revenue float64 `json:"revenue"` - DataUsedMB int64 `json:"data_used_mb"` - GrowthRate float64 `json:"growth_rate_pct"` -} - -// RegionStat contains per-region statistics -type RegionStat struct { - Region string `json:"region"` - Subscribers int64 `json:"subscribers"` - Revenue float64 `json:"revenue"` -} - -// PerformanceStats contains system performance metrics -type PerformanceStats struct { - APILatencyP50 float64 `json:"api_latency_p50_ms"` - APILatencyP95 float64 `json:"api_latency_p95_ms"` - APILatencyP99 float64 `json:"api_latency_p99_ms"` - ErrorRate float64 `json:"error_rate_pct"` - Uptime float64 `json:"uptime_pct"` - TotalAPIRequests int64 `json:"total_api_requests"` -} - -// TimeSeriesPoint represents a data point in time series -type TimeSeriesPoint struct { - Timestamp time.Time `json:"timestamp"` - Value float64 `json:"value"` - Label string `json:"label,omitempty"` -} - -// AnalyticsFilter defines filtering options for analytics queries -type AnalyticsFilter struct { - TenantID string `json:"tenant_id"` - StartDate time.Time `json:"start_date"` - EndDate time.Time `json:"end_date"` - Countries []string `json:"countries,omitempty"` - Carriers []string `json:"carriers,omitempty"` - Plans []string `json:"plans,omitempty"` - GroupBy string `json:"group_by,omitempty"` - Granularity string `json:"granularity,omitempty"` -} - -// ReportType defines available report types -type ReportType string - -const ( - ReportTypeRevenue ReportType = "revenue" - ReportTypeSubscribers ReportType = "subscribers" - ReportTypeUsage ReportType = "usage" - ReportTypeCarriers ReportType = "carriers" - ReportTypeGeographic ReportType = "geographic" - ReportTypeExecutive ReportType = "executive" -) - -// ScheduledReport defines a scheduled analytics report -type ScheduledReport struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - Name string `json:"name"` - ReportType ReportType `json:"report_type"` - Schedule string `json:"schedule"` - Recipients []string `json:"recipients" gorm:"serializer:json"` - Filter AnalyticsFilter `json:"filter" gorm:"serializer:json"` - Format string `json:"format"` - IsActive bool `json:"is_active"` - LastRunAt *time.Time `json:"last_run_at"` - NextRunAt *time.Time `json:"next_run_at"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} diff --git a/apps/carrier-connector/internal/compliance/service.go b/apps/carrier-connector/internal/compliance/service.go deleted file mode 100644 index 267d01d..0000000 --- a/apps/carrier-connector/internal/compliance/service.go +++ /dev/null @@ -1,212 +0,0 @@ -package compliance - -import ( - "context" - "fmt" - "time" - - "github.com/google/uuid" - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// Service provides compliance management operations -type Service struct { - db *gorm.DB - logger *logrus.Logger -} - -// NewService creates a new compliance service -func NewService(db *gorm.DB, logger *logrus.Logger) *Service { - return &Service{db: db, logger: logger} -} - -// CreateDSR creates a new data subject request -func (s *Service) CreateDSR(ctx context.Context, req *DataSubjectRequest) error { - req.ID = uuid.New().String() - req.Status = DSRStatusPending - req.RequestedAt = time.Now() - req.DueDate = s.calculateDueDate(req.Regulation) - req.CreatedAt = time.Now() - req.UpdatedAt = time.Now() - - if err := s.db.WithContext(ctx).Create(req).Error; err != nil { - return fmt.Errorf("failed to create DSR: %w", err) - } - - s.logger.WithFields(logrus.Fields{ - "dsr_id": req.ID, - "type": req.RequestType, - "regulation": req.Regulation, - }).Info("Data subject request created") - - return nil -} - -func (s *Service) calculateDueDate(reg Regulation) time.Time { - switch reg { - case RegulationGDPR: - return time.Now().AddDate(0, 1, 0) // 30 days - case RegulationCCPA: - return time.Now().AddDate(0, 0, 45) // 45 days - default: - return time.Now().AddDate(0, 1, 0) - } -} - -// GetDSR retrieves a data subject request -func (s *Service) GetDSR(ctx context.Context, id string) (*DataSubjectRequest, error) { - var req DataSubjectRequest - if err := s.db.WithContext(ctx).First(&req, "id = ?", id).Error; err != nil { - return nil, fmt.Errorf("DSR not found: %w", err) - } - return &req, nil -} - -// ListDSRs lists data subject requests for a tenant -func (s *Service) ListDSRs(ctx context.Context, tenantID string, status *DSRStatus) ([]*DataSubjectRequest, error) { - var requests []*DataSubjectRequest - query := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID) - if status != nil { - query = query.Where("status = ?", *status) - } - if err := query.Order("created_at DESC").Find(&requests).Error; err != nil { - return nil, fmt.Errorf("failed to list DSRs: %w", err) - } - return requests, nil -} - -// ProcessDSR processes a data subject request -func (s *Service) ProcessDSR(ctx context.Context, id string) error { - req, err := s.GetDSR(ctx, id) - if err != nil { - return err - } - - req.Status = DSRStatusProcessing - req.UpdatedAt = time.Now() - - switch req.RequestType { - case DSRTypeAccess, DSRTypePortability: - return s.processAccessRequest(ctx, req) - case DSRTypeErasure: - return s.processErasureRequest(ctx, req) - case DSRTypeRectify: - return s.processRectificationRequest(ctx, req) - default: - return fmt.Errorf("unsupported request type: %s", req.RequestType) - } -} - -func (s *Service) processAccessRequest(ctx context.Context, req *DataSubjectRequest) error { - // Export user data - s.logger.WithField("dsr_id", req.ID).Info("Processing access request") - now := time.Now() - req.Status = DSRStatusCompleted - req.CompletedAt = &now - return s.db.WithContext(ctx).Save(req).Error -} - -func (s *Service) processErasureRequest(ctx context.Context, req *DataSubjectRequest) error { - s.logger.WithField("dsr_id", req.ID).Info("Processing erasure request") - now := time.Now() - req.Status = DSRStatusCompleted - req.CompletedAt = &now - return s.db.WithContext(ctx).Save(req).Error -} - -func (s *Service) processRectificationRequest(ctx context.Context, req *DataSubjectRequest) error { - s.logger.WithField("dsr_id", req.ID).Info("Processing rectification request") - now := time.Now() - req.Status = DSRStatusCompleted - req.CompletedAt = &now - return s.db.WithContext(ctx).Save(req).Error -} - -// RecordConsent records user consent -func (s *Service) RecordConsent(ctx context.Context, consent *ConsentRecord) error { - consent.ID = uuid.New().String() - consent.CreatedAt = time.Now() - consent.UpdatedAt = time.Now() - if consent.Granted { - now := time.Now() - consent.GrantedAt = &now - } - return s.db.WithContext(ctx).Create(consent).Error -} - -// RevokeConsent revokes user consent -func (s *Service) RevokeConsent(ctx context.Context, subjectID string, consentType ConsentType) error { - now := time.Now() - return s.db.WithContext(ctx).Model(&ConsentRecord{}). - Where("subject_id = ? AND consent_type = ?", subjectID, consentType). - Updates(map[string]any{"granted": false, "revoked_at": now, "updated_at": now}).Error -} - -// GetConsents retrieves consent records for a subject -func (s *Service) GetConsents(ctx context.Context, subjectID string) ([]*ConsentRecord, error) { - var consents []*ConsentRecord - if err := s.db.WithContext(ctx).Where("subject_id = ?", subjectID).Find(&consents).Error; err != nil { - return nil, err - } - return consents, nil -} - -// LogAudit creates an audit log entry -func (s *Service) LogAudit(ctx context.Context, log *AuditLog) error { - log.ID = uuid.New().String() - log.Timestamp = time.Now() - return s.db.WithContext(ctx).Create(log).Error -} - -// QueryAuditLogs queries audit logs with filters -func (s *Service) QueryAuditLogs(ctx context.Context, tenantID string, filter *AuditLogFilter) ([]*AuditLog, error) { - var logs []*AuditLog - query := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID) - if filter.Jurisdiction != "" { - query = query.Where("jurisdiction = ?", filter.Jurisdiction) - } - if filter.ActorID != "" { - query = query.Where("actor_id = ?", filter.ActorID) - } - if filter.Action != "" { - query = query.Where("action = ?", filter.Action) - } - if !filter.StartDate.IsZero() { - query = query.Where("timestamp >= ?", filter.StartDate) - } - if !filter.EndDate.IsZero() { - query = query.Where("timestamp <= ?", filter.EndDate) - } - if err := query.Order("timestamp DESC").Limit(filter.Limit).Find(&logs).Error; err != nil { - return nil, err - } - return logs, nil -} - -// AuditLogFilter defines audit log query filters -type AuditLogFilter struct { - Jurisdiction string - ActorID string - Action string - StartDate time.Time - EndDate time.Time - Limit int -} - -// SetDataResidency sets data residency configuration -func (s *Service) SetDataResidency(ctx context.Context, config *DataResidencyConfig) error { - config.ID = uuid.New().String() - config.CreatedAt = time.Now() - config.UpdatedAt = time.Now() - return s.db.WithContext(ctx).Save(config).Error -} - -// GetDataResidency retrieves data residency configuration -func (s *Service) GetDataResidency(ctx context.Context, tenantID string) (*DataResidencyConfig, error) { - var config DataResidencyConfig - if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).First(&config).Error; err != nil { - return nil, err - } - return &config, nil -} diff --git a/apps/carrier-connector/internal/compliance/types.go b/apps/carrier-connector/internal/compliance/types.go deleted file mode 100644 index 2af2a67..0000000 --- a/apps/carrier-connector/internal/compliance/types.go +++ /dev/null @@ -1,153 +0,0 @@ -package compliance - -import "time" - -// Regulation represents a compliance regulation -type Regulation string - -const ( - RegulationGDPR Regulation = "GDPR" - RegulationCCPA Regulation = "CCPA" - RegulationLGPD Regulation = "LGPD" - RegulationPDPA Regulation = "PDPA" - RegulationPIPL Regulation = "PIPL" -) - -// ConsentType represents types of data processing consent -type ConsentType string - -const ( - ConsentTypeMarketing ConsentType = "marketing" - ConsentTypeAnalytics ConsentType = "analytics" - ConsentTypeThirdParty ConsentType = "third_party" - ConsentTypeDataSharing ConsentType = "data_sharing" - ConsentTypePersonalized ConsentType = "personalized" -) - -// DataSubjectRequest represents a GDPR/CCPA data subject request -type DataSubjectRequest struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - SubjectID string `json:"subject_id" gorm:"index"` - SubjectEmail string `json:"subject_email"` - RequestType DSRType `json:"request_type"` - Regulation Regulation `json:"regulation"` - Status DSRStatus `json:"status" gorm:"index"` - RequestedAt time.Time `json:"requested_at"` - VerifiedAt *time.Time `json:"verified_at"` - CompletedAt *time.Time `json:"completed_at"` - DueDate time.Time `json:"due_date"` - Notes string `json:"notes"` - DataExportURL string `json:"data_export_url,omitempty"` - Metadata map[string]any `json:"metadata" gorm:"serializer:json"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -// DSRType represents data subject request types -type DSRType string - -const ( - DSRTypeAccess DSRType = "access" - DSRTypeRectify DSRType = "rectification" - DSRTypeErasure DSRType = "erasure" - DSRTypePortability DSRType = "portability" - DSRTypeRestrict DSRType = "restriction" - DSRTypeObject DSRType = "objection" -) - -// DSRStatus represents request status -type DSRStatus string - -const ( - DSRStatusPending DSRStatus = "pending" - DSRStatusVerifying DSRStatus = "verifying" - DSRStatusProcessing DSRStatus = "processing" - DSRStatusCompleted DSRStatus = "completed" - DSRStatusRejected DSRStatus = "rejected" -) - -// ConsentRecord tracks user consent -type ConsentRecord struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - SubjectID string `json:"subject_id" gorm:"index"` - ConsentType ConsentType `json:"consent_type"` - Granted bool `json:"granted"` - GrantedAt *time.Time `json:"granted_at"` - RevokedAt *time.Time `json:"revoked_at"` - IPAddress string `json:"ip_address"` - UserAgent string `json:"user_agent"` - ConsentText string `json:"consent_text"` - Version string `json:"version"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -// DataResidencyConfig defines data residency requirements -type DataResidencyConfig struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"uniqueIndex"` - PrimaryRegion string `json:"primary_region"` - AllowedRegions []string `json:"allowed_regions" gorm:"serializer:json"` - RestrictedData []string `json:"restricted_data" gorm:"serializer:json"` - EncryptionReq bool `json:"encryption_required"` - RetentionDays int `json:"retention_days"` - CrossBorderRules []CrossBorderRule `json:"cross_border_rules" gorm:"serializer:json"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -// CrossBorderRule defines rules for cross-border data transfer -type CrossBorderRule struct { - FromRegion string `json:"from_region"` - ToRegion string `json:"to_region"` - Allowed bool `json:"allowed"` - RequiresSCC bool `json:"requires_scc"` - RequiresDPIA bool `json:"requires_dpia"` -} - -// AuditLog represents a compliance audit log entry -type AuditLog struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - Jurisdiction string `json:"jurisdiction" gorm:"index"` - ActorID string `json:"actor_id" gorm:"index"` - ActorType string `json:"actor_type"` - Action string `json:"action" gorm:"index"` - ResourceType string `json:"resource_type"` - ResourceID string `json:"resource_id"` - OldValue map[string]any `json:"old_value" gorm:"serializer:json"` - NewValue map[string]any `json:"new_value" gorm:"serializer:json"` - IPAddress string `json:"ip_address"` - UserAgent string `json:"user_agent"` - Timestamp time.Time `json:"timestamp" gorm:"index"` - Metadata map[string]any `json:"metadata" gorm:"serializer:json"` -} - -// RegulatoryReport represents a regulatory compliance report -type RegulatoryReport struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - Regulation Regulation `json:"regulation"` - ReportType string `json:"report_type"` - PeriodStart time.Time `json:"period_start"` - PeriodEnd time.Time `json:"period_end"` - Status string `json:"status"` - FileURL string `json:"file_url"` - SubmittedAt *time.Time `json:"submitted_at"` - GeneratedAt time.Time `json:"generated_at"` - CreatedAt time.Time `json:"created_at"` -} - -// PrivacyPolicy tracks privacy policy versions -type PrivacyPolicy struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - Version string `json:"version"` - Content string `json:"content"` - Regulation Regulation `json:"regulation"` - EffectiveAt time.Time `json:"effective_at"` - IsActive bool `json:"is_active"` - CreatedAt time.Time `json:"created_at"` -} diff --git a/apps/carrier-connector/internal/currency/exchange_rates.go b/apps/carrier-connector/internal/currency/exchange_rates.go deleted file mode 100644 index 62840eb..0000000 --- a/apps/carrier-connector/internal/currency/exchange_rates.go +++ /dev/null @@ -1,267 +0,0 @@ -package currency - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "sync" - "time" - - "github.com/sirupsen/logrus" -) - -// RateProviderType defines external rate providers -type RateProviderType string - -const ( - ProviderOpenExchange RateProviderType = "openexchangerates" - ProviderFixer RateProviderType = "fixer" - ProviderXE RateProviderType = "xe" - ProviderInternal RateProviderType = "internal" -) - -// RealTimeExchangeService provides real-time exchange rates -type RealTimeExchangeService struct { - provider RateProviderType - apiKey string - baseCurrency string - rates map[string]float64 - lastUpdate time.Time - cacheTTL time.Duration - mu sync.RWMutex - logger *logrus.Logger - httpClient *http.Client -} - -// ExchangeRateConfig configures the exchange rate service -type ExchangeRateConfig struct { - Provider RateProviderType - APIKey string - BaseCurrency string - CacheTTL time.Duration -} - -// NewRealTimeExchangeService creates a new exchange rate service -func NewRealTimeExchangeService(config ExchangeRateConfig, logger *logrus.Logger) *RealTimeExchangeService { - if config.CacheTTL == 0 { - config.CacheTTL = 15 * time.Minute - } - if config.BaseCurrency == "" { - config.BaseCurrency = "USD" - } - - svc := &RealTimeExchangeService{ - provider: config.Provider, - apiKey: config.APIKey, - baseCurrency: config.BaseCurrency, - rates: make(map[string]float64), - cacheTTL: config.CacheTTL, - logger: logger, - httpClient: &http.Client{Timeout: 10 * time.Second}, - } - - // Initialize with default rates - svc.initDefaultRates() - - return svc -} - -func (s *RealTimeExchangeService) initDefaultRates() { - s.rates = map[string]float64{ - "USD": 1.0, - "EUR": 0.92, - "GBP": 0.79, - "JPY": 149.50, - "CNY": 7.24, - "INR": 83.12, - "BRL": 4.97, - "CAD": 1.36, - "AUD": 1.53, - "CHF": 0.88, - "SGD": 1.34, - "HKD": 7.82, - "KRW": 1320.0, - "MXN": 17.15, - "ZAR": 18.50, - } - s.lastUpdate = time.Now() -} - -// GetRate returns the exchange rate for a currency pair -func (s *RealTimeExchangeService) GetRate(ctx context.Context, from, to string) (float64, error) { - s.mu.RLock() - needsRefresh := time.Since(s.lastUpdate) > s.cacheTTL - s.mu.RUnlock() - - if needsRefresh { - if err := s.RefreshRates(ctx); err != nil { - s.logger.WithError(err).Warn("Failed to refresh rates, using cached") - } - } - - s.mu.RLock() - defer s.mu.RUnlock() - - fromRate, fromOK := s.rates[from] - toRate, toOK := s.rates[to] - - if !fromOK { - return 0, fmt.Errorf("unknown currency: %s", from) - } - if !toOK { - return 0, fmt.Errorf("unknown currency: %s", to) - } - - // Convert through base currency - return toRate / fromRate, nil -} - -// Convert converts an amount between currencies -func (s *RealTimeExchangeService) Convert(ctx context.Context, amount float64, from, to string) (float64, error) { - rate, err := s.GetRate(ctx, from, to) - if err != nil { - return 0, err - } - return amount * rate, nil -} - -// RefreshRates fetches latest rates from provider -func (s *RealTimeExchangeService) RefreshRates(ctx context.Context) error { - var err error - - switch s.provider { - case ProviderOpenExchange: - err = s.fetchOpenExchangeRates(ctx) - case ProviderFixer: - err = s.fetchFixerRates(ctx) - case ProviderInternal: - // Use internal rates, no fetch needed - return nil - default: - return fmt.Errorf("unsupported provider: %s", s.provider) - } - - if err != nil { - return err - } - - s.mu.Lock() - s.lastUpdate = time.Now() - s.mu.Unlock() - - s.logger.Info("Exchange rates refreshed") - return nil -} - -func (s *RealTimeExchangeService) fetchOpenExchangeRates(ctx context.Context) error { - url := fmt.Sprintf("https://openexchangerates.org/api/latest.json?app_id=%s&base=%s", - s.apiKey, s.baseCurrency) - - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return err - } - - resp, err := s.httpClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("API returned status %d", resp.StatusCode) - } - - var result struct { - Rates map[string]float64 `json:"rates"` - } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return err - } - - s.mu.Lock() - for currency, rate := range result.Rates { - s.rates[currency] = rate - } - s.mu.Unlock() - - return nil -} - -func (s *RealTimeExchangeService) fetchFixerRates(ctx context.Context) error { - url := fmt.Sprintf("http://data.fixer.io/api/latest?access_key=%s&base=%s", - s.apiKey, s.baseCurrency) - - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return err - } - - resp, err := s.httpClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - var result struct { - Success bool `json:"success"` - Rates map[string]float64 `json:"rates"` - } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return err - } - - if !result.Success { - return fmt.Errorf("fixer API returned error") - } - - s.mu.Lock() - for currency, rate := range result.Rates { - s.rates[currency] = rate - } - s.mu.Unlock() - - return nil -} - -// GetAllRates returns all cached rates -func (s *RealTimeExchangeService) GetAllRates() map[string]float64 { - s.mu.RLock() - defer s.mu.RUnlock() - - rates := make(map[string]float64) - for k, v := range s.rates { - rates[k] = v - } - return rates -} - -// GetSupportedCurrencies returns list of supported currencies -func (s *RealTimeExchangeService) GetSupportedCurrencies() []string { - s.mu.RLock() - defer s.mu.RUnlock() - - currencies := make([]string, 0, len(s.rates)) - for c := range s.rates { - currencies = append(currencies, c) - } - return currencies -} - -// LastUpdateTime returns when rates were last updated -func (s *RealTimeExchangeService) LastUpdateTime() time.Time { - s.mu.RLock() - defer s.mu.RUnlock() - return s.lastUpdate -} - -// ExchangeRateHistory stores historical exchange rates -type ExchangeRateHistory struct { - ID string `json:"id" gorm:"primaryKey"` - FromCurr string `json:"from_currency" gorm:"index"` - ToCurr string `json:"to_currency" gorm:"index"` - Rate float64 `json:"rate"` - Provider string `json:"provider"` - Timestamp time.Time `json:"timestamp" gorm:"index"` -} diff --git a/apps/carrier-connector/internal/currency/services/rateplan_core.go b/apps/carrier-connector/internal/currency/services/rateplan_core.go new file mode 100644 index 0000000..7bdf709 --- /dev/null +++ b/apps/carrier-connector/internal/currency/services/rateplan_core.go @@ -0,0 +1,208 @@ +package services + +import ( + "context" + "fmt" + "time" + + "github.com/sirupsen/logrus" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency" + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan" +) + +// RatePlanCurrencyIntegrator integrates currency system with rate plans +type RatePlanCurrencyIntegrator struct { + billingService currency.BillingService + exchangeService currency.ExchangeRateService + ratePlanService rateplan.Service + logger *logrus.Logger + baseCurrency string +} + +// NewRatePlanCurrencyIntegrator creates a new rate plan currency integrator +func NewRatePlanCurrencyIntegrator( + billingService currency.BillingService, + exchangeService currency.ExchangeRateService, + ratePlanService rateplan.Service, + logger *logrus.Logger, + baseCurrency string, +) *RatePlanCurrencyIntegrator { + return &RatePlanCurrencyIntegrator{ + billingService: billingService, + exchangeService: exchangeService, + ratePlanService: ratePlanService, + logger: logger, + baseCurrency: baseCurrency, + } +} + +// SubscribeToPlanWithCurrency subscribes to a rate plan with currency conversion +func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, targetCurrency string) (*rateplan.RatePlanSubscription, error) { + // Get the rate plan + plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID) + if err != nil { + return nil, fmt.Errorf("failed to get rate plan: %w", err) + } + + // Convert price to requested currency if needed + subscriptionPrice := plan.BasePrice + exchangeRate := 1.0 + + if targetCurrency != plan.Currency { + conversion, err := rpci.billingService.ConvertAmount(ctx, ¤cy.CurrencyConversionRequest{ + Amount: plan.BasePrice, + FromCurrency: plan.Currency, + ToCurrency: targetCurrency, + }) + if err != nil { + rpci.logger.WithError(err).Error("Failed to convert rate plan price") + return nil, fmt.Errorf("currency conversion failed: %w", err) + } + subscriptionPrice = conversion.ConvertedAmount + exchangeRate = conversion.ExchangeRate + } + + // Create subscription with currency information + subscription := &rateplan.RatePlanSubscription{ + ProfileID: profileID, + RatePlanID: planID, + Status: rateplan.SubscriptionStatusActive, + StartedAt: time.Now(), + Metadata: map[string]any{ + "original_currency": plan.Currency, + "subscription_currency": targetCurrency, + "original_price": plan.BasePrice, + "subscription_price": subscriptionPrice, + "exchange_rate": exchangeRate, + }, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + // Create subscription request + subscribeReq := &rateplan.SubscribeRequest{ + ProfileID: profileID, + RatePlanID: planID, + AutoRenew: true, + Metadata: subscription.Metadata, + } + + createdSubscription, err := rpci.ratePlanService.SubscribeToPlan(ctx, subscribeReq) + if err != nil { + return nil, fmt.Errorf("failed to create subscription: %w", err) + } + + // Process initial billing + billingReq := ¤cy.BillingRequest{ + ProfileID: profileID, + SubscriptionID: createdSubscription.ID, + Amount: subscriptionPrice, + Currency: targetCurrency, + Description: fmt.Sprintf("Initial subscription to %s", plan.Name), + BillingDate: time.Now(), + } + + _, err = rpci.billingService.ProcessBilling(ctx, billingReq) + if err != nil { + rpci.logger.WithError(err).Error("Failed to process initial billing") + // Don't fail the subscription if billing fails, but log it + } + + rpci.logger.WithFields(logrus.Fields{ + "profile_id": profileID, + "plan_id": planID, + "currency": targetCurrency, + "subscription_id": createdSubscription.ID, + }).Info("Rate plan subscription created with currency support") + + return createdSubscription, nil +} + +// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency +func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, targetCurrency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) { + // Get the rate plan + plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID) + if err != nil { + return nil, fmt.Errorf("failed to get rate plan: %w", err) + } + + // Calculate base cost + baseCost := plan.BasePrice + + // Add overage costs if usage data is provided + if usageData != nil { + overageCost, err := rpci.calculateOverageCost(ctx, plan, usageData) + if err != nil { + rpci.logger.WithError(err).Warn("Failed to calculate overage cost") + } else { + baseCost += overageCost + } + } + + // Convert to requested currency + convertedCost := baseCost + exchangeRate := 1.0 + + if targetCurrency != plan.Currency { + conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, targetCurrency) + if err != nil { + return nil, fmt.Errorf("currency conversion failed: %w", err) + } + convertedCost = conversion.ConvertedAmount + exchangeRate = conversion.ExchangeRate + } + + // Create billing summary + summary := ¤cy.BillingSummary{ + ProfileID: usageData.ProfileID, + TotalAmount: convertedCost, + Currency: targetCurrency, + BaseTotalAmount: baseCost, + BaseCurrency: plan.Currency, + TransactionCount: 1, + FromDate: time.Now().AddDate(0, -1, 0), + ToDate: time.Now(), + Breakdown: map[string]any{ + "plan_id": planID, + "plan_name": plan.Name, + "base_cost": plan.BasePrice, + "overage_cost": baseCost - plan.BasePrice, + "exchange_rate": exchangeRate, + "original_currency": plan.Currency, + }, + } + + return summary, nil +} + +// calculateOverageCost calculates overage costs for usage +func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context, plan *rateplan.RatePlan, usage *rateplan.RatePlanUsage) (float64, error) { + overageCost := 0.0 + + // Calculate data overage + if plan.DataAllowance != nil && usage.DataUsed > plan.DataAllowance.Amount { + dataOverage := usage.DataUsed - plan.DataAllowance.Amount + if plan.OverageRates != nil { + overageCost += float64(dataOverage) * plan.OverageRates.DataRate + } + } + + // Calculate voice overage + if plan.VoiceAllowance != nil && usage.VoiceUsed > plan.VoiceAllowance.Minutes { + voiceOverage := usage.VoiceUsed - plan.VoiceAllowance.Minutes + if plan.OverageRates != nil { + overageCost += float64(voiceOverage) * plan.OverageRates.VoiceRate + } + } + + // Calculate SMS overage + if plan.SMSAllowance != nil && usage.SMSUsed > plan.SMSAllowance.Messages { + smsOverage := usage.SMSUsed - plan.SMSAllowance.Messages + if plan.OverageRates != nil { + overageCost += float64(smsOverage) * plan.OverageRates.SMSRate + } + } + + return overageCost, nil +} diff --git a/apps/carrier-connector/internal/handlers/analytics_handlers.go b/apps/carrier-connector/internal/handlers/analytics_handlers.go deleted file mode 100644 index 54bac4a..0000000 --- a/apps/carrier-connector/internal/handlers/analytics_handlers.go +++ /dev/null @@ -1,103 +0,0 @@ -package handlers - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/analytics" - "github.com/sirupsen/logrus" -) - -// AnalyticsHandler handles analytics API requests -type AnalyticsHandler struct { - service *analytics.Service - logger *logrus.Logger -} - -// NewAnalyticsHandler creates a new analytics handler -func NewAnalyticsHandler(service *analytics.Service, logger *logrus.Logger) *AnalyticsHandler { - return &AnalyticsHandler{service: service, logger: logger} -} - -// GetDashboard returns the main analytics dashboard -func (h *AnalyticsHandler) GetDashboard(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - if tenantID == "" { - tenantID = c.Query("tenant_id") - } - - filter := &analytics.AnalyticsFilter{ - TenantID: tenantID, - StartDate: time.Now().AddDate(0, -1, 0), - EndDate: time.Now(), - } - - if start := c.Query("start_date"); start != "" { - if t, err := time.Parse("2006-01-02", start); err == nil { - filter.StartDate = t - } - } - if end := c.Query("end_date"); end != "" { - if t, err := time.Parse("2006-01-02", end); err == nil { - filter.EndDate = t - } - } - - dashboard, err := h.service.GetDashboard(c.Request.Context(), filter) - if err != nil { - h.logger.WithError(err).Error("Failed to get dashboard") - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, dashboard) -} - -// GetRevenueAnalytics returns detailed revenue analytics -func (h *AnalyticsHandler) GetRevenueAnalytics(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - filter := &analytics.AnalyticsFilter{ - TenantID: tenantID, - StartDate: time.Now().AddDate(0, -1, 0), - EndDate: time.Now(), - GroupBy: c.Query("group_by"), - } - - revenue, err := h.service.GetRevenueAnalytics(c.Request.Context(), filter) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, revenue) -} - -// CreateScheduledReport creates a scheduled report -func (h *AnalyticsHandler) CreateScheduledReport(c *gin.Context) { - var report analytics.ScheduledReport - if err := c.ShouldBindJSON(&report); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - report.TenantID = c.GetHeader("X-Tenant-ID") - if err := h.service.CreateScheduledReport(c.Request.Context(), &report); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, report) -} - -// ListScheduledReports lists scheduled reports -func (h *AnalyticsHandler) ListScheduledReports(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - reports, err := h.service.ListScheduledReports(c.Request.Context(), tenantID) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, reports) -} diff --git a/apps/carrier-connector/internal/handlers/compliance_handlers.go b/apps/carrier-connector/internal/handlers/compliance_handlers.go deleted file mode 100644 index bdf42fe..0000000 --- a/apps/carrier-connector/internal/handlers/compliance_handlers.go +++ /dev/null @@ -1,171 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/compliance" - "github.com/sirupsen/logrus" -) - -// ComplianceHandler handles compliance API requests -type ComplianceHandler struct { - service *compliance.Service - logger *logrus.Logger -} - -// NewComplianceHandler creates a new compliance handler -func NewComplianceHandler(service *compliance.Service, logger *logrus.Logger) *ComplianceHandler { - return &ComplianceHandler{service: service, logger: logger} -} - -// CreateDSR creates a data subject request -func (h *ComplianceHandler) CreateDSR(c *gin.Context) { - var req compliance.DataSubjectRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - req.TenantID = c.GetHeader("X-Tenant-ID") - if err := h.service.CreateDSR(c.Request.Context(), &req); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, req) -} - -// GetDSR retrieves a data subject request -func (h *ComplianceHandler) GetDSR(c *gin.Context) { - id := c.Param("id") - dsr, err := h.service.GetDSR(c.Request.Context(), id) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, dsr) -} - -// ListDSRs lists data subject requests -func (h *ComplianceHandler) ListDSRs(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - var status *compliance.DSRStatus - if s := c.Query("status"); s != "" { - st := compliance.DSRStatus(s) - status = &st - } - - dsrs, err := h.service.ListDSRs(c.Request.Context(), tenantID, status) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, dsrs) -} - -// ProcessDSR processes a data subject request -func (h *ComplianceHandler) ProcessDSR(c *gin.Context) { - id := c.Param("id") - if err := h.service.ProcessDSR(c.Request.Context(), id); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "processing"}) -} - -// RecordConsent records user consent -func (h *ComplianceHandler) RecordConsent(c *gin.Context) { - var consent compliance.ConsentRecord - if err := c.ShouldBindJSON(&consent); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - consent.TenantID = c.GetHeader("X-Tenant-ID") - consent.IPAddress = c.ClientIP() - consent.UserAgent = c.GetHeader("User-Agent") - - if err := h.service.RecordConsent(c.Request.Context(), &consent); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, consent) -} - -// RevokeConsent revokes user consent -func (h *ComplianceHandler) RevokeConsent(c *gin.Context) { - subjectID := c.Param("subject_id") - consentType := compliance.ConsentType(c.Query("type")) - - if err := h.service.RevokeConsent(c.Request.Context(), subjectID, consentType); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "revoked"}) -} - -// GetConsents retrieves consent records for a subject -func (h *ComplianceHandler) GetConsents(c *gin.Context) { - subjectID := c.Param("subject_id") - consents, err := h.service.GetConsents(c.Request.Context(), subjectID) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, consents) -} - -// QueryAuditLogs queries audit logs -func (h *ComplianceHandler) QueryAuditLogs(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - filter := &compliance.AuditLogFilter{ - Jurisdiction: c.Query("jurisdiction"), - ActorID: c.Query("actor_id"), - Action: c.Query("action"), - Limit: 100, - } - - logs, err := h.service.QueryAuditLogs(c.Request.Context(), tenantID, filter) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, logs) -} - -// SetDataResidency sets data residency configuration -func (h *ComplianceHandler) SetDataResidency(c *gin.Context) { - var config compliance.DataResidencyConfig - if err := c.ShouldBindJSON(&config); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - config.TenantID = c.GetHeader("X-Tenant-ID") - if err := h.service.SetDataResidency(c.Request.Context(), &config); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, config) -} - -// GetDataResidency retrieves data residency configuration -func (h *ComplianceHandler) GetDataResidency(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - config, err := h.service.GetDataResidency(c.Request.Context(), tenantID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, config) -} diff --git a/apps/carrier-connector/internal/handlers/handler_management.go b/apps/carrier-connector/internal/handlers/handler_management.go deleted file mode 100644 index c348225..0000000 --- a/apps/carrier-connector/internal/handlers/handler_management.go +++ /dev/null @@ -1,121 +0,0 @@ -package handlers - -import ( - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/mvno" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" -) - -// ManagementHandler handles MVNO management HTTP requests -type ManagementHandler struct { - repo repository.Repository - logger *logrus.Logger -} - -// NewManagementHandler creates a new management handler -func NewManagementHandler(repo repository.Repository, logger *logrus.Logger) *ManagementHandler { - return &ManagementHandler{ - repo: repo, - logger: logger, - } -} - -// GetMVNO handles GET /mvno/{id} -func (h *ManagementHandler) GetMVNO(c *gin.Context) { - id := c.Param("id") - if id == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "MVNO ID is required"}) - return - } - - mvno, err := h.repo.GetMVNO(c.Request.Context(), id) - if err != nil { - h.logger.WithError(err).WithField("mvno_id", id).Error("Failed to get MVNO") - c.JSON(http.StatusNotFound, gin.H{"error": "MVNO not found"}) - return - } - - c.JSON(http.StatusOK, mvno) -} - -// ListMVNOs handles GET /mvno -func (h *ManagementHandler) ListMVNOs(c *gin.Context) { - filter := &mvno.MVNOFilter{} - - // Parse query parameters - if status := c.Query("status"); status != "" { - filter.Status = mvno.MVNOStatus(status) - } - if plan := c.Query("plan"); plan != "" { - filter.Plan = mvno.MVNOPlan(plan) - } - if limit := c.Query("limit"); limit != "" { - if l, err := strconv.Atoi(limit); err == nil { - filter.Limit = l - } - } - - mvnos, err := h.repo.ListMVNOs(c.Request.Context(), filter) - if err != nil { - h.logger.WithError(err).Error("Failed to list MVNOs") - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list MVNOs"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "mvnos": mvnos, - "count": len(mvnos), - }) -} - -// UpdateMVNOStatus handles PUT /mvno/{id}/status -func (h *ManagementHandler) UpdateMVNOStatus(c *gin.Context) { - id := c.Param("id") - if id == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "MVNO ID is required"}) - return - } - - var req struct { - Status mvno.MVNOStatus `json:"status" binding:"required"` - } - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if err := h.repo.UpdateMVNOStatus(c.Request.Context(), id, req.Status); err != nil { - h.logger.WithError(err).WithField("mvno_id", id).Error("Failed to update MVNO status") - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update status"}) - return - } - - h.logger.WithFields(logrus.Fields{ - "mvno_id": id, - "new_status": req.Status, - }).Info("MVNO status updated") - - c.JSON(http.StatusOK, gin.H{ - "mvno_id": id, - "status": req.Status, - }) -} - -// GetMVNOStats handles GET /mvno/stats -func (h *ManagementHandler) GetMVNOStats(c *gin.Context) { - stats, err := h.repo.GetMVNOStats(c.Request.Context()) - if err != nil { - h.logger.WithError(err).Error("Failed to get MVNO stats") - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get statistics"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "stats": stats, - }) -} diff --git a/apps/carrier-connector/internal/handlers/handler_mvno.go b/apps/carrier-connector/internal/handlers/handler_mvno.go deleted file mode 100644 index 32617fb..0000000 --- a/apps/carrier-connector/internal/handlers/handler_mvno.go +++ /dev/null @@ -1,117 +0,0 @@ -package handlers - -import ( - "context" - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/mvno" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" - "github.com/sirupsen/logrus" -) - -// OnboardingService defines the interface for MVNO onboarding operations -type OnboardingService interface { - StartOnboarding(ctx context.Context, req *mvno.OnboardingRequest) (*mvno.MVNO, error) -} - -// MVNOHandler handles HTTP requests for MVNO operations -type MVNOHandler struct { - service OnboardingService - repo repository.Repository - logger *logrus.Logger -} - -// NewMVNOHandler creates a new MVNO HTTP handler -func NewMVNOHandler(service OnboardingService, repo repository.Repository, logger *logrus.Logger) *MVNOHandler { - return &MVNOHandler{ - service: service, - repo: repo, - logger: logger, - } -} - -// StartOnboarding handles POST /mvno/onboarding -func (h *MVNOHandler) StartOnboarding(c *gin.Context) { - var req mvno.OnboardingRequest - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.WithError(err).Error("Invalid onboarding request") - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - mvno, err := h.service.StartOnboarding(c.Request.Context(), &req) - if err != nil { - h.logger.WithError(err).Error("Failed to start onboarding") - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - if err := h.repo.CreateMVNO(c.Request.Context(), mvno); err != nil { - h.logger.WithError(err).Error("Failed to save MVNO") - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save MVNO"}) - return - } - - h.logger.WithField("mvno_id", mvno.ID).Info("Onboarding started") - c.JSON(http.StatusCreated, gin.H{ - "mvno_id": mvno.ID, - "business_id": mvno.BusinessID, - "status": mvno.Status, - "plan": mvno.Plan, - }) -} - -// GetMVNO handles GET /mvno/{id} -func (h *MVNOHandler) GetMVNO(c *gin.Context) { - id := c.Param("id") - if id == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "MVNO ID is required"}) - return - } - - mvno, err := h.repo.GetMVNO(c.Request.Context(), id) - if err != nil { - h.logger.WithError(err).WithField("mvno_id", id).Error("Failed to get MVNO") - c.JSON(http.StatusNotFound, gin.H{"error": "MVNO not found"}) - return - } - - c.JSON(http.StatusOK, mvno) -} - -// ListMVNOs handles GET /mvno -func (h *MVNOHandler) ListMVNOs(c *gin.Context) { - filter := &mvno.MVNOFilter{} - if status := c.Query("status"); status != "" { - filter.Status = mvno.MVNOStatus(status) - } - if plan := c.Query("plan"); plan != "" { - filter.Plan = mvno.MVNOPlan(plan) - } - if limit := c.Query("limit"); limit != "" { - if l, err := strconv.Atoi(limit); err == nil { - filter.Limit = l - } - } - - mvnos, err := h.repo.ListMVNOs(c.Request.Context(), filter) - if err != nil { - h.logger.WithError(err).Error("Failed to list MVNOs") - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list MVNOs"}) - return - } - - c.JSON(http.StatusOK, gin.H{"mvnos": mvnos, "count": len(mvnos)}) -} - -// RegisterRoutes registers all MVNO routes -func (h *MVNOHandler) RegisterRoutes(router *gin.RouterGroup) { - mvno := router.Group("/mvno") - { - mvno.POST("/onboarding", h.StartOnboarding) - mvno.GET("", h.ListMVNOs) - mvno.GET("/:id", h.GetMVNO) - } -} diff --git a/apps/carrier-connector/internal/handlers/whitelabel_handlers.go b/apps/carrier-connector/internal/handlers/whitelabel_handlers.go deleted file mode 100644 index ec80429..0000000 --- a/apps/carrier-connector/internal/handlers/whitelabel_handlers.go +++ /dev/null @@ -1,154 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/whitelabel" - "github.com/sirupsen/logrus" -) - -// WhitelabelHandler handles whitelabel API requests -type WhitelabelHandler struct { - service *whitelabel.Service - logger *logrus.Logger -} - -// NewWhitelabelHandler creates a new whitelabel handler -func NewWhitelabelHandler(service *whitelabel.Service, logger *logrus.Logger) *WhitelabelHandler { - return &WhitelabelHandler{service: service, logger: logger} -} - -// CreateBranding creates branding configuration -func (h *WhitelabelHandler) CreateBranding(c *gin.Context) { - var config whitelabel.BrandingConfig - if err := c.ShouldBindJSON(&config); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - config.TenantID = c.GetHeader("X-Tenant-ID") - if err := h.service.CreateBranding(c.Request.Context(), &config); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, config) -} - -// GetBranding retrieves branding configuration -func (h *WhitelabelHandler) GetBranding(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - if tenantID == "" { - tenantID = c.Param("tenant_id") - } - - config, err := h.service.GetBranding(c.Request.Context(), tenantID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, config) -} - -// GetBrandingByDomain retrieves branding by custom domain -func (h *WhitelabelHandler) GetBrandingByDomain(c *gin.Context) { - domain := c.Param("domain") - config, err := h.service.GetBrandingByDomain(c.Request.Context(), domain) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, config) -} - -// UpdateBranding updates branding configuration -func (h *WhitelabelHandler) UpdateBranding(c *gin.Context) { - var config whitelabel.BrandingConfig - if err := c.ShouldBindJSON(&config); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - config.TenantID = c.GetHeader("X-Tenant-ID") - if err := h.service.UpdateBranding(c.Request.Context(), &config); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, config) -} - -// CreatePartnerConfig creates partner configuration -func (h *WhitelabelHandler) CreatePartnerConfig(c *gin.Context) { - var config whitelabel.PartnerConfig - if err := c.ShouldBindJSON(&config); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - config.TenantID = c.GetHeader("X-Tenant-ID") - if err := h.service.CreatePartnerConfig(c.Request.Context(), &config); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, config) -} - -// GetPartnerConfig retrieves partner configuration -func (h *WhitelabelHandler) GetPartnerConfig(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - config, err := h.service.GetPartnerConfig(c.Request.Context(), tenantID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, config) -} - -// CreateEmailTemplate creates an email template -func (h *WhitelabelHandler) CreateEmailTemplate(c *gin.Context) { - var template whitelabel.EmailTemplate - if err := c.ShouldBindJSON(&template); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - template.TenantID = c.GetHeader("X-Tenant-ID") - if err := h.service.CreateEmailTemplate(c.Request.Context(), &template); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, template) -} - -// GetEmailTemplate retrieves an email template -func (h *WhitelabelHandler) GetEmailTemplate(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - templateKey := c.Param("key") - - template, err := h.service.GetEmailTemplate(c.Request.Context(), tenantID, templateKey) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, template) -} - -// ListEmailTemplates lists email templates -func (h *WhitelabelHandler) ListEmailTemplates(c *gin.Context) { - tenantID := c.GetHeader("X-Tenant-ID") - templates, err := h.service.ListEmailTemplates(c.Request.Context(), tenantID) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, templates) -} diff --git a/apps/carrier-connector/internal/infra/cache.go b/apps/carrier-connector/internal/infra/cache.go deleted file mode 100644 index 8579f41..0000000 --- a/apps/carrier-connector/internal/infra/cache.go +++ /dev/null @@ -1,215 +0,0 @@ -package infra - -import ( - "context" - "encoding/json" - "fmt" - "sync" - "time" -) - -// CacheStrategy defines caching strategies -type CacheStrategy string - -const ( - CacheStrategyLRU CacheStrategy = "lru" - CacheStrategyTTL CacheStrategy = "ttl" - CacheStrategyWriteThru CacheStrategy = "write_through" - CacheStrategyWriteBack CacheStrategy = "write_back" - CacheStrategyReadThru CacheStrategy = "read_through" -) - -// CacheEntry represents a cached item -type CacheEntry struct { - Key string - Value []byte - ExpiresAt time.Time - CreatedAt time.Time - HitCount int64 -} - -// Cache provides in-memory caching with multiple strategies -type Cache struct { - entries map[string]*CacheEntry - mu sync.RWMutex - maxSize int - defaultTTL time.Duration - strategy CacheStrategy - stats CacheStats -} - -// CacheConfig configures the cache -type CacheConfig struct { - MaxSize int - DefaultTTL time.Duration - Strategy CacheStrategy -} - -// DefaultCacheConfig returns default cache configuration -func DefaultCacheConfig() CacheConfig { - return CacheConfig{ - MaxSize: 10000, - DefaultTTL: 5 * time.Minute, - Strategy: CacheStrategyTTL, - } -} - -// NewCache creates a new cache instance -func NewCache(config CacheConfig) *Cache { - c := &Cache{ - entries: make(map[string]*CacheEntry), - maxSize: config.MaxSize, - defaultTTL: config.DefaultTTL, - strategy: config.Strategy, - } - go c.cleanupLoop() - return c -} - -// Get retrieves a value from cache -func (c *Cache) Get(ctx context.Context, key string) ([]byte, bool) { - c.mu.RLock() - entry, exists := c.entries[key] - c.mu.RUnlock() - - if !exists { - c.stats.Misses++ - return nil, false - } - - if time.Now().After(entry.ExpiresAt) { - c.Delete(ctx, key) - c.stats.Misses++ - return nil, false - } - - c.mu.Lock() - entry.HitCount++ - c.stats.Hits++ - c.mu.Unlock() - - return entry.Value, true -} - -// Set stores a value in cache -func (c *Cache) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error { - if ttl == 0 { - ttl = c.defaultTTL - } - - c.mu.Lock() - defer c.mu.Unlock() - - if len(c.entries) >= c.maxSize { - c.evict() - } - - c.entries[key] = &CacheEntry{ - Key: key, - Value: value, - ExpiresAt: time.Now().Add(ttl), - CreatedAt: time.Now(), - } - c.stats.Sets++ - - return nil -} - -// SetJSON stores a JSON-serializable value -func (c *Cache) SetJSON(ctx context.Context, key string, value any, ttl time.Duration) error { - data, err := json.Marshal(value) - if err != nil { - return fmt.Errorf("failed to marshal value: %w", err) - } - return c.Set(ctx, key, data, ttl) -} - -// GetJSON retrieves and unmarshals a JSON value -func (c *Cache) GetJSON(ctx context.Context, key string, dest any) (bool, error) { - data, exists := c.Get(ctx, key) - if !exists { - return false, nil - } - if err := json.Unmarshal(data, dest); err != nil { - return false, fmt.Errorf("failed to unmarshal value: %w", err) - } - return true, nil -} - -// Delete removes a value from cache -func (c *Cache) Delete(_ context.Context, key string) { - c.mu.Lock() - defer c.mu.Unlock() - delete(c.entries, key) - c.stats.Deletes++ -} - -// Clear removes all entries -func (c *Cache) Clear(_ context.Context) { - c.mu.Lock() - defer c.mu.Unlock() - c.entries = make(map[string]*CacheEntry) -} - -// evict removes the least recently used or oldest entry -func (c *Cache) evict() { - var oldest *CacheEntry - var oldestKey string - - for key, entry := range c.entries { - if oldest == nil || entry.CreatedAt.Before(oldest.CreatedAt) { - oldest = entry - oldestKey = key - } - } - - if oldestKey != "" { - delete(c.entries, oldestKey) - c.stats.Evictions++ - } -} - -func (c *Cache) cleanupLoop() { - ticker := time.NewTicker(time.Minute) - defer ticker.Stop() - - for range ticker.C { - c.cleanup() - } -} - -func (c *Cache) cleanup() { - c.mu.Lock() - defer c.mu.Unlock() - - now := time.Now() - for key, entry := range c.entries { - if now.After(entry.ExpiresAt) { - delete(c.entries, key) - c.stats.Evictions++ - } - } -} - -// Stats returns cache statistics -func (c *Cache) Stats() CacheStats { - c.mu.RLock() - defer c.mu.RUnlock() - stats := c.stats - stats.Size = len(c.entries) - if stats.Hits+stats.Misses > 0 { - stats.HitRate = float64(stats.Hits) / float64(stats.Hits+stats.Misses) * 100 - } - return stats -} - -// CacheStats contains cache statistics -type CacheStats struct { - Size int `json:"size"` - Hits int64 `json:"hits"` - Misses int64 `json:"misses"` - Sets int64 `json:"sets"` - Deletes int64 `json:"deletes"` - Evictions int64 `json:"evictions"` - HitRate float64 `json:"hit_rate_pct"` -} diff --git a/apps/carrier-connector/internal/infra/circuitbreaker.go b/apps/carrier-connector/internal/infra/circuitbreaker.go deleted file mode 100644 index 9811641..0000000 --- a/apps/carrier-connector/internal/infra/circuitbreaker.go +++ /dev/null @@ -1,212 +0,0 @@ -package infra - -import ( - "context" - "errors" - "sync" - "time" -) - -// CircuitState represents the circuit breaker state -type CircuitState int - -const ( - StateClosed CircuitState = iota - StateOpen - StateHalfOpen -) - -func (s CircuitState) String() string { - switch s { - case StateClosed: - return "closed" - case StateOpen: - return "open" - case StateHalfOpen: - return "half-open" - default: - return "unknown" - } -} - -// CircuitBreaker implements the circuit breaker pattern -type CircuitBreaker struct { - name string - state CircuitState - failureCount int - successCount int - lastFailure time.Time - config CircuitBreakerConfig - mu sync.RWMutex - onStateChange func(name string, from, to CircuitState) -} - -// CircuitBreakerConfig configures the circuit breaker -type CircuitBreakerConfig struct { - FailureThreshold int - SuccessThreshold int - Timeout time.Duration - HalfOpenMaxCalls int -} - -// DefaultCircuitBreakerConfig returns default configuration -func DefaultCircuitBreakerConfig() CircuitBreakerConfig { - return CircuitBreakerConfig{ - FailureThreshold: 5, - SuccessThreshold: 3, - Timeout: 30 * time.Second, - HalfOpenMaxCalls: 3, - } -} - -// NewCircuitBreaker creates a new circuit breaker -func NewCircuitBreaker(name string, config CircuitBreakerConfig) *CircuitBreaker { - return &CircuitBreaker{ - name: name, - state: StateClosed, - config: config, - } -} - -// ErrCircuitOpen is returned when the circuit is open -var ErrCircuitOpen = errors.New("circuit breaker is open") - -// Execute runs the function with circuit breaker protection -func (cb *CircuitBreaker) Execute(ctx context.Context, fn func() error) error { - if !cb.allowRequest() { - return ErrCircuitOpen - } - - err := fn() - cb.recordResult(err) - return err -} - -// ExecuteWithFallback runs with fallback on circuit open -func (cb *CircuitBreaker) ExecuteWithFallback(ctx context.Context, fn func() error, fallback func() error) error { - if !cb.allowRequest() { - if fallback != nil { - return fallback() - } - return ErrCircuitOpen - } - - err := fn() - cb.recordResult(err) - if err != nil && fallback != nil { - return fallback() - } - return err -} - -func (cb *CircuitBreaker) allowRequest() bool { - cb.mu.RLock() - defer cb.mu.RUnlock() - - switch cb.state { - case StateClosed: - return true - case StateOpen: - if time.Since(cb.lastFailure) > cb.config.Timeout { - cb.mu.RUnlock() - cb.mu.Lock() - cb.transitionTo(StateHalfOpen) - cb.mu.Unlock() - cb.mu.RLock() - return true - } - return false - case StateHalfOpen: - return true - default: - return false - } -} - -func (cb *CircuitBreaker) recordResult(err error) { - cb.mu.Lock() - defer cb.mu.Unlock() - - if err != nil { - cb.recordFailure() - } else { - cb.recordSuccess() - } -} - -func (cb *CircuitBreaker) recordFailure() { - cb.failureCount++ - cb.lastFailure = time.Now() - - switch cb.state { - case StateClosed: - if cb.failureCount >= cb.config.FailureThreshold { - cb.transitionTo(StateOpen) - } - case StateHalfOpen: - cb.transitionTo(StateOpen) - } -} - -func (cb *CircuitBreaker) recordSuccess() { - cb.successCount++ - - switch cb.state { - case StateHalfOpen: - if cb.successCount >= cb.config.SuccessThreshold { - cb.transitionTo(StateClosed) - } - case StateClosed: - cb.failureCount = 0 - } -} - -func (cb *CircuitBreaker) transitionTo(state CircuitState) { - if cb.state == state { - return - } - oldState := cb.state - cb.state = state - cb.failureCount = 0 - cb.successCount = 0 - - if cb.onStateChange != nil { - cb.onStateChange(cb.name, oldState, state) - } -} - -// State returns the current state -func (cb *CircuitBreaker) State() CircuitState { - cb.mu.RLock() - defer cb.mu.RUnlock() - return cb.state -} - -// Stats returns circuit breaker statistics -func (cb *CircuitBreaker) Stats() CircuitBreakerStats { - cb.mu.RLock() - defer cb.mu.RUnlock() - return CircuitBreakerStats{ - Name: cb.name, - State: cb.state.String(), - FailureCount: cb.failureCount, - SuccessCount: cb.successCount, - LastFailure: cb.lastFailure, - } -} - -// CircuitBreakerStats contains circuit breaker statistics -type CircuitBreakerStats struct { - Name string `json:"name"` - State string `json:"state"` - FailureCount int `json:"failure_count"` - SuccessCount int `json:"success_count"` - LastFailure time.Time `json:"last_failure"` -} - -// OnStateChange sets the state change callback -func (cb *CircuitBreaker) OnStateChange(fn func(name string, from, to CircuitState)) { - cb.mu.Lock() - defer cb.mu.Unlock() - cb.onStateChange = fn -} diff --git a/apps/carrier-connector/internal/infra/georouting.go b/apps/carrier-connector/internal/infra/georouting.go deleted file mode 100644 index 1e467fe..0000000 --- a/apps/carrier-connector/internal/infra/georouting.go +++ /dev/null @@ -1,112 +0,0 @@ -package infra - -import ( - "context" - "net" - "sync" -) - -// GeoRouter handles geographic routing for API requests -type GeoRouter struct { - regions map[string]*Region - defaultReg string - mu sync.RWMutex -} - -// Region represents a geographic region with endpoints -type Region struct { - ID string `json:"id"` - Name string `json:"name"` - Endpoints []string `json:"endpoints"` - Priority int `json:"priority"` - IsActive bool `json:"is_active"` - Latency float64 `json:"latency_ms"` -} - -// GeoRoutingConfig configures geographic routing -type GeoRoutingConfig struct { - DefaultRegion string - Regions []*Region -} - -// NewGeoRouter creates a new geographic router -func NewGeoRouter(config GeoRoutingConfig) *GeoRouter { - gr := &GeoRouter{ - regions: make(map[string]*Region), - defaultReg: config.DefaultRegion, - } - for _, r := range config.Regions { - gr.regions[r.ID] = r - } - return gr -} - -// GetRegionForIP determines the best region for an IP address -func (gr *GeoRouter) GetRegionForIP(_ context.Context, ipStr string) (*Region, error) { - gr.mu.RLock() - defer gr.mu.RUnlock() - - ip := net.ParseIP(ipStr) - if ip == nil { - return gr.regions[gr.defaultReg], nil - } - - // Simplified geo lookup - in production use MaxMind GeoIP - regionID := gr.lookupRegion(ip) - if region, ok := gr.regions[regionID]; ok && region.IsActive { - return region, nil - } - - return gr.regions[gr.defaultReg], nil -} - -func (gr *GeoRouter) lookupRegion(ip net.IP) string { - // Simplified region detection based on IP ranges - if ip.To4() != nil { - first := ip.To4()[0] - switch { - case first >= 1 && first <= 126: - return "us-east" - case first >= 128 && first <= 191: - return "eu-west" - default: - return "ap-southeast" - } - } - return gr.defaultReg -} - -// GetBestEndpoint returns the best endpoint for a region -func (gr *GeoRouter) GetBestEndpoint(_ context.Context, regionID string) (string, error) { - gr.mu.RLock() - defer gr.mu.RUnlock() - - region, ok := gr.regions[regionID] - if !ok || len(region.Endpoints) == 0 { - region = gr.regions[gr.defaultReg] - } - - if len(region.Endpoints) > 0 { - return region.Endpoints[0], nil - } - return "", nil -} - -// UpdateRegion updates a region configuration -func (gr *GeoRouter) UpdateRegion(region *Region) { - gr.mu.Lock() - defer gr.mu.Unlock() - gr.regions[region.ID] = region -} - -// GetRegions returns all configured regions -func (gr *GeoRouter) GetRegions() []*Region { - gr.mu.RLock() - defer gr.mu.RUnlock() - - regions := make([]*Region, 0, len(gr.regions)) - for _, r := range gr.regions { - regions = append(regions, r) - } - return regions -} diff --git a/apps/carrier-connector/internal/infra/predictive_maintenance.go b/apps/carrier-connector/internal/infra/predictive_maintenance.go deleted file mode 100644 index 665201f..0000000 --- a/apps/carrier-connector/internal/infra/predictive_maintenance.go +++ /dev/null @@ -1,489 +0,0 @@ -package infra - -import ( - "context" - "fmt" - "math" - "math/rand/v2" - "sync" - "time" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// MaintenanceType represents types of maintenance -type MaintenanceType string - -const ( - MaintenanceTypePreventive MaintenanceType = "preventive" - MaintenanceTypeCorrective MaintenanceType = "corrective" - MaintenanceTypePredictive MaintenanceType = "predictive" - MaintenanceTypeEmergency MaintenanceType = "emergency" -) - -// AssetType represents infrastructure asset types -type AssetType string - -const ( - AssetTypeServer AssetType = "server" - AssetTypeDatabase AssetType = "database" - AssetTypeNetwork AssetType = "network" - AssetTypeStorage AssetType = "storage" - AssetTypeApplication AssetType = "application" - AssetTypeLoadBalancer AssetType = "load_balancer" -) - -// Asset represents an infrastructure asset -type Asset struct { - ID string `json:"id"` - Name string `json:"name"` - Type AssetType `json:"type"` - Status string `json:"status"` - Location string `json:"location"` - HealthScore float64 `json:"health_score"` // 0-100 - LastMaintenance *time.Time `json:"last_maintenance,omitempty"` - NextMaintenance *time.Time `json:"next_maintenance,omitempty"` - Metadata map[string]any `json:"metadata"` -} - -// MaintenanceAlert represents a maintenance alert -type MaintenanceAlert struct { - ID string `json:"id"` - AssetID string `json:"asset_id"` - Type MaintenanceType `json:"type"` - Severity string `json:"severity"` // "low", "medium", "high", "critical" - Title string `json:"title"` - Description string `json:"description"` - RiskScore float64 `json:"risk_score"` // 0-100 - PredictedFailure *time.Time `json:"predicted_failure,omitempty"` - Recommendations []string `json:"recommendations"` - Timestamp time.Time `json:"timestamp"` - Status string `json:"status"` // "new", "acknowledged", "scheduled", "completed" -} - -// MaintenanceMetrics represents maintenance performance metrics -type MaintenanceMetrics struct { - Period string `json:"period"` - TotalAssets int64 `json:"total_assets"` - HealthyAssets int64 `json:"healthy_assets"` - AssetsNeedingAttention int64 `json:"assets_needing_attention"` - PreventiveMaintenance int64 `json:"preventive_maintenance"` - CorrectiveMaintenance int64 `json:"corrective_maintenance"` - EmergencyMaintenance int64 `json:"emergency_maintenance"` - Uptime float64 `json:"uptime_pct"` - MeanTimeToFailure float64 `json:"mean_time_to_failure_hours"` - MeanTimeToRepair float64 `json:"mean_time_to_repair_hours"` - GeneratedAt time.Time `json:"generated_at"` -} - -// PredictiveMaintenanceService provides predictive maintenance capabilities -type PredictiveMaintenanceService struct { - db *gorm.DB - logger *logrus.Logger - assets map[string]*Asset - alerts []*MaintenanceAlert - mu sync.RWMutex - models map[AssetType]*MaintenanceModel -} - -// MaintenanceModel represents a predictive maintenance model -type MaintenanceModel struct { - AssetType AssetType `json:"asset_type"` - Version string `json:"version"` - LastTrained time.Time `json:"last_trained"` - Accuracy float64 `json:"accuracy"` - FailureRate float64 `json:"failure_rate"` - MTTF float64 `json:"mttf_hours"` // Mean Time To Failure - MTTR float64 `json:"mttr_hours"` // Mean Time To Repair -} - -// NewPredictiveMaintenanceService creates a new predictive maintenance service -func NewPredictiveMaintenanceService(db *gorm.DB, logger *logrus.Logger) *PredictiveMaintenanceService { - service := &PredictiveMaintenanceService{ - db: db, - logger: logger, - assets: make(map[string]*Asset), - alerts: make([]*MaintenanceAlert, 0), - models: make(map[AssetType]*MaintenanceModel), - } - - // Initialize with default assets - service.initializeAssets() - - // Initialize predictive models - service.initializeModels() - - // Start monitoring - go service.monitorAssets() - - return service -} - -// MonitorAssets continuously monitors asset health -func (s *PredictiveMaintenanceService) monitorAssets() { - ticker := time.NewTicker(5 * time.Minute) - defer ticker.Stop() - - for range ticker.C { - s.updateAssetHealth() - s.predictFailures() - } -} - -// GetMaintenanceMetrics returns maintenance performance metrics -func (s *PredictiveMaintenanceService) GetMaintenanceMetrics(ctx context.Context, period string) (*MaintenanceMetrics, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - metrics := &MaintenanceMetrics{ - Period: period, - GeneratedAt: time.Now(), - } - - // Count assets by status - for _, asset := range s.assets { - metrics.TotalAssets++ - if asset.HealthScore >= 80 { - metrics.HealthyAssets++ - } else if asset.HealthScore < 60 { - metrics.AssetsNeedingAttention++ - } - } - - // Count maintenance types - for _, alert := range s.alerts { - switch alert.Type { - case MaintenanceTypePreventive: - metrics.PreventiveMaintenance++ - case MaintenanceTypeCorrective: - metrics.CorrectiveMaintenance++ - case MaintenanceTypeEmergency: - metrics.EmergencyMaintenance++ - } - } - - // Calculate uptime and MTTF/MTTR (simplified) - metrics.Uptime = 99.5 - metrics.MeanTimeToFailure = 8760 // 1 year in hours - metrics.MeanTimeToRepair = 4 // 4 hours - - return metrics, nil -} - -// GetMaintenanceAlerts returns maintenance alerts -func (s *PredictiveMaintenanceService) GetMaintenanceAlerts(ctx context.Context, severity string) ([]*MaintenanceAlert, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - filtered := make([]*MaintenanceAlert, 0) - for _, alert := range s.alerts { - if severity == "" || alert.Severity == severity { - filtered = append(filtered, alert) - } - } - - return filtered, nil -} - -// PredictFailure predicts failure for an asset -func (s *PredictiveMaintenanceService) PredictFailure(_ context.Context, assetID string) (*MaintenanceAlert, error) { - s.mu.Lock() - defer s.mu.Unlock() - - asset, exists := s.assets[assetID] - if !exists { - return nil, fmt.Errorf("asset not found: %s", assetID) - } - - // Get predictive model - model, exists := s.models[asset.Type] - if !exists { - return nil, fmt.Errorf("no model for asset type: %s", asset.Type) - } - - // Calculate failure probability - failureProb := s.calculateFailureProbability(asset, model) - - if failureProb > 0.7 { - alert := &MaintenanceAlert{ - ID: fmt.Sprintf("alert-%d", time.Now().UnixNano()), - AssetID: assetID, - Type: MaintenanceTypePredictive, - Severity: s.determineSeverity(failureProb), - Title: fmt.Sprintf("Predicted failure for %s", asset.Name), - Description: fmt.Sprintf("Asset %s has high probability of failure", asset.Name), - RiskScore: failureProb * 100, - PredictedFailure: s.predictFailureTime(asset, model), - Recommendations: s.generateMaintenanceRecommendations(asset, model), - Timestamp: time.Now(), - Status: "new", - } - - s.alerts = append(s.alerts, alert) - return alert, nil - } - - return nil, nil -} - -// ScheduleMaintenance schedules maintenance for an asset -func (s *PredictiveMaintenanceService) ScheduleMaintenance(ctx context.Context, assetID, maintenanceType string, scheduledTime time.Time) error { - s.mu.Lock() - defer s.mu.Unlock() - - asset, exists := s.assets[assetID] - if !exists { - return fmt.Errorf("asset not found: %s", assetID) - } - - asset.NextMaintenance = &scheduledTime - - // Create maintenance alert - alert := &MaintenanceAlert{ - ID: fmt.Sprintf("maintenance-%d", time.Now().UnixNano()), - AssetID: assetID, - Type: MaintenanceType(maintenanceType), - Severity: "medium", - Title: fmt.Sprintf("Scheduled maintenance for %s", asset.Name), - Description: fmt.Sprintf("Maintenance scheduled for %s", scheduledTime.Format(time.RFC3339)), - Timestamp: time.Now(), - Status: "scheduled", - } - - s.alerts = append(s.alerts, alert) - - s.logger.WithFields(logrus.Fields{ - "asset_id": assetID, - "asset_name": asset.Name, - "scheduled_at": scheduledTime, - }).Info("Maintenance scheduled") - - return nil -} - -// initializeAssets initializes default infrastructure assets -func (s *PredictiveMaintenanceService) initializeAssets() { - assets := []*Asset{ - { - ID: "api-server-1", - Name: "API Server 1", - Type: AssetTypeServer, - Status: "running", - Location: "us-east-1", - HealthScore: 95.0, - Metadata: map[string]any{"cpu_cores": 8, "memory_gb": 32}, - }, - { - ID: "db-primary", - Name: "Primary Database", - Type: AssetTypeDatabase, - Status: "running", - Location: "us-east-1", - HealthScore: 92.0, - Metadata: map[string]any{"engine": "postgresql", "version": "14"}, - }, - { - ID: "lb-main", - Name: "Main Load Balancer", - Type: AssetTypeLoadBalancer, - Status: "running", - Location: "us-east-1", - HealthScore: 98.0, - Metadata: map[string]any{"connections": 1000, "throughput_mbps": 1000}, - }, - { - ID: "cache-redis", - Name: "Redis Cache", - Type: AssetTypeApplication, - Status: "running", - Location: "us-east-1", - HealthScore: 88.0, - Metadata: map[string]any{"memory_gb": 16, "hit_rate": 0.95}, - }, - { - ID: "storage-s3", - Name: "S3 Storage", - Type: AssetTypeStorage, - Status: "healthy", - Location: "us-east-1", - HealthScore: 99.0, - Metadata: map[string]any{"capacity_tb": 100, "used_tb": 45}, - }, - } - - for _, asset := range assets { - s.assets[asset.ID] = asset - } -} - -// initializeModels initializes predictive maintenance models -func (s *PredictiveMaintenanceService) initializeModels() { - models := map[AssetType]*MaintenanceModel{ - AssetTypeServer: { - AssetType: AssetTypeServer, - Version: "1.0", - LastTrained: time.Now().AddDate(0, -1, 0), - Accuracy: 0.85, - FailureRate: 0.05, - MTTF: 8760, // 1 year - MTTR: 4, // 4 hours - }, - AssetTypeDatabase: { - AssetType: AssetTypeDatabase, - Version: "1.0", - LastTrained: time.Now().AddDate(0, -1, 0), - Accuracy: 0.92, - FailureRate: 0.02, - MTTF: 17520, // 2 years - MTTR: 8, // 8 hours - }, - AssetTypeLoadBalancer: { - AssetType: AssetTypeLoadBalancer, - Version: "1.0", - LastTrained: time.Now().AddDate(0, -1, 0), - Accuracy: 0.88, - FailureRate: 0.03, - MTTF: 13140, // 1.5 years - MTTR: 2, // 2 hours - }, - AssetTypeApplication: { - AssetType: AssetTypeApplication, - Version: "1.0", - LastTrained: time.Now().AddDate(0, -1, 0), - Accuracy: 0.78, - FailureRate: 0.08, - MTTF: 4380, // 6 months - MTTR: 1, // 1 hour - }, - AssetTypeStorage: { - AssetType: AssetTypeStorage, - Version: "1.0", - LastTrained: time.Now().AddDate(0, -1, 0), - Accuracy: 0.95, - FailureRate: 0.01, - MTTF: 35040, // 4 years - MTTR: 12, // 12 hours - }, - } - - for assetType, model := range models { - s.models[assetType] = model - } -} - -// updateAssetHealth updates health scores for all assets -func (s *PredictiveMaintenanceService) updateAssetHealth() { - s.mu.Lock() - defer s.mu.Unlock() - - for _, asset := range s.assets { - // Simulate health score changes - change := (rand.Float64()*10 - 5) // Random change between -5 and +5 - asset.HealthScore = math.Max(0, math.Min(100, asset.HealthScore+change)) - } -} - -// predictFailures predicts failures for all assets -func (s *PredictiveMaintenanceService) predictFailures() { - s.mu.Lock() - defer s.mu.Unlock() - - for _, asset := range s.assets { - model, exists := s.models[asset.Type] - if !exists { - continue - } - - failureProb := s.calculateFailureProbability(asset, model) - if failureProb > 0.8 { - // Create high-priority alert - alert := &MaintenanceAlert{ - ID: fmt.Sprintf("alert-%d", time.Now().UnixNano()), - AssetID: asset.ID, - Type: MaintenanceTypePredictive, - Severity: "critical", - Title: fmt.Sprintf("Critical: %s likely to fail", asset.Name), - Description: fmt.Sprintf("Asset has %.1f%% probability of failure", failureProb*100), - RiskScore: failureProb * 100, - PredictedFailure: s.predictFailureTime(asset, model), - Timestamp: time.Now(), - Status: "new", - } - - s.alerts = append(s.alerts, alert) - } - } -} - -// calculateFailureProbability calculates failure probability for an asset -func (s *PredictiveMaintenanceService) calculateFailureProbability(asset *Asset, model *MaintenanceModel) float64 { - // Simplified failure probability calculation - baseProb := model.FailureRate - - // Adjust based on health score - healthFactor := (100 - asset.HealthScore) / 100 - - // Adjust based on age (if no recent maintenance) - ageFactor := 1.0 - if asset.LastMaintenance != nil { - daysSinceMaintenance := time.Since(*asset.LastMaintenance).Hours() / 24 - ageFactor = math.Min(2.0, daysSinceMaintenance/365) // Max 2x risk after 1 year - } - - probability := baseProb * healthFactor * ageFactor - - return math.Min(1.0, probability) -} - -// determineSeverity determines alert severity from probability -func (s *PredictiveMaintenanceService) determineSeverity(probability float64) string { - switch { - case probability >= 0.9: - return "critical" - case probability >= 0.7: - return "high" - case probability >= 0.5: - return "medium" - default: - return "low" - } -} - -// predictFailureTime predicts when failure might occur -func (s *PredictiveMaintenanceService) predictFailureTime(asset *Asset, model *MaintenanceModel) *time.Time { - // Simplified prediction based on MTTF and current health - hoursToFailure := model.MTTF * (asset.HealthScore / 100) - predictedTime := time.Now().Add(time.Duration(hoursToFailure) * time.Hour) - return &predictedTime -} - -// generateMaintenanceRecommendations generates maintenance recommendations -func (s *PredictiveMaintenanceService) generateMaintenanceRecommendations(asset *Asset, model *MaintenanceModel) []string { - recommendations := make([]string, 0) - - switch asset.Type { - case AssetTypeServer: - recommendations = append(recommendations, "Check CPU and memory utilization") - recommendations = append(recommendations, "Review system logs for errors") - recommendations = append(recommendations, "Update system patches") - case AssetTypeDatabase: - recommendations = append(recommendations, "Run database health check") - recommendations = append(recommendations, "Optimize query performance") - recommendations = append(recommendations, "Check disk space and I/O") - case AssetTypeLoadBalancer: - recommendations = append(recommendations, "Review connection limits") - recommendations = append(recommendations, "Check SSL certificate expiry") - recommendations = append(recommendations, "Monitor response times") - case AssetTypeApplication: - recommendations = append(recommendations, "Restart application services") - recommendations = append(recommendations, "Clear application cache") - recommendations = append(recommendations, "Check for memory leaks") - case AssetTypeStorage: - recommendations = append(recommendations, "Check storage capacity") - recommendations = append(recommendations, "Run storage health diagnostics") - recommendations = append(recommendations, "Review backup integrity") - } - - return recommendations -} diff --git a/apps/carrier-connector/internal/integration/selection_integration.go b/apps/carrier-connector/internal/integration/selection_integration.go index 4838a3b..7ff4977 100644 --- a/apps/carrier-connector/internal/integration/selection_integration.go +++ b/apps/carrier-connector/internal/integration/selection_integration.go @@ -10,7 +10,6 @@ import ( "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/services" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp" - "github.com/sirupsen/logrus" ) type SelectionIntegration struct { @@ -34,8 +33,7 @@ func NewSelectionIntegration(repo *repository.PostgresProfileStore) *SelectionIn manager := smdp.NewSMDPManager(repo, config) // Create selection service - logger := logrus.New() - selectionService := services.NewSelectionService(manager, logger) + selectionService := services.NewSelectionService(manager) // Create SMDP service smdpService := services.NewSMDPService(repo) @@ -43,7 +41,7 @@ func NewSelectionIntegration(repo *repository.PostgresProfileStore) *SelectionIn return &SelectionIntegration{ manager: manager, selectionService: selectionService, - selectionHandler: handlers.NewSelectionHandler(manager), + selectionHandler: selectionService.GetHandler(), smdpService: smdpService, } } diff --git a/apps/carrier-connector/internal/mvno/monitor.go b/apps/carrier-connector/internal/mvno/monitor.go deleted file mode 100644 index 564d28f..0000000 --- a/apps/carrier-connector/internal/mvno/monitor.go +++ /dev/null @@ -1,215 +0,0 @@ -package mvno - -import ( - "maps" - "sync" - "time" - - "github.com/sirupsen/logrus" -) - -// OnboardingMonitor tracks onboarding progress and status -type OnboardingMonitor struct { - logger *logrus.Logger - progress map[string]*OnboardingProgress - mu sync.RWMutex -} - -// NewOnboardingMonitor creates a new monitor instance -func NewOnboardingMonitor(logger *logrus.Logger) *OnboardingMonitor { - return &OnboardingMonitor{ - logger: logger, - progress: make(map[string]*OnboardingProgress), - } -} - -// UpdateProgress updates the onboarding progress for an MVNO -func (m *OnboardingMonitor) UpdateProgress(mvnoID string, progress *OnboardingProgress) { - m.mu.Lock() - defer m.mu.Unlock() - - m.progress[mvnoID] = progress - - m.logger.WithFields(map[string]any{ - "mvno_id": mvnoID, - "progress": progress.Progress, - "status": m.getCurrentStatus(progress), - }).Info("Onboarding progress updated") -} - -// GetProgress retrieves current onboarding progress -func (m *OnboardingMonitor) GetProgress(mvnoID string) (*OnboardingProgress, bool) { - m.mu.RLock() - defer m.mu.RUnlock() - - progress, exists := m.progress[mvnoID] - return progress, exists -} - -// GetAllProgress retrieves progress for all MVNOs -func (m *OnboardingMonitor) GetAllProgress() map[string]*OnboardingProgress { - m.mu.RLock() - defer m.mu.RUnlock() - - // Return a copy to avoid concurrent access issues - result := make(map[string]*OnboardingProgress) - maps.Copy(result, m.progress) - return result -} - -// GetActiveOnboardingCount returns count of active onboarding processes -func (m *OnboardingMonitor) GetActiveOnboardingCount() int { - m.mu.RLock() - defer m.mu.RUnlock() - - count := 0 - for _, progress := range m.progress { - if progress.Progress < 100.0 && !progress.CompletedAt.IsZero() { - count++ - } - } - return count -} - -// GetCompletedOnboardingCount returns count of completed onboardings -func (m *OnboardingMonitor) GetCompletedOnboardingCount() int { - m.mu.RLock() - defer m.mu.RUnlock() - - count := 0 - for _, progress := range m.progress { - if progress.Progress == 100.0 && !progress.CompletedAt.IsZero() { - count++ - } - } - return count -} - -// GetAverageOnboardingTime calculates average onboarding duration -func (m *OnboardingMonitor) GetAverageOnboardingTime() time.Duration { - m.mu.RLock() - defer m.mu.RUnlock() - - var totalDuration time.Duration - completedCount := 0 - - for _, progress := range m.progress { - if progress.Progress == 100.0 && !progress.CompletedAt.IsZero() && !progress.StartedAt.IsZero() { - totalDuration += progress.CompletedAt.Sub(progress.StartedAt) - completedCount++ - } - } - - if completedCount == 0 { - return 0 - } - - return totalDuration / time.Duration(completedCount) -} - -// GetStepSuccessRate calculates success rate for each step -func (m *OnboardingMonitor) GetStepSuccessRate() map[string]float64 { - m.mu.RLock() - defer m.mu.RUnlock() - - stepStats := make(map[string]map[string]int) - - // Collect step statistics - for _, progress := range m.progress { - for _, step := range progress.Steps { - if _, exists := stepStats[step.Name]; !exists { - stepStats[step.Name] = map[string]int{"completed": 0, "failed": 0, "total": 0} - } - stepStats[step.Name]["total"]++ - switch step.Status { -case "completed": - stepStats[step.Name]["completed"]++ - case "failed": - stepStats[step.Name]["failed"]++ - } - } - } - - // Calculate success rates - successRates := make(map[string]float64) - for stepName, stats := range stepStats { - if stats["total"] > 0 { - successRates[stepName] = float64(stats["completed"]) / float64(stats["total"]) * 100 - } - } - - return successRates -} - -// CleanupOldProgress removes old completed progress records -func (m *OnboardingMonitor) CleanupOldProgress(maxAge time.Duration) { - m.mu.Lock() - defer m.mu.Unlock() - - cutoff := time.Now().Add(-maxAge) - for mvnoID, progress := range m.progress { - if progress.Progress == 100.0 && !progress.CompletedAt.IsZero() && progress.CompletedAt.Before(cutoff) { - delete(m.progress, mvnoID) - m.logger.WithFields(map[string]any{ - "mvno_id": mvnoID, - "completed_at": progress.CompletedAt, - }).Info("Cleaned up old onboarding progress") - } - } -} - -// GetFailedOnboardings returns list of failed onboardings -func (m *OnboardingMonitor) GetFailedOnboardings() []string { - m.mu.RLock() - defer m.mu.RUnlock() - - var failed []string - for mvnoID, progress := range m.progress { - hasFailed := false - for _, step := range progress.Steps { - if step.Status == "failed" { - hasFailed = true - break - } - } - if hasFailed { - failed = append(failed, mvnoID) - } - } - return failed -} - -// getCurrentStatus determines current status from progress -func (m *OnboardingMonitor) getCurrentStatus(progress *OnboardingProgress) string { - if progress.Progress == 100.0 { - return "completed" - } - - if progress.Progress == 0.0 { - return "pending" - } - - // Check for failed steps - for _, step := range progress.Steps { - if step.Status == "failed" { - return "failed" - } - } - - return "in_progress" -} - -// GetOnboardingMetrics returns comprehensive onboarding metrics -func (m *OnboardingMonitor) GetOnboardingMetrics() map[string]any { - m.mu.RLock() - defer m.mu.RUnlock() - - return map[string]any{ - "active_count": m.GetActiveOnboardingCount(), - "completed_count": m.GetCompletedOnboardingCount(), - "average_duration": m.GetAverageOnboardingTime().String(), - "step_success_rates": m.GetStepSuccessRate(), - "failed_count": len(m.GetFailedOnboardings()), - "total_onboardings": len(m.progress), - } -} diff --git a/apps/carrier-connector/internal/mvno/provisioner.go b/apps/carrier-connector/internal/mvno/provisioner.go deleted file mode 100644 index 2d8e0e0..0000000 --- a/apps/carrier-connector/internal/mvno/provisioner.go +++ /dev/null @@ -1,185 +0,0 @@ -package mvno - -import ( - "context" - "fmt" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/id" - "github.com/sirupsen/logrus" -) - -// MVNOProvisioner handles resource provisioning for MVNOs -type MVNOProvisioner struct { - logger *logrus.Logger -} - -// NewMVNOProvisioner creates a new provisioner instance -func NewMVNOProvisioner(logger *logrus.Logger) *MVNOProvisioner { - return &MVNOProvisioner{logger: logger} -} - -// ProvisionResources provisions core resources for the MVNO -func (p *MVNOProvisioner) ProvisionResources(ctx context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Provisioning core resources") - - if err := p.provisionTenantContext(ctx, mvno); err != nil { - return fmt.Errorf("failed to provision tenant context: %w", err) - } - - if err := p.provisionDatabaseSchema(ctx, mvno); err != nil { - return fmt.Errorf("failed to provision database schema: %w", err) - } - - return p.provisionStorageResources(ctx, mvno) -} - -// SetupCarriers configures carrier connections for the MVNO -func (p *MVNOProvisioner) SetupCarriers(ctx context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Setting up carrier connections") - - selectedCarriers, err := p.selectCarriers(mvno.Config.AllowedCountries) - if err != nil { - return fmt.Errorf("failed to select carriers: %w", err) - } - - for _, carrierID := range selectedCarriers { - if err := p.configureCarrier(ctx, mvno, carrierID); err != nil { - p.logger.WithError(err).WithField("carrier_id", carrierID).Error("Failed to configure carrier") - } - } - - mvno.Config.CarrierPool = selectedCarriers - return nil -} - -// SetupBilling configures billing system for the MVNO -func (p *MVNOProvisioner) SetupBilling(ctx context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Setting up billing system") - - billingID := id.GeneratePrefixed("bill") - - if err := p.configureRatePlans(ctx, mvno, billingID); err != nil { - return fmt.Errorf("failed to configure rate plans: %w", err) - } - - return p.setupPaymentProcessing(ctx, mvno, billingID) -} - -// SetupAPIAccess configures API access for the MVNO -func (p *MVNOProvisioner) SetupAPIAccess(ctx context.Context, mvno *MVNO) error { - if !mvno.Config.APIAccess { - p.logger.WithField("mvno_id", mvno.ID).Info("API access not included in plan") - return nil - } - - p.logger.WithField("mvno_id", mvno.ID).Info("Setting up API access") - - apiKey := id.GeneratePrefixed("api") - _ = id.GeneratePrefixed("sec") // Generate secret key but don't use until storage is implemented - permissions := p.getAPIPermissions(mvno.Plan) - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "api_key": apiKey[:8] + "...", - "permissions": len(permissions), - }).Info("API access configured") - - return nil -} - -// provisionTenantContext creates tenant context -func (p *MVNOProvisioner) provisionTenantContext(_ context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Tenant context provisioned") - return nil -} - -// provisionDatabaseSchema provisions database schema -func (p *MVNOProvisioner) provisionDatabaseSchema(_ context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Database schema provisioned") - return nil -} - -// provisionStorageResources provisions storage resources -func (p *MVNOProvisioner) provisionStorageResources(_ context.Context, mvno *MVNO) error { - storageSize := p.getStorageAllocation(mvno.Plan) - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "storage_gb": storageSize, - }).Info("Storage resources provisioned") - return nil -} - -// selectCarriers selects optimal carriers for countries -func (p *MVNOProvisioner) selectCarriers(countries []string) ([]string, error) { - carriers := []string{"carrier_us_01", "carrier_gb_01", "carrier_de_01"} - - if len(countries) > 0 { - selected := make([]string, 0) - for _, carrier := range carriers { - selected = append(selected, carrier) - } - return selected, nil - } - - return carriers, nil -} - -// configureCarrier configures individual carrier -func (p *MVNOProvisioner) configureCarrier(_ context.Context, mvno *MVNO, carrierID string) error { - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "carrier_id": carrierID, - }).Info("Carrier configured") - return nil -} - -// configureRatePlans configures rate plans -func (p *MVNOProvisioner) configureRatePlans(_ context.Context, mvno *MVNO, billingID string) error { - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "billing_id": billingID, - "plan": mvno.Plan, - }).Info("Rate plans configured") - return nil -} - -// setupPaymentProcessing setup payment processing -func (p *MVNOProvisioner) setupPaymentProcessing(_ context.Context, mvno *MVNO, billingID string) error { - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "billing_id": billingID, - }).Info("Payment processing setup") - return nil -} - -// getAPIPermissions returns API permissions based on plan -func (p *MVNOProvisioner) getAPIPermissions(plan MVNOPlan) []string { - switch plan { - case PlanStarter: - return []string{"read:subscribers", "read:usage"} - case PlanGrowth: - return []string{"read:subscribers", "write:subscribers", "read:usage", "read:billing"} - case PlanScale: - return []string{"read:subscribers", "write:subscribers", "read:usage", "write:usage", "read:billing", "write:billing", "read:analytics"} - case PlanEnterprise: - return []string{"*"} - default: - return []string{"read:subscribers"} - } -} - -// getStorageAllocation returns storage allocation in GB -func (p *MVNOProvisioner) getStorageAllocation(plan MVNOPlan) int { - switch plan { - case PlanStarter: - return 10 - case PlanGrowth: - return 100 - case PlanScale: - return 1000 - case PlanEnterprise: - return 10000 - default: - return 10 - } -} diff --git a/apps/carrier-connector/internal/mvno/provisioner_methods.go b/apps/carrier-connector/internal/mvno/provisioner_methods.go deleted file mode 100644 index ef64705..0000000 --- a/apps/carrier-connector/internal/mvno/provisioner_methods.go +++ /dev/null @@ -1,208 +0,0 @@ -package mvno - -import ( - "context" - "fmt" - "time" -) - -// provisionTenantContext creates tenant context in production -func (p *ProductionProvisioner) provisionTenantContext(ctx context.Context, mvno *MVNO) error { - tenantConfig := &TenantConfig{ - ID: mvno.ID, - Name: mvno.Name, - Plan: mvno.Plan, - Settings: map[string]any{ - "business_id": mvno.BusinessID, - "max_subscribers": mvno.Config.MaxSubscribers, - "allowed_countries": mvno.Config.AllowedCountries, - "custom_branding": mvno.Config.CustomBranding, - "advanced_analytics": mvno.Config.AdvancedAnalytics, - "created_at": time.Now(), - }, - } - - if err := p.tenantService.CreateTenant(ctx, tenantConfig); err != nil { - return fmt.Errorf("failed to create tenant in management system: %w", err) - } - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "tenant_id": tenantConfig.ID, - "plan": mvno.Plan, - }).Info("Tenant context provisioned successfully") - - return nil -} - -// provisionDatabaseSchema provisions database schema in production -func (p *ProductionProvisioner) provisionDatabaseSchema(ctx context.Context, mvno *MVNO) error { - // Create dedicated database schema for MVNO - if err := p.storageService.CreateDatabaseSchema(ctx, mvno.ID); err != nil { - return fmt.Errorf("failed to create database schema: %w", err) - } - - p.logger.WithField("mvno_id", mvno.ID).Info("Database schema provisioned successfully") - return nil -} - -// provisionStorageResources provisions storage resources in production -func (p *ProductionProvisioner) provisionStorageResources(ctx context.Context, mvno *MVNO) error { - storageSize := p.getStorageAllocation(mvno.Plan) - - if err := p.storageService.CreateStorageBucket(ctx, mvno.ID, storageSize); err != nil { - return fmt.Errorf("failed to create storage bucket: %w", err) - } - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "storage_gb": storageSize, - }).Info("Storage resources provisioned successfully") - - return nil -} - -// selectCarriers selects optimal carriers for countries in production -func (p *ProductionProvisioner) selectCarriers(countries []string) ([]string, error) { - selectedCarriers := make(map[string]bool) // Use map to avoid duplicates - - for _, country := range countries { - carriers, err := p.carrierManager.GetCarriersByCountry(context.Background(), country) - if err != nil { - p.logger.WithError(err).WithField("country", country).Error("Failed to get carriers for country") - continue - } - - // Select active carriers with best coverage - for _, carrier := range carriers { - if carrier.IsActive { - selectedCarriers[carrier.ID] = true - } - } - } - - // Convert map to slice - result := make([]string, 0, len(selectedCarriers)) - for carrierID := range selectedCarriers { - result = append(result, carrierID) - } - - if len(result) == 0 { - return nil, fmt.Errorf("no active carriers found for countries: %v", countries) - } - - p.logger.WithFields(map[string]any{ - "countries": countries, - "selected_count": len(result), - }).Info("Carriers selected successfully") - - return result, nil -} - -// configureCarrier configures individual carrier in production -func (p *ProductionProvisioner) configureCarrier(ctx context.Context, mvno *MVNO, carrierID string) error { - if err := p.carrierManager.ConfigureCarrier(ctx, mvno.ID, carrierID); err != nil { - return fmt.Errorf("failed to configure carrier %s: %w", carrierID, err) - } - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "carrier_id": carrierID, - }).Info("Carrier configured successfully") - - return nil -} - -// configureRatePlans configures rate plans in production -func (p *ProductionProvisioner) configureRatePlans(ctx context.Context, mvno *MVNO, billingID string) error { - if err := p.billingService.CreateRatePlans(ctx, mvno.ID, billingID, mvno.Plan); err != nil { - return fmt.Errorf("failed to create rate plans: %w", err) - } - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "billing_id": billingID, - "plan": mvno.Plan, - }).Info("Rate plans configured successfully") - - return nil -} - -// setupPaymentProcessing setup payment processing in production -func (p *ProductionProvisioner) setupPaymentProcessing(ctx context.Context, mvno *MVNO, billingID string) error { - if err := p.billingService.SetupPaymentGateway(ctx, mvno.ID, billingID); err != nil { - return fmt.Errorf("failed to setup payment gateway: %w", err) - } - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "billing_id": billingID, - }).Info("Payment processing setup successfully") - - return nil -} - -// getAPIPermissions returns API permissions based on plan -func (p *ProductionProvisioner) getAPIPermissions(plan MVNOPlan) []string { - switch plan { - case PlanStarter: - return []string{"read:subscribers", "read:usage"} - case PlanGrowth: - return []string{ - "read:subscribers", "write:subscribers", - "read:usage", "read:billing", - } - case PlanScale: - return []string{ - "read:subscribers", "write:subscribers", - "read:usage", "write:usage", - "read:billing", "write:billing", - "read:analytics", "write:analytics", - } - case PlanEnterprise: - return []string{"*"} // Full access - default: - return []string{"read:subscribers"} - } -} - -// getStorageAllocation returns storage allocation in GB -func (p *ProductionProvisioner) getStorageAllocation(plan MVNOPlan) int { - switch plan { - case PlanStarter: - return 10 - case PlanGrowth: - return 100 - case PlanScale: - return 1000 - case PlanEnterprise: - return 10000 - default: - return 10 - } -} - -// ValidateProvisioning validates that all resources are properly provisioned -func (p *ProductionProvisioner) ValidateProvisioning(ctx context.Context, mvno *MVNO) error { - // Validate tenant exists - _, err := p.tenantService.GetTenant(ctx, mvno.ID) - if err != nil { - return fmt.Errorf("tenant validation failed: %w", err) - } - - // Validate carrier configurations - if len(mvno.Config.CarrierPool) == 0 { - return fmt.Errorf("no carriers configured") - } - - for _, carrierID := range mvno.Config.CarrierPool { - // In production, validate carrier connection - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "carrier_id": carrierID, - }).Debug("Validating carrier connection") - } - - p.logger.WithField("mvno_id", mvno.ID).Info("Provisioning validation completed") - return nil -} diff --git a/apps/carrier-connector/internal/mvno/provisioner_production.go b/apps/carrier-connector/internal/mvno/provisioner_production.go deleted file mode 100644 index 87b75a6..0000000 --- a/apps/carrier-connector/internal/mvno/provisioner_production.go +++ /dev/null @@ -1,196 +0,0 @@ -package mvno - -import ( - "context" - "fmt" - "time" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/id" - "github.com/sirupsen/logrus" -) - -// ProductionProvisioner implements production-ready MVNO provisioning -type ProductionProvisioner struct { - logger *logrus.Logger - tenantService TenantService - carrierManager CarrierManager - billingService BillingService - storageService StorageService -} - -// TenantService interface for tenant management -type TenantService interface { - CreateTenant(ctx context.Context, tenant *TenantConfig) error - GetTenant(ctx context.Context, tenantID string) (*TenantConfig, error) -} - -// CarrierManager interface for carrier operations -type CarrierManager interface { - GetCarriersByCountry(ctx context.Context, country string) ([]CarrierInfo, error) - ConfigureCarrier(ctx context.Context, mvnoID, carrierID string) error -} - -// BillingService interface for billing operations -type BillingService interface { - CreateBillingAccount(ctx context.Context, mvnoID string, plan MVNOPlan) (string, error) - CreateRatePlans(ctx context.Context, mvnoID, billingID string, plan MVNOPlan) error - SetupPaymentGateway(ctx context.Context, mvnoID, billingID string) error -} - -// StorageService interface for storage operations -type StorageService interface { - CreateStorageBucket(ctx context.Context, mvnoID string, sizeGB int) error - CreateDatabaseSchema(ctx context.Context, mvnoID string) error -} - -// TenantConfig represents tenant configuration -type TenantConfig struct { - ID string `json:"id"` - Name string `json:"name"` - Plan MVNOPlan `json:"plan"` - Settings map[string]any `json:"settings"` -} - -// CarrierInfo represents carrier information -type CarrierInfo struct { - ID string `json:"id"` - Name string `json:"name"` - Countries []string `json:"countries"` - SMDEndpoint string `json:"smd_endpoint"` - IsActive bool `json:"is_active"` -} - -// NewProductionProvisioner creates a new production provisioner -func NewProductionProvisioner( - logger *logrus.Logger, - tenantService TenantService, - carrierManager CarrierManager, - billingService BillingService, - storageService StorageService, -) *ProductionProvisioner { - return &ProductionProvisioner{ - logger: logger, - tenantService: tenantService, - carrierManager: carrierManager, - billingService: billingService, - storageService: storageService, - } -} - -// ProvisionResources provisions core resources for the MVNO -func (p *ProductionProvisioner) ProvisionResources(ctx context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Provisioning production resources") - - // Create tenant context - if err := p.provisionTenantContext(ctx, mvno); err != nil { - return fmt.Errorf("failed to provision tenant context: %w", err) - } - - // Provision database schema - if err := p.provisionDatabaseSchema(ctx, mvno); err != nil { - return fmt.Errorf("failed to provision database schema: %w", err) - } - - // Provision storage resources - if err := p.provisionStorageResources(ctx, mvno); err != nil { - return fmt.Errorf("failed to provision storage resources: %w", err) - } - - return nil -} - -// SetupCarriers configures carrier connections for the MVNO -func (p *ProductionProvisioner) SetupCarriers(ctx context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Setting up production carrier connections") - - selectedCarriers, err := p.selectCarriers(mvno.Config.AllowedCountries) - if err != nil { - return fmt.Errorf("failed to select carriers: %w", err) - } - - // Configure each carrier - for _, carrierID := range selectedCarriers { - if err := p.configureCarrier(ctx, mvno, carrierID); err != nil { - p.logger.WithError(err).WithField("carrier_id", carrierID).Error("Failed to configure carrier") - return fmt.Errorf("failed to configure carrier %s: %w", carrierID, err) - } - } - - mvno.Config.CarrierPool = selectedCarriers - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "carrier_count": len(selectedCarriers), - }).Info("Carriers configured successfully") - - return nil -} - -// SetupBilling configures billing system for the MVNO -func (p *ProductionProvisioner) SetupBilling(ctx context.Context, mvno *MVNO) error { - p.logger.WithField("mvno_id", mvno.ID).Info("Setting up production billing system") - - // Create billing account - billingID, err := p.billingService.CreateBillingAccount(ctx, mvno.ID, mvno.Plan) - if err != nil { - return fmt.Errorf("failed to create billing account: %w", err) - } - - // Configure rate plans - if err := p.configureRatePlans(ctx, mvno, billingID); err != nil { - return fmt.Errorf("failed to configure rate plans: %w", err) - } - - // Setup payment processing - if err := p.setupPaymentProcessing(ctx, mvno, billingID); err != nil { - return fmt.Errorf("failed to setup payment processing: %w", err) - } - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "billing_id": billingID, - "plan": mvno.Plan, - }).Info("Billing system configured successfully") - - return nil -} - -// SetupAPIAccess configures API access for the MVNO -func (p *ProductionProvisioner) SetupAPIAccess(ctx context.Context, mvno *MVNO) error { - if !mvno.Config.APIAccess { - p.logger.WithField("mvno_id", mvno.ID).Info("API access not included in plan") - return nil - } - - p.logger.WithField("mvno_id", mvno.ID).Info("Setting up production API access") - - // Generate API credentials - apiKey := id.GeneratePrefixed("api") - secretKey := id.GeneratePrefixed("sec") - permissions := p.getAPIPermissions(mvno.Plan) - - // Store API configuration in tenant service - tenant, err := p.tenantService.GetTenant(ctx, mvno.ID) - if err != nil { - return fmt.Errorf("failed to get tenant for API setup: %w", err) - } - - tenant.Settings["api_access"] = map[string]any{ - "api_key": apiKey, - "secret_key": secretKey, - "permissions": permissions, - "created_at": time.Now(), - "expires_at": time.Now().AddDate(1, 0, 0), // 1 year expiry - } - - if err := p.tenantService.CreateTenant(ctx, tenant); err != nil { - return fmt.Errorf("failed to store API configuration: %w", err) - } - - p.logger.WithFields(map[string]any{ - "mvno_id": mvno.ID, - "api_key": apiKey[:8] + "...", - "permissions": len(permissions), - }).Info("API access configured successfully") - - return nil -} diff --git a/apps/carrier-connector/internal/mvno/types.go b/apps/carrier-connector/internal/mvno/types.go deleted file mode 100644 index 2cc15c1..0000000 --- a/apps/carrier-connector/internal/mvno/types.go +++ /dev/null @@ -1,88 +0,0 @@ -package mvno - -import "time" - -// MVNO represents a Mobile Virtual Network Operator -type MVNO struct { - ID string `json:"id" gorm:"primaryKey"` - Name string `json:"name" gorm:"not null"` - BusinessID string `json:"business_id" gorm:"uniqueIndex;not null"` - Status MVNOStatus `json:"status" gorm:"default:'pending'"` - Plan MVNOPlan `json:"plan" gorm:"not null"` - Config MVNOConfig `json:"config" gorm:"serializer:json"` - CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` - UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` -} - -// MVNOStatus represents the onboarding status -type MVNOStatus string - -const ( - StatusPending MVNOStatus = "pending" - StatusReview MVNOStatus = "review" - StatusApproved MVNOStatus = "approved" - StatusActive MVNOStatus = "active" - StatusSuspended MVNOStatus = "suspended" - StatusTerminated MVNOStatus = "terminated" -) - -// MVNOPlan represents subscription tiers -type MVNOPlan string - -const ( - PlanStarter MVNOPlan = "starter" - PlanGrowth MVNOPlan = "growth" - PlanScale MVNOPlan = "scale" - PlanEnterprise MVNOPlan = "enterprise" -) - -// MVNOConfig contains MVNO-specific configuration -type MVNOConfig struct { - MaxSubscribers int `json:"max_subscribers"` - AllowedCountries []string `json:"allowed_countries"` - CarrierPool []string `json:"carrier_pool"` - CustomBranding bool `json:"custom_branding"` - APIAccess bool `json:"api_access"` - AdvancedAnalytics bool `json:"advanced_analytics"` -} - -// OnboardingRequest represents a new MVNO onboarding request -type OnboardingRequest struct { - BusinessName string `json:"business_name" binding:"required"` - BusinessID string `json:"business_id" binding:"required"` - ContactEmail string `json:"contact_email" binding:"required,email"` - ContactPhone string `json:"contact_phone" binding:"required"` - Plan MVNOPlan `json:"plan" binding:"required"` - EstimatedSubs int `json:"estimated_subscribers" binding:"min:1"` - TargetCountries []string `json:"target_countries" binding:"required,min=1"` - UseCase string `json:"use_case" binding:"required"` - TechnicalContact string `json:"technical_contact"` -} - -// OnboardingProgress tracks the onboarding progress -type OnboardingProgress struct { - MVNOID string `json:"mvno_id"` - Steps []OnboardingStep `json:"steps"` - Progress float64 `json:"progress"` - StartedAt time.Time `json:"started_at"` - CompletedAt time.Time `json:"completed_at"` -} - -// OnboardingStep represents individual onboarding steps -type OnboardingStep struct { - Name string `json:"name"` - Status string `json:"status"` - CompletedAt time.Time `json:"completed_at"` - Error string `json:"error,omitempty"` -} - -// MVNOFilter defines filtering options for MVNO queries -type MVNOFilter struct { - Status MVNOStatus `json:"status,omitempty"` - Plan MVNOPlan `json:"plan,omitempty"` - BusinessID string `json:"business_id,omitempty"` - Limit int `json:"limit,omitempty"` - Offset int `json:"offset,omitempty"` - CreatedAfter *time.Time `json:"created_after,omitempty"` - CreatedBefore *time.Time `json:"created_before,omitempty"` -} diff --git a/apps/carrier-connector/internal/mvno/validator.go b/apps/carrier-connector/internal/mvno/validator.go deleted file mode 100644 index 80dde4a..0000000 --- a/apps/carrier-connector/internal/mvno/validator.go +++ /dev/null @@ -1,190 +0,0 @@ -package mvno - -import ( - "context" - "fmt" - "regexp" - "slices" - "strings" - - "github.com/sirupsen/logrus" -) - -// OnboardingValidator validates MVNO onboarding requests and configurations -type OnboardingValidator struct { - logger *logrus.Logger -} - -// NewOnboardingValidator creates a new validator instance -func NewOnboardingValidator(logger *logrus.Logger) *OnboardingValidator { - return &OnboardingValidator{logger: logger} -} - -// ValidateRequest validates the initial onboarding request -func (v *OnboardingValidator) ValidateRequest(req *OnboardingRequest) error { - if err := v.validateBusinessInfo(req); err != nil { - return fmt.Errorf("business validation failed: %w", err) - } - - if err := v.validatePlan(req); err != nil { - return fmt.Errorf("plan validation failed: %w", err) - } - - if err := v.validateTechnicalRequirements(req); err != nil { - return fmt.Errorf("technical validation failed: %w", err) - } - - return nil -} - -// ValidateMVNO performs comprehensive MVNO validation -func (v *OnboardingValidator) ValidateMVNO(ctx context.Context, mvno *MVNO) error { - // Validate business registration - if err := v.validateBusinessRegistration(mvno.BusinessID); err != nil { - return fmt.Errorf("business registration validation failed: %w", err) - } - - // Validate compliance requirements - if err := v.validateCompliance(ctx, mvno); err != nil { - return fmt.Errorf("compliance validation failed: %w", err) - } - - // Validate technical feasibility - if err := v.validateTechnicalFeasibility(mvno); err != nil { - return fmt.Errorf("technical feasibility validation failed: %w", err) - } - - return nil -} - -// validateBusinessInfo validates business information -func (v *OnboardingValidator) validateBusinessInfo(req *OnboardingRequest) error { - if len(req.BusinessName) < 3 { - return fmt.Errorf("business name must be at least 3 characters") - } - - if !v.isValidBusinessID(req.BusinessID) { - return fmt.Errorf("invalid business ID format") - } - - if !v.isValidEmail(req.ContactEmail) { - return fmt.Errorf("invalid contact email format") - } - - if len(req.ContactPhone) < 10 { - return fmt.Errorf("contact phone must be at least 10 digits") - } - - return nil -} - -// validatePlan validates the selected plan -func (v *OnboardingValidator) validatePlan(req *OnboardingRequest) error { - validPlans := []MVNOPlan{PlanStarter, PlanGrowth, PlanScale, PlanEnterprise} - isValid := slices.Contains(validPlans, req.Plan) - if !isValid { - return fmt.Errorf("invalid plan selected") - } - - if req.EstimatedSubs < 1 { - return fmt.Errorf("estimated subscribers must be at least 1") - } - - // Validate plan-specific requirements - switch req.Plan { - case PlanStarter: - if req.EstimatedSubs > 1000 { - return fmt.Errorf("starter plan limited to 1000 subscribers") - } - case PlanGrowth: - if req.EstimatedSubs > 10000 { - return fmt.Errorf("growth plan limited to 10000 subscribers") - } - } - - return nil -} - -// validateTechnicalRequirements validates technical requirements -func (v *OnboardingValidator) validateTechnicalRequirements(req *OnboardingRequest) error { - if len(req.TargetCountries) == 0 { - return fmt.Errorf("at least one target country must be specified") - } - - if len(req.UseCase) < 10 { - return fmt.Errorf("use case description too short") - } - - // Validate country codes - for _, country := range req.TargetCountries { - if len(country) != 2 { - return fmt.Errorf("invalid country code format: %s", country) - } - } - - return nil -} - -// validateBusinessRegistration validates business registration -func (v *OnboardingValidator) validateBusinessRegistration(businessID string) error { - // In production, this would validate against business registry - // For now, basic format validation - if len(businessID) < 8 { - return fmt.Errorf("business ID too short") - } - return nil -} - -// validateCompliance validates regulatory compliance -func (v *OnboardingValidator) validateCompliance(_ context.Context, mvno *MVNO) error { - // Check regulatory compliance for target countries - for _, country := range mvno.Config.AllowedCountries { - if err := v.checkCountryCompliance(country); err != nil { - return fmt.Errorf("compliance check failed for %s: %w", country, err) - } - } - return nil -} - -// validateTechnicalFeasibility validates technical implementation feasibility -func (v *OnboardingValidator) validateTechnicalFeasibility(mvno *MVNO) error { - // Validate carrier availability for target countries - for _, country := range mvno.Config.AllowedCountries { - if !v.hasCarrierCoverage(country) { - return fmt.Errorf("no carrier coverage available for %s", country) - } - } - return nil -} - -// isValidBusinessID validates business ID format -func (v *OnboardingValidator) isValidBusinessID(id string) bool { - // Basic validation - alphanumeric with possible hyphens - matched, _ := regexp.MatchString(`^[A-Za-z0-9\-]{8,}$`, id) - return matched -} - -// isValidEmail validates email format -func (v *OnboardingValidator) isValidEmail(email string) bool { - matched, _ := regexp.MatchString(`^[^\s@]+@[^\s@]+\.[^\s@]+$`, email) - return matched -} - -// checkCountryCompliance checks if country is compliant -func (v *OnboardingValidator) checkCountryCompliance(country string) error { - // In production, this would check regulatory requirements - // For now, basic validation - restricted := []string{"XX", "YY"} // Example restricted countries - if slices.Contains(restricted, strings.ToUpper(country)) { - return fmt.Errorf("country not supported") - } - return nil -} - -// hasCarrierCoverage checks if carrier coverage exists -func (v *OnboardingValidator) hasCarrierCoverage(country string) bool { - // In production, this would check carrier availability - // For now, assume most countries have coverage - supported := []string{"US", "GB", "DE", "FR", "JP", "AU", "CA"} - return slices.Contains(supported, strings.ToUpper(country)) -} diff --git a/apps/carrier-connector/internal/pricing/optimization_opt_service.go b/apps/carrier-connector/internal/pricing/optimization_opt_service.go deleted file mode 100644 index bc350b9..0000000 --- a/apps/carrier-connector/internal/pricing/optimization_opt_service.go +++ /dev/null @@ -1,199 +0,0 @@ -package pricing - -import ( - "context" - "fmt" - "math" - "time" -) - -// calculateElasticity calculates price elasticity using advanced regression analysis -func (s *PricingOptimizationService) calculateElasticity(_ context.Context, ratePlan *RatePlan) float64 { - // For demonstration, use dynamic elasticity based on rate plan characteristics - // In production, this would use historical data and market analysis - - baseElasticity := -1.2 // Base telecom elasticity - - // Adjust elasticity based on price point - if ratePlan.BasePrice < 20 { - // Lower price plans tend to be more elastic - baseElasticity = -1.5 - } else if ratePlan.BasePrice > 50 { - // Higher price plans tend to be less elastic - baseElasticity = -0.8 - } - - // Add some randomness to simulate market variability - variation := (float64(time.Now().UnixNano()%1000)/1000.0)*0.4 - 0.2 - - finalElasticity := baseElasticity + variation - - // Bounds checking for realistic telecom elasticity - if finalElasticity < -2.0 { - finalElasticity = -2.0 - } else if finalElasticity > -0.3 { - finalElasticity = -0.3 - } - - return finalElasticity -} - -// calculateCompetitiveIndex calculates competitive positioning index using market analysis -func (s *PricingOptimizationService) calculateCompetitiveIndex(ctx context.Context, period string) float64 { - // Advanced competitive index calculation based on multiple factors - // In production, this would analyze real competitor data - - baseIndex := 75.0 // Base competitive position - - // Factor in market conditions (seasonal variations) - month := time.Now().Month() - if month >= time.November || month <= time.January { - // Holiday season - more competitive - baseIndex += 5.0 - } else if month >= time.June && month <= time.August { - // Summer - less competitive - baseIndex -= 3.0 - } - - // Add some market variability - variation := (float64(time.Now().UnixNano()%2000)/2000.0)*10.0 - 5.0 - - finalIndex := baseIndex + variation - - // Bounds: 0-100 scale - if finalIndex < 0 { - finalIndex = 0 - } else if finalIndex > 100 { - finalIndex = 100 - } - - return finalIndex -} - -// calculateOptimizationROI calculates ROI from optimizations using financial modeling -func (s *PricingOptimizationService) calculateOptimizationROI(ctx context.Context, period string) float64 { - // Advanced ROI calculation based on optimization effectiveness - // In production, this would track actual optimization results - - // Base ROI varies by optimization type and market conditions - baseROI := 15.5 // Base optimization ROI - - // Adjust based on period type - switch period { - case "daily": - baseROI *= 0.8 // Short-term optimizations have lower ROI - case "weekly": - baseROI *= 0.9 // Medium-term - case "monthly": - baseROI *= 1.0 // Standard - case "quarterly": - baseROI *= 1.2 // Long-term optimizations have higher ROI - default: - baseROI *= 1.0 - } - - // Factor in market maturity (simulated by time) - hour := time.Now().Hour() - if hour >= 9 && hour <= 17 { - // Business hours - better optimization results - baseROI += 2.0 - } - - // Add variability based on optimization success rate - variability := (float64(time.Now().UnixNano()%1500)/1500.0)*8.0 - 4.0 - - finalROI := baseROI + variability - - // Realistic bounds for telecom optimization ROI - if finalROI < 5.0 { - finalROI = 5.0 - } else if finalROI > 35.0 { - finalROI = 35.0 - } - - return finalROI -} - -// generateAnalysis generates reasoning, risks, and recommendations -func (s *PricingOptimizationService) generateAnalysis(ratePlan *RatePlan, optimalPrice float64, strategy OptimizationStrategy, data []HistoricalDataPoint) ([]string, []string, []string) { - reasoning := make([]string, 0) - risks := make([]string, 0) - recommendations := make([]string, 0) - - priceChange := ((optimalPrice - ratePlan.BasePrice) / ratePlan.BasePrice) * 100 - - // Generate reasoning based on strategy - switch strategy { - case StrategyRevenueMax: - reasoning = append(reasoning, "Optimized for maximum revenue generation") - reasoning = append(reasoning, fmt.Sprintf("Price change of %.1f%% expected to maximize revenue", priceChange)) - - if priceChange > 10 { - risks = append(risks, "Significant price increase may impact demand") - risks = append(risks, "Competitive pressure may increase") - } - - case StrategyMarketShare: - reasoning = append(reasoning, "Optimized for market share growth") - reasoning = append(reasoning, "Lower pricing strategy to attract more customers") - - risks = append(risks, "Lower margins may impact profitability") - risks = append(risks, "May attract price-sensitive customers with higher churn") - - case StrategyCompetitive: - reasoning = append(reasoning, "Priced competitively relative to market") - reasoning = append(reasoning, "Positioned below median competitor pricing") - - risks = append(risks, "Competitors may respond with price cuts") - risks = append(risks, "Margin pressure in competitive market") - } - - // General recommendations - recommendations = append(recommendations, "Monitor demand closely after price change") - recommendations = append(recommendations, "Track competitor pricing responses") - recommendations = append(recommendations, "Review customer feedback and churn rates") - - if math.Abs(priceChange) > 15 { - recommendations = append(recommendations, "Consider gradual price adjustment") - recommendations = append(recommendations, "Implement promotional offers for existing customers") - } - - return reasoning, risks, recommendations -} - -// calculateConfidence calculates confidence level for predictions -func (s *PricingOptimizationService) calculateConfidence(data []HistoricalDataPoint) float64 { - // More data points = higher confidence - dataPoints := len(data) - if dataPoints >= 12 { - return 85.0 - } else if dataPoints >= 6 { - return 70.0 - } else if dataPoints >= 3 { - return 50.0 - } else { - return 25.0 - } -} - -// calculateChurnRate calculates churn rate for period -func (s *PricingOptimizationService) calculateChurnRate(ctx context.Context, period string) float64 { - var totalSubs, churnedSubs int64 - - startDate := s.getPeriodStart(period) - endDate := s.getPeriodEnd(period) - - s.db.WithContext(ctx).Table("profiles"). - Where("created_at < ?", startDate). - Count(&totalSubs) - - s.db.WithContext(ctx).Table("rate_plan_subscriptions"). - Where("ended_at BETWEEN ? AND ?", startDate, endDate). - Count(&churnedSubs) - - if totalSubs == 0 { - return 0 - } - - return float64(churnedSubs) / float64(totalSubs) * 100 -} diff --git a/apps/carrier-connector/internal/pricing/optimization_optimize_service.go b/apps/carrier-connector/internal/pricing/optimization_optimize_service.go deleted file mode 100644 index 1943fab..0000000 --- a/apps/carrier-connector/internal/pricing/optimization_optimize_service.go +++ /dev/null @@ -1,180 +0,0 @@ -package pricing - -import ( - "math" -) - -func (s *PricingOptimizationService) calculatePriceElasticity(data []HistoricalDataPoint) float64 { - if len(data) < 2 { - return -1.2 - } - - var sumX, sumY, sumXY, sumX2 float64 - n := float64(len(data)) - - for i := 1; i < len(data); i++ { - priceChange := (data[i].Price - data[i-1].Price) / data[i-1].Price - demandChange := float64(data[i].Demand-data[i-1].Demand) / float64(data[i-1].Demand) - - if priceChange != 0 { - logPrice := math.Abs(priceChange) - logDemand := math.Abs(demandChange) - sumX += logPrice - sumY += logDemand - sumXY += logPrice * logDemand - sumX2 += logPrice * logPrice - } - } - - elasticity := (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX) - if elasticity > 0 { - elasticity = -elasticity - } - - if elasticity < -2.0 { - elasticity = -2.0 - } else if elasticity > -0.3 { - elasticity = -0.3 - } - - return elasticity -} - -func (s *PricingOptimizationService) optimizeForRevenue(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - if len(data) < 3 { - return ratePlan.BasePrice * 1.05 - } - - elasticity := s.calculatePriceElasticity(data) - optimalPrice := ratePlan.BasePrice * (1.0 - elasticity/(-elasticity+1)) - - minPrice := ratePlan.BasePrice * 0.7 - maxPrice := ratePlan.BasePrice * 1.8 - - if optimalPrice < minPrice { - optimalPrice = minPrice - } else if optimalPrice > maxPrice { - optimalPrice = maxPrice - } - - return math.Round(optimalPrice*100) / 100 -} - -func (s *PricingOptimizationService) optimizeForMarketShare(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - if len(data) < 3 { - return ratePlan.BasePrice * 0.90 - } - - elasticity := s.calculatePriceElasticity(data) - var priceReduction float64 - - if elasticity < -1.0 { - priceReduction = 0.15 - } else if elasticity < -0.5 { - priceReduction = 0.10 - } else { - priceReduction = 0.20 - } - - optimalPrice := ratePlan.BasePrice * (1.0 - priceReduction) - minPrice := ratePlan.BasePrice * 0.6 - - if optimalPrice < minPrice { - optimalPrice = minPrice - } - - return math.Round(optimalPrice*100) / 100 -} - -func (s *PricingOptimizationService) optimizeForProfitMargin(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - variableCost := ratePlan.BasePrice * 0.45 - fixedCost := ratePlan.BasePrice * 0.25 - totalCost := variableCost + fixedCost - targetMargin := 0.40 - - optimalPrice := totalCost / (1.0 - targetMargin) - - if len(data) >= 3 { - elasticity := s.calculatePriceElasticity(data) - if elasticity < -1.2 { - targetMargin = 0.25 - optimalPrice = totalCost / (1.0 - targetMargin) - } - } - - maxPrice := ratePlan.BasePrice * 2.0 - if optimalPrice > maxPrice { - optimalPrice = maxPrice - } - - return math.Round(optimalPrice*100) / 100 -} - -func (s *PricingOptimizationService) optimizeForCompetitive(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - competitorPrices := []float64{9.99, 12.99, 14.99, 16.99} - medianPrice := competitorPrices[len(competitorPrices)/2] - return medianPrice * 0.95 -} - -func (s *PricingOptimizationService) optimizeForChurnReduction(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - return ratePlan.BasePrice * 0.9 -} - -func (s *PricingOptimizationService) predictOutcomes(ratePlan *RatePlan, price float64, data []HistoricalDataPoint) (float64, int64) { - demand := s.predictDemand(price, data) - return price * float64(demand), demand -} - -func (s *PricingOptimizationService) predictDemand(price float64, data []HistoricalDataPoint) int64 { - if len(data) < 2 { - if price < 20 { - return 5000 - } else if price < 50 { - return 2000 - } else { - return 800 - } - } - - var totalElasticity, totalWeight float64 - for i := 1; i < len(data); i++ { - priceChange := (data[i].Price - data[i-1].Price) / data[i-1].Price - if priceChange != 0 { - demandChange := float64(data[i].Demand-data[i-1].Demand) / float64(data[i-1].Demand) - elasticity := demandChange / priceChange - weight := float64(len(data)-i) / float64(len(data)) - totalElasticity += elasticity * weight - totalWeight += weight - } - } - - avgElasticity := totalElasticity / totalWeight - baseDemand := float64(data[0].Demand) - - // Apply elasticity with non-linear adjustments - priceChangeNew := (price - data[0].Price) / data[0].Price - - var demandMultiplier float64 - if math.Abs(priceChangeNew) < 0.1 { - demandMultiplier = 1 + avgElasticity*priceChangeNew - } else { - sign := 1.0 - if priceChangeNew < 0 { - sign = -1.0 - } - magnitude := math.Abs(priceChangeNew) - demandMultiplier = 1 + sign*math.Pow(magnitude, 0.8)*avgElasticity - } - - predictedDemand := baseDemand * demandMultiplier - maxDemand := baseDemand * 3.0 - minDemand := baseDemand * 0.1 - - if predictedDemand > maxDemand { - predictedDemand = maxDemand - } else if predictedDemand < minDemand { - predictedDemand = minDemand - } - - return int64(math.Max(100, math.Round(predictedDemand))) -} diff --git a/apps/carrier-connector/internal/pricing/optimization_period_service.go b/apps/carrier-connector/internal/pricing/optimization_period_service.go deleted file mode 100644 index 02f8823..0000000 --- a/apps/carrier-connector/internal/pricing/optimization_period_service.go +++ /dev/null @@ -1,27 +0,0 @@ -package pricing - -import ( - "time" -) - -// getPeriodStart returns start date for period -func (s *PricingOptimizationService) getPeriodStart(period string) time.Time { - now := time.Now() - switch period { - case "daily": - return now.Truncate(24 * time.Hour) - case "weekly": - return now.AddDate(0, 0, -7) - case "monthly": - return now.AddDate(0, -1, 0) - case "quarterly": - return now.AddDate(0, -3, 0) - default: - return now.AddDate(0, -1, 0) - } -} - -// getPeriodEnd returns end date for period -func (s *PricingOptimizationService) getPeriodEnd(period string) time.Time { - return time.Now() -} diff --git a/apps/carrier-connector/internal/pricing/optimization_service.go b/apps/carrier-connector/internal/pricing/optimization_service.go deleted file mode 100644 index 2500498..0000000 --- a/apps/carrier-connector/internal/pricing/optimization_service.go +++ /dev/null @@ -1,211 +0,0 @@ -package pricing - -import ( - "context" - "fmt" - "time" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// OptimizationStrategy represents pricing optimization strategies -type OptimizationStrategy string - -const ( - StrategyRevenueMax OptimizationStrategy = "revenue_maximization" - StrategyMarketShare OptimizationStrategy = "market_share" - StrategyProfitMargin OptimizationStrategy = "profit_margin" - StrategyCompetitive OptimizationStrategy = "competitive" - StrategyChurnReduction OptimizationStrategy = "churn_reduction" -) - -// PricingOptimizationService provides automated pricing optimization -type PricingOptimizationService struct { - db *gorm.DB - logger *logrus.Logger -} - -// NewPricingOptimizationService creates a new pricing optimization service -func NewPricingOptimizationService(db *gorm.DB, logger *logrus.Logger) *PricingOptimizationService { - return &PricingOptimizationService{ - db: db, - logger: logger, - } -} - -// OptimizePricing optimizes pricing for rate plans -func (s *PricingOptimizationService) OptimizePricing(ctx context.Context, ratePlanIDs []string, strategy OptimizationStrategy) ([]*OptimizationResult, error) { - results := make([]*OptimizationResult, 0) - - for _, ratePlanID := range ratePlanIDs { - result, err := s.optimizeRatePlan(ctx, ratePlanID, strategy) - if err != nil { - s.logger.WithError(err).Error("Failed to optimize rate plan", "rate_plan_id", ratePlanID) - continue - } - results = append(results, result) - } - - return results, nil -} - -// optimizeRatePlan optimizes a single rate plan -func (s *PricingOptimizationService) optimizeRatePlan(ctx context.Context, ratePlanID string, strategy OptimizationStrategy) (*OptimizationResult, error) { - // Get current rate plan data - ratePlan, err := s.getRatePlan(ctx, ratePlanID) - if err != nil { - return nil, err - } - - // Get historical data - historicalData, err := s.getHistoricalData(ctx, ratePlanID) - if err != nil { - return nil, err - } - - // Calculate optimal price based on strategy - optimalPrice := s.calculateOptimalPrice(ratePlan, historicalData, strategy) - - // Calculate expected outcomes - expectedRevenue, expectedDemand := s.predictOutcomes(ratePlan, optimalPrice, historicalData) - - // Generate reasoning and recommendations - reasoning, risks, recommendations := s.generateAnalysis(ratePlan, optimalPrice, strategy, historicalData) - - result := &OptimizationResult{ - RatePlanID: ratePlanID, - Strategy: strategy, - CurrentPrice: ratePlan.BasePrice, - OptimalPrice: optimalPrice, - PriceChange: ((optimalPrice - ratePlan.BasePrice) / ratePlan.BasePrice) * 100, - ExpectedRevenue: expectedRevenue, - ExpectedDemand: expectedDemand, - Confidence: s.calculateConfidence(historicalData), - Reasoning: reasoning, - Risks: risks, - Recommendations: recommendations, - GeneratedAt: time.Now(), - } - - return result, nil -} - -// GetPricingMetrics returns pricing performance metrics -func (s *PricingOptimizationService) GetPricingMetrics(ctx context.Context, period string) (*PricingMetrics, error) { - metrics := &PricingMetrics{ - Period: period, - GeneratedAt: time.Now(), - } - - // Calculate total revenue - var totalRevenue float64 - s.db.WithContext(ctx).Table("billing_transactions"). - Where("status = ? AND created_at BETWEEN ? AND ?", "completed", - s.getPeriodStart(period), s.getPeriodEnd(period)). - Select("COALESCE(SUM(amount), 0)"). - Scan(&totalRevenue) - metrics.TotalRevenue = totalRevenue - - // Calculate total subscribers - var totalSubs int64 - s.db.WithContext(ctx).Table("profiles"). - Where("status = ?", "active"). - Count(&totalSubs) - metrics.TotalSubscribers = totalSubs - - // Calculate ARPU - if totalSubs > 0 { - metrics.ARPU = totalRevenue / float64(totalSubs) - } - - // Calculate churn rate - metrics.ChurnRate = s.calculateChurnRate(ctx, period) - - // Calculate price elasticity - metrics.PriceElasticity = s.calculateElasticity(ctx, &RatePlan{}) - - // Calculate competitive index - metrics.CompetitiveIndex = s.calculateCompetitiveIndex(ctx, period) - - // Calculate optimization ROI - metrics.OptimizationROI = s.calculateOptimizationROI(ctx, period) - - return metrics, nil -} - -// ApplyOptimization applies pricing optimization -func (s *PricingOptimizationService) ApplyOptimization(ctx context.Context, result *OptimizationResult) error { - // Update rate plan price - err := s.db.WithContext(ctx).Table("rate_plans"). - Where("id = ?", result.RatePlanID). - Updates(map[string]interface{}{ - "base_price": result.OptimalPrice, - "updated_at": time.Now(), - }).Error - - if err != nil { - return fmt.Errorf("failed to update rate plan: %w", err) - } - - // Log the optimization - s.logger.WithFields(logrus.Fields{ - "rate_plan_id": result.RatePlanID, - "strategy": result.Strategy, - "old_price": result.CurrentPrice, - "new_price": result.OptimalPrice, - "price_change": result.PriceChange, - "expected_revenue": result.ExpectedRevenue, - }).Info("Pricing optimization applied") - - return nil -} - -// getRatePlan retrieves rate plan data -func (s *PricingOptimizationService) getRatePlan(ctx context.Context, ratePlanID string) (*RatePlan, error) { - var ratePlan RatePlan - err := s.db.WithContext(ctx).Where("id = ?", ratePlanID).First(&ratePlan).Error - if err != nil { - return nil, fmt.Errorf("rate plan not found: %w", err) - } - return &ratePlan, nil -} - -// getHistoricalData retrieves historical pricing and demand data -func (s *PricingOptimizationService) getHistoricalData(_ context.Context, _ string) ([]HistoricalDataPoint, error) { - // Get pricing history and subscription data - var data []HistoricalDataPoint - - // This would query actual historical data - // For now, return simulated data - for i := 0; i < 12; i++ { // Last 12 months - date := time.Now().AddDate(0, -i, 0) - point := HistoricalDataPoint{ - Date: date, - Price: 10.0 + float64(i)*0.5, // Simulated price changes - Demand: 1000 - int64(i)*50, // Simulated demand changes - Revenue: (10.0 + float64(i)*0.5) * float64(1000-int64(i)*50), - } - data = append(data, point) - } - - return data, nil -} - -// calculateOptimalPrice calculates optimal price based on strategy -func (s *PricingOptimizationService) calculateOptimalPrice(ratePlan *RatePlan, data []HistoricalDataPoint, strategy OptimizationStrategy) float64 { - switch strategy { - case StrategyRevenueMax: - return s.optimizeForRevenue(ratePlan, data) - case StrategyMarketShare: - return s.optimizeForMarketShare(ratePlan, data) - case StrategyProfitMargin: - return s.optimizeForProfitMargin(ratePlan, data) - case StrategyCompetitive: - return s.optimizeForCompetitive(ratePlan, data) - case StrategyChurnReduction: - return s.optimizeForChurnReduction(ratePlan, data) - default: - return ratePlan.BasePrice - } -} diff --git a/apps/carrier-connector/internal/pricing/types.go b/apps/carrier-connector/internal/pricing/types.go index 638f566..7569ec7 100644 --- a/apps/carrier-connector/internal/pricing/types.go +++ b/apps/carrier-connector/internal/pricing/types.go @@ -141,45 +141,3 @@ type DiscountStatistics struct { SmallestDiscount float64 `json:"smallest_discount"` TotalDiscountValue float64 `json:"total_discount_value"` } -type OptimizationResult struct { - RatePlanID string `json:"rate_plan_id"` - Strategy OptimizationStrategy `json:"strategy"` - CurrentPrice float64 `json:"current_price"` - OptimalPrice float64 `json:"optimal_price"` - PriceChange float64 `json:"price_change_pct"` - ExpectedRevenue float64 `json:"expected_revenue"` - ExpectedDemand int64 `json:"expected_demand"` - Confidence float64 `json:"confidence"` // 0-100 - Reasoning []string `json:"reasoning"` - Risks []string `json:"risks"` - Recommendations []string `json:"recommendations"` - GeneratedAt time.Time `json:"generated_at"` -} - -// PricingMetrics represents pricing performance metrics -type PricingMetrics struct { - Period string `json:"period"` - TotalRevenue float64 `json:"total_revenue"` - TotalSubscribers int64 `json:"total_subscribers"` - ARPU float64 `json:"arpu"` - ChurnRate float64 `json:"churn_rate_pct"` - PriceElasticity float64 `json:"price_elasticity"` - CompetitiveIndex float64 `json:"competitive_index"` - OptimizationROI float64 `json:"optimization_roi_pct"` - GeneratedAt time.Time `json:"generated_at"` -} -// RatePlan represents a rate plan (simplified) -type RatePlan struct { - ID string `gorm:"primaryKey"` - Name string `json:"name"` - BasePrice float64 `json:"base_price"` - Currency string `json:"currency"` -} - -// HistoricalDataPoint represents historical pricing and demand data -type HistoricalDataPoint struct { - Date time.Time `json:"date"` - Price float64 `json:"price"` - Demand int64 `json:"demand"` - Revenue float64 `json:"revenue"` -} diff --git a/apps/carrier-connector/internal/repository/interfaces.go b/apps/carrier-connector/internal/repository/interfaces.go deleted file mode 100644 index fa7bf2d..0000000 --- a/apps/carrier-connector/internal/repository/interfaces.go +++ /dev/null @@ -1,89 +0,0 @@ -package repository - -import ( - "context" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/mvno" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant" -) - -// TenantRepository defines the interface for tenant data operations -type TenantRepository interface { - // Tenant operations - CreateTenant(ctx context.Context, tenant *tenant.Tenant) error - GetTenant(ctx context.Context, id string) (*tenant.Tenant, error) - GetTenantByDomain(ctx context.Context, domain string) (*tenant.Tenant, error) - UpdateTenant(ctx context.Context, tenant *tenant.Tenant) error - DeleteTenant(ctx context.Context, id string) error - ListTenants(ctx context.Context, filter *tenant.TenantFilter) ([]*tenant.Tenant, error) - CountTenants(ctx context.Context, filter *tenant.TenantFilter) (int, error) - - // Tenant user operations - CreateTenantUser(ctx context.Context, user *tenant.TenantUser) error - GetTenantUser(ctx context.Context, tenantID, userID string) (*tenant.TenantUser, error) - UpdateTenantUser(ctx context.Context, user *tenant.TenantUser) error - DeleteTenantUser(ctx context.Context, tenantID, userID string) error - ListTenantUsers(ctx context.Context, filter *tenant.TenantUserFilter) ([]*tenant.TenantUser, error) - CountTenantUsers(ctx context.Context, filter *tenant.TenantUserFilter) (int, error) - - // API key operations - CreateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error - GetAPIKey(ctx context.Context, id string) (*tenant.TenantAPIKey, error) - GetAPIKeyByHash(ctx context.Context, keyHash string) (*tenant.TenantAPIKey, error) - UpdateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error - DeleteAPIKey(ctx context.Context, id string) error - ListAPIKeys(ctx context.Context, tenantID string) ([]*tenant.TenantAPIKey, error) - - // Usage operations - CreateUsage(ctx context.Context, usage *tenant.TenantUsage) error - GetUsage(ctx context.Context, tenantID, resourceType string) (*tenant.TenantUsage, error) - UpdateUsage(ctx context.Context, usage *tenant.TenantUsage) error - ListUsage(ctx context.Context, filter *tenant.TenantUsageFilter) ([]*tenant.TenantUsage, error) - GetUsageStats(ctx context.Context, tenantID string) (*tenant.TenantUsageStats, error) - - // Configuration operations - GetConfig(ctx context.Context, tenantID string) (*tenant.TenantConfig, error) - UpdateConfig(ctx context.Context, config *tenant.TenantConfig) error - - // Event operations - CreateEvent(ctx context.Context, event *tenant.TenantEvent) error - ListEvents(ctx context.Context, tenantID string, limit int) ([]*tenant.TenantEvent, error) -} - -// Repository defines the interface for rate plan data operations -type Repository interface { - // Rate Plan operations - CreateRatePlan(ctx context.Context, plan *RatePlan) error - GetRatePlan(ctx context.Context, id string) (*RatePlan, error) - UpdateRatePlan(ctx context.Context, plan *RatePlan) error - DeleteRatePlan(ctx context.Context, id string) error - ListRatePlans(ctx context.Context, filter *RatePlanFilter) ([]*RatePlan, error) - - // Subscription operations - CreateSubscription(ctx context.Context, subscription *RatePlanSubscription) error - GetSubscription(ctx context.Context, id string) (*RatePlanSubscription, error) - UpdateSubscription(ctx context.Context, subscription *RatePlanSubscription) error - GetActiveSubscription(ctx context.Context, profileID string) (*RatePlanSubscription, error) - ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error) - - // Usage operations - CreateUsage(ctx context.Context, usage *RatePlanUsage) error - GetUsage(ctx context.Context, id string) (*RatePlanUsage, error) - UpdateUsage(ctx context.Context, usage *RatePlanUsage) error - GetCurrentUsage(ctx context.Context, profileID string) (*RatePlanUsage, error) - ListUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error) - - // Analytics operations - GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error) - GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error) - GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error) - - CreateMVNO(ctx context.Context, mvno *mvno.MVNO) error - GetMVNO(ctx context.Context, id string) (*mvno.MVNO, error) - GetMVNOByBusinessID(ctx context.Context, businessID string) (*mvno.MVNO, error) - UpdateMVNO(ctx context.Context, mvno *mvno.MVNO) error - ListMVNOs(ctx context.Context, filter *mvno.MVNOFilter) ([]*mvno.MVNO, error) - DeleteMVNO(ctx context.Context, id string) error - UpdateMVNOStatus(ctx context.Context, id string, status mvno.MVNOStatus) error - GetMVNOStats(ctx context.Context) (map[string]any, error) -} diff --git a/apps/carrier-connector/internal/repository/mvno_repository.go b/apps/carrier-connector/internal/repository/mvno_repository.go deleted file mode 100644 index eeceb49..0000000 --- a/apps/carrier-connector/internal/repository/mvno_repository.go +++ /dev/null @@ -1,172 +0,0 @@ -package repository - -import ( - "context" - "fmt" - "time" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/mvno" - "gorm.io/gorm" -) - -// MVNOFilter defines filtering options for MVNO queries -type MVNOFilter struct { - Status mvno.MVNOStatus `json:"status,omitempty"` - Plan mvno.MVNOPlan `json:"plan,omitempty"` - BusinessID string `json:"business_id,omitempty"` - Limit int `json:"limit,omitempty"` - Offset int `json:"offset,omitempty"` - CreatedAfter *time.Time `json:"created_after,omitempty"` - CreatedBefore *time.Time `json:"created_before,omitempty"` -} - -// CreateMVNO creates a new MVNO record -func (r *GormRepository) CreateMVNO(ctx context.Context, mvno *mvno.MVNO) error { - if err := r.db.WithContext(ctx).Create(mvno).Error; err != nil { - return fmt.Errorf("failed to create MVNO: %w", err) - } - - r.logger.Info("MVNO created", "id", mvno.ID, "business_id", mvno.BusinessID) - return nil -} - -// GetMVNO retrieves an MVNO by ID -func (r *GormRepository) GetMVNO(ctx context.Context, id string) (*mvno.MVNO, error) { - var mvno mvno.MVNO - if err := r.db.WithContext(ctx).Where("id = ?", id).First(&mvno).Error; err != nil { - if err == gorm.ErrRecordNotFound { - return nil, fmt.Errorf("MVNO not found: %s", id) - } - return nil, fmt.Errorf("failed to get MVNO: %w", err) - } - return &mvno, nil -} - -// GetMVNOByBusinessID retrieves an MVNO by business ID -func (r *GormRepository) GetMVNOByBusinessID(ctx context.Context, businessID string) (*mvno.MVNO, error) { - var mvno mvno.MVNO - if err := r.db.WithContext(ctx).Where("business_id = ?", businessID).First(&mvno).Error; err != nil { - if err == gorm.ErrRecordNotFound { - return nil, fmt.Errorf("MVNO not found for business ID: %s", businessID) - } - return nil, fmt.Errorf("failed to get MVNO by business ID: %w", err) - } - return &mvno, nil -} - -// UpdateMVNO updates an existing MVNO -func (r *GormRepository) UpdateMVNO(ctx context.Context, mvno *mvno.MVNO) error { - if err := r.db.WithContext(ctx).Save(mvno).Error; err != nil { - return fmt.Errorf("failed to update MVNO: %w", err) - } - - r.logger.Info("MVNO updated", "id", mvno.ID, "status", mvno.Status) - return nil -} - -// ListMVNOs lists MVNOs with optional filtering -func (r *GormRepository) ListMVNOs(ctx context.Context, filter *mvno.MVNOFilter) ([]*mvno.MVNO, error) { - query := r.db.WithContext(ctx).Model(&mvno.MVNO{}) - - // Apply filters - if filter != nil { - if filter.Status != "" { - query = query.Where("status = ?", filter.Status) - } - if filter.Plan != "" { - query = query.Where("plan = ?", filter.Plan) - } - if filter.BusinessID != "" { - query = query.Where("business_id = ?", filter.BusinessID) - } - if filter.CreatedAfter != nil { - query = query.Where("created_at >= ?", *filter.CreatedAfter) - } - if filter.CreatedBefore != nil { - query = query.Where("created_at <= ?", *filter.CreatedBefore) - } - if filter.Limit > 0 { - query = query.Limit(filter.Limit) - } - if filter.Offset > 0 { - query = query.Offset(filter.Offset) - } - } - - var mvnos []*mvno.MVNO - if err := query.Order("created_at DESC").Find(&mvnos).Error; err != nil { - return nil, fmt.Errorf("failed to list MVNOs: %w", err) - } - - return mvnos, nil -} - -// DeleteMVNO soft deletes an MVNO -func (r *GormRepository) DeleteMVNO(ctx context.Context, id string) error { - if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&mvno.MVNO{}).Error; err != nil { - return fmt.Errorf("failed to delete MVNO: %w", err) - } - - r.logger.Info("MVNO deleted", "id", id) - return nil -} - -// GetMVNOStats returns statistics about MVNOs -func (r *GormRepository) GetMVNOStats(ctx context.Context) (map[string]any, error) { - stats := make(map[string]any) - - // Total count - var totalCount int64 - if err := r.db.WithContext(ctx).Model(&mvno.MVNO{}).Count(&totalCount).Error; err != nil { - return nil, fmt.Errorf("failed to count total MVNOs: %w", err) - } - stats["total"] = totalCount - - // Count by status - var statusCounts []struct { - Status mvno.MVNOStatus `gorm:"column:status"` - Count int64 `gorm:"column:count"` - } - if err := r.db.WithContext(ctx).Model(&mvno.MVNO{}). - Select("status, COUNT(*) as count"). - Group("status"). - Scan(&statusCounts).Error; err != nil { - return nil, fmt.Errorf("failed to count by status: %w", err) - } - - statusMap := make(map[string]int64) - for _, sc := range statusCounts { - statusMap[string(sc.Status)] = sc.Count - } - stats["by_status"] = statusMap - - // Count by plan - var planCounts []struct { - Plan mvno.MVNOPlan `gorm:"column:plan"` - Count int64 `gorm:"column:count"` - } - if err := r.db.WithContext(ctx).Model(&mvno.MVNO{}). - Select("plan, COUNT(*) as count"). - Group("plan"). - Scan(&planCounts).Error; err != nil { - return nil, fmt.Errorf("failed to count by plan: %w", err) - } - - planMap := make(map[string]int64) - for _, pc := range planCounts { - planMap[string(pc.Plan)] = pc.Count - } - stats["by_plan"] = planMap - - return stats, nil -} - -// UpdateMVNOStatus updates only the status of an MVNO -func (r *GormRepository) UpdateMVNOStatus(ctx context.Context, id string, status mvno.MVNOStatus) error { - if err := r.db.WithContext(ctx).Model(&mvno.MVNO{}).Where("id = ?", id).Update("status", status).Error; err != nil { - return fmt.Errorf("failed to update MVNO status: %w", err) - } - - r.logger.Info("MVNO status updated", "id", id, "new_status", status) - return nil -} diff --git a/apps/carrier-connector/internal/repository/tenant_aware_repository.go b/apps/carrier-connector/internal/repository/tenant_aware_repository.go index c7003fc..207571c 100644 --- a/apps/carrier-connector/internal/repository/tenant_aware_repository.go +++ b/apps/carrier-connector/internal/repository/tenant_aware_repository.go @@ -80,7 +80,7 @@ func (r *TenantAwareRepository) TenantScopedTransaction(ctx context.Context, fn // Since the function expects *gorm.DB, we need to embed the wrapper properly return fn(wrappedTx.DB.Scopes(func(db *gorm.DB) *gorm.DB { // Apply tenant filtering to all queries - return db.Where("tenant_id = ?", wrappedTx.tenantID) + return db.Where("tenant_id = ?", r.tenantID) })) } return fn(tx) diff --git a/apps/carrier-connector/internal/repository/types.go b/apps/carrier-connector/internal/repository/types.go index 7884c7f..103cd62 100644 --- a/apps/carrier-connector/internal/repository/types.go +++ b/apps/carrier-connector/internal/repository/types.go @@ -1,9 +1,84 @@ package repository import ( + "context" "time" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant" ) +// TenantRepository defines the interface for tenant data operations +type TenantRepository interface { + // Tenant operations + CreateTenant(ctx context.Context, tenant *tenant.Tenant) error + GetTenant(ctx context.Context, id string) (*tenant.Tenant, error) + GetTenantByDomain(ctx context.Context, domain string) (*tenant.Tenant, error) + UpdateTenant(ctx context.Context, tenant *tenant.Tenant) error + DeleteTenant(ctx context.Context, id string) error + ListTenants(ctx context.Context, filter *tenant.TenantFilter) ([]*tenant.Tenant, error) + CountTenants(ctx context.Context, filter *tenant.TenantFilter) (int, error) + + // Tenant user operations + CreateTenantUser(ctx context.Context, user *tenant.TenantUser) error + GetTenantUser(ctx context.Context, tenantID, userID string) (*tenant.TenantUser, error) + UpdateTenantUser(ctx context.Context, user *tenant.TenantUser) error + DeleteTenantUser(ctx context.Context, tenantID, userID string) error + ListTenantUsers(ctx context.Context, filter *tenant.TenantUserFilter) ([]*tenant.TenantUser, error) + CountTenantUsers(ctx context.Context, filter *tenant.TenantUserFilter) (int, error) + + // API key operations + CreateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error + GetAPIKey(ctx context.Context, id string) (*tenant.TenantAPIKey, error) + GetAPIKeyByHash(ctx context.Context, keyHash string) (*tenant.TenantAPIKey, error) + UpdateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error + DeleteAPIKey(ctx context.Context, id string) error + ListAPIKeys(ctx context.Context, tenantID string) ([]*tenant.TenantAPIKey, error) + + // Usage operations + CreateUsage(ctx context.Context, usage *tenant.TenantUsage) error + GetUsage(ctx context.Context, tenantID, resourceType string) (*tenant.TenantUsage, error) + UpdateUsage(ctx context.Context, usage *tenant.TenantUsage) error + ListUsage(ctx context.Context, filter *tenant.TenantUsageFilter) ([]*tenant.TenantUsage, error) + GetUsageStats(ctx context.Context, tenantID string) (*tenant.TenantUsageStats, error) + + // Configuration operations + GetConfig(ctx context.Context, tenantID string) (*tenant.TenantConfig, error) + UpdateConfig(ctx context.Context, config *tenant.TenantConfig) error + + // Event operations + CreateEvent(ctx context.Context, event *tenant.TenantEvent) error + ListEvents(ctx context.Context, tenantID string, limit int) ([]*tenant.TenantEvent, error) +} + +// Repository defines the interface for rate plan data operations +type Repository interface { + // Rate Plan operations + CreateRatePlan(ctx context.Context, plan *RatePlan) error + GetRatePlan(ctx context.Context, id string) (*RatePlan, error) + UpdateRatePlan(ctx context.Context, plan *RatePlan) error + DeleteRatePlan(ctx context.Context, id string) error + ListRatePlans(ctx context.Context, filter *RatePlanFilter) ([]*RatePlan, error) + + // Subscription operations + CreateSubscription(ctx context.Context, subscription *RatePlanSubscription) error + GetSubscription(ctx context.Context, id string) (*RatePlanSubscription, error) + UpdateSubscription(ctx context.Context, subscription *RatePlanSubscription) error + GetActiveSubscription(ctx context.Context, profileID string) (*RatePlanSubscription, error) + ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error) + + // Usage operations + CreateUsage(ctx context.Context, usage *RatePlanUsage) error + GetUsage(ctx context.Context, id string) (*RatePlanUsage, error) + UpdateUsage(ctx context.Context, usage *RatePlanUsage) error + GetCurrentUsage(ctx context.Context, profileID string) (*RatePlanUsage, error) + ListUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error) + + // Analytics operations + GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error) + GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error) + GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error) +} + // RatePlanUsage represents usage data for a rate plan type RatePlanUsage struct { ID string `json:"id" gorm:"primaryKey"` diff --git a/apps/carrier-connector/internal/security/encryption.go b/apps/carrier-connector/internal/security/encryption.go deleted file mode 100644 index 66c0c7a..0000000 --- a/apps/carrier-connector/internal/security/encryption.go +++ /dev/null @@ -1,152 +0,0 @@ -package security - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "errors" - "io" - - "golang.org/x/crypto/pbkdf2" -) - -// EncryptionService provides data encryption at rest -type EncryptionService struct { - masterKey []byte - gcm cipher.AEAD -} - -// EncryptionConfig configures encryption -type EncryptionConfig struct { - MasterKey string - KeyDerivationSalt string -} - -// NewEncryptionService creates a new encryption service -func NewEncryptionService(config EncryptionConfig) (*EncryptionService, error) { - salt := []byte(config.KeyDerivationSalt) - if len(salt) == 0 { - salt = []byte("telecom-platform-default-salt") - } - - key := pbkdf2.Key([]byte(config.MasterKey), salt, 100000, 32, sha256.New) - - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - return &EncryptionService{ - masterKey: key, - gcm: gcm, - }, nil -} - -// Encrypt encrypts plaintext data -func (e *EncryptionService) Encrypt(plaintext []byte) ([]byte, error) { - nonce := make([]byte, e.gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - - ciphertext := e.gcm.Seal(nonce, nonce, plaintext, nil) - return ciphertext, nil -} - -// Decrypt decrypts ciphertext data -func (e *EncryptionService) Decrypt(ciphertext []byte) ([]byte, error) { - if len(ciphertext) < e.gcm.NonceSize() { - return nil, errors.New("ciphertext too short") - } - - nonce := ciphertext[:e.gcm.NonceSize()] - ciphertext = ciphertext[e.gcm.NonceSize():] - - plaintext, err := e.gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return nil, err - } - - return plaintext, nil -} - -// EncryptString encrypts a string and returns base64 -func (e *EncryptionService) EncryptString(plaintext string) (string, error) { - encrypted, err := e.Encrypt([]byte(plaintext)) - if err != nil { - return "", err - } - return base64.StdEncoding.EncodeToString(encrypted), nil -} - -// DecryptString decrypts a base64 string -func (e *EncryptionService) DecryptString(ciphertext string) (string, error) { - data, err := base64.StdEncoding.DecodeString(ciphertext) - if err != nil { - return "", err - } - decrypted, err := e.Decrypt(data) - if err != nil { - return "", err - } - return string(decrypted), nil -} - -// FieldEncryptor provides field-level encryption for sensitive data -type FieldEncryptor struct { - service *EncryptionService - fields map[string]bool -} - -// NewFieldEncryptor creates a field encryptor -func NewFieldEncryptor(service *EncryptionService, sensitiveFields []string) *FieldEncryptor { - fields := make(map[string]bool) - for _, f := range sensitiveFields { - fields[f] = true - } - return &FieldEncryptor{ - service: service, - fields: fields, - } -} - -// IsSensitive checks if a field is sensitive -func (fe *FieldEncryptor) IsSensitive(field string) bool { - return fe.fields[field] -} - -// EncryptField encrypts a field value if sensitive -func (fe *FieldEncryptor) EncryptField(field, value string) (string, error) { - if !fe.IsSensitive(field) { - return value, nil - } - return fe.service.EncryptString(value) -} - -// DecryptField decrypts a field value if sensitive -func (fe *FieldEncryptor) DecryptField(field, value string) (string, error) { - if !fe.IsSensitive(field) { - return value, nil - } - return fe.service.DecryptString(value) -} - -// DefaultSensitiveFields returns commonly sensitive fields -func DefaultSensitiveFields() []string { - return []string{ - "ssn", "social_security_number", - "credit_card", "card_number", "cvv", - "password", "secret", "api_key", - "phone", "email", "address", - "date_of_birth", "dob", - "bank_account", "routing_number", - "iccid", "imsi", "msisdn", - } -} diff --git a/apps/carrier-connector/internal/security/fraud_service.go b/apps/carrier-connector/internal/security/fraud_service.go deleted file mode 100644 index 3ef6179..0000000 --- a/apps/carrier-connector/internal/security/fraud_service.go +++ /dev/null @@ -1,263 +0,0 @@ -package security - -import ( - "context" - "fmt" - "math" - "sync" - "time" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// FraudDetectionService provides fraud detection capabilities -type FraudDetectionService struct { - db *gorm.DB - logger *logrus.Logger - patterns []FraudPattern - alerts []*FraudAlert - mu sync.RWMutex -} - -// NewFraudDetectionService creates a new fraud detection service -func NewFraudDetectionService(db *gorm.DB, logger *logrus.Logger, cfg FraudConfig) *FraudDetectionService { - svc := &FraudDetectionService{ - db: db, - logger: logger, - patterns: DefaultFraudPatterns(), - alerts: make([]*FraudAlert, 0), - } - go svc.cleanupAlerts(cfg.AlertRetentionDays) - return svc -} - -// AnalyzeTransaction analyzes a transaction for fraud -func (s *FraudDetectionService) AnalyzeTransaction(ctx context.Context, tx map[string]interface{}) (*FraudAlert, error) { - s.mu.Lock() - defer s.mu.Unlock() - - alert := &FraudAlert{ - ID: fmt.Sprintf("fraud-%d", time.Now().UnixNano()), - Timestamp: time.Now(), - Status: "new", - Metadata: make(map[string]any), - } - - if id, ok := tx["profile_id"].(string); ok { - alert.ProfileID = id - } - if ip, ok := tx["ip_address"].(string); ok { - alert.IPAddress = ip - } - - score, evidence := s.evaluatePatterns(ctx, tx) - alert.RiskScore = math.Min(100, score) - alert.Evidence = evidence - alert.Severity = SeverityFromScore(alert.RiskScore) - alert.Type = s.detectType(evidence) - alert.Description = fmt.Sprintf("%s %s fraud detected", alert.Severity, alert.Type) - - if alert.RiskScore >= 80 { - alert.Actions = append(alert.Actions, "auto_blocked") - s.blockProfile(ctx, alert.ProfileID) - } else if alert.RiskScore >= 60 { - alert.Actions = append(alert.Actions, "flagged_for_review") - } - - s.alerts = append(s.alerts, alert) - s.logger.WithField("alert_id", alert.ID).WithField("risk_score", alert.RiskScore).Warn("Fraud detected") - - return alert, nil -} - -// GetFraudAlerts retrieves fraud alerts -func (s *FraudDetectionService) GetFraudAlerts(_ context.Context, filter FraudAlertFilter) ([]*FraudAlert, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - var result []*FraudAlert - for _, a := range s.alerts { - if s.matchesFilter(a, filter) { - result = append(result, a) - } - } - return result, nil -} - -// UpdateAlertStatus updates the status of a fraud alert -func (s *FraudDetectionService) UpdateAlertStatus(_ context.Context, alertID, status string, actions []string) error { - s.mu.Lock() - defer s.mu.Unlock() - - for _, a := range s.alerts { - if a.ID == alertID { - a.Status = status - a.Actions = append(a.Actions, actions...) - return nil - } - } - return fmt.Errorf("alert not found: %s", alertID) -} - -// GetFraudMetrics returns fraud detection metrics -func (s *FraudDetectionService) GetFraudMetrics(_ context.Context, period string) (*FraudMetrics, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - start, end := fraudPeriodDates(period) - m := &FraudMetrics{Period: period, GeneratedAt: time.Now(), ByType: make(map[FraudType]int64), BySeverity: make(map[FraudSeverity]int64)} - - for _, a := range s.alerts { - if a.Timestamp.After(start) && a.Timestamp.Before(end) { - m.TotalAlerts++ - m.ByType[a.Type]++ - m.BySeverity[a.Severity]++ - switch a.Status { - case "resolved": - m.ResolvedAlerts++ - case "false_positive": - m.FalsePositives++ - } - } - } - - if m.TotalAlerts > 0 { - m.ResolutionRate = float64(m.ResolvedAlerts) / float64(m.TotalAlerts) * 100 - m.FalsePositiveRate = float64(m.FalsePositives) / float64(m.TotalAlerts) * 100 - } - return m, nil -} - -func (s *FraudDetectionService) evaluatePatterns(ctx context.Context, tx map[string]interface{}) (float64, []string) { - var score float64 - var evidence []string - - profileID, _ := tx["profile_id"].(string) - if profileID == "" { - return 0, nil - } - - for _, p := range s.patterns { - if !p.Enabled { - continue - } - ps, ev := s.checkPattern(ctx, profileID, p) - if ps > 0 { - score += ps * p.Weight - evidence = append(evidence, ev...) - } - } - return score, evidence -} - -func (s *FraudDetectionService) checkPattern(ctx context.Context, profileID string, p FraudPattern) (float64, []string) { - var count int64 - - switch p.ID { - case "multiple_subs": - s.db.WithContext(ctx).Table("rate_plan_subscriptions").Where("profile_id = ? AND status = ?", profileID, "active").Count(&count) - if count > int64(p.Threshold) { - return float64(count) * 20, []string{fmt.Sprintf("%d active subscriptions", count)} - } - case "rapid_sub": - s.db.WithContext(ctx).Table("rate_plan_subscriptions").Where("profile_id = ? AND created_at > ?", profileID, time.Now().Add(-time.Hour)).Count(&count) - if count > int64(p.Threshold) { - return float64(count) * 15, []string{fmt.Sprintf("%d subscriptions in last hour", count)} - } - case "payment_fail": - s.db.WithContext(ctx).Table("billing_transactions").Where("profile_id = ? AND status = ? AND created_at > ?", profileID, "failed", time.Now().Add(-24*time.Hour)).Count(&count) - if count > int64(p.Threshold) { - return float64(count) * 25, []string{fmt.Sprintf("%d payment failures", count)} - } - case "sim_swap": - s.db.WithContext(ctx).Table("profiles").Where("id = ? AND updated_at > ?", profileID, time.Now().Add(-time.Hour)).Count(&count) - if count > 2 { - return 80, []string{"possible SIM swap detected"} - } - } - return 0, nil -} - -func (s *FraudDetectionService) detectType(evidence []string) FraudType { - for _, e := range evidence { - if contains(e, "subscription") { - return FraudTypeSubscriptionFraud - } - if contains(e, "payment") { - return FraudTypePaymentFraud - } - if contains(e, "SIM") { - return FraudTypeSIMSwap - } - } - return FraudTypeSubscriptionFraud -} - -func (s *FraudDetectionService) blockProfile(ctx context.Context, profileID string) { - s.db.WithContext(ctx).Table("profiles").Where("id = ?", profileID).Updates(map[string]interface{}{"status": "blocked", "blocked_at": time.Now()}) - s.logger.WithField("profile_id", profileID).Warn("Profile blocked for fraud") -} - -func (s *FraudDetectionService) matchesFilter(a *FraudAlert, f FraudAlertFilter) bool { - if f.Type != "" && a.Type != f.Type { - return false - } - if f.Severity != "" && a.Severity != f.Severity { - return false - } - if f.Status != "" && a.Status != f.Status { - return false - } - if f.FromDate != nil && a.Timestamp.Before(*f.FromDate) { - return false - } - if f.ToDate != nil && a.Timestamp.After(*f.ToDate) { - return false - } - return true -} - -func (s *FraudDetectionService) cleanupAlerts(days int) { - ticker := time.NewTicker(24 * time.Hour) - defer ticker.Stop() - for range ticker.C { - s.mu.Lock() - cutoff := time.Now().AddDate(0, 0, -days) - var filtered []*FraudAlert - for _, a := range s.alerts { - if a.Timestamp.After(cutoff) { - filtered = append(filtered, a) - } - } - s.alerts = filtered - s.mu.Unlock() - } -} - -func fraudPeriodDates(period string) (time.Time, time.Time) { - now := time.Now() - switch period { - case "daily": - return now.Truncate(24 * time.Hour), now - case "weekly": - return now.AddDate(0, 0, -7), now - case "monthly": - return now.AddDate(0, -1, 0), now - default: - return now.AddDate(0, -1, 0), now - } -} - -func contains(s, substr string) bool { - return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsAt(s, substr)) -} - -func containsAt(s, substr string) bool { - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false -} diff --git a/apps/carrier-connector/internal/security/fraud_types.go b/apps/carrier-connector/internal/security/fraud_types.go deleted file mode 100644 index a387f07..0000000 --- a/apps/carrier-connector/internal/security/fraud_types.go +++ /dev/null @@ -1,105 +0,0 @@ -package security - -import "time" - -// FraudType represents different types of fraud -type FraudType string - -const ( - FraudTypeAccountTakeover FraudType = "account_takeover" - FraudTypeSubscriptionFraud FraudType = "subscription_fraud" - FraudTypePaymentFraud FraudType = "payment_fraud" - FraudTypeUsageAnomaly FraudType = "usage_anomaly" - FraudTypeSIMSwap FraudType = "sim_swap" -) - -// FraudSeverity represents the severity of fraud detection -type FraudSeverity string - -const ( - FraudSeverityLow FraudSeverity = "low" - FraudSeverityMedium FraudSeverity = "medium" - FraudSeverityHigh FraudSeverity = "high" - FraudSeverityCritical FraudSeverity = "critical" -) - -// FraudAlert represents a fraud detection alert -type FraudAlert struct { - ID string `json:"id"` - Type FraudType `json:"type"` - Severity FraudSeverity `json:"severity"` - ProfileID string `json:"profile_id"` - Description string `json:"description"` - RiskScore float64 `json:"risk_score"` - Evidence []string `json:"evidence"` - IPAddress string `json:"ip_address"` - Timestamp time.Time `json:"timestamp"` - Status string `json:"status"` - Actions []string `json:"actions_taken"` - Metadata map[string]any `json:"metadata"` -} - -// FraudPattern represents a fraud detection pattern -type FraudPattern struct { - ID string `json:"id"` - Name string `json:"name"` - Type FraudType `json:"type"` - Threshold float64 `json:"threshold"` - Weight float64 `json:"weight"` - Enabled bool `json:"enabled"` -} - -// FraudMetrics represents fraud detection metrics -type FraudMetrics struct { - Period string `json:"period"` - TotalAlerts int64 `json:"total_alerts"` - ResolvedAlerts int64 `json:"resolved_alerts"` - FalsePositives int64 `json:"false_positives"` - ResolutionRate float64 `json:"resolution_rate_pct"` - FalsePositiveRate float64 `json:"false_positive_rate_pct"` - ByType map[FraudType]int64 `json:"by_type"` - BySeverity map[FraudSeverity]int64 `json:"by_severity"` - GeneratedAt time.Time `json:"generated_at"` -} - -// FraudAlertFilter filters fraud alerts -type FraudAlertFilter struct { - Type FraudType `json:"type,omitempty"` - Severity FraudSeverity `json:"severity,omitempty"` - Status string `json:"status,omitempty"` - FromDate *time.Time `json:"from_date,omitempty"` - ToDate *time.Time `json:"to_date,omitempty"` -} - -// FraudConfig configures the fraud detection service -type FraudConfig struct { - EnableMLModels bool - AlertRetentionDays int - AutoBlockThreshold float64 -} - -// DefaultFraudPatterns returns standard fraud detection patterns -func DefaultFraudPatterns() []FraudPattern { - return []FraudPattern{ - {ID: "multiple_subs", Name: "Multiple Subscriptions", Type: FraudTypeSubscriptionFraud, Threshold: 3, Weight: 0.3, Enabled: true}, - {ID: "rapid_sub", Name: "Rapid Subscription", Type: FraudTypeSubscriptionFraud, Threshold: 5, Weight: 0.4, Enabled: true}, - {ID: "unusual_loc", Name: "Unusual Location", Type: FraudTypeAccountTakeover, Threshold: 0.8, Weight: 0.5, Enabled: true}, - {ID: "usage_spike", Name: "Usage Spike", Type: FraudTypeUsageAnomaly, Threshold: 1000, Weight: 0.3, Enabled: true}, - {ID: "payment_fail", Name: "Payment Failures", Type: FraudTypePaymentFraud, Threshold: 3, Weight: 0.6, Enabled: true}, - {ID: "sim_swap", Name: "SIM Swap", Type: FraudTypeSIMSwap, Threshold: 0.7, Weight: 0.8, Enabled: true}, - } -} - -// SeverityFromScore determines severity from risk score -func SeverityFromScore(score float64) FraudSeverity { - switch { - case score >= 80: - return FraudSeverityCritical - case score >= 60: - return FraudSeverityHigh - case score >= 40: - return FraudSeverityMedium - default: - return FraudSeverityLow - } -} diff --git a/apps/carrier-connector/internal/security/threat_detection.go b/apps/carrier-connector/internal/security/threat_detection.go deleted file mode 100644 index 3b1d5b2..0000000 --- a/apps/carrier-connector/internal/security/threat_detection.go +++ /dev/null @@ -1,298 +0,0 @@ -package security - -import ( - "context" - "sync" - "time" - - "github.com/sirupsen/logrus" -) - -// ThreatLevel represents severity of detected threats -type ThreatLevel string - -const ( - ThreatLevelLow ThreatLevel = "low" - ThreatLevelMedium ThreatLevel = "medium" - ThreatLevelHigh ThreatLevel = "high" - ThreatLevelCritical ThreatLevel = "critical" -) - -// ThreatType represents types of security threats -type ThreatType string - -const ( - ThreatTypeBruteForce ThreatType = "brute_force" - ThreatTypeRateLimitAbuse ThreatType = "rate_limit_abuse" - ThreatTypeSQLInjection ThreatType = "sql_injection" - ThreatTypeXSS ThreatType = "xss" - ThreatTypeUnauthorized ThreatType = "unauthorized_access" - ThreatTypeDataExfil ThreatType = "data_exfiltration" - ThreatTypeAnomalous ThreatType = "anomalous_behavior" -) - -// ThreatEvent represents a detected security threat -type ThreatEvent struct { - ID string `json:"id"` - TenantID string `json:"tenant_id"` - Type ThreatType `json:"type"` - Level ThreatLevel `json:"level"` - Source string `json:"source"` - Target string `json:"target"` - Description string `json:"description"` - Metadata map[string]any `json:"metadata"` - DetectedAt time.Time `json:"detected_at"` - Mitigated bool `json:"mitigated"` - MitigatedAt *time.Time `json:"mitigated_at"` -} - -// ThreatDetector provides threat detection capabilities -type ThreatDetector struct { - logger *logrus.Logger - rules []DetectionRule - events []*ThreatEvent - ipTracker map[string]*IPActivity - mu sync.RWMutex - alertHandler func(*ThreatEvent) -} - -// IPActivity tracks activity per IP -type IPActivity struct { - IP string - FailedLogins int - RequestCount int - LastRequest time.Time - FirstSeen time.Time - SuspiciousCount int -} - -// DetectionRule defines a threat detection rule -type DetectionRule struct { - ID string - Name string - Type ThreatType - Threshold int - Window time.Duration - Level ThreatLevel - Enabled bool -} - -// ThreatDetectorConfig configures the threat detector -type ThreatDetectorConfig struct { - MaxFailedLogins int - RateLimitThreshold int - AlertHandler func(*ThreatEvent) -} - -// DefaultThreatDetectorConfig returns default configuration -func DefaultThreatDetectorConfig() ThreatDetectorConfig { - return ThreatDetectorConfig{ - MaxFailedLogins: 5, - RateLimitThreshold: 1000, - } -} - -// NewThreatDetector creates a new threat detector -func NewThreatDetector(logger *logrus.Logger, config ThreatDetectorConfig) *ThreatDetector { - td := &ThreatDetector{ - logger: logger, - ipTracker: make(map[string]*IPActivity), - alertHandler: config.AlertHandler, - rules: defaultRules(config), - } - go td.cleanupLoop() - return td -} - -func defaultRules(config ThreatDetectorConfig) []DetectionRule { - return []DetectionRule{ - { - ID: "brute_force_login", - Name: "Brute Force Login Detection", - Type: ThreatTypeBruteForce, - Threshold: config.MaxFailedLogins, - Window: 5 * time.Minute, - Level: ThreatLevelHigh, - Enabled: true, - }, - { - ID: "rate_limit_abuse", - Name: "Rate Limit Abuse Detection", - Type: ThreatTypeRateLimitAbuse, - Threshold: config.RateLimitThreshold, - Window: time.Minute, - Level: ThreatLevelMedium, - Enabled: true, - }, - } -} - -// RecordRequest records an API request for analysis -func (td *ThreatDetector) RecordRequest(ctx context.Context, ip, path, method string) { - td.mu.Lock() - defer td.mu.Unlock() - - activity, exists := td.ipTracker[ip] - if !exists { - activity = &IPActivity{ - IP: ip, - FirstSeen: time.Now(), - } - td.ipTracker[ip] = activity - } - - activity.RequestCount++ - activity.LastRequest = time.Now() - - // Check rate limit rule - for _, rule := range td.rules { - if rule.Type == ThreatTypeRateLimitAbuse && rule.Enabled { - if activity.RequestCount > rule.Threshold { - td.raiseAlert(&ThreatEvent{ - Type: ThreatTypeRateLimitAbuse, - Level: rule.Level, - Source: ip, - Target: path, - Description: "Rate limit threshold exceeded", - DetectedAt: time.Now(), - }) - } - } - } -} - -// RecordFailedLogin records a failed login attempt -func (td *ThreatDetector) RecordFailedLogin(ctx context.Context, ip, userID string) { - td.mu.Lock() - defer td.mu.Unlock() - - activity, exists := td.ipTracker[ip] - if !exists { - activity = &IPActivity{ - IP: ip, - FirstSeen: time.Now(), - } - td.ipTracker[ip] = activity - } - - activity.FailedLogins++ - activity.LastRequest = time.Now() - - // Check brute force rule - for _, rule := range td.rules { - if rule.Type == ThreatTypeBruteForce && rule.Enabled { - if activity.FailedLogins >= rule.Threshold { - td.raiseAlert(&ThreatEvent{ - Type: ThreatTypeBruteForce, - Level: rule.Level, - Source: ip, - Target: userID, - Description: "Multiple failed login attempts detected", - DetectedAt: time.Now(), - Metadata: map[string]any{"attempts": activity.FailedLogins}, - }) - } - } - } -} - -// DetectSQLInjection checks for SQL injection patterns -func (td *ThreatDetector) DetectSQLInjection(_ context.Context, ip, input string) bool { - patterns := []string{ - "'--", "'; DROP", "1=1", "OR 1=1", "UNION SELECT", - "'; DELETE", "'; UPDATE", "'; INSERT", - } - - for _, pattern := range patterns { - if containsIgnoreCase(input, pattern) { - td.raiseAlert(&ThreatEvent{ - Type: ThreatTypeSQLInjection, - Level: ThreatLevelCritical, - Source: ip, - Description: "SQL injection attempt detected", - DetectedAt: time.Now(), - Metadata: map[string]any{"pattern": pattern}, - }) - return true - } - } - return false -} - -func containsIgnoreCase(s, substr string) bool { - return len(s) >= len(substr) // Simplified check -} - -func (td *ThreatDetector) raiseAlert(event *ThreatEvent) { - td.events = append(td.events, event) - - td.logger.WithFields(logrus.Fields{ - "type": event.Type, - "level": event.Level, - "source": event.Source, - }).Warn("Security threat detected") - - if td.alertHandler != nil { - td.alertHandler(event) - } -} - -// GetRecentEvents returns recent threat events -func (td *ThreatDetector) GetRecentEvents(limit int) []*ThreatEvent { - td.mu.RLock() - defer td.mu.RUnlock() - - if len(td.events) <= limit { - return td.events - } - return td.events[len(td.events)-limit:] -} - -// GetIPActivity returns activity for an IP -func (td *ThreatDetector) GetIPActivity(ip string) *IPActivity { - td.mu.RLock() - defer td.mu.RUnlock() - return td.ipTracker[ip] -} - -// BlockIP marks an IP as blocked -func (td *ThreatDetector) BlockIP(ip string) { - td.mu.Lock() - defer td.mu.Unlock() - - if activity, exists := td.ipTracker[ip]; exists { - activity.SuspiciousCount = 999 - } -} - -// IsBlocked checks if an IP is blocked -func (td *ThreatDetector) IsBlocked(ip string) bool { - td.mu.RLock() - defer td.mu.RUnlock() - - if activity, exists := td.ipTracker[ip]; exists { - return activity.SuspiciousCount >= 999 - } - return false -} - -func (td *ThreatDetector) cleanupLoop() { - ticker := time.NewTicker(10 * time.Minute) - defer ticker.Stop() - - for range ticker.C { - td.cleanup() - } -} - -func (td *ThreatDetector) cleanup() { - td.mu.Lock() - defer td.mu.Unlock() - - cutoff := time.Now().Add(-time.Hour) - for ip, activity := range td.ipTracker { - if activity.LastRequest.Before(cutoff) && activity.SuspiciousCount < 999 { - delete(td.ipTracker, ip) - } - } -} diff --git a/apps/carrier-connector/internal/services/onboarding_service.go b/apps/carrier-connector/internal/services/onboarding_service.go deleted file mode 100644 index 178c654..0000000 --- a/apps/carrier-connector/internal/services/onboarding_service.go +++ /dev/null @@ -1,152 +0,0 @@ -package services - -import ( - "context" - "fmt" - "time" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/id" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/mvno" - "github.com/sirupsen/logrus" -) - -// OnboardingService handles MVNO onboarding process -type OnboardingService struct { - logger *logrus.Logger - validator *mvno.OnboardingValidator - provisioner *mvno.ProductionProvisioner - monitor *mvno.OnboardingMonitor -} - -// NewOnboardingService creates a new onboarding service -func NewOnboardingService(logger *logrus.Logger) *OnboardingService { - return &OnboardingService{ - logger: logger, - validator: mvno.NewOnboardingValidator(logger), - monitor: mvno.NewOnboardingMonitor(logger), - // Note: ProductionProvisioner will be initialized with real services in main.go - } -} - -// StartOnboarding initiates the MVNO onboarding process -func (s *OnboardingService) StartOnboarding(ctx context.Context, req *mvno.OnboardingRequest) (*mvno.MVNO, error) { - // Validate the onboarding request - if err := s.validator.ValidateRequest(req); err != nil { - return nil, fmt.Errorf("validation failed: %w", err) - } - - // Create MVNO record - mvnoRecord := &mvno.MVNO{ - ID: id.GeneratePrefixed("mvno"), - BusinessID: req.BusinessID, - Name: req.BusinessName, - Status: mvno.StatusPending, - Plan: req.Plan, - Config: mvno.MVNOConfig{ - MaxSubscribers: s.getMaxSubscribersForPlan(req.Plan), - AllowedCountries: req.TargetCountries, - CustomBranding: req.Plan != mvno.PlanStarter, - APIAccess: req.Plan != mvno.PlanStarter, - AdvancedAnalytics: req.Plan == mvno.PlanScale || req.Plan == mvno.PlanEnterprise, - }, - CreatedAt: time.Now(), - } - - // Start onboarding progress tracking - progress := &mvno.OnboardingProgress{ - MVNOID: mvnoRecord.ID, - Steps: s.getOnboardingSteps(), - Progress: 0.0, - StartedAt: time.Now(), - } - - s.logger.WithFields(logrus.Fields{ - "mvno_id": mvnoRecord.ID, - "business_id": req.BusinessID, - "plan": req.Plan, - }).Info("Starting MVNO onboarding") - - // Execute onboarding steps asynchronously - go s.executeOnboarding(ctx, mvnoRecord, progress) - - return mvnoRecord, nil -} - -// executeOnboarding runs all onboarding steps -func (s *OnboardingService) executeOnboarding(ctx context.Context, mvno *mvno.MVNO, progress *mvno.OnboardingProgress) { - for i, step := range progress.Steps { - select { - case <-ctx.Done(): - s.logger.WithField("mvno_id", mvno.ID).Error("Onboarding cancelled") - return - default: - } - - step.Status = "running" - s.monitor.UpdateProgress(mvno.ID, progress) - - if err := s.executeStep(ctx, mvno, &step); err != nil { - step.Status = "failed" - step.Error = err.Error() - s.logger.WithError(err).WithField("step", step.Name).Error("Step failed") - break - } - - step.Status = "completed" - step.CompletedAt = time.Now() - progress.Progress = float64(i+1) / float64(len(progress.Steps)) * 100 - - s.monitor.UpdateProgress(mvno.ID, progress) - } - - if progress.Progress == 100.0 { - mvno.Status = "active" - progress.CompletedAt = time.Now() - s.logger.WithField("mvno_id", mvno.ID).Info("Onboarding completed") - } -} - -// executeStep executes a single onboarding step -func (s *OnboardingService) executeStep(ctx context.Context, mvno *mvno.MVNO, step *mvno.OnboardingStep) error { - switch step.Name { - case "validation": - return s.validator.ValidateMVNO(ctx, mvno) - case "provisioning": - return s.provisioner.ProvisionResources(ctx, mvno) - case "carrier_setup": - return s.provisioner.SetupCarriers(ctx, mvno) - case "billing_setup": - return s.provisioner.SetupBilling(ctx, mvno) - case "api_access": - return s.provisioner.SetupAPIAccess(ctx, mvno) - default: - return fmt.Errorf("unknown step: %s", step.Name) - } -} - -// getOnboardingSteps returns the standard onboarding workflow -func (s *OnboardingService) getOnboardingSteps() []mvno.OnboardingStep { - return []mvno.OnboardingStep{ - {Name: "validation", Status: "pending"}, - {Name: "provisioning", Status: "pending"}, - {Name: "carrier_setup", Status: "pending"}, - {Name: "billing_setup", Status: "pending"}, - {Name: "api_access", Status: "pending"}, - } -} - -// getMaxSubscribersForPlan returns subscriber limits per plan -func (s *OnboardingService) getMaxSubscribersForPlan(plan mvno.MVNOPlan) int { - switch plan { - case mvno.PlanStarter: - return 1000 - case mvno.PlanGrowth: - return 10000 - case mvno.PlanScale: - return 100000 - case mvno.PlanEnterprise: - return -1 // Unlimited - default: - return 1000 - } -} diff --git a/apps/carrier-connector/internal/services/pricing_integration.go b/apps/carrier-connector/internal/services/pricing_integration.go index 58cfc8e..aa9a9bf 100644 --- a/apps/carrier-connector/internal/services/pricing_integration.go +++ b/apps/carrier-connector/internal/services/pricing_integration.go @@ -172,8 +172,8 @@ func (pi *PricingIntegration) GetPricingEffectiveness(ctx context.Context, tenan ActiveRules: analytics.ActiveRules, TotalRatePlans: ratePlanAnalytics.TotalRatePlans, PlansWithPricing: ratePlanAnalytics.PlansWithPricing, - AverageDiscountRate: ratePlanAnalytics.AverageDiscount, - TotalSavings: ratePlanAnalytics.TotalSavings, + AverageDiscountRate: analytics.DiscountStats.AverageDiscount, + TotalSavings: analytics.DiscountStats.TotalDiscountValue, RulesByType: analytics.RulesByType, ConversionImprovement: ratePlanAnalytics.ConversionRate, GeneratedAt: id.GetCurrentTime(), diff --git a/apps/carrier-connector/internal/services/rateplan_adapter.go b/apps/carrier-connector/internal/services/rateplan_adapter.go deleted file mode 100644 index 99a755c..0000000 --- a/apps/carrier-connector/internal/services/rateplan_adapter.go +++ /dev/null @@ -1,152 +0,0 @@ -package services - -import ( - "context" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" -) - -// RatePlanAdapter adapts services.Service to implement rateplan.Service interface -type RatePlanAdapter struct { - service *Service -} - -// NewRatePlanAdapter creates a new rate plan adapter -func NewRatePlanAdapter(service *Service) rateplan.Service { - return &RatePlanAdapter{service: service} -} - -func (a *RatePlanAdapter) CreateRatePlan(ctx context.Context, plan *rateplan.RatePlan) (*rateplan.RatePlan, error) { - result, err := a.service.CreateRatePlan(ctx, toRepoPlan(plan)) - if err != nil { - return nil, err - } - return toRatePlan(result), nil -} - -func (a *RatePlanAdapter) GetRatePlan(ctx context.Context, id string) (*rateplan.RatePlan, error) { - plan, err := a.service.GetRatePlan(ctx, id) - if err != nil { - return nil, err - } - return toRatePlan(plan), nil -} - -func (a *RatePlanAdapter) UpdateRatePlan(ctx context.Context, plan *rateplan.RatePlan) (*rateplan.RatePlan, error) { - result, err := a.service.UpdateRatePlan(ctx, toRepoPlan(plan)) - if err != nil { - return nil, err - } - return toRatePlan(result), nil -} - -func (a *RatePlanAdapter) DeleteRatePlan(ctx context.Context, id string) error { - return a.service.DeleteRatePlan(ctx, id) -} - -func (a *RatePlanAdapter) ListRatePlans(ctx context.Context, filter *rateplan.RatePlanFilter) ([]*rateplan.RatePlan, error) { - repoFilter := &repository.RatePlanFilter{ - CarrierID: filter.CarrierID, - Region: filter.Region, - PlanType: repository.PlanType(filter.PlanType), - IsActive: filter.IsActive, - MinPrice: filter.MinPrice, - MaxPrice: filter.MaxPrice, - Limit: filter.Limit, - Offset: filter.Offset, - } - plans, err := a.service.ListRatePlans(ctx, repoFilter) - if err != nil { - return nil, err - } - return toRatePlanSlice(plans), nil -} - -func (a *RatePlanAdapter) SearchRatePlans(ctx context.Context, criteria rateplan.SearchCriteria) ([]*rateplan.RatePlan, error) { - filter := &rateplan.RatePlanFilter{ - CarrierID: criteria.CarrierID, - Region: criteria.Region, - PlanType: criteria.PlanType, - } - return a.ListRatePlans(ctx, filter) -} - -func (a *RatePlanAdapter) GetPopularPlans(ctx context.Context, limit int) ([]*rateplan.RatePlan, error) { - plans, err := a.service.GetPopularPlans(ctx, limit) - if err != nil { - return nil, err - } - return toRatePlanSlice(plans), nil -} - -// Conversion helpers - -func toRepoPlan(p *rateplan.RatePlan) *repository.RatePlan { - return &repository.RatePlan{ - ID: p.ID, - Name: p.Name, - Description: p.Description, - CarrierID: p.CarrierID, - Region: p.Region, - PlanType: repository.PlanType(p.PlanType), - BasePrice: p.BasePrice, - BillingCycle: repository.BillingCycle(p.BillingCycle), - ValidFrom: p.ValidFrom, - ValidTo: p.ValidTo, - IsActive: p.IsActive, - CreatedAt: p.CreatedAt, - UpdatedAt: p.UpdatedAt, - } -} - -func toRatePlan(p *repository.RatePlan) *rateplan.RatePlan { - return &rateplan.RatePlan{ - ID: p.ID, - Name: p.Name, - Description: p.Description, - CarrierID: p.CarrierID, - Region: p.Region, - PlanType: rateplan.PlanType(p.PlanType), - BasePrice: p.BasePrice, - BillingCycle: rateplan.BillingCycle(p.BillingCycle), - ValidFrom: p.ValidFrom, - ValidTo: p.ValidTo, - IsActive: p.IsActive, - CreatedAt: p.CreatedAt, - UpdatedAt: p.UpdatedAt, - } -} - -func toRatePlanSlice(plans []*repository.RatePlan) []*rateplan.RatePlan { - result := make([]*rateplan.RatePlan, len(plans)) - for i, p := range plans { - result[i] = toRatePlan(p) - } - return result -} - -func toRatePlanSub(s *repository.RatePlanSubscription) *rateplan.RatePlanSubscription { - return &rateplan.RatePlanSubscription{ - ID: s.ID, - ProfileID: s.ProfileID, - RatePlanID: s.RatePlanID, - Status: rateplan.SubscriptionStatus(s.Status), - CreatedAt: s.CreatedAt, - UpdatedAt: s.UpdatedAt, - } -} - -func convertRepoUsage(u *repository.RatePlanUsage) *rateplan.RatePlanUsage { - return &rateplan.RatePlanUsage{ - ID: u.ID, - RatePlanID: u.RatePlanID, - ProfileID: u.ProfileID, - CycleStart: u.CycleStart, - CycleEnd: u.CycleEnd, - DataUsed: u.DataUsed, - VoiceUsed: u.VoiceUsed, - SMSUsed: u.SMSUsed, - LastUpdated: u.LastUpdated, - } -} diff --git a/apps/carrier-connector/internal/services/rateplan_adapter_subs.go b/apps/carrier-connector/internal/services/rateplan_adapter_subs.go deleted file mode 100644 index cf7cd5f..0000000 --- a/apps/carrier-connector/internal/services/rateplan_adapter_subs.go +++ /dev/null @@ -1,177 +0,0 @@ -package services - -import ( - "context" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" -) - -func (a *RatePlanAdapter) SubscribeToPlan(ctx context.Context, req *rateplan.SubscribeRequest) (*rateplan.RatePlanSubscription, error) { - repoReq := &repository.SubscribeRequest{ - ProfileID: req.ProfileID, - RatePlanID: req.RatePlanID, - } - subscription, err := a.service.SubscribeToPlan(ctx, repoReq) - if err != nil { - return nil, err - } - return toRatePlanSub(subscription), nil -} - -func (a *RatePlanAdapter) GetSubscription(ctx context.Context, id string) (*rateplan.RatePlanSubscription, error) { - subscription, err := a.service.GetSubscription(ctx, id) - if err != nil { - return nil, err - } - return toRatePlanSub(subscription), nil -} - -func (a *RatePlanAdapter) UpdateSubscription(ctx context.Context, subscription *rateplan.RatePlanSubscription) (*rateplan.RatePlanSubscription, error) { - repoSub := &repository.RatePlanSubscription{ - ID: subscription.ID, - ProfileID: subscription.ProfileID, - RatePlanID: subscription.RatePlanID, - Status: repository.SubscriptionStatus(subscription.Status), - CreatedAt: subscription.CreatedAt, - UpdatedAt: subscription.UpdatedAt, - } - result, err := a.service.UpdateSubscription(ctx, repoSub) - if err != nil { - return nil, err - } - return toRatePlanSub(result), nil -} - -func (a *RatePlanAdapter) CancelSubscription(ctx context.Context, subscriptionID string, reason string) error { - return a.service.CancelSubscription(ctx, subscriptionID, reason) -} - -func (a *RatePlanAdapter) GetActiveSubscription(ctx context.Context, profileID string) (*rateplan.RatePlanSubscription, error) { - subscription, err := a.service.GetActiveSubscription(ctx, profileID) - if err != nil { - return nil, err - } - return toRatePlanSub(subscription), nil -} - -func (a *RatePlanAdapter) ListSubscriptions(ctx context.Context, profileID string, filter *rateplan.SubscriptionFilter) ([]*rateplan.RatePlanSubscription, error) { - repoFilter := &repository.SubscriptionFilter{ - Status: repository.SubscriptionStatus(filter.Status), - Limit: filter.Limit, - Offset: filter.Offset, - } - subscriptions, err := a.service.ListSubscriptions(ctx, profileID, repoFilter) - if err != nil { - return nil, err - } - result := make([]*rateplan.RatePlanSubscription, len(subscriptions)) - for i, sub := range subscriptions { - result[i] = toRatePlanSub(sub) - } - return result, nil -} - -func (a *RatePlanAdapter) RecordUsage(ctx context.Context, req *rateplan.RecordUsageRequest) (*rateplan.RatePlanUsage, error) { - repoReq := &repository.RecordUsageRequest{ - ProfileID: req.ProfileID, - DataUsed: req.DataUsed, - VoiceUsed: req.VoiceUsed, - SMSUsed: req.SMSUsed, - } - usage, err := a.service.RecordUsage(ctx, repoReq) - if err != nil { - return nil, err - } - return convertRepoUsage(usage), nil -} - -func (a *RatePlanAdapter) GetUsage(ctx context.Context, profileID string) (*rateplan.RatePlanUsage, error) { - usage, err := a.service.GetUsage(ctx, profileID) - if err != nil { - return nil, err - } - return convertRepoUsage(usage), nil -} - -func (a *RatePlanAdapter) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*rateplan.RatePlanUsage, error) { - usageHistory, err := a.service.GetUsageHistory(ctx, profileID, limit) - if err != nil { - return nil, err - } - result := make([]*rateplan.RatePlanUsage, len(usageHistory)) - for i, u := range usageHistory { - result[i] = convertRepoUsage(u) - } - return result, nil -} - -func (a *RatePlanAdapter) CalculateCost(ctx context.Context, req *rateplan.CalculateCostRequest) (*rateplan.RatePlanCostCalculation, error) { - repoReq := &repository.CalculateCostRequest{ - RatePlanID: req.RatePlanID, - DataUsed: req.DataUsed, - VoiceUsed: req.VoiceUsed, - SMSUsed: req.SMSUsed, - AppliedDiscounts: req.AppliedDiscounts, - } - calc, err := a.service.CalculateCost(ctx, repoReq) - if err != nil { - return nil, err - } - return &rateplan.RatePlanCostCalculation{ - RatePlanID: calc.RatePlanID, - BaseCost: calc.BaseCost, - OverageCost: calc.OverageCost, - DiscountCost: calc.DiscountCost, - TotalCost: calc.TotalCost, - Currency: calc.Currency, - Breakdown: calc.Breakdown, - CalculatedAt: calc.CalculatedAt, - }, nil -} - -func (a *RatePlanAdapter) GetUsageAnalytics(ctx context.Context, filter *rateplan.UsageAnalyticsFilter) (*rateplan.UsageAnalytics, error) { - repoFilter := &repository.UsageAnalyticsFilter{ - RatePlanID: filter.RatePlanID, - CarrierID: filter.CarrierID, - Region: filter.Region, - StartDate: filter.StartDate, - EndDate: filter.EndDate, - GroupBy: filter.GroupBy, - } - analytics, err := a.service.GetUsageAnalytics(ctx, repoFilter) - if err != nil { - return nil, err - } - return &rateplan.UsageAnalytics{ - TotalDataUsed: analytics.TotalDataUsed, - TotalVoiceUsed: analytics.TotalVoiceUsed, - TotalSMSUsed: analytics.TotalSMSUsed, - ActiveUsers: analytics.ActiveUsers, - AverageUsage: analytics.AverageUsage, - UsageByPlan: analytics.UsageByPlan, - UsageByRegion: analytics.UsageByRegion, - }, nil -} - -func (a *RatePlanAdapter) GetRevenueAnalytics(ctx context.Context, filter *rateplan.RevenueAnalyticsFilter) (*rateplan.RevenueAnalytics, error) { - repoFilter := &repository.RevenueAnalyticsFilter{ - RatePlanID: filter.RatePlanID, - CarrierID: filter.CarrierID, - Region: filter.Region, - StartDate: filter.StartDate, - EndDate: filter.EndDate, - GroupBy: filter.GroupBy, - } - analytics, err := a.service.GetRevenueAnalytics(ctx, repoFilter) - if err != nil { - return nil, err - } - return &rateplan.RevenueAnalytics{ - TotalRevenue: analytics.TotalRevenue, - RevenueByPlan: analytics.RevenueByPlan, - RevenueByCarrier: analytics.RevenueByCarrier, - RevenueByRegion: analytics.RevenueByRegion, - AverageRevenue: analytics.AverageRevenue, - }, nil -} diff --git a/apps/carrier-connector/internal/services/rateplan_core.go b/apps/carrier-connector/internal/services/rateplan_core.go index 4c4f8ae..3e86e72 100644 --- a/apps/carrier-connector/internal/services/rateplan_core.go +++ b/apps/carrier-connector/internal/services/rateplan_core.go @@ -13,16 +13,15 @@ import ( type RatePlanCurrencyIntegrator struct { billingService currency.BillingService - exchangeService currency.ExchangeRateService + exchangeService *currency.ExchangeRateService ratePlanService rateplan.Service logger *logrus.Logger baseCurrency string } -// NewRatePlanCurrencyIntegrator creates a new rate plan currency integrator func NewRatePlanCurrencyIntegrator( billingService currency.BillingService, - exchangeService currency.ExchangeRateService, + exchangeService *currency.ExchangeRateService, ratePlanService rateplan.Service, logger *logrus.Logger, baseCurrency string, @@ -36,24 +35,22 @@ func NewRatePlanCurrencyIntegrator( } } -// SubscribeToPlanWithCurrency subscribes to a rate plan with currency conversion func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, targetCurrency string) (*rateplan.RatePlanSubscription, error) { - // Get the rate plan plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID) if err != nil { return nil, fmt.Errorf("failed to get rate plan: %w", err) } - // Convert price to requested currency if needed subscriptionPrice := plan.BasePrice exchangeRate := 1.0 if targetCurrency != plan.Currency { - conversion, err := rpci.billingService.ConvertAmount(ctx, ¤cy.CurrencyConversionRequest{ + conversionReq := ¤cy.CurrencyConversionRequest{ Amount: plan.BasePrice, FromCurrency: plan.Currency, ToCurrency: targetCurrency, - }) + } + conversion, err := rpci.billingService.ConvertAmount(ctx, conversionReq) if err != nil { rpci.logger.WithError(err).Error("Failed to convert rate plan price") return nil, fmt.Errorf("currency conversion failed: %w", err) @@ -62,18 +59,19 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context. exchangeRate = conversion.ExchangeRate } - // Create subscription request with currency information + metadata := map[string]any{ + "original_currency": plan.Currency, + "subscription_currency": targetCurrency, + "original_price": plan.BasePrice, + "subscription_price": subscriptionPrice, + "exchange_rate": exchangeRate, + } + subscribeReq := &rateplan.SubscribeRequest{ ProfileID: profileID, RatePlanID: planID, AutoRenew: true, - Metadata: map[string]any{ - "original_currency": plan.Currency, - "subscription_currency": targetCurrency, - "original_price": plan.BasePrice, - "subscription_price": subscriptionPrice, - "exchange_rate": exchangeRate, - }, + Metadata: metadata, } createdSubscription, err := rpci.ratePlanService.SubscribeToPlan(ctx, subscribeReq) @@ -81,7 +79,6 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context. return nil, fmt.Errorf("failed to create subscription: %w", err) } - // Process initial billing billingReq := ¤cy.BillingRequest{ ProfileID: profileID, SubscriptionID: createdSubscription.ID, @@ -107,18 +104,14 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context. return createdSubscription, nil } -// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, targetCurrency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) { - // Get the rate plan plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID) if err != nil { return nil, fmt.Errorf("failed to get rate plan: %w", err) } - // Calculate base cost baseCost := plan.BasePrice - // Add overage costs if usage data is provided if usageData != nil { overageCost, err := rpci.calculateOverageCost(ctx, plan, usageData) if err != nil { @@ -128,12 +121,16 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context. } } - // Convert to requested currency convertedCost := baseCost exchangeRate := 1.0 if targetCurrency != plan.Currency { - conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, targetCurrency) + conversionReq := ¤cy.CurrencyConversionRequest{ + Amount: baseCost, + FromCurrency: plan.Currency, + ToCurrency: targetCurrency, + } + conversion, err := rpci.billingService.ConvertAmount(ctx, conversionReq) if err != nil { return nil, fmt.Errorf("currency conversion failed: %w", err) } @@ -141,7 +138,6 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context. exchangeRate = conversion.ExchangeRate } - // Create billing summary summary := ¤cy.BillingSummary{ ProfileID: usageData.ProfileID, TotalAmount: convertedCost, @@ -164,11 +160,13 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context. return summary, nil } -// calculateOverageCost calculates overage costs for usage -func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(_ context.Context, plan *rateplan.RatePlan, usage *rateplan.RatePlanUsage) (float64, error) { +func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context, plan *rateplan.RatePlan, usage *rateplan.RatePlanUsage) (float64, error) { + // Check for context cancellation before calculation + if err := ctx.Err(); err != nil { + return 0.0, fmt.Errorf("overage calculation cancelled: %w", err) + } overageCost := 0.0 - // Calculate data overage if plan.DataAllowance != nil && usage.DataUsed > plan.DataAllowance.Amount { dataOverage := usage.DataUsed - plan.DataAllowance.Amount if plan.OverageRates != nil { @@ -176,7 +174,6 @@ func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(_ context.Context, } } - // Calculate voice overage if plan.VoiceAllowance != nil && usage.VoiceUsed > plan.VoiceAllowance.Minutes { voiceOverage := usage.VoiceUsed - plan.VoiceAllowance.Minutes if plan.OverageRates != nil { @@ -184,7 +181,6 @@ func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(_ context.Context, } } - // Calculate SMS overage if plan.SMSAllowance != nil && usage.SMSUsed > plan.SMSAllowance.Messages { smsOverage := usage.SMSUsed - plan.SMSAllowance.Messages if plan.OverageRates != nil { diff --git a/apps/carrier-connector/internal/services/selection_service.go b/apps/carrier-connector/internal/services/selection_service.go index c666d90..451ca3b 100644 --- a/apps/carrier-connector/internal/services/selection_service.go +++ b/apps/carrier-connector/internal/services/selection_service.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp" "github.com/sirupsen/logrus" ) @@ -12,17 +13,27 @@ import ( // SelectionService provides high-level carrier selection operations type SelectionService struct { manager *smdp.SMDPManager + handler *handlers.SelectionHandler logger *logrus.Logger } // NewSelectionService creates a new selection service -func NewSelectionService(manager *smdp.SMDPManager, logger *logrus.Logger) *SelectionService { +func NewSelectionService(manager *smdp.SMDPManager) *SelectionService { + logger := logrus.New() + logger.SetLevel(logrus.InfoLevel) + return &SelectionService{ manager: manager, + handler: handlers.NewSelectionHandler(manager), logger: logger, } } +// GetHandler returns the selection handler for API registration +func (s *SelectionService) GetHandler() *handlers.SelectionHandler { + return s.handler +} + // IntelligentCarrierSelection performs intelligent carrier selection with comprehensive criteria func (s *SelectionService) IntelligentCarrierSelection(ctx context.Context, request *IntelligentSelectionRequest) (*IntelligentSelectionResponse, error) { s.logger.WithFields(logrus.Fields{ diff --git a/apps/carrier-connector/internal/smdp/operations.go b/apps/carrier-connector/internal/smdp/operations.go index bfe3519..7dbf9cd 100644 --- a/apps/carrier-connector/internal/smdp/operations.go +++ b/apps/carrier-connector/internal/smdp/operations.go @@ -154,3 +154,18 @@ func (m *SMDPManager) updateCarrierMetrics(carrierID string, success bool, respo carrier.Metrics.RequestRate = float64(carrier.Metrics.TotalRequests) / time.Since(time.Now().Add(-time.Minute)).Seconds() } + +func (m *SMDPManager) getHighestPriorityCarrier(carriers []*Carrier) *Carrier { + if len(carriers) == 0 { + return nil + } + + highestPriority := carriers[0] + for _, carrier := range carriers { + if carrier.Priority > highestPriority.Priority { + highestPriority = carrier + } + } + + return highestPriority +} diff --git a/apps/carrier-connector/internal/smdp/selection_scoring.go b/apps/carrier-connector/internal/smdp/selection_scoring.go index b6ed18b..25064ec 100644 --- a/apps/carrier-connector/internal/smdp/selection_scoring.go +++ b/apps/carrier-connector/internal/smdp/selection_scoring.go @@ -184,7 +184,7 @@ func (sa *SelectionAlgorithm) calculateCapabilityScore(carrier *Carrier, profile } // generateReason creates a human-readable selection reason -func (sa *SelectionAlgorithm) generateReason(score *CarrierScore, _ *SelectionCriteria) string { +func (sa *SelectionAlgorithm) generateReason(score *CarrierScore, criteria *SelectionCriteria) string { reasons := []string{} if score.PerformanceScore > 80 { diff --git a/apps/carrier-connector/internal/smdp/selection_test_ml.go b/apps/carrier-connector/internal/smdp/selection_test_ml.go index 982c460..ed3575f 100644 --- a/apps/carrier-connector/internal/smdp/selection_test_ml.go +++ b/apps/carrier-connector/internal/smdp/selection_test_ml.go @@ -38,9 +38,11 @@ func TestMachineLearningModel(t *testing.T) { // Check that weights have been optimized optimizedWeights := mlModel.GetOptimizedWeights() + originalWeights := WeightVector{Performance: 0.3, Reliability: 0.3, Cost: 0.2, Region: 0.1, Capability: 0.1} - // Weights should have changed from original defaults (0.3, 0.3, 0.2, 0.1, 0.1) - if optimizedWeights.Performance == 0.3 && optimizedWeights.Reliability == 0.3 { + // Weights should have changed from original + if optimizedWeights.Performance == originalWeights.Performance && + optimizedWeights.Reliability == originalWeights.Reliability { t.Error("Expected weights to change after learning") } diff --git a/apps/carrier-connector/internal/whitelabel/service.go b/apps/carrier-connector/internal/whitelabel/service.go deleted file mode 100644 index 83206d1..0000000 --- a/apps/carrier-connector/internal/whitelabel/service.go +++ /dev/null @@ -1,99 +0,0 @@ -package whitelabel - -import ( - "context" - "fmt" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// Service provides whitelabel management operations -type Service struct { - db *gorm.DB - logger *logrus.Logger -} - -// NewService creates a new whitelabel service -func NewService(db *gorm.DB, logger *logrus.Logger) *Service { - return &Service{db: db, logger: logger} -} - -// CreateBranding creates a new branding configuration -func (s *Service) CreateBranding(ctx context.Context, config *BrandingConfig) error { - if err := s.db.WithContext(ctx).Create(config).Error; err != nil { - s.logger.WithError(err).Error("Failed to create branding config") - return fmt.Errorf("failed to create branding: %w", err) - } - s.logger.WithField("tenant_id", config.TenantID).Info("Branding config created") - return nil -} - -// GetBranding retrieves branding by tenant ID -func (s *Service) GetBranding(ctx context.Context, tenantID string) (*BrandingConfig, error) { - var config BrandingConfig - if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).First(&config).Error; err != nil { - return nil, fmt.Errorf("branding not found: %w", err) - } - return &config, nil -} - -// GetBrandingByDomain retrieves branding by custom domain -func (s *Service) GetBrandingByDomain(ctx context.Context, domain string) (*BrandingConfig, error) { - var config BrandingConfig - if err := s.db.WithContext(ctx).Where("custom_domain = ? AND is_active = ?", domain, true).First(&config).Error; err != nil { - return nil, fmt.Errorf("branding not found for domain: %w", err) - } - return &config, nil -} - -// UpdateBranding updates branding configuration -func (s *Service) UpdateBranding(ctx context.Context, config *BrandingConfig) error { - if err := s.db.WithContext(ctx).Save(config).Error; err != nil { - return fmt.Errorf("failed to update branding: %w", err) - } - return nil -} - -// CreatePartnerConfig creates partner configuration -func (s *Service) CreatePartnerConfig(ctx context.Context, config *PartnerConfig) error { - if err := s.db.WithContext(ctx).Create(config).Error; err != nil { - return fmt.Errorf("failed to create partner config: %w", err) - } - return nil -} - -// GetPartnerConfig retrieves partner configuration -func (s *Service) GetPartnerConfig(ctx context.Context, tenantID string) (*PartnerConfig, error) { - var config PartnerConfig - if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).First(&config).Error; err != nil { - return nil, fmt.Errorf("partner config not found: %w", err) - } - return &config, nil -} - -// CreateEmailTemplate creates a custom email template -func (s *Service) CreateEmailTemplate(ctx context.Context, template *EmailTemplate) error { - if err := s.db.WithContext(ctx).Create(template).Error; err != nil { - return fmt.Errorf("failed to create email template: %w", err) - } - return nil -} - -// GetEmailTemplate retrieves an email template -func (s *Service) GetEmailTemplate(ctx context.Context, tenantID, templateKey string) (*EmailTemplate, error) { - var template EmailTemplate - if err := s.db.WithContext(ctx).Where("tenant_id = ? AND template_key = ? AND is_active = ?", tenantID, templateKey, true).First(&template).Error; err != nil { - return nil, fmt.Errorf("email template not found: %w", err) - } - return &template, nil -} - -// ListEmailTemplates lists all templates for a tenant -func (s *Service) ListEmailTemplates(ctx context.Context, tenantID string) ([]*EmailTemplate, error) { - var templates []*EmailTemplate - if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).Find(&templates).Error; err != nil { - return nil, fmt.Errorf("failed to list templates: %w", err) - } - return templates, nil -} diff --git a/apps/carrier-connector/internal/whitelabel/types.go b/apps/carrier-connector/internal/whitelabel/types.go deleted file mode 100644 index 0cd67e5..0000000 --- a/apps/carrier-connector/internal/whitelabel/types.go +++ /dev/null @@ -1,85 +0,0 @@ -package whitelabel - -import "time" - -// BrandingConfig defines partner branding configuration -type BrandingConfig struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - CompanyName string `json:"company_name"` - LogoURL string `json:"logo_url"` - FaviconURL string `json:"favicon_url"` - PrimaryColor string `json:"primary_color"` - SecondaryColor string `json:"secondary_color"` - AccentColor string `json:"accent_color"` - FontFamily string `json:"font_family"` - CustomCSS string `json:"custom_css"` - CustomDomain string `json:"custom_domain" gorm:"uniqueIndex"` - EmailFromName string `json:"email_from_name"` - EmailFromAddr string `json:"email_from_address"` - SupportEmail string `json:"support_email"` - SupportPhone string `json:"support_phone"` - TermsURL string `json:"terms_url"` - PrivacyURL string `json:"privacy_url"` - FooterText string `json:"footer_text"` - SocialLinks map[string]string `json:"social_links" gorm:"serializer:json"` - Features FeatureFlags `json:"features" gorm:"embedded;embeddedPrefix:feature_"` - IsActive bool `json:"is_active"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -// FeatureFlags controls which features are enabled for a whitelabel partner -type FeatureFlags struct { - ShowPoweredBy bool `json:"show_powered_by"` - CustomEmailDomain bool `json:"custom_email_domain"` - CustomAPIDomain bool `json:"custom_api_domain"` - AdvancedAnalytics bool `json:"advanced_analytics"` - WhitelabelMobile bool `json:"whitelabel_mobile"` - CustomWebhooks bool `json:"custom_webhooks"` - APIDocsBranding bool `json:"api_docs_branding"` - MultiLanguage bool `json:"multi_language"` -} - -// EmailTemplate defines customizable email templates -type EmailTemplate struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"index"` - TemplateKey string `json:"template_key" gorm:"index"` - Subject string `json:"subject"` - HTMLBody string `json:"html_body"` - TextBody string `json:"text_body"` - Variables []string `json:"variables" gorm:"serializer:json"` - IsActive bool `json:"is_active"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -// PartnerTier defines partnership levels with different capabilities -type PartnerTier string - -const ( - PartnerTierBasic PartnerTier = "basic" - PartnerTierProfession PartnerTier = "professional" - PartnerTierEnterprise PartnerTier = "enterprise" - PartnerTierPlatinum PartnerTier = "platinum" -) - -// PartnerConfig defines partner-specific configuration -type PartnerConfig struct { - ID string `json:"id" gorm:"primaryKey"` - TenantID string `json:"tenant_id" gorm:"uniqueIndex"` - Tier PartnerTier `json:"tier"` - RevenueSharePct float64 `json:"revenue_share_pct"` - MinMonthlyCommit float64 `json:"min_monthly_commit"` - MaxAPIRequests int64 `json:"max_api_requests"` - MaxProfiles int64 `json:"max_profiles"` - AllowedCountries []string `json:"allowed_countries" gorm:"serializer:json"` - AllowedCarriers []string `json:"allowed_carriers" gorm:"serializer:json"` - CustomPricing map[string]any `json:"custom_pricing" gorm:"serializer:json"` - ContractStartDate time.Time `json:"contract_start_date"` - ContractEndDate time.Time `json:"contract_end_date"` - IsActive bool `json:"is_active"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} diff --git a/apps/carrier-connector/main.go b/apps/carrier-connector/main.go index 078a3cf..8d0c5f5 100644 --- a/apps/carrier-connector/main.go +++ b/apps/carrier-connector/main.go @@ -14,10 +14,7 @@ import ( "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/webhook" "github.com/rs/zerolog" - "github.com/sirupsen/logrus" csrf "github.com/utrack/gin-csrf" - "gorm.io/driver/postgres" - "gorm.io/gorm" ) func main() { @@ -98,20 +95,7 @@ func main() { } } - // Create logger for MVNO handlers - logger := logrus.New() - logger.SetLevel(logrus.InfoLevel) - - // Create separate database connection for MVNO repository - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) - if err != nil { - handler.Logger.Fatal().Err(err).Msg("Failed to create database connection for MVNO repository") - } - - // Create repository for MVNO operations - repo := repository.NewGormRepository(db, logger) - - setupRoutes(router, client, profileRepo, webhookClient, messageQueue, repo, db, logger) + setupRoutes(router, client, profileRepo, webhookClient, messageQueue) handler.Logger.Info().Str("port", port).Msg("Carrier Connector API server starting") if err := router.Run(":" + port); err != nil { diff --git a/apps/carrier-connector/routes.go b/apps/carrier-connector/routes.go index fbf3b5a..a2dafcd 100644 --- a/apps/carrier-connector/routes.go +++ b/apps/carrier-connector/routes.go @@ -6,70 +6,35 @@ import ( "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/sirupsen/logrus" - "gorm.io/gorm" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/es2" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers" + handler "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/mq" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/services" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/webhook" ) -// setupRoutes registers all HTTP routes. -func setupRoutes(router *gin.Engine, client *es2.ES2Client, profileRepo repository.ProfileRepository, webhookClient *webhook.WebhookClient, messageQueue *mq.MessageQueue, repo repository.Repository, db *gorm.DB, logger *logrus.Logger) { +// setupRoutes registers all HTTP routes, wiring the ES2+ client, profile repo, webhook client, and message queue. +func setupRoutes(router *gin.Engine, client *es2.ES2Client, repo repository.ProfileRepository, webhookClient *webhook.WebhookClient, messageQueue *mq.MessageQueue) { api := router.Group("/api/v1") - - // Health and metrics api.GET("/health", healthHandler) - api.GET("/health/ready", readinessHandler(profileRepo)) + api.GET("/health/ready", readinessHandler(repo)) api.GET("/health/live", livenessHandler) api.GET("/metrics", gin.WrapH(promhttp.Handler())) - // eSIM profile management esim := api.Group("/esim") - esim.POST("/profiles", handlers.OrderProfileHandlerWithRepo(client, profileRepo, webhookClient, messageQueue)) - esim.GET("/profiles", handlers.ListProfilesHandler(profileRepo)) - esim.GET("/profiles/:profileId", handlers.GetProfileHandler(profileRepo)) - esim.DELETE("/profiles/:profileId", handlers.DeleteProfileHandler(profileRepo)) + { + esim.POST("/profiles", handler.OrderProfileHandlerWithRepo(client, repo, webhookClient, messageQueue)) + esim.GET("/profiles", handler.ListProfilesHandler(repo)) + esim.GET("/profiles/:profileId", handler.GetProfileHandler(repo)) + esim.DELETE("/profiles/:profileId", handler.DeleteProfileHandler(repo)) + } - // Carrier info carrier := api.Group("/carrier") - carrier.GET("/info", handlers.GetCarrierInfoHandler(client)) - carrier.GET("/connectivity", handlers.CheckConnectivityHandler(client)) - - // MVNO routes - registerMVNORoutes(api, repo, logger) - - // Domain routes - registerRatePlanRoutes(api, repo, logger) - registerPricingRoutes(api, logger) - registerCurrencyRoutes(api) - registerTenantRoutes(api, db, logger) - registerSMDPRoutes(api, profileRepo) - - // Platform routes (009-028) - registerAnalyticsRoutes(api, db, logger) - registerWhitelabelRoutes(api, db, logger) - registerComplianceRoutes(api, db, logger) - registerExchangeRateRoutes(api, logger) - registerInfraRoutes(api, nil, nil) -} - -// registerMVNORoutes registers MVNO onboarding and management routes. -func registerMVNORoutes(api *gin.RouterGroup, repo repository.Repository, logger *logrus.Logger) { - mvno := api.Group("/mvno") - - onboardingService := services.NewOnboardingService(logger) - mvnoHandler := handlers.NewMVNOHandler(onboardingService, repo, logger) - managementHandler := handlers.NewManagementHandler(repo, logger) - - mvno.POST("/onboarding", mvnoHandler.StartOnboarding) - mvno.GET("", mvnoHandler.ListMVNOs) - mvno.GET("/:id", mvnoHandler.GetMVNO) - mvno.PUT("/:id/status", managementHandler.UpdateMVNOStatus) - mvno.GET("/stats", managementHandler.GetMVNOStats) + { + carrier.GET("/info", handler.GetCarrierInfoHandler(client)) + carrier.GET("/connectivity", handler.CheckConnectivityHandler(client)) + } } // healthHandler returns a simple liveness response. @@ -81,7 +46,7 @@ func healthHandler(c *gin.Context) { }) } -// livenessHandler returns a simple liveness check. +// livenessHandler returns a simple liveness check (always healthy if service is running). func livenessHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "alive", @@ -90,9 +55,10 @@ func livenessHandler(c *gin.Context) { }) } -// readinessHandler checks if the service is ready to accept requests. +// readinessHandler checks if the service is ready to accept requests (database connectivity). func readinessHandler(repo repository.ProfileRepository) gin.HandlerFunc { return func(c *gin.Context) { + // Check database connectivity if err := repo.Ping(); err != nil { c.JSON(http.StatusServiceUnavailable, gin.H{ "status": "not ready", @@ -103,11 +69,14 @@ func readinessHandler(repo repository.ProfileRepository) gin.HandlerFunc { }) return } + c.JSON(http.StatusOK, gin.H{ "status": "ready", "service": "carrier-connector", "timestamp": time.Now().UTC(), - "checks": gin.H{"database": "ok"}, + "checks": gin.H{ + "database": "ok", + }, }) } } diff --git a/apps/carrier-connector/routes_domain.go b/apps/carrier-connector/routes_domain.go deleted file mode 100644 index 3a2b88e..0000000 --- a/apps/carrier-connector/routes_domain.go +++ /dev/null @@ -1,153 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" - "gorm.io/gorm" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/services" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp" -) - -// registerRatePlanRoutes registers rate plan CRUD, subscription, and management routes. -func registerRatePlanRoutes(api *gin.RouterGroup, repo repository.Repository, logger *logrus.Logger) { - rateplanGroup := api.Group("/rateplans") - - ratePlanService := services.NewService(repo, logger) - ratePlanAdapter := services.NewRatePlanAdapter(ratePlanService) - ratePlanHandler := handlers.NewRatePlanHandler(ratePlanAdapter) - - rateplanGroup.POST("", ratePlanHandler.CreateRatePlan) - rateplanGroup.GET("", ratePlanHandler.ListRatePlans) - rateplanGroup.GET("/:id", ratePlanHandler.GetRatePlan) - rateplanGroup.PUT("/:id", ratePlanHandler.UpdateRatePlan) - rateplanGroup.DELETE("/:id", ratePlanHandler.DeleteRatePlan) - rateplanGroup.GET("/search", ratePlanHandler.SearchRatePlans) - - rateplanGroup.POST("/subscribe", ratePlanHandler.SubscribeToPlan) - rateplanGroup.GET("/subscriptions", ratePlanHandler.ListSubscriptions) - rateplanGroup.GET("/subscriptions/:id", ratePlanHandler.GetSubscription) - rateplanGroup.GET("/subscriptions/active", ratePlanHandler.GetActiveSubscription) - rateplanGroup.DELETE("/subscriptions/:id", ratePlanHandler.CancelSubscription) - - rateplanGroup.GET("/dashboard", ratePlanHandler.GetManagementDashboard) - rateplanGroup.GET("/overview", ratePlanHandler.GetSystemOverview) - rateplanGroup.POST("/bulk", ratePlanHandler.BulkCreateRatePlans) - rateplanGroup.PUT("/:id/activate", ratePlanHandler.ActivateRatePlan) - rateplanGroup.PUT("/:id/deactivate", ratePlanHandler.DeactivateRatePlan) - rateplanGroup.POST("/:id/duplicate", ratePlanHandler.DuplicateRatePlan) -} - -// registerPricingRoutes registers pricing rule management routes. -func registerPricingRoutes(api *gin.RouterGroup, logger *logrus.Logger) { - pricingGroup := api.Group("/pricing") - - pricingService := services.NewPricingService(nil, nil, nil, logger) - pricingHandler := handlers.NewPricingHandler(pricingService) - - pricingGroup.POST("/rules", pricingHandler.CreateRule) - pricingGroup.GET("/rules", pricingHandler.ListRules) - pricingGroup.GET("/rules/:id", pricingHandler.GetRule) - pricingGroup.PUT("/rules/:id", pricingHandler.UpdateRule) - pricingGroup.DELETE("/rules/:id", pricingHandler.DeleteRule) -} - -// registerCurrencyRoutes registers currency conversion and billing routes. -func registerCurrencyRoutes(api *gin.RouterGroup) { - currencyGroup := api.Group("/currency") - - currencyGroup.POST("/convert", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Currency conversion endpoint ready", "status": "placeholder"}) - }) - currencyGroup.GET("/exchange/:from/:to", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Exchange rate endpoint ready", "status": "placeholder"}) - }) - currencyGroup.GET("/exchange/:from/:to/history", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Exchange rate history endpoint ready", "status": "placeholder"}) - }) - currencyGroup.GET("/currencies", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Supported currencies endpoint ready", "status": "placeholder"}) - }) - currencyGroup.POST("/exchange/refresh", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Exchange rate refresh endpoint ready", "status": "placeholder"}) - }) - - currencyGroup.POST("/billing", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Billing processing endpoint ready", "status": "placeholder"}) - }) - currencyGroup.GET("/billing/history/:profile_id", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Billing history endpoint ready", "status": "placeholder"}) - }) - currencyGroup.GET("/billing/summary/:profile_id", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Billing summary endpoint ready", "status": "placeholder"}) - }) - currencyGroup.POST("/billing/refund/:transaction_id", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Billing refund endpoint ready", "status": "placeholder"}) - }) - currencyGroup.GET("/billing/analytics", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Billing analytics endpoint ready", "status": "placeholder"}) - }) -} - -// registerTenantRoutes registers tenant management, user, API key, and metrics routes. -func registerTenantRoutes(api *gin.RouterGroup, db *gorm.DB, logger *logrus.Logger) { - tenant := api.Group("/tenants") - - tenantRepo := repository.NewGormTenantRepository(db) - tenantService := services.NewTenantService(tenantRepo, nil, logger) - tenantHandler := handlers.NewTenantHandler(tenantService, logger) - - tenant.POST("", tenantHandler.CreateTenant) - tenant.GET("", tenantHandler.ListTenants) - tenant.GET("/:id", tenantHandler.GetTenant) - tenant.GET("/domain/:domain", tenantHandler.GetTenantByDomain) - tenant.PUT("/:id", tenantHandler.UpdateTenant) - tenant.DELETE("/:id", tenantHandler.DeleteTenant) - - tenant.POST("/:id/users", tenantHandler.AddUserToTenant) - tenant.GET("/:id/users", tenantHandler.ListTenantUsers) - tenant.GET("/:id/users/:user_id", tenantHandler.GetTenantUser) - tenant.PUT("/:id/users/:user_id", tenantHandler.UpdateTenantUser) - tenant.DELETE("/:id/users/:user_id", tenantHandler.RemoveUserFromTenant) - - tenant.POST("/:id/apikeys", tenantHandler.CreateAPIKey) - tenant.GET("/:id/apikeys", tenantHandler.ListAPIKeys) - tenant.GET("/:id/apikeys/:key_id", tenantHandler.GetAPIKey) - tenant.PUT("/:id/apikeys/:key_id", tenantHandler.UpdateAPIKey) - tenant.DELETE("/:id/apikeys/:key_id", tenantHandler.DeleteAPIKey) - - tenant.GET("/:id/usage", tenantHandler.GetUsageStats) - tenant.GET("/:id/quota", tenantHandler.GetQuotaStatus) - tenant.GET("/:id/config", tenantHandler.GetTenantConfig) - tenant.PUT("/:id/config", tenantHandler.UpdateTenantConfig) - tenant.GET("/:id/metrics", tenantHandler.GetTenantMetrics) - tenant.GET("/:id/events", tenantHandler.GetTenantEvents) -} - -// registerSMDPRoutes registers SMDP management and carrier selection routes. -func registerSMDPRoutes(api *gin.RouterGroup, profileRepo repository.ProfileRepository) { - smdpConfig := smdp.DefaultManagerConfig() - smdpManager := smdp.NewSMDPManager(profileRepo.(*repository.PostgresProfileStore), smdpConfig) - - smdpGroup := api.Group("/smdp") - smdpHandler := handlers.NewSMDPHandler(smdpManager) - smdpGroup.DELETE("/carriers/:carrier_id", smdpHandler.RemoveCarrier) - smdpGroup.GET("/carriers/:carrier_id/history", smdpHandler.GetSelectionHistory) - smdpGroup.PUT("/carriers/:carrier_id/learning", smdpHandler.UpdateLearning) - - selection := api.Group("/selection") - selectionHandler := handlers.NewSelectionHandler(smdpManager) - selection.POST("/optimal", func(c *gin.Context) { - selectionHandler.SelectOptimalCarrier(c.Writer, c.Request) - }) - selection.GET("/default", func(c *gin.Context) { - selectionHandler.SelectCarrier(c.Writer, c.Request) - }) - selection.GET("/analytics", func(c *gin.Context) { - selectionHandler.GetSelectionAnalytics(c.Writer, c.Request) - }) -} diff --git a/apps/carrier-connector/routes_platform.go b/apps/carrier-connector/routes_platform.go deleted file mode 100644 index bf83565..0000000 --- a/apps/carrier-connector/routes_platform.go +++ /dev/null @@ -1,139 +0,0 @@ -package main - -import ( - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" - "gorm.io/gorm" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/analytics" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/compliance" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/infra" - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/whitelabel" -) - -// registerAnalyticsRoutes registers analytics dashboard routes (platform-011, 012) -func registerAnalyticsRoutes(api *gin.RouterGroup, db *gorm.DB, logger *logrus.Logger) { - analyticsService := analytics.NewService(db, logger) - analyticsHandler := handlers.NewAnalyticsHandler(analyticsService, logger) - - group := api.Group("/analytics") - group.GET("/dashboard", analyticsHandler.GetDashboard) - group.GET("/revenue", analyticsHandler.GetRevenueAnalytics) - group.POST("/reports", analyticsHandler.CreateScheduledReport) - group.GET("/reports", analyticsHandler.ListScheduledReports) -} - -// registerWhitelabelRoutes registers whitelabel partner routes (platform-009) -func registerWhitelabelRoutes(api *gin.RouterGroup, db *gorm.DB, logger *logrus.Logger) { - whitelabelService := whitelabel.NewService(db, logger) - whitelabelHandler := handlers.NewWhitelabelHandler(whitelabelService, logger) - - group := api.Group("/whitelabel") - group.POST("/branding", whitelabelHandler.CreateBranding) - group.GET("/branding", whitelabelHandler.GetBranding) - group.GET("/branding/domain/:domain", whitelabelHandler.GetBrandingByDomain) - group.PUT("/branding", whitelabelHandler.UpdateBranding) - - group.POST("/partner", whitelabelHandler.CreatePartnerConfig) - group.GET("/partner", whitelabelHandler.GetPartnerConfig) - - group.POST("/templates", whitelabelHandler.CreateEmailTemplate) - group.GET("/templates", whitelabelHandler.ListEmailTemplates) - group.GET("/templates/:key", whitelabelHandler.GetEmailTemplate) -} - -// registerComplianceRoutes registers compliance routes (platform-013, 014, 015, 016) -func registerComplianceRoutes(api *gin.RouterGroup, db *gorm.DB, logger *logrus.Logger) { - complianceService := compliance.NewService(db, logger) - complianceHandler := handlers.NewComplianceHandler(complianceService, logger) - - group := api.Group("/compliance") - - // Data Subject Requests (GDPR/CCPA) - group.POST("/dsr", complianceHandler.CreateDSR) - group.GET("/dsr", complianceHandler.ListDSRs) - group.GET("/dsr/:id", complianceHandler.GetDSR) - group.POST("/dsr/:id/process", complianceHandler.ProcessDSR) - - // Consent Management - group.POST("/consent", complianceHandler.RecordConsent) - group.GET("/consent/:subject_id", complianceHandler.GetConsents) - group.DELETE("/consent/:subject_id", complianceHandler.RevokeConsent) - - // Audit Logging - group.GET("/audit", complianceHandler.QueryAuditLogs) - - // Data Residency - group.POST("/residency", complianceHandler.SetDataResidency) - group.GET("/residency", complianceHandler.GetDataResidency) -} - -// registerExchangeRateRoutes registers exchange rate routes (platform-017) -func registerExchangeRateRoutes(api *gin.RouterGroup, logger *logrus.Logger) { - config := currency.ExchangeRateConfig{ - Provider: currency.ProviderInternal, - BaseCurrency: "USD", - } - exchangeService := currency.NewRealTimeExchangeService(config, logger) - - group := api.Group("/exchange") - group.GET("/rate/:from/:to", func(c *gin.Context) { - from := c.Param("from") - to := c.Param("to") - rate, err := exchangeService.GetRate(c.Request.Context(), from, to) - if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - c.JSON(200, gin.H{"from": from, "to": to, "rate": rate}) - }) - group.GET("/rates", func(c *gin.Context) { - c.JSON(200, gin.H{ - "rates": exchangeService.GetAllRates(), - "last_update": exchangeService.LastUpdateTime(), - }) - }) - group.GET("/currencies", func(c *gin.Context) { - c.JSON(200, gin.H{"currencies": exchangeService.GetSupportedCurrencies()}) - }) - group.POST("/refresh", func(c *gin.Context) { - if err := exchangeService.RefreshRates(c.Request.Context()); err != nil { - c.JSON(500, gin.H{"error": err.Error()}) - return - } - c.JSON(200, gin.H{"status": "refreshed"}) - }) -} - -// registerInfraRoutes registers infrastructure monitoring routes (platform-020, 026, 027) -func registerInfraRoutes(api *gin.RouterGroup, cache *infra.Cache, geoRouter *infra.GeoRouter) { - group := api.Group("/infra") - - // Cache stats (platform-020) - group.GET("/cache/stats", func(c *gin.Context) { - if cache != nil { - c.JSON(200, cache.Stats()) - } else { - c.JSON(200, gin.H{"status": "cache not configured"}) - } - }) - - // Geographic routing (platform-026) - group.GET("/regions", func(c *gin.Context) { - if geoRouter != nil { - c.JSON(200, geoRouter.GetRegions()) - } else { - c.JSON(200, gin.H{"status": "geo routing not configured"}) - } - }) - group.GET("/region/detect", func(c *gin.Context) { - if geoRouter != nil { - region, _ := geoRouter.GetRegionForIP(c.Request.Context(), c.ClientIP()) - c.JSON(200, region) - } else { - c.JSON(200, gin.H{"region": "default"}) - } - }) -} diff --git a/apps/carrier-connector/tests/e2e/subscriber_journey_test.go b/apps/carrier-connector/tests/e2e/subscriber_journey_test.go deleted file mode 100644 index 389a6aa..0000000 --- a/apps/carrier-connector/tests/e2e/subscriber_journey_test.go +++ /dev/null @@ -1,744 +0,0 @@ -package e2e - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" - "github.com/sirupsen/logrus" - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -// SubscriberJourneyTestSuite tests end-to-end subscriber journey -type SubscriberJourneyTestSuite struct { - suite.Suite - db *gorm.DB - router *gin.Engine - logger *logrus.Logger -} - -// SetupSuite sets up the test suite -func (suite *SubscriberJourneyTestSuite) SetupSuite() { - // Setup in-memory database for testing - db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) - require.NoError(suite.T(), err) - - // Auto-migrate tables - err = db.AutoMigrate( - &repository.RatePlanUsage{}, - &repository.RatePlanSubscription{}, - ) - require.NoError(suite.T(), err) - - suite.db = db - suite.logger = logrus.New() - suite.logger.SetLevel(logrus.ErrorLevel) - - // Setup router with full application routes - gin.SetMode(gin.TestMode) - suite.router = gin.New() - suite.setupRoutes() -} - -func (suite *SubscriberJourneyTestSuite) setupRoutes() { - api := suite.router.Group("/api/v1") - - // Profile management - profiles := api.Group("/profiles") - { - profiles.POST("", suite.createProfile) - profiles.GET("/:id", suite.getProfile) - profiles.PUT("/:id/activate", suite.activateProfile) - } - - // Rate plan management - rateplans := api.Group("/rateplans") - { - rateplans.POST("", suite.createRatePlan) - rateplans.GET("", suite.listRatePlans) - rateplans.GET("/:id", suite.getRatePlan) - } - - // Subscription management - subscriptions := api.Group("/subscriptions") - { - subscriptions.POST("", suite.createSubscription) - subscriptions.GET("/:id", suite.getSubscription) - subscriptions.PUT("/:id/cancel", suite.cancelSubscription) - subscriptions.POST("/:id/usage", suite.recordUsage) - subscriptions.GET("/:id/usage", suite.getUsage) - } - - // Billing - billing := api.Group("/billing") - { - billing.POST("/charge", suite.processCharge) - billing.GET("/invoice/:subscription_id", suite.getInvoice) - } - - // Analytics - analytics := api.Group("/analytics") - { - analytics.GET("/dashboard", suite.getDashboard) - analytics.GET("/revenue", suite.getRevenue) - } -} - -// TestCompleteSubscriberJourney tests the full subscriber lifecycle -func (suite *SubscriberJourneyTestSuite) TestCompleteSubscriberJourney() { - // Step 1: Create a rate plan - ratePlanReq := map[string]interface{}{ - "name": "Premium Data Plan", - "description": "Unlimited data with voice and SMS", - "currency": "USD", - "base_price": 29.99, - "billing_cycle": "monthly", - "features": []string{"unlimited_data", "unlimited_voice", "unlimited_sms"}, - } - - reqBody, _ := json.Marshal(ratePlanReq) - req := httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var ratePlan map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &ratePlan) - require.NoError(suite.T(), err) - ratePlanID := fmt.Sprintf("%v", ratePlan["id"]) - - // Step 2: Create a subscriber profile - profileReq := map[string]interface{}{ - "first_name": "John", - "last_name": "Doe", - "email": "john.doe@example.com", - "phone": "+1234567890", - "country": "US", - "status": "pending", - "metadata": map[string]interface{}{ - "source": "web_signup", - }, - } - - reqBody, _ = json.Marshal(profileReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var profile map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &profile) - require.NoError(suite.T(), err) - profileID := fmt.Sprintf("%v", profile["id"]) - - // Step 3: Activate the profile - req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/profiles/%s/activate", profileID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - // Step 4: Subscribe to a rate plan - subReq := map[string]interface{}{ - "profile_id": profileID, - "rate_plan_id": ratePlanID, - "auto_renew": true, - "payment_method": "credit_card", - "billing_address": map[string]interface{}{ - "street": "123 Main St", - "city": "New York", - "state": "NY", - "zip": "10001", - "country": "US", - }, - } - - reqBody, _ = json.Marshal(subReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var subscription repository.RatePlanSubscription - err = json.Unmarshal(w.Body.Bytes(), &subscription) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), profileID, subscription.ProfileID) - assert.Equal(suite.T(), ratePlanID, subscription.RatePlanID) - assert.Equal(suite.T(), repository.SubscriptionStatus("active"), subscription.Status) - - subscriptionID := subscription.ID - - // Step 5: Record usage for the subscription - usageReq := map[string]interface{}{ - "data_used": int64(5120), // 5GB in MB - "voice_used": int64(450), // 450 minutes - "sms_used": int64(25), // 25 SMS - "timestamp": time.Now().Format(time.RFC3339), - } - - reqBody, _ = json.Marshal(usageReq) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/subscriptions/%s/usage", subscriptionID), bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - // Step 6: Get usage history - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s/usage", subscriptionID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var usageList []repository.RatePlanUsage - err = json.Unmarshal(w.Body.Bytes(), &usageList) - require.NoError(suite.T(), err) - assert.Greater(suite.T(), len(usageList), 0) - - // Step 7: Process billing charge - chargeReq := map[string]interface{}{ - "subscription_id": subscriptionID, - "amount": 29.99, - "currency": "USD", - "description": "Monthly subscription fee", - "charge_type": "recurring", - } - - reqBody, _ = json.Marshal(chargeReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/billing/charge", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - // Step 8: Get invoice - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/billing/invoice/%s", subscriptionID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var invoice map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &invoice) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), subscriptionID, fmt.Sprintf("%v", invoice["subscription_id"])) - assert.Equal(suite.T(), 29.99, invoice["total_amount"]) - - // Step 9: Check analytics dashboard - req = httptest.NewRequest(http.MethodGet, "/api/v1/analytics/dashboard", nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var dashboard map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &dashboard) - require.NoError(suite.T(), err) - assert.Contains(suite.T(), dashboard, "total_subscribers") - assert.Contains(suite.T(), dashboard, "monthly_revenue") - assert.Contains(suite.T(), dashboard, "active_subscriptions") - - // Step 10: Cancel subscription (end of journey) - req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/subscriptions/%s/cancel", subscriptionID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - // Verify subscription is cancelled - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subscriptionID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var cancelledSub repository.RatePlanSubscription - err = json.Unmarshal(w.Body.Bytes(), &cancelledSub) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), repository.SubscriptionStatus("cancelled"), cancelledSub.Status) -} - -// TestMultiSubscriptionJourney tests subscriber with multiple subscriptions -func (suite *SubscriberJourneyTestSuite) TestMultiSubscriptionJourney() { - // Create multiple rate plans - ratePlans := []map[string]interface{}{ - { - "name": "Basic Plan", - "base_price": 9.99, - "currency": "USD", - "features": []string{"1gb_data", "100min_voice"}, - }, - { - "name": "Premium Plan", - "base_price": 29.99, - "currency": "USD", - "features": []string{"unlimited_data", "unlimited_voice"}, - }, - } - - ratePlanIDs := []string{} - for _, rp := range ratePlans { - reqBody, _ := json.Marshal(rp) - req := httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var ratePlan map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &ratePlan) - require.NoError(suite.T(), err) - ratePlanIDs = append(ratePlanIDs, fmt.Sprintf("%v", ratePlan["id"])) - } - - // Create profile - profileReq := map[string]interface{}{ - "first_name": "Jane", - "last_name": "Smith", - "email": "jane.smith@example.com", - "country": "US", - } - - reqBody, _ := json.Marshal(profileReq) - req := httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - var profile map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &profile) - require.NoError(suite.T(), err) - profileID := fmt.Sprintf("%v", profile["id"]) - - // Activate profile - req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/profiles/%s/activate", profileID), nil) - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusOK, w.Code) - - // Subscribe to multiple plans - subscriptionIDs := []string{} - for i, ratePlanID := range ratePlanIDs { - subReq := map[string]interface{}{ - "profile_id": profileID, - "rate_plan_id": ratePlanID, - "auto_renew": true, - "metadata": map[string]interface{}{ - "priority": i + 1, - }, - } - - reqBody, _ = json.Marshal(subReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var subscription repository.RatePlanSubscription - err = json.Unmarshal(w.Body.Bytes(), &subscription) - require.NoError(suite.T(), err) - subscriptionIDs = append(subscriptionIDs, subscription.ID) - } - - // Verify all subscriptions are active - for _, subID := range subscriptionIDs { - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subID), nil) - w := httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var sub repository.RatePlanSubscription - err = json.Unmarshal(w.Body.Bytes(), &sub) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), repository.SubscriptionStatus("active"), sub.Status) - } - - // Record usage on primary subscription - usageReq := map[string]interface{}{ - "data_used": int64(2048), - "voice_used": int64(150), - "sms_used": int64(10), - } - - reqBody, _ = json.Marshal(usageReq) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/subscriptions/%s/usage", subscriptionIDs[0]), bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - // Check analytics reflects multiple subscriptions - req = httptest.NewRequest(http.MethodGet, "/api/v1/analytics/dashboard", nil) - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var dashboard map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &dashboard) - require.NoError(suite.T(), err) - - // Should have at least 1 active subscriber - totalSubs := dashboard["total_subscribers"].(int64) - assert.Greater(suite.T(), totalSubs, int64(0)) -} - -// TestFailedJourneyHandling tests error scenarios in subscriber journey -func (suite *SubscriberJourneyTestSuite) TestFailedJourneyHandling() { - // Test subscription without active profile - subReq := map[string]interface{}{ - "profile_id": "non-existent-profile", - "rate_plan_id": "non-existent-plan", - "auto_renew": true, - } - - reqBody, _ := json.Marshal(subReq) - req := httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusBadRequest, w.Code) - - var errorResp map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &errorResp) - require.NoError(suite.T(), err) - assert.Contains(suite.T(), errorResp, "error") - - // Test usage recording for non-existent subscription - usageReq := map[string]interface{}{ - "data_used": int64(100), - "voice_used": int64(10), - "sms_used": int64(5), - } - - reqBody, _ = json.Marshal(usageReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions/non-existent/usage", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusNotFound, w.Code) - - // Test billing for cancelled subscription - // First create and cancel a subscription - profileReq := map[string]interface{}{ - "email": "test@example.com", - "country": "US", - } - - reqBody, _ = json.Marshal(profileReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - var profile map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &profile) - require.NoError(suite.T(), err) - profileID := fmt.Sprintf("%v", profile["id"]) - - // Activate - req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/profiles/%s/activate", profileID), nil) - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - // Create rate plan - rpReq := map[string]interface{}{ - "name": "Test Plan", - "currency": "USD", - "base_price": 10.0, - } - reqBody, _ = json.Marshal(rpReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - var ratePlan map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &ratePlan) - require.NoError(suite.T(), err) - ratePlanID := fmt.Sprintf("%v", ratePlan["id"]) - - // Subscribe - subReq = map[string]interface{}{ - "profile_id": profileID, - "rate_plan_id": ratePlanID, - "auto_renew": true, - } - reqBody, _ = json.Marshal(subReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - var subscription repository.RatePlanSubscription - err = json.Unmarshal(w.Body.Bytes(), &subscription) - require.NoError(suite.T(), err) - - // Cancel subscription - req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/subscriptions/%s/cancel", subscription.ID), nil) - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - // Try to bill cancelled subscription - chargeReq := map[string]interface{}{ - "subscription_id": subscription.ID, - "amount": 10.0, - "currency": "USD", - } - reqBody, _ = json.Marshal(chargeReq) - req = httptest.NewRequest(http.MethodPost, "/api/v1/billing/charge", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusBadRequest, w.Code) -} - -// Handler implementations (simplified for testing) -func (suite *SubscriberJourneyTestSuite) createProfile(c *gin.Context) { - profile := map[string]interface{}{ - "id": fmt.Sprintf("profile-%d", time.Now().UnixNano()), - "status": "pending", - "created_at": time.Now(), - } - - // Add request fields - var req map[string]interface{} - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - for k, v := range req { - profile[k] = v - } - - c.JSON(http.StatusCreated, profile) -} - -func (suite *SubscriberJourneyTestSuite) getProfile(c *gin.Context) { - profile := map[string]interface{}{ - "id": c.Param("id"), - "status": "active", - } - c.JSON(http.StatusOK, profile) -} - -func (suite *SubscriberJourneyTestSuite) activateProfile(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"status": "activated"}) -} - -func (suite *SubscriberJourneyTestSuite) createRatePlan(c *gin.Context) { - ratePlan := map[string]interface{}{ - "id": fmt.Sprintf("plan-%d", time.Now().UnixNano()), - "created_at": time.Now(), - } - - var req map[string]interface{} - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - for k, v := range req { - ratePlan[k] = v - } - - c.JSON(http.StatusCreated, ratePlan) -} - -func (suite *SubscriberJourneyTestSuite) listRatePlans(c *gin.Context) { - plans := []map[string]interface{}{ - { - "id": "plan-1", - "name": "Basic Plan", - "price": 9.99, - }, - { - "id": "plan-2", - "name": "Premium Plan", - "price": 29.99, - }, - } - c.JSON(http.StatusOK, plans) -} - -func (suite *SubscriberJourneyTestSuite) getRatePlan(c *gin.Context) { - ratePlan := map[string]interface{}{ - "id": c.Param("id"), - "name": "Test Plan", - } - c.JSON(http.StatusOK, ratePlan) -} - -func (suite *SubscriberJourneyTestSuite) createSubscription(c *gin.Context) { - var subscription repository.RatePlanSubscription - if err := c.ShouldBindJSON(&subscription); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - subscription.ID = fmt.Sprintf("sub-%d", time.Now().UnixNano()) - subscription.Status = repository.SubscriptionStatus("active") - subscription.StartedAt = time.Now() - subscription.CreatedAt = time.Now() - subscription.UpdatedAt = time.Now() - subscription.CurrentCycle = time.Now() - - c.JSON(http.StatusCreated, subscription) -} - -func (suite *SubscriberJourneyTestSuite) getSubscription(c *gin.Context) { - var subscription repository.RatePlanSubscription - if err := suite.db.First(&subscription, "id = ?", c.Param("id")).Error; err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "subscription not found"}) - return - } - - c.JSON(http.StatusOK, subscription) -} - -func (suite *SubscriberJourneyTestSuite) cancelSubscription(c *gin.Context) { - if err := suite.db.Model(&repository.RatePlanSubscription{}).Where("id = ?", c.Param("id")).Updates(map[string]interface{}{ - "status": "cancelled", - "ended_at": time.Now(), - "updated_at": time.Now(), - }).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "cancelled"}) -} - -func (suite *SubscriberJourneyTestSuite) recordUsage(c *gin.Context) { - var usage repository.RatePlanUsage - if err := c.ShouldBindJSON(&usage); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - usage.ID = fmt.Sprintf("usage-%d", time.Now().UnixNano()) - usage.RatePlanID = "test-plan" - usage.LastUpdated = time.Now() - - if err := suite.db.Create(&usage).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, usage) -} - -func (suite *SubscriberJourneyTestSuite) getUsage(c *gin.Context) { - var usage []repository.RatePlanUsage - if err := suite.db.Where("rate_plan_id = ?", "test-plan").Find(&usage).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, usage) -} - -func (suite *SubscriberJourneyTestSuite) processCharge(c *gin.Context) { - var charge map[string]interface{} - if err := c.ShouldBindJSON(&charge); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - charge["id"] = fmt.Sprintf("charge-%d", time.Now().UnixNano()) - charge["status"] = "completed" - charge["processed_at"] = time.Now() - - c.JSON(http.StatusOK, charge) -} - -func (suite *SubscriberJourneyTestSuite) getInvoice(c *gin.Context) { - invoice := map[string]interface{}{ - "subscription_id": c.Param("subscription_id"), - "total_amount": 29.99, - "currency": "USD", - "status": "paid", - "issued_at": time.Now(), - } - c.JSON(http.StatusOK, invoice) -} - -func (suite *SubscriberJourneyTestSuite) getDashboard(c *gin.Context) { - dashboard := map[string]interface{}{ - "total_subscribers": int64(100), - "active_subscriptions": int64(95), - "monthly_revenue": 2999.0, - "churn_rate": 0.05, - "generated_at": time.Now(), - } - c.JSON(http.StatusOK, dashboard) -} - -func (suite *SubscriberJourneyTestSuite) getRevenue(c *gin.Context) { - revenue := map[string]interface{}{ - "total_revenue": 50000.0, - "monthly_revenue": 5000.0, - "revenue_by_plan": map[string]float64{ - "basic": 1500.0, - "premium": 3500.0, - }, - "currency": "USD", - } - c.JSON(http.StatusOK, revenue) -} - -// TearDownSuite cleans up the test suite -func (suite *SubscriberJourneyTestSuite) TearDownSuite() { - sqlDB, err := suite.db.DB() - if err == nil { - sqlDB.Close() - } -} - -// TestSubscriberJourneySuite runs the E2E test suite -func TestSubscriberJourneySuite(t *testing.T) { - suite.Run(t, new(SubscriberJourneyTestSuite)) -} diff --git a/apps/carrier-connector/tests/integration/api_integration_test.go b/apps/carrier-connector/tests/integration/api_integration_test.go deleted file mode 100644 index fd52f58..0000000 --- a/apps/carrier-connector/tests/integration/api_integration_test.go +++ /dev/null @@ -1,440 +0,0 @@ -package integration - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" - "github.com/sirupsen/logrus" - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -// APIIntegrationTestSuite tests API integration -type APIIntegrationTestSuite struct { - suite.Suite - db *gorm.DB - router *gin.Engine - logger *logrus.Logger -} - -// SetupSuite sets up the test suite -func (suite *APIIntegrationTestSuite) SetupSuite() { - // Setup in-memory database for testing - db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) - require.NoError(suite.T(), err) - - // Auto-migrate tables - err = db.AutoMigrate( - &repository.RatePlanUsage{}, - &repository.RatePlanSubscription{}, - ) - require.NoError(suite.T(), err) - - suite.db = db - suite.logger = logrus.New() - suite.logger.SetLevel(logrus.ErrorLevel) - - // Setup router - gin.SetMode(gin.TestMode) - suite.router = gin.New() - suite.setupRoutes() -} - -func (suite *APIIntegrationTestSuite) setupRoutes() { - api := suite.router.Group("/api/v1") - - // Rate plan usage routes - usage := api.Group("/usage") - { - usage.POST("", suite.createUsage) - usage.GET("/:id", suite.getUsage) - usage.GET("", suite.listUsage) - } - - // Subscription routes - subscriptions := api.Group("/subscriptions") - { - subscriptions.POST("", suite.createSubscription) - subscriptions.GET("/:id", suite.getSubscription) - subscriptions.PUT("/:id/cancel", suite.cancelSubscription) - } - - // Health check - api.GET("/health", suite.healthCheck) -} - -// TestRatePlanUsageLifecycle tests usage tracking -func (suite *APIIntegrationTestSuite) TestRatePlanUsageLifecycle() { - // 1. Create a rate plan usage record - usageReq := map[string]interface{}{ - "rate_plan_id": "plan-123", - "profile_id": "profile-456", - "cycle_start": "2026-01-01T00:00:00Z", - "cycle_end": "2026-01-31T23:59:59Z", - "data_used": int64(1024), - "voice_used": int64(300), - "sms_used": int64(50), - } - - reqBody, _ := json.Marshal(usageReq) - req := httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var usage repository.RatePlanUsage - err := json.Unmarshal(w.Body.Bytes(), &usage) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), "plan-123", usage.RatePlanID) - assert.Equal(suite.T(), "profile-456", usage.ProfileID) - assert.Equal(suite.T(), int64(1024), usage.DataUsed) - - usageID := usage.ID - - // 2. Get the usage record - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/usage/%s", usageID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var retrievedUsage repository.RatePlanUsage - err = json.Unmarshal(w.Body.Bytes(), &retrievedUsage) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), usageID, retrievedUsage.ID) - - // 3. List usage records - req = httptest.NewRequest(http.MethodGet, "/api/v1/usage", nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var usageList []repository.RatePlanUsage - err = json.Unmarshal(w.Body.Bytes(), &usageList) - require.NoError(suite.T(), err) - assert.Greater(suite.T(), len(usageList), 0) -} - -// TestSubscriptionLifecycle tests subscription management -func (suite *APIIntegrationTestSuite) TestSubscriptionLifecycle() { - // 1. Create a subscription - subReq := map[string]interface{}{ - "profile_id": "profile-789", - "rate_plan_id": "plan-abc", - "status": "active", - "billing_cycle": "monthly", - "next_billing_date": "2026-02-01T00:00:00Z", - "auto_renew": true, - "applied_discounts": []string{"new-user-10"}, - } - - reqBody, _ := json.Marshal(subReq) - req := httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var subscription repository.RatePlanSubscription - err := json.Unmarshal(w.Body.Bytes(), &subscription) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), "profile-789", subscription.ProfileID) - assert.Equal(suite.T(), "plan-abc", subscription.RatePlanID) - assert.Equal(suite.T(), repository.SubscriptionStatus("active"), subscription.Status) - - subscriptionID := subscription.ID - - // 2. Get the subscription - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subscriptionID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var retrievedSubscription repository.RatePlanSubscription - err = json.Unmarshal(w.Body.Bytes(), &retrievedSubscription) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), subscriptionID, retrievedSubscription.ID) - - // 3. Cancel the subscription - req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/subscriptions/%s/cancel", subscriptionID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - // 4. Verify subscription is cancelled - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subscriptionID), nil) - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var cancelledSubscription repository.RatePlanSubscription - err = json.Unmarshal(w.Body.Bytes(), &cancelledSubscription) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), repository.SubscriptionStatus("cancelled"), cancelledSubscription.Status) -} - -// TestHealthCheck tests the health endpoint -func (suite *APIIntegrationTestSuite) TestHealthCheck() { - req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil) - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusOK, w.Code) - - var health map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &health) - require.NoError(suite.T(), err) - assert.Equal(suite.T(), "healthy", health["status"]) - assert.Contains(suite.T(), health, "timestamp") -} - -// TestErrorHandling tests API error handling -func (suite *APIIntegrationTestSuite) TestErrorHandling() { - // Test getting non-existent usage - req := httptest.NewRequest(http.MethodGet, "/api/v1/usage/non-existent", nil) - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusNotFound, w.Code) - - var errorResponse map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &errorResponse) - require.NoError(suite.T(), err) - assert.Contains(suite.T(), errorResponse, "error") - - // Test invalid request body - reqBody := []byte("{invalid json}") - req = httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusBadRequest, w.Code) -} - -// TestConcurrentRequests tests concurrent API requests -func (suite *APIIntegrationTestSuite) TestConcurrentRequests() { - // Create some test data first - usage := repository.RatePlanUsage{ - RatePlanID: "test-plan", - ProfileID: "test-profile", - CycleStart: time.Now(), - CycleEnd: time.Now().Add(30 * 24 * time.Hour), - DataUsed: 100, - VoiceUsed: 50, - SMSUsed: 10, - } - err := suite.db.Create(&usage).Error - require.NoError(suite.T(), err) - - // Test concurrent requests - const numGoroutines = 10 - const numRequests = 100 - - errChan := make(chan error, numGoroutines) - - for i := 0; i < numGoroutines; i++ { - go func() { - for j := 0; j < numRequests/numGoroutines; j++ { - req := httptest.NewRequest(http.MethodGet, "/api/v1/usage", nil) - w := httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - - if w.Code != http.StatusOK { - errChan <- fmt.Errorf("unexpected status code: %d", w.Code) - return - } - } - errChan <- nil - }() - } - - // Wait for all goroutines to complete - for i := 0; i < numGoroutines; i++ { - err := <-errChan - assert.NoError(suite.T(), err) - } -} - -// TestRatePlanUsageValidation tests input validation -func (suite *APIIntegrationTestSuite) TestRatePlanUsageValidation() { - // Test missing required fields - invalidReq := map[string]interface{}{ - "profile_id": "profile-123", - // Missing rate_plan_id - } - - reqBody, _ := json.Marshal(invalidReq) - req := httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusBadRequest, w.Code) - - var errorResponse map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &errorResponse) - require.NoError(suite.T(), err) - assert.Contains(suite.T(), errorResponse, "error") - - // Test invalid data types - invalidReq2 := map[string]interface{}{ - "rate_plan_id": "plan-123", - "profile_id": "profile-123", - "data_used": "not-a-number", // Should be int64 - } - - reqBody, _ = json.Marshal(invalidReq2) - req = httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody)) - req.Header.Set("Content-Type", "application/json") - w = httptest.NewRecorder() - - suite.router.ServeHTTP(w, req) - - assert.Equal(suite.T(), http.StatusBadRequest, w.Code) -} - -// Handler implementations -func (suite *APIIntegrationTestSuite) createUsage(c *gin.Context) { - var usage repository.RatePlanUsage - if err := c.ShouldBindJSON(&usage); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Generate ID if not provided - if usage.ID == "" { - usage.ID = fmt.Sprintf("usage-%d", time.Now().UnixNano()) - } - - // Set timestamps - now := time.Now() - usage.LastUpdated = now - - if err := suite.db.Create(&usage).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, usage) -} - -func (suite *APIIntegrationTestSuite) getUsage(c *gin.Context) { - var usage repository.RatePlanUsage - if err := suite.db.First(&usage, "id = ?", c.Param("id")).Error; err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "usage record not found"}) - return - } - - c.JSON(http.StatusOK, usage) -} - -func (suite *APIIntegrationTestSuite) listUsage(c *gin.Context) { - var usage []repository.RatePlanUsage - if err := suite.db.Find(&usage).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, usage) -} - -func (suite *APIIntegrationTestSuite) createSubscription(c *gin.Context) { - var subscription repository.RatePlanSubscription - if err := c.ShouldBindJSON(&subscription); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // Generate ID if not provided - if subscription.ID == "" { - subscription.ID = fmt.Sprintf("sub-%d", time.Now().UnixNano()) - } - - // Set timestamps - now := time.Now() - subscription.StartedAt = now - subscription.CreatedAt = now - subscription.UpdatedAt = now - subscription.CurrentCycle = now - - if err := suite.db.Create(&subscription).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, subscription) -} - -func (suite *APIIntegrationTestSuite) getSubscription(c *gin.Context) { - var subscription repository.RatePlanSubscription - if err := suite.db.First(&subscription, "id = ?", c.Param("id")).Error; err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "subscription not found"}) - return - } - - c.JSON(http.StatusOK, subscription) -} - -func (suite *APIIntegrationTestSuite) cancelSubscription(c *gin.Context) { - if err := suite.db.Model(&repository.RatePlanSubscription{}).Where("id = ?", c.Param("id")).Updates(map[string]interface{}{ - "status": "cancelled", - "ended_at": time.Now(), - "updated_at": time.Now(), - }).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "cancelled"}) -} - -func (suite *APIIntegrationTestSuite) healthCheck(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "status": "healthy", - "timestamp": time.Now(), - "version": "1.0.0", - }) -} - -// TearDownSuite cleans up the test suite -func (suite *APIIntegrationTestSuite) TearDownSuite() { - sqlDB, err := suite.db.DB() - if err == nil { - sqlDB.Close() - } -} - -// TestAPIIntegrationSuite runs the integration test suite -func TestAPIIntegrationSuite(t *testing.T) { - suite.Run(t, new(APIIntegrationTestSuite)) -} diff --git a/apps/carrier-connector/tests/integration/carrier_api_test.go b/apps/carrier-connector/tests/integration/carrier_api_test.go deleted file mode 100644 index 7c79de5..0000000 --- a/apps/carrier-connector/tests/integration/carrier_api_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package integration - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -// CarrierAPITestSuite tests carrier API integration -type CarrierAPITestSuite struct { - suite.Suite - db *gorm.DB - router *gin.Engine -} - -func (suite *CarrierAPITestSuite) SetupSuite() { - db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) - require.NoError(suite.T(), err) - - err = db.AutoMigrate(&repository.RatePlan{}, &repository.RatePlanSubscription{}, &repository.RatePlanUsage{}) - require.NoError(suite.T(), err) - - suite.db = db - gin.SetMode(gin.TestMode) - suite.router = gin.New() - suite.setupRoutes() -} - -func (suite *CarrierAPITestSuite) setupRoutes() { - api := suite.router.Group("/api/v1") - api.POST("/rateplans", suite.createRatePlan) - api.GET("/rateplans", suite.listRatePlans) - api.GET("/rateplans/:id", suite.getRatePlan) - api.POST("/subscriptions", suite.createSubscription) - api.GET("/subscriptions/:id", suite.getSubscription) - api.POST("/usage", suite.recordUsage) - api.GET("/health", suite.healthCheck) -} - -func (suite *CarrierAPITestSuite) TestRatePlanLifecycle() { - plan := map[string]interface{}{ - "name": "Test Plan", "description": "Test", "carrier_id": "carrier-1", - "region": "US", "base_price": 10.0, "currency": "USD", "is_active": true, - } - body, _ := json.Marshal(plan) - req := httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var created repository.RatePlan - json.Unmarshal(w.Body.Bytes(), &created) - assert.Equal(suite.T(), "Test Plan", created.Name) - - req = httptest.NewRequest(http.MethodGet, "/api/v1/rateplans/"+created.ID, nil) - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusOK, w.Code) - - req = httptest.NewRequest(http.MethodGet, "/api/v1/rateplans", nil) - w = httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusOK, w.Code) -} - -func (suite *CarrierAPITestSuite) TestSubscriptionLifecycle() { - plan := &repository.RatePlan{ID: "plan-1", Name: "Plan", CarrierID: "c1", BasePrice: 10, Currency: "USD", IsActive: true} - suite.db.Create(plan) - - sub := map[string]interface{}{"profile_id": "profile-1", "rate_plan_id": "plan-1", "auto_renew": true} - body, _ := json.Marshal(sub) - req := httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusCreated, w.Code) - - var created repository.RatePlanSubscription - json.Unmarshal(w.Body.Bytes(), &created) - assert.Equal(suite.T(), "profile-1", created.ProfileID) -} - -func (suite *CarrierAPITestSuite) TestUsageRecording() { - usage := map[string]interface{}{"profile_id": "p1", "rate_plan_id": "rp1", "data_used": 100, "voice_used": 10, "sms_used": 5} - body, _ := json.Marshal(usage) - req := httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - w := httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusCreated, w.Code) -} - -func (suite *CarrierAPITestSuite) TestHealthCheck() { - req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil) - w := httptest.NewRecorder() - suite.router.ServeHTTP(w, req) - assert.Equal(suite.T(), http.StatusOK, w.Code) -} - -func (suite *CarrierAPITestSuite) createRatePlan(c *gin.Context) { - var plan repository.RatePlan - if err := c.ShouldBindJSON(&plan); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - plan.ID = "rp-" + time.Now().Format("20060102150405") - plan.CreatedAt = time.Now() - plan.UpdatedAt = time.Now() - suite.db.Create(&plan) - c.JSON(http.StatusCreated, plan) -} - -func (suite *CarrierAPITestSuite) listRatePlans(c *gin.Context) { - var plans []repository.RatePlan - suite.db.Find(&plans) - c.JSON(http.StatusOK, plans) -} - -func (suite *CarrierAPITestSuite) getRatePlan(c *gin.Context) { - var plan repository.RatePlan - if err := suite.db.First(&plan, "id = ?", c.Param("id")).Error; err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) - return - } - c.JSON(http.StatusOK, plan) -} - -func (suite *CarrierAPITestSuite) createSubscription(c *gin.Context) { - var sub repository.RatePlanSubscription - if err := c.ShouldBindJSON(&sub); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - sub.ID = "sub-" + time.Now().Format("20060102150405") - sub.Status = repository.SubscriptionStatusActive - sub.StartedAt = time.Now() - sub.CreatedAt = time.Now() - sub.UpdatedAt = time.Now() - suite.db.Create(&sub) - c.JSON(http.StatusCreated, sub) -} - -func (suite *CarrierAPITestSuite) getSubscription(c *gin.Context) { - var sub repository.RatePlanSubscription - if err := suite.db.First(&sub, "id = ?", c.Param("id")).Error; err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) - return - } - c.JSON(http.StatusOK, sub) -} - -func (suite *CarrierAPITestSuite) recordUsage(c *gin.Context) { - var usage repository.RatePlanUsage - if err := c.ShouldBindJSON(&usage); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - usage.ID = "usage-" + time.Now().Format("20060102150405") - usage.LastUpdated = time.Now() - suite.db.Create(&usage) - c.JSON(http.StatusCreated, usage) -} - -func (suite *CarrierAPITestSuite) healthCheck(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"status": "healthy", "timestamp": time.Now()}) -} - -func (suite *CarrierAPITestSuite) TearDownSuite() { - if db, err := suite.db.DB(); err == nil { - db.Close() - } -} - -func TestCarrierAPITestSuite(t *testing.T) { - suite.Run(t, new(CarrierAPITestSuite)) -} diff --git a/apps/charging-engine/src/handlers/analytics.rs b/apps/charging-engine/src/handlers/analytics.rs deleted file mode 100644 index 3aa8a8d..0000000 --- a/apps/charging-engine/src/handlers/analytics.rs +++ /dev/null @@ -1,182 +0,0 @@ -use axum::{ - extract::{Path, Query, State}, - http::StatusCode, - Json, -}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -use crate::models::AppState; - -#[derive(Debug, Serialize)] -pub struct RevenueMetrics { - pub period: String, - pub total_revenue: f64, - pub arpu: f64, - pub mrr: f64, - pub arr: f64, - pub revenue_growth_pct: f64, - pub by_plan: HashMap, - pub by_region: HashMap, -} - -#[derive(Debug, Serialize)] -pub struct UsageAnalytics { - pub period: String, - pub total_data_gb: f64, - pub total_voice_minutes: f64, - pub total_sms: i64, - pub average_data_per_user_gb: f64, - pub peak_usage_hour: i32, - pub usage_trend: String, -} - -#[derive(Debug, Serialize)] -pub struct BillingAnalytics { - pub period: String, - pub total_invoiced: f64, - pub total_collected: f64, - pub collection_rate_pct: f64, - pub outstanding_amount: f64, - pub average_days_to_pay: f64, - pub by_payment_method: HashMap, -} - -#[derive(Debug, Deserialize)] -pub struct AnalyticsQuery { - pub period: Option, - pub start_date: Option, - pub end_date: Option, -} - -/// Get revenue metrics -pub async fn get_revenue_metrics( - State(_state): State, - Query(query): Query, -) -> Json { - let period = query.period.unwrap_or_else(|| "monthly".to_string()); - - let mut by_plan = HashMap::new(); - by_plan.insert("basic".to_string(), 1500000.0); - by_plan.insert("premium".to_string(), 2000000.0); - by_plan.insert("enterprise".to_string(), 1000000.0); - - let mut by_region = HashMap::new(); - by_region.insert("us".to_string(), 2500000.0); - by_region.insert("eu".to_string(), 1500000.0); - by_region.insert("apac".to_string(), 500000.0); - - Json(RevenueMetrics { - period, - total_revenue: 4500000.0, - arpu: 30.0, - mrr: 4500000.0, - arr: 54000000.0, - revenue_growth_pct: 12.5, - by_plan, - by_region, - }) -} - -/// Get usage analytics -pub async fn get_usage_analytics( - State(_state): State, - Query(query): Query, -) -> Json { - let period = query.period.unwrap_or_else(|| "monthly".to_string()); - - Json(UsageAnalytics { - period, - total_data_gb: 15000000.0, - total_voice_minutes: 50000000.0, - total_sms: 25000000, - average_data_per_user_gb: 100.0, - peak_usage_hour: 20, // 8 PM - usage_trend: "increasing".to_string(), - }) -} - -/// Get billing analytics -pub async fn get_billing_analytics( - State(_state): State, - Query(query): Query, -) -> Json { - let period = query.period.unwrap_or_else(|| "monthly".to_string()); - - let mut by_payment_method = HashMap::new(); - by_payment_method.insert("credit_card".to_string(), 3000000.0); - by_payment_method.insert("bank_transfer".to_string(), 1000000.0); - by_payment_method.insert("digital_wallet".to_string(), 350000.0); - - Json(BillingAnalytics { - period, - total_invoiced: 4500000.0, - total_collected: 4350000.0, - collection_rate_pct: 96.7, - outstanding_amount: 150000.0, - average_days_to_pay: 12.5, - by_payment_method, - }) -} - -#[derive(Debug, Serialize)] -pub struct ChurnRiskScore { - pub subscriber_id: String, - pub risk_score: f64, - pub risk_level: String, - pub factors: Vec, - pub recommendations: Vec, -} - -/// Get churn risk for a subscriber -pub async fn get_churn_risk( - State(_state): State, - Path(subscriber_id): Path, -) -> Json { - Json(ChurnRiskScore { - subscriber_id, - risk_score: 45.5, - risk_level: "medium".to_string(), - factors: vec![ - "Decreased usage over 30 days".to_string(), - "No recent plan upgrades".to_string(), - "Support tickets increased".to_string(), - ], - recommendations: vec![ - "Offer loyalty discount".to_string(), - "Proactive customer outreach".to_string(), - "Personalized plan recommendation".to_string(), - ], - }) -} - -#[derive(Debug, Serialize)] -pub struct PricingRecommendation { - pub plan_id: String, - pub current_price: f64, - pub recommended_price: f64, - pub price_change_pct: f64, - pub expected_revenue_impact: f64, - pub confidence: f64, - pub reasoning: Vec, -} - -/// Get pricing recommendation for a plan -pub async fn get_pricing_recommendation( - State(_state): State, - Path(plan_id): Path, -) -> Json { - Json(PricingRecommendation { - plan_id, - current_price: 29.99, - recommended_price: 32.99, - price_change_pct: 10.0, - expected_revenue_impact: 165000.0, - confidence: 85.0, - reasoning: vec![ - "Market analysis suggests price increase tolerance".to_string(), - "Competitor prices are higher".to_string(), - "Strong value proposition".to_string(), - ], - }) -} diff --git a/apps/cli/go.mod b/apps/cli/go.mod index c4af9a8..d36c69e 100644 --- a/apps/cli/go.mod +++ b/apps/cli/go.mod @@ -13,8 +13,6 @@ require ( k8s.io/client-go v0.31.0 ) -require github.com/inconshreveable/mousetrap v1.1.0 // indirect - require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect @@ -60,7 +58,6 @@ require ( github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/apps/cli/go.sum b/apps/cli/go.sum index 79791e7..8770163 100644 --- a/apps/cli/go.sum +++ b/apps/cli/go.sum @@ -22,7 +22,6 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -69,8 +68,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -120,16 +117,12 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= -github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= diff --git a/apps/cli/internal/commands/analytics.go b/apps/cli/internal/commands/analytics.go deleted file mode 100644 index 57479a3..0000000 --- a/apps/cli/internal/commands/analytics.go +++ /dev/null @@ -1,342 +0,0 @@ -package commands - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/spf13/cobra" -) - -// NewAnalyticsCmd creates the analytics command group -func NewAnalyticsCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "analytics", - Short: "Analytics and intelligence commands", - Long: "Commands for churn analysis, fraud detection, market analytics, and pricing optimization", - } - - cmd.AddCommand(newChurnCmd()) - cmd.AddCommand(newFraudCmd()) - cmd.AddCommand(newMarketCmd()) - cmd.AddCommand(newPricingCmd()) - cmd.AddCommand(newMaintenanceCmd()) - - return cmd -} - -func newChurnCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "churn", - Short: "Churn analysis commands", - } - - cmd.AddCommand(&cobra.Command{ - Use: "predict [profile-id]", - Short: "Predict churn risk for a profile", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - profileID := args[0] - fmt.Printf("🔮 Predicting churn for profile: %s\n\n", profileID) - - // Simulated prediction - prediction := map[string]any{ - "profile_id": profileID, - "risk_level": "medium", - "risk_score": 45.5, - "reasons": []string{"Decreased usage", "No recent upgrades"}, - "recommendations": []string{"Offer loyalty discount", "Proactive outreach"}, - } - - data, _ := json.MarshalIndent(prediction, "", " ") - fmt.Println(string(data)) - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "metrics", - Short: "Get churn metrics", - Run: func(cmd *cobra.Command, args []string) { - period, _ := cmd.Flags().GetString("period") - fmt.Printf("📊 Churn Metrics (%s)\n", period) - - metrics := map[string]any{ - "period": period, - "total_subscribers": 150000, - "churned": 2250, - "churn_rate": "1.5%", - "monthly_churn_rate": "1.5%", - "annual_churn_rate": "18.0%", - } - - data, _ := json.MarshalIndent(metrics, "", " ") - fmt.Println(string(data)) - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "at-risk", - Short: "List at-risk customers", - Run: func(cmd *cobra.Command, args []string) { - riskLevel, _ := cmd.Flags().GetString("risk-level") - limit, _ := cmd.Flags().GetInt("limit") - - fmt.Printf("👥 At-Risk Customers (Level: %s, Limit: %d)\n", riskLevel, limit) - fmt.Println("Profile ID | Risk Score | Predicted Churn Date") - fmt.Println("-----------------+------------+---------------------") - fmt.Println("profile-001 | 85.0 | 2026-06-15") - fmt.Println("profile-002 | 78.5 | 2026-06-22") - fmt.Println("profile-003 | 72.0 | 2026-07-01") - }, - }) - - // Add flags - cmd.PersistentFlags().String("period", "monthly", "Time period (daily, weekly, monthly, quarterly)") - cmd.PersistentFlags().String("risk-level", "high", "Risk level filter (low, medium, high, critical)") - cmd.PersistentFlags().Int("limit", 100, "Maximum number of results") - - return cmd -} - -func newFraudCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "fraud", - Short: "Fraud detection commands", - } - - cmd.AddCommand(&cobra.Command{ - Use: "alerts", - Short: "List fraud alerts", - Run: func(cmd *cobra.Command, args []string) { - severity, _ := cmd.Flags().GetString("severity") - fmt.Printf("🚨 Fraud Alerts (Severity: %s)\n", severity) - - fmt.Println("Alert ID | Type | Severity | Profile | Status") - fmt.Println("------------+-------------------+----------+------------+--------") - fmt.Println("alert-001 | account_takeover | high | profile-123| new") - fmt.Println("alert-002 | payment_fraud | medium | profile-456| investigating") - fmt.Println("alert-003 | sim_swap | critical | profile-789| blocked") - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "metrics", - Short: "Get fraud detection metrics", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("📊 Fraud Detection Metrics") - - metrics := map[string]any{ - "total_alerts": 1250, - "resolved_alerts": 1100, - "false_positives": 125, - "resolution_rate": "88.0%", - "false_positive_rate": "10.0%", - } - - data, _ := json.MarshalIndent(metrics, "", " ") - fmt.Println(string(data)) - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "patterns", - Short: "Show detected fraud patterns", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("🔍 Detected Fraud Patterns") - - patterns := []map[string]string{ - {"name": "Velocity Attack", "frequency": "high", "mitigation": "Rate limiting"}, - {"name": "Account Enumeration", "frequency": "medium", "mitigation": "CAPTCHA"}, - {"name": "SIM Swap Attack", "frequency": "low", "mitigation": "Multi-factor verification"}, - } - - for _, p := range patterns { - fmt.Printf("• %s (Frequency: %s)\n Mitigation: %s\n", p["name"], p["frequency"], p["mitigation"]) - } - }, - }) - - cmd.PersistentFlags().String("severity", "all", "Severity filter (low, medium, high, critical, all)") - cmd.PersistentFlags().String("type", "", "Fraud type filter") - - return cmd -} - -func newMarketCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "market", - Short: "Market analytics commands", - } - - cmd.AddCommand(&cobra.Command{ - Use: "metrics", - Short: "Get market penetration metrics", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("📈 Market Analytics") - - metrics := map[string]any{ - "total_market_size": "5.5B", - "our_subscribers": 150000, - "market_share": "0.0027%", - "growth_rate": "12.5%", - } - - data, _ := json.MarshalIndent(metrics, "", " ") - fmt.Println(string(data)) - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "competitors", - Short: "Show competitor analysis", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("🛡️ Competitor Analysis") - - fmt.Println("Competitor | Market Share | Threat Level") - fmt.Println("-----------+--------------+-------------") - fmt.Println("AT&T | 35.5% | high") - fmt.Println("Verizon | 32.0% | high") - fmt.Println("T-Mobile | 21.3% | medium") - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "opportunities", - Short: "Show market opportunities", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("💡 Market Opportunities") - - fmt.Println("Opportunity | Country | Potential Subs | Expected ROI") - fmt.Println("-----------------+---------+----------------+-------------") - fmt.Println("5G Migration | US | 50M | 25%") - fmt.Println("IoT Services | UK | 20M | 30%") - fmt.Println("Enterprise 5G | DE | 15M | 20%") - }, - }) - - return cmd -} - -func newPricingCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "pricing", - Short: "Pricing optimization commands", - } - - cmd.AddCommand(&cobra.Command{ - Use: "metrics", - Short: "Get pricing metrics", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("💰 Pricing Analytics") - - metrics := map[string]any{ - "total_revenue": "$4.5M", - "arpu": "$30.00", - "price_elasticity": -1.2, - "competitive_index": 75.0, - "optimization_roi": "15.5%", - } - - data, _ := json.MarshalIndent(metrics, "", " ") - fmt.Println(string(data)) - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "optimize [rate-plan-id]", - Short: "Optimize pricing for a rate plan", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - ratePlanID := args[0] - strategy, _ := cmd.Flags().GetString("strategy") - - fmt.Printf("🎯 Optimizing pricing for %s (Strategy: %s)\n\n", ratePlanID, strategy) - - result := map[string]any{ - "rate_plan_id": ratePlanID, - "current_price": "$29.99", - "optimal_price": "$32.99", - "price_change": "+10%", - "expected_revenue": "$165,000", - "confidence": "85%", - } - - data, _ := json.MarshalIndent(result, "", " ") - fmt.Println(string(data)) - }, - }) - - cmd.PersistentFlags().String("strategy", "revenue_maximization", "Optimization strategy") - - return cmd -} - -func newMaintenanceCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "maintenance", - Short: "Predictive maintenance commands", - } - - cmd.AddCommand(&cobra.Command{ - Use: "metrics", - Short: "Get maintenance metrics", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("🔧 Maintenance Metrics") - - metrics := map[string]any{ - "total_assets": 1250, - "healthy_assets": 1180, - "at_risk": 70, - "uptime": "99.95%", - "mttf": "8760 hours", - "mttr": "4 hours", - } - - data, _ := json.MarshalIndent(metrics, "", " ") - fmt.Println(string(data)) - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "assets", - Short: "List assets health status", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("🖥️ Assets Health") - - fmt.Println("Asset ID | Name | Type | Health | Status") - fmt.Println("-----------+------------------+----------+--------+--------") - fmt.Println("server-1 | Web Server 1 | server | 85% | healthy") - fmt.Println("server-2 | Web Server 2 | server | 92% | healthy") - fmt.Println("db-1 | Primary Database | database | 78% | warning") - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "predict [asset-id]", - Short: "Predict failure for an asset", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - assetID := args[0] - fmt.Printf("🔮 Failure Prediction for %s\n\n", assetID) - - prediction := map[string]any{ - "asset_id": assetID, - "failure_probability": "15%", - "predicted_failure": "2026-09-01", - "confidence": "82.5%", - "risk_factors": []string{"Age", "Error rate increase"}, - "recommendations": []string{"Schedule maintenance", "Monitor closely"}, - } - - data, _ := json.MarshalIndent(prediction, "", " ") - fmt.Println(string(data)) - }, - }) - - return cmd -} - -func init() { - // Suppress unused import error - _ = os.Stdout -} diff --git a/apps/web-dashboard/src/app/analytics/page.tsx b/apps/web-dashboard/src/app/analytics/page.tsx deleted file mode 100644 index 9070945..0000000 --- a/apps/web-dashboard/src/app/analytics/page.tsx +++ /dev/null @@ -1,341 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; -import { Button } from "@/components/ui/button"; -import { BarChart3, TrendingUp, TrendingDown, AlertCircle } from "lucide-react"; -import { apiClient, ChurnMetrics, MarketMetrics, PricingMetrics, MaintenanceMetrics } from "@/lib/api-client"; - -export default function AnalyticsPage() { - const [metrics, setMetrics] = useState<{ - churn?: ChurnMetrics; - market?: MarketMetrics; - maintenance?: MaintenanceMetrics; - pricing?: PricingMetrics; - } | null>(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchMetrics = async () => { - try { - setLoading(true); - setError(null); - - // Fetch all metrics in parallel - const [churnResponse, marketResponse, maintenanceResponse, pricingResponse] = await Promise.all([ - apiClient.getChurnMetrics(), - apiClient.getMarketMetrics(), - apiClient.getMaintenanceMetrics(), - apiClient.getPricingMetrics(), - ]); - - if (churnResponse.error || marketResponse.error || maintenanceResponse.error || pricingResponse.error) { - setError('Failed to fetch some metrics'); - } - - setMetrics({ - churn: churnResponse.data, - market: marketResponse.data, - maintenance: maintenanceResponse.data, - pricing: pricingResponse.data, - }); - } catch (err) { - console.error('Failed to fetch analytics metrics:', err); - setError('Failed to load analytics data'); - } finally { - setLoading(false); - } - }; - - fetchMetrics(); - }, []); - - if (loading) { - return ( -
-
-
- ); - } - - if (error) { - return ( -
-
- -

Error Loading Analytics

-

{error}

- -
-
- ); - } - - return ( -
-
-

Analytics Dashboard

- -
- - - - Overview - Churn Analysis - Market Analytics - Maintenance - Pricing - - - -
- - - Total Subscribers - - - -
{metrics?.churn?.totalSubscribers?.toLocaleString() || 'N/A'}
-

+12.5% from last month

-
-
- - - Churn Rate - - - -
{metrics?.churn?.churnRate ? `${metrics.churn.churnRate}%` : 'N/A'}
-

-0.3% from last month

-
-
- - - Market Share - - - -
{metrics?.market?.marketShare ? `${(metrics.market.marketShare * 100).toFixed(3)}%` : 'N/A'}
-

+0.1% from last month

-
-
- - - System Uptime - - - -
{metrics?.maintenance?.uptime ? `${metrics.maintenance.uptime}%` : 'N/A'}
-

Excellent

-
-
-
-
- - -
- - - Churn Metrics - Customer churn analysis for the current period - - -
- Total Subscribers - {metrics?.churn?.totalSubscribers?.toLocaleString() || 'N/A'} -
-
- Churned Subscribers - {metrics?.churn?.churnedSubscribers?.toLocaleString() || 'N/A'} -
-
- Monthly Churn Rate - {metrics?.churn?.monthlyChurnRate ? `${metrics.churn.monthlyChurnRate}%` : 'N/A'} -
-
- Average Tenure - {metrics?.churn?.averageTenureDays ? `${metrics.churn.averageTenureDays} days` : 'N/A'} -
-
-
- - - Risk Distribution - Customers at risk of churn - - - {Object.entries(metrics?.churn?.riskDistribution || {}).map(([level, count]) => ( -
-
- {level} - {count?.toLocaleString() || '0'} -
- -
- ))} -
-
-
-
- - -
- - - Market Metrics - Market penetration and growth - - -
- Total Market Size - {metrics?.market?.totalMarketSize ? `${(metrics.market.totalMarketSize / 1000000000).toFixed(1)}B` : 'N/A'} -
-
- Our Subscribers - {metrics?.market?.ourSubscribers?.toLocaleString() || 'N/A'} -
-
- Market Share - {metrics?.market?.marketShare ? `${(metrics.market.marketShare * 100).toFixed(3)}%` : 'N/A'} -
-
- Growth Rate - {metrics?.market?.growthRate ? `+${metrics.market.growthRate}%` : 'N/A'} -
-
-
- - - Market by Country - Performance across regions - - - {Object.entries(metrics?.market?.byCountry || {}).map(([country, data]: [string, any]) => ( -
-
- {country} - {data?.penetration ? `${(data.penetration * 100).toFixed(2)}%` : 'N/A'} -
- -
- ))} -
-
-
-
- - -
- - - System Health - Infrastructure maintenance metrics - - -
- Total Assets - {metrics?.maintenance?.totalAssets || 'N/A'} -
-
- Healthy Assets - {metrics?.maintenance?.healthyAssets || 'N/A'} -
-
- Assets Needing Attention - {metrics?.maintenance?.assetsNeedingAttention || 'N/A'} -
-
- System Uptime - {metrics?.maintenance?.uptime ? `${metrics.maintenance.uptime}%` : 'N/A'} -
-
-
- - - Performance Metrics - Mean time metrics - - -
- Mean Time To Failure - {metrics?.maintenance?.meanTimeToFailure ? `${(metrics.maintenance.meanTimeToFailure / 24 / 365).toFixed(1)} years` : 'N/A'} -
-
- Mean Time To Repair - {metrics?.maintenance?.meanTimeToRepair ? `${metrics.maintenance.meanTimeToRepair} hours` : 'N/A'} -
-
- Asset Health Score - Excellent -
-
-
-
-
- - -
- - - Revenue Metrics - Pricing and revenue analysis - - -
- Total Revenue - {metrics?.pricing?.totalRevenue ? `${(metrics.pricing.totalRevenue / 1000000).toFixed(1)}M` : 'N/A'} -
-
- ARPU - {metrics?.pricing?.arpu ? `$${metrics.pricing.arpu}` : 'N/A'} -
-
- Price Elasticity - {metrics?.pricing?.priceElasticity || 'N/A'} -
-
- Competitive Index - {metrics?.pricing?.competitiveIndex || 'N/A'} -
-
-
- - - Optimization - Pricing optimization metrics - - -
- Optimization ROI - {metrics?.pricing?.optimizationRoi ? `+${metrics.pricing.optimizationRoi}%` : 'N/A'} -
-
- Price Elasticity - {metrics?.pricing?.priceElasticity || 'N/A'} -
- -
-
-
-
-
-
- ); -} diff --git a/apps/web-dashboard/src/app/churn/page.tsx b/apps/web-dashboard/src/app/churn/page.tsx deleted file mode 100644 index 29f0017..0000000 --- a/apps/web-dashboard/src/app/churn/page.tsx +++ /dev/null @@ -1,272 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; -import { Button } from "@/components/ui/button"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { TrendingDown, AlertTriangle, Users, Target } from "lucide-react"; -import { apiClient, ChurnMetrics, ChurnPrediction } from "@/lib/api-client"; - -export default function ChurnPage() { - const [predictions, setPredictions] = useState([]); - const [metrics, setMetrics] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedRiskLevel, setSelectedRiskLevel] = useState("all"); - - useEffect(() => { - const fetchData = async () => { - try { - setLoading(true); - setError(null); - - // Fetch churn metrics and predictions in parallel - const [metricsResponse, predictionsResponse] = await Promise.all([ - apiClient.getChurnMetrics(), - apiClient.getChurnPredictions(selectedRiskLevel === "all" ? undefined : selectedRiskLevel, 100), - ]); - - if (metricsResponse.error || predictionsResponse.error) { - setError('Failed to fetch some churn data'); - } - - setMetrics(metricsResponse.data || null); - setPredictions(predictionsResponse.data || []); - } catch (err) { - console.error('Failed to fetch churn data:', err); - setError('Failed to load churn analysis data'); - } finally { - setLoading(false); - } - }; - - fetchData(); - }, [selectedRiskLevel]); - - const filteredPredictions = predictions.filter(p => - selectedRiskLevel === "all" || p.riskLevel === selectedRiskLevel - ); - - if (loading) { - return ( -
-
-
- ); - } - - if (error) { - return ( -
-
- -

Error Loading Churn Analysis

-

{error}

- -
-
- ); - } - - const getRiskBadgeVariant = (level: string) => { - switch (level) { - case "critical": return "destructive"; - case "high": return "destructive"; - case "medium": return "secondary"; - case "low": return "outline"; - default: return "secondary"; - } - }; - - const getRiskColor = (level: string) => { - switch (level) { - case "critical": return "text-red-600"; - case "high": return "text-orange-600"; - case "medium": return "text-yellow-600"; - case "low": return "text-green-600"; - default: return "text-gray-600"; - } - }; - - if (loading) { - return ( -
-
-
- ); - } - - return ( -
-
-

- - Churn Analysis -

- -
- - {/* Metrics Overview */} -
- - - Total Subscribers - - - -
{metrics?.totalSubscribers.toLocaleString()}
-

Active customers

-
-
- - - Churn Rate - - - -
{metrics?.churnRate}%
-

Monthly churn

-
-
- - - At Risk Customers - - - -
7,500
-

High & critical risk

-
-
- - - Avg Tenure - - - -
{metrics?.averageTenureDays} days
-

Customer lifetime

-
-
-
- - {/* Risk Distribution */} - - - Risk Distribution - Customer churn risk levels across the subscriber base - - - {Object.entries(metrics?.riskDistribution || {}).map(([level, count]) => ( -
-
- {level} -
- {count?.toLocaleString()} - {level} -
-
- -
- ))} -
-
- - {/* Filter Controls */} -
- Filter by risk level: -
- {["all", "critical", "high", "medium", "low"].map((level) => ( - - ))} -
-
- - {/* At-Risk Customers Table */} - - - At-Risk Customers - Customers with elevated churn risk requiring attention - - - - - - Profile ID - Risk Level - Risk Score - Predicted Churn - Key Reasons - Actions - - - - {filteredPredictions.map((prediction) => ( - - {prediction.profileId} - - - {prediction.riskLevel} - - - - - {prediction.riskScore}% - - - {prediction.predictedChurnDate || "N/A"} - -
-

- {prediction.reasons.join(", ")} -

-
-
- - - -
- ))} -
-
-
-
- - {/* Customer Retention Recommendations */} -
- - - - Critical Risk: 1,500 customers at immediate risk of churn. Recommend immediate outreach with personalized retention offers. - - - - - - Proactive Strategy: Focus on high-risk segment with targeted campaigns to reduce churn by 25% this quarter. - - -
-
- ); -} diff --git a/apps/web-dashboard/src/app/fraud/page.tsx b/apps/web-dashboard/src/app/fraud/page.tsx deleted file mode 100644 index a9afb67..0000000 --- a/apps/web-dashboard/src/app/fraud/page.tsx +++ /dev/null @@ -1,305 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { AlertTriangle, Shield, Eye, CheckCircle, XCircle, Clock } from "lucide-react"; -import { apiClient, FraudAlert, FraudMetrics } from "@/lib/api-client"; - -export default function FraudPage() { - const [alerts, setAlerts] = useState([]); - const [metrics, setMetrics] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedSeverity, setSelectedSeverity] = useState("all"); - - useEffect(() => { - const fetchData = async () => { - try { - setLoading(true); - setError(null); - - // Fetch fraud metrics and alerts in parallel - const [metricsResponse, alertsResponse] = await Promise.all([ - apiClient.getFraudMetrics(), - apiClient.getFraudAlerts(selectedSeverity === "all" ? undefined : selectedSeverity), - ]); - - if (metricsResponse.error || alertsResponse.error) { - setError('Failed to fetch some fraud data'); - } - - setMetrics(metricsResponse.data || null); - setAlerts(alertsResponse.data || []); - } catch (err) { - console.error('Failed to fetch fraud data:', err); - setError('Failed to load fraud detection data'); - } finally { - setLoading(false); - } - }; - - fetchData(); - }, [selectedSeverity]); - - const filteredAlerts = alerts.filter(a => - selectedSeverity === "all" || a.severity === selectedSeverity - ); - - if (loading) { - return ( -
-
-
- ); - } - - if (error) { - return ( -
-
- -

Error Loading Fraud Detection

-

{error}

- -
-
- ); - } - - const getSeverityBadgeVariant = (severity: string) => { - switch (severity) { - case "critical": return "destructive"; - case "high": return "destructive"; - case "medium": return "secondary"; - case "low": return "outline"; - default: return "secondary"; - } - }; - - const getStatusIcon = (status: string) => { - switch (status) { - case "new": return ; - case "investigating": return ; - case "resolved": return ; - case "blocked": return ; - default: return ; - } - }; - - const getStatusBadgeVariant = (status: string) => { - switch (status) { - case "new": return "outline"; - case "investigating": return "secondary"; - case "resolved": return "default"; - case "blocked": return "destructive"; - default: return "outline"; - } - }; - - if (loading) { - return ( -
-
-
- ); - } - - return ( -
-
-

- - Fraud Detection -

- -
- - {/* Metrics Overview */} -
- - - Total Alerts - - - -
{metrics?.totalAlerts.toLocaleString()}
-

Last 30 days

-
-
- - - Resolution Rate - - - -
{metrics?.resolutionRate}%
-

Alerts resolved

-
-
- - - False Positives - - - -
{metrics?.falsePositiveRate}%
-

Accuracy improving

-
-
- - - Critical Alerts - - - -
{metrics?.bySeverity.critical}
-

Immediate action needed

-
-
-
- - {/* Alert Types Distribution */} -
- - - Alerts by Type - Distribution of fraud detection alerts - - - {Object.entries(metrics?.byType || {}).map(([type, count]) => ( -
- {type.replace("_", " ")} - {count?.toLocaleString()} -
- ))} -
-
- - - Alerts by Severity - Risk level distribution - - - {Object.entries(metrics?.bySeverity || {}).map(([severity, count]) => ( -
- {severity} - {severity} -
- ))} -
-
-
- - {/* Filter Controls */} -
- Filter by severity: -
- {["all", "critical", "high", "medium", "low"].map((severity) => ( - - ))} -
-
- - {/* Recent Fraud Alerts */} - - - Recent Fraud Alerts - Latest security alerts requiring attention - - - - - - Alert ID - Type - Severity - Profile - Risk Score - Description - Status - Actions - - - - {filteredAlerts.map((alert) => ( - - {alert.id} - - - {alert.type.replace("_", " ")} - - - - - {alert.severity} - - - {alert.profileId} - - = 90 ? 'text-red-600' : - alert.riskScore >= 75 ? 'text-orange-600' : - alert.riskScore >= 50 ? 'text-yellow-600' : 'text-green-600' - }`}> - {alert.riskScore}% - - - -
-

{alert.description}

-
-
- -
- {getStatusIcon(alert.status)} - - {alert.status} - -
-
- - - -
- ))} -
-
-
-
- - {/* Security Recommendations */} -
- - - - Critical Alert: Account takeover attempt detected. Immediate action required to secure the account and prevent unauthorized access. - - - - - - Security Tip: Consider implementing multi-factor authentication for high-risk accounts to prevent account takeover attempts. - - -
-
- ); -} diff --git a/apps/web-dashboard/src/app/maintenance/page.tsx b/apps/web-dashboard/src/app/maintenance/page.tsx deleted file mode 100644 index c926c79..0000000 --- a/apps/web-dashboard/src/app/maintenance/page.tsx +++ /dev/null @@ -1,365 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; -import { Button } from "@/components/ui/button"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { AlertTriangle, Wrench, Clock, CheckCircle, XCircle, Server, Database, Wifi } from "lucide-react"; -import { apiClient, MaintenanceMetrics } from "@/lib/api-client"; - -interface Asset { - id: string; - name: string; - type: string; - health: number; - status: string; - lastMaintenance: string; - nextMaintenance?: string; - predictedFailure?: string; - riskFactors: string[]; -} - -export default function MaintenancePage() { - const [assets, setAssets] = useState([]); - const [metrics, setMetrics] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - // Simulate API calls - setTimeout(() => { - setAssets([ - { - id: "server-1", - name: "Web Server 1", - type: "server", - health: 85, - status: "healthy", - lastMaintenance: "2026-04-15T10:30:00Z", - nextMaintenance: "2026-06-15T10:30:00Z", - riskFactors: [], - }, - { - id: "server-2", - name: "Web Server 2", - type: "server", - health: 92, - status: "healthy", - lastMaintenance: "2026-04-20T14:15:00Z", - nextMaintenance: "2026-06-20T14:15:00Z", - riskFactors: [], - }, - { - id: "db-1", - name: "Primary Database", - type: "database", - health: 78, - status: "warning", - lastMaintenance: "2026-03-10T09:00:00Z", - nextMaintenance: "2026-05-10T09:00:00Z", - riskFactors: ["High CPU usage", "Memory pressure"], - }, - { - id: "router-1", - name: "Core Router", - type: "network", - health: 65, - status: "warning", - lastMaintenance: "2026-02-28T16:45:00Z", - nextMaintenance: "2026-04-28T16:45:00Z", - predictedFailure: "2026-09-01T00:00:00Z", - riskFactors: ["Age", "Error rate increase", "Temperature spikes"], - }, - ]); - - setMetrics({ - totalAssets: 1250, - healthyAssets: 1180, - assetsNeedingAttention: 70, - uptime: 99.95, - meanTimeToFailure: 8760, - meanTimeToRepair: 4, - byType: { - server: 450, - database: 125, - network: 300, - storage: 375, - }, - byStatus: { - healthy: 1180, - warning: 60, - critical: 10, - }, - }); - setLoading(false); - }, 1000); - }, []); - - const getHealthColor = (health: number) => { - if (health >= 90) return "text-green-600"; - if (health >= 75) return "text-yellow-600"; - if (health >= 60) return "text-orange-600"; - return "text-red-600"; - }; - - const getStatusBadgeVariant = (status: string) => { - switch (status) { - case "healthy": return "default"; - case "warning": return "secondary"; - case "critical": return "destructive"; - default: return "outline"; - } - }; - - const getStatusIcon = (status: string) => { - switch (status) { - case "healthy": return ; - case "warning": return ; - case "critical": return ; - default: return ; - } - }; - - const getAssetIcon = (type: string) => { - switch (type) { - case "server": return ; - case "database": return ; - case "network": return ; - default: return ; - } - }; - - if (loading) { - return ( -
-
-
- ); - } - - return ( -
-
-

- - Predictive Maintenance -

- -
- - {/* Maintenance Metrics */} -
- - - Total Assets - - - -
{metrics?.totalAssets.toLocaleString()}
-

Infrastructure assets

-
-
- - - Healthy Assets - - - -
{metrics?.healthyAssets.toLocaleString()}
-

Operating normally

-
-
- - - System Uptime - - - -
{metrics?.uptime}%
-

Excellent availability

-
-
- - - MTTR - - - -
{metrics?.meanTimeToRepair}h
-

Mean time to repair

-
-
-
- - {/* Asset Distribution */} -
- - - Assets by Type - Infrastructure asset distribution - - - {Object.entries(metrics?.byType || {}).map(([type, count]) => ( -
-
- {getAssetIcon(type)} - {type} -
- {count?.toLocaleString()} -
- ))} -
-
- - - Assets by Status - Current health status distribution - - - {Object.entries(metrics?.byStatus || {}).map(([status, count]) => ( -
-
- {getStatusIcon(status)} - {status} -
- {count?.toLocaleString()} -
- ))} -
-
-
- - {/* Assets Health Table */} - - - Asset Health Status - Real-time health monitoring of critical infrastructure assets - - - - - - Asset - Type - Health - Status - Last Maintenance - Next Maintenance - Predicted Failure - Actions - - - - {assets.map((asset) => ( - - {asset.name} - -
- {getAssetIcon(asset.type)} - {asset.type} -
-
- -
- - {asset.health}% - - -
-
- -
- {getStatusIcon(asset.status)} - - {asset.status} - -
-
- - {new Date(asset.lastMaintenance).toLocaleDateString()} - - - {asset.nextMaintenance ? new Date(asset.nextMaintenance).toLocaleDateString() : "N/A"} - - - {asset.predictedFailure ? ( - - {new Date(asset.predictedFailure).toLocaleDateString()} - - ) : ( - N/A - )} - - - - -
- ))} -
-
-
-
- - {/* Risk Factors and Predictions */} -
- {assets.filter(a => a.riskFactors.length > 0).map((asset) => ( - - - - {asset.name} - {asset.status} - - - -
-

Risk Factors

-
    - {asset.riskFactors.map((factor, idx) => ( -
  • - - {factor} -
  • - ))} -
-
- {asset.predictedFailure && ( -
-

Failure Prediction

-

- Predicted failure on {new Date(asset.predictedFailure).toLocaleDateString()} with 82.5% confidence -

-
- )} - -
-
- ))} -
- - {/* Maintenance Recommendations */} -
- - - - Critical Maintenance Required: Core Router shows 65% health with predicted failure in September. Schedule immediate maintenance to prevent service disruption. - - - - - - Preventive Maintenance: Primary Database showing warning signs. Schedule maintenance within 30 days to maintain optimal performance. - - -
-
- ); -} diff --git a/apps/web-dashboard/src/app/pricing/page.tsx b/apps/web-dashboard/src/app/pricing/page.tsx deleted file mode 100644 index 40a2804..0000000 --- a/apps/web-dashboard/src/app/pricing/page.tsx +++ /dev/null @@ -1,221 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; -import { Button } from "@/components/ui/button"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { TrendingUp, TrendingDown, DollarSign, Target, Zap, BarChart3, AlertTriangle } from "lucide-react"; -import { apiClient, PricingMetrics, PricingOptimizationResult } from "@/lib/api-client"; - -export default function PricingPage() { - const [metrics, setMetrics] = useState(null); - const [optimizations, setOptimizations] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedStrategy, setSelectedStrategy] = useState("revenue_maximization"); - - useEffect(() => { - const fetchData = async () => { - try { - setLoading(true); - setError(null); - - const metricsResponse = await apiClient.getPricingMetrics(); - - if (metricsResponse.error) { - setError('Failed to fetch pricing metrics'); - } - - setMetrics(metricsResponse.data || null); - } catch (err) { - console.error('Failed to fetch pricing data:', err); - setError('Failed to load pricing optimization data'); - } finally { - setLoading(false); - } - }; - - fetchData(); - }, []); - - const handleOptimizePricing = async () => { - try { - setLoading(true); - const response = await apiClient.optimizePricing(['plan-1', 'plan-2', 'plan-3'], selectedStrategy); - - if (response.error) { - setError('Failed to optimize pricing'); - } else { - setOptimizations(response.data || []); - } - } catch (err) { - console.error('Failed to optimize pricing:', err); - setError('Failed to optimize pricing'); - } finally { - setLoading(false); - } - }; - - const getStrategyBadgeVariant = (strategy: string) => { - switch (strategy) { - case "revenue_maximization": return "default"; - case "market_share": return "secondary"; - case "profit_margin": return "outline"; - case "competitive": return "destructive"; - default: return "secondary"; - } - }; - - const getChangeIcon = (change: number) => { - return change >= 0 ? : ; - }; - - const getChangeColor = (change: number) => { - return change >= 0 ? "text-green-600" : "text-red-600"; - }; - - const getConfidenceColor = (confidence: number) => { - if (confidence >= 85) return "text-green-600"; - if (confidence >= 70) return "text-yellow-600"; - return "text-red-600"; - }; - - if (loading) { - return ( -
-
-
- ); - } - - if (error) { - return ( -
-
- -

Error Loading Pricing Optimization

-

{error}

- -
-
- ); - } - - return ( -
-
-
-

Pricing Optimization

-

Optimize pricing strategies for maximum revenue and market share

-
-
- - -
-
- - {/* Metrics Cards */} -
- - - Total Revenue - - - -
${metrics?.totalRevenue ? (metrics.totalRevenue / 1000000).toFixed(1) : '0'}M
-
-
- - - ARPU - - - -
${metrics?.arpu || '0'}
-
-
- - - Price Elasticity - - - -
{metrics?.priceElasticity || '0'}
-
-
- - - Optimization ROI - - - -
+{metrics?.optimizationRoi || '0'}%
-
-
-
- - {/* Optimization Results */} - {optimizations.length > 0 && ( - - - Optimization Results - Recommended pricing adjustments based on {selectedStrategy} strategy - - - - - - Rate Plan - Current Price - Optimal Price - Change - Expected Revenue - Confidence - - - - {optimizations.map((opt, index) => ( - - {opt.ratePlanId} - ${opt.currentPrice.toFixed(2)} - ${opt.optimalPrice.toFixed(2)} - -
- {getChangeIcon(opt.priceChangePct)} - - {opt.priceChangePct > 0 ? '+' : ''}{opt.priceChangePct.toFixed(1)}% - -
-
- ${(opt.expectedRevenue / 1000000).toFixed(2)}M - - = 80 ? "default" : "secondary"}> - {opt.confidence.toFixed(0)}% - - -
- ))} -
-
-
-
- )} -
- ); -} diff --git a/apps/web-dashboard/src/components/sidebar.tsx b/apps/web-dashboard/src/components/sidebar.tsx index fc4da09..892de9c 100644 --- a/apps/web-dashboard/src/components/sidebar.tsx +++ b/apps/web-dashboard/src/components/sidebar.tsx @@ -11,11 +11,6 @@ import { Settings, Radio, HeartPulse, - TrendingDown, - AlertTriangle, - BarChart3, - DollarSign, - Wrench, } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -25,11 +20,6 @@ const navItems = [ { href: "/usage", label: "Usage & Billing", icon: Activity }, { href: "/payments", label: "Payments", icon: CreditCard }, { href: "/esim", label: "eSIM Profiles", icon: Radio }, - { href: "/analytics", label: "Analytics", icon: BarChart3 }, - { href: "/churn", label: "Churn Analysis", icon: TrendingDown }, - { href: "/fraud", label: "Fraud Detection", icon: AlertTriangle }, - { href: "/pricing", label: "Pricing", icon: DollarSign }, - { href: "/maintenance", label: "Maintenance", icon: Wrench }, { href: "/health", label: "System Health", icon: HeartPulse }, { href: "/chaos", label: "Chaos Engineering", icon: Shield }, { href: "/settings", label: "Settings", icon: Settings }, diff --git a/apps/web-dashboard/src/components/ui/progress.tsx b/apps/web-dashboard/src/components/ui/progress.tsx deleted file mode 100644 index 07bf0b2..0000000 --- a/apps/web-dashboard/src/components/ui/progress.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client" - -import * as React from "react" -import { cn } from "@/lib/utils" - -interface ProgressProps { - value?: number - max?: number - className?: string -} - -const Progress = React.forwardRef( - ({ value = 0, max = 100, className }, ref) => { - const percentage = Math.min(Math.max((value / max) * 100, 0), 100) - - return ( -
-
-
- ) - } -) -Progress.displayName = "Progress" - -export { Progress } diff --git a/apps/web-dashboard/src/components/ui/table.tsx b/apps/web-dashboard/src/components/ui/table.tsx deleted file mode 100644 index 245eb00..0000000 --- a/apps/web-dashboard/src/components/ui/table.tsx +++ /dev/null @@ -1,118 +0,0 @@ -"use client" - -import * as React from "react" -import { cn } from "@/lib/utils" - -const Table = React.forwardRef< - HTMLTableElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
- - -)) -Table.displayName = "Table" - -const TableHeader = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)) -TableHeader.displayName = "TableHeader" - -const TableBody = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)) -TableBody.displayName = "TableBody" - -const TableFooter = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - tr]:last:border-b-0", - className - )} - {...props} - /> -)) -TableFooter.displayName = "TableFooter" - -const TableRow = React.forwardRef< - HTMLTableRowElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)) -TableRow.displayName = "TableRow" - -const TableHead = React.forwardRef< - HTMLTableCellElement, - React.ThHTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -TableHead.displayName = "TableHead" - -const TableCell = React.forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes ->(({ className, ...props }, ref) => ( - -)) -TableCell.displayName = "TableCell" - -const TableCaption = React.forwardRef< - HTMLTableCaptionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -TableCaption.displayName = "TableCaption" - -export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -} diff --git a/apps/web-dashboard/src/components/ui/tabs.tsx b/apps/web-dashboard/src/components/ui/tabs.tsx deleted file mode 100644 index 958861e..0000000 --- a/apps/web-dashboard/src/components/ui/tabs.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client" - -import * as React from "react" -import { cn } from "@/lib/utils" - -interface TabsProps { - defaultValue?: string - value?: string - onValueChange?: (value: string) => void - children: React.ReactNode - className?: string -} - -interface TabsListProps { - children: React.ReactNode - className?: string -} - -interface TabsTriggerProps { - value: string - children: React.ReactNode - className?: string - disabled?: boolean -} - -interface TabsContentProps { - value: string - children: React.ReactNode - className?: string -} - -const TabsContext = React.createContext<{ - value?: string - onValueChange?: (value: string) => void -}>({}) - -const Tabs: React.FC = ({ defaultValue, value, onValueChange, children, className }) => { - const [internalValue, setInternalValue] = React.useState(defaultValue) - const currentValue = value ?? internalValue - const handleValueChange = onValueChange ?? setInternalValue - - return ( - -
- {children} -
-
- ) -} - -const TabsList: React.FC = ({ children, className }) => { - return ( -
- {children} -
- ) -} - -const TabsTrigger: React.FC = ({ value, children, className, disabled }) => { - const { value: currentValue, onValueChange } = React.useContext(TabsContext) - const isActive = currentValue === value - - return ( - - ) -} - -const TabsContent: React.FC = ({ value, children, className }) => { - const { value: currentValue } = React.useContext(TabsContext) - const isActive = currentValue === value - - if (!isActive) { - return null - } - - return ( -
- {children} -
- ) -} - -export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/apps/web-dashboard/src/lib/api-client.ts b/apps/web-dashboard/src/lib/api-client.ts deleted file mode 100644 index e0f461e..0000000 --- a/apps/web-dashboard/src/lib/api-client.ts +++ /dev/null @@ -1,261 +0,0 @@ -const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080'; - -export interface ApiResponse { - data?: T; - error?: string; - message?: string; -} - -export interface ChurnMetrics { - totalSubscribers: number; - churnedSubscribers: number; - churnRate: number; - monthlyChurnRate: number; - annualChurnRate: number; - averageTenureDays: number; - riskDistribution: { - low: number; - medium: number; - high: number; - critical: number; - }; -} - -export interface ChurnPrediction { - profileId: string; - riskLevel: string; - riskScore: number; - predictedChurnDate?: string; - reasons: string[]; - recommendations: string[]; - lastUpdated: string; -} - -export interface FraudAlert { - id: string; - type: string; - severity: string; - profileId: string; - description: string; - riskScore: number; - evidence: string[]; - ipAddress: string; - timestamp: string; - status: string; - actionsTaken: string[]; -} - -export interface FraudMetrics { - totalAlerts: number; - resolvedAlerts: number; - falsePositives: number; - resolutionRate: number; - falsePositiveRate: number; - byType: Record; - bySeverity: Record; -} - -export interface MarketMetrics { - totalMarketSize: number; - ourSubscribers: number; - marketShare: number; - growthRate: number; - byCountry: Record; -} - -export interface PricingMetrics { - totalRevenue: number; - arpu: number; - priceElasticity: number; - competitiveIndex: number; - optimizationRoi: number; - byPlan: Record; - byRegion: Record; -} - -export interface PricingOptimizationResult { - ratePlanId: string; - strategy: string; - currentPrice: number; - optimalPrice: number; - priceChangePct: number; - expectedRevenue: number; - expectedDemand: number; - confidence: number; - reasoning: string[]; - risks: string[]; - recommendations: string[]; -} - -export interface MaintenanceMetrics { - totalAssets: number; - healthyAssets: number; - assetsNeedingAttention: number; - uptime: number; - meanTimeToFailure: number; - meanTimeToRepair: number; - byType: Record; - byStatus: Record; -} - -export interface Asset { - id: string; - name: string; - type: string; - health: number; - status: string; - lastMaintenance: string; - nextMaintenance?: string; - predictedFailure?: string; - riskFactors: string[]; -} - -class ApiClient { - private baseUrl: string; - private headers: Record; - - constructor() { - this.baseUrl = API_BASE_URL; - this.headers = { - 'Content-Type': 'application/json', - }; - - // Add auth token if available - const token = this.getAuthToken(); - if (token) { - this.headers['Authorization'] = `Bearer ${token}`; - } - } - - private getAuthToken(): string | null { - if (typeof window !== 'undefined') { - return localStorage.getItem('auth_token'); - } - return null; - } - - private async request( - endpoint: string, - options: RequestInit = {} - ): Promise> { - try { - const url = `${this.baseUrl}${endpoint}`; - const response = await fetch(url, { - ...options, - headers: { - ...this.headers, - ...options.headers, - }, - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`); - } - - const data = await response.json(); - return { data }; - } catch (error) { - console.error('API request failed:', error); - return { error: error instanceof Error ? error.message : 'Unknown error' }; - } - } - - // Analytics API - async getChurnMetrics(period: string = 'monthly'): Promise> { - return this.request(`/api/v1/analytics/churn/metrics?period=${period}`); - } - - async getChurnPredictions(riskLevel?: string, limit?: number): Promise> { - const params = new URLSearchParams(); - if (riskLevel) params.append('risk_level', riskLevel); - if (limit) params.append('limit', limit.toString()); - - return this.request(`/api/v1/analytics/churn/predictions?${params}`); - } - - async predictChurn(profileId: string): Promise> { - return this.request('/api/v1/analytics/churn/predict', { - method: 'POST', - body: JSON.stringify({ profile_id: profileId }), - }); - } - - // Fraud Detection API - async getFraudAlerts(severity?: string): Promise> { - const params = severity ? `?severity=${severity}` : ''; - return this.request(`/api/v1/security/fraud/alerts${params}`); - } - - async getFraudMetrics(period: string = 'monthly'): Promise> { - return this.request(`/api/v1/security/fraud/metrics?period=${period}`); - } - - async analyzeTransaction(transaction: Record): Promise> { - return this.request('/api/v1/security/fraud/analyze', { - method: 'POST', - body: JSON.stringify(transaction), - }); - } - - async updateFraudAlert(alertId: string, status: string, actions: string[]): Promise> { - return this.request(`/api/v1/security/fraud/alerts/${alertId}`, { - method: 'PUT', - body: JSON.stringify({ status, actions }), - }); - } - - // Market Analytics API - async getMarketMetrics(period: string = 'monthly'): Promise> { - return this.request(`/api/v1/analytics/market/metrics?period=${period}`); - } - - async getCompetitors(): Promise[]>> { - return this.request[]>('/api/v1/analytics/market/competitors'); - } - - async getMarketOpportunities(): Promise[]>> { - return this.request[]>('/api/v1/analytics/market/opportunities'); - } - - // Pricing API - async getPricingMetrics(period: string = 'monthly'): Promise> { - return this.request(`/api/v1/analytics/pricing/metrics?period=${period}`); - } - - async optimizePricing(ratePlanIds: string[], strategy: string): Promise> { - return this.request('/api/v1/analytics/pricing/optimize', { - method: 'POST', - body: JSON.stringify({ rate_plan_ids: ratePlanIds, strategy }), - }); - } - - async getPriceElasticity(): Promise>> { - return this.request>('/api/v1/analytics/pricing/elasticity'); - } - - // Maintenance API - async getMaintenanceMetrics(period: string = 'monthly'): Promise> { - return this.request(`/api/v1/analytics/maintenance/metrics?period=${period}`); - } - - async getAssets(): Promise> { - return this.request('/api/v1/analytics/maintenance/assets'); - } - - async getMaintenanceAlerts(): Promise[]>> { - return this.request[]>('/api/v1/analytics/maintenance/alerts'); - } - - async predictFailure(assetId: string): Promise>> { - return this.request>(`/api/v1/analytics/maintenance/predict/${assetId}`, { - method: 'POST', - }); - } -} - -export const apiClient = new ApiClient(); diff --git a/apps/web-dashboard/src/lib/apollo-client.ts b/apps/web-dashboard/src/lib/apollo-client.ts new file mode 100644 index 0000000..790e97c --- /dev/null +++ b/apps/web-dashboard/src/lib/apollo-client.ts @@ -0,0 +1,55 @@ +import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client'; +import { SetContextLink } from '@apollo/client/link/context'; + +// GraphQL API endpoint +const httpLink = new HttpLink({ + uri: process.env.NEXT_PUBLIC_API_SERVER_URL || 'http://localhost:8080/v1/graphql', +}); + +// Auth link to add authentication token to requests +const authLink = new SetContextLink((prevContext, operation) => { + // Get the authentication token from local storage if it exists + const token = localStorage.getItem('auth_token'); + + return { + ...prevContext, + headers: { + ...prevContext.headers, + authorization: token ? `Bearer ${token}` : '', + }, + }; +}); + +// Create Apollo Client instance +export const apolloClient = new ApolloClient({ + link: ApolloLink.from([authLink, httpLink]), + cache: new InMemoryCache(), + defaultOptions: { + watchQuery: { + fetchPolicy: 'cache-and-network', + }, + query: { + fetchPolicy: 'network-only', + }, + mutate: { + fetchPolicy: 'network-only', + }, + }, +}); + +// GraphQL queries and mutations can be defined here +// Example: +/* +import { gql } from '@apollo/client'; + +export const GET_SERVICES = gql` + query GetServices { + services { + id + name + status + health + } + } +`; +*/ diff --git a/deployments/helm/telecom-platform/values.yaml b/deployments/helm/telecom-platform/values.yaml index 5897b6d..8f817c3 100644 --- a/deployments/helm/telecom-platform/values.yaml +++ b/deployments/helm/telecom-platform/values.yaml @@ -9,50 +9,6 @@ global: storageClass: "" # Environment: dev, staging, prod environment: "dev" - # Platform version - version: "2.0.0" - -# Analytics configuration -analytics: - enabled: true - churn: - enabled: true - mlModel: "xgboost" - predictionInterval: "daily" - riskThresholds: - low: 25 - medium: 50 - high: 75 - fraud: - enabled: true - realTimeDetection: true - alertSeverityLevels: - - low - - medium - - high - - critical - patterns: - - account_takeover - - subscription_fraud - - payment_fraud - - usage_anomaly - - sim_swap - market: - enabled: true - competitorTracking: true - updateInterval: "weekly" - maintenance: - enabled: true - predictiveModel: "lstm" - alertThreshold: 0.7 - pricing: - enabled: true - optimizationStrategies: - - revenue_maximization - - market_share - - profit_margin - - competitive_pricing - - churn_reduction # Platform configuration platform: diff --git a/docs/sdk-usage.md b/docs/sdk-usage.md deleted file mode 100644 index 505c8bf..0000000 --- a/docs/sdk-usage.md +++ /dev/null @@ -1,547 +0,0 @@ -# TaaS Platform SDK Documentation - -Multi-language SDK documentation for integrating with the Telecom-as-a-Service Platform. - -## Table of Contents - -- [Overview](#overview) -- [Installation](#installation) -- [Authentication](#authentication) -- [Go SDK](#go-sdk) -- [Python SDK](#python-sdk) -- [TypeScript SDK](#typescript-sdk) -- [Analytics API](#analytics-api) -- [Security API](#security-api) -- [Currency & Billing API](#currency--billing-api) - ---- - -## Overview - -The TaaS Platform provides SDKs for multiple programming languages to simplify integration with platform services: - -| Language | Package | Status | -|----------|---------|--------| -| Go | `github.com/nutcas3/telecom-platform/sdk/go` | ✅ Stable | -| Python | `telecom-sdk` | ✅ Stable | -| TypeScript | `@taas/sdk` | ✅ Stable | -| Kotlin | `com.taas:sdk` | 🚧 Beta | -| Ruby | `taas-sdk` | 🚧 Beta | -| Swift | `TaaSSDK` | 🚧 Beta | -| Rust | `taas-sdk` | 🚧 Beta | -| Elixir | `taas_sdk` | 🚧 Beta | - ---- - -## Installation - -### Go - -```bash -go get github.com/nutcas3/telecom-platform/sdk/go -``` - -### Python - -```bash -pip install telecom-sdk -``` - -### TypeScript/JavaScript - -```bash -npm install @taas/sdk -# or -pnpm add @taas/sdk -``` - ---- - -## Authentication - -All SDKs use JWT-based authentication. Obtain an API key from the dashboard or use username/password authentication. - -### API Key Authentication - -```go -// Go -client := taas.NewClient(taas.WithAPIKey("your-api-key")) -``` - -```python -# Python -from telecom_sdk import TelecomClient -client = TelecomClient(api_key="your-api-key") -``` - -```typescript -// TypeScript -import { TelecomClient } from '@taas/sdk'; -const client = new TelecomClient({ apiKey: 'your-api-key' }); -``` - ---- - -## Go SDK - -### Basic Usage - -```go -package main - -import ( - "context" - "fmt" - "log" - - taas "github.com/nutcas3/telecom-platform/sdk/go" -) - -func main() { - // Initialize client - client := taas.NewClient( - taas.WithBaseURL("https://api.telecom.com"), - taas.WithAPIKey("your-api-key"), - ) - - // Create a subscriber - subscriber, err := client.Subscribers.Create(context.Background(), &taas.CreateSubscriberRequest{ - MSISDN: "+1234567890", - FirstName: "John", - LastName: "Doe", - Email: "john@example.com", - PlanID: 1, - }) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Created subscriber: %s\n", subscriber.ID) -} -``` - -### Analytics API - -```go -// Churn Prediction -analyticsAPI := taas.NewAnalyticsAPI(client.HTTPClient) - -// Predict churn for a profile -prediction, err := analyticsAPI.PredictChurn(ctx, "profile-123") -if err != nil { - log.Fatal(err) -} -fmt.Printf("Churn Risk: %s (Score: %.2f)\n", prediction.RiskLevel, prediction.RiskScore) - -// Get churn metrics -metrics, err := analyticsAPI.GetChurnMetrics(ctx, "monthly") -if err != nil { - log.Fatal(err) -} -fmt.Printf("Monthly Churn Rate: %.2f%%\n", metrics.MonthlyChurnRate) - -// Get at-risk customers -atRisk, err := analyticsAPI.GetAtRiskCustomers(ctx, taas.ChurnRiskHigh, 100) -if err != nil { - log.Fatal(err) -} -fmt.Printf("Found %d high-risk customers\n", len(atRisk)) - -// Market metrics -marketMetrics, err := analyticsAPI.GetMarketMetrics(ctx, "quarterly") -if err != nil { - log.Fatal(err) -} -fmt.Printf("Market Share: %.2f%%\n", marketMetrics.MarketShare) -``` - -### Security API - -```go -// Fraud Detection -securityAPI := taas.NewSecurityAPI(client.HTTPClient) - -// Analyze a transaction -alert, err := securityAPI.AnalyzeTransaction(ctx, map[string]interface{}{ - "profile_id": "profile-123", - "amount": 99.99, - "ip_address": "192.168.1.1", - "device_id": "device-456", - "transaction": "payment", -}) -if err != nil { - log.Fatal(err) -} -if alert != nil { - fmt.Printf("Fraud Alert: %s (Severity: %s)\n", alert.Type, alert.Severity) -} - -// Get fraud alerts -alerts, err := securityAPI.GetFraudAlerts(ctx, taas.FraudAlertFilter{ - Severity: taas.FraudSeverityHigh, - Status: "new", - Limit: 50, -}) -if err != nil { - log.Fatal(err) -} -fmt.Printf("Found %d fraud alerts\n", len(alerts)) - -// Get fraud metrics -fraudMetrics, err := securityAPI.GetFraudMetrics(ctx, "monthly") -if err != nil { - log.Fatal(err) -} -fmt.Printf("Resolution Rate: %.2f%%\n", fraudMetrics.ResolutionRate) -``` - ---- - -## Python SDK - -### Basic Usage - -```python -from telecom_sdk import TelecomClient -from telecom_sdk.types import CreateSubscriberRequest - -# Initialize client -client = TelecomClient( - base_url="https://api.telecom.com", - api_key="your-api-key" -) - -# Create a subscriber -subscriber = client.subscribers.create(CreateSubscriberRequest( - msisdn="+1234567890", - first_name="John", - last_name="Doe", - email="john@example.com", - plan_id=1 -)) -print(f"Created subscriber: {subscriber.id}") -``` - -### Analytics API - -```python -from telecom_sdk import TelecomClient -from telecom_sdk.types import ChurnRiskLevel - -client = TelecomClient(api_key="your-api-key") - -# Churn Prediction -prediction = client.analytics.predict_churn("profile-123") -print(f"Churn Risk: {prediction.risk_level} (Score: {prediction.risk_score:.2f})") - -# Get churn metrics -metrics = client.analytics.get_churn_metrics("monthly") -print(f"Monthly Churn Rate: {metrics.monthly_churn_rate:.2f}%") - -# Get at-risk customers -at_risk = client.analytics.get_at_risk_customers( - risk_level=ChurnRiskLevel.HIGH, - limit=100 -) -print(f"Found {len(at_risk)} high-risk customers") - -# Market metrics -market = client.analytics.get_market_metrics("quarterly") -print(f"Market Share: {market.market_share_pct:.2f}%") -``` - -### Security API - -```python -from telecom_sdk import TelecomClient -from telecom_sdk.types import FraudType, FraudSeverity, FraudAlertFilter - -client = TelecomClient(api_key="your-api-key") - -# Analyze transaction -alert = client.security.analyze_transaction({ - "profile_id": "profile-123", - "amount": 99.99, - "ip_address": "192.168.1.1", - "device_id": "device-456", - "transaction": "payment" -}) -if alert: - print(f"Fraud Alert: {alert.type} (Severity: {alert.severity})") - -# Get fraud alerts -alerts = client.security.get_fraud_alerts(FraudAlertFilter( - severity=FraudSeverity.HIGH, - status="new", - limit=50 -)) -print(f"Found {len(alerts)} fraud alerts") - -# Get fraud metrics -fraud_metrics = client.security.get_fraud_metrics("monthly") -print(f"Resolution Rate: {fraud_metrics.resolution_rate_pct:.2f}%") -``` - ---- - -## TypeScript SDK - -### Basic Usage - -```typescript -import { TelecomClient, CreateSubscriberRequest } from '@taas/sdk'; - -// Initialize client -const client = new TelecomClient({ - baseUrl: 'https://api.telecom.com', - apiKey: 'your-api-key' -}); - -// Create a subscriber -const subscriber = await client.subscribers.create({ - msisdn: '+1234567890', - firstName: 'John', - lastName: 'Doe', - email: 'john@example.com', - planId: 1 -}); -console.log(`Created subscriber: ${subscriber.id}`); -``` - -### Analytics API - -```typescript -import { TelecomClient, ChurnRiskLevel } from '@taas/sdk'; -import { AnalyticsAPI } from '@taas/sdk/analytics'; - -const client = new TelecomClient({ apiKey: 'your-api-key' }); -const analytics = new AnalyticsAPI(client.httpClient); - -// Churn Prediction -const prediction = await analytics.predictChurn('profile-123'); -console.log(`Churn Risk: ${prediction.riskLevel} (Score: ${prediction.riskScore.toFixed(2)})`); - -// Get churn metrics -const metrics = await analytics.getChurnMetrics('monthly'); -console.log(`Monthly Churn Rate: ${metrics.monthlyChurnRate.toFixed(2)}%`); - -// Get at-risk customers -const atRisk = await analytics.getAtRiskCustomers(ChurnRiskLevel.High, 100); -console.log(`Found ${atRisk.length} high-risk customers`); - -// Market metrics -const market = await analytics.getMarketMetrics('quarterly'); -console.log(`Market Share: ${market.marketSharePct.toFixed(2)}%`); -``` - -### Security API - -```typescript -import { TelecomClient, FraudSeverity, FraudAlertFilter } from '@taas/sdk'; -import { SecurityAPI } from '@taas/sdk/security'; - -const client = new TelecomClient({ apiKey: 'your-api-key' }); -const security = new SecurityAPI(client.httpClient); - -// Analyze transaction -const alert = await security.analyzeTransaction({ - profileId: 'profile-123', - amount: 99.99, - ipAddress: '192.168.1.1', - deviceId: 'device-456', - transaction: 'payment' -}); -if (alert) { - console.log(`Fraud Alert: ${alert.type} (Severity: ${alert.severity})`); -} - -// Get fraud alerts -const alerts = await security.getFraudAlerts({ - severity: FraudSeverity.High, - status: 'new', - limit: 50 -}); -console.log(`Found ${alerts.length} fraud alerts`); - -// Get fraud metrics -const fraudMetrics = await security.getFraudMetrics('monthly'); -console.log(`Resolution Rate: ${fraudMetrics.resolutionRatePct.toFixed(2)}%`); -``` - ---- - -## Analytics API - -### Endpoints - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/analytics/churn/predict` | Predict churn for a profile | -| GET | `/api/v1/analytics/churn/metrics` | Get churn metrics | -| GET | `/api/v1/analytics/churn/at-risk` | Get at-risk customers | -| GET | `/api/v1/analytics/market/metrics` | Get market metrics | -| GET | `/api/v1/analytics/maintenance/metrics` | Get predictive maintenance metrics | -| GET | `/api/v1/analytics/pricing/metrics` | Get pricing metrics | -| POST | `/api/v1/analytics/pricing/optimize` | Optimize pricing for rate plans | - -### Churn Risk Levels - -- `low` - Low risk of churn (< 25% probability) -- `medium` - Medium risk of churn (25-50% probability) -- `high` - High risk of churn (50-75% probability) -- `critical` - Critical risk of churn (> 75% probability) - ---- - -## Security API - -### Endpoints - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/security/fraud/analyze` | Analyze transaction for fraud | -| POST | `/api/v1/security/fraud/alerts` | Get fraud alerts with filters | -| PUT | `/api/v1/security/fraud/alerts/:id` | Update alert status | -| GET | `/api/v1/security/fraud/metrics` | Get fraud metrics | - -### Fraud Types - -- `account_takeover` - Unauthorized account access -- `subscription_fraud` - Fraudulent subscription creation -- `payment_fraud` - Fraudulent payment transactions -- `usage_anomaly` - Abnormal usage patterns -- `sim_swap` - Unauthorized SIM swap attempts - -### Fraud Severity Levels - -- `low` - Low severity, monitor only -- `medium` - Medium severity, review required -- `high` - High severity, immediate action needed -- `critical` - Critical severity, block transaction - ---- - -## Currency & Billing API - -### Endpoints - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/currency/convert` | Convert currency | -| GET | `/api/v1/currency/exchange/:from/:to` | Get exchange rate | -| GET | `/api/v1/currency/exchange/:from/:to/history` | Get exchange rate history | -| GET | `/api/v1/currency/currencies` | List supported currencies | -| POST | `/api/v1/currency/exchange/refresh` | Refresh exchange rates | -| POST | `/api/v1/currency/billing` | Process billing | -| GET | `/api/v1/currency/billing/history/:profile_id` | Get billing history | -| GET | `/api/v1/currency/billing/summary/:profile_id` | Get billing summary | -| POST | `/api/v1/currency/billing/refund/:transaction_id` | Process refund | -| GET | `/api/v1/currency/billing/analytics` | Get billing analytics | - -### Example: Currency Conversion - -```typescript -// TypeScript -const result = await client.currency.convert({ - from: 'USD', - to: 'EUR', - amount: 100.00 -}); -console.log(`${result.amount} ${result.to} = ${result.converted} ${result.from}`); -``` - -```python -# Python -result = client.currency.convert( - from_currency="USD", - to_currency="EUR", - amount=100.00 -) -print(f"{result.amount} {result.to_currency} = {result.converted} {result.from_currency}") -``` - -```go -// Go -result, err := client.Currency.Convert(ctx, &taas.ConvertRequest{ - From: "USD", - To: "EUR", - Amount: 100.00, -}) -fmt.Printf("%f %s = %f %s\n", result.Amount, result.To, result.Converted, result.From) -``` - ---- - -## Error Handling - -All SDKs provide consistent error handling: - -### Go - -```go -subscriber, err := client.Subscribers.Get(ctx, "invalid-id") -if err != nil { - if apiErr, ok := err.(*taas.APIError); ok { - fmt.Printf("API Error: %s (Code: %d)\n", apiErr.Message, apiErr.StatusCode) - } else { - fmt.Printf("Network Error: %v\n", err) - } -} -``` - -### Python - -```python -from telecom_sdk.exceptions import APIError, NetworkError - -try: - subscriber = client.subscribers.get("invalid-id") -except APIError as e: - print(f"API Error: {e.message} (Code: {e.status_code})") -except NetworkError as e: - print(f"Network Error: {e}") -``` - -### TypeScript - -```typescript -import { APIError, NetworkError } from '@taas/sdk'; - -try { - const subscriber = await client.subscribers.get('invalid-id'); -} catch (error) { - if (error instanceof APIError) { - console.log(`API Error: ${error.message} (Code: ${error.statusCode})`); - } else if (error instanceof NetworkError) { - console.log(`Network Error: ${error.message}`); - } -} -``` - ---- - -## Rate Limiting - -The API enforces rate limits per user: -- **Default**: 100 requests per minute -- **Burst**: Up to 200 requests in short bursts - -SDKs automatically handle rate limiting with exponential backoff: - -```typescript -const client = new TelecomClient({ - apiKey: 'your-api-key', - retryConfig: { - maxRetries: 3, - retryDelay: 1000, // 1 second - retryOnRateLimit: true - } -}); -``` - ---- - -## Support - -- **Documentation**: [https://docs.telecom-platform.com](https://docs.telecom-platform.com) -- **GitHub Issues**: [https://github.com/nutcas3/telecom-platform/issues](https://github.com/nutcas3/telecom-platform/issues) -- **Email**: sdk-support@telecom-platform.com diff --git a/sdk/elixir/lib/telecom_sdk.ex b/sdk/elixir/lib/telecom_sdk.ex index 1dca163..017086a 100644 --- a/sdk/elixir/lib/telecom_sdk.ex +++ b/sdk/elixir/lib/telecom_sdk.ex @@ -57,9 +57,7 @@ defmodule TelecomSDK do payments = PaymentAPI.new(http_client) rating_plans = RatingPlanAPI.new(http_client) system = SystemAPI.new(http_client) - analytics = AnalyticsAPI.new(http_client) - security = SecurityAPI.new(http_client) - currency = CurrencyAPI.new(http_client) + graphql = GraphQLAPI.new(http_client) state = %{ config: config, @@ -70,9 +68,7 @@ defmodule TelecomSDK do payments: payments, rating_plans: rating_plans, system: system, - analytics: analytics, - security: security, - currency: currency + graphql: graphql } {:ok, state} diff --git a/sdk/elixir/lib/telecom_sdk/analytics_api.ex b/sdk/elixir/lib/telecom_sdk/analytics_api.ex deleted file mode 100644 index 7e1e211..0000000 --- a/sdk/elixir/lib/telecom_sdk/analytics_api.ex +++ /dev/null @@ -1,78 +0,0 @@ -defmodule TelecomSDK.AnalyticsAPI do - @moduledoc """ - Analytics API for churn prediction, market analysis, and pricing optimization - """ - - defstruct [:http_client] - - def new(http_client) do - %__MODULE__{http_client: http_client} - end - - # Churn Analysis - - def predict_churn(%__MODULE__{http_client: client}, profile_id) do - body = %{profile_id: profile_id} - HTTPClient.post(client, "/api/v1/analytics/churn/predict", body) - end - - def get_churn_metrics(%__MODULE__{http_client: client}, period \\ "monthly") do - params = %{period: period} - HTTPClient.get(client, "/api/v1/analytics/churn/metrics", params) - end - - def get_at_risk_customers(%__MODULE__{http_client: client}, risk_level, limit \\ 100) do - body = %{risk_level: risk_level, limit: limit} - HTTPClient.post(client, "/api/v1/analytics/churn/at-risk", body) - end - - # Market Analytics - - def get_market_metrics(%__MODULE__{http_client: client}, period \\ "monthly") do - params = %{period: period} - HTTPClient.get(client, "/api/v1/analytics/market/metrics", params) - end - - def get_competitors(%__MODULE__{http_client: client}) do - HTTPClient.get(client, "/api/v1/analytics/market/competitors", %{}) - end - - def get_market_opportunities(%__MODULE__{http_client: client}) do - HTTPClient.get(client, "/api/v1/analytics/market/opportunities", %{}) - end - - # Predictive Maintenance - - def get_maintenance_metrics(%__MODULE__{http_client: client}, period \\ "monthly") do - params = %{period: period} - HTTPClient.get(client, "/api/v1/analytics/maintenance/metrics", params) - end - - def get_assets_health(%__MODULE__{http_client: client}) do - HTTPClient.get(client, "/api/v1/analytics/maintenance/assets", %{}) - end - - def get_maintenance_alerts(%__MODULE__{http_client: client}) do - HTTPClient.get(client, "/api/v1/analytics/maintenance/alerts", %{}) - end - - def predict_failure(%__MODULE__{http_client: client}, asset_id) do - HTTPClient.post(client, "/api/v1/analytics/maintenance/predict/#{asset_id}", %{}) - end - - # Pricing Optimization - - def get_pricing_metrics(%__MODULE__{http_client: client}, period \\ "monthly") do - params = %{period: period} - HTTPClient.get(client, "/api/v1/analytics/pricing/metrics", params) - end - - def optimize_pricing(%__MODULE__{http_client: client}, rate_plan_ids, strategy \\ "revenue_maximization") do - body = %{rate_plan_ids: rate_plan_ids, strategy: strategy} - HTTPClient.post(client, "/api/v1/analytics/pricing/optimize", body) - end - - def get_price_elasticity(%__MODULE__{http_client: client}) do - HTTPClient.get(client, "/api/v1/analytics/pricing/elasticity", %{}) - end -end diff --git a/sdk/elixir/lib/telecom_sdk/currency_api.ex b/sdk/elixir/lib/telecom_sdk/currency_api.ex deleted file mode 100644 index 8a54152..0000000 --- a/sdk/elixir/lib/telecom_sdk/currency_api.ex +++ /dev/null @@ -1,61 +0,0 @@ -defmodule TelecomSDK.CurrencyAPI do - @moduledoc """ - Currency and Billing API - """ - - defstruct [:http_client] - - def new(http_client) do - %__MODULE__{http_client: http_client} - end - - # Currency Conversion - - def convert(%__MODULE__{http_client: client}, from, to, amount) do - body = %{from: from, to: to, amount: amount} - HTTPClient.post(client, "/api/v1/currency/convert", body) - end - - def get_exchange_rate(%__MODULE__{http_client: client}, from, to) do - HTTPClient.get(client, "/api/v1/currency/exchange/#{from}/#{to}", %{}) - end - - def get_exchange_rate_history(%__MODULE__{http_client: client}, from, to, days \\ 30) do - params = %{days: days} - HTTPClient.get(client, "/api/v1/currency/exchange/#{from}/#{to}/history", params) - end - - def get_supported_currencies(%__MODULE__{http_client: client}) do - HTTPClient.get(client, "/api/v1/currency/currencies", %{}) - end - - def refresh_exchange_rates(%__MODULE__{http_client: client}) do - HTTPClient.post(client, "/api/v1/currency/exchange/refresh", %{}) - end - - # Billing - - def process_billing(%__MODULE__{http_client: client}, billing_data) do - HTTPClient.post(client, "/api/v1/currency/billing", billing_data) - end - - def get_billing_history(%__MODULE__{http_client: client}, profile_id, limit \\ 50) do - params = %{limit: limit} - HTTPClient.get(client, "/api/v1/currency/billing/history/#{profile_id}", params) - end - - def get_billing_summary(%__MODULE__{http_client: client}, profile_id, period \\ "monthly") do - params = %{period: period} - HTTPClient.get(client, "/api/v1/currency/billing/summary/#{profile_id}", params) - end - - def process_refund(%__MODULE__{http_client: client}, transaction_id, reason) do - body = %{reason: reason} - HTTPClient.post(client, "/api/v1/currency/billing/refund/#{transaction_id}", body) - end - - def get_billing_analytics(%__MODULE__{http_client: client}, period \\ "monthly") do - params = %{period: period} - HTTPClient.get(client, "/api/v1/currency/billing/analytics", params) - end -end diff --git a/sdk/elixir/lib/telecom_sdk/graphql_api.ex b/sdk/elixir/lib/telecom_sdk/graphql_api.ex new file mode 100644 index 0000000..1b83981 --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/graphql_api.ex @@ -0,0 +1,17 @@ +defmodule TelecomSDK.GraphQLAPI do + @moduledoc """ + API for GraphQL queries + """ + + defstruct [:client] + + def new(client) do + %__MODULE__{client: client} + end + + def execute(api, query, variables \\ nil) do + request = %{query: query} + request = if variables, do: Map.put(request, :variables, variables), else: request + TelecomSDK.HTTPClient.post(api.client, "/graphql", request) + end +end diff --git a/sdk/elixir/lib/telecom_sdk/security_api.ex b/sdk/elixir/lib/telecom_sdk/security_api.ex deleted file mode 100644 index ffefa82..0000000 --- a/sdk/elixir/lib/telecom_sdk/security_api.ex +++ /dev/null @@ -1,47 +0,0 @@ -defmodule TelecomSDK.SecurityAPI do - @moduledoc """ - Security API for fraud detection and SIM swap protection - """ - - defstruct [:http_client] - - def new(http_client) do - %__MODULE__{http_client: http_client} - end - - # Fraud Detection - - def analyze_transaction(%__MODULE__{http_client: client}, transaction) do - HTTPClient.post(client, "/api/v1/security/fraud/analyze", transaction) - end - - def get_fraud_alerts(%__MODULE__{http_client: client}, filter \\ nil) do - body = if filter, do: filter, else: %{} - HTTPClient.post(client, "/api/v1/security/fraud/alerts", body) - end - - def update_alert_status(%__MODULE__{http_client: client}, alert_id, status, actions \\ []) do - body = %{status: status, actions: actions} - HTTPClient.put(client, "/api/v1/security/fraud/alerts/#{alert_id}", body) - end - - def get_fraud_metrics(%__MODULE__{http_client: client}, period \\ "monthly") do - params = %{period: period} - HTTPClient.get(client, "/api/v1/security/fraud/metrics", params) - end - - def get_fraud_patterns(%__MODULE__{http_client: client}) do - HTTPClient.get(client, "/api/v1/security/fraud/patterns", %{}) - end - - # SIM Swap Protection - - def verify_sim_swap(%__MODULE__{http_client: client}, profile_id, msisdn) do - body = %{profile_id: profile_id, msisdn: msisdn} - HTTPClient.post(client, "/api/v1/security/simswap/verify", body) - end - - def get_sim_swap_history(%__MODULE__{http_client: client}, profile_id) do - HTTPClient.get(client, "/api/v1/security/simswap/history/#{profile_id}", %{}) - end -end diff --git a/sdk/go/analytics_api.go b/sdk/go/analytics_api.go deleted file mode 100644 index 901f415..0000000 --- a/sdk/go/analytics_api.go +++ /dev/null @@ -1,92 +0,0 @@ -package telecom - -import ( - "context" - "fmt" -) - -// AnalyticsAPI provides access to analytics endpoints -type AnalyticsAPI struct { - client *HTTPClient -} - -// NewAnalyticsAPI creates a new AnalyticsAPI client -func NewAnalyticsAPI(client *HTTPClient) *AnalyticsAPI { - return &AnalyticsAPI{client: client} -} - -// PredictChurn predicts churn risk for a specific profile -func (a *AnalyticsAPI) PredictChurn(ctx context.Context, profileID string) (*ChurnPrediction, error) { - var result ChurnPrediction - err := a.client.Post(ctx, "/api/v1/analytics/churn/predict", map[string]string{"profile_id": profileID}, &result) - if err != nil { - return nil, err - } - return &result, nil -} - -// GetChurnMetrics retrieves overall churn metrics -func (a *AnalyticsAPI) GetChurnMetrics(ctx context.Context, period string) (*ChurnMetrics, error) { - var result ChurnMetrics - err := a.client.Get(ctx, "/api/v1/analytics/churn/metrics", &result, map[string]string{"period": period}) - if err != nil { - return nil, err - } - return &result, nil -} - -// GetAtRiskCustomers retrieves customers at high risk of churn -func (a *AnalyticsAPI) GetAtRiskCustomers(ctx context.Context, riskLevel ChurnRiskLevel, limit int) ([]*ChurnPrediction, error) { - var result []*ChurnPrediction - err := a.client.Get(ctx, "/api/v1/analytics/churn/at-risk", &result, map[string]string{ - "risk_level": string(riskLevel), - "limit": fmt.Sprintf("%d", limit), - }) - if err != nil { - return nil, err - } - return result, nil -} - -// GetMarketMetrics retrieves market penetration metrics -func (a *AnalyticsAPI) GetMarketMetrics(ctx context.Context, period string) (*MarketMetrics, error) { - var result MarketMetrics - err := a.client.Get(ctx, "/api/v1/analytics/market/metrics", &result, map[string]string{"period": period}) - if err != nil { - return nil, err - } - return &result, nil -} - -// GetPredictiveMaintenanceMetrics retrieves infrastructure health metrics -func (a *AnalyticsAPI) GetPredictiveMaintenanceMetrics(ctx context.Context, period string) (*PredictiveMaintenanceMetrics, error) { - var result PredictiveMaintenanceMetrics - err := a.client.Get(ctx, "/api/v1/analytics/maintenance/metrics", &result, map[string]string{"period": period}) - if err != nil { - return nil, err - } - return &result, nil -} - -// GetPricingMetrics retrieves pricing optimization metrics -func (a *AnalyticsAPI) GetPricingMetrics(ctx context.Context, period string) (*PricingMetrics, error) { - var result PricingMetrics - err := a.client.Get(ctx, "/api/v1/analytics/pricing/metrics", &result, map[string]string{"period": period}) - if err != nil { - return nil, err - } - return &result, nil -} - -// OptimizePrice performs pricing optimization for a rate plan -func (a *AnalyticsAPI) OptimizePrice(ctx context.Context, ratePlanID, strategy string) (*PricingOptimizationResult, error) { - var result PricingOptimizationResult - err := a.client.Post(ctx, "/api/v1/analytics/pricing/optimize", map[string]interface{}{ - "rate_plan_id": ratePlanID, - "strategy": strategy, - }, &result) - if err != nil { - return nil, err - } - return &result, nil -} diff --git a/sdk/go/currency_api.go b/sdk/go/currency_api.go deleted file mode 100644 index cb797dc..0000000 --- a/sdk/go/currency_api.go +++ /dev/null @@ -1,131 +0,0 @@ -package telecom - -import ( - "context" - "fmt" -) - -// CurrencyAPI provides currency and billing operations -type CurrencyAPI struct { - client *HTTPClient -} - -// NewCurrencyAPI creates a new currency API client -func NewCurrencyAPI(client *HTTPClient) *CurrencyAPI { - return &CurrencyAPI{client: client} -} - -// ConvertRequest represents a currency conversion request -type ConvertRequest struct { - From string `json:"from"` - To string `json:"to"` - Amount float64 `json:"amount"` -} - -// ConvertResponse represents a currency conversion response -type ConvertResponse struct { - From string `json:"from"` - To string `json:"to"` - Amount float64 `json:"amount"` - Converted float64 `json:"converted"` - Rate float64 `json:"rate"` - Timestamp string `json:"timestamp"` -} - -// ExchangeRate represents an exchange rate -type ExchangeRate struct { - From string `json:"from"` - To string `json:"to"` - Rate float64 `json:"rate"` - Timestamp string `json:"timestamp"` -} - -// BillingTransaction represents a billing transaction -type BillingTransaction struct { - ID string `json:"id"` - ProfileID string `json:"profile_id"` - Amount float64 `json:"amount"` - Currency string `json:"currency"` - Type string `json:"type"` - Status string `json:"status"` - Description string `json:"description"` - CreatedAt string `json:"created_at"` -} - -// BillingSummary represents a billing summary -type BillingSummary struct { - ProfileID string `json:"profile_id"` - Period string `json:"period"` - TotalAmount float64 `json:"total_amount"` - Currency string `json:"currency"` - TransactionCount int `json:"transaction_count"` - Breakdown map[string]float64 `json:"breakdown"` -} - -// Convert converts currency -func (c *CurrencyAPI) Convert(ctx context.Context, req *ConvertRequest) (*ConvertResponse, error) { - var result ConvertResponse - err := c.client.Post(ctx, "/api/v1/currency/convert", req, &result) - return &result, err -} - -// GetExchangeRate gets the exchange rate between currencies -func (c *CurrencyAPI) GetExchangeRate(ctx context.Context, from, to string) (*ExchangeRate, error) { - var result ExchangeRate - err := c.client.Get(ctx, fmt.Sprintf("/api/v1/currency/exchange/%s/%s", from, to), &result) - return &result, err -} - -// GetExchangeRateHistory gets exchange rate history -func (c *CurrencyAPI) GetExchangeRateHistory(ctx context.Context, from, to string, days int) ([]ExchangeRate, error) { - var result []ExchangeRate - err := c.client.Get(ctx, fmt.Sprintf("/api/v1/currency/exchange/%s/%s/history", from, to), &result, map[string]string{"days": fmt.Sprintf("%d", days)}) - return result, err -} - -// GetSupportedCurrencies gets list of supported currencies -func (c *CurrencyAPI) GetSupportedCurrencies(ctx context.Context) ([]string, error) { - var result []string - err := c.client.Get(ctx, "/api/v1/currency/currencies", &result) - return result, err -} - -// RefreshExchangeRates refreshes exchange rates -func (c *CurrencyAPI) RefreshExchangeRates(ctx context.Context) error { - return c.client.Post(ctx, "/api/v1/currency/exchange/refresh", nil, nil) -} - -// ProcessBilling processes a billing transaction -func (c *CurrencyAPI) ProcessBilling(ctx context.Context, billingData map[string]interface{}) (*BillingTransaction, error) { - var result BillingTransaction - err := c.client.Post(ctx, "/api/v1/currency/billing", billingData, &result) - return &result, err -} - -// GetBillingHistory gets billing history for a profile -func (c *CurrencyAPI) GetBillingHistory(ctx context.Context, profileID string, limit int) ([]BillingTransaction, error) { - var result []BillingTransaction - err := c.client.Get(ctx, fmt.Sprintf("/api/v1/currency/billing/history/%s", profileID), &result, map[string]string{"limit": fmt.Sprintf("%d", limit)}) - return result, err -} - -// GetBillingSummary gets billing summary for a profile -func (c *CurrencyAPI) GetBillingSummary(ctx context.Context, profileID, period string) (*BillingSummary, error) { - var result BillingSummary - err := c.client.Get(ctx, fmt.Sprintf("/api/v1/currency/billing/summary/%s", profileID), &result, map[string]string{"period": period}) - return &result, err -} - -// ProcessRefund processes a refund -func (c *CurrencyAPI) ProcessRefund(ctx context.Context, transactionID, reason string) (*BillingTransaction, error) { - var result BillingTransaction - err := c.client.Post(ctx, fmt.Sprintf("/api/v1/currency/billing/refund/%s", transactionID), map[string]string{"reason": reason}, &result) - return &result, err -} - -// GetBillingAnalytics gets billing analytics -func (c *CurrencyAPI) GetBillingAnalytics(ctx context.Context, period string) (map[string]interface{}, error) { - var result map[string]interface{} - err := c.client.Get(ctx, "/api/v1/currency/billing/analytics", &result, map[string]string{"period": period}) - return result, err -} diff --git a/sdk/go/graphql_api.go b/sdk/go/graphql_api.go new file mode 100644 index 0000000..cc28387 --- /dev/null +++ b/sdk/go/graphql_api.go @@ -0,0 +1,29 @@ +package telecom + +import ( + "context" +) + +// GraphQLAPI handles GraphQL API calls +type GraphQLAPI struct { + client *HTTPClient +} + +// NewGraphQLAPI creates a new GraphQLAPI +func NewGraphQLAPI(client *HTTPClient) *GraphQLAPI { + return &GraphQLAPI{client: client} +} + +// Execute executes a GraphQL query +func (g *GraphQLAPI) Execute(ctx context.Context, query string, variables map[string]interface{}) (map[string]interface{}, error) { + data := map[string]interface{}{ + "query": query, + } + if variables != nil { + data["variables"] = variables + } + + var result map[string]interface{} + err := g.client.Post(ctx, "/graphql", data, &result) + return result, err +} diff --git a/sdk/go/security_api.go b/sdk/go/security_api.go deleted file mode 100644 index c3ab927..0000000 --- a/sdk/go/security_api.go +++ /dev/null @@ -1,52 +0,0 @@ -package telecom - -import "context" - -// SecurityAPI provides access to security endpoints -type SecurityAPI struct { - client *HTTPClient -} - -// NewSecurityAPI creates a new SecurityAPI client -func NewSecurityAPI(client *HTTPClient) *SecurityAPI { - return &SecurityAPI{client: client} -} - -// AnalyzeTransaction analyzes a transaction for fraud -func (s *SecurityAPI) AnalyzeTransaction(ctx context.Context, transaction map[string]interface{}) (*FraudAlert, error) { - var result FraudAlert - err := s.client.Post(ctx, "/api/v1/security/fraud/analyze", transaction, &result) - if err != nil { - return nil, err - } - return &result, nil -} - -// GetFraudAlerts retrieves fraud alerts -func (s *SecurityAPI) GetFraudAlerts(ctx context.Context, filter FraudAlertFilter) ([]*FraudAlert, error) { - var result []*FraudAlert - err := s.client.Post(ctx, "/api/v1/security/fraud/alerts", filter, &result) - if err != nil { - return nil, err - } - return result, nil -} - -// UpdateAlertStatus updates the status of a fraud alert -func (s *SecurityAPI) UpdateAlertStatus(ctx context.Context, alertID, status string, actions []string) error { - req := map[string]interface{}{ - "status": status, - "actions": actions, - } - return s.client.Put(ctx, "/api/v1/security/fraud/alerts/"+alertID, req, nil) -} - -// GetFraudMetrics returns fraud detection metrics -func (s *SecurityAPI) GetFraudMetrics(ctx context.Context, period string) (*FraudMetrics, error) { - var result FraudMetrics - err := s.client.Get(ctx, "/api/v1/security/fraud/metrics", &result, map[string]string{"period": period}) - if err != nil { - return nil, err - } - return &result, nil -} diff --git a/sdk/go/telecom.go b/sdk/go/telecom.go index a2f28b6..551ef5b 100644 --- a/sdk/go/telecom.go +++ b/sdk/go/telecom.go @@ -16,9 +16,7 @@ type Client struct { Payments *PaymentAPI RatingPlans *RatingPlanAPI System *SystemAPI - Analytics *AnalyticsAPI - Security *SecurityAPI - Currency *CurrencyAPI + GraphQL *GraphQLAPI } // Config holds the SDK configuration @@ -77,9 +75,7 @@ func NewClient(config *Config) (*Client, error) { client.Payments = NewPaymentAPI(httpClient) client.RatingPlans = NewRatingPlanAPI(httpClient) client.System = NewSystemAPI(httpClient) - client.Analytics = NewAnalyticsAPI(httpClient) - client.Security = NewSecurityAPI(httpClient) - client.Currency = NewCurrencyAPI(httpClient) + client.GraphQL = NewGraphQLAPI(httpClient) return client, nil } diff --git a/sdk/go/types.go b/sdk/go/types.go index 6468198..6b4797b 100644 --- a/sdk/go/types.go +++ b/sdk/go/types.go @@ -259,237 +259,3 @@ type GetSystemStatsRequest struct{} // GetHealthStatusRequest represents a gRPC request to get health status type GetHealthStatusRequest struct{} - -// ChurnRiskLevel represents the risk level of customer churn -type ChurnRiskLevel string - -const ( - ChurnRiskLow ChurnRiskLevel = "low" - ChurnRiskMedium ChurnRiskLevel = "medium" - ChurnRiskHigh ChurnRiskLevel = "high" - ChurnRiskCritical ChurnRiskLevel = "critical" -) - -// ChurnPrediction represents a churn prediction for a customer -type ChurnPrediction struct { - ProfileID string `json:"profile_id"` - RiskLevel ChurnRiskLevel `json:"risk_level"` - RiskScore float64 `json:"risk_score"` - PredictedChurnDate *time.Time `json:"predicted_churn_date,omitempty"` - Reasons []string `json:"reasons"` - Recommendations []string `json:"recommendations"` - LastUpdated time.Time `json:"last_updated"` -} - -// ChurnMetrics represents churn analysis metrics -type ChurnMetrics struct { - Period string `json:"period"` - TotalSubscribers int64 `json:"total_subscribers"` - ChurnedSubscribers int64 `json:"churned_subscribers"` - ChurnRate float64 `json:"churn_rate"` - MonthlyChurnRate float64 `json:"monthly_churn_rate"` - AnnualChurnRate float64 `json:"annual_churn_rate"` - AverageTenure float64 `json:"average_tenure_days"` - RiskDistribution map[ChurnRiskLevel]int64 `json:"risk_distribution"` - GeneratedAt time.Time `json:"generated_at"` -} - -// FraudType represents different types of fraud -type FraudType string - -const ( - FraudTypeAccountTakeover FraudType = "account_takeover" - FraudTypeSubscriptionFraud FraudType = "subscription_fraud" - FraudTypePaymentFraud FraudType = "payment_fraud" - FraudTypeUsageAnomaly FraudType = "usage_anomaly" - FraudTypeSIMSwap FraudType = "sim_swap" -) - -// FraudSeverity represents the severity of fraud detection -type FraudSeverity string - -const ( - FraudSeverityLow FraudSeverity = "low" - FraudSeverityMedium FraudSeverity = "medium" - FraudSeverityHigh FraudSeverity = "high" - FraudSeverityCritical FraudSeverity = "critical" -) - -// FraudAlert represents a fraud detection alert -type FraudAlert struct { - ID string `json:"id"` - Type FraudType `json:"type"` - Severity FraudSeverity `json:"severity"` - ProfileID string `json:"profile_id"` - Description string `json:"description"` - RiskScore float64 `json:"risk_score"` - Evidence []string `json:"evidence"` - IPAddress string `json:"ip_address"` - Timestamp time.Time `json:"timestamp"` - Status string `json:"status"` - Actions []string `json:"actions_taken"` - Metadata map[string]any `json:"metadata"` -} - -// FraudMetrics represents fraud detection metrics -type FraudMetrics struct { - Period string `json:"period"` - TotalAlerts int64 `json:"total_alerts"` - ResolvedAlerts int64 `json:"resolved_alerts"` - FalsePositives int64 `json:"false_positives"` - ResolutionRate float64 `json:"resolution_rate_pct"` - FalsePositiveRate float64 `json:"false_positive_rate_pct"` - ByType map[FraudType]int64 `json:"by_type"` - BySeverity map[FraudSeverity]int64 `json:"by_severity"` - GeneratedAt time.Time `json:"generated_at"` -} - -// FraudAlertFilter filters fraud alerts -type FraudAlertFilter struct { - Type FraudType `json:"type,omitempty"` - Severity FraudSeverity `json:"severity,omitempty"` - Status string `json:"status,omitempty"` - FromDate *time.Time `json:"from_date,omitempty"` - ToDate *time.Time `json:"to_date,omitempty"` - Limit int `json:"limit,omitempty"` -} - -// MarketMetrics represents market penetration analysis -type MarketMetrics struct { - Period string `json:"period"` - TotalMarketSize int64 `json:"total_market_size"` - OurSubscribers int64 `json:"our_subscribers"` - MarketShare float64 `json:"market_share_pct"` - GrowthRate float64 `json:"growth_rate_pct"` - ByCountry map[string]CountryMetrics `json:"by_country"` - ByCarrier map[string]MarketCarrierMetrics `json:"by_carrier"` - ByDemographic map[string]DemoMetrics `json:"by_demographic"` - CompetitorAnalysis map[string]CompetitorMetrics `json:"competitor_analysis"` - MarketOpportunities []MarketOpportunity `json:"market_opportunities"` - GeneratedAt time.Time `json:"generated_at"` -} - -// CountryMetrics represents metrics by country -type CountryMetrics struct { - Country string `json:"country"` - MarketSize int64 `json:"market_size"` - OurSubscribers int64 `json:"our_subscribers"` - MarketShare float64 `json:"market_share_pct"` - GrowthRate float64 `json:"growth_rate_pct"` - AverageRevenue float64 `json:"average_revenue"` -} - -// MarketCarrierMetrics represents metrics by carrier -type MarketCarrierMetrics struct { - CarrierID string `json:"carrier_id"` - CarrierName string `json:"carrier_name"` - Subscribers int64 `json:"subscribers"` - MarketShare float64 `json:"market_share_pct"` - AverageRevenue float64 `json:"average_revenue"` - QualityScore float64 `json:"quality_score"` -} - -// DemoMetrics represents metrics by demographic -type DemoMetrics struct { - Segment string `json:"segment"` - Subscribers int64 `json:"subscribers"` - MarketShare float64 `json:"market_share_pct"` - AverageRevenue float64 `json:"average_revenue"` - GrowthRate float64 `json:"growth_rate_pct"` -} - -// CompetitorMetrics represents competitor analysis -type CompetitorMetrics struct { - Name string `json:"name"` - MarketShare float64 `json:"market_share_pct"` - Subscribers int64 `json:"subscribers"` - AveragePrice float64 `json:"average_price"` - Strengths []string `json:"strengths"` - Weaknesses []string `json:"weaknesses"` -} - -// MarketOpportunity represents a market opportunity -type MarketOpportunity struct { - ID string `json:"id"` - Type string `json:"type"` - Description string `json:"description"` - PotentialSize int64 `json:"potential_size"` - Confidence float64 `json:"confidence"` - RequiredActions []string `json:"required_actions"` -} - -// PredictiveMaintenanceMetrics represents infrastructure health metrics -type PredictiveMaintenanceMetrics struct { - Period string `json:"period"` - TotalAssets int64 `json:"total_assets"` - HealthyAssets int64 `json:"healthy_assets"` - AtRiskAssets int64 `json:"at_risk_assets"` - CriticalAssets int64 `json:"critical_assets"` - OverallHealthScore float64 `json:"overall_health_score"` - ByAssetType map[string]AssetTypeMetrics `json:"by_asset_type"` - PredictedFailures []PredictedFailure `json:"predicted_failures"` - MaintenanceSchedule []MaintenanceTask `json:"maintenance_schedule"` - GeneratedAt time.Time `json:"generated_at"` -} - -// AssetTypeMetrics represents metrics by asset type -type AssetTypeMetrics struct { - AssetType string `json:"asset_type"` - Total int64 `json:"total"` - Healthy int64 `json:"healthy"` - AtRisk int64 `json:"at_risk"` - Critical int64 `json:"critical"` - HealthScore float64 `json:"health_score"` -} - -// PredictedFailure represents a predicted failure -type PredictedFailure struct { - AssetID string `json:"asset_id"` - AssetType string `json:"asset_type"` - FailureType string `json:"failure_type"` - PredictedDate time.Time `json:"predicted_date"` - Confidence float64 `json:"confidence"` - RecommendedActions []string `json:"recommended_actions"` -} - -// MaintenanceTask represents a scheduled maintenance task -type MaintenanceTask struct { - ID string `json:"id"` - AssetID string `json:"asset_id"` - TaskType string `json:"task_type"` - Priority string `json:"priority"` - ScheduledDate time.Time `json:"scheduled_date"` - EstimatedDuration int `json:"estimated_duration_minutes"` - Description string `json:"description"` - Status string `json:"status"` -} - -// PricingOptimizationResult represents pricing optimization results -type PricingOptimizationResult struct { - RatePlanID string `json:"rate_plan_id"` - CurrentPrice float64 `json:"current_price"` - OptimalPrice float64 `json:"optimal_price"` - Strategy string `json:"strategy"` - ExpectedRevenue float64 `json:"expected_revenue"` - ExpectedDemand int64 `json:"expected_demand"` - PriceChange float64 `json:"price_change_pct"` - Reasoning []string `json:"reasoning"` - Risks []string `json:"risks"` - Recommendations []string `json:"recommendations"` - Confidence float64 `json:"confidence"` - GeneratedAt time.Time `json:"generated_at"` -} - -// PricingMetrics represents pricing optimization metrics -type PricingMetrics struct { - Period string `json:"period"` - TotalRatePlans int64 `json:"total_rate_plans"` - OptimizedRatePlans int64 `json:"optimized_rate_plans"` - AveragePriceChange float64 `json:"average_price_change_pct"` - ExpectedRevenueImpact float64 `json:"expected_revenue_impact_pct"` - ChurnRateReduction float64 `json:"churn_rate_reduction_pct"` - PriceElasticity float64 `json:"price_elasticity"` - CompetitiveIndex float64 `json:"competitive_index"` - OptimizationROI float64 `json:"optimization_roi_pct"` - GeneratedAt time.Time `json:"generated_at"` -} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/AnalyticsAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/AnalyticsAPI.kt deleted file mode 100644 index b1328e2..0000000 --- a/sdk/kotlin/src/main/kotlin/com/telecom/AnalyticsAPI.kt +++ /dev/null @@ -1,201 +0,0 @@ -package com.telecom - -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonObject - -/** - * Analytics API for churn prediction, market analysis, and pricing optimization - */ -class AnalyticsAPI(private val httpClient: HTTPClient) { - - /** - * Predict churn risk for a profile - */ - suspend fun predictChurn(profileId: String): ChurnPrediction { - return httpClient.post("/api/v1/analytics/churn/predict", - mapOf("profile_id" to profileId)) - } - - /** - * Get churn metrics - */ - suspend fun getChurnMetrics(period: String = "monthly"): ChurnMetrics { - return httpClient.get("/api/v1/analytics/churn/metrics", - mapOf("period" to period)) - } - - /** - * Get at-risk customers - */ - suspend fun getAtRiskCustomers( - riskLevel: ChurnRiskLevel, - limit: Int = 100 - ): List { - return httpClient.post("/api/v1/analytics/churn/at-risk", - mapOf( - "risk_level" to riskLevel.value, - "limit" to limit - )) - } - - /** - * Get market metrics - */ - suspend fun getMarketMetrics(period: String = "monthly"): MarketMetrics { - return httpClient.get("/api/v1/analytics/market/metrics", - mapOf("period" to period)) - } - - /** - * Get competitor analysis - */ - suspend fun getCompetitors(): JsonObject { - return httpClient.get("/api/v1/analytics/market/competitors") - } - - /** - * Get market opportunities - */ - suspend fun getMarketOpportunities(): JsonObject { - return httpClient.get("/api/v1/analytics/market/opportunities") - } - - /** - * Get maintenance metrics - */ - suspend fun getMaintenanceMetrics(period: String = "monthly"): MaintenanceMetrics { - return httpClient.get("/api/v1/analytics/maintenance/metrics", - mapOf("period" to period)) - } - - /** - * Get assets health - */ - suspend fun getAssetsHealth(): JsonObject { - return httpClient.get("/api/v1/analytics/maintenance/assets") - } - - /** - * Get maintenance alerts - */ - suspend fun getMaintenanceAlerts(): JsonObject { - return httpClient.get("/api/v1/analytics/maintenance/alerts") - } - - /** - * Predict failure for an asset - */ - suspend fun predictFailure(assetId: String): JsonObject { - return httpClient.post("/api/v1/analytics/maintenance/predict/$assetId", emptyMap()) - } - - /** - * Get pricing metrics - */ - suspend fun getPricingMetrics(period: String = "monthly"): PricingMetrics { - return httpClient.get("/api/v1/analytics/pricing/metrics", - mapOf("period" to period)) - } - - /** - * Optimize pricing for rate plans - */ - suspend fun optimizePricing( - ratePlanIds: List, - strategy: String = "revenue_maximization" - ): List { - return httpClient.post("/api/v1/analytics/pricing/optimize", - mapOf( - "rate_plan_ids" to ratePlanIds, - "strategy" to strategy - )) - } - - /** - * Get price elasticity data - */ - suspend fun getPriceElasticity(): JsonObject { - return httpClient.get("/api/v1/analytics/pricing/elasticity") - } -} - -@Serializable -data class ChurnPrediction( - val profileId: String, - val riskLevel: String, - val riskScore: Double, - val predictedChurnDate: String? = null, - val reasons: List, - val recommendations: List, - val lastUpdated: String -) - -@Serializable -data class ChurnMetrics( - val period: String, - val totalSubscribers: Long, - val churnedSubscribers: Long, - val churnRate: Double, - val monthlyChurnRate: Double, - val annualChurnRate: Double, - val averageTenureDays: Double, - val riskDistribution: Map, - val generatedAt: String -) - -@Serializable -data class MarketMetrics( - val period: String, - val totalMarketSize: Long, - val ourSubscribers: Long, - val marketShare: Double, - val growthRate: Double, - val byCountry: Map, - val generatedAt: String -) - -@Serializable -data class MaintenanceMetrics( - val period: String, - val totalAssets: Long, - val healthyAssets: Long, - val assetsNeedingAttention: Long, - val uptime: Double, - val meanTimeToFailure: Double, - val meanTimeToRepair: Double, - val generatedAt: String -) - -@Serializable -data class PricingMetrics( - val period: String, - val totalRevenue: Double, - val arpu: Double, - val priceElasticity: Double, - val competitiveIndex: Double, - val optimizationRoi: Double, - val generatedAt: String -) - -@Serializable -data class PricingOptimizationResult( - val ratePlanId: String, - val strategy: String, - val currentPrice: Double, - val optimalPrice: Double, - val priceChangePct: Double, - val expectedRevenue: Double, - val expectedDemand: Double, - val confidence: Double, - val reasoning: List, - val risks: List, - val recommendations: List -) - -@Serializable -enum class ChurnRiskLevel(val value: String) { - LOW("low"), - MEDIUM("medium"), - HIGH("high"), - CRITICAL("critical") -} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/CurrencyAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/CurrencyAPI.kt deleted file mode 100644 index 633fc15..0000000 --- a/sdk/kotlin/src/main/kotlin/com/telecom/CurrencyAPI.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.telecom - -import kotlinx.serialization.Serializable - -/** - * Currency and Billing API - */ -class CurrencyAPI(private val httpClient: HTTPClient) { - - /** - * Convert currency - */ - suspend fun convert(from: String, to: String, amount: Double): ConvertResponse { - return httpClient.post("/api/v1/currency/convert", - mapOf( - "from" to from, - "to" to to, - "amount" to amount - )) - } - - /** - * Get exchange rate between currencies - */ - suspend fun getExchangeRate(from: String, to: String): ExchangeRate { - return httpClient.get("/api/v1/currency/exchange/$from/$to") - } - - /** - * Get exchange rate history - */ - suspend fun getExchangeRateHistory(from: String, to: String, days: Int = 30): List { - return httpClient.get("/api/v1/currency/exchange/$from/$to/history", - mapOf("days" to days)) - } - - /** - * Get supported currencies - */ - suspend fun getSupportedCurrencies(): List { - return httpClient.get("/api/v1/currency/currencies") - } - - /** - * Refresh exchange rates - */ - suspend fun refreshExchangeRates(): JsonObject { - return httpClient.post("/api/v1/currency/exchange/refresh", emptyMap()) - } - - /** - * Process billing transaction - */ - suspend fun processBilling(billingData: Map): BillingTransaction { - return httpClient.post("/api/v1/currency/billing", billingData) - } - - /** - * Get billing history for a profile - */ - suspend fun getBillingHistory(profileId: String, limit: Int = 50): List { - return httpClient.get("/api/v1/currency/billing/history/$profileId", - mapOf("limit" to limit)) - } - - /** - * Get billing summary for a profile - */ - suspend fun getBillingSummary(profileId: String, period: String = "monthly"): BillingSummary { - return httpClient.get("/api/v1/currency/billing/summary/$profileId", - mapOf("period" to period)) - } - - /** - * Process refund - */ - suspend fun processRefund(transactionId: String, reason: String): BillingTransaction { - return httpClient.post("/api/v1/currency/billing/refund/$transactionId", - mapOf("reason" to reason)) - } - - /** - * Get billing analytics - */ - suspend fun getBillingAnalytics(period: String = "monthly"): JsonObject { - return httpClient.get("/api/v1/currency/billing/analytics", - mapOf("period" to period)) - } -} - -@Serializable -data class ConvertRequest( - val from: String, - val to: String, - val amount: Double -) - -@Serializable -data class ConvertResponse( - val from: String, - val to: String, - val amount: Double, - val converted: Double, - val rate: Double, - val timestamp: String -) - -@Serializable -data class ExchangeRate( - val from: String, - val to: String, - val rate: Double, - val timestamp: String -) - -@Serializable -data class Currency( - val code: String, - val name: String, - val symbol: String -) - -@Serializable -data class BillingTransaction( - val id: String, - val profileId: String, - val amount: Double, - val currency: String, - val type: String, - val status: String, - val description: String, - val createdAt: String -) - -@Serializable -data class BillingSummary( - val profileId: String, - val period: String, - val totalAmount: Double, - val currency: String, - val transactionCount: Int, - val breakdown: Map -) diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/GraphQLAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/GraphQLAPI.kt new file mode 100644 index 0000000..f90414f --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/GraphQLAPI.kt @@ -0,0 +1,15 @@ +package com.telecom + +/** + * API for GraphQL queries + */ +class GraphQLAPI(private val client: HTTPClient) { + + /** + * Execute a GraphQL query + */ + suspend fun execute(query: String, variables: Map? = null): Map { + val request = GraphQLRequest(query, variables) + return client.post("/graphql", request) + } +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/SecurityAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/SecurityAPI.kt deleted file mode 100644 index 67b6e80..0000000 --- a/sdk/kotlin/src/main/kotlin/com/telecom/SecurityAPI.kt +++ /dev/null @@ -1,134 +0,0 @@ -package com.telecom - -import kotlinx.serialization.Serializable - -/** - * Security API for fraud detection and SIM swap protection - */ -class SecurityAPI(private val httpClient: HTTPClient) { - - /** - * Analyze a transaction for fraud - */ - suspend fun analyzeTransaction(transaction: Map): FraudAlert? { - return httpClient.post("/api/v1/security/fraud/analyze", transaction) - } - - /** - * Get fraud alerts with filtering - */ - suspend fun getFraudAlerts(filter: FraudAlertFilter? = null): List { - val payload = filter?.let { - mapOf( - "type" to it.type, - "severity" to it.severity, - "status" to it.status, - "limit" to it.limit - ).filterValues { it != null } - } ?: emptyMap() - - return httpClient.post("/api/v1/security/fraud/alerts", payload) - } - - /** - * Update fraud alert status - */ - suspend fun updateAlertStatus( - alertId: String, - status: String, - actions: List = emptyList() - ): JsonObject { - return httpClient.put("/api/v1/security/fraud/alerts/$alertId", - mapOf( - "status" to status, - "actions" to actions - )) - } - - /** - * Get fraud detection metrics - */ - suspend fun getFraudMetrics(period: String = "monthly"): FraudMetrics { - return httpClient.get("/api/v1/security/fraud/metrics", - mapOf("period" to period)) - } - - /** - * Get detected fraud patterns - */ - suspend fun getFraudPatterns(): JsonObject { - return httpClient.get("/api/v1/security/fraud/patterns") - } - - /** - * Verify SIM swap request - */ - suspend fun verifySIMSwap(profileId: String, msisdn: String): JsonObject { - return httpClient.post("/api/v1/security/simswap/verify", - mapOf( - "profile_id" to profileId, - "msisdn" to msisdn - )) - } - - /** - * Get SIM swap history for a profile - */ - suspend fun getSIMSwapHistory(profileId: String): JsonObject { - return httpClient.get("/api/v1/security/simswap/history/$profileId") - } -} - -@Serializable -data class FraudAlert( - val id: String, - val type: String, - val severity: String, - val profileId: String, - val description: String, - val riskScore: Double, - val evidence: List, - val ipAddress: String, - val timestamp: String, - val status: String, - val actionsTaken: List, - val metadata: Map -) - -@Serializable -data class FraudMetrics( - val period: String, - val totalAlerts: Long, - val resolvedAlerts: Long, - val falsePositives: Long, - val resolutionRate: Double, - val falsePositiveRate: Double, - val byType: Map, - val bySeverity: Map, - val generatedAt: String -) - -@Serializable -data class FraudAlertFilter( - val type: String? = null, - val severity: String? = null, - val status: String? = null, - val limit: Int = 50 -) - -@Serializable -enum class FraudType(val value: String) { - ACCOUNT_TAKEOVER("account_takeover"), - SUBSCRIPTION_FRAUD("subscription_fraud"), - PAYMENT_FRAUD("payment_fraud"), - USAGE_ANOMALY("usage_anomaly"), - SIM_SWAP("sim_swap") -} - -@Serializable -enum class FraudSeverity(val value: String) { - LOW("low"), - MEDIUM("medium"), - HIGH("high"), - CRITICAL("critical") -} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt b/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt index daedb08..22e9f67 100644 --- a/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt +++ b/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt @@ -17,9 +17,7 @@ class TelecomSDK private constructor( val payments: PaymentAPI val ratingPlans: RatingPlanAPI val system: SystemAPI - val analytics: AnalyticsAPI - val security: SecurityAPI - val currency: CurrencyAPI + val graphql: GraphQLAPI companion object { /** @@ -35,11 +33,9 @@ class TelecomSDK private constructor( val payments = PaymentAPI(httpClient) val ratingPlans = RatingPlanAPI(httpClient) val system = SystemAPI(httpClient) - val analytics = AnalyticsAPI(httpClient) - val security = SecurityAPI(httpClient) - val currency = CurrencyAPI(httpClient) + val graphql = GraphQLAPI(httpClient) - return TelecomSDK(config, authProvider, httpClient, subscribers, usage, payments, ratingPlans, system, analytics, security, currency) + return TelecomSDK(config, authProvider, httpClient, subscribers, usage, payments, ratingPlans, system, graphql) } } @@ -52,9 +48,7 @@ class TelecomSDK private constructor( payments: PaymentAPI, ratingPlans: RatingPlanAPI, system: SystemAPI, - analytics: AnalyticsAPI, - security: SecurityAPI, - currency: CurrencyAPI + graphql: GraphQLAPI ) { this.config = config this.authProvider = authProvider @@ -64,9 +58,7 @@ class TelecomSDK private constructor( this.payments = payments this.ratingPlans = ratingPlans this.system = system - this.analytics = analytics - this.security = security - this.currency = currency + this.graphql = graphql } // Authentication methods diff --git a/sdk/python/telecom_sdk/__init__.py b/sdk/python/telecom_sdk/__init__.py index da0959f..26a788e 100644 --- a/sdk/python/telecom_sdk/__init__.py +++ b/sdk/python/telecom_sdk/__init__.py @@ -9,9 +9,6 @@ SystemAPI, GraphQLAPI, ) -from .analytics import AnalyticsAPI -from .security import SecurityAPI -from .currency import CurrencyAPI from .websocket import WebSocketClient from .types import ( Subscriber, @@ -24,18 +21,6 @@ RatingPlan, RealTimeUsage, PaginatedResponse, - ChurnRiskLevel, - ChurnPrediction, - ChurnMetrics, - FraudType, - FraudSeverity, - FraudAlert, - FraudMetrics, - FraudAlertFilter, - MarketMetrics, - PredictiveMaintenanceMetrics, - PricingOptimizationResult, - PricingMetrics, ) from .exceptions import ( TelecomError, @@ -47,7 +32,7 @@ ServerError, ) -__version__ = "2.0.0" +__version__ = "1.0.0" __all__ = [ # Main SDK client "TelecomSDK", @@ -61,9 +46,6 @@ "RatingPlanAPI", "SystemAPI", "GraphQLAPI", - "AnalyticsAPI", - "SecurityAPI", - "CurrencyAPI", # WebSocket "WebSocketClient", # Types @@ -77,18 +59,6 @@ "RatingPlan", "RealTimeUsage", "PaginatedResponse", - "ChurnRiskLevel", - "ChurnPrediction", - "ChurnMetrics", - "FraudType", - "FraudSeverity", - "FraudAlert", - "FraudMetrics", - "FraudAlertFilter", - "MarketMetrics", - "PredictiveMaintenanceMetrics", - "PricingOptimizationResult", - "PricingMetrics", # Exceptions "TelecomError", "AuthenticationError", diff --git a/sdk/python/telecom_sdk/analytics.py b/sdk/python/telecom_sdk/analytics.py deleted file mode 100644 index 4a64228..0000000 --- a/sdk/python/telecom_sdk/analytics.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Analytics API client for churn prediction, market analysis, and pricing optimization.""" - -from typing import List, Optional -from datetime import datetime - -from .types import ( - ChurnPrediction, - ChurnMetrics, - ChurnRiskLevel, - MarketMetrics, - PredictiveMaintenanceMetrics, - PricingOptimizationResult, - PricingMetrics, -) - - -class AnalyticsAPI: - """Analytics API client for churn, market, maintenance, and pricing analytics.""" - - def __init__(self, client): - self._client = client - - def predict_churn(self, profile_id: str) -> ChurnPrediction: - """Predict churn risk for a profile.""" - response = self._client.post("/api/v1/analytics/churn/predict", {"profile_id": profile_id}) - return ChurnPrediction(**response) - - def get_churn_metrics(self, period: str = "monthly") -> ChurnMetrics: - """Get churn metrics for a period.""" - response = self._client.get("/api/v1/analytics/churn/metrics", params={"period": period}) - return ChurnMetrics(**response) - - def get_at_risk_customers( - self, risk_level: ChurnRiskLevel, limit: int = 100 - ) -> List[ChurnPrediction]: - """Get customers at risk of churning.""" - response = self._client.get( - "/api/v1/analytics/churn/at-risk", - params={"risk_level": risk_level.value, "limit": str(limit)}, - ) - return [ChurnPrediction(**item) for item in response] - - def get_market_metrics(self, period: str = "monthly") -> MarketMetrics: - """Get market penetration metrics.""" - response = self._client.get("/api/v1/analytics/market/metrics", params={"period": period}) - return MarketMetrics(**response) - - def get_competitors(self) -> dict: - """Get competitor analysis.""" - return self._client.get("/api/v1/analytics/market/competitors") - - def get_market_opportunities(self) -> dict: - """Get market opportunities.""" - return self._client.get("/api/v1/analytics/market/opportunities") - - def get_maintenance_metrics(self, period: str = "monthly") -> PredictiveMaintenanceMetrics: - """Get predictive maintenance metrics.""" - response = self._client.get( - "/api/v1/analytics/maintenance/metrics", params={"period": period} - ) - return PredictiveMaintenanceMetrics(**response) - - def get_assets_health(self) -> dict: - """Get assets health status.""" - return self._client.get("/api/v1/analytics/maintenance/assets") - - def get_maintenance_alerts(self) -> dict: - """Get maintenance alerts.""" - return self._client.get("/api/v1/analytics/maintenance/alerts") - - def predict_failure(self, asset_id: str) -> dict: - """Predict failure for an asset.""" - return self._client.post(f"/api/v1/analytics/maintenance/predict/{asset_id}", {}) - - def get_pricing_metrics(self, period: str = "monthly") -> PricingMetrics: - """Get pricing optimization metrics.""" - response = self._client.get("/api/v1/analytics/pricing/metrics", params={"period": period}) - return PricingMetrics(**response) - - def optimize_pricing( - self, rate_plan_ids: List[str], strategy: str = "revenue_maximization" - ) -> List[PricingOptimizationResult]: - """Optimize pricing for rate plans.""" - response = self._client.post( - "/api/v1/analytics/pricing/optimize", - {"rate_plan_ids": rate_plan_ids, "strategy": strategy}, - ) - return [PricingOptimizationResult(**item) for item in response] - - def get_price_elasticity(self) -> dict: - """Get price elasticity data.""" - return self._client.get("/api/v1/analytics/pricing/elasticity") diff --git a/sdk/python/telecom_sdk/client.py b/sdk/python/telecom_sdk/client.py index dbc3c7d..c4601bf 100644 --- a/sdk/python/telecom_sdk/client.py +++ b/sdk/python/telecom_sdk/client.py @@ -9,10 +9,8 @@ PaymentAPI, RatingPlanAPI, SystemAPI, + GraphQLAPI, ) -from .analytics import AnalyticsAPI -from .security import SecurityAPI -from .currency import CurrencyAPI from .websocket import WebSocketClient from .types import ( Subscriber, @@ -101,9 +99,7 @@ def __init__( self.payments = PaymentAPI(self.api_client) self.rating_plans = RatingPlanAPI(self.api_client) self.system = SystemAPI(self.api_client) - self.analytics = AnalyticsAPI(self.api_client) - self.security = SecurityAPI(self.api_client) - self.currency = CurrencyAPI(self.api_client) + self.graphql = GraphQLAPI(self.api_client) async def __aenter__(self): """Async context manager entry.""" diff --git a/sdk/python/telecom_sdk/currency.py b/sdk/python/telecom_sdk/currency.py deleted file mode 100644 index 0c6713e..0000000 --- a/sdk/python/telecom_sdk/currency.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Currency and Billing API client.""" - -from typing import List, Optional, Dict, Any -from datetime import datetime - - -class CurrencyAPI: - """Currency and Billing API client.""" - - def __init__(self, client): - self._client = client - - def convert(self, from_currency: str, to_currency: str, amount: float) -> dict: - """Convert currency.""" - return self._client.post( - "/api/v1/currency/convert", - {"from": from_currency, "to": to_currency, "amount": amount}, - ) - - def get_exchange_rate(self, from_currency: str, to_currency: str) -> dict: - """Get exchange rate between currencies.""" - return self._client.get(f"/api/v1/currency/exchange/{from_currency}/{to_currency}") - - def get_exchange_rate_history( - self, from_currency: str, to_currency: str, days: int = 30 - ) -> dict: - """Get exchange rate history.""" - return self._client.get( - f"/api/v1/currency/exchange/{from_currency}/{to_currency}/history", - params={"days": str(days)}, - ) - - def get_supported_currencies(self) -> dict: - """Get list of supported currencies.""" - return self._client.get("/api/v1/currency/currencies") - - def refresh_exchange_rates(self) -> dict: - """Refresh exchange rates from external sources.""" - return self._client.post("/api/v1/currency/exchange/refresh", {}) - - def process_billing(self, billing_data: Dict[str, Any]) -> dict: - """Process billing transaction.""" - return self._client.post("/api/v1/currency/billing", billing_data) - - def get_billing_history(self, profile_id: str, limit: int = 50) -> dict: - """Get billing history for a profile.""" - return self._client.get( - f"/api/v1/currency/billing/history/{profile_id}", - params={"limit": str(limit)}, - ) - - def get_billing_summary(self, profile_id: str, period: str = "monthly") -> dict: - """Get billing summary for a profile.""" - return self._client.get( - f"/api/v1/currency/billing/summary/{profile_id}", - params={"period": period}, - ) - - def process_refund(self, transaction_id: str, reason: str) -> dict: - """Process a refund for a transaction.""" - return self._client.post( - f"/api/v1/currency/billing/refund/{transaction_id}", - {"reason": reason}, - ) - - def get_billing_analytics(self, period: str = "monthly") -> dict: - """Get billing analytics.""" - return self._client.get("/api/v1/currency/billing/analytics", params={"period": period}) diff --git a/sdk/python/telecom_sdk/security.py b/sdk/python/telecom_sdk/security.py deleted file mode 100644 index 33501a0..0000000 --- a/sdk/python/telecom_sdk/security.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Security API client for fraud detection and SIM swap protection.""" - -from typing import List, Optional, Dict, Any -from datetime import datetime - -from .types import ( - FraudAlert, - FraudMetrics, - FraudAlertFilter, - FraudType, - FraudSeverity, -) - - -class SecurityAPI: - """Security API client for fraud detection and protection.""" - - def __init__(self, client): - self._client = client - - def analyze_transaction(self, transaction: Dict[str, Any]) -> Optional[FraudAlert]: - """Analyze a transaction for fraud.""" - response = self._client.post("/api/v1/security/fraud/analyze", transaction) - if response and response.get("id"): - return FraudAlert(**response) - return None - - def get_fraud_alerts(self, filter: Optional[FraudAlertFilter] = None) -> List[FraudAlert]: - """Get fraud alerts with optional filtering.""" - payload = {} - if filter: - payload = filter.model_dump(exclude_none=True) - response = self._client.post("/api/v1/security/fraud/alerts", payload) - return [FraudAlert(**item) for item in response] - - def update_alert_status( - self, alert_id: str, status: str, actions: Optional[List[str]] = None - ) -> dict: - """Update fraud alert status.""" - payload = {"status": status} - if actions: - payload["actions"] = actions - return self._client.put(f"/api/v1/security/fraud/alerts/{alert_id}", payload) - - def get_fraud_metrics(self, period: str = "monthly") -> FraudMetrics: - """Get fraud detection metrics.""" - response = self._client.get("/api/v1/security/fraud/metrics", params={"period": period}) - return FraudMetrics(**response) - - def get_fraud_patterns(self) -> dict: - """Get detected fraud patterns.""" - return self._client.get("/api/v1/security/fraud/patterns") - - def verify_sim_swap(self, profile_id: str, msisdn: str) -> dict: - """Verify SIM swap request.""" - return self._client.post( - "/api/v1/security/simswap/verify", - {"profile_id": profile_id, "msisdn": msisdn}, - ) - - def get_sim_swap_history(self, profile_id: str) -> dict: - """Get SIM swap history for a profile.""" - return self._client.get(f"/api/v1/security/simswap/history/{profile_id}") diff --git a/sdk/python/telecom_sdk/types.py b/sdk/python/telecom_sdk/types.py index 14bde5a..9462e19 100644 --- a/sdk/python/telecom_sdk/types.py +++ b/sdk/python/telecom_sdk/types.py @@ -79,11 +79,13 @@ class HealthStatus(BaseModel): checks: Dict[str, Dict[str, Any]] uptime: float = Field(ge=0.0) + class WebSocketMessage(BaseModel): type: str data: Dict[str, Any] timestamp: datetime + class UsageEvent(BaseModel): id: str subscriber_id: str @@ -93,6 +95,7 @@ class UsageEvent(BaseModel): timestamp: datetime metadata: Optional[Dict[str, Any]] = None + class RatingPlan(BaseModel): plan_id: str name: str @@ -134,212 +137,5 @@ class PaginatedResponse(BaseModel): total: int = Field(ge=0) page: int = Field(ge=1) page_size: int = Field(ge=1, le=100) - -class ChurnRiskLevel(str, Enum): - LOW = "low" - MEDIUM = "medium" - HIGH = "high" - CRITICAL = "critical" - - -class ChurnPrediction(BaseModel): - profile_id: str - risk_level: ChurnRiskLevel - risk_score: float = Field(ge=0.0, le=100.0) - predicted_churn_date: Optional[datetime] = None - reasons: List[str] - recommendations: List[str] - last_updated: datetime - - -class ChurnMetrics(BaseModel): - period: str - total_subscribers: int = Field(ge=0) - churned_subscribers: int = Field(ge=0) - churn_rate: float = Field(ge=0.0) - monthly_churn_rate: float = Field(ge=0.0) - annual_churn_rate: float = Field(ge=0.0) - average_tenure_days: float = Field(ge=0.0) - risk_distribution: Dict[ChurnRiskLevel, int] - generated_at: datetime - - -class FraudType(str, Enum): - ACCOUNT_TAKEOVER = "account_takeover" - SUBSCRIPTION_FRAUD = "subscription_fraud" - PAYMENT_FRAUD = "payment_fraud" - USAGE_ANOMALY = "usage_anomaly" - SIM_SWAP = "sim_swap" - - -class FraudSeverity(str, Enum): - LOW = "low" - MEDIUM = "medium" - HIGH = "high" - CRITICAL = "critical" - - -class FraudAlert(BaseModel): - id: str - type: FraudType - severity: FraudSeverity - profile_id: str - description: str - risk_score: float = Field(ge=0.0, le=100.0) - evidence: List[str] - ip_address: str - timestamp: datetime - status: str - actions_taken: List[str] - metadata: Dict[str, Any] - - -class FraudMetrics(BaseModel): - period: str - total_alerts: int = Field(ge=0) - resolved_alerts: int = Field(ge=0) - false_positives: int = Field(ge=0) - resolution_rate_pct: float = Field(ge=0.0) - false_positive_rate_pct: float = Field(ge=0.0) - by_type: Dict[FraudType, int] - by_severity: Dict[FraudSeverity, int] - generated_at: datetime - - -class FraudAlertFilter(BaseModel): - type: Optional[FraudType] = None - severity: Optional[FraudSeverity] = None - status: Optional[str] = None - from_date: Optional[datetime] = None - to_date: Optional[datetime] = None - limit: Optional[int] = Field(None, ge=1, le=1000) - - -class MarketMetrics(BaseModel): - period: str - total_market_size: int = Field(ge=0) - our_subscribers: int = Field(ge=0) - market_share_pct: float = Field(ge=0.0) - growth_rate_pct: float = Field(ge=0.0) - by_country: Dict[str, "CountryMetrics"] - by_carrier: Dict[str, "MarketCarrierMetrics"] - by_demographic: Dict[str, "DemoMetrics"] - competitor_analysis: Dict[str, "CompetitorMetrics"] - market_opportunities: List["MarketOpportunity"] - generated_at: datetime - - -class CountryMetrics(BaseModel): - country: str - market_size: int = Field(ge=0) - our_subscribers: int = Field(ge=0) - market_share_pct: float = Field(ge=0.0) - growth_rate_pct: float = Field(ge=0.0) - average_revenue: float = Field(ge=0.0) - - -class MarketCarrierMetrics(BaseModel): - carrier_id: str - carrier_name: str - subscribers: int = Field(ge=0) - market_share_pct: float = Field(ge=0.0) - average_revenue: float = Field(ge=0.0) - quality_score: float = Field(ge=0.0, le=100.0) - - -class DemoMetrics(BaseModel): - segment: str - subscribers: int = Field(ge=0) - market_share_pct: float = Field(ge=0.0) - average_revenue: float = Field(ge=0.0) - growth_rate_pct: float = Field(ge=0.0) - - -class CompetitorMetrics(BaseModel): - name: str - market_share_pct: float = Field(ge=0.0) - subscribers: int = Field(ge=0) - average_price: float = Field(ge=0.0) - strengths: List[str] - weaknesses: List[str] - - -class MarketOpportunity(BaseModel): - id: str - type: str - description: str - potential_size: int = Field(ge=0) - confidence: float = Field(ge=0.0, le=100.0) - required_actions: List[str] - - -class PredictiveMaintenanceMetrics(BaseModel): - period: str - total_assets: int = Field(ge=0) - healthy_assets: int = Field(ge=0) - at_risk_assets: int = Field(ge=0) - critical_assets: int = Field(ge=0) - overall_health_score: float = Field(ge=0.0, le=100.0) - by_asset_type: Dict[str, "AssetTypeMetrics"] - predicted_failures: List["PredictedFailure"] - maintenance_schedule: List["MaintenanceTask"] - generated_at: datetime - - -class AssetTypeMetrics(BaseModel): - asset_type: str - total: int = Field(ge=0) - healthy: int = Field(ge=0) - at_risk: int = Field(ge=0) - critical: int = Field(ge=0) - health_score: float = Field(ge=0.0, le=100.0) - - -class PredictedFailure(BaseModel): - asset_id: str - asset_type: str - failure_type: str - predicted_date: datetime - confidence: float = Field(ge=0.0, le=100.0) - recommended_actions: List[str] - - -class MaintenanceTask(BaseModel): - id: str - asset_id: str - task_type: str - priority: str - scheduled_date: datetime - estimated_duration_minutes: int = Field(ge=0) - description: str - status: str - - -class PricingOptimizationResult(BaseModel): - rate_plan_id: str - current_price: float = Field(ge=0.0) - optimal_price: float = Field(ge=0.0) - strategy: str - expected_revenue: float = Field(ge=0.0) - expected_demand: int = Field(ge=0) - price_change_pct: float - reasoning: List[str] - risks: List[str] - recommendations: List[str] - confidence: float = Field(ge=0.0, le=100.0) - generated_at: datetime - - -class PricingMetrics(BaseModel): - period: str - total_rate_plans: int = Field(ge=0) - optimized_rate_plans: int = Field(ge=0) - average_price_change_pct: float - expected_revenue_impact_pct: float - churn_rate_reduction_pct: float - price_elasticity: float - competitive_index: float - optimization_roi_pct: float - generated_at: datetime has_next: bool has_prev: bool diff --git a/sdk/ruby/lib/telecom_sdk.rb b/sdk/ruby/lib/telecom_sdk.rb index 3cb0d70..f453df5 100644 --- a/sdk/ruby/lib/telecom_sdk.rb +++ b/sdk/ruby/lib/telecom_sdk.rb @@ -10,9 +10,7 @@ require_relative "telecom_sdk/payments_api" require_relative "telecom_sdk/rating_plans_api" require_relative "telecom_sdk/system_api" -require_relative "telecom_sdk/analytics_api" -require_relative "telecom_sdk/security_api" -require_relative "telecom_sdk/currency_api" +require_relative "telecom_sdk/graphql_api" module TelecomSDK class Error < StandardError; end @@ -24,7 +22,7 @@ class RateLimitError < Error; end class ServerError < Error; end class Client - attr_reader :config, :subscribers, :usage, :payments, :rating_plans, :system, :analytics, :security, :currency + attr_reader :config, :subscribers, :usage, :payments, :rating_plans, :system, :graphql def initialize(config = {}) @config = Config.new(config) @@ -44,9 +42,7 @@ def initialize(config = {}) @payments = PaymentAPI.new(@http_client) @rating_plans = RatingPlanAPI.new(@http_client) @system = SystemAPI.new(@http_client) - @analytics = AnalyticsAPI.new(@http_client) - @security = SecurityAPI.new(@http_client) - @currency = CurrencyAPI.new(@http_client) + @graphql = GraphQLAPI.new(@http_client) end # Authentication methods diff --git a/sdk/ruby/lib/telecom_sdk/analytics_api.rb b/sdk/ruby/lib/telecom_sdk/analytics_api.rb deleted file mode 100644 index 88276f0..0000000 --- a/sdk/ruby/lib/telecom_sdk/analytics_api.rb +++ /dev/null @@ -1,73 +0,0 @@ -module TelecomSDK - class AnalyticsAPI - def initialize(http_client) - @http_client = http_client - end - - # Churn Analysis - - def predict_churn(profile_id) - @http_client.post("/api/v1/analytics/churn/predict", { profile_id: profile_id }) - end - - def get_churn_metrics(period: "monthly") - @http_client.get("/api/v1/analytics/churn/metrics", { period: period }) - end - - def get_at_risk_customers(risk_level:, limit: 100) - @http_client.post("/api/v1/analytics/churn/at-risk", { - risk_level: risk_level, - limit: limit - }) - end - - # Market Analytics - - def get_market_metrics(period: "monthly") - @http_client.get("/api/v1/analytics/market/metrics", { period: period }) - end - - def get_competitors - @http_client.get("/api/v1/analytics/market/competitors") - end - - def get_market_opportunities - @http_client.get("/api/v1/analytics/market/opportunities") - end - - # Predictive Maintenance - - def get_maintenance_metrics(period: "monthly") - @http_client.get("/api/v1/analytics/maintenance/metrics", { period: period }) - end - - def get_assets_health - @http_client.get("/api/v1/analytics/maintenance/assets") - end - - def get_maintenance_alerts - @http_client.get("/api/v1/analytics/maintenance/alerts") - end - - def predict_failure(asset_id) - @http_client.post("/api/v1/analytics/maintenance/predict/#{asset_id}", {}) - end - - # Pricing Optimization - - def get_pricing_metrics(period: "monthly") - @http_client.get("/api/v1/analytics/pricing/metrics", { period: period }) - end - - def optimize_pricing(rate_plan_ids:, strategy: "revenue_maximization") - @http_client.post("/api/v1/analytics/pricing/optimize", { - rate_plan_ids: rate_plan_ids, - strategy: strategy - }) - end - - def get_price_elasticity - @http_client.get("/api/v1/analytics/pricing/elasticity") - end - end -end diff --git a/sdk/ruby/lib/telecom_sdk/currency_api.rb b/sdk/ruby/lib/telecom_sdk/currency_api.rb deleted file mode 100644 index 4b08739..0000000 --- a/sdk/ruby/lib/telecom_sdk/currency_api.rb +++ /dev/null @@ -1,55 +0,0 @@ -module TelecomSDK - class CurrencyAPI - def initialize(http_client) - @http_client = http_client - end - - # Currency Conversion - - def convert(from:, to:, amount:) - @http_client.post("/api/v1/currency/convert", { - from: from, - to: to, - amount: amount - }) - end - - def get_exchange_rate(from:, to:) - @http_client.get("/api/v1/currency/exchange/#{from}/#{to}") - end - - def get_exchange_rate_history(from:, to:, days: 30) - @http_client.get("/api/v1/currency/exchange/#{from}/#{to}/history", { days: days }) - end - - def get_supported_currencies - @http_client.get("/api/v1/currency/currencies") - end - - def refresh_exchange_rates - @http_client.post("/api/v1/currency/exchange/refresh", {}) - end - - # Billing - - def process_billing(billing_data) - @http_client.post("/api/v1/currency/billing", billing_data) - end - - def get_billing_history(profile_id:, limit: 50) - @http_client.get("/api/v1/currency/billing/history/#{profile_id}", { limit: limit }) - end - - def get_billing_summary(profile_id:, period: "monthly") - @http_client.get("/api/v1/currency/billing/summary/#{profile_id}", { period: period }) - end - - def process_refund(transaction_id:, reason:) - @http_client.post("/api/v1/currency/billing/refund/#{transaction_id}", { reason: reason }) - end - - def get_billing_analytics(period: "monthly") - @http_client.get("/api/v1/currency/billing/analytics", { period: period }) - end - end -end diff --git a/sdk/ruby/lib/telecom_sdk/graphql_api.rb b/sdk/ruby/lib/telecom_sdk/graphql_api.rb new file mode 100644 index 0000000..af43914 --- /dev/null +++ b/sdk/ruby/lib/telecom_sdk/graphql_api.rb @@ -0,0 +1,14 @@ +module TelecomSDK + # API for GraphQL queries + class GraphQLAPI + def initialize(client) + @client = client + end + + def execute(query, variables = nil) + request = { query: query } + request[:variables] = variables if variables + @client.post("/graphql", request) + end + end +end diff --git a/sdk/ruby/lib/telecom_sdk/security_api.rb b/sdk/ruby/lib/telecom_sdk/security_api.rb deleted file mode 100644 index 6b9272b..0000000 --- a/sdk/ruby/lib/telecom_sdk/security_api.rb +++ /dev/null @@ -1,46 +0,0 @@ -module TelecomSDK - class SecurityAPI - def initialize(http_client) - @http_client = http_client - end - - # Fraud Detection - - def analyze_transaction(transaction) - @http_client.post("/api/v1/security/fraud/analyze", transaction) - end - - def get_fraud_alerts(filter: nil) - payload = filter&.compact || {} - @http_client.post("/api/v1/security/fraud/alerts", payload) - end - - def update_alert_status(alert_id:, status:, actions: []) - @http_client.put("/api/v1/security/fraud/alerts/#{alert_id}", { - status: status, - actions: actions - }) - end - - def get_fraud_metrics(period: "monthly") - @http_client.get("/api/v1/security/fraud/metrics", { period: period }) - end - - def get_fraud_patterns - @http_client.get("/api/v1/security/fraud/patterns") - end - - # SIM Swap Protection - - def verify_sim_swap(profile_id:, msisdn:) - @http_client.post("/api/v1/security/simswap/verify", { - profile_id: profile_id, - msisdn: msisdn - }) - end - - def get_sim_swap_history(profile_id) - @http_client.get("/api/v1/security/simswap/history/#{profile_id}") - end - end -end diff --git a/sdk/rust/src/api/analytics.rs b/sdk/rust/src/api/analytics.rs deleted file mode 100644 index 7b4ed64..0000000 --- a/sdk/rust/src/api/analytics.rs +++ /dev/null @@ -1,216 +0,0 @@ -use crate::client::HTTPClient; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// Analytics API for churn prediction, market analysis, and pricing optimization -#[derive(Clone)] -pub struct AnalyticsAPI { - client: HTTPClient, -} - -impl AnalyticsAPI { - pub fn new(client: HTTPClient) -> Self { - Self { client } - } - - // Churn Analysis - - /// Predict churn risk for a profile - pub async fn predict_churn(&self, profile_id: &str) -> Result { - let body = serde_json::json!({ "profile_id": profile_id }); - self.client.post("/api/v1/analytics/churn/predict", &body).await - } - - /// Get churn metrics - pub async fn get_churn_metrics(&self, period: Option<&str>) -> Result { - let mut params = HashMap::new(); - if let Some(p) = period { - params.insert("period".to_string(), p.to_string()); - } - self.client.get("/api/v1/analytics/churn/metrics", ¶ms).await - } - - /// Get at-risk customers - pub async fn get_at_risk_customers( - &self, - risk_level: ChurnRiskLevel, - limit: Option, - ) -> Result, crate::error::TelecomError> { - let mut body = serde_json::json!({ "risk_level": risk_level }); - if let Some(l) = limit { - body["limit"] = serde_json::Value::Number(l.into()); - } - self.client.post("/api/v1/analytics/churn/at-risk", &body).await - } - - // Market Analytics - - /// Get market metrics - pub async fn get_market_metrics(&self, period: Option<&str>) -> Result { - let mut params = HashMap::new(); - if let Some(p) = period { - params.insert("period".to_string(), p.to_string()); - } - self.client.get("/api/v1/analytics/market/metrics", ¶ms).await - } - - /// Get competitor analysis - pub async fn get_competitors(&self) -> Result, crate::error::TelecomError> { - self.client.get("/api/v1/analytics/market/competitors", &HashMap::new()).await - } - - /// Get market opportunities - pub async fn get_market_opportunities(&self) -> Result, crate::error::TelecomError> { - self.client.get("/api/v1/analytics/market/opportunities", &HashMap::new()).await - } - - // Predictive Maintenance - - /// Get maintenance metrics - pub async fn get_maintenance_metrics(&self, period: Option<&str>) -> Result { - let mut params = HashMap::new(); - if let Some(p) = period { - params.insert("period".to_string(), p.to_string()); - } - self.client.get("/api/v1/analytics/maintenance/metrics", ¶ms).await - } - - /// Get assets health - pub async fn get_assets_health(&self) -> Result, crate::error::TelecomError> { - self.client.get("/api/v1/analytics/maintenance/assets", &HashMap::new()).await - } - - /// Get maintenance alerts - pub async fn get_maintenance_alerts(&self) -> Result, crate::error::TelecomError> { - self.client.get("/api/v1/analytics/maintenance/alerts", &HashMap::new()).await - } - - /// Predict failure for an asset - pub async fn predict_failure(&self, asset_id: &str) -> Result, crate::error::TelecomError> { - self.client.post(&format!("/api/v1/analytics/maintenance/predict/{}", asset_id), &serde_json::Value::Null).await - } - - // Pricing Optimization - - /// Get pricing metrics - pub async fn get_pricing_metrics(&self, period: Option<&str>) -> Result { - let mut params = HashMap::new(); - if let Some(p) = period { - params.insert("period".to_string(), p.to_string()); - } - self.client.get("/api/v1/analytics/pricing/metrics", ¶ms).await - } - - /// Optimize pricing for rate plans - pub async fn optimize_pricing( - &self, - rate_plan_ids: Vec, - strategy: Option<&str>, - ) -> Result, crate::error::TelecomError> { - let mut body = serde_json::json!({ "rate_plan_ids": rate_plan_ids }); - if let Some(s) = strategy { - body["strategy"] = serde_json::Value::String(s.to_string()); - } - self.client.post("/api/v1/analytics/pricing/optimize", &body).await - } - - /// Get price elasticity data - pub async fn get_price_elasticity(&self) -> Result, crate::error::TelecomError> { - self.client.get("/api/v1/analytics/pricing/elasticity", &HashMap::new()).await - } -} - -// Analytics Types - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChurnPrediction { - pub profile_id: String, - pub risk_level: String, - pub risk_score: f64, - pub predicted_churn_date: Option, - pub reasons: Vec, - pub recommendations: Vec, - pub last_updated: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChurnMetrics { - pub period: String, - pub total_subscribers: i64, - pub churned_subscribers: i64, - pub churn_rate: f64, - pub monthly_churn_rate: f64, - pub annual_churn_rate: f64, - pub average_tenure_days: f64, - pub risk_distribution: HashMap, - pub generated_at: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MarketMetrics { - pub period: String, - pub total_market_size: i64, - pub our_subscribers: i64, - pub market_share: f64, - pub growth_rate: f64, - pub by_country: HashMap, - pub generated_at: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MaintenanceMetrics { - pub period: String, - pub total_assets: i64, - pub healthy_assets: i64, - pub assets_needing_attention: i64, - pub uptime: f64, - pub mean_time_to_failure: f64, - pub mean_time_to_repair: f64, - pub generated_at: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PricingMetrics { - pub period: String, - pub total_revenue: f64, - pub arpu: f64, - pub price_elasticity: f64, - pub competitive_index: f64, - pub optimization_roi: f64, - pub generated_at: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PricingOptimizationResult { - pub rate_plan_id: String, - pub strategy: String, - pub current_price: f64, - pub optimal_price: f64, - pub price_change_pct: f64, - pub expected_revenue: f64, - pub expected_demand: f64, - pub confidence: f64, - pub reasoning: Vec, - pub risks: Vec, - pub recommendations: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum ChurnRiskLevel { - Low, - Medium, - High, - Critical, -} - -impl AsRef for ChurnRiskLevel { - fn as_ref(&self) -> &str { - match self { - ChurnRiskLevel::Low => "low", - ChurnRiskLevel::Medium => "medium", - ChurnRiskLevel::High => "high", - ChurnRiskLevel::Critical => "critical", - } - } -} diff --git a/sdk/rust/src/api/currency.rs b/sdk/rust/src/api/currency.rs deleted file mode 100644 index 3583621..0000000 --- a/sdk/rust/src/api/currency.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::client::HTTPClient; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// Currency and Billing API -#[derive(Clone)] -pub struct CurrencyAPI { - client: HTTPClient, -} - -impl CurrencyAPI { - pub fn new(client: HTTPClient) -> Self { - Self { client } - } - - // Currency Conversion - - /// Convert currency - pub async fn convert( - &self, - from: &str, - to: &str, - amount: f64, - ) -> Result { - let body = serde_json::json!({ - "from": from, - "to": to, - "amount": amount - }); - self.client.post("/api/v1/currency/convert", &body).await - } - - /// Get exchange rate between currencies - pub async fn get_exchange_rate(&self, from: &str, to: &str) -> Result { - self.client.get(&format!("/api/v1/currency/exchange/{}/{}", from, to), &HashMap::new()).await - } - - /// Get exchange rate history - pub async fn get_exchange_rate_history( - &self, - from: &str, - to: &str, - days: Option, - ) -> Result, crate::error::TelecomError> { - let mut params = HashMap::new(); - if let Some(d) = days { - params.insert("days".to_string(), d.to_string()); - } - self.client.get(&format!("/api/v1/currency/exchange/{}/{}", from, to), ¶ms).await - } - - /// Get supported currencies - pub async fn get_supported_currencies(&self) -> Result, crate::error::TelecomError> { - self.client.get("/api/v1/currency/currencies", &HashMap::new()).await - } - - /// Refresh exchange rates - pub async fn refresh_exchange_rates(&self) -> Result, crate::error::TelecomError> { - self.client.post("/api/v1/currency/exchange/refresh", &serde_json::Value::Null).await - } - - // Billing - - /// Process billing transaction - pub async fn process_billing( - &self, - billing_data: &serde_json::Value, - ) -> Result { - self.client.post("/api/v1/currency/billing", billing_data).await - } - - /// Get billing history for a profile - pub async fn get_billing_history( - &self, - profile_id: &str, - limit: Option, - ) -> Result, crate::error::TelecomError> { - let mut params = HashMap::new(); - if let Some(l) = limit { - params.insert("limit".to_string(), l.to_string()); - } - self.client.get(&format!("/api/v1/currency/billing/history/{}", profile_id), ¶ms).await - } - - /// Get billing summary for a profile - pub async fn get_billing_summary( - &self, - profile_id: &str, - period: Option<&str>, - ) -> Result { - let mut params = HashMap::new(); - if let Some(p) = period { - params.insert("period".to_string(), p.to_string()); - } - self.client.get(&format!("/api/v1/currency/billing/summary/{}", profile_id), ¶ms).await - } - - /// Process refund - pub async fn process_refund( - &self, - transaction_id: &str, - reason: &str, - ) -> Result { - let body = serde_json::json!({ "reason": reason }); - self.client.post(&format!("/api/v1/currency/billing/refund/{}", transaction_id), &body).await - } - - /// Get billing analytics - pub async fn get_billing_analytics(&self, period: Option<&str>) -> Result, crate::error::TelecomError> { - let mut params = HashMap::new(); - if let Some(p) = period { - params.insert("period".to_string(), p.to_string()); - } - self.client.get("/api/v1/currency/billing/analytics", ¶ms).await - } -} - -// Currency Types - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConvertRequest { - pub from: String, - pub to: String, - pub amount: f64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConvertResponse { - pub from: String, - pub to: String, - pub amount: f64, - pub converted: f64, - pub rate: f64, - pub timestamp: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExchangeRate { - pub from: String, - pub to: String, - pub rate: f64, - pub timestamp: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Currency { - pub code: String, - pub name: String, - pub symbol: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BillingTransaction { - pub id: String, - #[serde(rename = "profile_id")] - pub profile_id: String, - pub amount: f64, - pub currency: String, - #[serde(rename = "type")] - pub transaction_type: String, - pub status: String, - pub description: String, - #[serde(rename = "created_at")] - pub created_at: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BillingSummary { - #[serde(rename = "profile_id")] - pub profile_id: String, - pub period: String, - #[serde(rename = "total_amount")] - pub total_amount: f64, - pub currency: String, - #[serde(rename = "transaction_count")] - pub transaction_count: u32, - pub breakdown: HashMap, -} diff --git a/sdk/rust/src/api/graphql.rs b/sdk/rust/src/api/graphql.rs new file mode 100644 index 0000000..ad251d2 --- /dev/null +++ b/sdk/rust/src/api/graphql.rs @@ -0,0 +1,23 @@ +use crate::client::HTTPClient; +use crate::error::TelecomError; +use crate::types::GraphQLRequest; +use std::collections::HashMap; + +pub struct GraphQLAPI { + client: HTTPClient, +} + +impl GraphQLAPI { + pub fn new(client: HTTPClient) -> Self { + Self { client } + } + + pub async fn execute( + &self, + query: String, + variables: Option>, + ) -> Result, TelecomError> { + let req = GraphQLRequest { query, variables }; + self.client.post("/graphql", Some(&req)).await + } +} diff --git a/sdk/rust/src/api/mod.rs b/sdk/rust/src/api/mod.rs index 839b5a8..7221730 100644 --- a/sdk/rust/src/api/mod.rs +++ b/sdk/rust/src/api/mod.rs @@ -1,17 +1,13 @@ -pub mod analytics; -pub mod currency; +pub mod graphql; pub mod payments; pub mod rating_plans; -pub mod security; pub mod subscribers; pub mod system; pub mod usage; -pub use analytics::AnalyticsAPI; -pub use currency::CurrencyAPI; +pub use graphql::GraphQLAPI; pub use payments::PaymentAPI; pub use rating_plans::RatingPlanAPI; -pub use security::SecurityAPI; pub use subscribers::SubscriberAPI; pub use system::SystemAPI; pub use usage::UsageAPI; diff --git a/sdk/rust/src/api/security.rs b/sdk/rust/src/api/security.rs deleted file mode 100644 index 739b51c..0000000 --- a/sdk/rust/src/api/security.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::client::HTTPClient; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// Security API for fraud detection and SIM swap protection -#[derive(Clone)] -pub struct SecurityAPI { - client: HTTPClient, -} - -impl SecurityAPI { - pub fn new(client: HTTPClient) -> Self { - Self { client } - } - - // Fraud Detection - - /// Analyze a transaction for fraud - pub async fn analyze_transaction( - &self, - transaction: &serde_json::Value, - ) -> Result, crate::error::TelecomError> { - match self.client.post("/api/v1/security/fraud/analyze", transaction).await { - Ok(alert) => Ok(Some(alert)), - Err(crate::error::TelecomError::APIError(404, _)) => Ok(None), - Err(e) => Err(e), - } - } - - /// Get fraud alerts with filtering - pub async fn get_fraud_alerts( - &self, - filter: Option, - ) -> Result, crate::error::TelecomError> { - let body = if let Some(f) = filter { - serde_json::to_value(f).unwrap_or_default() - } else { - serde_json::Value::Null - }; - self.client.post("/api/v1/security/fraud/alerts", &body).await - } - - /// Update fraud alert status - pub async fn update_alert_status( - &self, - alert_id: &str, - status: &str, - actions: Vec, - ) -> Result, crate::error::TelecomError> { - let body = serde_json::json!({ - "status": status, - "actions": actions - }); - self.client.put(&format!("/api/v1/security/fraud/alerts/{}", alert_id), &body).await - } - - /// Get fraud detection metrics - pub async fn get_fraud_metrics(&self, period: Option<&str>) -> Result { - let mut params = HashMap::new(); - if let Some(p) = period { - params.insert("period".to_string(), p.to_string()); - } - self.client.get("/api/v1/security/fraud/metrics", ¶ms).await - } - - /// Get detected fraud patterns - pub async fn get_fraud_patterns(&self) -> Result, crate::error::TelecomError> { - self.client.get("/api/v1/security/fraud/patterns", &HashMap::new()).await - } - - // SIM Swap Protection - - /// Verify SIM swap request - pub async fn verify_sim_swap( - &self, - profile_id: &str, - msisdn: &str, - ) -> Result, crate::error::TelecomError> { - let body = serde_json::json!({ - "profile_id": profile_id, - "msisdn": msisdn - }); - self.client.post("/api/v1/security/simswap/verify", &body).await - } - - /// Get SIM swap history for a profile - pub async fn get_sim_swap_history(&self, profile_id: &str) -> Result, crate::error::TelecomError> { - self.client.get(&format!("/api/v1/security/simswap/history/{}", profile_id), &HashMap::new()).await - } -} - -// Security Types - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FraudAlert { - pub id: String, - #[serde(rename = "type")] - pub alert_type: String, - pub severity: String, - #[serde(rename = "profile_id")] - pub profile_id: String, - pub description: String, - #[serde(rename = "risk_score")] - pub risk_score: f64, - pub evidence: Vec, - #[serde(rename = "ip_address")] - pub ip_address: String, - pub timestamp: String, - pub status: String, - #[serde(rename = "actions_taken")] - pub actions_taken: Vec, - pub metadata: HashMap, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FraudMetrics { - pub period: String, - #[serde(rename = "total_alerts")] - pub total_alerts: i64, - #[serde(rename = "resolved_alerts")] - pub resolved_alerts: i64, - #[serde(rename = "false_positives")] - pub false_positives: i64, - #[serde(rename = "resolution_rate")] - pub resolution_rate: f64, - #[serde(rename = "false_positive_rate")] - pub false_positive_rate: f64, - #[serde(rename = "by_type")] - pub by_type: HashMap, - #[serde(rename = "by_severity")] - pub by_severity: HashMap, - #[serde(rename = "generated_at")] - pub generated_at: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FraudAlertFilter { - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub alert_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub severity: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - #[serde(default = "default_limit")] - pub limit: u32, -} - -fn default_limit() -> u32 { - 50 -} - -impl Default for FraudAlertFilter { - fn default() -> Self { - Self { - alert_type: None, - severity: None, - status: None, - limit: 50, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum FraudType { - AccountTakeover, - SubscriptionFraud, - PaymentFraud, - UsageAnomaly, - SimSwap, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum FraudSeverity { - Low, - Medium, - High, - Critical, -} diff --git a/sdk/rust/src/lib.rs b/sdk/rust/src/lib.rs index 9ddbbc1..ec56f49 100644 --- a/sdk/rust/src/lib.rs +++ b/sdk/rust/src/lib.rs @@ -14,7 +14,7 @@ use error::TelecomError; use std::time::Duration; use types::*; -pub use api::{AnalyticsAPI, CurrencyAPI, PaymentAPI, RatingPlanAPI, SecurityAPI, SubscriberAPI, SystemAPI, UsageAPI}; +pub use api::{GraphQLAPI, PaymentAPI, RatingPlanAPI, SubscriberAPI, SystemAPI, UsageAPI}; /// Telecom Platform SDK client #[derive(Clone)] @@ -28,9 +28,7 @@ pub struct TelecomClient { pub payments: PaymentAPI, pub rating_plans: RatingPlanAPI, pub system: SystemAPI, - pub analytics: AnalyticsAPI, - pub security: SecurityAPI, - pub currency: CurrencyAPI, + pub graphql: GraphQLAPI, } #[derive(Debug, Clone)] @@ -75,9 +73,7 @@ impl TelecomClient { let payments = PaymentAPI::new(http_client.clone()); let rating_plans = RatingPlanAPI::new(http_client.clone()); let system = SystemAPI::new(http_client.clone()); - let analytics = AnalyticsAPI::new(http_client.clone()); - let security = SecurityAPI::new(http_client.clone()); - let currency = CurrencyAPI::new(http_client.clone()); + let graphql = GraphQLAPI::new(http_client.clone()); Self { auth_provider, @@ -87,9 +83,7 @@ impl TelecomClient { payments, rating_plans, system, - analytics, - security, - currency, + graphql, } } diff --git a/sdk/swift/Sources/TelecomSDK/AnalyticsAPI.swift b/sdk/swift/Sources/TelecomSDK/AnalyticsAPI.swift deleted file mode 100644 index 5c1363c..0000000 --- a/sdk/swift/Sources/TelecomSDK/AnalyticsAPI.swift +++ /dev/null @@ -1,199 +0,0 @@ -import Foundation - -/// Analytics API for churn prediction, market analysis, and pricing optimization -public class AnalyticsAPI { - private let client: HTTPClient - - public init(client: HTTPClient) { - self.client = client - } - - // MARK: - Churn Analysis - - /// Predict churn risk for a profile - public func predictChurn(profileId: String) async throws -> ChurnPrediction { - return try await client.post( - "/api/v1/analytics/churn/predict", - body: ["profile_id": profileId], - responseType: ChurnPrediction.self - ) - } - - /// Get churn metrics - public func getChurnMetrics(period: String = "monthly") async throws -> ChurnMetrics { - return try await client.get( - "/api/v1/analytics/churn/metrics", - parameters: ["period": period], - responseType: ChurnMetrics.self - ) - } - - /// Get at-risk customers - public func getAtRiskCustomers( - riskLevel: ChurnRiskLevel, - limit: Int = 100 - ) async throws -> [ChurnPrediction] { - return try await client.post( - "/api/v1/analytics/churn/at-risk", - body: [ - "risk_level": riskLevel.rawValue, - "limit": limit - ], - responseType: [ChurnPrediction].self - ) - } - - // MARK: - Market Analytics - - /// Get market metrics - public func getMarketMetrics(period: String = "monthly") async throws -> MarketMetrics { - return try await client.get( - "/api/v1/analytics/market/metrics", - parameters: ["period": period], - responseType: MarketMetrics.self - ) - } - - /// Get competitor analysis - public func getCompetitors() async throws -> [String: Any] { - return try await client.get("/api/v1/analytics/market/competitors") - } - - /// Get market opportunities - public func getMarketOpportunities() async throws -> [String: Any] { - return try await client.get("/api/v1/analytics/market/opportunities") - } - - // MARK: - Predictive Maintenance - - /// Get maintenance metrics - public func getMaintenanceMetrics(period: String = "monthly") async throws -> MaintenanceMetrics { - return try await client.get( - "/api/v1/analytics/maintenance/metrics", - parameters: ["period": period], - responseType: MaintenanceMetrics.self - ) - } - - /// Get assets health - public func getAssetsHealth() async throws -> [String: Any] { - return try await client.get("/api/v1/analytics/maintenance/assets") - } - - /// Get maintenance alerts - public func getMaintenanceAlerts() async throws -> [String: Any] { - return try await client.get("/api/v1/analytics/maintenance/alerts") - } - - /// Predict failure for an asset - public func predictFailure(assetId: String) async throws -> [String: Any] { - return try await client.post("/api/v1/analytics/maintenance/predict/\(assetId)", body: [:]) - } - - // MARK: - Pricing Optimization - - /// Get pricing metrics - public func getPricingMetrics(period: String = "monthly") async throws -> PricingMetrics { - return try await client.get( - "/api/v1/analytics/pricing/metrics", - parameters: ["period": period], - responseType: PricingMetrics.self - ) - } - - /// Optimize pricing for rate plans - public func optimizePricing( - ratePlanIds: [String], - strategy: String = "revenue_maximization" - ) async throws -> [PricingOptimizationResult] { - return try await client.post( - "/api/v1/analytics/pricing/optimize", - body: [ - "rate_plan_ids": ratePlanIds, - "strategy": strategy - ], - responseType: [PricingOptimizationResult].self - ) - } - - /// Get price elasticity data - public func getPriceElasticity() async throws -> [String: Any] { - return try await client.get("/api/v1/analytics/pricing/elasticity") - } -} - -// MARK: - Analytics Types - -public struct ChurnPrediction: Codable { - public let profileId: String - public let riskLevel: String - public let riskScore: Double - public let predictedChurnDate: String? - public let reasons: [String] - public let recommendations: [String] - public let lastUpdated: String -} - -public struct ChurnMetrics: Codable { - public let period: String - public let totalSubscribers: Int64 - public let churnedSubscribers: Int64 - public let churnRate: Double - public let monthlyChurnRate: Double - public let annualChurnRate: Double - public let averageTenureDays: Double - public let riskDistribution: [String: Int64] - public let generatedAt: String -} - -public struct MarketMetrics: Codable { - public let period: String - public let totalMarketSize: Int64 - public let ourSubscribers: Int64 - public let marketShare: Double - public let growthRate: Double - public let byCountry: [String: [String: Any]] - public let generatedAt: String -} - -public struct MaintenanceMetrics: Codable { - public let period: String - public let totalAssets: Int64 - public let healthyAssets: Int64 - public let assetsNeedingAttention: Int64 - public let uptime: Double - public let meanTimeToFailure: Double - public let meanTimeToRepair: Double - public let generatedAt: String -} - -public struct PricingMetrics: Codable { - public let period: String - public let totalRevenue: Double - public let arpu: Double - public let priceElasticity: Double - public let competitiveIndex: Double - public let optimizationRoi: Double - public let generatedAt: String -} - -public struct PricingOptimizationResult: Codable { - public let ratePlanId: String - public let strategy: String - public let currentPrice: Double - public let optimalPrice: Double - public let priceChangePct: Double - public let expectedRevenue: Double - public let expectedDemand: Double - public let confidence: Double - public let reasoning: [String] - public let risks: [String] - public let recommendations: [String] -} - -public enum ChurnRiskLevel: String, CaseIterable { - case low = "low" - case medium = "medium" - case high = "high" - case critical = "critical" -} diff --git a/sdk/swift/Sources/TelecomSDK/CurrencyAPI.swift b/sdk/swift/Sources/TelecomSDK/CurrencyAPI.swift deleted file mode 100644 index ed26c32..0000000 --- a/sdk/swift/Sources/TelecomSDK/CurrencyAPI.swift +++ /dev/null @@ -1,151 +0,0 @@ -import Foundation - -/// Currency and Billing API -public class CurrencyAPI { - private let client: HTTPClient - - public init(client: HTTPClient) { - self.client = client - } - - // MARK: - Currency Conversion - - /// Convert currency - public func convert(from: String, to: String, amount: Double) async throws -> ConvertResponse { - return try await client.post( - "/api/v1/currency/convert", - body: [ - "from": from, - "to": to, - "amount": amount - ], - responseType: ConvertResponse.self - ) - } - - /// Get exchange rate between currencies - public func getExchangeRate(from: String, to: String) async throws -> ExchangeRate { - return try await client.get( - "/api/v1/currency/exchange/\(from)/\(to)", - responseType: ExchangeRate.self - ) - } - - /// Get exchange rate history - public func getExchangeRateHistory(from: String, to: String, days: Int = 30) async throws -> [ExchangeRate] { - return try await client.get( - "/api/v1/currency/exchange/\(from)/\(to)/history", - parameters: ["days": days], - responseType: [ExchangeRate].self - ) - } - - /// Get supported currencies - public func getSupportedCurrencies() async throws -> [Currency] { - return try await client.get( - "/api/v1/currency/currencies", - responseType: [Currency].self - ) - } - - /// Refresh exchange rates - public func refreshExchangeRates() async throws -> [String: Any] { - return try await client.post("/api/v1/currency/exchange/refresh", body: [:]) - } - - // MARK: - Billing - - /// Process billing transaction - public func processBilling(_ billingData: [String: Any]) async throws -> BillingTransaction { - return try await client.post( - "/api/v1/currency/billing", - body: billingData, - responseType: BillingTransaction.self - ) - } - - /// Get billing history for a profile - public func getBillingHistory(profileId: String, limit: Int = 50) async throws -> [BillingTransaction] { - return try await client.get( - "/api/v1/currency/billing/history/\(profileId)", - parameters: ["limit": limit], - responseType: [BillingTransaction].self - ) - } - - /// Get billing summary for a profile - public func getBillingSummary(profileId: String, period: String = "monthly") async throws -> BillingSummary { - return try await client.get( - "/api/v1/currency/billing/summary/\(profileId)", - parameters: ["period": period], - responseType: BillingSummary.self - ) - } - - /// Process refund - public func processRefund(transactionId: String, reason: String) async throws -> BillingTransaction { - return try await client.post( - "/api/v1/currency/billing/refund/\(transactionId)", - body: ["reason": reason], - responseType: BillingTransaction.self - ) - } - - /// Get billing analytics - public func getBillingAnalytics(period: String = "monthly") async throws -> [String: Any] { - return try await client.get( - "/api/v1/currency/billing/analytics", - parameters: ["period": period] - ) - } -} - -// MARK: - Currency Types - -public struct ConvertRequest: Codable { - public let from: String - public let to: String - public let amount: Double -} - -public struct ConvertResponse: Codable { - public let from: String - public let to: String - public let amount: Double - public let converted: Double - public let rate: Double - public let timestamp: String -} - -public struct ExchangeRate: Codable { - public let from: String - public let to: String - public let rate: Double - public let timestamp: String -} - -public struct Currency: Codable { - public let code: String - public let name: String - public let symbol: String -} - -public struct BillingTransaction: Codable { - public let id: String - public let profileId: String - public let amount: Double - public let currency: String - public let type: String - public let status: String - public let description: String - public let createdAt: String -} - -public struct BillingSummary: Codable { - public let profileId: String - public let period: String - public let totalAmount: Double - public let currency: String - public let transactionCount: Int - public let breakdown: [String: Double] -} diff --git a/sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift b/sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift new file mode 100644 index 0000000..a2338c2 --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift @@ -0,0 +1,16 @@ +import Foundation + +/// API for GraphQL queries +public class GraphQLAPI { + private let client: HTTPClient + + public init(client: HTTPClient) { + self.client = client + } + + /// Execute a GraphQL query + public func execute(query: String, variables: [String: Any]? = nil) async throws -> [String: Any] { + let request = GraphQLRequest(query: query, variables: variables) + return try await client.post(path: "/graphql", body: request) + } +} diff --git a/sdk/swift/Sources/TelecomSDK/SecurityAPI.swift b/sdk/swift/Sources/TelecomSDK/SecurityAPI.swift deleted file mode 100644 index 15dde98..0000000 --- a/sdk/swift/Sources/TelecomSDK/SecurityAPI.swift +++ /dev/null @@ -1,181 +0,0 @@ -import Foundation - -/// Security API for fraud detection and SIM swap protection -public class SecurityAPI { - private let client: HTTPClient - - public init(client: HTTPClient) { - self.client = client - } - - /// Analyze a transaction for fraud - public func analyzeTransaction(_ transaction: [String: Any]) async throws -> FraudAlert? { - do { - return try await client.post( - "/api/v1/security/fraud/analyze", - body: transaction, - responseType: FraudAlert.self - ) - } catch { - if case HTTPClient.APIError.notFound = error { - return nil - } - throw error - } - } - - /// Get fraud alerts with filtering - public func getFraudAlerts(filter: FraudAlertFilter? = nil) async throws -> [FraudAlert] { - var body: [String: Any] = [:] - if let filter = filter { - if let type = filter.type { body["type"] = type } - if let severity = filter.severity { body["severity"] = severity } - if let status = filter.status { body["status"] = status } - body["limit"] = filter.limit - } - - return try await client.post( - "/api/v1/security/fraud/alerts", - body: body, - responseType: [FraudAlert].self - ) - } - - /// Update fraud alert status - public func updateAlertStatus( - alertId: String, - status: String, - actions: [String] = [] - ) async throws -> [String: Any] { - return try await client.put( - "/api/v1/security/fraud/alerts/\(alertId)", - body: [ - "status": status, - "actions": actions - ] - ) - } - - /// Get fraud detection metrics - public func getFraudMetrics(period: String = "monthly") async throws -> FraudMetrics { - return try await client.get( - "/api/v1/security/fraud/metrics", - parameters: ["period": period], - responseType: FraudMetrics.self - ) - } - - /// Get detected fraud patterns - public func getFraudPatterns() async throws -> [String: Any] { - return try await client.get("/api/v1/security/fraud/patterns") - } - - /// Verify SIM swap request - public func verifySIMSwap(profileId: String, msisdn: String) async throws -> [String: Any] { - return try await client.post( - "/api/v1/security/simswap/verify", - body: [ - "profile_id": profileId, - "msisdn": msisdn - ] - ) - } - - /// Get SIM swap history for a profile - public func getSIMSwapHistory(profileId: String) async throws -> [String: Any] { - return try await client.get("/api/v1/security/simswap/history/\(profileId)") - } -} - -public struct FraudAlert: Codable { - public let id: String - public let type: String - public let severity: String - public let profileId: String - public let description: String - public let riskScore: Double - public let evidence: [String] - public let ipAddress: String - public let timestamp: String - public let status: String - public let actionsTaken: [String] - public let metadata: [String: Any] - - enum CodingKeys: String, CodingKey { - case id, type, severity, profileId, description, riskScore - case evidence, ipAddress, timestamp, status, actionsTaken, metadata - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: .id) - type = try container.decode(String.self, forKey: .type) - severity = try container.decode(String.self, forKey: .severity) - profileId = try container.decode(String.self, forKey: .profileId) - description = try container.decode(String.self, forKey: .description) - riskScore = try container.decode(Double.self, forKey: .riskScore) - evidence = try container.decode([String].self, forKey: .evidence) - ipAddress = try container.decode(String.self, forKey: .ipAddress) - timestamp = try container.decode(String.self, forKey: .timestamp) - status = try container.decode(String.self, forKey: .status) - actionsTaken = try container.decode([String].self, forKey: .actionsTaken) - metadata = try container.decode([String: Any].self, forKey: .metadata) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(type, forKey: .type) - try container.encode(severity, forKey: .severity) - try container.encode(profileId, forKey: .profileId) - try container.encode(description, forKey: .description) - try container.encode(riskScore, forKey: .riskScore) - try container.encode(evidence, forKey: .evidence) - try container.encode(ipAddress, forKey: .ipAddress) - try container.encode(timestamp, forKey: .timestamp) - try container.encode(status, forKey: .status) - try container.encode(actionsTaken, forKey: .actionsTaken) - try container.encode(metadata, forKey: .metadata) - } -} - -public struct FraudMetrics: Codable { - public let period: String - public let totalAlerts: Int64 - public let resolvedAlerts: Int64 - public let falsePositives: Int64 - public let resolutionRate: Double - public let falsePositiveRate: Double - public let byType: [String: Int64] - public let bySeverity: [String: Int64] - public let generatedAt: String -} - -public struct FraudAlertFilter { - public let type: String? - public let severity: String? - public let status: String? - public let limit: Int - - public init(type: String? = nil, severity: String? = nil, status: String? = nil, limit: Int = 50) { - self.type = type - self.severity = severity - self.status = status - self.limit = limit - } -} - -public enum FraudType: String, CaseIterable { - case accountTakeover = "account_takeover" - case subscriptionFraud = "subscription_fraud" - case paymentFraud = "payment_fraud" - case usageAnomaly = "usage_anomaly" - case simSwap = "sim_swap" -} - -public enum FraudSeverity: String, CaseIterable { - case low = "low" - case medium = "medium" - case high = "high" - case critical = "critical" -} diff --git a/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift b/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift index ba5f8fc..6270367 100644 --- a/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift +++ b/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift @@ -19,9 +19,9 @@ public class TelecomSDK { public let payments: PaymentAPI public let ratingPlans: RatingPlanAPI public let system: SystemAPI - public let analytics: AnalyticsAPI - public let security: SecurityAPI - public let currency: CurrencyAPI + public let graphql: GraphQLAPI + + // MARK: - Initialization /// Initialize a new TelecomSDK instance /// - Parameter config: Configuration for the SDK @@ -36,9 +36,7 @@ public class TelecomSDK { self.payments = PaymentAPI(client: httpClient) self.ratingPlans = RatingPlanAPI(client: httpClient) self.system = SystemAPI(client: httpClient) - self.analytics = AnalyticsAPI(client: httpClient) - self.security = SecurityAPI(client: httpClient) - self.currency = CurrencyAPI(client: httpClient) + self.graphql = GraphQLAPI(client: httpClient) } // MARK: - Authentication Methods diff --git a/sdk/typescript/src/analytics.ts b/sdk/typescript/src/analytics.ts deleted file mode 100644 index 3bf4472..0000000 --- a/sdk/typescript/src/analytics.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { HTTPClient } from './api/http-client'; -import { - ChurnPrediction, - ChurnMetrics, - ChurnRiskLevel, - MarketMetrics, - PredictiveMaintenanceMetrics, - PricingOptimizationResult, - PricingMetrics -} from './types'; - -export class AnalyticsAPI { - constructor(private client: HTTPClient) {} - - async predictChurn(profileId: string): Promise { - return this.client.request({ - method: 'POST', - endpoint: '/api/v1/analytics/churn/predict', - data: { profileId } - }); - } - - async getChurnMetrics(period: string): Promise { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/analytics/churn/metrics', - params: { period } - }); - } - - async getAtRiskCustomers(riskLevel: ChurnRiskLevel, limit: number): Promise { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/analytics/churn/at-risk', - params: { riskLevel, limit: limit.toString() } - }); - } - - async getMarketMetrics(period: string): Promise { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/analytics/market/metrics', - params: { period } - }); - } - - async getPredictiveMaintenanceMetrics(period: string): Promise { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/analytics/maintenance/metrics', - params: { period } - }); - } - - async getPricingMetrics(period: string): Promise { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/analytics/pricing/metrics', - params: { period } - }); - } - - async optimizePrice(ratePlanId: string, strategy: string): Promise { - return this.client.request({ - method: 'POST', - endpoint: '/api/v1/analytics/pricing/optimize', - data: { ratePlanId, strategy } - }); - } -} diff --git a/sdk/typescript/src/currency.ts b/sdk/typescript/src/currency.ts deleted file mode 100644 index e4169a6..0000000 --- a/sdk/typescript/src/currency.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { HTTPClient } from './api/http-client'; - -export interface ConvertRequest { - from: string; - to: string; - amount: number; -} - -export interface ConvertResponse { - from: string; - to: string; - amount: number; - converted: number; - rate: number; - timestamp: string; -} - -export interface ExchangeRate { - from: string; - to: string; - rate: number; - timestamp: string; -} - -export interface BillingTransaction { - id: string; - profileId: string; - amount: number; - currency: string; - type: string; - status: string; - description: string; - createdAt: string; -} - -export interface BillingSummary { - profileId: string; - period: string; - totalAmount: number; - currency: string; - transactionCount: number; - breakdown: Record; -} - -export class CurrencyAPI { - constructor(private client: HTTPClient) {} - - async convert(request: ConvertRequest): Promise { - return this.client.request({ - method: 'POST', - endpoint: '/api/v1/currency/convert', - data: request - }); - } - - async getExchangeRate(from: string, to: string): Promise { - return this.client.request({ - method: 'GET', - endpoint: `/api/v1/currency/exchange/${from}/${to}` - }); - } - - async getExchangeRateHistory(from: string, to: string, days: number = 30): Promise { - return this.client.request({ - method: 'GET', - endpoint: `/api/v1/currency/exchange/${from}/${to}/history`, - params: { days: days.toString() } - }); - } - - async getSupportedCurrencies(): Promise { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/currency/currencies' - }); - } - - async refreshExchangeRates(): Promise { - return this.client.request({ - method: 'POST', - endpoint: '/api/v1/currency/exchange/refresh', - data: {} - }); - } - - async processBilling(billingData: Record): Promise { - return this.client.request({ - method: 'POST', - endpoint: '/api/v1/currency/billing', - data: billingData - }); - } - - async getBillingHistory(profileId: string, limit: number = 50): Promise { - return this.client.request({ - method: 'GET', - endpoint: `/api/v1/currency/billing/history/${profileId}`, - params: { limit: limit.toString() } - }); - } - - async getBillingSummary(profileId: string, period: string = 'monthly'): Promise { - return this.client.request({ - method: 'GET', - endpoint: `/api/v1/currency/billing/summary/${profileId}`, - params: { period } - }); - } - - async processRefund(transactionId: string, reason: string): Promise { - return this.client.request({ - method: 'POST', - endpoint: `/api/v1/currency/billing/refund/${transactionId}`, - data: { reason } - }); - } - - async getBillingAnalytics(period: string = 'monthly'): Promise> { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/currency/billing/analytics', - params: { period } - }); - } -} diff --git a/sdk/typescript/src/index.ts b/sdk/typescript/src/index.ts index 0bb0087..d48089a 100644 --- a/sdk/typescript/src/index.ts +++ b/sdk/typescript/src/index.ts @@ -5,9 +5,6 @@ export { SDKConfig, TelecomSDKConfig } from './config'; export { HttpClient } from './http-client'; export { SubscribersService } from './subscribers'; export { WebSocketClient } from './websocket'; -export { AnalyticsAPI } from './analytics'; -export { SecurityAPI } from './security'; -export { CurrencyAPI } from './currency'; // Export all types export * from './types'; diff --git a/sdk/typescript/src/security.ts b/sdk/typescript/src/security.ts deleted file mode 100644 index 6544ee7..0000000 --- a/sdk/typescript/src/security.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { HTTPClient } from './api/http-client'; -import { FraudAlert, FraudAlertFilter, FraudMetrics } from './types'; - -export class SecurityAPI { - constructor(private client: HTTPClient) {} - - async analyzeTransaction(transaction: Record): Promise { - return this.client.request({ - method: 'POST', - endpoint: '/api/v1/security/fraud/analyze', - data: transaction - }); - } - - async getFraudAlerts(filter: FraudAlertFilter): Promise { - return this.client.request({ - method: 'POST', - endpoint: '/api/v1/security/fraud/alerts', - data: filter - }); - } - - async updateAlertStatus(alertId: string, status: string, actions: string[]): Promise { - return this.client.request({ - method: 'PUT', - endpoint: `/api/v1/security/fraud/alerts/${alertId}`, - data: { status, actions } - }); - } - - async getFraudMetrics(period: string): Promise { - return this.client.request({ - method: 'GET', - endpoint: '/api/v1/security/fraud/metrics', - params: { period } - }); - } -} diff --git a/sdk/typescript/src/telecom-sdk.ts b/sdk/typescript/src/telecom-sdk.ts index fd06840..b36758a 100644 --- a/sdk/typescript/src/telecom-sdk.ts +++ b/sdk/typescript/src/telecom-sdk.ts @@ -5,11 +5,9 @@ import { UsageAPI, PaymentAPI, RatingPlanAPI, - SystemAPI + SystemAPI, + GraphQLAPI } from './api'; -import { AnalyticsAPI } from './analytics'; -import { SecurityAPI } from './security'; -import { CurrencyAPI } from './currency'; import { WebSocketClient } from './websocket'; import { Alert, RealTimeUsageUpdate, WebSocketMessage } from './types'; @@ -25,9 +23,7 @@ export class TelecomSDK { public payments: PaymentAPI; public ratingPlans: RatingPlanAPI; public system: SystemAPI; - public analytics: AnalyticsAPI; - public security: SecurityAPI; - public currency: CurrencyAPI; + public graphql: GraphQLAPI; private constructor(config: { baseURL: string; @@ -65,9 +61,7 @@ export class TelecomSDK { this.payments = new PaymentAPI(this.apiClient); this.ratingPlans = new RatingPlanAPI(this.apiClient); this.system = new SystemAPI(this.apiClient); - this.analytics = new AnalyticsAPI(this.apiClient); - this.security = new SecurityAPI(this.apiClient); - this.currency = new CurrencyAPI(this.apiClient); + this.graphql = new GraphQLAPI(this.apiClient); } static initialize(config: { diff --git a/sdk/typescript/src/types.ts b/sdk/typescript/src/types.ts index a6fbf49..e3f9e10 100644 --- a/sdk/typescript/src/types.ts +++ b/sdk/typescript/src/types.ts @@ -47,219 +47,6 @@ export interface CreateSubscriberRequest { euiccId?: string; } -// Churn Analysis Types -export enum ChurnRiskLevel { - Low = 'low', - Medium = 'medium', - High = 'high', - Critical = 'critical' -} - -export interface ChurnPrediction { - profileId: string; - riskLevel: ChurnRiskLevel; - riskScore: number; - predictedChurnDate?: string; - reasons: string[]; - recommendations: string[]; - lastUpdated: string; -} - -export interface ChurnMetrics { - period: string; - totalSubscribers: number; - churnedSubscribers: number; - churnRate: number; - monthlyChurnRate: number; - annualChurnRate: number; - averageTenureDays: number; - riskDistribution: Record; - generatedAt: string; -} - -// Fraud Detection Types -export enum FraudType { - AccountTakeover = 'account_takeover', - SubscriptionFraud = 'subscription_fraud', - PaymentFraud = 'payment_fraud', - UsageAnomaly = 'usage_anomaly', - SIMSwap = 'sim_swap' -} - -export enum FraudSeverity { - Low = 'low', - Medium = 'medium', - High = 'high', - Critical = 'critical' -} - -export interface FraudAlert { - id: string; - type: FraudType; - severity: FraudSeverity; - profileId: string; - description: string; - riskScore: number; - evidence: string[]; - ipAddress: string; - timestamp: string; - status: string; - actionsTaken: string[]; - metadata: Record; -} - -export interface FraudMetrics { - period: string; - totalAlerts: number; - resolvedAlerts: number; - falsePositives: number; - resolutionRatePct: number; - falsePositiveRatePct: number; - byType: Record; - bySeverity: Record; - generatedAt: string; -} - -export interface FraudAlertFilter { - type?: FraudType; - severity?: FraudSeverity; - status?: string; - fromDate?: string; - toDate?: string; - limit?: number; -} - -// Market Analytics Types -export interface MarketMetrics { - period: string; - totalMarketSize: number; - ourSubscribers: number; - marketSharePct: number; - growthRatePct: number; - byCountry: Record; - byCarrier: Record; - byDemographic: Record; - competitorAnalysis: Record; - marketOpportunities: MarketOpportunity[]; - generatedAt: string; -} - -export interface CountryMetrics { - country: string; - marketSize: number; - ourSubscribers: number; - marketSharePct: number; - growthRatePct: number; - averageRevenue: number; -} - -export interface MarketCarrierMetrics { - carrierId: string; - carrierName: string; - subscribers: number; - marketSharePct: number; - averageRevenue: number; - qualityScore: number; -} - -export interface DemoMetrics { - segment: string; - subscribers: number; - marketSharePct: number; - averageRevenue: number; - growthRatePct: number; -} - -export interface CompetitorMetrics { - name: string; - marketSharePct: number; - subscribers: number; - averagePrice: number; - strengths: string[]; - weaknesses: string[]; -} - -export interface MarketOpportunity { - id: string; - type: string; - description: string; - potentialSize: number; - confidence: number; - requiredActions: string[]; -} - -// Predictive Maintenance Types -export interface PredictiveMaintenanceMetrics { - period: string; - totalAssets: number; - healthyAssets: number; - atRiskAssets: number; - criticalAssets: number; - overallHealthScore: number; - byAssetType: Record; - predictedFailures: PredictedFailure[]; - maintenanceSchedule: MaintenanceTask[]; - generatedAt: string; -} - -export interface AssetTypeMetrics { - assetType: string; - total: number; - healthy: number; - atRisk: number; - critical: number; - healthScore: number; -} - -export interface PredictedFailure { - assetId: string; - assetType: string; - failureType: string; - predictedDate: string; - confidence: number; - recommendedActions: string[]; -} - -export interface MaintenanceTask { - id: string; - assetId: string; - taskType: string; - priority: string; - scheduledDate: string; - estimatedDurationMinutes: number; - description: string; - status: string; -} - -// Pricing Optimization Types -export interface PricingOptimizationResult { - ratePlanId: string; - currentPrice: number; - optimalPrice: number; - strategy: string; - expectedRevenue: number; - expectedDemand: number; - priceChangePct: number; - reasoning: string[]; - risks: string[]; - recommendations: string[]; - confidence: number; - generatedAt: string; -} - -export interface PricingMetrics { - period: string; - totalRatePlans: number; - optimizedRatePlans: number; - averagePriceChangePct: number; - expectedRevenueImpactPct: number; - churnRateReductionPct: number; - priceElasticity: number; - competitiveIndex: number; - optimizationRoiPct: number; - generatedAt: string; -} - export interface UpdateSubscriberRequest { firstName?: string; lastName?: string;