// handlers/auth.go package handlers import ( "bytes" "encoding/json" "io" "net/http" "strings" "time" "api_bb/internal/models" "api_bb/internal/service" "api_bb/pkg/logger" "api_bb/pkg/utils" "go.uber.org/zap" ) type AuthHandler struct { authService service.AuthService jwtService service.JWTService logger logger.LoggerInterface } 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"))), } } type RegisterRequest struct { Email string `json:"email"` Password string `json:"password"` FirstName string `json:"firstName"` LastName string `json:"lastName"` Phone string `json:"phone"` Experience string `json:"experience"` Goals string `json:"goals"` Newsletter bool `json:"newsletter"` } type LoginRequest struct { Email string `json:"email"` Password string `json:"password"` } func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { 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 } // Восстанавливаем тело для дальнейшего использования r.Body = io.NopCloser(bytes.NewBuffer(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 { h.logger.Error("failed to decode JSON payload", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON payload: "+err.Error()) return } 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 } user := &models.User{ Email: req.Email, Password: req.Password, FirstName: req.FirstName, LastName: req.LastName, Phone: req.Phone, Experience: req.Experience, Goals: req.Goals, Newsletter: req.Newsletter, Role: "user", CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := h.authService.Register(user); err != nil { 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", "user": toUserResponse(user), }) } func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling login request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) // Проверяем Content-Type if r.Header.Get("Content-Type") != "application/json" { h.logger.Warn("invalid content type", zap.String("content_type", r.Header.Get("Content-Type"))) utils.RespondWithError(w, http.StatusBadRequest, "Content-Type must be application/json") return } // Читаем и логируем тело запроса 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") return } defer r.Body.Close() // Восстанавливаем тело r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) h.logger.Debug("request body", zap.String("body", string(bodyBytes))) var req LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { h.logger.Error("JSON decode failed", zap.Error(err), zap.String("raw_body", string(bodyBytes)), ) utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON: "+err.Error()) return } req.Email = strings.TrimSpace(req.Email) req.Password = strings.TrimSpace(req.Password) // Валидация if req.Email == "" || req.Password == "" { h.logger.Warn("validation failed", zap.String("email", req.Email), zap.Int("password_len", len(req.Password)), ) utils.RespondWithError(w, http.StatusBadRequest, "Email and password are required") return } h.logger.Info("attempting 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 } // Устанавливаем куки http.SetCookie(w, &http.Cookie{ Name: "auth_token", Value: token, Path: "/", HttpOnly: true, Secure: false, SameSite: http.SameSiteLaxMode, Expires: time.Now().Add(24 * time.Hour), }) h.logger.Info("login successful", zap.Uint("user_id", user.ID), zap.String("email", user.Email), ) utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ "message": "Login successful", "token": token, "user": toUserResponse(user), }) } func (h *AuthHandler) Logout(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 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", Value: "", Path: "/", HttpOnly: true, Secure: false, SameSite: http.SameSiteLaxMode, Expires: time.Now().Add(-1 * time.Hour), MaxAge: -1, }) h.logger.Info("user logged out successfully") utils.RespondWithJSON(w, http.StatusOK, map[string]string{ "message": "Logout successful", }) }