package handler import ( "encoding/json" "net/http" "strconv" "api_es/internal/dto" appMiddleware "api_es/internal/middleware" "api_es/internal/service" "github.com/go-chi/chi/v5" "github.com/go-playground/validator/v10" ) type UserHandler struct { userService service.UserService validator *validator.Validate } func NewUserHandler(userService service.UserService) *UserHandler { return &UserHandler{ userService: userService, validator: validator.New(), } } // Register godoc // @Summary Register new user // @Description Create a new user account // @Tags auth // @Accept json // @Produce json // @Param request body dto.RegisterRequest true "Register request" // @Success 201 {object} dto.AuthResponse // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /auth/register [post] func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) { var req dto.RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := h.validator.Struct(req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } response, err := h.userService.Register(r.Context(), req) if err != nil { switch err { case service.ErrUserAlreadyExists: http.Error(w, "User already exists", http.StatusConflict) default: http.Error(w, "Internal server error", http.StatusInternalServerError) } return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(response) } // Login godoc // @Summary Login user // @Description Authenticate user and get token // @Tags auth // @Accept json // @Produce json // @Param request body dto.LoginRequest true "Login request" // @Success 200 {object} dto.AuthResponse // @Failure 400 {object} map[string]string // @Failure 401 {object} map[string]string // @Router /auth/login [post] func (h *UserHandler) Login(w http.ResponseWriter, r *http.Request) { var req dto.LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := h.validator.Struct(req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } response, err := h.userService.Login(r.Context(), req) if err != nil { switch err { case service.ErrInvalidCredentials: http.Error(w, "Invalid credentials", http.StatusUnauthorized) default: http.Error(w, err.Error(), http.StatusInternalServerError) } return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // GetProfile godoc // @Summary Get user profile // @Description Get current user profile // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} dto.UserResponse // @Failure 404 {object} map[string]string // @Router /users/profile [get] func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) { userID, ok := r.Context().Value(appMiddleware.UserIDKey).(uint) if !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } user, err := h.userService.GetUserProfile(r.Context(), userID) if err != nil { http.Error(w, "User not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } // UpdateProfile godoc // @Summary Update user profile // @Description Update current user profile // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param request body dto.UpdateUserRequest true "Update request" // @Success 200 {object} dto.UserResponse // @Failure 400 {object} map[string]string // @Router /users/profile [put] func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) { userID, ok := r.Context().Value(appMiddleware.UserIDKey).(uint) if !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } var req dto.UpdateUserRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } user, err := h.userService.UpdateUser(r.Context(), userID, req) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } // GetUser godoc // @Summary Get user by ID // @Description Get user details by ID (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "User ID" // @Success 200 {object} dto.UserResponse // @Failure 404 {object} map[string]string // @Router /users/{id} [get] func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { http.Error(w, "Invalid user ID", http.StatusBadRequest) return } user, err := h.userService.GetUser(r.Context(), uint(id)) if err != nil { http.Error(w, "User not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } // ListUsers godoc // @Summary List users // @Description Get paginated list of users (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param limit query int false "Limit" default(10) // @Param offset query int false "Offset" default(0) // @Success 200 {array} dto.UserResponse // @Router /users [get] func (h *UserHandler) ListUsers(w http.ResponseWriter, r *http.Request) { limitStr := r.URL.Query().Get("limit") offsetStr := r.URL.Query().Get("offset") limit := 10 offset := 0 if limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil { limit = l } } if offsetStr != "" { if o, err := strconv.Atoi(offsetStr); err == nil { offset = o } } users, err := h.userService.ListUsers(r.Context(), limit, offset) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(users) } func (h *UserHandler) Routes(r chi.Router) chi.Router { r.Route("/auth", func(r chi.Router) { r.Post("/register", h.Register) r.Post("/login", h.Login) }) r.Route("/users", func(r chi.Router) { r.Use(appMiddleware.AuthMiddleware) r.Get("/profile", h.GetProfile) r.Put("/profile", h.UpdateProfile) // Admin routes r.With(appMiddleware.AdminMiddleware).Get("/", h.ListUsers) r.With(appMiddleware.AdminMiddleware).Get("/{id}", h.GetUser) }) return r }