feat: user service

This commit is contained in:
Ilia Denisov
2026-04-10 19:05:02 +02:00
committed by GitHub
parent 710bad712e
commit 23ffcb7535
140 changed files with 33418 additions and 952 deletions
@@ -0,0 +1,205 @@
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
}