2024-01-25 14:35:04 +02:00
|
|
|
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"
|
2024-01-31 09:18:29 +02:00
|
|
|
"github.com/go-chi/cors"
|
2024-01-25 14:35:04 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
2024-01-31 09:18:29 +02:00
|
|
|
// Basic CORS
|
|
|
|
router.Use(cors.Handler(cors.Options{
|
|
|
|
AllowedOrigins: []string{"https://*", "http://*"},
|
|
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
|
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
|
|
|
ExposedHeaders: []string{"Link"},
|
|
|
|
AllowCredentials: false,
|
|
|
|
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
|
|
|
}))
|
2024-01-25 14:35:04 +02:00
|
|
|
// routes
|
2024-01-31 09:18:29 +02:00
|
|
|
router.Post("/recipe", recipe.New(log, storage))
|
|
|
|
router.Post("/recipes_page", recipes.New(log, storage))
|
|
|
|
router.Post("/recipes_by_category", recipes_by_category.New(log, storage))
|
2024-01-25 14:35:04 +02:00
|
|
|
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 {
|
2024-01-25 14:44:38 +02:00
|
|
|
app.log.Error("failed to start server", slog.String("op", op), sl.Err(err))
|
2024-01-25 14:35:04 +02:00
|
|
|
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 {
|
2024-01-25 14:44:38 +02:00
|
|
|
app.log.Error("failed to stop server", slog.String("op", op), sl.Err(err))
|
2024-01-25 14:35:04 +02:00
|
|
|
}
|
|
|
|
shutdownCancel()
|
|
|
|
}
|