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