package app import ( "context" "encoding/json" "strconv" "strings" "github.com/jackc/pgx/v5" ) func normalizePlatformIDs(platformIDs []string) []string { if len(platformIDs) == 0 { return nil } seen := make(map[string]struct{}, len(platformIDs)) normalized := make([]string, 0, len(platformIDs)) for _, platformID := range platformIDs { next := strings.TrimSpace(platformID) if next == "" { continue } if _, exists := seen[next]; exists { continue } seen[next] = struct{}{} normalized = append(normalized, next) } if len(normalized) == 0 { return nil } return normalized } type scheduleAutoPublishPlatformQuerier interface { Query(context.Context, string, ...interface{}) (pgx.Rows, error) } func loadScheduleAutoPublishPlatforms( ctx context.Context, db scheduleAutoPublishPlatformQuerier, tenantID int64, inputParamsJSON []byte, ) ([]string, error) { if db == nil || tenantID <= 0 || len(inputParamsJSON) == 0 { return []string{}, nil } var payload map[string]interface{} if err := json.Unmarshal(inputParamsJSON, &payload); err != nil { return []string{}, nil } if !extractBool(payload, "schedule_auto_publish") { return []string{}, nil } accountIDs := normalizeSchedulePublishAccountIDs(extractStringList(payload["schedule_publish_account_ids"], 64)) if len(accountIDs) == 0 { return []string{}, nil } return loadPublishAccountPlatformsByIDs(ctx, db, tenantID, accountIDs) } func loadPublishAccountPlatformsByIDs( ctx context.Context, db scheduleAutoPublishPlatformQuerier, tenantID int64, accountIDs []string, ) ([]string, error) { accountIDs = normalizeSchedulePublishAccountIDs(accountIDs) if db == nil || tenantID <= 0 || len(accountIDs) == 0 { return []string{}, nil } rows, err := db.Query(ctx, ` SELECT DISTINCT platform_id FROM platform_accounts WHERE tenant_id = $1 AND desktop_id::text = ANY($2::text[]) AND deleted_at IS NULL ORDER BY platform_id `, tenantID, accountIDs) if err != nil { return nil, err } defer rows.Close() platformIDs := make([]string, 0, len(accountIDs)) for rows.Next() { var platformID string if err := rows.Scan(&platformID); err != nil { return nil, err } platformIDs = append(platformIDs, platformID) } if err := rows.Err(); err != nil { return nil, err } return emptyStringSliceIfNil(normalizePlatformIDs(platformIDs)), nil } func emptyStringSliceIfNil(values []string) []string { if values == nil { return []string{} } return values } func resolveArticleCoverAssetURL(wizardStateJSON []byte) *string { value := strings.TrimSpace(extractStringFromJSONPayload(wizardStateJSON, "cover_asset_url")) if value == "" { return nil } return &value } func resolveArticleCoverImageAssetID(wizardStateJSON []byte) *int64 { value, ok := extractInt64FromJSONPayload(wizardStateJSON, "cover_image_asset_id") if !ok || value <= 0 { return nil } return &value } func extractStringFromJSONPayload(raw []byte, key string) string { if len(raw) == 0 { return "" } var payload map[string]interface{} if err := json.Unmarshal(raw, &payload); err != nil { return "" } value, ok := payload[key].(string) if !ok { return "" } return strings.TrimSpace(value) } func extractInt64FromJSONPayload(raw []byte, key string) (int64, bool) { if len(raw) == 0 { return 0, false } var payload map[string]interface{} if err := json.Unmarshal(raw, &payload); err != nil { return 0, false } switch value := payload[key].(type) { case nil: return 0, false case float64: return int64(value), true case int64: return value, true case int: return int64(value), true case string: value = strings.TrimSpace(value) if value == "" { return 0, false } parsed, err := strconv.ParseInt(value, 10, 64) if err != nil { return 0, false } return parsed, true default: return 0, false } } func mergeArticleWizardState(raw []byte, title string, coverAssetURL *string, coverImageAssetID NullableInt64Input) ([]byte, error) { state := make(map[string]interface{}) if len(raw) > 0 { _ = json.Unmarshal(raw, &state) } delete(state, "platforms") delete(state, "target_platforms") delete(state, "target_platform") trimmedTitle := strings.TrimSpace(title) if trimmedTitle != "" { state["title"] = trimmedTitle } if coverAssetURL != nil { trimmedCoverURL := strings.TrimSpace(*coverAssetURL) if trimmedCoverURL == "" { delete(state, "cover_asset_url") } else { state["cover_asset_url"] = trimmedCoverURL } } if coverImageAssetID.Set { if coverImageAssetID.Value == nil { delete(state, "cover_image_asset_id") } else { state["cover_image_asset_id"] = *coverImageAssetID.Value } } return json.Marshal(state) }