package http import ( "errors" "hyapp/services/gateway-service/internal/transport/http/httpkit" "io" "net/http" "strconv" "strings" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" "hyapp/services/gateway-service/internal/auth" ) type resourceData struct { ResourceID int64 `json:"resource_id"` ResourceCode string `json:"resource_code"` ResourceType string `json:"resource_type"` Name string `json:"name"` Status string `json:"status"` Grantable bool `json:"grantable"` GrantStrategy string `json:"grant_strategy"` WalletAssetType string `json:"wallet_asset_type,omitempty"` WalletAssetAmount int64 `json:"wallet_asset_amount,omitempty"` UsageScopes []string `json:"usage_scopes"` AssetURL string `json:"asset_url"` PreviewURL string `json:"preview_url"` AnimationURL string `json:"animation_url"` MetadataJSON string `json:"metadata_json"` SortOrder int32 `json:"sort_order"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } type resourceGroupItemData struct { GroupItemID int64 `json:"group_item_id"` ItemType string `json:"item_type"` ResourceID int64 `json:"resource_id"` Resource *resourceData `json:"resource,omitempty"` WalletAssetType string `json:"wallet_asset_type,omitempty"` WalletAssetAmount int64 `json:"wallet_asset_amount,omitempty"` Quantity int64 `json:"quantity"` DurationMS int64 `json:"duration_ms"` SortOrder int32 `json:"sort_order"` } type resourceGroupData struct { GroupID int64 `json:"group_id"` GroupCode string `json:"group_code"` Name string `json:"name"` Status string `json:"status"` Description string `json:"description"` SortOrder int32 `json:"sort_order"` Items []resourceGroupItemData `json:"items"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } type giftConfigData struct { GiftID string `json:"gift_id"` ResourceID int64 `json:"resource_id"` Resource resourceData `json:"resource"` Status string `json:"status"` Name string `json:"name"` SortOrder int32 `json:"sort_order"` PresentationJSON string `json:"presentation_json"` PriceVersion string `json:"price_version"` GiftTypeCode string `json:"gift_type_code"` ChargeAssetType string `json:"charge_asset_type"` CoinPrice int64 `json:"coin_price"` GiftPointAmount int64 `json:"gift_point_amount"` HeatValue int64 `json:"heat_value"` EffectiveFromMS int64 `json:"effective_from_ms"` EffectiveToMS int64 `json:"effective_to_ms"` EffectTypes []string `json:"effect_types"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` RegionIDs []int64 `json:"region_ids"` } type userResourceData struct { EntitlementID string `json:"entitlement_id"` ResourceID int64 `json:"resource_id"` Resource resourceData `json:"resource"` Status string `json:"status"` Quantity int64 `json:"quantity"` RemainingQuantity int64 `json:"remaining_quantity"` EffectiveAtMS int64 `json:"effective_at_ms"` ExpiresAtMS int64 `json:"expires_at_ms"` SourceGrantID string `json:"source_grant_id"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } func (h *Handler) listResources(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } page, pageSize, ok := resourcePage(request) if !ok { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } resp, err := h.walletClient.ListResources(request.Context(), &walletv1.ListResourcesRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), ResourceType: strings.TrimSpace(request.URL.Query().Get("resource_type")), Keyword: strings.TrimSpace(request.URL.Query().Get("keyword")), Page: page, PageSize: pageSize, ActiveOnly: true, }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } items := make([]resourceData, 0, len(resp.GetResources())) for _, item := range resp.GetResources() { items = append(items, resourceFromProto(item)) } httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": resp.GetTotal(), "page": page, "page_size": pageSize}) } func (h *Handler) getResourceGroup(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } groupID, ok := httpkit.PositiveInt64PathValue(request, "group_id") if !ok { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } resp, err := h.walletClient.GetResourceGroup(request.Context(), &walletv1.GetResourceGroupRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), GroupId: groupID, }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } group := resourceGroupFromProto(resp.GetGroup()) if group.Status != "active" { httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found") return } httpkit.WriteOK(writer, request, group) } func (h *Handler) listGifts(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } page, pageSize, ok := resourcePage(request) if !ok { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } regionID, _, ok := optionalNonNegativeInt64Query(request, "region_id") if !ok { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } resp, err := h.walletClient.ListGiftConfigs(request.Context(), &walletv1.ListGiftConfigsRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), Keyword: strings.TrimSpace(request.URL.Query().Get("keyword")), Page: page, PageSize: pageSize, ActiveOnly: true, RegionId: regionID, FilterRegion: true, }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } items := make([]giftConfigData, 0, len(resp.GetGifts())) for _, item := range resp.GetGifts() { items = append(items, giftFromProto(item)) } httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": resp.GetTotal(), "page": page, "page_size": pageSize}) } func (h *Handler) listMyResources(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } resp, err := h.walletClient.ListUserResources(request.Context(), &walletv1.ListUserResourcesRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()), ResourceType: strings.TrimSpace(request.URL.Query().Get("resource_type")), ActiveOnly: true, }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } items := make([]userResourceData, 0, len(resp.GetResources())) for _, item := range resp.GetResources() { items = append(items, userResourceFromProto(item)) } httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) } func (h *Handler) equipMyResource(writer http.ResponseWriter, request *http.Request) { if request.Method != http.MethodPost { httpkit.WriteError(writer, request, http.StatusMethodNotAllowed, httpkit.CodeInvalidArgument, "invalid argument") return } if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } resourceID, ok := httpkit.PositiveInt64PathValue(request, "resource_id") if !ok { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } var body struct { EntitlementID string `json:"entitlement_id"` } if request.Body != nil && request.Body != http.NoBody { if err := httpkit.DecodeJSON(request.Body, &body); err != nil && !errors.Is(err, io.EOF) { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body") return } } resp, err := h.walletClient.EquipUserResource(request.Context(), &walletv1.EquipUserResourceRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()), ResourceId: resourceID, EntitlementId: strings.TrimSpace(body.EntitlementID), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } httpkit.WriteOK(writer, request, userResourceFromProto(resp.GetResource())) } func resourceFromProto(item *walletv1.Resource) resourceData { if item == nil { return resourceData{} } return resourceData{ ResourceID: item.GetResourceId(), ResourceCode: item.GetResourceCode(), ResourceType: item.GetResourceType(), Name: item.GetName(), Status: item.GetStatus(), Grantable: item.GetGrantable(), GrantStrategy: item.GetGrantStrategy(), WalletAssetType: item.GetWalletAssetType(), WalletAssetAmount: item.GetWalletAssetAmount(), UsageScopes: item.GetUsageScopes(), AssetURL: item.GetAssetUrl(), PreviewURL: item.GetPreviewUrl(), AnimationURL: item.GetAnimationUrl(), MetadataJSON: item.GetMetadataJson(), SortOrder: item.GetSortOrder(), CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), } } func resourceGroupFromProto(group *walletv1.ResourceGroup) resourceGroupData { if group == nil { return resourceGroupData{} } items := make([]resourceGroupItemData, 0, len(group.GetItems())) for _, item := range group.GetItems() { items = append(items, resourceGroupItemData{ GroupItemID: item.GetGroupItemId(), ItemType: item.GetItemType(), ResourceID: item.GetResourceId(), Resource: resourcePointerFromProto(item.GetResource()), WalletAssetType: item.GetWalletAssetType(), WalletAssetAmount: item.GetWalletAssetAmount(), Quantity: item.GetQuantity(), DurationMS: item.GetDurationMs(), SortOrder: item.GetSortOrder(), }) } return resourceGroupData{ GroupID: group.GetGroupId(), GroupCode: group.GetGroupCode(), Name: group.GetName(), Status: group.GetStatus(), Description: group.GetDescription(), SortOrder: group.GetSortOrder(), Items: items, CreatedAtMS: group.GetCreatedAtMs(), UpdatedAtMS: group.GetUpdatedAtMs(), } } func resourcePointerFromProto(item *walletv1.Resource) *resourceData { if item == nil || item.GetResourceId() == 0 { return nil } data := resourceFromProto(item) return &data } func giftFromProto(gift *walletv1.GiftConfig) giftConfigData { if gift == nil { return giftConfigData{} } return giftConfigData{ GiftID: gift.GetGiftId(), ResourceID: gift.GetResourceId(), Resource: resourceFromProto(gift.GetResource()), Status: gift.GetStatus(), Name: gift.GetName(), SortOrder: gift.GetSortOrder(), PresentationJSON: gift.GetPresentationJson(), PriceVersion: gift.GetPriceVersion(), GiftTypeCode: gift.GetGiftTypeCode(), ChargeAssetType: gift.GetChargeAssetType(), CoinPrice: gift.GetCoinPrice(), GiftPointAmount: gift.GetGiftPointAmount(), HeatValue: gift.GetHeatValue(), EffectiveFromMS: gift.GetEffectiveFromMs(), EffectiveToMS: gift.GetEffectiveToMs(), EffectTypes: gift.GetEffectTypes(), CreatedAtMS: gift.GetCreatedAtMs(), UpdatedAtMS: gift.GetUpdatedAtMs(), RegionIDs: gift.GetRegionIds(), } } func userResourceFromProto(item *walletv1.UserResourceEntitlement) userResourceData { if item == nil { return userResourceData{} } return userResourceData{ EntitlementID: item.GetEntitlementId(), ResourceID: item.GetResourceId(), Resource: resourceFromProto(item.GetResource()), Status: item.GetStatus(), Quantity: item.GetQuantity(), RemainingQuantity: item.GetRemainingQuantity(), EffectiveAtMS: item.GetEffectiveAtMs(), ExpiresAtMS: item.GetExpiresAtMs(), SourceGrantID: item.GetSourceGrantId(), CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), } } func resourcePage(request *http.Request) (int32, int32, bool) { page, ok := parsePositiveInt32Query(request, "page", 1) if !ok { return 0, 0, false } pageSize, ok := parsePositiveInt32Query(request, "page_size", 20) if !ok { return 0, 0, false } if raw := strings.TrimSpace(request.URL.Query().Get("pageSize")); raw != "" { parsed, err := strconv.Atoi(raw) if err != nil || parsed <= 0 { return 0, 0, false } pageSize = int32(parsed) } return page, pageSize, true } func parsePositiveInt32Query(request *http.Request, name string, fallback int32) (int32, bool) { raw := strings.TrimSpace(request.URL.Query().Get(name)) if raw == "" { return fallback, true } value, err := strconv.Atoi(raw) if err != nil || value <= 0 { return 0, false } return int32(value), true } func optionalNonNegativeInt64Query(request *http.Request, name string) (int64, bool, bool) { raw := strings.TrimSpace(request.URL.Query().Get(name)) if raw == "" { return 0, false, true } value, err := strconv.ParseInt(raw, 10, 64) if err != nil || value < 0 { return 0, false, false } return value, true, true }