package services import ( "encoding/json" "fmt" "log" "time" "github.com/google/uuid" "github.com/ydn/yourdreamnamehere/internal/config" "github.com/ydn/yourdreamnamehere/internal/models" "gorm.io/gorm" ) // getEnvOrDefault is defined in cloudron_service.go type DeploymentService struct { db *gorm.DB config *config.Config ovhService *OVHService cloudronService *CloudronService stripeService *StripeService dolibarrService *DolibarrService emailService *EmailService userService *UserService } func NewDeploymentService( db *gorm.DB, config *config.Config, ovhService *OVHService, cloudronService *CloudronService, stripeService *StripeService, dolibarrService *DolibarrService, emailService *EmailService, userService *UserService, ) *DeploymentService { return &DeploymentService{ db: db, config: config, ovhService: ovhService, cloudronService: cloudronService, stripeService: stripeService, dolibarrService: dolibarrService, emailService: emailService, userService: userService, } } func (s *DeploymentService) ProcessSuccessfulPayment(eventData json.RawMessage) error { // Parse the checkout session var session struct { Customer struct { Email string `json:"email"` ID string `json:"id"` } `json:"customer"` Metadata map[string]string `json:"metadata"` } if err := json.Unmarshal(eventData, &session); err != nil { return fmt.Errorf("failed to parse checkout session: %w", err) } domainName := session.Metadata["domain_name"] customerEmail := session.Metadata["customer_email"] if domainName == "" || customerEmail == "" { return fmt.Errorf("missing required metadata in checkout session") } // Start the deployment process go s.startDeploymentProcess(session.Customer.ID, domainName, customerEmail) return nil } func (s *DeploymentService) ProcessFailedPayment(eventData json.RawMessage) error { // Handle failed payment - update customer status, send notifications, etc. log.Printf("Processing failed payment: %s", string(eventData)) // Implementation would depend on specific requirements // For now, we'll just log it return nil } func (s *DeploymentService) startDeploymentProcess(stripeCustomerID, domainName, customerEmail string) { log.Printf("Starting deployment process for domain: %s, customer: %s", domainName, customerEmail) // Get customer from database var customer models.Customer if err := s.db.Where("stripe_id = ?", stripeCustomerID).First(&customer).Error; err != nil { log.Printf("Failed to find customer: %v", err) return } // Create deployment record deploymentLog := &models.DeploymentLog{ VPSID: uuid.New(), // Will be updated after VPS creation Step: "deployment_start", Status: "started", Message: "Starting full deployment process", CreatedAt: time.Now(), } s.db.Create(deploymentLog) // Step 1: Check domain availability if err := s.registerDomain(customer.ID, domainName, customerEmail); err != nil { log.Printf("Domain registration failed: %v", err) return } // Step 2: Provision VPS vps, err := s.provisionVPS(customer.ID, domainName) if err != nil { log.Printf("VPS provisioning failed: %v", err) return } // Step 3: Install Cloudron if err := s.installCloudron(vps.ID, domainName, customerEmail); err != nil { log.Printf("Cloudron installation failed: %v", err) return } // Step 4: Create Dolibarr records if err := s.createBackOfficeRecords(customer.ID, domainName); err != nil { log.Printf("Dolibarr record creation failed: %v", err) return } // Step 5: Send admin invitation if err := s.sendAdminInvitation(vps.ID, customerEmail, domainName); err != nil { log.Printf("Failed to send admin invitation: %v", err) return } // Mark deployment as completed s.logDeploymentStep(customer.ID, "deployment_complete", "completed", "Full deployment completed successfully", "") } func (s *DeploymentService) registerDomain(customerID uuid.UUID, domainName, customerEmail string) error { s.logDeploymentStep(customerID, "domain_registration", "started", "Checking domain availability", "") // Check if domain is available available, err := s.ovhService.CheckDomainAvailability(domainName) if err != nil { s.logDeploymentStep(customerID, "domain_registration", "failed", "Failed to check domain availability", err.Error()) return err } if !available { s.logDeploymentStep(customerID, "domain_registration", "failed", "Domain is not available", "") return fmt.Errorf("domain %s is not available", domainName) } // Create domain order with configurable contact information order := OVHDomainOrder{ Domain: domainName, Owner: struct { FirstName string `json:"firstName"` LastName string `json:"lastName"` Email string `json:"email"` Phone string `json:"phone"` Country string `json:"country"` }{ FirstName: getEnvOrDefault("YDN_CONTACT_FIRSTNAME", "YourDreamNameHere"), LastName: getEnvOrDefault("YDN_CONTACT_LASTNAME", "Customer"), Email: customerEmail, Phone: getEnvOrDefault("YDN_CONTACT_PHONE", "+1234567890"), Country: getEnvOrDefault("YDN_CONTACT_COUNTRY", "US"), }, // Set owner, admin, and tech contacts the same for simplicity Admin: struct { FirstName string `json:"firstName"` LastName string `json:"lastName"` Email string `json:"email"` Phone string `json:"phone"` Country string `json:"country"` }{ FirstName: getEnvOrDefault("YDN_CONTACT_FIRSTNAME", "YourDreamNameHere"), LastName: getEnvOrDefault("YDN_CONTACT_LASTNAME", "Customer"), Email: customerEmail, Phone: getEnvOrDefault("YDN_CONTACT_PHONE", "+1234567890"), Country: getEnvOrDefault("YDN_CONTACT_COUNTRY", "US"), }, Tech: struct { FirstName string `json:"firstName"` LastName string `json:"lastName"` Email string `json:"email"` Phone string `json:"phone"` Country string `json:"country"` }{ FirstName: getEnvOrDefault("YDN_TECH_CONTACT_FIRSTNAME", "Technical"), LastName: getEnvOrDefault("YDN_TECH_CONTACT_LASTNAME", "Support"), Email: getEnvOrDefault("YDN_TECH_CONTACT_EMAIL", "tech@yourdreamnamehere.com"), Phone: getEnvOrDefault("YDN_TECH_CONTACT_PHONE", "+1234567890"), Country: getEnvOrDefault("YDN_CONTACT_COUNTRY", "US"), }, } if err := s.ovhService.RegisterDomain(order); err != nil { s.logDeploymentStep(customerID, "domain_registration", "failed", "Failed to register domain", err.Error()) return err } // Update domain record in database if err := s.db.Model(&models.Domain{}). Where("customer_id = ? AND name = ?", customerID, domainName). Updates(map[string]interface{}{ "status": "registered", "registered_at": time.Now(), }).Error; err != nil { return fmt.Errorf("failed to update domain status: %w", err) } s.logDeploymentStep(customerID, "domain_registration", "completed", "Domain registration completed successfully", "") return nil } func (s *DeploymentService) provisionVPS(customerID uuid.UUID, domainName string) (*models.VPS, error) { s.logDeploymentStep(customerID, "vps_provisioning", "started", "Starting VPS provisioning", "") // Get domain record var domain models.Domain if err := s.db.Where("customer_id = ? AND name = ?", customerID, domainName).First(&domain).Error; err != nil { return nil, fmt.Errorf("failed to find domain: %w", err) } // Create VPS order order := OVHVPSOrder{ Name: fmt.Sprintf("%s-vps", domainName), Region: "GRA", // Gravelines, France Flavor: "vps-ssd-1", // Basic VPS Image: "ubuntu_22_04", MonthlyBilling: true, } vps, err := s.ovhService.ProvisionVPS(order) if err != nil { s.logDeploymentStep(customerID, "vps_provisioning", "failed", "Failed to provision VPS", err.Error()) return nil, err } // Update VPS with domain association vps.DomainID = domain.ID if err := s.db.Save(vps).Error; err != nil { return nil, fmt.Errorf("failed to save VPS: %w", err) } s.logDeploymentStep(customerID, "vps_provisioning", "completed", "VPS provisioning completed successfully", fmt.Sprintf("VPS ID: %s, IP: %s", vps.OVHInstanceID, vps.IPAddress)) return vps, nil } func (s *DeploymentService) installCloudron(vpsID uuid.UUID, domainName, customerEmail string) error { return s.cloudronService.InstallCloudron(vpsID, domainName) } func (s *DeploymentService) createBackOfficeRecords(customerID uuid.UUID, domainName string) error { s.logDeploymentStep(customerID, "dolibarr_setup", "started", "Creating back-office records", "") // Get customer var customer models.Customer if err := s.db.Where("id = ?", customerID).First(&customer).Error; err != nil { return fmt.Errorf("failed to find customer: %w", err) } // Create customer in Dolibarr dolibarrCustomer, err := s.dolibarrService.CreateCustomer(&customer) if err != nil { s.logDeploymentStep(customerID, "dolibarr_setup", "failed", "Failed to create customer in Dolibarr", err.Error()) return err } // Create product if it doesn't exist if err := s.dolibarrService.CreateOrUpdateProduct( "SOVEREIGN_HOSTING", "Sovereign Data Hosting", "Complete sovereign data hosting package with domain, VPS, and Cloudron", 250.00, ); err != nil { log.Printf("Warning: failed to create product in Dolibarr: %v", err) } // Create monthly invoice if _, err := s.dolibarrService.CreateInvoice( dolibarrCustomer.ID, 250.00, fmt.Sprintf("Monthly subscription for %s", domainName), ); err != nil { s.logDeploymentStep(customerID, "dolibarr_setup", "failed", "Failed to create invoice in Dolibarr", err.Error()) return err } s.logDeploymentStep(customerID, "dolibarr_setup", "completed", "Back-office records created successfully", "") return nil } func (s *DeploymentService) sendAdminInvitation(vpsID uuid.UUID, customerEmail, domainName string) error { s.logDeploymentStepByVPS(vpsID, "admin_invitation", "started", "Sending administrator invitation", "") // Get VPS details var vps models.VPS if err := s.db.Where("id = ?", vpsID).First(&vps).Error; err != nil { return fmt.Errorf("failed to find VPS: %w", err) } if err := s.cloudronService.SendAdministratorInvite(vps.CloudronURL, customerEmail); err != nil { s.logDeploymentStepByVPS(vpsID, "admin_invitation", "failed", "Failed to send administrator invitation", err.Error()) return err } s.logDeploymentStepByVPS(vpsID, "admin_invitation", "completed", "Administrator invitation sent successfully", "") return nil } func (s *DeploymentService) CreateDomain(userID, domainName string) (*models.Domain, error) { // Get user user, err := s.userService.GetUserByID(userID) if err != nil { return nil, fmt.Errorf("failed to get user: %w", err) } // Get customer for user var customer models.Customer if err := s.db.Where("user_id = ?", user.ID).First(&customer).Error; err != nil { return nil, fmt.Errorf("failed to find customer for user: %w", err) } // Create domain record domain := &models.Domain{ CustomerID: customer.ID, Name: domainName, Status: "pending", CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := s.db.Create(domain).Error; err != nil { return nil, fmt.Errorf("failed to create domain: %w", err) } return domain, nil } func (s *DeploymentService) logDeploymentStep(customerID uuid.UUID, step, status, message, details string) { log := &models.DeploymentLog{ VPSID: uuid.New(), // Temporary VPS ID, should be updated when VPS is created Step: step, Status: status, Message: message, Details: details, CreatedAt: time.Now(), } s.db.Create(log) } func (s *DeploymentService) logDeploymentStepByVPS(vpsID uuid.UUID, step, status, message, details string) { log := &models.DeploymentLog{ VPSID: vpsID, Step: step, Status: status, Message: message, Details: details, CreatedAt: time.Now(), } s.db.Create(log) }