package main import ( "errors" "fmt" "spend-sparrow/db" "spend-sparrow/handler" "spend-sparrow/handler/middleware" "spend-sparrow/log" "spend-sparrow/service" "spend-sparrow/types" "context" "net/http" "os" "os/signal" "sync" "syscall" "time" "github.com/jmoiron/sqlx" "github.com/joho/godotenv" _ "github.com/mattn/go-sqlite3" "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } db, err := sqlx.Open("sqlite3", "./data/spend-sparrow.db") if err != nil { log.Fatal("Could not open Database data.db: %v", err) } defer func() { err := db.Close() log.Fatal("Could not close Database data.db: %v", err) }() err = run(context.Background(), db, os.Getenv) if err != nil { log.Error("Error running server: %v", err) return } } func run(ctx context.Context, database *sqlx.DB, env func(string) string) error { ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) defer cancel() log.Info("Starting server...") // init server settings serverSettings := types.NewSettingsFromEnv(env) // init db err := db.RunMigrations(database, "") if err != nil { return fmt.Errorf("could not run migrations: %w", err) } // init servers var prometheusServer *http.Server if serverSettings.PrometheusEnabled { prometheusServer := &http.Server{ Addr: ":8081", Handler: promhttp.Handler(), ReadHeaderTimeout: 10 * time.Second, } go startServer(prometheusServer) } httpServer := &http.Server{ Addr: ":" + serverSettings.Port, Handler: createHandler(database, serverSettings), ReadHeaderTimeout: 10 * time.Second, } go startServer(httpServer) // graceful shutdown var wg sync.WaitGroup wg.Add(2) go shutdownServer(httpServer, ctx, &wg) go shutdownServer(prometheusServer, ctx, &wg) wg.Wait() return nil } func startServer(s *http.Server) { log.Info("Starting server on %q", s.Addr) if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Error("error listening and serving: %v", err) } } func shutdownServer(s *http.Server, ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() if s == nil { return } <-ctx.Done() shutdownCtx := context.Background() shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second) defer cancel() if err := s.Shutdown(shutdownCtx); err != nil { log.Error("error shutting down http server: %v", err) } else { log.Info("Gracefully stopped http server on %v", s.Addr) } } func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler { var router = http.NewServeMux() authDb := db.NewAuthSqlite(d) randomService := service.NewRandom() clockService := service.NewClock() mailService := service.NewMail(serverSettings) authService := service.NewAuth(authDb, randomService, clockService, mailService, serverSettings) accountService := service.NewAccount(d, randomService, clockService, serverSettings) treasureChestService := service.NewTreasureChest(d, randomService, clockService, serverSettings) transactionService := service.NewTransaction(d, randomService, clockService, serverSettings) transactionRecurringService := service.NewTransactionRecurring(d, randomService, clockService, serverSettings) render := handler.NewRender() indexHandler := handler.NewIndex(render) authHandler := handler.NewAuth(authService, render) accountHandler := handler.NewAccount(accountService, render) treasureChestHandler := handler.NewTreasureChest(treasureChestService, transactionRecurringService, render) transactionHandler := handler.NewTransaction(transactionService, accountService, treasureChestService, render) transactionRecurringHandler := handler.NewTransactionRecurring(transactionRecurringService, render) indexHandler.Handle(router) accountHandler.Handle(router) treasureChestHandler.Handle(router) authHandler.Handle(router) transactionHandler.Handle(router) transactionRecurringHandler.Handle(router) // Serve static files (CSS, JS and images) router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) return middleware.Wrapper( router, middleware.SecurityHeaders(serverSettings), middleware.CacheControl, middleware.CrossSiteRequestForgery(authService), middleware.Authenticate(authService), middleware.Gzip, middleware.Log, ) }