🏠

API de Highlights da Homepage

🏠 API de Highlights da Homepage

Endpoint para obter conteúdo destacado (highlights) para a página inicial da aplicação.

Endpoint

GET /home/highlights

Descrição

Este endpoint retorna uma seleção curada de conteúdo destacado, incluindo:

Autenticação

❌ Não requer autenticação (endpoint público)

Request

Não requer parâmetros.

Exemplo

curl -X GET http://localhost:3000/home/highlights

Debug da ordenação por idioma

Com debugRanking=true, um administrador com sessão iniciada recebe _ranking
em cada rádio e podcast popular (pontuação da posição no grupo do pool, bónus
do idioma, pontuação final e o grupo) e as preferências resolvidas em
_rankingDebug e no cabeçalho X-Ranking-Debug. Para os restantes pedidos o
parâmetro é ignorado. Ver
Debug da ordenação por idioma.

Response

Success (200 OK)

{
  "popular_radios": [
    {
      "id": "507f1f77bcf86cd799439011",
      "name": "Rádio Comercial",
      "stream_url": "https://stream.radiocomercial.pt/comercial.mp3",
      "genre": ["Pop", "Rock"],
      "language": "pt",
      "country": "Portugal"
    }
  ],
  "popular_podcasts": [
    {
      "id": "507f1f77bcf86cd799439012",
      "title": "Podcast Exemplo",
      "description": "Um podcast interessante sobre tecnologia",
      "rss_url": "https://exemplo.pt/feed.rss",
      "author": "João Silva",
      "logo": "https://exemplo.pt/logo.png",
      "language": "pt",
      "categories": ["Technology", "Business"],
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "on_air_programs": [
    {
      "radio_id": "507f1f77bcf86cd799439011",
      "radio_name": "Rádio Comercial",
      "radio_logo": "https://exemplo.pt/logo.png",
      "program_name": "Morning Show",
      "program_description": "Programa matinal com música e notícias",
      "start_time": "08:00",
      "end_time": "10:00"
    }
  ]
}

Estrutura da Resposta

Array com as rádios mais populares.

Campo Tipo Descrição
id string ID único da rádio
name string Nome da rádio
stream_url string URL do stream de áudio
genre string[] Lista de géneros musicais
language string Código do idioma (ISO)
country string País de origem

Array com os podcasts mais populares.

Campo Tipo Descrição
id string ID único do podcast
title string Título do podcast
description string Descrição do podcast
rss_url string URL do feed RSS
author string Autor/Criador do podcast
logo string URL da imagem de capa
language string Código do idioma (ISO)
categories string[] Categorias do podcast
createdAt Date Data de criação
updatedAt Date Data de última atualização

`on_air_programs` (array)

Array com programas de rádio atualmente no ar.

Campo Tipo Descrição
radio_id string ID da rádio associada
radio_name string Nome da rádio
radio_logo string URL do logo da rádio
program_name string Nome do programa
program_description string Descrição do programa
start_time string Hora de início (HH:MM)
end_time string Hora de término (HH:MM)

Lógica de Popularidade

Rádios Populares

As rádios são ordenadas por:

  1. clickcount: Número de cliques/acessos
  2. votes: Número de votos recebidos

Critérios adicionais:

Podcasts Populares

Ordenados pela qualidade do feed, nunca pela idade (HOME_PODCAST_POOL_SORT,
em worker-shared/src/utils/home-ranking.util.ts):

  1. health_score (0 a 100, do job diário de saúde dos feeds), maior primeiro
  2. episode_count, maior primeiro
  3. last_publish_date (último episódio), mais recente primeiro; uma data
    no futuro conta como nenhuma
  4. _id, como desempate estável

Critérios:

💡 Nota: a popularidade real (reproduções, favoritos) ainda cobre menos
de 1 % dos podcasts; quando tiver cobertura passa para a frente da ordem.
Detalhes e medições na spec docs/specs/2026-10-23-idiomas-das-sugestoes-design.md
(secção 8, "Destaques da home: ordem dos podcasts").

Programas no Ar

Programas filtrados por:

  1. Dia da semana atual
  2. Hora atual (entre start_time e end_time)
  3. Rádio ativa (is_active: true)

Critérios:

Casos de Uso

1. Página Inicial da App

Exibir conteúdo relevante assim que o utilizador abre a aplicação.

const response = await fetch('/home/highlights');
const data = await response.json();

// Exibir rádios populares
data.popular_radios.forEach((radio) => {
  displayRadio(radio);
});

// Exibir podcasts populares
data.popular_podcasts.forEach((podcast) => {
  displayPodcast(podcast);
});

// Exibir programas no ar
data.on_air_programs.forEach((program) => {
  displayLiveProgram(program);
});

2. Widget de Sugestões

Mostrar sugestões rápidas em seções específicas da aplicação.

const { popular_radios } = await fetchHighlights();
showQuickAccessWidget(popular_radios.slice(0, 5));

3. Conteúdo Contextual

Utilizar programas no ar para destacar conteúdo ao vivo.

const { on_air_programs } = await fetchHighlights();
if (on_air_programs.length > 0) {
  showLiveNowBanner(on_air_programs);
}

Performance

Ver mais e continuação do ranking

As secções populares da Home são um snapshot pré-computado. Para abrir a
listagem completa sem trocar a definição do ranking, o cliente deve usar:

GET /radios?sortBy=home&page=1&pageSize=50
GET /podcasts?sortBy=home&page=1&pageSize=50

sortBy=home centraliza a mesma ordenação usada no snapshot: rádios ativas
por clickcount, votes e diversidade de género; podcasts pt pela ordem dos
destaques (HOME_PODCAST_POOL_SORT: health_score, episode_count,
last_publish_date, _id), sem explicit nem feed_dead, um de cada
programa e no máximo 2 do mesmo autor e 3 da mesma primeira categoria em cada
janela de 10 (até 200 podcasts, lidos de 300 candidatos pela ordem do índice).
A lista de podcasts é sempre a de pt: ainda não segue os idiomas do
utilizador. Nas rádios, com idiomas de conteúdo (sessão iniciada, ou
countryCode + locale de um convidado), a lista começa pelos países das
variantes do utilizador e é ordenada por idioma com a mesma força dos
destaques; sem idiomas é a de Portugal de sempre (ver
API de Listagem de Rádios). Todos os empates usam
_id, garantindo páginas determinísticas e sem duplicados. Os filtros de género/categoria podem ser
aplicados durante a navegação; ao limpar pesquisa/filtro, o cliente deve
voltar a estas chamadas para restaurar o ranking.

As listagens aplicam .select(), .lean() e limites de 200 candidatos por
pedido. O page/pageSize é resolvido pela API (padrão 50, máximo 200).

Cache Recomendado

Para otimizar performance, recomenda-se implementar cache:

// Exemplo de cache no cliente (5 minutos)
const CACHE_TTL = 5 * 60 * 1000; // 5 minutos

async function getCachedHighlights() {
  const cached = localStorage.getItem('highlights');
  const timestamp = localStorage.getItem('highlights_timestamp');

  if (cached && timestamp) {
    const age = Date.now() - parseInt(timestamp);
    if (age < CACHE_TTL) {
      return JSON.parse(cached);
    }
  }

  const data = await fetchHighlights();
  localStorage.setItem('highlights', JSON.stringify(data));
  localStorage.setItem('highlights_timestamp', Date.now().toString());

  return data;
}

Otimizações Futuras

  1. Cache no servidor: Implementar Redis para cachear respostas
  2. CDN: Servir conteúdo estático via CDN
  3. Paginação: Adicionar suporte para carregar mais conteúdo
  4. Personalização: Adaptar conteúdo baseado em preferências do utilizador

Melhorias Futuras

1. Métricas de Popularidade para Podcasts

Implementar sistema de tracking:

2. Personalização

Adaptar conteúdo baseado em:

3. A/B Testing

Testar diferentes algoritmos de ranking:

4. Machine Learning

Utilizar ML para:

5. Filtros Dinâmicos

Permitir filtros opcionais:


Última Atualização: 25 de dezembro de 2025