package transport import ( "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/geo-platform/tenant-api/internal/bootstrap" "github.com/geo-platform/tenant-api/internal/shared/response" "github.com/geo-platform/tenant-api/internal/tenant/app" ) type MediaSupplyHandler struct { svc *app.MediaSupplyService } func NewMediaSupplyHandler(a *bootstrap.App) *MediaSupplyHandler { svc := a.MediaSupplyService if svc == nil { svc = app.NewMediaSupplyService(a.DB, a.Redis, a.Logger, a.ConfigStore).WithObjectStorage(a.ObjectStorage) } return &MediaSupplyHandler{ svc: svc, } } func (h *MediaSupplyHandler) ListResources(c *gin.Context) { modelID, err := parseOptionalIntQuery(c, "model_id") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_model_id", "媒体资源类型参数必须是数字")) return } page, _ := parseOptionalIntQuery(c, "page") pageSize, _ := parseOptionalIntQuery(c, "page_size") minPriceCents, err := parseOptionalInt64Query(c, "min_price_cents") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_min_price_cents", "最低价格参数必须是数字")) return } maxPriceCents, err := parseOptionalInt64Query(c, "max_price_cents") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_max_price_cents", "最高价格参数必须是数字")) return } data, err := h.svc.ListCustomerResources(c.Request.Context(), app.ListSupplierMediaResourcesRequest{ ModelID: modelID, Keyword: c.Query("keyword"), RemarkKeyword: c.Query("remark_keyword"), ChannelType: c.Query("channel_type"), Portal: c.Query("portal"), Region: c.Query("region"), InclusionEffect: c.Query("inclusion_effect"), SpecialIndustry: c.Query("special_industry"), LinkType: c.Query("link_type"), DeliverySpeed: c.Query("delivery_speed"), AIInclude: c.Query("ai_include"), OtherOption: c.Query("other_option"), MinPriceCents: minPriceCents, MaxPriceCents: maxPriceCents, Sort: c.Query("sort"), Page: page, PageSize: pageSize, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) ListResourcesByIDs(c *gin.Context) { ids, err := parseInt64ListQuery(c, "ids") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_resource_ids", "媒体资源参数必须是数字")) return } modelID, err := parseOptionalIntQuery(c, "model_id") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_model_id", "媒体资源类型参数必须是数字")) return } data, err := h.svc.ListCustomerResourcesByIDs(c.Request.Context(), ids, modelID) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) QueueSync(c *gin.Context) { response.Error(c, response.ErrNotFound(40400, "not_found", "接口不存在")) } func (h *MediaSupplyHandler) SearchOptions(c *gin.Context) { modelID, err := parseOptionalIntQuery(c, "model_id") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_model_id", "媒体资源类型参数必须是数字")) return } data, err := h.svc.SearchOptions(c.Request.Context(), modelID) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) SetResourcePrice(c *gin.Context) { response.Error(c, response.ErrNotFound(40400, "not_found", "接口不存在")) } func (h *MediaSupplyHandler) CreateOrder(c *gin.Context) { var req app.CreateMediaSupplyOrderRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_params", "投稿参数不正确")) return } data, err := h.svc.CreateOrder(c.Request.Context(), req) if err != nil { response.Error(c, err) return } response.SuccessWithStatus(c, 201, data) } func (h *MediaSupplyHandler) WalletStatus(c *gin.Context) { data, err := h.svc.WalletStatus(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) ListWalletLedgers(c *gin.Context) { page, _ := parseOptionalIntQuery(c, "page") pageSize, _ := parseOptionalIntQuery(c, "page_size") userID, err := parseOptionalInt64Query(c, "user_id") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_user_id", "用户参数必须是数字")) return } createdFrom, err := parseOptionalTimeQuery(c, "created_from") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_created_from", "开始时间格式不正确")) return } createdTo, err := parseOptionalTimeQuery(c, "created_to") if err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_created_to", "结束时间格式不正确")) return } data, err := h.svc.ListWalletLedgers(c.Request.Context(), app.ListMediaSupplyWalletLedgersRequest{ Page: page, PageSize: pageSize, UserID: userID, CreatedFrom: createdFrom, CreatedTo: createdTo, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) AdjustWallet(c *gin.Context) { response.Error(c, response.ErrNotFound(40400, "not_found", "接口不存在")) } func (h *MediaSupplyHandler) ListOrders(c *gin.Context) { page, _ := parseOptionalIntQuery(c, "page") pageSize, _ := parseOptionalIntQuery(c, "page_size") var articleID *int64 if raw := c.Query("article_id"); raw != "" { parsed, err := strconv.ParseInt(raw, 10, 64) if err != nil || parsed <= 0 { response.Error(c, response.ErrBadRequest(40001, "invalid_article_id", "文章参数必须是数字")) return } articleID = &parsed } data, err := h.svc.ListOrders(c.Request.Context(), app.ListMediaSupplyOrdersRequest{ Status: c.Query("status"), ArticleID: articleID, Page: page, PageSize: pageSize, }) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) GetOrder(c *gin.Context) { orderID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil || orderID <= 0 { response.Error(c, response.ErrBadRequest(40001, "invalid_id", "订单参数必须是数字")) return } data, err := h.svc.GetOrder(c.Request.Context(), orderID) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) ImportSession(c *gin.Context) { var req app.ImportMeijiequanSessionRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, response.ErrBadRequest(40001, "invalid_params", "登录状态参数不正确")) return } data, err := h.svc.ImportSession(c.Request.Context(), req) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) LoginConfiguredAccount(c *gin.Context) { data, err := h.svc.LoginConfiguredAccount(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, data) } func (h *MediaSupplyHandler) SessionStatus(c *gin.Context) { data, err := h.svc.SessionStatus(c.Request.Context()) if err != nil { response.Error(c, err) return } response.Success(c, data) } func parseOptionalIntQuery(c *gin.Context, key string) (int, error) { raw := c.Query(key) if raw == "" { return 0, nil } value, err := strconv.Atoi(raw) if err != nil { return 0, err } return value, nil } func parseOptionalInt64Query(c *gin.Context, key string) (int64, error) { raw := c.Query(key) if raw == "" { return 0, nil } value, err := strconv.ParseInt(raw, 10, 64) if err != nil { return 0, err } return value, nil } func parseOptionalTimeQuery(c *gin.Context, key string) (*time.Time, error) { raw := c.Query(key) if raw == "" { return nil, nil } value, err := time.Parse(time.RFC3339, raw) if err != nil { return nil, err } return &value, nil } func parseInt64ListQuery(c *gin.Context, key string) ([]int64, error) { rawValues := c.QueryArray(key) if len(rawValues) == 0 { rawValues = []string{c.Query(key)} } values := make([]int64, 0, len(rawValues)) for _, raw := range rawValues { for _, part := range strings.Split(raw, ",") { part = strings.TrimSpace(part) if part == "" { continue } value, err := strconv.ParseInt(part, 10, 64) if err != nil || value <= 0 { return nil, strconv.ErrSyntax } values = append(values, value) if len(values) >= 100 { return values, nil } } } return values, nil }