Дисклеймер: да, я использовал Claude, чтобы помочь мне исправить эту проблему:
Запуск ядра Discourse на коммите 5ee8e24 (2026-08-19). Синхронизация с Patreon завершается без ошибок, но синхронизирует ноль пользователей:
Patreon sync complete: 63 pledges, 0 users synced
Все ответы API имеют status=200, так что сами запросы в порядке. Отслеживая это в консоли Rails, я обнаружил, что запрос к участникам, который формирует плагин, корректен и действительно запрашивает email в ресурсе member:
fields[member]=full_name,last_charge_date,last_charge_status,currently_entitled_amount_cents,patron_status,email
При прямом обращении к этому эндпоинту с правильно настроенным токеном создателя (предоставлен campaigns.members[email]), email присутствует в объекте member, а не в объекте user внутри included. Проверка для одного участника это подтверждает:
data.attributes.email => "patron@example.com" # присутствует
included[].attributes.email => (отсутствует) # объекты user содержат только full_name
Таким образом, в списке участников кампании member.attributes.email заполнен (у 46 из моих 63 участников; остальные имеют ограниченный обмен или являются бесплатными подписчиками), тогда как записи пользователей в included вообще не имеют поля email.
Проблема находится в Patreon::ApiVersion::V2.extract (lib/api_version/v2.rb). Он строит карту users на основе объектов пользователей из included:
ruby
(member_data["included"] || []).each do |entry|
if entry["type"] == "user" && entry["attributes"]["email"].present?
users[entry["id"]] = entry["attributes"]["email"].downcase
end
end
Поскольку эти объекты пользователей никогда не содержат email, карта оказывается пустой, и каждый участник проваливает совпадение email, в результате чего синхронизируется 0 пользователей.
Чтение email из записи участника вместо этого (с использованием ключа по id пользователя, который уже доступен в member.relationships.user.data.id, и который является тем же patron_id, что используется для pledges) решило проблему на моей установке: 0 стало 46 users synced. Внесенное мной изменение:
diff
+ users[patron_id] = attrs["email"].downcase if attrs["email"].present?
pledges[patron_id] = attrs["currently_entitled_amount_cents"]
declines[patron_id] = attrs["last_charge_date"] if attrs["last_charge_status"] == "Declined"
end
-
- (member_data["included"] || []).each do |entry|
- if entry["type"] == "user" && entry["attributes"]["email"].present?
- users[entry["id"]] = entry["attributes"]["email"].downcase
- end
- end
Я не уверен, что полное удаление цикла included — правильное решение для всех случаев — если в некоторых конфигурациях email действительно приходит в объекте user, возможно, стоит оставить его как резервный вариант, а не заменять. Публикую наблюдаемое поведение и минимальное изменение, которое сработало здесь, на случай, если это полезно или указывает на правильное исправление.