Skip to content

fix(integration-discord): normaliza metadata sem sobrescrever dados da identidade #479

Description

@henrique-leme

Descrição do problema

A mesma identidade do Discord é criada e atualizada por três fluxos que salvam os dados do usuário em formatos diferentes.

  1. O OAuth salva username, email e avatar na raiz de metadata
  2. A importação de mensagens salva o usuário em metadata.author
  3. A importação de perfil salva o dump em metadata.user

Os fluxos não apenas usam formatos diferentes. O OAuth e a importação de perfil usam updateOrCreate e substituem o metadata inteiro. Com isso, o último fluxo executado apaga informações salvas pelo anterior.

A importação de perfil também grava um ClientAccessManager vazio em credentials e substitui connected_at pela data em que a pessoa entrou no servidor. Esses campos pertencem à conexão OAuth e não deveriam ser atualizados por uma importação de dados públicos do perfil.

Comportamento esperado

Os dados principais do usuário do Discord devem possuir um formato canônico independente da origem.

Cada fluxo deve atualizar somente os campos pelos quais é responsável. A importação de perfil ou mensagem não deve apagar credenciais, email ou informações da conexão OAuth. Um novo login OAuth também não deve descartar o payload útil importado do perfil.

A identidade deve continuar ligada ao mesmo usuário durante qualquer atualização, seguindo a resolução por provider e external_account_id já adotada no PR #207.

Comportamento atual

Foi possível reproduzir o problema localmente sem conta real e sem acessar o Discord.

No cenário OAuth seguido de importação de perfil, a importação remove email e avatar da raiz do metadata, substitui o payload pelo formato com metadata.user, apaga access token e refresh token e troca connected_at pela data de entrada no servidor.

No cenário importação de perfil seguida de OAuth, o OAuth substitui o payload completo e remove campos como user, badges e guild_member, deixando apenas email, avatar e username na raiz.

A reprodução automatizada executou 2 testes com 15 assertions e confirmou a sobrescrita nos dois sentidos.

Passos para reproduzir

  1. Criar localmente um usuário e uma ExternalIdentity do Discord com metadata e credenciais equivalentes ao resultado do OAuth
  2. Executar ImportDiscordProfileAction para o mesmo provider e external_account_id
  3. Atualizar o model e conferir que os campos do OAuth foram substituídos
  4. Repetir na ordem inversa, criando a identidade pela importação de perfil
  5. Executar AttachProviderToUser com DTOs locais de OAuth
  6. Conferir que o payload importado do perfil foi substituído pelo recorte do OAuth

Ambiente

  • Sistema operacional: Windows
  • Navegador / versão: não se aplica
  • Ambiente: local
  • Branch: 4.x
  • Commit validado: 021bc456
  • Banco: PostgreSQL local em Docker
  • Reprodução: Pest com factories e DTOs locais, sem conta real e sem chamadas externas

Sugestão de correção (opcional)

Criar um normalizador único para os dados públicos da identidade do Discord dentro de integration-discord. Os três produtores devem projetar username, global_name, avatar e email para o mesmo contrato canônico. Payloads brutos que ainda forem necessários podem ficar separados por origem, sem obrigar os consumidores a descobrir se o usuário está em user, author ou na raiz.

Definir responsabilidade por campo antes de alterar as Actions.

  1. O fluxo OAuth é responsável por credentials, connected_at, connected_by, disconnected_at e pelos dados recebidos na autenticação
  2. A importação de perfil é responsável pelos dados públicos e pelo snapshot do perfil do Discord
  3. A importação de mensagem pode criar uma identidade ausente com os dados públicos disponíveis, mas não deve substituir credenciais ou dados mais completos de uma identidade existente
  4. guild_member.joined_at deve continuar como dado de participação no servidor e não substituir o horário de conexão OAuth

Substituir as atualizações amplas por merge controlado ou por uma Action dedicada que atualize somente os campos pertencentes a cada origem. A resolução deve continuar identity first e nunca trocar model_id de uma identidade existente, seguindo a correção feita no PR #207.

Adicionar testes para as ordens OAuth seguido de perfil, perfil seguido de OAuth, mensagem seguida de perfil e identidade já existente. Os testes devem validar metadata canônico, preservação e leitura das credenciais, datas da conexão e manutenção do mesmo usuário.

Durante a transição, manter a compatibilidade adicionada no PR #478. Para os registros antigos, avaliar um backfill em chunks com dry-run, contadores e nenhuma alteração de model_id, seguindo o cuidado usado nas ferramentas de recuperação do PR #207.

Contexto adicional

O problema apareceu durante a revisão do PR #478, que já foi mergeado. Esse PR corrigiu o Meeting Showcase para ler os três formatos existentes, mas não alterou a origem da inconsistência.

Os padrões considerados para esta proposta foram o contrato de metadata e credenciais introduzido no PR #186, a resolução identity first e recuperação segura do PR #207, o fluxo de reconexão OAuth do PR #290 e a compatibilidade temporária do consumidor criada no PR #478.

Pontos envolvidos no código:

  1. app-modules/identity/src/Auth/Actions/AttachProviderToUser.php
  2. app-modules/integration-discord/src/ETL/Actions/ImportDiscordProfileAction.php
  3. app-modules/integration-discord/src/ETL/Actions/ImportDiscordMessageAction.php

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions