package main import ( "me-fit/db" "me-fit/handler" "me-fit/handler/middleware" "me-fit/log" "me-fit/service" "me-fit/types" "context" "database/sql" "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: %v", err) } defer db.Close() run(context.Background(), db, os.Getenv) } func run(ctx context.Context, database *sql.DB, env func(string) string) { 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 { log.Error("Could not run migrations: %v", err) 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(database, 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) { log.Info("Starting server on %v", s.Addr) if err := s.ListenAndServe(); err != nil && 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 *sql.DB, serverSettings *types.Settings) http.Handler { var router = http.NewServeMux() authDb := db.NewAuthSqlite(d) workoutDb := db.NewWorkoutDbSqlite(d) randomService := service.NewRandomImpl() clockService := service.NewClockImpl() mailService := service.NewMailImpl(serverSettings) authService := service.NewAuthImpl(authDb, randomService, clockService, mailService, serverSettings) workoutService := service.NewWorkoutImpl(workoutDb, randomService, clockService, mailService, serverSettings) render := handler.NewRender(serverSettings) indexHandler := handler.NewIndex(authService, render) authHandler := handler.NewAuth(authService, render) workoutHandler := handler.NewWorkout(workoutService, authService, render) indexHandler.Handle(router) workoutHandler.Handle(router) authHandler.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.Log, middleware.ContentSecurityPolicy, middleware.Cors(serverSettings), middleware.Corp, middleware.Coop, ) }