커스텀 통합 플러그인 설계 관련 도움 요청

안녕하세요, 여러분! 저는 브라질의 경쟁 프로그래밍(CP) 커뮤니티를 구축하기 위해 다양한 포럼 플랫폼을 조사하고 있었는데, 마침 이 플랫폼을 찾은 것 같습니다!

이 포럼에 도입하고 싶은 기능 중 하나는 Codeforces와의 통합입니다. Codeforces는 CP 대회와 토론을 호스팅하는 온라인 플랫폼입니다. Codeforces 내에서 참가자들은 평가 대상 대회에서의 성과에 따라 변하는 _레이팅_을 가집니다. 저는 포럼 프로필에서 사용자가 어렵게 얻은 레이팅을 표시할 수 있도록 하고 싶습니다.

제가 개발하려는 이 플러그인이 해야 할 첫 번째 일은 사용자가 자신의 Codeforces 핸들을 입력하도록 하고, 그 핸들이 해당 포럼 사용자의 것인지某种 방식으로 인증하는 것입니다. 이를 위해 Custom Wizard 플러그인을 사용하여 핸들을 입력받게 하고, Codeforces Talks API를 통해 무작위 문자열을 전송한 뒤 사용자가 이를 올바르게 입력하도록 한 다음, 모든 것이 정상이라면 해당 핸들을 사용자 커스텀 필드에 저장하는 방식을 고려하고 있습니다. 이 방식이 괜찮아 보이시나요? 다른 방식으로 구현할 것을 추천하시나요?

저는 제 [미래의] 사용자들을 잘 알고 있습니다.毕竟 저 역시 CP어이니까요! 그들은 포럼 핸들 색상으로 자신의 레이팅을 표시하는 것을 좋아할 것입니다. 즉, 레이팅이 1400을 초과하면 핸들이 청록색(cyan)이 되고, 1600을 초과하면 파란색이 되며, 그 이상은 같은 방식으로 적용됩니다.

Discourse에서 핸들 색상을 커스터마이즈하는 방법에 대한 간단한 조사 결과, 다음과 같은 해결책을 찾았습니다. 각 레이팅 구간별로 사용자 그룹을 생성하고, 사용자를 해당 그룹에 동적으로 할당해야 합니다. (더 나은/더 쉬운 방법이 있을까요?)

따라서 사용자가 처음에 Codeforces 계정을 프로필에 연결할 때, 레이팅을 조회하여 올바른 그룹(specialist, expert, candidate master, …, international grandmaster)에 할당할 수 있습니다. 그러나 레이팅은 곧 변경될 수 있으며, 레이팅이 변경될 때 사용자의 그룹을 자동으로 업데이트하고 싶습니다.

이를 달성하기 위한 몇 가지 방법을 생각해 보았는데, 어떤 방식을 따르는 것이 좋을지 제안을 받고 싶습니다. 가능하면 Discourse 관련 구체적인 가이드도 부탁드립니다:

  • 각 사용자의 개별 레이팅과 그룹을 업데이트하는 정기적으로 실행되는 Job을 가지는 것 (Codeforces API에 많은 외부 요청이 발생함)
  • 대회가 진행되는 대로 처리하는 정기적으로 실행되는 Job을 가지는 것. 레이팅은 대회 중에만 변경될 수 있으므로, 사용자의 레이팅이 초기에 일관성이 있다면 레이팅 변경 사항을 실시간으로 처리함으로써 변경된 레이팅만 업데이트하고 단일 외부 API 호출로 일관성을 유지할 수 있습니다.
  • 커스텀 레이팅 및 그룹 리졸버를 가지는 것. 즉, 마지막 레이팅 조회 시점을 저장해 두고, 다음 GET rating/groups 호출 시 레이팅이 만료(stale)되었다면 Codeforces API를 호출하여 사용자의 레이팅/그룹을 업데이트합니다. 이 방식이 제 선호하는 솔루션입니다. 구현이 쉽고 최종 일관성(eventual consistency)을 항상 보장할 것 같아서입니다. 하지만 이 방식을 구현하는 방법이나, 사용자의 그룹을 동적으로 제거하고 추가하는 것이 가져올 결과에 대해서는 확신이 서지 않습니다. 아니면 이것이 플러그인으로 전혀 불가능한 것인지도 궁금합니다.

글이 길어졌네요, 죄송합니다! 이 모든 것에 대한 어떤 형태의 의견도 환영합니다. 관심을 가져주셔서 감사합니다.

사용자의 Codeforces 핸들을 user_custom_field에 저장하면, 로그인할 때 해당 프로필에서 원하는 데이터를 업데이트하는 것이 충분히 쉬워집니다 (그룹 할당, Codeforces 프로필 데이터를 사용자 커스텀 필드에 저장, 등).

다음과 같은 방식으로 구현할 수 있습니다:

after_initialize do
  DiscourseEvent.on(:user_logged_in) do |user|
    # get stuff from codeforce
      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

end

이 방식은 매우 간단하며, 실제로 커뮤니티에서 활동하는 사용자들의 데이터만 업데이트됩니다.

정말 감사합니다! 아무것도 구현하기 전에 먼저 물어봐서 다행이에요.

정말 감사합니다!

모든 사용자가 Codeforces 사용자라면, 단일 로그인(SSO, Codeforces를 인증 제공자로 사용)을 구현하는 것이 좋습니다. 이렇게 하면 추가적인 인증 단계가 필요하지 않고, 사용자의 가입/인증 플로우도 더 좋아집니다. 이렇게 하면 그룹까지 동기화할 수 있습니다!

출처, 즉 Codeforces에서 레이팅이 변경된 것을 아는 곳에서 처리하는 것이 좋습니다. Codeforces가 레이팅이 변경되면 Discourse로 API 호출을 하여 레이팅(또는 해당 레이팅에 영향을 받는 사용자 속성)을 업데이트하도록 하면 됩니다. 이렇게 하면 모든 정보가 항상 최신 상태를 유지합니다. 동기화가 필요할 때 폴링(polling)은 어떤 식으로든 문제를 일으키므로, 가능한 한 피하고 이벤트 기반 접근 방식을 사용하세요.

Codeforce를 SSO 마스터로 사용하는 것이 불가능하다고 가정했습니다. 가능하다면, 이 방식으로 진행하는 것이 확실히 좋습니다!

@pfaffman 님의 “Codeforces를 인증 제공자로 사용할 수 없다”는 가정은 정확했습니다. Codeforces는 사실상 한 사람이 유지 관리하고 있으며, 아직 계획/자금 조달 단계인 프로젝트에 대해 해당 기능을 구현할 것이라고는 생각되지 않습니다.

폴링이 비효율적이라는 데 동의합니다! 그래서 사용자가 로그인할 때 쿼리를 실행하는 제안된 해결책이 일관성과 Codeforces 서버에 과도한 부하를 걸어 주의나 차단 조치를 받지 않는 것 사이의 좋은 균형이라고 생각합니다.

브라질 커뮤니티의 특정 사례가 성공하고 다른 커뮤니티들도 같은 시도를 한다면, Codeforces를 책임지고 있는 유일한 사람인 Mike 님이 이와 유사한 기능을 구현할지도 모릅니다.

그럼에도 불구하고 조언에 진심으로 감사드립니다! 내년 6월에 Mike 님을 다시 만나면 도움을 요청하고, 그 시점에 우리 포럼이 사용할 수 있는 최적의 솔루션을 고민해 볼 수 있도록 이 조언들을 잘 기억해 두겠습니다.