feat: GET /tags/popular — top tags by usage count

This commit is contained in:
2026-05-14 15:34:40 +02:00
parent eb7dbb0aee
commit 9b47779e63
2 changed files with 29 additions and 0 deletions

View File

@@ -51,6 +51,21 @@ impl TagRepository for PgTagRepository {
Ok(Paginated { items: rows.into_iter().map(Thought::from).collect(), total, page: page.page, per_page: page.per_page }) Ok(Paginated { items: rows.into_iter().map(Thought::from).collect(), total, page: page.page, per_page: page.per_page })
} }
async fn popular_tags(&self, limit: usize) -> Result<Vec<(String, i64)>, DomainError> {
sqlx::query_as::<_, (String, i64)>(
"SELECT t.name, COUNT(tt.thought_id) AS thought_count
FROM tags t
JOIN thought_tags tt ON t.id = tt.tag_id
GROUP BY t.id, t.name
ORDER BY thought_count DESC
LIMIT $1"
)
.bind(limit as i64)
.fetch_all(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))
}
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -133,6 +133,20 @@ pub async fn user_thoughts_handler(
}))) })))
} }
pub async fn get_popular_tags(
State(s): State<AppState>,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> Result<Json<serde_json::Value>, ApiError> {
let limit: usize = params.get("limit").and_then(|v| v.parse().ok()).unwrap_or(20);
let tags = s.tags.popular_tags(limit.min(100)).await?;
Ok(Json(serde_json::json!({
"tags": tags.iter().map(|(name, count)| serde_json::json!({
"name": name,
"thought_count": count,
})).collect::<Vec<_>>()
})))
}
#[utoipa::path( #[utoipa::path(
get, path = "/tags/{name}", get, path = "/tags/{name}",
params( params(