Ajuda com o design de plugin de integração personalizado

Olá a todos! Estou pesquisando diferentes plataformas de fóruns para construir uma comunidade para programadores competitivos brasileiros (CP), mas parece que já encontrei a ideal!

Uma funcionalidade que gostaria de ter neste fórum é uma integração com o Codeforces — uma plataforma online que hospeda competições e discussões sobre CP. No Codeforces, os participantes têm uma classificação (rating) que muda de acordo com o desempenho do participante em competições classificatórias. Gostaria de permitir que meus usuários exibam suas classificações conquistadas com esforço em seus perfis do fórum.

A primeira coisa que este plugin que quero desenvolver precisaria fazer, então, seria permitir que o usuário insira seu identificador (handle) do Codeforces e, de alguma forma, autenticar que esse identificador pertence ao usuário do fórum. Para isso, estou pensando em usar o Plugin Custom Wizard para solicitar o identificador, depois enviar uma string aleatória via Codeforces Talks API, que o usuário teria que inserir corretamente de volta; se tudo estiver correto, armazenar o identificador em um campo personalizado do usuário. Isso parece adequado? Você faria isso de maneira diferente?

Agora, conheço meus usuários [futuros], afinal sou um CPer também! Eles adorariam exibir sua classificação através da cor do identificador no fórum. Ou seja, se você tem uma classificação superior a 1400, seu identificador será ciano; se for superior a 1600, será azul, e assim por diante.

Minha rápida pesquisa sobre como personalizar as cores dos identificadores no Discourse me levou à seguinte solução: criar um grupo de usuários para cada faixa de classificação e, em seguida, atribuir dinamicamente meus usuários ao grupo correto. (Existe uma maneira melhor ou mais fácil?)

Então, quando o usuário vincular inicialmente sua conta do Codeforces ao seu perfil, eu poderia recuperar sua classificação e atribuir a ele o grupo correto (specialist, expert, candidate master, …, international grandmaster). No entanto, a classificação pode mudar em breve e gostaria de atualizar automaticamente o grupo do usuário quando sua classificação mudar.

Agora, pensei em algumas maneiras de realizar isso e gostaria de sugestões sobre qual caminho seguir e, se possível, algumas orientações sobre os aspectos específicos do Discourse:

  • Ter um Job que roda regularmente e atualiza a classificação individual e o grupo de cada usuário (muitas solicitações externas atingindo a API do Codeforces)
  • Ter um Job que roda regularmente e processa as competições conforme elas acontecem. Como as classificações só podem mudar durante uma competição, se as classificações dos usuários forem inicialmente consistentes, ao processar as mudanças de classificação conforme elas ocorrem, posso atualizar apenas as classificações alteradas e manter a consistência com uma única chamada à API externa
  • Ter um resolvedor personalizado de classificação e grupos. Nesse caso, eu precisaria armazenar a última recuperação de classificação e, na próxima chamada GET rating/groups, se a classificação estiver desatualizada, eu chamaria a API do Codeforces e atualizaria a classificação/grupos do usuário. Esta é minha solução preferida porque parece simples e será eventualmente consistente. No entanto, não tenho certeza de como implementá-la ou quais seriam as consequências de remover e adicionar dinamicamente um grupo a um usuário. Ou mesmo se isso é possível de forma alguma como um plugin.

Bem, peço desculpas pelo texto longo! Adoraria receber qualquer tipo de feedback sobre tudo isso. Obrigado pela atenção.

Assim que você tiver o identificador do Codeforces deles em um user_custom_field, será bastante fácil atualizar qualquer coisa que desejar no perfil deles (atribuir grupos, inserir quaisquer dados de perfil do Codeforces em campos personalizados do usuário, e assim por diante) quando eles fizerem login.

Algo como:

after_initialize do
  DiscourseEvent.on(:user_logged_in) do |user|
    # obter dados do 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

end

Dessa forma, é realmente fácil e você atualizará os dados apenas para pessoas que estão realmente ativas na comunidade.

Que maravilha! Estou feliz por ter perguntado antes de implementar qualquer coisa.

Muito obrigado!

Se todos os seus usuários forem do Codeforces, eu implementaria o login único (onde o Codeforces é o provedor de autenticação). Dessa forma, não seria necessário um passo extra de autenticação e seus usuários teriam um fluxo de cadastro/autenticação melhor. Você até poderia sincronizar os grupos assim!

Eu faria isso na origem, ou seja, onde o Codeforces sabe que a classificação mudou, faria uma chamada de API ao Discourse para atualizar a classificação (ou qualquer propriedade do usuário influenciada por essa classificação). Dessa forma, tudo estará sempre atualizado. Quando você precisa sincronizar coisas, a sondagem (polling) sempre leva a problemas de uma forma ou de outra, então evite-a sempre que possível e use uma abordagem baseada em eventos.

Eu assumi que usar o Codeforces como mestre do SSO não era possível. Se for, você definitivamente quer fazer isso dessa maneira!

A suposição de @pfaffman de que não era possível usar o Codeforces como provedor de autenticação estava correta. O Codeforces é basicamente mantido por uma única pessoa, e duvido que ele implemente algo para um projeto que ainda está na fase de planejamento ou captação de recursos.

Concordo que o polling é subótimo! É por isso que a solução proposta de consultar o sistema quando o usuário faz login parece ser um bom equilíbrio entre [o interesse do usuário de] consistência e não sobrecarregar os servidores do Codeforces a ponto de chamar atenção e, possivelmente, ser bloqueado.

Se o nosso caso específico para a comunidade brasileira for bem-sucedido e outras comunidades tentarem fazer o mesmo, talvez Mike (a única pessoa responsável pelo Codeforces) implemente algo assim.

De qualquer forma, muito obrigado pelas sugestões! Vou mantê-las em mente para a próxima vez que encontrar Mike em junho do ano que vem, quando poderei tentar convencê-lo a ajudar e, possivelmente, otimizar qualquer solução que nosso fórum venha a utilizar até lá.