From 3807d060a81bf24ce820f70a98df03537f227b4a Mon Sep 17 00:00:00 2001 From: valitovgaziz Date: Sat, 11 Oct 2025 09:21:30 +0500 Subject: [PATCH] divide auth and users handlers --- serv_nginx/api_bb/.env | 2 +- serv_nginx/api_bb/internal/handlers/auth.go | 189 ++++++----------- .../internal/handlers/touserresponse.go | 22 ++ serv_nginx/api_bb/internal/handlers/user.go | 195 ++++++++++++++++++ serv_nginx/api_bb/internal/routes/routes.go | 12 +- serv_nginx/api_bb/pkg/middleware/logger.go | 2 +- .../api_bb/pkg/middleware/middleware.go | 2 +- 7 files changed, 283 insertions(+), 141 deletions(-) create mode 100644 serv_nginx/api_bb/internal/handlers/touserresponse.go create mode 100644 serv_nginx/api_bb/internal/handlers/user.go diff --git a/serv_nginx/api_bb/.env b/serv_nginx/api_bb/.env index c5d5e5f..bd63ad1 100644 --- a/serv_nginx/api_bb/.env +++ b/serv_nginx/api_bb/.env @@ -9,7 +9,7 @@ JWT_SECRET=your-super-secret-jwt-key-change-in-production # .env -LOG_LEVEL=debug +LOG_LEVEL=trace ENVIRONMENT=development # app diff --git a/serv_nginx/api_bb/internal/handlers/auth.go b/serv_nginx/api_bb/internal/handlers/auth.go index edc07c0..499204f 100644 --- a/serv_nginx/api_bb/internal/handlers/auth.go +++ b/serv_nginx/api_bb/internal/handlers/auth.go @@ -4,28 +4,30 @@ package handlers import ( "bytes" "encoding/json" - "fmt" "io" "net/http" "time" "api_bb/internal/models" "api_bb/internal/service" - "api_bb/pkg/middleware" + "api_bb/pkg/logger" "api_bb/pkg/utils" "github.com/go-chi/chi/v5" + "go.uber.org/zap" ) type AuthHandler struct { authService service.AuthService jwtService service.JWTService + logger logger.Interface } func NewAuthHandler(authService service.AuthService, jwtService service.JWTService) *AuthHandler { return &AuthHandler{ authService: authService, jwtService: jwtService, + logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "auth"))), } } @@ -36,21 +38,17 @@ func (h *AuthHandler) Routes() chi.Router { r.Options("/register", h.handleOptions) r.Options("/login", h.handleOptions) r.Options("/logout", h.handleOptions) - r.Options("/profile", h.handleOptions) - r.Options("/editProfile", h.handleOptions) r.Post("/register", h.Register) r.Post("/login", h.Login) r.Post("/logout", h.Logout) - r.Get("/profile", h.GetProfile) - r.Put("/editProfile", h.UpdateProfile) return r } // Обработчик для OPTIONS запросов func (h *AuthHandler) handleOptions(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") w.Header().Set("Access-Control-Max-Age", "300") w.WriteHeader(http.StatusOK) @@ -72,28 +70,21 @@ type LoginRequest struct { Password string `json:"password"` } -type UserResponse struct { - ID uint `json:"id"` - Email string `json:"email"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Phone string `json:"phone"` - Experience string `json:"experience"` - Goals string `json:"goals"` - Newsletter bool `json:"newsletter"` - Role string `json:"role"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { // Устанавливаем CORS заголовки w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Credentials", "true") + h.logger.Info("handling register request", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) + // Логируем тело запроса для отладки bodyBytes, err := io.ReadAll(r.Body) if err != nil { + h.logger.Error("failed to read request body", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Failed to read request body: "+err.Error()) return } @@ -101,35 +92,44 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { // Восстанавливаем тело для дальнейшего использования r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - fmt.Printf("Raw request body: %s\n", string(bodyBytes)) + h.logger.Debug("raw register request body", zap.String("body", string(bodyBytes))) var req RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - fmt.Printf("JSON decode error: %v\n", err) + h.logger.Error("failed to decode JSON payload", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON payload: "+err.Error()) return } - fmt.Printf("Parsed register request: %+v\n", req) + h.logger.Info("parsed register request", + zap.String("email", req.Email), + zap.String("first_name", req.FirstName), + zap.String("last_name", req.LastName), + ) // Валидация обязательных полей if req.FirstName == "" { + h.logger.Warn("register failed - first name required") utils.RespondWithError(w, http.StatusBadRequest, "First name is required") return } if req.LastName == "" { + h.logger.Warn("register failed - last name required") utils.RespondWithError(w, http.StatusBadRequest, "Last name is required") return } if req.Email == "" { + h.logger.Warn("register failed - email required") utils.RespondWithError(w, http.StatusBadRequest, "Email is required") return } if req.Password == "" { + h.logger.Warn("register failed - password required") utils.RespondWithError(w, http.StatusBadRequest, "Password is required") return } if len(req.Password) < 6 { + h.logger.Warn("register failed - password too short") utils.RespondWithError(w, http.StatusBadRequest, "Password must be at least 6 characters") return } @@ -149,11 +149,19 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { } if err := h.authService.Register(user); err != nil { - fmt.Printf("Auth service error: %v\n", err) + h.logger.Error("auth service registration failed", + zap.String("email", req.Email), + zap.Error(err), + ) utils.RespondWithError(w, http.StatusBadRequest, err.Error()) return } + h.logger.Info("user registered successfully", + zap.Uint("user_id", user.ID), + zap.String("email", user.Email), + ) + // После успешной регистрации возвращаем данные пользователя utils.RespondWithJSON(w, http.StatusCreated, map[string]interface{}{ "message": "User registered successfully", @@ -166,20 +174,34 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Credentials", "true") + h.logger.Info("handling login request", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) + var req LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + h.logger.Error("failed to decode login request", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error()) return } // Валидация if req.Email == "" || req.Password == "" { + h.logger.Warn("login failed - email or password empty") utils.RespondWithError(w, http.StatusBadRequest, "Email and password are required") return } + h.logger.Info("attempting user login", zap.String("email", req.Email)) + user, token, err := h.authService.Login(req.Email, req.Password) if err != nil { + h.logger.Warn("login failed", + zap.String("email", req.Email), + zap.Error(err), + ) utils.RespondWithError(w, http.StatusUnauthorized, err.Error()) return } @@ -195,6 +217,11 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { Expires: time.Now().Add(24 * time.Hour), }) + h.logger.Info("user logged in successfully", + zap.Uint("user_id", user.ID), + zap.String("email", user.Email), + ) + utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ "message": "Login successful", "token": token, @@ -207,6 +234,12 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Credentials", "true") + h.logger.Info("handling logout request", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) + // Удаляем куку http.SetCookie(w, &http.Cookie{ Name: "auth_token", @@ -219,112 +252,10 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { MaxAge: -1, }) + h.logger.Info("user logged out successfully") + utils.RespondWithJSON(w, http.StatusOK, map[string]string{ "message": "Logout successful", }) } -func (h *AuthHandler) GetProfile(w http.ResponseWriter, r *http.Request) { - // Устанавливаем CORS заголовки - w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) - w.Header().Set("Access-Control-Allow-Credentials", "true") - - user, ok := middleware.GetUserFromContext(r.Context()) - if !ok { - utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") - return - } - - utils.RespondWithJSON(w, http.StatusOK, toUserResponse(user)) -} - -func toUserResponse(user *models.User) UserResponse { - return UserResponse{ - ID: user.ID, - Email: user.Email, - FirstName: user.FirstName, - LastName: user.LastName, - Phone: user.Phone, - Experience: user.Experience, - Goals: user.Goals, - Newsletter: user.Newsletter, - Role: user.Role, - CreatedAt: user.CreatedAt, - UpdatedAt: user.UpdatedAt, - } -} - -type UpdateProfileRequest struct { - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Phone string `json:"phone"` - Experience string `json:"experience"` - Goals string `json:"goals"` - Newsletter bool `json:"newsletter"` -} - -func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) { - // Устанавливаем CORS заголовки - w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Access-Control-Allow-Methods", "PUT, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") - - // Логируем тело запроса для отладки - bodyBytes, err := io.ReadAll(r.Body) - if err != nil { - utils.RespondWithError(w, http.StatusBadRequest, "Failed to read request body: "+err.Error()) - return - } - - // Восстанавливаем тело для дальнейшего использования - r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - - fmt.Printf("Raw request body: %s\n", string(bodyBytes)) - - // Получаем пользователя из контекста - currentUser, ok := middleware.GetUserFromContext(r.Context()) - if !ok { - utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") - return - } - - var req UpdateProfileRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error()) - return - } - - // Валидация обязательных полей - if req.FirstName == "" { - utils.RespondWithError(w, http.StatusBadRequest, "First name is required") - return - } - if req.LastName == "" { - utils.RespondWithError(w, http.StatusBadRequest, "Last name is required") - return - } - - // Обновляем данные пользователя - updatedUser := &models.User{ - ID: currentUser.ID, - FirstName: req.FirstName, - LastName: req.LastName, - Phone: req.Phone, - Experience: req.Experience, - Goals: req.Goals, - Newsletter: req.Newsletter, - UpdatedAt: time.Now(), - } - - // Сохраняем обновленные данные - if err := h.authService.UpdateProfile(updatedUser); err != nil { - utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update profile: "+err.Error()) - return - } - - utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ - "message": "Profile updated successfully", - "user": toUserResponse(updatedUser), - }) -} diff --git a/serv_nginx/api_bb/internal/handlers/touserresponse.go b/serv_nginx/api_bb/internal/handlers/touserresponse.go new file mode 100644 index 0000000..161a7c8 --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/touserresponse.go @@ -0,0 +1,22 @@ +package handlers + +import ( + "api_bb/internal/models" +) + +// Общая функция для преобразования User в UserResponse +func toUserResponse(user *models.User) UserResponse { + return UserResponse{ + ID: user.ID, + Email: user.Email, + FirstName: user.FirstName, + LastName: user.LastName, + Phone: user.Phone, + Experience: user.Experience, + Goals: user.Goals, + Newsletter: user.Newsletter, + Role: user.Role, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } +} diff --git a/serv_nginx/api_bb/internal/handlers/user.go b/serv_nginx/api_bb/internal/handlers/user.go new file mode 100644 index 0000000..127d7cb --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/user.go @@ -0,0 +1,195 @@ +// handlers/user.go +package handlers + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "time" + + "api_bb/internal/models" + "api_bb/internal/service" + "api_bb/pkg/logger" + "api_bb/pkg/middleware" + "api_bb/pkg/utils" + + "github.com/go-chi/chi/v5" + "go.uber.org/zap" +) + +type UserHandler struct { + authService service.AuthService + logger logger.Interface +} + +func NewUserHandler(authService service.AuthService) *UserHandler { + return &UserHandler{ + authService: authService, + logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "user"))), + } +} + +func (h *UserHandler) Routes() chi.Router { + r := chi.NewRouter() + + // Обработка OPTIONS запросов для CORS + r.Options("/profile", h.handleOptions) + r.Options("/editProfile", h.handleOptions) + + r.Get("/profile", h.GetProfile) + r.Put("/editProfile", h.UpdateProfile) + + return r +} + +// Обработчик для OPTIONS запросов +func (h *UserHandler) handleOptions(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Methods", "PUT, GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + w.Header().Set("Access-Control-Max-Age", "300") + w.WriteHeader(http.StatusOK) +} + +type UserResponse struct { + ID uint `json:"id"` + Email string `json:"email"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Phone string `json:"phone"` + Experience string `json:"experience"` + Goals string `json:"goals"` + Newsletter bool `json:"newsletter"` + Role string `json:"role"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) { + // Устанавливаем CORS заголовки + w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) + w.Header().Set("Access-Control-Allow-Credentials", "true") + + h.logger.Info("handling get profile request", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) + + user, ok := middleware.GetUserFromContext(r.Context()) + if !ok { + h.logger.Warn("get profile failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + h.logger.Info("profile retrieved successfully", + zap.Uint("user_id", user.ID), + zap.String("email", user.Email), + ) + + utils.RespondWithJSON(w, http.StatusOK, toUserResponse(user)) +} + +type UpdateProfileRequest struct { + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Phone string `json:"phone"` + Experience string `json:"experience"` + Goals string `json:"goals"` + Newsletter bool `json:"newsletter"` +} + +func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) { + // Устанавливаем CORS заголовки + w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) + w.Header().Set("Access-Control-Allow-Credentials", "true") + w.Header().Set("Access-Control-Allow-Methods", "PUT, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + h.logger.Info("handling update profile request", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) + + // Логируем тело запроса для отладки + bodyBytes, err := io.ReadAll(r.Body) + if err != nil { + h.logger.Error("failed to read request body", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Failed to read request body: "+err.Error()) + return + } + + // Восстанавливаем тело для дальнейшего использования + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + + h.logger.Debug("raw request body", zap.String("body", string(bodyBytes))) + + // Получаем пользователя из контекста + currentUser, ok := middleware.GetUserFromContext(r.Context()) + if !ok { + h.logger.Warn("update profile failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + var req UpdateProfileRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + h.logger.Error("failed to decode JSON payload", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error()) + return + } + + // Валидация обязательных полей + if req.FirstName == "" { + h.logger.Warn("update profile failed - first name required") + utils.RespondWithError(w, http.StatusBadRequest, "First name is required") + return + } + if req.LastName == "" { + h.logger.Warn("update profile failed - last name required") + utils.RespondWithError(w, http.StatusBadRequest, "Last name is required") + return + } + + h.logger.Info("updating user profile", + zap.Uint("user_id", currentUser.ID), + zap.String("first_name", req.FirstName), + zap.String("last_name", req.LastName), + zap.String("experience", req.Experience), + zap.String("goals", req.Goals), + zap.Bool("newsletter", req.Newsletter), + ) + + // Обновляем данные пользователя + updatedUser := &models.User{ + ID: currentUser.ID, + FirstName: req.FirstName, + LastName: req.LastName, + Phone: req.Phone, + Experience: req.Experience, + Goals: req.Goals, + Newsletter: req.Newsletter, + UpdatedAt: time.Now(), + } + + // Сохраняем обновленные данные + if err := h.authService.UpdateProfile(updatedUser); err != nil { + h.logger.Error("failed to update profile in service", + zap.Uint("user_id", currentUser.ID), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update profile: "+err.Error()) + return + } + + h.logger.Info("profile updated successfully", + zap.Uint("user_id", currentUser.ID), + ) + + utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ + "message": "Profile updated successfully", + "user": toUserResponse(updatedUser), + }) +} diff --git a/serv_nginx/api_bb/internal/routes/routes.go b/serv_nginx/api_bb/internal/routes/routes.go index b429612..98d3ce1 100644 --- a/serv_nginx/api_bb/internal/routes/routes.go +++ b/serv_nginx/api_bb/internal/routes/routes.go @@ -32,6 +32,7 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { // Initialize handlers healthHandler := handlers.NewHealthHandler() authHandler := handlers.NewAuthHandler(authService, jwtService) + userHandler := handlers.NewUserHandler(authService) // Health routes r.Mount("/", healthHandler.Routes()) @@ -42,15 +43,8 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { // Public auth routes r.Mount("/auth", authHandler.Routes()) - - // Protected routes - r.Route("/user", func(r chi.Router) { - r.Use(middleware.AuthMiddleware(jwtService, userRepo)) - r.Use(middleware.RequireAuth) - - r.Get("/profile", authHandler.GetProfile) - // Здесь будут другие защищенные маршруты пользователя - }) + r.Mount("/user", userHandler.Routes()) + // Здесь будут добавлены другие маршруты: // r.Mount("/events", eventHandler.Routes()) diff --git a/serv_nginx/api_bb/pkg/middleware/logger.go b/serv_nginx/api_bb/pkg/middleware/logger.go index 0644efe..e77fdbb 100644 --- a/serv_nginx/api_bb/pkg/middleware/logger.go +++ b/serv_nginx/api_bb/pkg/middleware/logger.go @@ -12,7 +12,7 @@ import ( ) // Logger middleware для логирования HTTP запросов -func Logger(next http.Handler) http.Handler { +func ZapLogger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() diff --git a/serv_nginx/api_bb/pkg/middleware/middleware.go b/serv_nginx/api_bb/pkg/middleware/middleware.go index 1547337..265f6cc 100644 --- a/serv_nginx/api_bb/pkg/middleware/middleware.go +++ b/serv_nginx/api_bb/pkg/middleware/middleware.go @@ -9,7 +9,7 @@ import ( func CommonMiddleware() []func(http.Handler) http.Handler { return []func(http.Handler) http.Handler{ - middleware.Logger, + ZapLogger, middleware.Recoverer, middleware.RequestID, cors.Handler(cors.Options{