package sense import ( "context" "fmt" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "yovision.local/sense/internal/platform" ) type moduleRegistrar func(*platform.App) error var moduleRegistrars []moduleRegistrar // registerModule is intentionally package-local. Feature integration files in // this directory append one registrar without changing the shared bootstrap. func registerModule(registrar moduleRegistrar) { moduleRegistrars = append(moduleRegistrars, registrar) } func Run() error { cfg, err := platform.LoadConfig() if err != nil { return err } logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.LogLevel()})) database, err := platform.OpenDatabase(context.Background(), cfg) if err != nil { return err } if database != nil { defer database.Close() } app := platform.NewApp(cfg, database, logger) for _, registrar := range moduleRegistrars { if err := registrar(app); err != nil { return fmt.Errorf("register Sense module: %w", err) } } if err := app.ApplyMigrations(context.Background()); err != nil { return err } if err := app.Start(context.Background()); err != nil { return fmt.Errorf("start Sense modules: %w", err) } server := &http.Server{ Addr: cfg.HTTPAddress, Handler: app.Handler(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } errCh := make(chan error, 1) go func() { logger.Info("Sense server started", "address", cfg.HTTPAddress, "database_mode", cfg.DatabaseMode) errCh <- server.ListenAndServe() }() stopCh := make(chan os.Signal, 1) signal.Notify(stopCh, syscall.SIGINT, syscall.SIGTERM) select { case <-stopCh: ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() return server.Shutdown(ctx) case serveErr := <-errCh: if serveErr == http.ErrServerClosed { return nil } return serveErr } }