diff --git a/graphql/api/src/helpers/handler.rs b/graphql/api/src/helpers/handler.rs index 1370faf..c8673f3 100644 --- a/graphql/api/src/helpers/handler.rs +++ b/graphql/api/src/helpers/handler.rs @@ -11,6 +11,7 @@ use chrono::Utc; use crawler::RssNews; use search::Search; use std::sync::Arc; +use tokio::sync::RwLock; use url::Url; #[derive(Debug, Default)] @@ -42,7 +43,7 @@ fn find_media(from_url: &str) -> Media { /// Handling incoming messages from MPSC channel. pub async fn process_article( article: RssNews, - searcher: &Arc, + searcher: &Arc>, ranker: &mut Ranker, ) -> Result<(), Box> { let summary = summary::get_summary(&article.content).await?; @@ -94,7 +95,12 @@ pub async fn process_article( ranker.add_entry(&news.title).await?; - searcher.add_entry(news).await.map_err(|e| e.into()) + searcher + .read() + .await + .add_entry(news) + .await + .map_err(|e| e.into()) } else { Err("No media found with this URL".into()) } diff --git a/graphql/api/src/helpers/ranking.rs b/graphql/api/src/helpers/ranking.rs index b95c01e..45bc641 100644 --- a/graphql/api/src/helpers/ranking.rs +++ b/graphql/api/src/helpers/ranking.rs @@ -31,12 +31,12 @@ impl Ranker { } /// Get a single ranking from multiple sources. - pub async fn get_rank(&self) -> Result, Error> { + pub async fn get_rank(&self, length: u32) -> Result, Error> { let mut result = Vec::new(); if let Some(squid) = &self.squid { result.append( - &mut squid.write().await.leaderboard().await.map_err( + &mut squid.write().await.leaderboard(length).await.map_err( |error| { Error::new( Unspecified, diff --git a/graphql/api/src/main.rs b/graphql/api/src/main.rs index 5f453af..36f8ffe 100644 --- a/graphql/api/src/main.rs +++ b/graphql/api/src/main.rs @@ -15,6 +15,7 @@ use search::{Attributes, Search}; use std::{sync::Arc, time::Duration}; use strum::IntoEnumIterator; use tokio::sync::mpsc; +use tokio::sync::RwLock; use tracing::{debug, error, info, Level}; use tracing_subscriber::fmt; use url::Url; @@ -120,7 +121,7 @@ async fn main() -> Result<(), Box> { crawler.crawl()?; // Create meilisearch client. - let searcher = Arc::new( + let searcher = Arc::new(RwLock::new( Search::new( std::env::var("MEILISEARCH_URL") .unwrap_or("http://localhost:7700".into()), @@ -128,7 +129,17 @@ async fn main() -> Result<(), Box> { )? .index("news".into()) .await, - ); + )); + + // Add country field as a filterable attribute. + searcher + .write() + .await + .index + .as_ref() + .unwrap() + .set_filterable_attributes(&["source.country"]) + .await?; // Create ranking platform. let ranker = Ranker::new().await?; diff --git a/graphql/api/src/schema/mod.rs b/graphql/api/src/schema/mod.rs index cf85576..a20acf3 100644 --- a/graphql/api/src/schema/mod.rs +++ b/graphql/api/src/schema/mod.rs @@ -8,6 +8,7 @@ use mcq::QuestionQuery; use news::NewsQuery; use search::Search; use std::sync::Arc; +use tokio::sync::RwLock; pub type Date = DateTime; @@ -15,7 +16,7 @@ pub type Date = DateTime; #[derive(Clone, Debug)] pub struct Context { /// [`search::Search`] meilisearch client. - pub meilisearch: Arc, + pub meilisearch: Arc>, /// Custom [`Ranker`] supporting multiple sources. pub ranker: Ranker, } diff --git a/graphql/api/src/schema/news.rs b/graphql/api/src/schema/news.rs index 0e7ca2d..4aca6fc 100644 --- a/graphql/api/src/schema/news.rs +++ b/graphql/api/src/schema/news.rs @@ -1,7 +1,7 @@ use crate::models::{image::Image, news::News, source::Media}; use crate::schema::Date; use crate::Context; -use juniper::{graphql_object, FieldResult}; +use juniper::{graphql_object, graphql_value, FieldError, FieldResult}; /// Implement GraphQL on News structure. #[graphql_object(context = Context, description = "A media article.")] @@ -49,18 +49,59 @@ pub struct NewsQuery; /// Implement the GraphQL object for the news query. #[graphql_object(context = Context)] impl NewsQuery { - /// Get the most relevant news of the day. + /// Get the *three* most relevant news of the day. + async fn get_top_news( + ctx: &Context, + #[graphql(description = "ISO 3166-1 alpha-2 country code.")] + country: String, + #[graphql(description = "Maximum number of articles sent.")] limit: i32, + ) -> FieldResult> { + let rank = ctx.ranker.get_rank(limit.try_into()?).await?; + let mut most_revelant_news = Vec::new(); + + for word in rank { + let news = ctx + .meilisearch + .write() + .await + .index + .as_ref() + .ok_or_else(|| FieldError::new("Index not found", graphql_value!({ "internal_error": "Meilisearch index is not selected" })))? + .search() + .with_query(&word) + .with_limit(1) + .with_filter(&format!("source.country={:?}", country)) + .with_attributes_to_search_on(&["title"]) + .execute::() + .await?; + + most_revelant_news.push(news.hits[0].result.clone()) + } + + Ok(most_revelant_news) + } + + /// Get news of the day. async fn get_news( ctx: &Context, #[graphql(description = "ISO 3166-1 alpha-2 country code.")] - _country: String, - #[graphql(description = "Maximum number of articles sent.")] - _limit: i32, + country: String, + #[graphql(description = "Maximum number of articles sent.")] limit: i32, ) -> FieldResult> { - let _rank = ctx.ranker.get_rank().await?; + let news = ctx + .meilisearch + .write() + .await + .index + .as_ref() + .ok_or_else(|| FieldError::new("Index not found", graphql_value!({ "internal_error": "Meilisearch index is not selected" })))? + .search() + .with_query("*") + .with_limit(limit.try_into()?) + .with_filter(&format!("source.country={:?}", country)) + .execute::() + .await?; - Ok(vec![News { - ..Default::default() - }]) + Ok(news.hits.iter().map(|r| r.result.clone()).collect()) } } diff --git a/graphql/rank/src/squid.rs b/graphql/rank/src/squid.rs index 7e2184b..61f2413 100644 --- a/graphql/rank/src/squid.rs +++ b/graphql/rank/src/squid.rs @@ -38,8 +38,11 @@ impl Squid { } /// Get top 10 most used words in news articles. - pub async fn leaderboard(&mut self) -> Result, Status> { - let request = tonic::Request::new(LeaderboardRequest { length: 10 }); + pub async fn leaderboard( + &mut self, + length: u32, + ) -> Result, Status> { + let request = tonic::Request::new(LeaderboardRequest { length }); let leaderboard = self .client diff --git a/graphql/search/src/lib.rs b/graphql/search/src/lib.rs index 9b707ec..5dae75a 100644 --- a/graphql/search/src/lib.rs +++ b/graphql/search/src/lib.rs @@ -22,7 +22,8 @@ pub trait Attributes { #[derive(Clone, Debug)] pub struct Search { client: Client, - index: Option, + /// Meilisearch index. + pub index: Option, ttl_row: Option, }