package cmd import ( "fmt" "log" "net/http" "os" "os/signal" "syscall" "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/insertr/insertr/internal/api" "github.com/insertr/insertr/internal/auth" "github.com/insertr/insertr/internal/content" "github.com/insertr/insertr/internal/db" ) var serveCmd = &cobra.Command{ Use: "serve", Short: "Start the content API server", Long: `Start the HTTP API server that provides content storage and retrieval. Supports both development and production modes with SQLite or PostgreSQL databases.`, Run: runServe, } var ( port int devMode bool ) func init() { serveCmd.Flags().IntVarP(&port, "port", "p", 8080, "Server port") serveCmd.Flags().BoolVar(&devMode, "dev-mode", false, "Enable development mode features") // Bind flags to viper viper.BindPFlag("server.port", serveCmd.Flags().Lookup("port")) viper.BindPFlag("dev_mode", serveCmd.Flags().Lookup("dev-mode")) } func runServe(cmd *cobra.Command, args []string) { // Get configuration values port := viper.GetInt("server.port") dbPath := viper.GetString("database.path") devMode := viper.GetBool("dev_mode") // Initialize database database, err := db.NewDatabase(dbPath) if err != nil { log.Fatalf("Failed to initialize database: %v", err) } defer database.Close() // Initialize authentication service authConfig := &auth.AuthConfig{ DevMode: viper.GetBool("dev_mode"), JWTSecret: viper.GetString("jwt_secret"), } // Set default JWT secret if not configured if authConfig.JWTSecret == "" { authConfig.JWTSecret = "dev-secret-change-in-production" if authConfig.DevMode { log.Printf("šŸ”‘ Using default JWT secret for development") } } authService := auth.NewAuthService(authConfig) // Initialize content client for site manager contentClient := content.NewDatabaseClient(database) // Initialize site manager siteManager := content.NewSiteManager(contentClient, "./insertr-backups", devMode) // Load sites from configuration if siteConfigs := viper.Get("server.sites"); siteConfigs != nil { if configs, ok := siteConfigs.([]interface{}); ok { var sites []*content.SiteConfig for _, configInterface := range configs { if configMap, ok := configInterface.(map[string]interface{}); ok { site := &content.SiteConfig{} if siteID, ok := configMap["site_id"].(string); ok { site.SiteID = siteID } if path, ok := configMap["path"].(string); ok { site.Path = path } if domain, ok := configMap["domain"].(string); ok { site.Domain = domain } if autoEnhance, ok := configMap["auto_enhance"].(bool); ok { site.AutoEnhance = autoEnhance } if backupOriginals, ok := configMap["backup_originals"].(bool); ok { site.BackupOriginals = backupOriginals } if site.SiteID != "" && site.Path != "" { sites = append(sites, site) } } } if err := siteManager.RegisterSites(sites); err != nil { log.Printf("āš ļø Failed to register some sites: %v", err) } } } // Initialize handlers contentHandler := api.NewContentHandler(database, authService) contentHandler.SetSiteManager(siteManager) // Setup router router := mux.NewRouter() // Add middleware router.Use(api.CORSMiddleware) router.Use(api.LoggingMiddleware) router.Use(api.ContentTypeMiddleware) // Health check endpoint router.HandleFunc("/health", api.HealthMiddleware()) // API routes apiRouter := router.PathPrefix("/api").Subrouter() // Content endpoints contentRouter := apiRouter.PathPrefix("/content").Subrouter() contentRouter.HandleFunc("/bulk", contentHandler.GetBulkContent).Methods("GET") contentRouter.HandleFunc("/{id}", contentHandler.GetContent).Methods("GET") contentRouter.HandleFunc("", contentHandler.GetAllContent).Methods("GET") contentRouter.HandleFunc("", contentHandler.CreateContent).Methods("POST") // Version control endpoints contentRouter.HandleFunc("/{id}/versions", contentHandler.GetContentVersions).Methods("GET") contentRouter.HandleFunc("/{id}/rollback", contentHandler.RollbackContent).Methods("POST") // Site enhancement endpoint apiRouter.HandleFunc("/enhance", contentHandler.EnhanceSite).Methods("POST") // Static library serving (for demo sites) router.HandleFunc("/insertr.js", contentHandler.ServeInsertrJS).Methods("GET") // Static site serving - serve registered sites at /sites/{site_id} siteRouter := router.PathPrefix("/sites").Subrouter() for siteID, siteConfig := range siteManager.GetAllSites() { log.Printf("šŸ“ Serving site %s from %s at /sites/%s/", siteID, siteConfig.Path, siteID) siteRouter.PathPrefix("/" + siteID + "/").Handler( http.StripPrefix("/sites/"+siteID+"/", http.FileServer(http.Dir(siteConfig.Path)))) } // Handle CORS preflight requests explicitly contentRouter.HandleFunc("/{id}", api.CORSPreflightHandler).Methods("OPTIONS") contentRouter.HandleFunc("", api.CORSPreflightHandler).Methods("OPTIONS") contentRouter.HandleFunc("/bulk", api.CORSPreflightHandler).Methods("OPTIONS") contentRouter.HandleFunc("/{id}/versions", api.CORSPreflightHandler).Methods("OPTIONS") contentRouter.HandleFunc("/{id}/rollback", api.CORSPreflightHandler).Methods("OPTIONS") apiRouter.HandleFunc("/enhance", api.CORSPreflightHandler).Methods("OPTIONS") // Start server addr := fmt.Sprintf(":%d", port) mode := "production" if devMode { mode = "development" } fmt.Printf("šŸš€ Insertr Content Server starting (%s mode)...\n", mode) fmt.Printf("šŸ“ Database: %s\n", dbPath) fmt.Printf("🌐 Server running at: http://localhost%s\n", addr) fmt.Printf("šŸ’š Health check: http://localhost%s/health\n", addr) fmt.Printf("šŸ“Š API endpoints:\n") fmt.Printf(" GET /api/content?site_id={site}\n") fmt.Printf(" GET /api/content/{id}?site_id={site}\n") fmt.Printf(" GET /api/content/bulk?site_id={site}&ids[]={id1}&ids[]={id2}\n") fmt.Printf(" POST /api/content\n") fmt.Printf(" PUT /api/content/{id}\n") fmt.Printf(" GET /api/content/{id}/versions?site_id={site}\n") fmt.Printf(" POST /api/content/{id}/rollback\n") fmt.Printf("🌐 Static sites:\n") for siteID, _ := range siteManager.GetAllSites() { fmt.Printf(" %s: http://localhost%s/sites/%s/\n", siteID, addr, siteID) } fmt.Printf("\nšŸ”„ Press Ctrl+C to shutdown gracefully\n\n") // Setup graceful shutdown server := &http.Server{ Addr: addr, Handler: router, } // Start server in a goroutine go func() { if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed to start: %v", err) } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit fmt.Println("\nšŸ›‘ Shutting down server...") if err := server.Close(); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } fmt.Println("āœ… Server shutdown complete") }