Payload de diagnóstico no ping de sessão

TLDR: Enriquece o endpoint de ping de presença para aceitar e persistir um payload de diagnóstico, permitindo reconstruir uma linha do tempo de qualidade de conexão por participante em uma chamada.

Contexto

Hoje o endpoint PUT /me/meetings/:id/pings recebe payload vazio e registra apenas um timestamp no array room_pings ou wait_pings do MeetingParticipant. Não há dados sobre a qualidade da conexão, o estado do dispositivo ou métricas do Twilio.

O objetivo é conseguir responder, para um atendimento com problema reportado: “o gargalo foi a internet do usuário, o nosso servidor, ou o Twilio?” — o que exige uma linha do tempo com snapshots de diagnóstico a cada heartbeat.

Origem do problema registrada em sessions_ghost_pings_and_missing_connection_data.

Objetivos

  • Aceitar payload de diagnóstico opcional no ping (compatível com clientes que enviam payload vazio)
  • Persistir cada ping como um registro individual com seu snapshot de diagnóstico em JSONB
  • Manter os arrays room_pings/wait_pings existentes (usados em presence_status e no serializer)

Fora de escopo

Mudanças

Nova tabela meeting_participant_pings

Coluna Tipo Notas
id bigint PK  
meeting_participant_id bigint FK NOT NULL  
kind integer NOT NULL enum: room: 0, wait: 1
pinged_at datetime NOT NULL timestamp do servidor
diagnostics jsonb NOT NULL DEFAULT {} payload de diagnóstico
created_at / updated_at datetime  

Schema do payload

json { "device": { "user_agent": "Mozilla/5.0 ...", "browser": "Chrome 120", "os": "macOS 14", "device_type": "desktop" }, "network": { "effective_type": "4g", "downlink": 10.0, "rtt": 50, "save_data": false, "online": true }, "app_state": { "visibility_state": "visible" }, "media_devices": { "camera": { "label": "FaceTime HD Camera", "device_id": "..." }, "microphone": { "label": "Built-in Microphone", "device_id": "..." }, "audio_output": { "label": "Built-in Speakers", "device_id": "..." } }, "twilio": { "network_quality_level": 4, "network_quality_stats": { "audio": { "send": 5, "recv": 5 }, "video": { "send": 4, "recv": 4 } }, "room_stats": {}, "reconnection_events": [], "errors": [] } }

Todos os campos são opcionais — payload vazio {} é válido e resulta em diagnostics: {}.

Arquivos

Arquivo Ação
db/migrate/TIMESTAMP_create_meeting_participant_pings.rb criado
app/models/meeting_participant_ping.rb criado
app/models/meeting_participant.rb adiciona has_many :meeting_participant_pings
app/use_cases/meeting_participants/create_ping.rb atualizado para criar MeetingParticipantPing
app/controllers/api/v1/meetings/participant_pings_controller.rb atualizado para aceitar payload
spec/models/meeting_participant_ping_spec.rb criado
spec/requests/api/v1/meetings/participant_pings_update_spec.rb atualizado
spec/factories/meeting_participant_pings.rb criado

Plano de implementação

  1. test: validações do model MeetingParticipantPing — spec/models/meeting_participant_ping_spec.rb
  2. feat: migration + model com enum kind e associação ao MeetingParticipant
  3. test: request spec — ping com payload de diagnóstico persiste um MeetingParticipantPing; ping sem payload também persiste (diagnostics vazio); compatibilidade com clientes antigos
  4. feat: atualizar CreatePing para criar o registro com diagnostics vindo do contexto
  5. feat: atualizar o controller para receber e repassar diagnostics ao flow
  6. refactor: extrair a criação do MeetingParticipantPing para use case dedicado MeetingParticipants::CreatePingRecord se o CreatePing ficar complexo

Nota: a coluna schema_version, criada junto desta feature para versionar o formato do JSON, foi removida antes do merge — ver remove_ping_diagnostics_schema_version.

Como verificar

```bash # Com payload vazio (compatibilidade) curl -X PUT …/api/v1/me/meetings/:pid/pings \ -H “Authorization: Bearer " # → 200, MeetingParticipantPing criado com diagnostics: {}

Com payload de diagnóstico

curl -X PUT …/api/v1/me/meetings/:pid/pings \ -H “Authorization: Bearer " \ -d '{"diagnostics": {"network": {"effective_type": "4g"}}}' # → 200, MeetingParticipantPing criado com diagnostics preenchido

No console do Rails

MeetingParticipantPing.last.diagnostics MeetingParticipantPing.where(meeting_participant: participant).order(:pinged_at) ```

Documentação

O comportamento resultante, incluindo a classificação de qualidade derivada de network.rtt, está em room_ping_quality.