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 getUserByUserNameRequest struct { UserName string `json:"user_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 handleGetUserByUserName(useCase GetUserByUserNameUseCase, timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) { var request getUserByUserNameRequest 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.GetUserByUserNameInput{ UserName: request.UserName, }) 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"), UserName: c.Query("user_name"), DisplayName: c.Query("display_name"), DisplayNameMatch: c.Query("display_name_match"), 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 }