🏠 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:
- Rádios populares: As 10 rádios mais populares
- Podcasts populares: Os 10 melhores podcasts pela qualidade do feed (ver abaixo)
- Programas no ar: Até 10 programas de rádio que estão sendo transmitidos no momento atual
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
`popular_radios` (array)
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 |
`popular_podcasts` (array)
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:
- clickcount: Número de cliques/acessos
- votes: Número de votos recebidos
Critérios adicionais:
- Apenas rádios ativas são incluídas (
is_active: true) - Limite: 10 rádios
Podcasts Populares
Ordenados pela qualidade do feed, nunca pela idade (HOME_PODCAST_POOL_SORT,
em worker-shared/src/utils/home-ranking.util.ts):
health_score(0 a 100, do job diário de saúde dos feeds), maior primeiroepisode_count, maior primeirolast_publish_date(último episódio), mais recente primeiro; uma data
no futuro conta como nenhuma_id, como desempate estável
Critérios:
- Ficam de fora os feeds
explicit: trueefeed_dead: true - Um de cada programa: cópias com o mesmo título e autor (outro URL do feed)
não entram - No máximo 2 do mesmo autor e 3 da mesma primeira categoria por
secção, enquanto houver outros; se a secção ficar curta, completa-se com os
que os limites deixaram de fora, pela ordem - Limite: 10 podcasts (de 30 candidatos por grupo do pool)
💡 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 specdocs/specs/2026-10-23-idiomas-das-sugestoes-design.md
(secção 8, "Destaques da home: ordem dos podcasts").
Programas no Ar
Programas filtrados por:
- Dia da semana atual
- Hora atual (entre
start_timeeend_time) - Rádio ativa (
is_active: true)
Critérios:
- Baseado no horário do servidor
- Limite: 10 programas
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, oucountryCode + 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
- Cache no servidor: Implementar Redis para cachear respostas
- CDN: Servir conteúdo estático via CDN
- Paginação: Adicionar suporte para carregar mais conteúdo
- Personalização: Adaptar conteúdo baseado em preferências do utilizador
Melhorias Futuras
1. Métricas de Popularidade para Podcasts
Implementar sistema de tracking:
- Número de plays
- Downloads de episódios
- Favoritos/Seguidos
- Avaliações
2. Personalização
Adaptar conteúdo baseado em:
- Histórico de escuta do utilizador
- Preferências de género/categoria
- Localização geográfica
- Horário de acesso
3. A/B Testing
Testar diferentes algoritmos de ranking:
- Popularidade vs Novidade
- Baseado em tempo vs Baseado em métricas
- Diversidade de conteúdo
4. Machine Learning
Utilizar ML para:
- Recomendações personalizadas
- Predição de conteúdo relevante
- Detecção de tendências
5. Filtros Dinâmicos
Permitir filtros opcionais:
?language=pt- Filtrar por idioma?genre=rock- Filtrar por género (rádios)?category=technology- Filtrar por categoria (podcasts)?limit=5- Limitar número de resultados
Links Relacionados
Última Atualização: 25 de dezembro de 2025