- 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
388 lines
12 KiB
Go
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)
|
|
} |