Tópicos FCM por plataforma
TLDR: Tópicos FCM por plataforma (
platform_ios/platform_android) permitem envio segmentado por SO com uma única chamada ao Firebase, sem fanout manual sobre milhares de tokens.
Visão geral
- Cada
PushDevicesregistra emtopics(ArrayField) a lista de tópicos FCM em que está inscrito - Para envio segmentado por SO, o backend usa dois tópicos canônicos:
platform_ioseplatform_android - Os demais tópicos (
all/all_staging,refresh_home,user_<id>) continuam sendo inscritos pelo app no momento do registro do device. O backend apenas espelha esses valores no campotopics
Campo topics
| Campo | Tipo | Descrição |
|---|---|---|
topics |
ArrayField(CharField(100)) |
Lista de tópicos FCM em que o device está inscrito. Default [] |
Contratos
Service: apps/notifications/services/topic_subscription.py
Service puro — uma chamada equivale a uma operação sobre um único device. Sem batch, sem responsabilidades de infraestrutura. Pode ser reutilizado em qualquer fluxo (signal de registro de device, jornadas futuras, etc.).
| Função | Descrição |
|---|---|
subscribe(device, topics) |
Inscreve o device em cada tópico via FCM. Persiste em device.topics apenas quando o FCM aceita. Retorna False se o token está morto. Idempotente |
unsubscribe(device, topics) |
Análogo, removendo do array |
Tokens mortos e exceções (FirebaseError, ValueError) são apenas logados — não são removidos do banco nesta fase.
Envio segmentado por plataforma
```python from firebase_admin import messaging
msg = messaging.Message( topic=”platform_ios”, # ou platform_android notification=messaging.Notification(title=”…”, body=”…”), ) messaging.send(msg) ```
Para envio cruzado:
python
msg = messaging.Message(
condition="'platform_ios' in topics || 'platform_android' in topics",
notification=...,
)
Enviar pelo Admin
O campo Notification.platform_target (choices ios/android, opcional) permite ao admin enviar direto para o tópico da plataforma. Precedência do roteamento em NotificationService._get_target:
platform_target > send_to_all > push_device
platform_target e send_to_all são mutuamente exclusivos — o form do admin recusa o save quando os dois vêm preenchidos.
Backfill via data migration
A migration 0017_backfill_platform_topics.py roda automaticamente no python manage.py migrate e:
- Itera todos os
PushDevicescomtokeneplatformem (ios,android) - Chama
subscribe(device, [platform_<os>])no FCM — única chamada externa - Acrescenta ao campo
topicsos tópicos legados que o mobile já gerencia:all_<env>(all_stagingem staging,allem prod),refresh_home,user_<id> - Falhas FCM são ignoradas por device (apenas log)
Não é necessário rodar nada no shell — basta o migrate normal do deploy.
Inscrição automática no registro do device
O signal post_save de PushDevices (com created=True) dispara a task subscribe_device_to_default_topics, que inscreve o device em all/all_staging, refresh_home e platform_<os>. O subscribe é idempotente, então a inscrição feita pelo app no login permanece como redundância segura.
Expiração e unsubscribe
O envio para tópico é feito pelo Firebase e ignora a flag is_expired do banco. Por isso, todo ponto que marca um device como expirado também chama unsubscribe(device, device.topics) — sem isso, tokens antigos ainda entregáveis continuam recebendo o canal “All”, gerando notificações duplicadas.
Script de teste manual
scripts/test_platform_topic_subscription.py — ajustar USER_EMAIL e TARGET_PLATFORM no topo e rodar:
bash
python manage.py shell < scripts/test_platform_topic_subscription.py
Inscreve os devices do usuário no tópico e envia uma mensagem para confirmar a entrega.