package transport import ( "strconv" "github.com/gin-gonic/gin" "github.com/geo-platform/tenant-api/internal/ops/app" "github.com/geo-platform/tenant-api/internal/shared/response" ) type setMediaSupplyPriceRequest struct { PriceType string `json:"price_type"` SellPriceCents int64 `json:"sell_price_cents"` Enabled *bool `json:"enabled,omitempty"` } type setMediaSupplyVisibilityRequest struct { CustomerVisible bool `json:"customer_visible"` } type adjustMediaSupplyWalletRequest struct { TenantID int64 `json:"tenant_id" binding:"required"` UserID int64 `json:"user_id" binding:"required"` DeltaCents int64 `json:"delta_cents" binding:"required"` Note string `json:"note"` } func listMediaSupplyResourcesHandler(svc *app.MediaSupplyService) gin.HandlerFunc { return func(c *gin.Context) { modelID, err := parseOptionalOpsIntQuery(c, "model_id") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_model_id", "媒体资源类型参数必须是数字")) return } minPriceCents, err := parseOptionalOpsInt64Query(c, "min_price_cents") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_min_price_cents", "最低价格参数必须是数字")) return } maxPriceCents, err := parseOptionalOpsInt64Query(c, "max_price_cents") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_max_price_cents", "最高价格参数必须是数字")) return } page, _ := parseOptionalOpsIntQuery(c, "page") size, _ := parseOptionalOpsIntQuery(c, "page_size") result, err := svc.ListResources(c.Request.Context(), app.ListMediaSupplyResourcesInput{ ModelID: modelID, Keyword: c.Query("keyword"), RemarkKeyword: c.Query("remark_keyword"), ChannelType: c.Query("channel_type"), Region: c.Query("region"), InclusionEffect: c.Query("inclusion_effect"), LinkType: c.Query("link_type"), MinPriceCents: minPriceCents, MaxPriceCents: maxPriceCents, Visibility: c.Query("visibility"), Page: page, Size: size, }) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func setMediaSupplyResourcePriceHandler(svc *app.MediaSupplyService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } var body setMediaSupplyPriceRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_payload", "价格参数不正确")) return } enabled := true if body.Enabled != nil { enabled = *body.Enabled } if err := svc.SetResourcePrice(c.Request.Context(), actorFromGin(c), id, app.SetMediaSupplyPriceInput{ PriceType: body.PriceType, SellPriceCents: body.SellPriceCents, Enabled: enabled, }); err != nil { response.Error(c, err) return } response.Success(c, gin.H{"updated": true}) } } func setMediaSupplyResourceVisibilityHandler(svc *app.MediaSupplyService) gin.HandlerFunc { return func(c *gin.Context) { id, err := parseIDParam(c) if err != nil { response.Error(c, err) return } var body setMediaSupplyVisibilityRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_payload", "展示状态参数不正确")) return } if err := svc.SetResourceVisibility(c.Request.Context(), actorFromGin(c), id, app.SetMediaSupplyVisibilityInput{ CustomerVisible: body.CustomerVisible, }); err != nil { response.Error(c, err) return } response.Success(c, gin.H{"updated": true}) } } func queueMediaSupplySyncHandler(svc *app.MediaSupplyService) gin.HandlerFunc { return func(c *gin.Context) { var body struct { ModelID int `json:"model_id"` } if c.Request.Body != nil { _ = c.ShouldBindJSON(&body) } jobID, err := svc.QueueSync(c.Request.Context(), actorFromGin(c), body.ModelID) if err != nil { response.Error(c, err) return } response.Success(c, gin.H{"job_id": jobID}) } } func listMediaSupplyWalletsHandler(svc *app.MediaSupplyService) gin.HandlerFunc { return func(c *gin.Context) { tenantID, err := parseOptionalOpsInt64Query(c, "tenant_id") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_tenant_id", "租户参数必须是数字")) return } page, _ := parseOptionalOpsIntQuery(c, "page") size, _ := parseOptionalOpsIntQuery(c, "page_size") result, err := svc.ListWallets(c.Request.Context(), app.ListMediaSupplyWalletsInput{ Keyword: c.Query("keyword"), TenantID: tenantID, Page: page, Size: size, }) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func adjustMediaSupplyWalletHandler(svc *app.MediaSupplyService) gin.HandlerFunc { return func(c *gin.Context) { var body adjustMediaSupplyWalletRequest if err := c.ShouldBindJSON(&body); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_payload", "余额调整参数不正确")) return } actor := actorFromGin(c) operatorTag := "ops manual adjustment" if actor != nil && actor.DisplayName != "" { operatorTag = "ops " + actor.DisplayName } result, err := svc.AdjustWallet(c.Request.Context(), actor, app.AdjustMediaSupplyWalletInput{ TenantID: body.TenantID, UserID: body.UserID, DeltaCents: body.DeltaCents, Note: body.Note, OperatorTag: operatorTag, }) if err != nil { response.Error(c, err) return } response.Success(c, result) } } func parseOptionalOpsIntQuery(c *gin.Context, key string) (int, error) { raw := c.Query(key) if raw == "" { return 0, nil } return strconv.Atoi(raw) } func parseOptionalOpsInt64Query(c *gin.Context, key string) (int64, error) { raw := c.Query(key) if raw == "" { return 0, nil } return strconv.ParseInt(raw, 10, 64) }