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:
224
backend/internal/api/handlers/users.go
Normal file
224
backend/internal/api/handlers/users.go
Normal 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",
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user