package router import ( "api_yal/internal/config" "api_yal/internal/domain/account" "api_yal/internal/domain/auth" "api_yal/internal/domain/comment" "api_yal/internal/domain/feetback" "api_yal/internal/domain/object" "api_yal/internal/domain/rating" "api_yal/internal/logger" "time" "encoding/json" "net/http" "github.com/go-chi/chi/v5" ChiMiddleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "go.uber.org/zap" "gorm.io/gorm" ) // SetupRouter инициализирует и настраивает основной маршрутизатор приложения func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { zapLogger := logger.Get() zapLogger.Info("Начало настройки маршрутов") r := chi.NewRouter() // ВСЕ middleware должны быть определены ДО маршрутов // 1. Сначала добавляем production middleware addProductionMiddleware(r, config) // 2. Затем добавляем middleware аутентификации (он тоже применяется ко всем маршрутам) zapLogger.Debug("Auth middleware применён") // 3. И только потом регистрируем маршруты // Health check (не требует аутентификации, поэтому должен быть выше AuthMiddleware) r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}); err != nil { zapLogger.Error("Ошибка при отправке health check ответа", zap.String("path", r.URL.Path), zap.Error(err), ) } }) zapLogger.Debug("Health check маршрут зарегистрирован") // Группируем API маршруты под /api/v1 r.Route("/api/v1", func(r chi.Router) { // Регистрируем маршруты аутентификации auth.RegisterRoutes(r, db, config.JWTSecret) // Регистрируем маршруты аккаунтов account.RegisterRoutes(r, db, config.JWTSecret) // Регистрируем маршурты обьектов object.RegisterRoutes(r, db, config.JWTSecret) // Регистрируем маршруты отзывов feetback.RegisterRoutes(r, db, config.JWTSecret) // Регистрация маршрутов для комментариев comment.RegisterRoutes(r, db, config.JWTSecret) // Регистрация маршрутов для райтинга rating.RegisterRoutes(r, db, config.JWTSecret) }) zapLogger.Info("Настройка маршрутов завершена") // Логируем маршруты (опционально) routeLogger := logger.NewRouteLogger(logger.NewWrapper(zapLogger)) routeLogger.LogRoutes(r) return r } // addProductionMiddleware добавляет все middleware для production func addProductionMiddleware(r *chi.Mux, config *config.Config) { // Базовые middleware r.Use(ChiMiddleware.RequestID) r.Use(ChiMiddleware.RealIP) r.Use(ChiMiddleware.Logger) r.Use(ChiMiddleware.Recoverer) r.Use(ChiMiddleware.Timeout(30 * time.Second)) r.Use(ChiMiddleware.Compress(5, "gzip")) r.Use(ChiMiddleware.StripSlashes) // CORS r.Use(cors.Handler(cors.Options{ AllowedOrigins: config.CORS.AllowedOrigins, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Request-ID"}, ExposedHeaders: []string{"Link", "X-Request-ID"}, AllowCredentials: true, MaxAge: 300, })) // Content-Type проверка r.Use(ChiMiddleware.AllowContentType("application/json", "application/xml")) // Rate limiting if config.RateLimit.Enabled { r.Use(ChiMiddleware.Throttle(config.RateLimit.RequestsPerSecond)) } // Security headers r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-XSS-Protection", "1; mode=block") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") if config.Environment == "production" { w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") } next.ServeHTTP(w, r) }) }) }