diff --git a/apps/carrier-connector/internal/mvno/handler.go b/apps/carrier-connector/internal/mvno/handler.go deleted file mode 100644 index fc7d803..0000000 --- a/apps/carrier-connector/internal/mvno/handler.go +++ /dev/null @@ -1,109 +0,0 @@ -package mvno - -import ( - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" -) - -// Handler handles HTTP requests for MVNO operations -type Handler struct { - service *OnboardingService - repo Repository - logger *logrus.Logger -} - -// NewHandler creates a new HTTP handler -func NewHandler(service *OnboardingService, repo Repository, logger *logrus.Logger) *Handler { - return &Handler{ - service: service, - repo: repo, - logger: logger, - } -} - -// StartOnboarding handles POST /mvno/onboarding -func (h *Handler) StartOnboarding(c *gin.Context) { - var req 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 *Handler) 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 *Handler) ListMVNOs(c *gin.Context) { - filter := &MVNOFilter{} - if status := c.Query("status"); status != "" { - filter.Status = MVNOStatus(status) - } - if plan := c.Query("plan"); plan != "" { - filter.Plan = 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 *Handler) 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/mvno/handler_management.go b/apps/carrier-connector/internal/mvno/handler_management.go deleted file mode 100644 index 753ba15..0000000 --- a/apps/carrier-connector/internal/mvno/handler_management.go +++ /dev/null @@ -1,118 +0,0 @@ -package mvno - -import ( - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" -) - -// ManagementHandler handles MVNO management HTTP requests -type ManagementHandler struct { - repo Repository - logger *logrus.Logger -} - -// NewManagementHandler creates a new management handler -func NewManagementHandler(repo 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 := &MVNOFilter{} - - // Parse query parameters - if status := c.Query("status"); status != "" { - filter.Status = MVNOStatus(status) - } - if plan := c.Query("plan"); plan != "" { - filter.Plan = 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 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/mvno/onboarding_service.go b/apps/carrier-connector/internal/mvno/onboarding_service.go deleted file mode 100644 index eb72e7b..0000000 --- a/apps/carrier-connector/internal/mvno/onboarding_service.go +++ /dev/null @@ -1,151 +0,0 @@ -package mvno - -import ( - "context" - "fmt" - "time" - - "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/id" - "github.com/sirupsen/logrus" -) - -// OnboardingService handles MVNO onboarding process -type OnboardingService struct { - logger *logrus.Logger - validator *OnboardingValidator - provisioner *MVNOProvisioner - monitor *OnboardingMonitor -} - -// NewOnboardingService creates a new onboarding service -func NewOnboardingService(logger *logrus.Logger) *OnboardingService { - return &OnboardingService{ - logger: logger, - validator: NewOnboardingValidator(logger), - provisioner: NewMVNOProvisioner(logger), - monitor: NewOnboardingMonitor(logger), - } -} - -// StartOnboarding initiates the MVNO onboarding process -func (s *OnboardingService) StartOnboarding(ctx context.Context, req *OnboardingRequest) (*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 - mvno := &MVNO{ - ID: id.GeneratePrefixed("mvno"), - BusinessID: req.BusinessID, - Name: req.BusinessName, - Status: StatusPending, - Plan: req.Plan, - Config: MVNOConfig{ - MaxSubscribers: s.getMaxSubscribersForPlan(req.Plan), - AllowedCountries: req.TargetCountries, - CustomBranding: req.Plan != PlanStarter, - APIAccess: req.Plan != PlanStarter, - AdvancedAnalytics: req.Plan == PlanScale || req.Plan == PlanEnterprise, - }, - CreatedAt: time.Now(), - } - - // Start onboarding progress tracking - progress := &OnboardingProgress{ - MVNOID: mvno.ID, - Steps: s.getOnboardingSteps(), - Progress: 0.0, - StartedAt: time.Now(), - } - - s.logger.WithFields(logrus.Fields{ - "mvno_id": mvno.ID, - "business_id": req.BusinessID, - "plan": req.Plan, - }).Info("Starting MVNO onboarding") - - // Execute onboarding steps asynchronously - go s.executeOnboarding(ctx, mvno, progress) - - return mvno, nil -} - -// executeOnboarding runs all onboarding steps -func (s *OnboardingService) executeOnboarding(ctx context.Context, mvno *MVNO, progress *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 = StatusActive - 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, step *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() []OnboardingStep { - return []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 MVNOPlan) int { - switch plan { - case PlanStarter: - return 1000 - case PlanGrowth: - return 10000 - case PlanScale: - return 100000 - case PlanEnterprise: - return -1 // Unlimited - default: - return 1000 - } -} diff --git a/apps/carrier-connector/internal/mvno/provisioner.go b/apps/carrier-connector/internal/mvno/provisioner.go index fcf19a4..404138d 100644 --- a/apps/carrier-connector/internal/mvno/provisioner.go +++ b/apps/carrier-connector/internal/mvno/provisioner.go @@ -78,7 +78,7 @@ func (p *MVNOProvisioner) SetupAPIAccess(ctx context.Context, mvno *MVNO) error _ = id.GeneratePrefixed("sec") // Generate secret key but don't use until storage is implemented permissions := p.getAPIPermissions(mvno.Plan) - p.logger.WithFields(map[string]interface{}{ + p.logger.WithFields(map[string]any{ "mvno_id": mvno.ID, "api_key": apiKey[:8] + "...", "permissions": len(permissions), @@ -102,7 +102,7 @@ func (p *MVNOProvisioner) provisionDatabaseSchema(ctx context.Context, mvno *MVN // provisionStorageResources provisions storage resources func (p *MVNOProvisioner) provisionStorageResources(ctx context.Context, mvno *MVNO) error { storageSize := p.getStorageAllocation(mvno.Plan) - p.logger.WithFields(map[string]interface{}{ + p.logger.WithFields(map[string]any{ "mvno_id": mvno.ID, "storage_gb": storageSize, }).Info("Storage resources provisioned") @@ -126,7 +126,7 @@ func (p *MVNOProvisioner) selectCarriers(countries []string) ([]string, error) { // configureCarrier configures individual carrier func (p *MVNOProvisioner) configureCarrier(ctx context.Context, mvno *MVNO, carrierID string) error { - p.logger.WithFields(map[string]interface{}{ + p.logger.WithFields(map[string]any{ "mvno_id": mvno.ID, "carrier_id": carrierID, }).Info("Carrier configured") @@ -135,7 +135,7 @@ func (p *MVNOProvisioner) configureCarrier(ctx context.Context, mvno *MVNO, carr // configureRatePlans configures rate plans func (p *MVNOProvisioner) configureRatePlans(ctx context.Context, mvno *MVNO, billingID string) error { - p.logger.WithFields(map[string]interface{}{ + p.logger.WithFields(map[string]any{ "mvno_id": mvno.ID, "billing_id": billingID, "plan": mvno.Plan, @@ -145,7 +145,7 @@ func (p *MVNOProvisioner) configureRatePlans(ctx context.Context, mvno *MVNO, bi // setupPaymentProcessing setup payment processing func (p *MVNOProvisioner) setupPaymentProcessing(ctx context.Context, mvno *MVNO, billingID string) error { - p.logger.WithFields(map[string]interface{}{ + p.logger.WithFields(map[string]any{ "mvno_id": mvno.ID, "billing_id": billingID, }).Info("Payment processing setup") diff --git a/apps/carrier-connector/internal/mvno/repository.go b/apps/carrier-connector/internal/mvno/repository.go deleted file mode 100644 index 6c59999..0000000 --- a/apps/carrier-connector/internal/mvno/repository.go +++ /dev/null @@ -1,198 +0,0 @@ -package mvno - -import ( - "context" - "fmt" - "time" - - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// Repository defines the interface for MVNO data operations -type Repository interface { - CreateMVNO(ctx context.Context, mvno *MVNO) error - GetMVNO(ctx context.Context, id string) (*MVNO, error) - GetMVNOByBusinessID(ctx context.Context, businessID string) (*MVNO, error) - UpdateMVNO(ctx context.Context, mvno *MVNO) error - ListMVNOs(ctx context.Context, filter *MVNOFilter) ([]*MVNO, error) - DeleteMVNO(ctx context.Context, id string) error - UpdateMVNOStatus(ctx context.Context, id string, status MVNOStatus) error - GetMVNOStats(ctx context.Context) (map[string]interface{}, error) -} - -// 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"` -} - -// GormRepository implements Repository using GORM -type GormRepository struct { - db *gorm.DB - logger *logrus.Logger -} - -// NewGormRepository creates a new GORM repository -func NewGormRepository(db *gorm.DB, logger *logrus.Logger) *GormRepository { - return &GormRepository{ - db: db, - logger: logger, - } -} - -// CreateMVNO creates a new MVNO record -func (r *GormRepository) CreateMVNO(ctx context.Context, 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, error) { - var 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, error) { - var 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) 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 *MVNOFilter) ([]*MVNO, error) { - query := r.db.WithContext(ctx).Model(&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 - 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{}).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{}).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 MVNOStatus `gorm:"column:status"` - Count int64 `gorm:"column:count"` - } - if err := r.db.WithContext(ctx).Model(&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 MVNOPlan `gorm:"column:plan"` - Count int64 `gorm:"column:count"` - } - if err := r.db.WithContext(ctx).Model(&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 MVNOStatus) error { - if err := r.db.WithContext(ctx).Model(&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/services/selection_service.go b/apps/carrier-connector/internal/services/selection_service.go index 451ca3b..c666d90 100644 --- a/apps/carrier-connector/internal/services/selection_service.go +++ b/apps/carrier-connector/internal/services/selection_service.go @@ -5,7 +5,6 @@ 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" ) @@ -13,27 +12,17 @@ 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) *SelectionService { - logger := logrus.New() - logger.SetLevel(logrus.InfoLevel) - +func NewSelectionService(manager *smdp.SMDPManager, logger *logrus.Logger) *SelectionService { 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{