package main import ( "context" "fmt" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "github.com/ultisuite/ulti-backend/internal/config" "github.com/ultisuite/ulti-backend/internal/dbmigrate" "github.com/ultisuite/ulti-backend/internal/envexpand" "github.com/ultisuite/ulti-backend/internal/server" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() loadDotEnv() cfg, err := config.Load() if err != nil { slog.Error("failed to load config", "error", err) os.Exit(1) } if err := dbmigrate.Up(cfg.DatabaseURL); err != nil { slog.Error("database migration failed", "error", err) os.Exit(1) } app, err := server.New(ctx, cfg, server.Options{}) if err != nil { slog.Error("failed to bootstrap server", "error", err) os.Exit(1) } defer app.Close() srv := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.Port), Handler: app.Router, } errCh := make(chan error, 1) go func() { slog.Info("server starting", "port", cfg.Port) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { errCh <- err } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) select { case <-quit: case err := <-errCh: slog.Error("server error", "error", err) } slog.Info("shutting down server") cancel() shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() if err := srv.Shutdown(shutdownCtx); err != nil { slog.Error("server forced shutdown", "error", err) os.Exit(1) } slog.Info("server stopped") } func loadDotEnv() { for _, path := range []string{".env", "../.env"} { if err := envexpand.ApplyFile(path); err == nil { slog.Debug("loaded env file", "path", path) return } } }