package main import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "reanimator/internal/api" "reanimator/internal/config" "reanimator/internal/repository" "reanimator/internal/repository/migrate" ) func main() { slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, }))) cfg, err := config.Load() if err != nil { slog.Error("load config failed", "err", err) os.Exit(1) } db, err := repository.Open(cfg.DatabaseDSN) if err != nil { slog.Error("open database failed", "err", err) os.Exit(1) } defer func() { _ = db.Close() }() if err := migrate.EnsureSchema(db, cfg.MigrationsDir); err != nil { slog.Error("apply migrations failed", "err", err) os.Exit(1) } if err := migrate.ValidateOwnershipSchema(db); err != nil { slog.Error("schema validation failed", "err", err) os.Exit(1) } slog.Info("database schema ready") srv := api.NewServer(cfg.HTTPAddr, cfg.ReadTimeout, cfg.WriteTimeout, db) errCh := make(chan error, 1) go func() { slog.Info("starting API server", "addr", cfg.HTTPAddr) errCh <- srv.Start() }() signalCh := make(chan os.Signal, 1) signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) select { case sig := <-signalCh: slog.Info("received signal, shutting down", "signal", sig) case err := <-errCh: if err == nil || errors.Is(err, http.ErrServerClosed) { return } slog.Error("server error", "err", err) os.Exit(1) } ctx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownGrace) defer cancel() if err := srv.Shutdown(ctx); err != nil { slog.Error("shutdown server failed", "err", err) os.Exit(1) } // Ensure in-flight logs flush before exit in short-lived environments. time.Sleep(25 * time.Millisecond) }