カスタム統合プラグインの設計に関するヘルプ

こんにちは!ブラジルの競技プログラミング(CP)コミュニティを構築するために、さまざまなフォーラムプラットフォームを調査していたのですが、ついにこれを見つけました!

このフォーラムに実装したい機能の一つが、Codeforces との連携です。Codeforces は競技プログラミングに関する大会やディスカッションをホストするオンラインプラットフォームです。Codeforces 内では、参加者はレーティングされた大会でのパフォーマンスに応じて変化する「レーティング」を持っています。私は、ユーザーがフォーラムのプロフィールに、苦労して獲得したレーティングを表示できるようにしたいと考えています。

私が開発したいプラグインが最初にしなければならないことは、ユーザーに Codeforces のハンドル名を入力させ、そのハンドル名がフォーラムのユーザーに属することを何らかの方法で認証することです。そのために、Custom Wizard Plugin を使ってハンドル名を聞き出し、Codeforces Talks API を介してランダムな文字列を送信し、ユーザーがそれを正しく入力してもらおうと考えています。すべて問題なければ、そのハンドル名をカスタムユーザーフィールドに保存します。このアプローチで問題ないでしょうか?もっと良い方法があるでしょうか?

さて、私は [未来の] ユーザーを知っています。というのも、私も CP 界の人間だからです!彼らは、フォーラムのハンドル名の色を通じてレーティングを表示することを喜ぶでしょう。つまり、レーティングが 1400 以上ならシアン色、1600 以上なら青色、といった具合です。

Discourse 上でハンドル名の色をカスタマイズする方法について簡易的に調査したところ、以下の解決策に行き着きました。レーティングの範囲ごとにユーザーグループを作成し、ユーザーを動的に適切なグループに割り当てるのです。もっと良い、あるいは簡単な方法はありませんか?

したがって、ユーザーが最初に Codeforces アカウントをプロフィールにリンクする際、レーティングを取得し、適切なグループ(specialistexpertcandidate master、…、international grandmaster)を割り当てることができます。ただし、レーティングはすぐに変わる可能性があり、レーティングが変化した際にユーザーのグループを自動的に更新したいと考えています。

その実現方法についていくつか考えましたので、どのアプローチを採用すべきかご提案いただければ幸いです。可能であれば、Discourse 固有の事項についてもご指導ください。

  • 各ユーザーの個別のレーティングとグループを更新する定期的なジョブを実行する(Codeforces API への外部リクエストが多数発生します)
  • 大会が発生するたびに処理する定期的なジョブを実行する。レーティングは大会中にのみ変更されるため、ユーザーのレーティングが初期状態で一貫していれば、変更をリアルタイムに処理することで、変更されたレーティングのみを更新し、単一の外部 API 呼び出しで一貫性を維持できます
  • カスタムのレーティングとグループ解決機能を実装する。つまり、最後のレーティング取得時刻を保存し、次の GET rating/groups 呼び出しでレーティングが古くなっていれば、Codeforces API を叩いてユーザーのレーティングとグループを更新します。これは私の希望する解決策です。実装が簡単で、最終的には常に一貫性が保たれるように思えるからです。ただし、動的にユーザーからグループを削除したり追加したりする方法や、その影響について確信が持てません。あるいは、これがプラグインとして可能なのかどうかも不明です。

長文になってしまい申し訳ありません!これらすべてについて、どのようなフィードバックでもいただければ嬉しいです。ご配慮ありがとうございます。

user_custom_field に Codeforces のハンドルが格納されていれば、ログイン時にプロフィールから必要な情報を更新するのは比較的簡単です(グループの割り当て、Codeforces のプロフィールデータを user custom fields に格納するなど)。

以下のような実装が可能です:

after_initialize do
  DiscourseEvent.on(:user_logged_in) do |user|
    # Codeforces からデータを取得
    if codeforce_rating > 1400
      group = Group.find_by(name: group_1400)
      if group
        gu = GroupUser.find_by(group_id: group.id, user_id: user.id)
        GroupUser.create(group_id: group.id, user_id: user.id) unless gu
      end
    end
  end
end

この方法であれば非常に簡単で、実際にコミュニティでアクティブなユーザーに対してのみデータを更新できます。

それは素敵ですね!何かを実装する前に周囲に聞いてみてよかったです。

本当にありがとうございます!

すべてのユーザーが Codeforces ユーザーである場合、シングルサインオン(Codeforces を認証プロバイダーとして使用)を実装することをお勧めします。これにより、追加の認証ステップが不要になり、ユーザーのサインアップ/認証フローも向上します。さらに、この方法でグループを同期することも可能です!

これはソース側から行うべきです。つまり、Codeforces でレーティングの変更が検知された時点で、Discourse に対して API リクエストを送信し、レーティング(またはそのレーティングに影響される他のユーザープロパティ)を更新させます。これにより、常に最新の状態を維持できます。同期が必要な場合、ポーリングは必ず何らかの問題を引き起こすため、可能な限り回避し、イベントベースのアプローチを採用してください。

Codeforce を SSO マスターとして使用することは不可能だと想定していました。もし可能であれば、間違いなくこの方法で実施すべきです!

@pfaffman さんの、Codeforces を認証プロバイダーとして使用できないという仮定は正しかったです。Codeforces は基本的に一人の人物によって維持されており、まだ計画・資金調達段階の何かのために何かを実装するとは考えにくいです。

ポーリングが最適ではないことに同意します!そのため、ユーザーがログインしたときにクエリを実行するという提案された解決策は、[ユーザーの一貫性への関心] と、Codeforces のサーバーに過度にアクセスして注意を引いたり、ブロックされたりするリスクのバランスが良いように思われます。

もしブラジルコミュニティのこの特定のケースが成功し、他のコミュニティも同様の試みをするようになれば、Mike(Codeforces を一人で担当している人物)がこのような機能を実装してくれるかもしれません。

ともあれ、ご提案に心から感謝します!来年 6 月に Mike と会う際に、彼の協力を説得し、その時点で当フォーラムが採用している可能性のある解決策を最適化できるよう、これらの提案を心に留めておきます。