Files
insertr/internal/api/middleware.go
Joakim a3fc3089d2 Simplify development workflow and fix content editing conflicts
- Replace complex multi-server setup (live-server + API) with unified Go server
- Serve all sites at /sites/{site_id} endpoints, eliminating port conflicts
- Fix content-type middleware to serve proper MIME types for static files
- Prevent script injection duplication with future-proof CDN-compatible detection
- Remove auto page reload from enhance button to eliminate editing interruptions
- Enable seamless content editing workflow with manual enhancement control

Development now requires only 'just dev' instead of complex demo commands.
All sites immediately available at localhost:8080 without hot reload conflicts.
2025-09-16 19:10:57 +02:00

133 lines
4.0 KiB
Go

package api
import (
"log"
"net/http"
"strings"
"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")
// In development mode, allow all localhost origins including demo sites
if isLocalhostOrigin(origin) {
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 only, not static sites
if r.URL.Path != "/" &&
!strings.HasPrefix(r.URL.Path, "/sites/") &&
!strings.HasPrefix(r.URL.Path, "/insertr.js") &&
(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://localhost:8080",
"http://127.0.0.1:3000",
"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)
}
// isLocalhostOrigin checks if an origin is a localhost/127.0.0.1 development origin
func isLocalhostOrigin(origin string) bool {
if origin == "" {
return false
}
// Allow localhost and 127.0.0.1 on any port for development
return strings.HasPrefix(origin, "http://localhost:") ||
strings.HasPrefix(origin, "https://localhost:") ||
strings.HasPrefix(origin, "http://127.0.0.1:") ||
strings.HasPrefix(origin, "https://127.0.0.1:") ||
origin == "http://localhost" ||
origin == "https://localhost" ||
origin == "http://127.0.0.1" ||
origin == "https://127.0.0.1"
}