diff --git a/.circleci/config.yml b/.circleci/config.yml index cd7f2d06..d6b208ec 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,7 +11,7 @@ executors: default: false docker: - image: plumeorg/plume-buildenv:v0.4.0 - - image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<><<^parameters.postgres>>alpine:latest<> + - image: <<#parameters.postgres>>cimg/postgres:14.2<><<^parameters.postgres>>alpine:latest<> environment: POSTGRES_USER: postgres POSTGRES_DB: plume diff --git a/Cargo.toml b/Cargo.toml index ee6c4ba1..198f3671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ rocket_contrib = { version = "0.4.5", features = ["json"] } rocket_i18n = "0.4.1" scheduled-thread-pool = "0.2.2" serde = "1.0" -serde_json = "1.0.80" +serde_json = "1.0.81" shrinkwraprs = "0.3.0" validator = { version = "0.15", features = ["derive"] } webfinger = "0.4.1" diff --git a/assets/themes/default/_global.scss b/assets/themes/default/_global.scss index fe92d062..23f6acec 100644 --- a/assets/themes/default/_global.scss +++ b/assets/themes/default/_global.scss @@ -507,6 +507,7 @@ figure { margin: auto $horizontal-margin 2em; overflow: auto; display: flex; + justify-content: center; a { display: inline-block; diff --git a/plume-common/Cargo.toml b/plume-common/Cargo.toml index 4539dba0..65934a7c 100644 --- a/plume-common/Cargo.toml +++ b/plume-common/Cargo.toml @@ -9,12 +9,12 @@ array_tool = "1.0" base64 = "0.13" heck = "0.4.0" hex = "0.4" -openssl = "0.10.22" +openssl = "0.10.40" rocket = "0.4.6" reqwest = { version = "0.11.10", features = ["blocking", "json", "socks"] } serde = "1.0" serde_derive = "1.0" -serde_json = "1.0.80" +serde_json = "1.0.81" shrinkwraprs = "0.3.0" syntect = "4.5.0" regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] } diff --git a/plume-common/src/activity_pub/mod.rs b/plume-common/src/activity_pub/mod.rs index 73632eed..cebaa924 100644 --- a/plume-common/src/activity_pub/mod.rs +++ b/plume-common/src/activity_pub/mod.rs @@ -157,6 +157,9 @@ where .build() .expect("Error while initializing tokio runtime for federation"); rt.block_on(async { + // TODO: should be determined dependent on database connections because + // after broadcasting, target instance sends request to this instance, + // and Plume accesses database at that time. let capacity = 6; let (tx, rx) = flume::bounded::(capacity); let mut handles = Vec::with_capacity(capacity); @@ -206,7 +209,7 @@ where .expect("activity_pub::broadcast: request signature error"), ); let request_builder = client.post(&inbox).headers(headers.clone()).body(body); - tx.send_async(request_builder).await.unwrap(); + let _ = tx.send_async(request_builder).await; } drop(tx); join_all(handles).await; diff --git a/plume-models/Cargo.toml b/plume-models/Cargo.toml index 6436e739..9d968530 100644 --- a/plume-models/Cargo.toml +++ b/plume-models/Cargo.toml @@ -12,14 +12,14 @@ itertools = "0.10.3" lazy_static = "1.0" ldap3 = "0.10.4" migrations_internals= "1.4.0" -openssl = "0.10.22" +openssl = "0.10.40" rocket = "0.4.6" rocket_i18n = "0.4.1" reqwest = "0.11.10" scheduled-thread-pool = "0.2.2" serde = "1.0" serde_derive = "1.0" -serde_json = "1.0.80" +serde_json = "1.0.81" tantivy = "0.13.3" url = "2.1" walkdir = "2.2" diff --git a/plume-models/src/timeline/mod.rs b/plume-models/src/timeline/mod.rs index d6b2a59d..8f80310c 100644 --- a/plume-models/src/timeline/mod.rs +++ b/plume-models/src/timeline/mod.rs @@ -6,6 +6,7 @@ use crate::{ Connection, Error, Result, }; use diesel::{self, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl}; +use std::cmp::Ordering; use std::ops::Deref; pub(crate) mod query; @@ -85,6 +86,16 @@ impl Timeline { .or(timeline_definition::user_id.is_null()), ) .load::(conn) + .map(|mut timelines| { + timelines.sort_by(|t1, t2| { + if t1.user_id.is_some() && t2.user_id.is_none() { + Ordering::Less + } else { + Ordering::Equal + } + }); + timelines + }) .map_err(Error::from) } else { timeline_definition::table diff --git a/src/routes/errors.rs b/src/routes/errors.rs index 74dc4dd5..8d69d0b5 100644 --- a/src/routes/errors.rs +++ b/src/routes/errors.rs @@ -21,8 +21,9 @@ impl<'r> Responder<'r> for ErrorPage { warn!("{:?}", self.0); match self.0 { - Error::NotFound => Err(Status::NotFound), - Error::Unauthorized => Err(Status::NotFound), + Error::NotFound | Error::Unauthorized | Error::Db(diesel::result::Error::NotFound) => { + Err(Status::NotFound) + } _ => Err(Status::InternalServerError), } } diff --git a/src/routes/instance.rs b/src/routes/instance.rs index cedaa900..526bde31 100644 --- a/src/routes/instance.rs +++ b/src/routes/instance.rs @@ -28,25 +28,26 @@ use plume_models::{ #[get("/")] pub fn index(conn: DbConn, rockets: PlumeRocket) -> Result { - let inst = Instance::get_local()?; - let timelines = Timeline::list_all_for_user(&conn, rockets.user.clone().map(|u| u.id))? - .into_iter() - .filter_map(|t| { - if let Ok(latest) = t.get_latest(&conn, 12) { - Some((t, latest)) - } else { - None - } - }) - .collect(); - - Ok(render!(instance::index( - &(&conn, &rockets).to_context(), - inst, - User::count_local(&conn)?, - Post::count_local(&conn)?, - timelines - ))) + let all_tl = Timeline::list_all_for_user(&conn, rockets.user.clone().map(|u| u.id))?; + if all_tl.is_empty() { + Err(Error::NotFound.into()) + } else { + let inst = Instance::get_local()?; + let page = Page::default(); + let tl = &all_tl[0]; + let posts = tl.get_page(&conn, page.limits())?; + let total_posts = tl.count_posts(&conn)?; + Ok(render!(instance::index( + &(&conn, &rockets).to_context(), + inst, + User::count_local(&conn)?, + Post::count_local(&conn)?, + tl.id, + posts, + all_tl, + Page::total(total_posts as i32) + ))) + } } #[get("/admin")] diff --git a/src/routes/mod.rs b/src/routes/mod.rs old mode 100755 new mode 100644 index b239abec..5ea53c96 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -59,6 +59,12 @@ impl From> for RespondOrRedirect { #[derive(Shrinkwrap, Copy, Clone, UriDisplayQuery)] pub struct Page(i32); +impl From for Page { + fn from(page: i32) -> Self { + Self(page) + } +} + impl<'v> FromFormValue<'v> for Page { type Error = &'v RawStr; fn from_form_value(form_value: &'v RawStr) -> Result { diff --git a/templates/instance/index.rs.html b/templates/instance/index.rs.html index 0361318e..4e25401a 100644 --- a/templates/instance/index.rs.html +++ b/templates/instance/index.rs.html @@ -4,37 +4,37 @@ @use crate::templates::{base, partials::*}; @use crate::template_utils::*; @use crate::routes::*; +@use rocket::uri; -@(ctx: BaseContext, instance: Instance, n_users: i64, n_articles: i64, all_tl: Vec<(Timeline, Vec)>) +@(ctx: BaseContext, instance: Instance, n_users: i64, n_articles: i64, tl_id: i32, articles: Vec, all_tl: Vec, n_pages: i32) @:base(ctx, instance.name.clone(), {}, {}, { -

