discourse/plugins/discourse-patreon/lib/campaign.rb
Rafael dos Santos Silva b4c5526c51
FIX: Restore "All Patrons" pseudo-tier in admin UI (#39349)
## Summary

The Patreon plugin maintains a synthetic `rewards[\"0\"]` tier ("All
Patrons") that catches every patron, including paying patrons whose
pledge doesn't map to any predefined tier. It's supposed to appear as
`\$0 - All Patrons` in the admin dropdown where group filters are
configured.

In affected campaigns, this option silently disappeared and was replaced
by `\$0 - Free` (Patreon's real default free tier, with its own numeric
id). Admins lost the ability to filter on "any patron".

### Root cause

`rewards[\"0\"] ||= {}` in `lib/campaign.rb` relied on Patreon's payload
having already populated `rewards[\"0\"][\"id\"] = \"0\"` via the
adapter's `parse_campaigns`. For campaigns where Patreon no longer
returns a reward keyed on `\"0\"` (e.g. when the campaign has a real
"Free" tier), the pseudo-tier ended up stored with only `title` and
`amount_cents` — no `id`.

The admin dropdown filters rewards client-side with `r.id >= 0` (in
`assets/javascripts/discourse/controllers/admin-plugins/patreon.js`).
Since `undefined >= 0` evaluates to `false`, "All Patrons" was dropped
from the options. Existing filter rules keyed on `\"0\"` continued to
work on the backend, but admins could no longer create or edit them.

### Fix

Assign `rewards[\"0\"][\"id\"] = \"0\"` explicitly so the pseudo-tier is
always selectable. Confirmed against a live v1 site:
`reward-users[\"0\"]` still tracked all 946 patrons, the backend state
was healthy, only the UI was broken. Applies equally to v1 and v2 since
the assignment runs adapter-agnostic.

Existing filter rules keyed on `\"0\"` keep working and start matching
again on the next sync — no data migration needed.

## Test plan

- [x] `bin/rspec plugins/discourse-patreon/spec/lib/campaign_spec.rb`
passes under both v1 and v2 shared examples, asserting `rewards[\"0\"]`
includes `id: \"0\"`
- [x] `bin/lint` clean
- [ ] On the affected site, run \`Patreon::Campaign.update!\` (or click
*Update Data*) and verify \`\$0 - All Patrons\` reappears in the admin
dropdown and can be assigned to a group
2026-04-17 14:15:37 -03:00

56 lines
1.4 KiB
Ruby

# frozen_string_literal: true
require "json"
module Patreon
class Campaign
def self.update!
verbose = SiteSetting.patreon_verbose_log
adapter = ApiVersion.current
if verbose
Rails.logger.warn("Patreon sync started using API v#{SiteSetting.patreon_api_version}")
end
response = Patreon::Api.campaign_data
if response.blank? || response["data"].blank?
Rails.logger.warn("Patreon sync: no campaign data returned") if verbose
return false
end
campaign_data = adapter.parse_campaigns(response)
rewards = campaign_data[:rewards]
if verbose
Rails.logger.warn(
"Patreon sync: found #{rewards.size} rewards/tiers across #{response["data"].size} campaigns",
)
end
# Special catch all patrons virtual reward
rewards["0"] ||= {}
rewards["0"]["id"] = "0"
rewards["0"]["title"] = "All Patrons"
rewards["0"]["amount_cents"] = 0
Patreon.set("rewards", rewards)
adapter.pull_pledges!(campaign_data)
pledges = Patreon.get("pledges") || {}
users = Patreon.get("users") || {}
if verbose
Rails.logger.warn(
"Patreon sync complete: #{pledges.size} pledges, #{users.size} users synced",
)
end
# Sets all patrons to the seed group by default on first run
filters = Patreon.get("filters")
Patreon::Seed.seed_content! if filters.blank?
true
end
end
end