免責事項: はい、この問題の修正にClaudeの助けを借りました。
Discourse core はコミット 5ee8e24 (2026-08-19) で実行しています。Patreon の同期はエラーなく完了しますが、同期されるユーザーはゼロです:
Patreon sync complete: 63 pledges, 0 users synced
API のレスポンスはすべて status=200 なので、リクエスト自体には問題ありません。Rails コンソールでトレースすると、プラグインが構築しているメンバーリクエストは正しく、member リソースに対して email の取得を要求しています:
fields[member]=full_name,last_charge_date,last_charge_status,currently_entitled_amount_cents,patron_status,email
適切にスコープされたクリエイタートークン (campaigns.members[email] が付与されている) を直接使用してそのエンドポイントを呼び出すと、email は included 内の user オブジェクトではなく、member オブジェクト上に存在します。単一のメンバー取得でも確認できます:
data.attributes.email => "patron@example.com" # 存在
included[].attributes.email => (なし) # user オブジェクトは full_name のみ
つまり、キャンペーンメンバーリストでは、member.attributes.email が設定されています(私の63人のメンバーのうち46人。残りは共有が制限されているか、無料フォロワーです)。一方、included の user エントリには email フィールドが全くありません。
問題は Patreon::ApiVersion::V2.extract (lib/api_version/v2.rb) にあります。これは included の user オブジェクトから users マップを構築しています:
ruby
(member_data["included"] || []).each do |entry|
if entry["type"] == "user" && entry["attributes"]["email"].present?
users[entry["id"]] = entry["attributes"]["email"].downcase
end
end
これらの user オブジェクトには email が含まれないため、マップは空になり、すべてのメンバーが email マッチングに失敗し、結果として同期数が 0 になっています。
代わりに member エントリから email を読み取ること(pledges で使用されているのと同じ patron_id である member.relationships.user.data.id をキーとして利用可能)で、私の環境では修正されました: 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 ループを完全に削除することが、すべてのケースにおいて正しい判断であるとは確信できません。一部の環境では user オブジェクトに email が返される可能性があるため、置き換えではなくフォールバックとして残すことを検討してもよいでしょう。参考になるか、あるいは適切な修正のヒントになるかと思い、観察された挙動と、ここで機能した最小限の変更を投稿します。