From 732f514da7aa5090f5277d44fbd8feedc4c54e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Gali=C4=87?= Date: Tue, 19 Mar 2019 14:37:56 +0100 Subject: [PATCH] Refactor with the help of Clippy (#462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We add clippy as our build — also rectifying the missing `plume-cli` build! In the next step we follow clippy's advise and fix some of the "simple" mistakes in our code, such as style or map usage. Finally, we refactor some hard bits that need extraction of new types, or refactoring of function call-types, especially those that thread thru macros, and, of course functions with ~15 parameters should probably be rethought. --- .travis.yml | 26 ++- plume-cli/src/search.rs | 10 +- plume-front/src/editor.rs | 8 +- plume-front/src/main.rs | 49 +++-- plume-models/src/api_tokens.rs | 4 +- plume-models/src/lib.rs | 1 + plume-models/src/medias.rs | 4 +- plume-models/src/posts.rs | 24 +-- plume-models/src/safe_string.rs | 2 +- plume-models/src/search/mod.rs | 11 +- plume-models/src/search/query.rs | 111 +++++----- plume-models/src/search/searcher.rs | 2 +- plume-models/src/users.rs | 9 +- src/api/mod.rs | 1 + src/inbox.rs | 11 +- src/mail.rs | 1 + src/main.rs | 3 + src/routes/blogs.rs | 27 ++- src/routes/medias.rs | 2 +- src/routes/mod.rs | 41 +++- src/routes/posts.rs | 289 ++++++++++++++------------- src/routes/search.rs | 3 +- src/routes/session.rs | 10 +- src/routes/user.rs | 13 +- src/template_utils.rs | 2 +- templates/blogs/details.rs.html | 4 +- templates/instance/federated.rs.html | 2 +- templates/instance/local.rs.html | 2 +- templates/medias/details.rs.html | 4 +- templates/partials/comment.rs.html | 4 +- templates/partials/home_feed.rs.html | 2 +- templates/posts/details.rs.html | 4 +- templates/posts/new.rs.html | 2 +- templates/users/header.rs.html | 2 +- 34 files changed, 393 insertions(+), 297 deletions(-) diff --git a/.travis.yml b/.travis.yml index d1aead71..b6f79c0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,17 +27,36 @@ jobs: include: - stage: build name: "Build front" - script: (cargo web -h || cargo install cargo-web) && cd plume-front && cargo web check + script: (cargo web -h || cargo install cargo-web) && cd plume-front && cargo clippy -- -D warnings && cargo web check + before_script: rustup component add clippy + - stage: build name: "Build with postgresql" env: - MIGRATION_DIR=migrations/postgres FEATURES=postgres DATABASE_URL=postgres://postgres@localhost/plume - script: cargo build --no-default-features --features="${FEATURES}" --release + script: cargo clippy --no-default-features --features="${FEATURES}" --release -- -D warnings + before_script: rustup component add clippy + + - stage: build + name: "Build CLI with postgresql" + env: + - MIGRATION_DIR=migrations/postgres FEATURES=postgres DATABASE_URL=postgres://postgres@localhost/plume + script: cd plume-cli && cargo clippy --no-default-features --features="${FEATURES}" --release -- -D warnings + before_script: rustup component add clippy - stage: build name: "Build with sqlite" env: - MIGRATION_DIR=migrations/sqlite FEATURES=sqlite DATABASE_URL=plume.sqlite3 - script: cargo build --no-default-features --features="${FEATURES}" --release + script: cargo clippy --no-default-features --features="${FEATURES}" --release -- -D warnings + before_script: rustup component add clippy + + - stage: build + name: "Build CLI with sqlite" + env: + - MIGRATION_DIR=migrations/sqlite FEATURES=sqlite DATABASE_URL=plume.sqlite3 + script: cd plume-cli && cargo clippy --no-default-features --features="${FEATURES}" --release -- -D warnings + before_script: rustup component add clippy + - stage: test and coverage name: "Test with potgresql backend" env: @@ -48,6 +67,7 @@ jobs: - | cargo test --features "${FEATURES}" --no-default-features --all --exclude plume-front && ./script/compute_coverage.sh + - stage: test and coverage name: "Test with Sqlite backend" env: diff --git a/plume-cli/src/search.rs b/plume-cli/src/search.rs index 7083e552..f078fc99 100644 --- a/plume-cli/src/search.rs +++ b/plume-cli/src/search.rs @@ -60,13 +60,7 @@ fn init<'a>(args: &ArgMatches<'a>, conn: &Connection) { let path = Path::new(path).join("search_index"); let can_do = match read_dir(path.clone()) { // try to read the directory specified - Ok(mut contents) => { - if contents.next().is_none() { - true - } else { - false - } - }, + Ok(mut contents) => contents.next().is_none(), Err(e) => if e.kind() == ErrorKind::NotFound { true } else { @@ -107,5 +101,3 @@ fn unlock<'a>(args: &ArgMatches<'a>) { remove_file(path).unwrap(); } - - diff --git a/plume-front/src/editor.rs b/plume-front/src/editor.rs index b3ab5cea..8abfab36 100644 --- a/plume-front/src/editor.rs +++ b/plume-front/src/editor.rs @@ -72,7 +72,7 @@ fn init_widget( } widget.append_child(&document().create_text_node(&content)); if disable_return { - widget.add_event_listener(no_return); + widget.add_event_listener(no_return); } parent.append_child(&widget); @@ -128,7 +128,7 @@ pub fn init() -> Result<(), EditorError> { popup.class_list().add("show").unwrap(); bg.class_list().add("show").unwrap(); - })); + })); } Ok(()) } @@ -233,7 +233,7 @@ fn make_editable(tag: &'static str) -> Element { elt } -fn placeholder<'a>(elt: HtmlElement, text: &'a str) -> HtmlElement { +fn placeholder(elt: HtmlElement, text: &str) -> HtmlElement { elt.dataset().insert("placeholder", text).unwrap(); elt.dataset().insert("edited", "false").unwrap(); @@ -248,7 +248,7 @@ fn placeholder<'a>(elt: HtmlElement, text: &'a str) -> HtmlElement { let ph = document().create_element("span").expect("Couldn't create placeholder"); ph.class_list().add("placeholder").expect("Couldn't add class"); - ph.append_child(&document().create_text_node(&elt.dataset().get("placeholder").unwrap_or(String::new()))); + ph.append_child(&document().create_text_node(&elt.dataset().get("placeholder").unwrap_or_default())); elt.append_child(&ph); } })); diff --git a/plume-front/src/main.rs b/plume-front/src/main.rs index 30999957..96f1d5be 100644 --- a/plume-front/src/main.rs +++ b/plume-front/src/main.rs @@ -38,37 +38,34 @@ fn main() { /// It should normally be working fine even without this code /// But :focus-within is not yet supported by Webkit/Blink fn menu() { - document().get_element_by_id("menu") - .map(|button| { - document().get_element_by_id("content") - .map(|menu| { - button.add_event_listener(|_: ClickEvent| { - document().get_element_by_id("menu").map(|menu| menu.class_list().add("show")); - }); - menu.add_event_listener(|_: ClickEvent| { - document().get_element_by_id("menu").map(|menu| menu.class_list().remove("show")); - }); - }) - }); + if let Some(button) = document().get_element_by_id("menu") { + if let Some(menu) = document().get_element_by_id("content") { + button.add_event_listener(|_: ClickEvent| { + document().get_element_by_id("menu").map(|menu| menu.class_list().add("show")); + }); + menu.add_event_listener(|_: ClickEvent| { + document().get_element_by_id("menu").map(|menu| menu.class_list().remove("show")); + }); + } + } } /// Clear the URL of the search page before submitting request fn search() { - document().get_element_by_id("form") - .map(|form| { - form.add_event_listener(|_: SubmitEvent| { - document().query_selector_all("#form input").map(|inputs| { - for input in inputs { - js! { - if (@{&input}.name === "") { - @{&input}.name = @{&input}.id - } - if (@{&input}.name && !@{&input}.value) { - @{&input}.name = ""; - } + if let Some(form) = document().get_element_by_id("form") { + form.add_event_listener(|_: SubmitEvent| { + document().query_selector_all("#form input").map(|inputs| { + for input in inputs { + js! { + if (@{&input}.name === "") { + @{&input}.name = @{&input}.id + } + if (@{&input}.name && !@{&input}.value) { + @{&input}.name = ""; } } - }).ok(); - }); + } + }).ok(); }); + } } diff --git a/plume-models/src/api_tokens.rs b/plume-models/src/api_tokens.rs index b19bc126..3ec9ed91 100644 --- a/plume-models/src/api_tokens.rs +++ b/plume-models/src/api_tokens.rs @@ -90,9 +90,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApiToken { let mut parsed_header = headers[0].split(' '); let auth_type = parsed_header.next() - .map_or_else(|| Outcome::Failure((Status::BadRequest, TokenError::NoType)), |t| Outcome::Success(t))?; + .map_or_else(|| Outcome::Failure((Status::BadRequest, TokenError::NoType)), Outcome::Success)?; let val = parsed_header.next() - .map_or_else(|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)), |t| Outcome::Success(t))?; + .map_or_else(|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)), Outcome::Success)?; if auth_type == "Bearer" { let conn = request.guard::().map_failure(|_| (Status::InternalServerError, TokenError::DbError))?; diff --git a/plume-models/src/lib.rs b/plume-models/src/lib.rs index a9041394..edf6c617 100644 --- a/plume-models/src/lib.rs +++ b/plume-models/src/lib.rs @@ -1,4 +1,5 @@ #![feature(try_trait)] +#![feature(never_type)] extern crate activitypub; extern crate ammonia; diff --git a/plume-models/src/medias.rs b/plume-models/src/medias.rs index cc1ed965..37fd1e2b 100644 --- a/plume-models/src/medias.rs +++ b/plume-models/src/medias.rs @@ -70,8 +70,8 @@ impl Media { pub fn page_for_user(conn: &Connection, user: &User, (min, max): (i32, i32)) -> Result> { medias::table .filter(medias::owner_id.eq(user.id)) - .offset(min as i64) - .limit((max - min) as i64) + .offset(i64::from(min)) + .limit(i64::from(max - min)) .load::(conn) .map_err(Error::from) } diff --git a/plume-models/src/posts.rs b/plume-models/src/posts.rs index b31c7c9e..6091ca6b 100644 --- a/plume-models/src/posts.rs +++ b/plume-models/src/posts.rs @@ -127,11 +127,11 @@ impl<'a> Provider<(&'a Connection, &'a Worker, &'a Searcher, Option)> for P published: Some(p.published), creation_date: Some(p.creation_date.format("%Y-%m-%d").to_string()), license: Some(p.license.clone()), - tags: Some(Tag::for_post(conn, p.id).unwrap_or(vec![]).into_iter().map(|t| t.tag).collect()), + tags: Some(Tag::for_post(conn, p.id).unwrap_or_else(|_| vec![]).into_iter().map(|t| t.tag).collect()), cover_id: p.cover_id, }) .collect() - ).unwrap_or(vec![]) + ).unwrap_or_else(|_| vec![]) } fn update( @@ -146,7 +146,7 @@ impl<'a> Provider<(&'a Connection, &'a Worker, &'a Searcher, Option)> for P let user_id = user_id.expect("Post as Provider::delete: not authenticated"); if let Ok(post) = Post::get(conn, id) { if post.is_author(conn, user_id).unwrap_or(false) { - post.delete(&(conn, search)).ok().expect("Post as Provider::delete: delete error"); + post.delete(&(conn, search)).expect("Post as Provider::delete: delete error"); } } } @@ -168,7 +168,7 @@ impl<'a> Provider<(&'a Connection, &'a Worker, &'a Searcher, Option)> for P let domain = &Instance::get_local(&conn) .map_err(|_| ApiError::NotFound("posts::update: Error getting local instance".into()))? .public_domain; - let (content, mentions, hashtags) = md_to_html(query.source.clone().unwrap_or(String::new()).clone().as_ref(), domain); + let (content, mentions, hashtags) = md_to_html(query.source.clone().unwrap_or_default().clone().as_ref(), domain); let author = User::get(conn, user_id.expect("::create: no user_id error")) .map_err(|_| ApiError::NotFound("Author not found".into()))?; @@ -185,16 +185,16 @@ impl<'a> Provider<(&'a Connection, &'a Worker, &'a Searcher, Option)> for P let post = Post::insert(conn, NewPost { blog_id: blog, - slug: slug, - title: title, + slug, + title, content: SafeString::new(content.as_ref()), published: query.published.unwrap_or(true), - license: query.license.unwrap_or(Instance::get_local(conn) + license: query.license.unwrap_or_else(|| Instance::get_local(conn) .map(|i| i.default_license) - .unwrap_or(String::from("CC-BY-SA"))), + .unwrap_or_else(|_| String::from("CC-BY-SA"))), creation_date: date, ap_url: String::new(), - subtitle: query.subtitle.unwrap_or(String::new()), + subtitle: query.subtitle.unwrap_or_default(), source: query.source.expect("Post API::create: no source error"), cover_id: query.cover_id, }, search).map_err(|_| ApiError::NotFound("Creation error".into()))?; @@ -207,7 +207,7 @@ impl<'a> Provider<(&'a Connection, &'a Worker, &'a Searcher, Option)> for P if let Some(tags) = query.tags { for tag in tags { Tag::insert(conn, NewTag { - tag: tag, + tag, is_hashtag: false, post_id: post.id }).map_err(|_| ApiError::NotFound("Error saving tags".into()))?; @@ -311,7 +311,7 @@ impl Post { .load(conn)? .iter() .next() - .map(|x| *x) + .cloned() .ok_or(Error::NotFound) } @@ -908,7 +908,7 @@ impl<'a> FromActivity for Post .content_string()?, ), published: true, - license: license, + license, // FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields ap_url: article.object_props.url_string().or_else(|_| article diff --git a/plume-models/src/safe_string.rs b/plume-models/src/safe_string.rs index 3a923f02..2f6fcdea 100644 --- a/plume-models/src/safe_string.rs +++ b/plume-models/src/safe_string.rs @@ -24,7 +24,7 @@ lazy_static! { .url_relative(UrlRelative::Custom(Box::new(url_add_prefix))) .add_tag_attributes( "iframe", - [ "width", "height", "src", "frameborder" ].iter().map(|&v| v), + [ "width", "height", "src", "frameborder" ].iter().cloned(), ) .add_tag_attributes( "video", diff --git a/plume-models/src/search/mod.rs b/plume-models/src/search/mod.rs index 14ce88ae..83515e76 100644 --- a/plume-models/src/search/mod.rs +++ b/plume-models/src/search/mod.rs @@ -9,6 +9,7 @@ pub use self::query::PlumeQuery as Query; pub(crate) mod tests { use super::{Query, Searcher}; use std::env::temp_dir; + use std::str::FromStr; use diesel::Connection; use plume_common::activity_pub::inbox::Deletable; @@ -65,7 +66,7 @@ pub(crate) mod tests { ("after:2017-11-05 after:2018-01-01", "after:2018-01-01"), ]; for (source, res) in vector { - assert_eq!(&Query::from_str(source).to_string(), res); + assert_eq!(&Query::from_str(source).unwrap().to_string(), res); assert_eq!(Query::new().parse_query(source).to_string(), res); } } @@ -141,18 +142,18 @@ pub(crate) mod tests { }).unwrap(); searcher.commit(); - assert_eq!(searcher.search_document(conn, Query::from_str(&title), (0,1))[0].id, post.id); + assert_eq!(searcher.search_document(conn, Query::from_str(&title).unwrap(), (0,1))[0].id, post.id); let newtitle = random_hex()[..8].to_owned(); post.title = newtitle.clone(); post.update(conn, &searcher).unwrap(); searcher.commit(); - assert_eq!(searcher.search_document(conn, Query::from_str(&newtitle), (0,1))[0].id, post.id); - assert!(searcher.search_document(conn, Query::from_str(&title), (0,1)).is_empty()); + assert_eq!(searcher.search_document(conn, Query::from_str(&newtitle).unwrap(), (0,1))[0].id, post.id); + assert!(searcher.search_document(conn, Query::from_str(&title).unwrap(), (0,1)).is_empty()); post.delete(&(conn, &searcher)).unwrap(); searcher.commit(); - assert!(searcher.search_document(conn, Query::from_str(&newtitle), (0,1)).is_empty()); + assert!(searcher.search_document(conn, Query::from_str(&newtitle).unwrap(), (0,1)).is_empty()); Ok(()) }); diff --git a/plume-models/src/search/query.rs b/plume-models/src/search/query.rs index 50e49884..2984ae6c 100644 --- a/plume-models/src/search/query.rs +++ b/plume-models/src/search/query.rs @@ -87,9 +87,9 @@ macro_rules! gen_to_string { $( for (occur, val) in &$self.$field { if val.contains(' ') { - $result.push_str(&format!("{}{}:\"{}\" ", Self::occur_to_str(&occur), stringify!($field), val)); + $result.push_str(&format!("{}{}:\"{}\" ", Self::occur_to_str(*occur), stringify!($field), val)); } else { - $result.push_str(&format!("{}{}:{} ", Self::occur_to_str(&occur), stringify!($field), val)); + $result.push_str(&format!("{}{}:{} ", Self::occur_to_str(*occur), stringify!($field), val)); } } )* @@ -148,20 +148,6 @@ impl PlumeQuery { Default::default() } - /// Create a new Query from &str - /// Same as doing - /// ```rust - /// # extern crate plume_models; - /// # use plume_models::search::Query; - /// let mut q = Query::new(); - /// q.parse_query("some query"); - /// ``` - pub fn from_str(query: &str) -> Self { - let mut res: Self = Default::default(); - - res.from_str_req(&query.trim()); - res - } /// Parse a query string into this Query pub fn parse_query(&mut self, query: &str) -> &mut Self { @@ -222,35 +208,31 @@ impl PlumeQuery { } // split a string into a token and a rest - pub fn get_first_token<'a>(mut query: &'a str) -> (&'a str, &'a str) { + pub fn get_first_token(mut query: &str) -> (&str, &str) { query = query.trim(); if query.is_empty() { ("", "") - } else { - if query.get(0..1).map(|v| v=="\"").unwrap_or(false) { - if let Some(index) = query[1..].find('"') { - query.split_at(index+2) - } else { - (query, "") - } - } else if query.get(0..2).map(|v| v=="+\"" || v=="-\"").unwrap_or(false) { - if let Some(index) = query[2..].find('"') { - query.split_at(index+3) - } else { - (query, "") - } - } else { - if let Some(index) = query.find(' ') { - query.split_at(index) + } else if query.get(0..1).map(|v| v=="\"").unwrap_or(false) { + if let Some(index) = query[1..].find('"') { + query.split_at(index+2) } else { (query, "") } - } + } else if query.get(0..2).map(|v| v=="+\"" || v=="-\"").unwrap_or(false) { + if let Some(index) = query[2..].find('"') { + query.split_at(index+3) + } else { + (query, "") + } + } else if let Some(index) = query.find(' ') { + query.split_at(index) + } else { + (query, "") } } // map each Occur state to a prefix - fn occur_to_str(occur: &Occur) -> &'static str { + fn occur_to_str(occur: Occur) -> &'static str { match occur { Occur::Should => "", Occur::Must => "+", @@ -259,25 +241,28 @@ impl PlumeQuery { } // recursive parser for query string + // allow this clippy lint for now, until someone figures out how to + // refactor this better. + #[allow(clippy::wrong_self_convention)] fn from_str_req(&mut self, mut query: &str) -> &mut Self { query = query.trim_left(); if query.is_empty() { - self - } else { - let occur = if query.get(0..1).map(|v| v=="+").unwrap_or(false) { - query = &query[1..]; - Occur::Must - } else if query.get(0..1).map(|v| v=="-").unwrap_or(false) { - query = &query[1..]; - Occur::MustNot - } else { - Occur::Should - }; - gen_parser!(self, query, occur; normal: title, subtitle, content, tag, - instance, author, blog, lang, license; - date: after, before); - self.from_str_req(query) + return self } + + let occur = if query.get(0..1).map(|v| v=="+").unwrap_or(false) { + query = &query[1..]; + Occur::Must + } else if query.get(0..1).map(|v| v=="-").unwrap_or(false) { + query = &query[1..]; + Occur::MustNot + } else { + Occur::Should + }; + gen_parser!(self, query, occur; normal: title, subtitle, content, tag, + instance, author, blog, lang, license; + date: after, before); + self.from_str_req(query) } // map a token and it's field to a query @@ -290,7 +275,7 @@ impl PlumeQuery { let user_term = Term::from_field_text(field, &token[..pos]); let instance_term = Term::from_field_text(Searcher::schema().get_field("instance").unwrap(), &token[pos+1..]); Box::new(BooleanQuery::from(vec![ - (Occur::Must, Box::new(TermQuery::new(user_term, if field_name=="author" { IndexRecordOption::Basic } + (Occur::Must, Box::new(TermQuery::new(user_term, if field_name=="author" { IndexRecordOption::Basic } else { IndexRecordOption::WithFreqsAndPositions } )) as Box), (Occur::Must, Box::new(TermQuery::new(instance_term, IndexRecordOption::Basic))), @@ -320,15 +305,34 @@ impl PlumeQuery { } } +impl std::str::FromStr for PlumeQuery { + + type Err = !; + + /// Create a new Query from &str + /// Same as doing + /// ```rust + /// # extern crate plume_models; + /// # use plume_models::search::Query; + /// let mut q = Query::new(); + /// q.parse_query("some query"); + /// ``` + fn from_str(query: &str) -> Result { + let mut res: PlumeQuery = Default::default(); + + res.from_str_req(&query.trim()); + Ok(res) + } +} impl ToString for PlumeQuery { fn to_string(&self) -> String { let mut result = String::new(); for (occur, val) in &self.text { if val.contains(' ') { - result.push_str(&format!("{}\"{}\" ", Self::occur_to_str(&occur), val)); + result.push_str(&format!("{}\"{}\" ", Self::occur_to_str(*occur), val)); } else { - result.push_str(&format!("{}{} ", Self::occur_to_str(&occur), val)); + result.push_str(&format!("{}{} ", Self::occur_to_str(*occur), val)); } } @@ -340,4 +344,3 @@ impl ToString for PlumeQuery { result } } - diff --git a/plume-models/src/search/searcher.rs b/plume-models/src/search/searcher.rs index fa67eecf..295626e4 100644 --- a/plume-models/src/search/searcher.rs +++ b/plume-models/src/search/searcher.rs @@ -183,7 +183,7 @@ impl Searcher { let res = searcher.search(&query.into_query(), &collector).unwrap(); res.get(min as usize..).unwrap_or(&[]) - .into_iter() + .iter() .filter_map(|(_,doc_add)| { let doc = searcher.doc(*doc_add).ok()?; let id = doc.get_first(post_id)?; diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index 1778182a..f2510499 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -171,6 +171,9 @@ impl User { .select(post_authors::post_id) .load(conn)?; for post_id in all_their_posts_ids { + // disabling this lint, because otherwise we'd have to turn it on + // the head, and make it even harder to follow! + #[allow(clippy::op_ref)] let has_other_authors = post_authors::table .filter(post_authors::post_id.eq(post_id)) .filter(post_authors::author_id.ne(self.id)) @@ -489,7 +492,7 @@ impl User { Ok(json["items"] .as_array() .unwrap_or(&vec![]) - .into_iter() + .iter() .filter_map(|j| serde_json::from_value(j.clone()).ok()) .collect::>()) } @@ -512,7 +515,7 @@ impl User { Ok(json["items"] .as_array() .unwrap_or(&vec![]) - .into_iter() + .iter() .filter_map(|j| serde_json::from_value(j.clone()).ok()) .collect::>()) } @@ -746,7 +749,7 @@ impl User { pub fn avatar_url(&self, conn: &Connection) -> String { self.avatar_id.and_then(|id| Media::get(conn, id).and_then(|m| m.url(conn)).ok() - ).unwrap_or("/static/default-avatar.png".to_string()) + ).unwrap_or_else(|| "/static/default-avatar.png".to_string()) } pub fn webfinger(&self, conn: &Connection) -> Result { diff --git a/src/api/mod.rs b/src/api/mod.rs index c1583186..25a9d653 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,4 @@ +#![warn(clippy::too_many_arguments)] use rocket::{response::{self, Responder}, request::{Form, Request}}; use rocket_contrib::json::Json; use serde_json; diff --git a/src/inbox.rs b/src/inbox.rs index b5a9677d..8eb5898b 100644 --- a/src/inbox.rs +++ b/src/inbox.rs @@ -1,3 +1,4 @@ +#![warn(clippy::too_many_arguments)] use activitypub::{ activity::{ Announce, @@ -14,7 +15,7 @@ use failure::Error; use rocket::{ data::*, http::Status, - Outcome::{self, *}, + Outcome::*, Request, }; use rocket_contrib::json::*; @@ -129,7 +130,7 @@ pub trait Inbox { _ => Err(InboxError::CantUndo)?, } } else { - let link = act.undo_props.object.as_str().expect("Inbox::received: undo don't contain type and isn't Link"); + let link = act.undo_props.object.as_str().expect("Inbox::received: undo doesn't contain a type and isn't Link"); if let Ok(like) = likes::Like::find_by_ap_url(conn, link) { likes::Like::delete_id(&like.ap_url, actor_id.as_ref(), conn).expect("Inbox::received: delete Like error"); Ok(()) @@ -146,7 +147,7 @@ pub trait Inbox { } "Update" => { let act: Update = serde_json::from_value(act.clone())?; - Post::handle_update(conn, &act.update_props.object_object()?, searcher).expect("Inbox::received: post update error");; + Post::handle_update(conn, &act.update_props.object_object()?, searcher).expect("Inbox::received: post update error"); Ok(()) } _ => Err(InboxError::InvalidType)?, @@ -168,7 +169,7 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for SignedJson { type Owned = String; type Borrowed = str; - fn transform(r: &Request, d: Data) -> Transform> { + fn transform(r: &Request, d: Data) -> Transform> { let size_limit = r.limits().get("json").unwrap_or(JSON_LIMIT); let mut s = String::with_capacity(512); match d.open().take(size_limit).read_to_string(&mut s) { @@ -177,7 +178,7 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for SignedJson { } } - fn from_data(_: &Request, o: Transformed<'a, Self>) -> Outcome { + fn from_data(_: &Request, o: Transformed<'a, Self>) -> rocket::data::Outcome { let string = o.borrowed()?; match serde_json::from_str(&string) { Ok(v) => Success(SignedJson(Digest::from_body(&string),Json(v))), diff --git a/src/mail.rs b/src/mail.rs index 7b645cac..893927a6 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -1,3 +1,4 @@ +#![warn(clippy::too_many_arguments)] use lettre_email::Email; use std::env; diff --git a/src/main.rs b/src/main.rs index 195eaad9..16c3a914 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![allow(clippy::too_many_arguments)] #![feature(decl_macro, proc_macro_hygiene)] extern crate activitypub; @@ -83,6 +84,8 @@ fn init_pool() -> Option { fn main() { let dbpool = init_pool().expect("main: database pool initialization error"); let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get()); + // we want a fast exit here, so + #[allow(clippy::match_wild_err_arm)] let searcher = match UnmanagedSearcher::open(&"search_index") { Err(Error::Search(e)) => match e { SearcherError::WriteLockAcquisitionError => panic!( diff --git a/src/routes/blogs.rs b/src/routes/blogs.rs index 63f199b0..57fb6b5f 100644 --- a/src/routes/blogs.rs +++ b/src/routes/blogs.rs @@ -17,19 +17,20 @@ use plume_models::{ db_conn::DbConn, instance::Instance, posts::Post, - users::User }; -use routes::{Page, errors::ErrorPage}; +use routes::{Page, PlumeRocket, errors::ErrorPage}; use template_utils::Ructe; -use Searcher; #[get("/~/?", rank = 2)] -pub fn details(intl: I18n, name: String, conn: DbConn, user: Option, page: Option) -> Result { +pub fn details(name: String, page: Option, rockets: PlumeRocket) -> Result { let page = page.unwrap_or_default(); + let conn = rockets.conn; let blog = Blog::find_by_fqn(&*conn, &name)?; let posts = Post::blog_page(&*conn, &blog, page.limits())?; let articles_count = Post::count_for_blog(&*conn, &blog)?; let authors = &blog.list_authors(&*conn)?; + let user = rockets.user; + let intl = rockets.intl; Ok(render!(blogs::details( &(&*conn, &intl.catalog, user.clone()), @@ -50,7 +51,11 @@ pub fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option Ructe { +pub fn new(rockets: PlumeRocket) -> Ructe { + let user = rockets.user.unwrap(); + let intl = rockets.intl; + let conn = rockets.conn; + render!(blogs::new( &(&*conn, &intl.catalog, Some(user)), &NewBlogForm::default(), @@ -82,8 +87,11 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> { } #[post("/blogs/new", data = "
")] -pub fn create(conn: DbConn, form: LenientForm, user: User, intl: I18n) -> Result { +pub fn create(form: LenientForm, rockets: PlumeRocket) -> Result { let slug = utils::make_actor_id(&form.title); + let conn = rockets.conn; + let intl = rockets.intl; + let user = rockets.user.unwrap(); let mut errors = match form.validate() { Ok(_) => ValidationErrors::new(), @@ -122,8 +130,13 @@ pub fn create(conn: DbConn, form: LenientForm, user: User, intl: I1 } #[post("/~//delete")] -pub fn delete(conn: DbConn, name: String, user: Option, intl: I18n, searcher: Searcher) -> Result{ +pub fn delete(name: String, rockets: PlumeRocket) -> Result{ + let conn = rockets.conn; let blog = Blog::find_by_fqn(&*conn, &name).expect("blog::delete: blog not found"); + let user = rockets.user; + let intl = rockets.intl; + let searcher = rockets.searcher; + if user.clone().and_then(|u| u.is_author_in(&*conn, &blog).ok()).unwrap_or(false) { blog.delete(&conn, &searcher).expect("blog::expect: deletion error"); Ok(Redirect::to(uri!(super::instance::index))) diff --git a/src/routes/medias.rs b/src/routes/medias.rs index aea2e733..097b0218 100644 --- a/src/routes/medias.rs +++ b/src/routes/medias.rs @@ -35,7 +35,7 @@ pub fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Result< SaveResult::Full(entries) => { let fields = entries.fields; - let filename = fields.get("file").and_then(|v| v.into_iter().next()) + let filename = fields.get("file").and_then(|v| v.iter().next()) .ok_or_else(|| status::BadRequest(Some("No file uploaded")))?.headers .filename.clone(); // Remove extension if it contains something else than just letters and numbers diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 0fb2b190..0aa402c3 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,3 +1,4 @@ +#![warn(clippy::too_many_arguments)] use atom_syndication::{ContentBuilder, Entry, EntryBuilder, LinkBuilder, Person, PersonBuilder}; use rocket::{ http::{ @@ -8,9 +9,45 @@ use rocket::{ request::{self, FromFormValue, FromRequest, Request}, response::NamedFile, }; +use rocket_i18n::I18n; use std::path::{Path, PathBuf}; -use plume_models::{Connection, posts::Post}; +use plume_models::{ + Connection, + users::User, + posts::Post, + db_conn::DbConn, +}; + +use Worker; +use Searcher; + +pub struct PlumeRocket<'a> { + conn: DbConn, + intl: I18n, + user: Option, + searcher: Searcher<'a>, + worker: Worker<'a>, +} + +impl<'a, 'r> FromRequest<'a, 'r> for PlumeRocket<'a> { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> request::Outcome, ()> { + let conn = request.guard::()?; + let intl = request.guard::()?; + let user = request.guard::().succeeded(); + let worker = request.guard::()?; + let searcher = request.guard::()?; + rocket::Outcome::Success(PlumeRocket { + conn, + intl, + user, + worker, + searcher, + }) + } +} const ITEMS_PER_PAGE: i32 = 12; @@ -45,7 +82,7 @@ impl Page { } } - pub fn limits(&self) -> (i32, i32) { + pub fn limits(self) -> (i32, i32) { ((self.0 - 1) * ITEMS_PER_PAGE, self.0 * ITEMS_PER_PAGE) } } diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 2dc33056..380c65d8 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -24,10 +24,8 @@ use plume_models::{ tags::*, users::User }; -use routes::{errors::ErrorPage, comments::NewCommentForm, ContentLen}; +use routes::{PlumeRocket, errors::ErrorPage, comments::NewCommentForm, ContentLen}; use template_utils::Ructe; -use Worker; -use Searcher; #[get("/~//?", rank = 4)] pub fn details(blog: String, slug: String, conn: DbConn, user: Option, responding_to: Option, intl: I18n) -> Result { @@ -103,86 +101,96 @@ pub fn new_auth(blog: String, i18n: I18n) -> Flash { } #[get("/~//new", rank = 1)] -pub fn new(blog: String, user: User, cl: ContentLen, conn: DbConn, intl: I18n) -> Result { +pub fn new(blog: String, cl: ContentLen, rockets: PlumeRocket) -> Result { + let conn = rockets.conn; let b = Blog::find_by_fqn(&*conn, &blog)?; + let user = rockets.user.unwrap(); + let intl = rockets.intl; if !user.is_author_in(&*conn, &b)? { // TODO actually return 403 error code - Ok(render!(errors::not_authorized( + return Ok(render!(errors::not_authorized( &(&*conn, &intl.catalog, Some(user)), i18n!(intl.catalog, "You are not author in this blog.") ))) - } else { - let medias = Media::for_user(&*conn, user.id)?; - Ok(render!(posts::new( - &(&*conn, &intl.catalog, Some(user)), - i18n!(intl.catalog, "New post"), - b, - false, - &NewPostForm { - license: Instance::get_local(&*conn)?.default_license, - ..NewPostForm::default() - }, - true, - None, - ValidationErrors::default(), - medias, - cl.0 - ))) } + + let medias = Media::for_user(&*conn, user.id)?; + Ok(render!(posts::new( + &(&*conn, &intl.catalog, Some(user)), + i18n!(intl.catalog, "New post"), + b, + false, + &NewPostForm { + license: Instance::get_local(&*conn)?.default_license, + ..NewPostForm::default() + }, + true, + None, + ValidationErrors::default(), + medias, + cl.0 + ))) } #[get("/~///edit")] -pub fn edit(blog: String, slug: String, user: User, cl: ContentLen, conn: DbConn, intl: I18n) -> Result { +pub fn edit(blog: String, slug: String, cl: ContentLen, rockets: PlumeRocket) -> Result { + let conn = rockets.conn; + let intl = rockets.intl; let b = Blog::find_by_fqn(&*conn, &blog)?; let post = Post::find_by_slug(&*conn, &slug, b.id)?; + let user = rockets.user.unwrap(); if !user.is_author_in(&*conn, &b)? { - Ok(render!(errors::not_authorized( + return Ok(render!(errors::not_authorized( &(&*conn, &intl.catalog, Some(user)), i18n!(intl.catalog, "You are not author in this blog.") ))) - } else { - let source = if !post.source.is_empty() { - post.source.clone() - } else { - post.content.get().clone() // fallback to HTML if the markdown was not stored - }; - - let medias = Media::for_user(&*conn, user.id)?; - let title = post.title.clone(); - Ok(render!(posts::new( - &(&*conn, &intl.catalog, Some(user)), - i18n!(intl.catalog, "Edit {0}"; &title), - b, - true, - &NewPostForm { - title: post.title.clone(), - subtitle: post.subtitle.clone(), - content: source, - tags: Tag::for_post(&*conn, post.id)? - .into_iter() - .filter_map(|t| if !t.is_hashtag {Some(t.tag)} else {None}) - .collect::>() - .join(", "), - license: post.license.clone(), - draft: true, - cover: post.cover_id, - }, - !post.published, - Some(post), - ValidationErrors::default(), - medias, - cl.0 - ))) } + + + let source = if !post.source.is_empty() { + post.source.clone() + } else { + post.content.get().clone() // fallback to HTML if the markdown was not stored + }; + + let medias = Media::for_user(&*conn, user.id)?; + let title = post.title.clone(); + Ok(render!(posts::new( + &(&*conn, &intl.catalog, Some(user)), + i18n!(intl.catalog, "Edit {0}"; &title), + b, + true, + &NewPostForm { + title: post.title.clone(), + subtitle: post.subtitle.clone(), + content: source, + tags: Tag::for_post(&*conn, post.id)? + .into_iter() + .filter_map(|t| if !t.is_hashtag {Some(t.tag)} else {None}) + .collect::>() + .join(", "), + license: post.license.clone(), + draft: true, + cover: post.cover_id, + }, + !post.published, + Some(post), + ValidationErrors::default(), + medias, + cl.0 + ))) } #[post("/~///edit", data = "")] -pub fn update(blog: String, slug: String, user: User, cl: ContentLen, form: LenientForm, worker: Worker, conn: DbConn, intl: I18n, searcher: Searcher) +pub fn update(blog: String, slug: String, cl: ContentLen, form: LenientForm, rockets: PlumeRocket) -> Result { + let conn = rockets.conn; let b = Blog::find_by_fqn(&*conn, &blog).expect("post::update: blog error"); let mut post = Post::find_by_slug(&*conn, &slug, b.id).expect("post::update: find by slug error"); + let user = rockets.user.unwrap(); + let intl = rockets.intl; let new_slug = if !post.published { form.title.to_string().to_kebab_case() @@ -219,6 +227,8 @@ pub fn update(blog: String, slug: String, user: User, cl: ContentLen, form: Leni false }; + let searcher = rockets.searcher; + let worker = rockets.worker; post.slug = new_slug.clone(); post.title = form.title.clone(); post.subtitle = form.subtitle.clone(); @@ -263,7 +273,7 @@ pub fn update(blog: String, slug: String, user: User, cl: ContentLen, form: Leni b, true, &*form, - form.draft.clone(), + form.draft, Some(post), errors.clone(), medias.clone(), @@ -296,9 +306,11 @@ pub fn valid_slug(title: &str) -> Result<(), ValidationError> { } #[post("/~//new", data = "")] -pub fn create(blog_name: String, form: LenientForm, user: User, cl: ContentLen, conn: DbConn, worker: Worker, intl: I18n, searcher: Searcher) -> Result> { +pub fn create(blog_name: String, form: LenientForm, cl: ContentLen, rockets: PlumeRocket) -> Result> { + let conn = rockets.conn; let blog = Blog::find_by_fqn(&*conn, &blog_name).expect("post::create: blog error");; let slug = form.title.to_string().to_kebab_case(); + let user = rockets.user.unwrap(); let mut errors = match form.validate() { Ok(_) => ValidationErrors::new(), @@ -315,73 +327,76 @@ pub fn create(blog_name: String, form: LenientForm, user: User, cl: if errors.is_empty() { if !user.is_author_in(&*conn, &blog).expect("post::create: is author in error") { // actually it's not "Ok"… - Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _))) - } else { - let (content, mentions, hashtags) = utils::md_to_html( - form.content.to_string().as_ref(), - &Instance::get_local(&conn).expect("post::create: local instance error").public_domain - ); - - let post = Post::insert(&*conn, NewPost { - blog_id: blog.id, - slug: slug.to_string(), - title: form.title.to_string(), - content: SafeString::new(&content), - published: !form.draft, - license: form.license.clone(), - ap_url: "".to_string(), - creation_date: None, - subtitle: form.subtitle.clone(), - source: form.content.clone(), - cover_id: form.cover, - }, - &searcher, - ).expect("post::create: post save error"); - - PostAuthor::insert(&*conn, NewPostAuthor { - post_id: post.id, - author_id: user.id - }).expect("post::create: author save error"); - - let tags = form.tags.split(',') - .map(|t| t.trim().to_camel_case()) - .filter(|t| !t.is_empty()) - .collect::>(); - for tag in tags { - Tag::insert(&*conn, NewTag { - tag, - is_hashtag: false, - post_id: post.id - }).expect("post::create: tags save error"); - } - for hashtag in hashtags { - Tag::insert(&*conn, NewTag { - tag: hashtag.to_camel_case(), - is_hashtag: true, - post_id: post.id - }).expect("post::create: hashtags save error"); - } - - if post.published { - for m in mentions { - Mention::from_activity( - &*conn, - &Mention::build_activity(&*conn, &m).expect("post::create: mention build error"), - post.id, - true, - true - ).expect("post::create: mention save error"); - } - - let act = post.create_activity(&*conn).expect("posts::create: activity error"); - let dest = User::one_by_instance(&*conn).expect("posts::create: dest error"); - worker.execute(move || broadcast(&user, act, dest)); - } - - Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug, responding_to = _))) + return Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _))) } + + let (content, mentions, hashtags) = utils::md_to_html( + form.content.to_string().as_ref(), + &Instance::get_local(&conn).expect("post::create: local instance error").public_domain + ); + + let searcher = rockets.searcher; + let post = Post::insert(&*conn, NewPost { + blog_id: blog.id, + slug: slug.to_string(), + title: form.title.to_string(), + content: SafeString::new(&content), + published: !form.draft, + license: form.license.clone(), + ap_url: "".to_string(), + creation_date: None, + subtitle: form.subtitle.clone(), + source: form.content.clone(), + cover_id: form.cover, + }, + &searcher, + ).expect("post::create: post save error"); + + PostAuthor::insert(&*conn, NewPostAuthor { + post_id: post.id, + author_id: user.id + }).expect("post::create: author save error"); + + let tags = form.tags.split(',') + .map(|t| t.trim().to_camel_case()) + .filter(|t| !t.is_empty()) + .collect::>(); + for tag in tags { + Tag::insert(&*conn, NewTag { + tag, + is_hashtag: false, + post_id: post.id + }).expect("post::create: tags save error"); + } + for hashtag in hashtags { + Tag::insert(&*conn, NewTag { + tag: hashtag.to_camel_case(), + is_hashtag: true, + post_id: post.id + }).expect("post::create: hashtags save error"); + } + + if post.published { + for m in mentions { + Mention::from_activity( + &*conn, + &Mention::build_activity(&*conn, &m).expect("post::create: mention build error"), + post.id, + true, + true + ).expect("post::create: mention save error"); + } + + let act = post.create_activity(&*conn).expect("posts::create: activity error"); + let dest = User::one_by_instance(&*conn).expect("posts::create: dest error"); + let worker = rockets.worker; + worker.execute(move || broadcast(&user, act, dest)); + } + + Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug, responding_to = _))) } else { let medias = Media::for_user(&*conn, user.id).expect("posts::create: medias error"); + let intl = rockets.intl; Err(Ok(render!(posts::new( &(&*conn, &intl.catalog, Some(user)), i18n!(intl.catalog, "New post"), @@ -398,22 +413,28 @@ pub fn create(blog_name: String, form: LenientForm, user: User, cl: } #[post("/~///delete")] -pub fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: Worker, searcher: Searcher) -> Result { +pub fn delete(blog_name: String, slug: String, rockets: PlumeRocket) -> Result { + let conn = rockets.conn; + let user = rockets.user.unwrap(); let post = Blog::find_by_fqn(&*conn, &blog_name) .and_then(|blog| Post::find_by_slug(&*conn, &slug, blog.id)); if let Ok(post) = post { if !post.get_authors(&*conn)?.into_iter().any(|a| a.id == user.id) { - Ok(Redirect::to(uri!(details: blog = blog_name.clone(), slug = slug.clone(), responding_to = _))) - } else { - let dest = User::one_by_instance(&*conn)?; - let delete_activity = post.delete(&(&conn, &searcher))?; - let user_c = user.clone(); - worker.execute(move || broadcast(&user_c, delete_activity, dest)); - worker.execute_after(Duration::from_secs(10*60), move || {user.rotate_keypair(&conn).expect("Failed to rotate keypair");}); - - Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _))) + return Ok(Redirect::to(uri!(details: blog = blog_name.clone(), slug = slug.clone(), responding_to = _))) } + + let searcher = rockets.searcher; + let worker = rockets.worker; + + let dest = User::one_by_instance(&*conn)?; + let delete_activity = post.delete(&(&conn, &searcher))?; + let user_c = user.clone(); + + worker.execute(move || broadcast(&user_c, delete_activity, dest)); + worker.execute_after(Duration::from_secs(10*60), move || {user.rotate_keypair(&conn).expect("Failed to rotate keypair");}); + + Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _))) } else { Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _))) } diff --git a/src/routes/search.rs b/src/routes/search.rs index 2bbb10c3..7ddafb69 100644 --- a/src/routes/search.rs +++ b/src/routes/search.rs @@ -8,6 +8,7 @@ use plume_models::{ use routes::Page; use template_utils::Ructe; use Searcher; +use std::str::FromStr; #[derive(Default, FromForm)] pub struct SearchQuery { @@ -57,7 +58,7 @@ macro_rules! param_to_query { pub fn search(query: Option>, conn: DbConn, searcher: Searcher, user: Option, intl: I18n) -> Ructe { let query = query.map(|f| f.into_inner()).unwrap_or_default(); let page = query.page.unwrap_or_default(); - let mut parsed_query = Query::from_str(&query.q.as_ref().map(|q| q.as_str()).unwrap_or_default()); + let mut parsed_query = Query::from_str(&query.q.as_ref().map(|q| q.as_str()).unwrap_or_default()).unwrap_or_default(); param_to_query!(query, parsed_query; normal: title, subtitle, content, tag, instance, author, blog, lang, license; diff --git a/src/routes/session.rs b/src/routes/session.rs index c7f16f9c..6157ed8e 100644 --- a/src/routes/session.rs +++ b/src/routes/session.rs @@ -161,10 +161,10 @@ pub fn password_reset_request( i18n!(intl.catalog, "Password reset"), i18n!(intl.catalog, "Here is the link to reset your password: {0}"; link) ) { - match *mail.lock().unwrap() { - Some(ref mut mail) => { mail.send(message.into()).map_err(|_| eprintln!("Couldn't send password reset mail")).ok(); } - None => {} - } + if let Some(ref mut mail) = *mail.lock().unwrap() { + mail + .send(message.into()) + .map_err(|_| eprintln!("Couldn't send password reset mail")).ok(); } } } render!(session::password_reset_request_ok( @@ -214,7 +214,7 @@ pub fn password_reset( form.validate() .and_then(|_| { let mut requests = requests.lock().unwrap(); - let req = requests.iter().find(|x| x.id == token.clone()).ok_or(to_validation(0))?.clone(); + let req = requests.iter().find(|x| x.id == token.clone()).ok_or_else(|| to_validation(0))?.clone(); if req.creation_date.elapsed().as_secs() < 60 * 60 * 2 { // Reset link is only valid for 2 hours requests.retain(|r| *r != req); let user = User::find_by_email(&*conn, &req.mail).map_err(to_validation)?; diff --git a/src/routes/user.rs b/src/routes/user.rs index 023446de..018364bc 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -23,7 +23,7 @@ use plume_models::{ blogs::Blog, db_conn::DbConn, follows, headers::Headers, instance::Instance, posts::{LicensedArticle, Post}, reshares::Reshare, users::*, }; -use routes::{Page, errors::ErrorPage}; +use routes::{Page, PlumeRocket, errors::ErrorPage}; use template_utils::Ructe; use Worker; use Searcher; @@ -39,18 +39,17 @@ pub fn me(user: Option) -> Result> { #[get("/@/", rank = 2)] pub fn details( name: String, - conn: DbConn, - account: Option, - worker: Worker, + rockets: PlumeRocket, fetch_articles_conn: DbConn, fetch_followers_conn: DbConn, update_conn: DbConn, - intl: I18n, - searcher: Searcher, ) -> Result { + let conn = rockets.conn; let user = User::find_by_fqn(&*conn, &name)?; let recents = Post::get_recents_for_author(&*conn, &user, 6)?; let reshares = Reshare::get_recents_for_author(&*conn, &user, 6)?; + let searcher = rockets.searcher; + let worker = rockets.worker; if !user.get_instance(&*conn)?.local { // Fetch new articles @@ -99,6 +98,8 @@ pub fn details( } } + let account = rockets.user; + let intl = rockets.intl; Ok(render!(users::details( &(&*conn, &intl.catalog, account.clone()), user.clone(), diff --git a/src/template_utils.rs b/src/template_utils.rs index 854d2dac..5b407607 100644 --- a/src/template_utils.rs +++ b/src/template_utils.rs @@ -27,7 +27,7 @@ impl<'r> Responder<'r> for Ructe { let mut hasher = DefaultHasher::new(); hasher.write(&self.0); let etag = format!("{:x}", hasher.finish()); - if r.headers().get("If-None-Match").any(|s| &s[1..s.len()-1] == etag) { + if r.headers().get("If-None-Match").any(|s| s[1..s.len()-1] == etag) { Response::build() .status(Status::NotModified) .header(ETag(EntityTag::strong(etag))) diff --git a/templates/blogs/details.rs.html b/templates/blogs/details.rs.html index f1403b70..1f50329d 100644 --- a/templates/blogs/details.rs.html +++ b/templates/blogs/details.rs.html @@ -5,7 +5,7 @@ @use template_utils::*; @use routes::*; -@(ctx: BaseContext, blog: Blog, authors: &Vec, total_articles: i64, page: i32, n_pages: i32, is_author: bool, posts: Vec) +@(ctx: BaseContext, blog: Blog, authors: &[User], total_articles: i64, page: i32, n_pages: i32, is_author: bool, posts: Vec) @:base(ctx, blog.title.clone(), {}, { @blog.title @@ -36,7 +36,7 @@ @i18n!(ctx.1, "Latest articles") @icon!("rss") - @if posts.len() < 1 { + @if posts.is_empty() {

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

} @if is_author { diff --git a/templates/instance/federated.rs.html b/templates/instance/federated.rs.html index 49194d2a..7c1510b2 100644 --- a/templates/instance/federated.rs.html +++ b/templates/instance/federated.rs.html @@ -9,7 +9,7 @@

@i18n!(ctx.1, "All the articles of the Fediverse")

- @if let Some(_) = ctx.2 { + @if ctx.2.is_some() { @tabs(&[ (&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false), (&uri!(instance::feed: _).to_string(), i18n!(ctx.1, "Your feed"), false), diff --git a/templates/instance/local.rs.html b/templates/instance/local.rs.html index 2ba74ba9..919181f4 100644 --- a/templates/instance/local.rs.html +++ b/templates/instance/local.rs.html @@ -10,7 +10,7 @@

@i18n!(ctx.1, "Articles from {}"; instance.name)

- @if let Some(_) = ctx.2 { + @if ctx.2.is_some() { @tabs(&[ (&uri!(instance::index).to_string(), i18n!(ctx.1, "Latest articles"), false), (&uri!(instance::feed: _).to_string(), i18n!(ctx.1, "Your feed"), false), diff --git a/templates/medias/details.rs.html b/templates/medias/details.rs.html index 3974b972..323f6031 100644 --- a/templates/medias/details.rs.html +++ b/templates/medias/details.rs.html @@ -14,7 +14,7 @@
- @Html(media.html(ctx.0).unwrap_or(SafeString::new(""))) + @Html(media.html(ctx.0).unwrap_or_else(|_| SafeString::new("")))
@media.alt_text
@@ -22,7 +22,7 @@ @i18n!(ctx.1, "Markdown syntax") @i18n!(ctx.1, "Copy it into your articles, to insert this media:")

- @media.markdown(ctx.0).unwrap_or(SafeString::new("")) + @media.markdown(ctx.0).unwrap_or_else(|_| SafeString::new(""))
@if media.category() == MediaCategory::Image { diff --git a/templates/partials/comment.rs.html b/templates/partials/comment.rs.html index fba02d39..2dd0468c 100644 --- a/templates/partials/comment.rs.html +++ b/templates/partials/comment.rs.html @@ -5,12 +5,12 @@ @(ctx: BaseContext, comment_tree: &CommentTree, in_reply_to: Option<&str>, blog: &str, slug: &str) @if let Some(ref comm) = Some(&comment_tree.comment) { -@if let Some(author) = comm.get_author(ctx.0).ok() { +@if let Ok(author) = comm.get_author(ctx.0) {
@avatar(ctx.0, &author, Size::Small, true, ctx.1) @author.name() - @&author.fqn + @author.fqn @if let Some(ref ap_url) = comm.ap_url { diff --git a/templates/partials/home_feed.rs.html b/templates/partials/home_feed.rs.html index b39b628a..f3a4f204 100644 --- a/templates/partials/home_feed.rs.html +++ b/templates/partials/home_feed.rs.html @@ -4,7 +4,7 @@ @(ctx: BaseContext, articles: Vec, link: &str, title: String) -@if articles.len() > 0 { +@if !articles.is_empty() {

@title@i18n!(ctx.1, "View all")

diff --git a/templates/posts/details.rs.html b/templates/posts/details.rs.html index 9044c37f..702ce6cb 100644 --- a/templates/posts/details.rs.html +++ b/templates/posts/details.rs.html @@ -23,8 +23,8 @@ @blog.title }, {
-

@&article.title

-

@&article.subtitle

+

@article.title

+

@article.subtitle