Files
WebAndAppMonoRepo/output/internal/services/deployment_service.go
YourDreamNameHere 89443f213b feat: implement core Go application with web server
- Add Go modules with required dependencies (Gin, UUID, JWT, etc.)
- Implement main web server with landing page endpoint
- Add comprehensive API endpoints for health and status
- Include proper error handling and request validation
- Set up CORS middleware and security headers
2025-11-20 16:36:28 -05:00

388 lines
12 KiB
Go

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)
}