package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/alecthomas/kong" "github.com/gin-gonic/gin" "git.linuxforward.com/byom/byom-onboard/internal/common/jwt" "git.linuxforward.com/byom/byom-onboard/internal/domain/billing" "git.linuxforward.com/byom/byom-onboard/internal/domain/provisioning" "git.linuxforward.com/byom/byom-onboard/internal/domain/register" "git.linuxforward.com/byom/byom-onboard/internal/platform/config" "git.linuxforward.com/byom/byom-onboard/internal/platform/database" "git.linuxforward.com/byom/byom-onboard/internal/platform/mailer" "git.linuxforward.com/byom/byom-onboard/internal/platform/middleware" ) var ( // Build information - values will be set during build version = "dev" commit = "none" buildDate = "unknown" ) type CLI struct { Config string `help:"Config file path" default:"config.yaml" type:"path"` Debug bool `help:"Enable debug mode"` Version VersionCmd `cmd:"" help:"Show version information"` Serve ServeCmd `cmd:"" help:"Start the HTTP server"` } type VersionCmd struct{} func (v VersionCmd) Run() error { fmt.Printf("Version:\t%s\n", version) fmt.Printf("Commit:\t\t%s\n", commit) fmt.Printf("Built:\t\t%s\n", buildDate) return nil } // ServeCmd handles the serve command type ServeCmd struct { Port int `help:"Port to listen on" default:"8080"` Host string `help:"Host to bind to" default:"0.0.0.0"` } func (s *ServeCmd) Run(cli *CLI) error { // Load configuration cfg, err := config.Load() if err != nil { return fmt.Errorf("failed to load configuration: %w", err) } // Initialize Gin gin.SetMode(gin.ReleaseMode) r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) if cli.Debug { log.Printf("Debug mode enabled") gin.SetMode(gin.DebugMode) log.Printf("Using config file: %s", cli.Config) } // Initialize database db, err := database.NewDatabase(cfg.Database.DSN) if err != nil { return fmt.Errorf("failed to initialize database: %w", err) } jwtClient := jwt.NewJWTClient([]byte(cfg.JWT.Secret)) // Initialize services stripeClient := billing.NewStripeClient(cfg.Stripe.SecretKey, cfg.Stripe.WebhookSecret) mailerClient := mailer.NewMailer(&cfg.Mailer) ovhClient := provisioning.NewOvhClient(cfg.OVH.Endpoint, cfg.OVH.AppKey, cfg.OVH.AppSecret) registrationService := register.NewRegistrationService(db, jwtClient) // Initialize handlers billingHandler := billing.NewHandler(stripeClient, db) provisioningHandler := provisioning.NewHandler(ovhClient, db) registerHandler := register.NewHandler(registrationService, mailerClient, db) // Setup server server := setupServer(r, billingHandler, provisioningHandler, registerHandler, cfg) server.Addr = fmt.Sprintf(":%d", s.Port) // Start server in a goroutine go func() { log.Printf("Starting server on port %d", s.Port) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Failed to start server: %v", err) } }() // Setup graceful shutdown quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { return fmt.Errorf("server forced to shutdown: %w", err) } log.Println("Server exited properly") return nil } func setupServer( r *gin.Engine, billingHandler *billing.Handler, provisioningHandler *provisioning.Handler, registerHandler *register.Handler, config *config.Config, ) *http.Server { api := r.Group("/api/v1") { // Public routes api.POST("/register", registerHandler.Register) api.GET("/validate-email", registerHandler.ValidateEmail) // Payment routes api.POST("/payment", billingHandler.CreatePayment) api.POST("/payment/webhook", billingHandler.HandleWebhook) // Admin routes adminMiddleware := middleware.NewAdminMiddleware() admin := api.Group("/admin", adminMiddleware.Middleware()) { admin.GET("/vms", provisioningHandler.ListVPS) admin.POST("/vms", provisioningHandler.AddVPS) admin.GET("/provisioning/:id", provisioningHandler.GetVPSStatus) } } return &http.Server{ Addr: config.Server.Address, Handler: r, } } func main() { cli := CLI{} ctx := kong.Parse(&cli, kong.Name("byom"), kong.Description("BYOM - Onboarding service"), kong.UsageOnError(), kong.ConfigureHelp(kong.HelpOptions{ Compact: true, Summary: true, }), ) err := ctx.Run(&cli) ctx.FatalIfErrorf(err) }