main.go
Rama main · réplica de lectura del repositorio original.
package main
import (
"embed"
"io/fs"
"net/http"
"os"
"strings"
"time"
"github.com/NYTimes/gziphandler"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
)
//go:embed dist
var staticFiles embed.FS
var db *gorm.DB
func init() {
// Configure zerolog for console output with colors
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.SetGlobalLevel(zerolog.InfoLevel)
// Use console writer for pretty output in development
// In production, you can set ZEROLOG_LOG_LEVEL env var to control log level
if os.Getenv("ZEROLOG_LOG_LEVEL") == "" {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339})
}
// Set log level from environment variable if provided
if level := os.Getenv("ZEROLOG_LOG_LEVEL"); level != "" {
switch level {
case "debug":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case "info":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "warn":
zerolog.SetGlobalLevel(zerolog.WarnLevel)
case "error":
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
}
}
}
func main() {
// Initialize database
initDB()
// Start background checker
startChecker()
// Start cleanup scheduler (runs daily at midnight)
go startCleanupScheduler()
// API routes - Create a dedicated mux for routes that should be compressed
gzipMux := http.NewServeMux()
gzipMux.HandleFunc("/api/monitors", apiMonitors)
gzipMux.HandleFunc("/api/monitors/create", apiCreateMonitor)
gzipMux.HandleFunc("/api/monitors/export", apiExportMonitors)
gzipMux.HandleFunc("/api/stats", apiStats)
gzipMux.HandleFunc("/api/response-time", apiResponseTime)
gzipMux.HandleFunc("/api/monitor", apiMonitor)
// Serve static files
staticFS, err := fs.Sub(staticFiles, "dist")
if err != nil {
log.Fatal().Err(err).Msg("Failed to create sub filesystem")
}
fileServer := http.FileServer(http.FS(staticFS))
// Handle SPA routing - serve index.html for all non-API routes
gzipMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Don't serve index.html for API routes
if strings.HasPrefix(r.URL.Path, "/api") {
http.NotFound(w, r)
return
}
// Try to serve the requested file
path := strings.TrimPrefix(r.URL.Path, "/")
if path == "" {
path = "index.html"
}
// Try to open the file - if it exists, serve it directly
file, err := staticFS.Open(path)
if err == nil {
// File exists, let FileServer handle it (will close the file)
file.Close()
fileServer.ServeHTTP(w, r)
return
}
// File doesn't exist, serve index.html for SPA routing
// Use FileServer to serve index.html efficiently
r.URL.Path = "/index.html"
fileServer.ServeHTTP(w, r)
})
// Initialize main router
mainMux := http.NewServeMux()
// SSE route MUST NOT be gzipped to prevent buffering delays
mainMux.HandleFunc("/api/events", apiSSE)
// Wrap the other routes with gzip compression and attach to main mux
mainMux.Handle("/", gziphandler.GzipHandler(gzipMux))
port := ":8080"
if envPort := os.Getenv("PORT"); envPort != "" {
port = ":" + envPort
}
log.Info().Str("port", port).Msg("🚀 Server starting")
log.Info().Msg("📊 API endpoints:")
log.Info().Msg(" GET /api/monitors - List all monitors")
log.Info().Msg(" POST /api/monitors/create - Create a new monitor")
log.Info().Msg(" GET /api/monitors/export - Export monitors as YAML")
log.Info().Msg(" GET /api/stats - Get overall statistics")
log.Info().Msg(" GET /api/response-time?id=<id>&range=<range> - Get response time data")
log.Info().Msg(" GET /api/monitor?id=<id> - Get specific monitor")
log.Info().Msg(" PUT /api/monitor?id=<id> - Update monitor")
log.Info().Msg(" DELETE /api/monitor?id=<id> - Delete a monitor")
log.Info().Msg(" GET /api/events - Server-Sent Events stream")
log.Fatal().Err(http.ListenAndServe(port, mainMux)).Msg("Server failed")
}