@i18n!(ctx.1, "Welcome to {}"; instance.name.as_str())

+
+

@i18n!(ctx.1, "Welcome to {}"; instance.name.as_str())

+
- @tabs(&vec![(format!("{}", uri!(instance::index)), i18n!(ctx.1, "Latest articles"), true)] - .into_iter().chain(all_tl.clone() + @tabs(&all_tl .into_iter() - .map(|(tl, _)| { - let url = format!("{}", uri!(timelines::details: id = tl.id, page = _)); - (url, i18n_timeline_name(ctx.1, &tl.name), false) + .map(|t| { + let url = format!("{}", uri!(timelines::details: id = t.id, page = _)); + (url, i18n_timeline_name(ctx.1, &t.name), t.id == tl_id) }) - ).collect::>() + .collect::>() ) - @for (tl, articles) in all_tl { - @if !articles.is_empty() { -
-

- @i18n_timeline_name(ctx.1, &tl.name) - — - @i18n!(ctx.1, "View all") -

-
- @for article in articles { - @:post_card(ctx, article) - } -
-
- } + @if !articles.is_empty() { +
+ @for article in articles { + @:post_card(ctx, article) + } +
+ } else { +

@i18n!(ctx.1, "Nothing to see here yet.")

+ } + @if n_pages > 1 { + } @:instance_description(ctx, instance, n_users, n_articles) diff --git a/templates/timelines/details.rs.html b/templates/timelines/details.rs.html index 916b4f76..d745a4ba 100644 --- a/templates/timelines/details.rs.html +++ b/templates/timelines/details.rs.html @@ -8,21 +8,20 @@ @(ctx: BaseContext, tl: Timeline, articles: Vec, all_tl: Vec, page: i32, n_pages: i32) @:base(ctx, tl.name.clone(), {}, {}, { -
-

@i18n_timeline_name(ctx.1, &tl.name)

-
+
+

@i18n_timeline_name(ctx.1, &tl.name)

+
- @tabs(&vec![(format!("{}", uri!(instance::index)), i18n!(ctx.1, "Latest articles"), false)] - .into_iter().chain(all_tl + @tabs(&all_tl .into_iter() .map(|t| { - let url = format!("{}", uri!(timelines::details: id = t.id, page = _)); + let url = format!("{}", uri!(timelines::details: id = t.id, page = _)); (url, i18n_timeline_name(ctx.1, &t.name), t.id == tl.id) }) - ).collect::>() + .collect::>() ) - @if !articles.is_empty() { + @if !articles.is_empty() {
@for article in articles { @:post_card(ctx, article)