Fechar os gaps de analytics de conteúdo (vídeo + downloads)
Contexto
A auditoria de analytics (ver docs/FILE_DOWNLOAD_ANALYTICS.md) encontrou três gaps:
- A telemetria de playback de vídeo existe mas não coleta nada em produção. O backend está completo (
apps.streaming—MediaPlaybackSession/MediaPlaybackHeartbeat,POST /api/v1/media/<id>/heartbeat/,GET /api/v1/media/<id>/analytics/, ADR 0009) e oVideoPlayer(@repo/ui) embute o motor de telemetria (VideoAnalyticsConfig:analyticsUrl,mediaId,sessionId,heartbeatIntervalMs— heartbeats a cada 5s + pause/unmount). Porém nenhum chamador do SPA passa a propanalytics— aulas e produtos digitais renderizam o player sem telemetria. Os únicos dados de sessão existentes vêm do seed (_seed_gaps/media.py) e do admin do Django. Além disso, o detail de aula (CourseLessonDetailView) expõevideo_urlmas não ouidda mídia do vídeo principal, sem o qual o player não consegue apontar o heartbeat para a mídia correta. - Permissões frouxas no resumo de analytics.
MediaAnalyticsSummaryViewexige apenasRequiresActiveMembership— qualquer membro ativo da comunidade vêtotal_views/watchtime/heatmap de qualquer mídia. Analytics é dado de administração. - Downloads de arquivos não têm analytics nenhum. Drive (
POST /api/v1/files/<id>/download/), anexos de aula (LessonAttachmentDownloadView) e produtos digitais (ProductItem.fileviafile_urlpresigned no detail) não registram download algum — nenhum modelo, contador ou endpoint de sumário. Pior: o download de produto digital não passa pelo backend (o frontend baixa direto do storage via<a href={file_url} download>), então nem seria possível registrar sem mudar o fluxo.
Decisão
Fechar os três gaps na mesma frente de trabalho "analytics de conteúdo":
1. Conectar a telemetria de vídeo de ponta a ponta no SPA
- Backend:
CourseLessonDetailViewpassa a exporvideo_media_id(uid doMediado vídeo principal) na resposta e noCourseLessonDetailSerializer(inclusive no stublocked). Produtos digitais já expõem o uid:ProductPackageItemVideoSerializer.id. - Frontend (
apps/web): os players de aula (courses/$courseId/$lessonId.tsx) e de item de produto (products/$packageId/$itemId.tsx) passam{{ analyticsUrl: "/api/v1/media/<id>/heartbeat/", mediaId: <uid>, heartbeatIntervalMs: 5000 }}aoVideoPlayer.sessionIdé opcional — o player usa osession_idretornado pelo servidor no primeiro heartbeat. - Regenerar o client Orval (
make generate-api) após a mudança de schema.
2. Analytics é dado de administração
MediaAnalyticsSummaryView(e todos os novos endpoints de sumário de downloads) passam a exigirIsAdminOrModerator(deapps.spaces.permissions), além deRequiresCommunity/IsAuthenticated/RequiresActiveMembership. O endpoint de heartbeat permanece permissivo (authentication_classes = []+RequiresCommunity) — ele é o ponto de ingestão do player e não expõe agregados.
3. Analytics de downloads em app dedicado apps.downloads
- Modelo:
FileDownloadEvent(estendeBaseModel) com GenericForeignKey paraDriveFile | Media | ProductItem+community,membership(nullable),ip_address,user_agent,referrer, snapshots decontent_type/file_size. Um evento por download (download é evento discreto; sem contador denormalizado — derivável por agregação). - Registro: nos pontos onde o backend já gera a URL —
DriveFileViewSet.downloadeLessonAttachmentDownloadView; novoProductItemDownloadView(GET /api/headless/v1/products/{package_id}/items/{item_id}/download) valida acesso → registra →302para presigned URL (mesmo padrão de anexos de aula). O frontend de produto troca o link direto por esse endpoint. - Sumários: endpoint genérico
GET /api/v1/downloads/analytics/?object_type=drive|media|product&object_id=<uid>(total, 30d, únicos, timeline, top baixadores) + seçãofilesnoAdminAnalyticsView(dashboard admin). GateIsAdminOrModerator. - Detalhes de schema/endpoints/UI:
docs/FILE_DOWNLOAD_ANALYTICS.md(design completo, fases de implementação).
Decisões provisionais
As perguntas em aberto da seção 8 de docs/FILE_DOWNLOAD_ANALYTICS.md foram resolvidas por padrão neste ADR: endpoint de sumário genérico (object_type+object_id), file_url mantido para preview inline (TablatureViewer), contagem de downloads somente no admin (sem badge público). Revisitar se o produto exigir exposição pública.
Consequências
- O pipeline de vídeo passa a coletar dados reais em produção (fonte:
apps.streaming), tornando o seed e o admin meros instrumentos de inspeção. - Analytics de playback e downloads ficam com gate uniforme (admin/moderador) — corrige a exposição atual do
MediaAnalyticsSummaryView. - Downloads passam a ser auditáveis e mensuráveis (quem baixou o quê, quando), com o produto digital deixando de contornar o backend.
apps.downloadscresce como o app natural para futuras métricas de arquivos (visualizações/previews) sem incharapps.media/apps.spaces.- Volume:
FileDownloadEventé de cardinalidade baixa-média por tenant; sem necessidade de worker/agregação separada no primeiro momento. - Mudanças de API (novo campo
video_media_id, novo endpoint de download de produto, novos endpoints de analytics) exigemmake generate-api— client gerado é o contrato (regra Orval).