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, }, }) }