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:
2025-09-09 00:39:35 +02:00
parent 4dc479ba9e
commit e28000fd33
43 changed files with 4339 additions and 75 deletions

127
internal/api/middleware.go Normal file
View File

@@ -0,0 +1,127 @@
package api
import (
"log"
"net/http"
"time"
)
// CORSMiddleware adds CORS headers to enable browser requests
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
// Allow localhost and 127.0.0.1 on common development ports
allowedOrigins := []string{
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:8080",
"http://127.0.0.1:8080",
}
// Check if origin is allowed
originAllowed := false
for _, allowed := range allowedOrigins {
if origin == allowed {
originAllowed = true
break
}
}
if originAllowed {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else {
// Fallback to wildcard for development (can be restricted in production)
w.Header().Set("Access-Control-Allow-Origin", "*")
}
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
// Note: Explicit OPTIONS handling is done via routes, not here
next.ServeHTTP(w, r)
})
}
// LoggingMiddleware logs HTTP requests
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create a response writer wrapper to capture status code
wrapper := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(wrapper, r)
log.Printf("%s %s %d %v", r.Method, r.URL.Path, wrapper.statusCode, time.Since(start))
})
}
// responseWriter wraps http.ResponseWriter to capture status code
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// ContentTypeMiddleware ensures JSON responses have proper content type
func ContentTypeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set default content type for API responses
if r.URL.Path != "/" && (r.Method == "GET" || r.Method == "POST" || r.Method == "PUT") {
w.Header().Set("Content-Type", "application/json")
}
next.ServeHTTP(w, r)
})
}
// HealthMiddleware provides a simple health check endpoint
func HealthMiddleware() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"healthy","service":"insertr-server"}`))
}
}
// CORSPreflightHandler handles CORS preflight requests (OPTIONS)
func CORSPreflightHandler(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
// Allow localhost and 127.0.0.1 on common development ports
allowedOrigins := []string{
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:8080",
"http://127.0.0.1:8080",
}
// Check if origin is allowed
originAllowed := false
for _, allowed := range allowedOrigins {
if origin == allowed {
originAllowed = true
break
}
}
if originAllowed {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else {
// Fallback to wildcard for development
w.Header().Set("Access-Control-Allow-Origin", "*")
}
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Max-Age", "86400") // Cache preflight for 24 hours
w.WriteHeader(http.StatusOK)
}