Corrigir race na criação da UserJourney ao aceitar termos
TLDR: A criação da
UserJourneyno aceite de termos é assíncrona (Celery); se o cliente chamarGET /audios/dailyantes da task rodar, o usuário recebe o áudio do dia genérico em vez do áudio 1 da jornada. O fix torna a criação síncrona.
Contexto
Ao aceitar os termos, AcceptTermsSerializer.update() (apps/accounts/serializers/account.py) salva accept_terms_at, disparando o signal create_user_journey_on_terms_acceptance (apps/audios/signals.py), que hoje enfileira a task assíncrona create_user_journey.delay(user.pk) (apps/audios/tasks.py). Essa task chama JourneyService.create_for(user) (apps/audios/services.py), que cria a UserJourney e marca is_in_journey=True.
Como a task roda via Celery/RabbitMQ, existe uma janela de corrida: se o cliente chamar GET /audios/daily antes da task ser processada, is_in_journey ainda está False no banco e AudioService.get_daily_audio cai no fallback _get_current_daily_audio() (áudio do dia genérico), em vez de retornar journey.first_audio (áudio 1). No próximo acesso, a task já rodou e o áudio correto aparece — o que bate exatamente com o sintoma relatado: usuário trial vê o áudio do dia na primeira entrada e o áudio 1 na segunda.
Confirmado por grep que create_user_journey (apps/audios/tasks.py) não tem nenhum outro chamador além do signal.
Objetivos
- Eliminar a race: a
UserJourneydeve existir eis_in_journey=Trueantes da resposta do endpoint de aceite de termos retornar ao cliente. - Remover a task Celery
create_user_journey, que fica órfã após o fix.
Fora de escopo
- Mudanças no fluxo de
advance_user_journey(avanço de posição na jornada) — não relacionado a esta race. - Mudanças em
JourneyService.create_forouJourneyService.advance_foralém da forma de chamada. - Qualquer alteração no client (mobile/web) — o fix é inteiramente no backend.
Mudanças
apps/audios/signals.py: no handlercreate_user_journey_on_terms_acceptance, substituircreate_user_journey.delay(instance.pk)por chamada direta e síncrona aJourneyService.create_for(instance).apps/audios/tasks.py: remover a taskcreate_user_journey, agora sem chamadores.- Import de
create_user_journeyemapps/audios/signals.pyé substituído pelo import deJourneyService(já existe emapps/audios/services.py).
Como verificar
- Teste automatizado cobrindo o cenário: usuário aceita os termos (signal disparado) e, na sequência imediata (sem processar nenhuma task Celery),
GET /audios/dailyretorna o áudio na posição 1 daJourneyativa — não o áudio do dia genérico. - Teste do signal handler confirmando que
UserJourneyé criada de forma síncrona (sem depender de.delay()/worker) quandoaccept_terms_até setado. make run.test path=apps/audiosdeve passar.- Rodar
make run.ci_localantes de abrir o PR.
Documentação
Atualizar .project/docs/reference/audios/daily_audio_journey.md — a seção que descreve “O UserJourney é criado no momento em que o usuário aceita os termos” deve deixar explícito que a criação é síncrona (via signal, sem Celery), já que a versão atual não menciona o mecanismo e isso motivou o bug.