package resource import ( "context" "encoding/json" "fmt" "strings" activityv1 "hyapp.local/api/proto/activity/v1" walletv1 "hyapp.local/api/proto/wallet/v1" ) const ( resourceGrantNoticeTitle = "System Gift" resourceGrantNoticeBody = "You received the following system-granted resources." ) type resourceNoticeSnapshot struct { ResourceType string Name string AssetURL string PreviewURL string MetadataJSON string } func (h *Handler) createResourceGrantNotice(ctx context.Context, requestID string, appCode string, grant *walletv1.ResourceGrant) error { if grant == nil { return fmt.Errorf("resource grant response is empty") } return h.createResourceBatchGrantNotice(ctx, requestID, appCode, grant.GetGrantId(), grant.GetTargetUserId(), []*walletv1.ResourceGrant{grant}) } func (h *Handler) createResourceBatchGrantNotice(ctx context.Context, requestID string, appCode string, commandID string, targetUserID int64, grants []*walletv1.ResourceGrant) error { if h.activity == nil { // Tests and isolated local handlers may omit integrations; the production constructor always wires activity-service. return nil } snapshots, err := resourceNoticeSnapshots(grants) if err != nil { return err } body := resourceGrantNoticeBody imageURLs := resourceNoticeImageURLs(snapshots) if len(grants) == 1 && len(snapshots) == 1 && strings.EqualFold(strings.TrimSpace(snapshots[0].ResourceType), resourceTypeVIPTrialCard) { level := vipTrialLevel(snapshots[0].MetadataJSON) body = "You received a VIP trial card." if level > 0 { body = fmt.Sprintf("You received a VIP%d trial card.", level) } imageURLs = nil } producerEventID := "resource_grant:" + strings.TrimSpace(commandID) _, err = h.activity.CreateInboxMessage(ctx, &activityv1.CreateInboxMessageRequest{ Meta: &activityv1.RequestMeta{RequestId: strings.TrimSpace(requestID), Caller: "admin-server", AppCode: strings.TrimSpace(appCode)}, TargetUserId: targetUserID, Producer: "admin-server", ProducerEventId: producerEventID, ProducerEventType: "AdminResourceGranted", MessageType: "system", AggregateType: "resource_grant", AggregateId: strings.TrimSpace(commandID), Title: resourceGrantNoticeTitle, Summary: body, Body: body, ImageUrl: firstNoticeImageURL(imageURLs), ImageUrls: imageURLs, }) return err } func resourceNoticeSnapshots(grants []*walletv1.ResourceGrant) ([]resourceNoticeSnapshot, error) { result := make([]resourceNoticeSnapshot, 0) for _, grant := range grants { if grant == nil { continue } for _, item := range grant.GetItems() { raw := strings.TrimSpace(item.GetResourceSnapshotJson()) if raw == "" || raw == "{}" { continue } var snapshot resourceNoticeSnapshot if err := json.Unmarshal([]byte(raw), &snapshot); err != nil { return nil, fmt.Errorf("decode granted resource snapshot: %w", err) } result = append(result, snapshot) } } return result, nil } func resourceNoticeImageURLs(snapshots []resourceNoticeSnapshot) []string { result := make([]string, 0, len(snapshots)) for _, snapshot := range snapshots { url := strings.TrimSpace(snapshot.PreviewURL) if url == "" { url = strings.TrimSpace(snapshot.AssetURL) } if url == "" { continue } result = append(result, url) } return result } func vipTrialLevel(metadataJSON string) int32 { var metadata struct { VIPLevel int32 `json:"vip_level"` } if err := json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &metadata); err != nil { return 0 } return metadata.VIPLevel } func firstNoticeImageURL(imageURLs []string) string { if len(imageURLs) == 0 { return "" } return imageURLs[0] }