diff --git a/internal/pkg/app/app.go b/internal/pkg/app/app.go index cdd7c7e..5ee9e6f 100644 --- a/internal/pkg/app/app.go +++ b/internal/pkg/app/app.go @@ -16,6 +16,8 @@ import ( ) type App struct { + cfg *config + logger *slog.Logger dbConn *pgxpool.Pool @@ -34,7 +36,7 @@ func NewApp() (*App, error) { app := &App{} // - config - // TODO: add config + app.initConfig() // - logger app.initLogger() // - db diff --git a/internal/pkg/app/config.go b/internal/pkg/app/config.go new file mode 100644 index 0000000..8dd2930 --- /dev/null +++ b/internal/pkg/app/config.go @@ -0,0 +1,13 @@ +package app + +type config struct { + HTTP *configHTTP + DB *configDB +} + +func (a *App) initConfig() { + c := new(config) + c.HTTP = newHTTPConfig() + c.DB = newDBConfig() + a.cfg = c +} diff --git a/internal/pkg/app/db.go b/internal/pkg/app/db.go index e6fa703..788b889 100644 --- a/internal/pkg/app/db.go +++ b/internal/pkg/app/db.go @@ -2,27 +2,17 @@ package app import ( "context" - "os" - "time" "github.com/jackc/pgx/v4/pgxpool" ) func (a *App) initDBConn() error { - databaseURL := os.Getenv("DATABASE_URL") - - config, err := pgxpool.ParseConfig(databaseURL) + cfg, err := pgxpool.ParseConfig(a.cfg.DB.URL) if err != nil { return err } - config.MaxConns = 10 - config.MaxConnIdleTime = 30 * time.Minute - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - db, err := pgxpool.ConnectConfig(ctx, config) + db, err := pgxpool.ConnectConfig(context.TODO(), cfg) if err != nil { return err } diff --git a/internal/pkg/app/db_config.go b/internal/pkg/app/db_config.go new file mode 100644 index 0000000..76b5eb8 --- /dev/null +++ b/internal/pkg/app/db_config.go @@ -0,0 +1,24 @@ +package app + +import "os" + +type configDB struct { + URL string +} + +func newDBConfig() *configDB { + c := &configDB{} + c.Load() + c.Validate() + return c +} + +func (c *configDB) Load() { + c.URL = os.Getenv("DATABASE_URL") +} + +func (c *configDB) Validate() { + if c.URL == "" { + panic("env DATABASE_URL must be set") + } +} diff --git a/internal/pkg/app/http.go b/internal/pkg/app/http.go index 63886b7..9ec0c14 100644 --- a/internal/pkg/app/http.go +++ b/internal/pkg/app/http.go @@ -3,9 +3,8 @@ package app import ( "context" "fmt" + "log/slog" "net/http" - "os" - "time" "github.com/gin-gonic/gin" @@ -23,7 +22,7 @@ func (a *App) initServer() error { a.handlers = handlers.NewHandlers(a.logger, a.repository) - //gin.SetMode(gin.ReleaseMode) + gin.SetMode(a.cfg.HTTP.GinMode) router := gin.Default() router.POST("/api/v1/bookings/:workshop_id", a.handlers.CreateBooking) router.GET("/api/v1/bookings/:workshop_id", a.handlers.ListBookings) @@ -31,13 +30,17 @@ func (a *App) initServer() error { a.router = router server := &http.Server{ - Addr: fmt.Sprintf(":%s", os.Getenv("HTTP_PORT")), - Handler: router, + Addr: fmt.Sprintf(":%s", a.cfg.HTTP.Server.Port), + Handler: http.TimeoutHandler(router, a.cfg.HTTP.Server.HandlerTimeout, "Timeout"), + ErrorLog: slog.NewLogLogger(a.logger.Handler(), slog.LevelError), + ReadHeaderTimeout: a.cfg.HTTP.Server.ReadHeaderTimeout, + ReadTimeout: a.cfg.HTTP.Server.ReadTimeout, + IdleTimeout: a.cfg.HTTP.Server.IdleTimeout, } a.http = server a.closers = append(a.closers, func() error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), a.cfg.HTTP.Server.ShutdownTimeout) defer cancel() return a.http.Shutdown(ctx) diff --git a/internal/pkg/app/http_config.go b/internal/pkg/app/http_config.go new file mode 100644 index 0000000..8437021 --- /dev/null +++ b/internal/pkg/app/http_config.go @@ -0,0 +1,83 @@ +package app + +import ( + "fmt" + "os" + "time" + + "github.com/gin-gonic/gin" +) + +type configHTTP struct { + Server struct { + Port string + HandlerTimeout time.Duration + ReadHeaderTimeout time.Duration + ReadTimeout time.Duration + IdleTimeout time.Duration + ShutdownTimeout time.Duration + } + GinMode string // "debug" | "release" | "test" +} + +func newHTTPConfig() *configHTTP { + c := &configHTTP{} + + // Set default + c.Server.Port = "8080" + c.Server.HandlerTimeout = 6 * time.Second + c.Server.ReadHeaderTimeout = 3 * time.Second + c.Server.ReadTimeout = 3 * time.Second + c.Server.IdleTimeout = 3 * time.Second + c.Server.ShutdownTimeout = 10 * time.Second + + c.Load() + c.Validate() + return c +} + +func (c *configHTTP) Load() { + if httpPort := os.Getenv("HTTP_PORT"); httpPort != "" { + c.Server.Port = httpPort + } + if httpTimeout := os.Getenv("HTTP_TIMEOUT"); httpTimeout != "" { + handlerTimeoutDuration, err := time.ParseDuration(httpTimeout) + if err != nil { + panic(fmt.Errorf("incorrect env HTTP_TIMEOUT: %w", err)) + } + c.Server.HandlerTimeout = handlerTimeoutDuration + } + if httpReadHeaderTimeout := os.Getenv("HTTP_READ_HEADER_TIMEOUT"); httpReadHeaderTimeout != "" { + readHeaderTimeoutDuration, err := time.ParseDuration(httpReadHeaderTimeout) + if err != nil { + panic(fmt.Errorf("incorrect env HTTP_READ_HEADER_TIMEOUT: %w", err)) + } + c.Server.ReadHeaderTimeout = readHeaderTimeoutDuration + } + if httpReadTimeout := os.Getenv("HTTP_READ_TIMEOUT"); httpReadTimeout != "" { + readTimeoutDuration, err := time.ParseDuration(httpReadTimeout) + if err != nil { + panic(fmt.Errorf("incorrect env HTTP_READ_TIMEOUT: %w", err)) + } + c.Server.ReadTimeout = readTimeoutDuration + } + if httpIdleTimeout := os.Getenv("HTTP_IDLE_TIMEOUT"); httpIdleTimeout != "" { + idleTimeoutDuration, err := time.ParseDuration(httpIdleTimeout) + if err != nil { + panic(fmt.Errorf("incorrect env HTTP_IDLE_TIMEOUT: %w", err)) + } + c.Server.IdleTimeout = idleTimeoutDuration + } + if httpShutdownTimeout := os.Getenv("HTTP_SHUTDOWN_TIMEOUT"); httpShutdownTimeout != "" { + shutdownTimeoutDuration, err := time.ParseDuration(httpShutdownTimeout) + if err != nil { + panic(fmt.Errorf("incorrect env HTTP_SHUTDOWN_TIMEOUT: %w", err)) + } + c.Server.ShutdownTimeout = shutdownTimeoutDuration + } + c.GinMode = os.Getenv(gin.EnvGinMode) +} + +func (c *configHTTP) Validate() { + +}