diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index a315804..7707608 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -66,6 +66,8 @@ pub const TIMELINES_FOLLOWING: &str = include_str!("./public/html/timelines/foll pub const TIMELINES_ALL: &str = include_str!("./public/html/timelines/all.html"); pub const TIMELINES_HOME_QUESTIONS: &str = include_str!("./public/html/timelines/home_questions.html"); +pub const TIMELINES_POPULAR_QUESTIONS: &str = + include_str!("./public/html/timelines/popular_questions.html"); pub const TIMELINES_FOLLOWING_QUESTIONS: &str = include_str!("./public/html/timelines/following_questions.html"); pub const TIMELINES_ALL_QUESTIONS: &str = @@ -207,6 +209,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"timelines/following.html"(crate::assets::TIMELINES_FOLLOWING) --config=config); write_template!(html_path->"timelines/all.html"(crate::assets::TIMELINES_ALL) --config=config); write_template!(html_path->"timelines/home_questions.html"(crate::assets::TIMELINES_HOME_QUESTIONS) --config=config); + write_template!(html_path->"timelines/popular_questions.html"(crate::assets::TIMELINES_POPULAR_QUESTIONS) --config=config); write_template!(html_path->"timelines/following_questions.html"(crate::assets::TIMELINES_FOLLOWING_QUESTIONS) --config=config); write_template!(html_path->"timelines/all_questions.html"(crate::assets::TIMELINES_ALL_QUESTIONS) --config=config); diff --git a/crates/app/src/public/html/timelines/popular.html b/crates/app/src/public/html/timelines/popular.html index b8b8f87..ef4f3c7 100644 --- a/crates/app/src/public/html/timelines/popular.html +++ b/crates/app/src/public/html/timelines/popular.html @@ -2,7 +2,9 @@ Popular - {{ config.name }} {% endblock %} {% block body %} {{ macros::nav(selected="popular") }}
- {{ macros::timelines_nav(selected="popular") }} + {{ macros::timelines_nav(selected="popular") }} {{ + macros::timelines_secondary_nav(posts="/popular", + questions="/popular/questions") }}
diff --git a/crates/app/src/public/html/timelines/popular_questions.html b/crates/app/src/public/html/timelines/popular_questions.html new file mode 100644 index 0000000..9c015d3 --- /dev/null +++ b/crates/app/src/public/html/timelines/popular_questions.html @@ -0,0 +1,18 @@ +{% extends "root.html" %} {% block head %} +Popular (questions) - {{ config.name }} +{% endblock %} {% block body %} {{ macros::nav() }} +
+ {{ macros::timelines_nav(selected="popular") }} {{ + macros::timelines_secondary_nav(posts="/popular", + questions="/popular/questions", selected="popular") }} + + +
+ {% for question in list %} + {{ components::global_question(question=question, can_manage_questions=false, secondary=true) }} + {% endfor %} + + {{ components::pagination(page=page, items=list|length) }} +
+
+{% endblock %} diff --git a/crates/app/src/routes/pages/misc.rs b/crates/app/src/routes/pages/misc.rs index 8ea30f3..992cc58 100644 --- a/crates/app/src/routes/pages/misc.rs +++ b/crates/app/src/routes/pages/misc.rs @@ -196,6 +196,44 @@ pub async fn index_questions_request( ) } +/// `/popular/questions` +pub async fn popular_questions_request( + jar: CookieJar, + Extension(data): Extension, + Query(req): Query, +) -> impl IntoResponse { + let data = data.read().await; + let user = match get_user_from_token!(jar, data.0) { + Some(ua) => ua, + None => { + return Html(render_error(Error::NotAllowed, &jar, &data, &None).await); + } + }; + + let list = match data + .0 + .get_popular_global_questions(12, req.page, 604_800_000) + .await + { + Ok(l) => match data.0.fill_questions(l).await { + Ok(l) => l, + Err(e) => return Html(render_error(e, &jar, &data, &Some(user)).await), + }, + Err(e) => return Html(render_error(e, &jar, &data, &Some(user)).await), + }; + + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0, lang, &Some(user)).await; + + context.insert("list", &list); + context.insert("page", &req.page); + Html( + data.1 + .render("timelines/popular_questions.html", &context) + .unwrap(), + ) +} + /// `/following/questions` pub async fn following_questions_request( jar: CookieJar, diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index 66eb71a..b3c2292 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -23,6 +23,7 @@ pub fn routes() -> Router { .route("/all", get(misc::all_request)) // question timelines .route("/questions", get(misc::index_questions_request)) + .route("/popular/questions", get(misc::popular_questions_request)) .route( "/following/questions", get(misc::following_questions_request), diff --git a/crates/core/src/database/questions.rs b/crates/core/src/database/questions.rs index 5c221bc..dcabc13 100644 --- a/crates/core/src/database/questions.rs +++ b/crates/core/src/database/questions.rs @@ -15,6 +15,7 @@ use crate::{auto_method, execute, get, query_row, query_rows, params}; #[cfg(feature = "sqlite")] use rusqlite::Row; +use tetratto_shared::unix_epoch_timestamp; #[cfg(feature = "postgres")] use tokio_postgres::Row; @@ -246,6 +247,42 @@ impl DataManager { Ok(res.unwrap()) } + /// Get global questions from all communities, sorted by likes. + /// + /// # Arguments + /// * `batch` - the limit of questions in each page + /// * `page` - the page number + /// * `cutoff` - the maximum number of milliseconds ago the question could have been created + pub async fn get_popular_global_questions( + &self, + batch: usize, + page: usize, + cutoff: usize, + ) -> Result> { + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = query_rows!( + &conn, + "SELECT * FROM questions WHERE is_global = 1 AND ($1 - created) < $2 ORDER BY likes DESC, created ASC LIMIT $3 OFFSET $4", + &[ + &(unix_epoch_timestamp() as i64), + &(cutoff as i64), + &(batch as i64), + &((page * batch) as i64) + ], + |x| { Self::get_question_from_row(x) } + ); + + if res.is_err() { + return Err(Error::GeneralNotFound("question".to_string())); + } + + Ok(res.unwrap()) + } + /// Create a new question in the database. /// /// # Arguments