refactor: implement unified binary architecture
🏗️ **Major Architecture Refactoring: Separate CLI + Server → Unified Binary** **Key Changes:** ✅ **Unified Binary**: Single 'insertr' binary with subcommands (enhance, serve) ✅ **Preserved Database Architecture**: Maintained sophisticated sqlc multi-DB setup ✅ **Smart Configuration**: Viper + YAML config with CLI flag precedence ✅ **Updated Build System**: Unified justfile, Air, and npm scripts **Command Structure:** - `insertr enhance [input-dir]` - Build-time content injection - `insertr serve` - HTTP API server (dev + production modes) - `insertr --config insertr.yaml` - YAML configuration support **Architecture Benefits:** - **Shared Database Layer**: Single source of truth for content models - **Flexible Workflows**: Local DB for dev, remote API for production - **Simple Deployment**: One binary for all use cases - **Better UX**: Consistent configuration across build and runtime **Preserved Features:** - Multi-database support (SQLite + PostgreSQL) - sqlc code generation and type safety - Version control system with rollback - Professional API endpoints - Content enhancement pipeline **Development Workflow:** - `just dev` - Full-stack development (API server + demo site) - `just serve` - API server only - `just enhance` - Build-time content injection - `air` - Hot reload unified binary **Migration:** Consolidated insertr-cli/ and insertr-server/ → unified root structure
This commit is contained in:
134
cmd/serve.go
Normal file
134
cmd/serve.go
Normal file
@@ -0,0 +1,134 @@
|
||||
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/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("server.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("server.dev_mode")
|
||||
|
||||
// Initialize database
|
||||
database, err := db.NewDatabase(dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize database: %v", err)
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
// Initialize handlers
|
||||
contentHandler := api.NewContentHandler(database)
|
||||
|
||||
// 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/content").Subrouter()
|
||||
|
||||
// Content endpoints matching the expected API contract
|
||||
apiRouter.HandleFunc("/bulk", contentHandler.GetBulkContent).Methods("GET")
|
||||
apiRouter.HandleFunc("/{id}", contentHandler.GetContent).Methods("GET")
|
||||
apiRouter.HandleFunc("/{id}", contentHandler.UpdateContent).Methods("PUT")
|
||||
apiRouter.HandleFunc("", contentHandler.GetAllContent).Methods("GET")
|
||||
apiRouter.HandleFunc("", contentHandler.CreateContent).Methods("POST")
|
||||
|
||||
// Version control endpoints
|
||||
apiRouter.HandleFunc("/{id}/versions", contentHandler.GetContentVersions).Methods("GET")
|
||||
apiRouter.HandleFunc("/{id}/rollback", contentHandler.RollbackContent).Methods("POST")
|
||||
|
||||
// Handle CORS preflight requests explicitly
|
||||
apiRouter.HandleFunc("/{id}", api.CORSPreflightHandler).Methods("OPTIONS")
|
||||
apiRouter.HandleFunc("", api.CORSPreflightHandler).Methods("OPTIONS")
|
||||
apiRouter.HandleFunc("/bulk", api.CORSPreflightHandler).Methods("OPTIONS")
|
||||
apiRouter.HandleFunc("/{id}/versions", api.CORSPreflightHandler).Methods("OPTIONS")
|
||||
apiRouter.HandleFunc("/{id}/rollback", 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("\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")
|
||||
}
|
||||
Reference in New Issue
Block a user