diff --git a/cmd/recipes/main.go b/cmd/recipes/main.go index 57c2894..25472a8 100644 --- a/cmd/recipes/main.go +++ b/cmd/recipes/main.go @@ -5,6 +5,7 @@ import ( "log/slog" "os" "os/signal" + "recipes/internal/app" "recipes/internal/config" "syscall" @@ -19,7 +20,6 @@ const ( //TODO // cache -// http server // app // graceful sd // tests @@ -34,6 +34,9 @@ func main() { log.Debug("Application config", slog.Any("config", fmt.Sprintf("%+v", *cfg))) // init app (storage, cache, media storage) + application := app.New(log, cfg) + // start app + go application.HTTPSrv.MustRun() // graceful shutdown stop := make(chan os.Signal, 1) @@ -43,7 +46,7 @@ func main() { log.Info("stopping application...") - // application.GRPCSrv.Stop() + application.HTTPSrv.Stop() log.Info("application stopped") } diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..1eb84c8 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,31 @@ +package app + +import ( + "context" + "log/slog" + httpsrv "recipes/internal/app/httpSrv" + "recipes/internal/config" + "recipes/internal/media_storage/minio" + "recipes/internal/storage/postgresql" +) + +type App struct { + HTTPSrv *httpsrv.App +} + +func New(log *slog.Logger, cfg *config.Config) *App { + // init storage + storage, err := postgresql.New(context.Background(), cfg.Postgresql.User, cfg.Postgresql.Password, cfg.Postgresql.Address, cfg.Postgresql.DBName) + if err != nil { + panic(err) + } + // init media storage + mstorage, err := minio.New(context.Background(), cfg.Minio.Address, cfg.Minio.User, cfg.Minio.Password) + if err != nil { + panic(err) + } + // init http server + httpsrv := httpsrv.New(log, &cfg.HTTPServerConfig, storage, mstorage) + // return + return &App{HTTPSrv: httpsrv} +} diff --git a/internal/app/httpSrv/httpSrv.go b/internal/app/httpSrv/httpSrv.go new file mode 100644 index 0000000..a6e4dcb --- /dev/null +++ b/internal/app/httpSrv/httpSrv.go @@ -0,0 +1,82 @@ +package httpsrv + +import ( + "context" + "log/slog" + "net/http" + "recipes/internal/config" + "recipes/internal/http-server/handlers/recipe" + recipeimage "recipes/internal/http-server/handlers/recipeImage" + "recipes/internal/http-server/handlers/recipes" + recipes_by_category "recipes/internal/http-server/handlers/recipesByCategory" + mwLogger "recipes/internal/http-server/middleware/logger" + "recipes/internal/lib/logger/sl" + "time" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +type App struct { + log *slog.Logger + router *chi.Mux + srv *http.Server +} + +type Storage interface { + recipe.RecipeProvider + recipes.RecipesProvider + recipes_by_category.RecipesProvider +} + +type MediaStorage interface { + recipeimage.ImageProvider +} + +func New(log *slog.Logger, cfg *config.HTTPServerConfig, storage Storage, mediaStorage MediaStorage) *App { + // init router + router := chi.NewRouter() + // middlewares + router.Use(middleware.RequestID) + router.Use(mwLogger.New(log)) + router.Use(middleware.Recoverer) + // routes + router.Get("/recipe", recipe.New(log, storage)) + router.Get("/recipes_page", recipes.New(log, storage)) + router.Get("/recipes_by_category", recipes_by_category.New(log, storage)) + router.Get("/recipe_img", recipeimage.New(log, mediaStorage)) + // server config + srv := &http.Server{ + Addr: cfg.Address, + Handler: router, + ReadTimeout: cfg.Timeout, + WriteTimeout: cfg.Timeout, + IdleTimeout: cfg.IdleTimeout, + } + + return &App{ + log: log, + router: router, + srv: srv, + } +} + +// MustRun starts http server or panic in case of error. Blocking function. +func (app *App) MustRun() { + const op = "app.httpSrv.Stop" + if err := app.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + app.log.Error("failed to start server", sl.Err(err)) + panic(err) + } + app.log.Info("HTTP server stopped") +} + +func (app *App) Stop() { + const op = "app.httpSrv.Stop" + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) + err := app.srv.Shutdown(shutdownCtx) + if err != nil { + app.log.Error("failed to stop server", sl.Err(err)) + } + shutdownCancel() +} diff --git a/internal/config/config.go b/internal/config/config.go index f41a7b3..8fba01d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,13 +10,9 @@ import ( ) type Config struct { - Env string `yaml:"env" env-required:"true"` - HTTPServer struct { - Address string `yaml:"address" env-required:"true"` - Timeout time.Duration `yaml:"timeout" env-default:"4s"` - IdleTimeout time.Duration `yaml:"idle_timeout" env-default:"60s"` - } `yaml:"http-server"` - Postgresql struct { + Env string `yaml:"env" env-required:"true"` + HTTPServerConfig `yaml:"http-server"` + Postgresql struct { DBName string `yaml:"db_name" env-required:"true"` User string `yaml:"user" env-required:"true"` Password string `yaml:"password" env-required:"true"` @@ -33,6 +29,12 @@ type Config struct { } `yaml:"minio"` } +type HTTPServerConfig struct { + Address string `yaml:"address" env-required:"true"` + Timeout time.Duration `yaml:"timeout" env-default:"4s"` + IdleTimeout time.Duration `yaml:"idle_timeout" env-default:"60s"` +} + // MustLoad returns config or panic. func MustLoad() *Config { configPath := fetchConfigPath() diff --git a/internal/http-server/handlers/recipesByCategory/recipesByCategory.go b/internal/http-server/handlers/recipesByCategory/recipesByCategory.go index 1b810ef..3b9b8ce 100644 --- a/internal/http-server/handlers/recipesByCategory/recipesByCategory.go +++ b/internal/http-server/handlers/recipesByCategory/recipesByCategory.go @@ -63,7 +63,6 @@ func New(log *slog.Logger, recipesProvider RecipesProvider) http.HandlerFunc { return } // return - // return render.JSON(w, r, Response{ Response: resp.OK(), Recipes: recipes, diff --git a/internal/http-server/middleware/logger.go b/internal/http-server/middleware/logger/logger.go similarity index 98% rename from internal/http-server/middleware/logger.go rename to internal/http-server/middleware/logger/logger.go index 04b33aa..c39b714 100644 --- a/internal/http-server/middleware/logger.go +++ b/internal/http-server/middleware/logger/logger.go @@ -1,4 +1,4 @@ -package middleware +package logger import ( "log/slog"