Files
public-api/pkg/api/handlers/user.go
itzaname 9f7faadd83
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
Add missing state_id filter in rank call
2025-07-04 19:32:54 -04:00

222 lines
5.7 KiB
Go

package handlers
import (
"git.itzana.me/strafesnet/go-grpc/ranks"
"git.itzana.me/strafesnet/go-grpc/users"
"git.itzana.me/strafesnet/public-api/pkg/api/dto"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net/http"
"strconv"
)
// UserHandler handles HTTP requests related to users.
type UserHandler struct {
*Handler
}
// NewUserHandler creates a new UserHandler with the provided options.
func NewUserHandler(options ...HandlerOption) (*UserHandler, error) {
baseHandler, err := NewHandler(options...)
if err != nil {
return nil, err
}
return &UserHandler{
Handler: baseHandler,
}, nil
}
// @Summary Get user by ID
// @Description Get a specific user by their ID
// @Tags users
// @Produce json
// @Security ApiKeyAuth
// @Param id path int true "User ID"
// @Success 200 {object} dto.Response[dto.User]
// @Failure 404 {object} dto.Error "User not found"
// @Failure default {object} dto.Error "General error response"
// @Router /user/{id} [get]
func (h *UserHandler) Get(ctx *gin.Context) {
// Extract user ID from path parameter
id := ctx.Param("id")
userID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
ctx.JSON(http.StatusBadRequest, dto.Error{
Error: "Invalid user ID format",
})
return
}
// Call the gRPC service
userData, err := users.NewUsersServiceClient(h.dataClient).Get(ctx, &users.IdMessage{
ID: userID,
})
if err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Failed to get user"
// Check if it's a "not found" error
if status.Code(err) == codes.NotFound {
statusCode = http.StatusNotFound
errorMessage = "User not found"
}
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Failed to get user",
)
return
}
// Convert gRPC UserResponse object to dto.UserData object
var user dto.User
result := user.FromGRPC(userData)
// Return the user data
ctx.JSON(http.StatusOK, dto.Response[dto.User]{
Data: *result,
})
}
// @Summary Get rank by user ID
// @Description Get a specific rank for a user by their ID
// @Tags users
// @Produce json
// @Security ApiKeyAuth
// @Param id path int true "User ID"
// @Param filter query dto.UserRankFilter true "Rank query parameters"
// @Success 200 {object} dto.Response[dto.Rank]
// @Failure 404 {object} dto.Error "User not found"
// @Failure default {object} dto.Error "General error response"
// @Router /user/{id}/rank [get]
func (h *UserHandler) GetRank(ctx *gin.Context) {
// Extract user ID from path parameter
id := ctx.Param("id")
userID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
ctx.JSON(http.StatusBadRequest, dto.Error{
Error: "Invalid user ID format",
})
return
}
var filter dto.RankFilter
if err := ctx.ShouldBindQuery(&filter); err != nil {
ctx.JSON(http.StatusBadRequest, dto.Error{
Error: err.Error(),
})
return
}
// Call the gRPC service
rankItem, err := ranks.NewRanksServiceClient(h.dataClient).Get(ctx, &ranks.GetRequest{
UserID: userID,
StyleID: filter.StyleID,
GameID: filter.GameID,
ModeID: filter.ModeID,
StateID: []int32{0, 1},
})
if err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Failed to get rank"
// Check if it's a "not found" error
if status.Code(err) == codes.NotFound {
statusCode = http.StatusNotFound
errorMessage = "Rank not found"
}
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Failed to get rank",
)
return
}
// Convert gRPC Rank object to dto.Rank object
var rank dto.Rank
result := rank.FromGRPC(rankItem)
// Return the user data
ctx.JSON(http.StatusOK, dto.Response[dto.Rank]{
Data: *result,
})
}
// @Summary List users
// @Description Get a list of users
// @Tags users
// @Produce json
// @Security ApiKeyAuth
// @Param page_size query int false "Page size (max 100)" default(10) minimum(1) maximum(100)
// @Param page_number query int false "Page number" default(1) minimum(1)
// @Param filter query dto.UserFilter false "User filter parameters"
// @Success 200 {object} dto.PagedResponse[dto.User]
// @Failure default {object} dto.Error "General error response"
// @Router /user [get]
func (h *UserHandler) List(ctx *gin.Context) {
// Extract and constrain pagination parameters
query := struct {
PageSize int `form:"page_size,default=10" binding:"min=1,max=100"`
PageNumber int `form:"page_number,default=1" binding:"min=1"`
SortBy int `form:"sort_by,default=0" binding:"min=0,max=3"`
}{}
if err := ctx.ShouldBindQuery(&query); err != nil {
ctx.JSON(http.StatusBadRequest, dto.Error{
Error: err.Error(),
})
return
}
// Get list filter
var filter dto.UserFilter
if err := ctx.ShouldBindQuery(&filter); err != nil {
ctx.JSON(http.StatusBadRequest, dto.Error{
Error: err.Error(),
})
return
}
// Call the gRPC service
userList, err := users.NewUsersServiceClient(h.dataClient).List(ctx, &users.ListRequest{
Filter: &users.UserFilter{
StateID: filter.StateID,
},
Page: &users.Pagination{
Size: int32(query.PageSize),
Number: int32(query.PageNumber),
},
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, dto.Error{
Error: "Failed to list users",
})
log.WithError(err).Error(
"Failed to list users",
)
return
}
// Convert gRPC UserResponse objects to dto.UserData objects
dtoUsers := make([]dto.User, len(userList.Users))
for i, u := range userList.Users {
var user dto.User
dtoUsers[i] = *user.FromGRPC(u)
}
// Return the paged response
ctx.JSON(http.StatusOK, dto.PagedResponse[dto.User]{
Data: dtoUsers,
Pagination: dto.Pagination{
Page: query.PageNumber,
PageSize: query.PageSize,
},
})
}