package main import ( "me-fit/db" "me-fit/handler" "me-fit/middleware" "me-fit/service" "me-fit/types" "me-fit/utils" "context" "database/sql" "log" "log/slog" "net/http" "os" "os/signal" "sync" "syscall" "time" "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 := sql.Open("sqlite3", "./data.db") if err != nil { log.Fatal("Could not open Database data.db: ", err) } defer db.Close() run(context.Background(), db, os.Getenv) } func run(ctx context.Context, db *sql.DB, env func(string) string) { ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) defer cancel() slog.Info("Starting server...") // init server settings serverSettings := types.NewServerSettingsFromEnv(env) // init db err := utils.RunMigrations(db, "") if err != nil { slog.Error("Could not run migrations: " + err.Error()) os.Exit(1) } // init servers var prometheusServer *http.Server if serverSettings.PrometheusEnabled { prometheusServer := &http.Server{ Addr: ":8081", Handler: promhttp.Handler(), } go startServer(prometheusServer) } httpServer := &http.Server{ Addr: ":" + serverSettings.Port, Handler: createHandler(db, serverSettings), } go startServer(httpServer) // graceful shutdown var wg sync.WaitGroup wg.Add(2) go shutdownServer(httpServer, ctx, &wg) go shutdownServer(prometheusServer, ctx, &wg) wg.Wait() } func startServer(s *http.Server) { slog.Info("Starting server on " + s.Addr) if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { slog.Error("error listening and serving: " + err.Error()) } } 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 { slog.Error("error shutting down http server: " + err.Error()) } else { slog.Info("Gracefully stopped http server on " + s.Addr) } } func createHandler(d *sql.DB, serverSettings *types.ServerSettings) http.Handler { var router = http.NewServeMux() authDb := db.NewAuthDbSqlite(d) workoutDb := db.NewWorkoutDbSqlite(d) randomService := service.NewRandomServiceImpl() clockService := service.NewClockServiceImpl() mailService := service.NewMailServiceImpl(serverSettings) authService := service.NewAuthServiceImpl(authDb, randomService, clockService, mailService, serverSettings) workoutService := service.NewWorkoutServiceImpl(workoutDb, randomService, clockService, mailService, serverSettings) indexHandler := handler.NewIndexHandler(d, authService, serverSettings) authHandler := handler.NewHandlerAuth(d, authService, serverSettings) workoutHandler := handler.NewWorkoutHandler(d, workoutService, authService, serverSettings) indexHandler.Handle(router) // Serve static files (CSS, JS and images) router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) workoutHandler.Handle(router) authHandler.Handle(router) return middleware.Wrapper( router, middleware.Log, middleware.ContentSecurityPolicy, middleware.Cors(serverSettings), middleware.Corp, middleware.Coop, ) }