feat: GET /tags/popular — top tags by usage count
This commit is contained in:
@@ -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)]
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user