feat: Implement User Management API (Issue #6) and wire up router

- Create UserService with CRUD operations
- Implement ListUsers with pagination
- Implement GetUser, CreateUser, UpdateUser, DeleteUser
- Add ResetUserPassword for admin password resets
- Create UserHandler with HTTP endpoints
- Update router to wire up all handlers and middleware
- Add authentication middleware to protected routes
- Add admin-only middleware to user management routes

Closes #6

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-02-05 00:51:02 +03:00
parent bddd988b36
commit 9bd17156e4
3 changed files with 509 additions and 57 deletions

View File

@@ -0,0 +1,224 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/yourusername/victorialogs-manager/internal/services"
)
// UserHandler handles user management endpoints
type UserHandler struct {
userService *services.UserService
}
// NewUserHandler creates a new user handler
func NewUserHandler(userService *services.UserService) *UserHandler {
return &UserHandler{
userService: userService,
}
}
// ListUsers returns a paginated list of users
// @Summary List users
// @Description Get paginated list of users (admin only)
// @Tags users
// @Produce json
// @Param limit query int false "Limit" default(20)
// @Param offset query int false "Offset" default(0)
// @Success 200 {object} services.ListUsersResponse
// @Failure 401 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/users [get]
func (h *UserHandler) ListUsers(c *gin.Context) {
var req services.ListUsersRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid request",
Message: err.Error(),
})
return
}
resp, err := h.userService.ListUsers(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse{
Error: "failed to list users",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, resp)
}
// GetUser returns a user by ID
// @Summary Get user
// @Description Get user by ID (admin only)
// @Tags users
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} models.User
// @Failure 401 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/users/{id} [get]
func (h *UserHandler) GetUser(c *gin.Context) {
id := c.Param("id")
user, err := h.userService.GetUser(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, ErrorResponse{
Error: "user not found",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, user)
}
// CreateUser creates a new user
// @Summary Create user
// @Description Create a new user (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Param request body services.CreateUserRequest true "User data"
// @Success 201 {object} models.User
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/users [post]
func (h *UserHandler) CreateUser(c *gin.Context) {
var req services.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid request",
Message: err.Error(),
})
return
}
user, err := h.userService.CreateUser(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "failed to create user",
Message: err.Error(),
})
return
}
c.JSON(http.StatusCreated, user)
}
// UpdateUser updates a user
// @Summary Update user
// @Description Update user information (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Param request body services.UpdateUserRequest true "User data"
// @Success 200 {object} models.User
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/users/{id} [put]
func (h *UserHandler) UpdateUser(c *gin.Context) {
id := c.Param("id")
var req services.UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid request",
Message: err.Error(),
})
return
}
user, err := h.userService.UpdateUser(c.Request.Context(), id, &req)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "failed to update user",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, user)
}
// DeleteUser deletes a user
// @Summary Delete user
// @Description Delete a user (admin only)
// @Tags users
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/users/{id} [delete]
func (h *UserHandler) DeleteUser(c *gin.Context) {
id := c.Param("id")
err := h.userService.DeleteUser(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "failed to delete user",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, SuccessResponse{
Message: "user deleted successfully",
})
}
// ResetUserPassword resets a user's password
// @Summary Reset user password
// @Description Reset a user's password (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Param request body services.ResetPasswordRequest true "New password"
// @Success 200 {object} SuccessResponse
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/users/{id}/reset-password [post]
func (h *UserHandler) ResetUserPassword(c *gin.Context) {
id := c.Param("id")
var req services.ResetPasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid request",
Message: err.Error(),
})
return
}
err := h.userService.ResetUserPassword(c.Request.Context(), id, &req)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "failed to reset password",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, SuccessResponse{
Message: "password reset successfully",
})
}