package internalhttp import ( "context" "net/http" "strconv" "strings" "time" "galaxy/user/internal/service/adminusers" "galaxy/user/internal/service/shared" "github.com/gin-gonic/gin" ) type getUserByEmailRequest struct { Email string `json:"email"` } type getUserByRaceNameRequest struct { RaceName string `json:"race_name"` } func handleGetUserByID(useCase GetUserByIDUseCase, timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) { callCtx, cancel := context.WithTimeout(c.Request.Context(), timeout) defer cancel() result, err := useCase.Execute(callCtx, adminusers.GetUserByIDInput{ UserID: c.Param("user_id"), }) if err != nil { abortWithProjection(c, shared.ProjectInternalError(err)) return } c.JSON(http.StatusOK, result) } } func handleGetUserByEmail(useCase GetUserByEmailUseCase, timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) { var request getUserByEmailRequest if err := decodeJSONRequest(c.Request, &request); err != nil { abortWithProjection(c, shared.ProjectInternalError(shared.InvalidRequest(err.Error()))) return } callCtx, cancel := context.WithTimeout(c.Request.Context(), timeout) defer cancel() result, err := useCase.Execute(callCtx, adminusers.GetUserByEmailInput{ Email: request.Email, }) if err != nil { abortWithProjection(c, shared.ProjectInternalError(err)) return } c.JSON(http.StatusOK, result) } } func handleGetUserByRaceName(useCase GetUserByRaceNameUseCase, timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) { var request getUserByRaceNameRequest if err := decodeJSONRequest(c.Request, &request); err != nil { abortWithProjection(c, shared.ProjectInternalError(shared.InvalidRequest(err.Error()))) return } callCtx, cancel := context.WithTimeout(c.Request.Context(), timeout) defer cancel() result, err := useCase.Execute(callCtx, adminusers.GetUserByRaceNameInput{ RaceName: request.RaceName, }) if err != nil { abortWithProjection(c, shared.ProjectInternalError(err)) return } c.JSON(http.StatusOK, result) } } func handleListUsers(useCase ListUsersUseCase, timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) { input, err := buildListUsersInput(c) if err != nil { abortWithProjection(c, shared.ProjectInternalError(err)) return } callCtx, cancel := context.WithTimeout(c.Request.Context(), timeout) defer cancel() result, err := useCase.Execute(callCtx, input) if err != nil { abortWithProjection(c, shared.ProjectInternalError(err)) return } c.JSON(http.StatusOK, result) } } func buildListUsersInput(c *gin.Context) (adminusers.ListUsersInput, error) { pageSize, err := parseOptionalPageSize(c, "page_size") if err != nil { return adminusers.ListUsersInput{}, err } pageToken, err := parseOptionalPageToken(c, "page_token") if err != nil { return adminusers.ListUsersInput{}, err } paidExpiresBefore, err := parseOptionalRFC3339Query(c, "paid_expires_before") if err != nil { return adminusers.ListUsersInput{}, err } paidExpiresAfter, err := parseOptionalRFC3339Query(c, "paid_expires_after") if err != nil { return adminusers.ListUsersInput{}, err } canLogin, err := parseOptionalBoolQuery(c, "can_login") if err != nil { return adminusers.ListUsersInput{}, err } canCreatePrivateGame, err := parseOptionalBoolQuery(c, "can_create_private_game") if err != nil { return adminusers.ListUsersInput{}, err } canJoinGame, err := parseOptionalBoolQuery(c, "can_join_game") if err != nil { return adminusers.ListUsersInput{}, err } return adminusers.ListUsersInput{ PageSize: pageSize, PageToken: pageToken, PaidState: c.Query("paid_state"), PaidExpiresBefore: paidExpiresBefore, PaidExpiresAfter: paidExpiresAfter, DeclaredCountry: c.Query("declared_country"), SanctionCode: c.Query("sanction_code"), LimitCode: c.Query("limit_code"), CanLogin: canLogin, CanCreatePrivateGame: canCreatePrivateGame, CanJoinGame: canJoinGame, }, nil } func parseOptionalPageSize(c *gin.Context, name string) (int, error) { raw, present := c.GetQuery(name) if !present { return 0, nil } value, err := strconv.Atoi(strings.TrimSpace(raw)) if err != nil || value < 1 || value > 200 { return 0, shared.InvalidRequest("page_size must be between 1 and 200") } return value, nil } func parseOptionalPageToken(c *gin.Context, name string) (string, error) { raw, present := c.GetQuery(name) if !present { return "", nil } if strings.TrimSpace(raw) != raw { return "", shared.InvalidRequest("page_token must not contain surrounding whitespace") } return raw, nil } func parseOptionalRFC3339Query(c *gin.Context, name string) (*time.Time, error) { raw, present := c.GetQuery(name) if !present { return nil, nil } parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(raw)) if err != nil { return nil, shared.InvalidRequest(name + " must be a valid RFC 3339 timestamp") } return &parsed, nil } func parseOptionalBoolQuery(c *gin.Context, name string) (*bool, error) { raw, present := c.GetQuery(name) if !present { return nil, nil } parsed, err := strconv.ParseBool(strings.TrimSpace(raw)) if err != nil { return nil, shared.InvalidRequest(name + " must be a valid boolean") } return &parsed, nil }