Синхронизация Patreon v2 сообщает «N pledges, 0 users synced» — email участника считывается из неверного ресурса при извлечении

Дисклеймер: да, я использовал 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, возможно, стоит оставить его как резервный вариант, а не заменять. Публикую наблюдаемое поведение и минимальное изменение, которое сработало здесь, на случай, если это полезно или указывает на правильное исправление.