Run cargo clippy on whole project (#322)
* Run cargo clippy on plume-common Run clippy on plume-common and adjuste code accordingly * Run cargo clippy on plume-model Run clippy on plume-model and adjuste code accordingly * Reduce need for allocation in plume-common * Reduce need for allocation in plume-model add a quick compilation failure if no database backend is enabled * Run cargo clippy on plume-cli * Run cargo clippy on plume
This commit is contained in:
		
							parent
							
								
									8a4702df92
								
							
						
					
					
						commit
						74c398d60c
					
				| @ -46,12 +46,12 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) { | ||||
|         .unwrap_or_else(|| env::var("BASE_URL") | ||||
|             .unwrap_or_else(|_| super::ask_for("Domain name"))); | ||||
|     let name = args.value_of("name").map(String::from).unwrap_or_else(|| super::ask_for("Instance name")); | ||||
|     let license = args.value_of("default-license").map(String::from).unwrap_or(String::from("CC-BY-SA")); | ||||
|     let license = args.value_of("default-license").map(String::from).unwrap_or_else(|| String::from("CC-BY-SA")); | ||||
|     let open_reg = !args.is_present("private"); | ||||
| 
 | ||||
|     Instance::insert(conn, NewInstance { | ||||
|         public_domain: domain, | ||||
|         name: name, | ||||
|         name, | ||||
|         local: true, | ||||
|         long_description: SafeString::new(""), | ||||
|         short_description: SafeString::new(""), | ||||
|  | ||||
| @ -70,8 +70,8 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) { | ||||
|         username, | ||||
|         display_name, | ||||
|         admin, | ||||
|         bio, | ||||
|         &bio, | ||||
|         email, | ||||
|         User::hash_pass(password), | ||||
|         User::hash_pass(&password), | ||||
|     ).update_boxes(conn); | ||||
| } | ||||
|  | ||||
| @ -37,7 +37,7 @@ pub trait Notify<C> { | ||||
| 
 | ||||
| pub trait Deletable<C, A> { | ||||
|     fn delete(&self, conn: &C) -> A; | ||||
|     fn delete_id(id: String, actor_id: String, conn: &C); | ||||
|     fn delete_id(id: &str, actor_id: &str, conn: &C); | ||||
| } | ||||
| 
 | ||||
| pub trait WithInbox { | ||||
|  | ||||
| @ -15,10 +15,10 @@ pub mod inbox; | ||||
| pub mod request; | ||||
| pub mod sign; | ||||
| 
 | ||||
| pub const CONTEXT_URL: &'static str = "https://www.w3.org/ns/activitystreams"; | ||||
| pub const PUBLIC_VISIBILTY: &'static str = "https://www.w3.org/ns/activitystreams#Public"; | ||||
| pub const CONTEXT_URL: &str = "https://www.w3.org/ns/activitystreams"; | ||||
| pub const PUBLIC_VISIBILTY: &str = "https://www.w3.org/ns/activitystreams#Public"; | ||||
| 
 | ||||
| pub const AP_CONTENT_TYPE: &'static str = r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#; | ||||
| pub const AP_CONTENT_TYPE: &str = r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#; | ||||
| 
 | ||||
| pub fn ap_accept_header() -> Vec<&'static str> { | ||||
|     vec![ | ||||
| @ -84,7 +84,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApRequest { | ||||
|             .get_one("Accept") | ||||
|             .map(|header| { | ||||
|                 header | ||||
|                     .split(",") | ||||
|                     .split(',') | ||||
|                     .map(|ct| match ct.trim() { | ||||
|                         // bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise
 | ||||
|                         "application/ld+json; profile=\"https://w3.org/ns/activitystreams\"" | ||||
| @ -95,7 +95,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApRequest { | ||||
|                         _ => Outcome::Forward(false), | ||||
|                     }) | ||||
|                     .fold(Outcome::Forward(false), |out, ct| { | ||||
|                         if out.clone().forwarded().unwrap_or(out.is_success()) { | ||||
|                         if out.clone().forwarded().unwrap_or_else(|| out.is_success()) { | ||||
|                             out | ||||
|                         } else { | ||||
|                             ct | ||||
| @ -114,7 +114,7 @@ pub fn broadcast<S: sign::Signer, A: Activity, T: inbox::WithInbox + Actor>( | ||||
|     let boxes = to | ||||
|         .into_iter() | ||||
|         .filter(|u| !u.is_local()) | ||||
|         .map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url())) | ||||
|         .map(|u| u.get_shared_inbox_url().unwrap_or_else(|| u.get_inbox_url())) | ||||
|         .collect::<Vec<String>>() | ||||
|         .unique(); | ||||
| 
 | ||||
| @ -124,13 +124,14 @@ pub fn broadcast<S: sign::Signer, A: Activity, T: inbox::WithInbox + Actor>( | ||||
| 
 | ||||
|     for inbox in boxes { | ||||
|         // TODO: run it in Sidekiq or something like that
 | ||||
|         let body = signed.to_string(); | ||||
|         let mut headers = request::headers(); | ||||
|         headers.insert("Digest", request::Digest::digest(signed.to_string())); | ||||
|         headers.insert("Digest", request::Digest::digest(&body)); | ||||
|         let res = Client::new() | ||||
|             .post(&inbox[..]) | ||||
|             .post(&inbox) | ||||
|             .headers(headers.clone()) | ||||
|             .header("Signature", request::signature(sender, headers)) | ||||
|             .body(signed.to_string()) | ||||
|             .header("Signature", request::signature(sender, &headers)) | ||||
|             .body(body) | ||||
|             .send(); | ||||
|         match res { | ||||
|             Ok(mut r) => { | ||||
| @ -161,6 +162,12 @@ impl Into<String> for Id { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl AsRef<str> for Id { | ||||
|     fn as_ref(&self) -> &str { | ||||
|         &self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait IntoId { | ||||
|     fn into_id(self) -> Id; | ||||
| } | ||||
|  | ||||
| @ -8,28 +8,28 @@ use std::time::SystemTime; | ||||
| use activity_pub::{AP_CONTENT_TYPE, ap_accept_header}; | ||||
| use activity_pub::sign::Signer; | ||||
| 
 | ||||
| const PLUME_USER_AGENT: &'static str = concat!("Plume/", env!("CARGO_PKG_VERSION")); | ||||
| const PLUME_USER_AGENT: &str = concat!("Plume/", env!("CARGO_PKG_VERSION")); | ||||
| 
 | ||||
| pub struct Digest(String); | ||||
| 
 | ||||
| impl Digest { | ||||
|     pub fn digest(body: String) -> HeaderValue { | ||||
|     pub fn digest(body: &str) -> HeaderValue { | ||||
|         let mut hasher = | ||||
|             Hasher::new(MessageDigest::sha256()).expect("Digest::digest: initialization error"); | ||||
|         hasher | ||||
|             .update(&body.into_bytes()[..]) | ||||
|             .update(body.as_bytes()) | ||||
|             .expect("Digest::digest: content insertion error"); | ||||
|         let res = base64::encode(&hasher.finish().expect("Digest::digest: finalizing error")); | ||||
|         HeaderValue::from_str(&format!("SHA-256={}", res)) | ||||
|             .expect("Digest::digest: header creation error") | ||||
|     } | ||||
| 
 | ||||
|     pub fn verify(&self, body: String) -> bool { | ||||
|     pub fn verify(&self, body: &str) -> bool { | ||||
|         if self.algorithm() == "SHA-256" { | ||||
|             let mut hasher = | ||||
|                 Hasher::new(MessageDigest::sha256()).expect("Digest::digest: initialization error"); | ||||
|             hasher | ||||
|                 .update(&body.into_bytes()) | ||||
|                 .update(body.as_bytes()) | ||||
|                 .expect("Digest::digest: content insertion error"); | ||||
|             self.value().deref() | ||||
|                 == hasher | ||||
| @ -60,7 +60,7 @@ impl Digest { | ||||
|     pub fn from_header(dig: &str) -> Result<Self, ()> { | ||||
|         if let Some(pos) = dig.find('=') { | ||||
|             let pos = pos + 1; | ||||
|             if let Ok(_) = base64::decode(&dig[pos..]) { | ||||
|             if base64::decode(&dig[pos..]).is_ok() { | ||||
|                 Ok(Digest(dig.to_owned())) | ||||
|             } else { | ||||
|                 Err(()) | ||||
| @ -94,7 +94,7 @@ pub fn headers() -> HeaderMap { | ||||
|     headers | ||||
| } | ||||
| 
 | ||||
| pub fn signature<S: Signer>(signer: &S, headers: HeaderMap) -> HeaderValue { | ||||
| pub fn signature<S: Signer>(signer: &S, headers: &HeaderMap) -> HeaderValue { | ||||
|     let signed_string = headers | ||||
|         .iter() | ||||
|         .map(|(h, v)| { | ||||
| @ -114,8 +114,8 @@ pub fn signature<S: Signer>(signer: &S, headers: HeaderMap) -> HeaderValue { | ||||
|         .join(" ") | ||||
|         .to_lowercase(); | ||||
| 
 | ||||
|     let data = signer.sign(signed_string); | ||||
|     let sign = base64::encode(&data[..]); | ||||
|     let data = signer.sign(&signed_string); | ||||
|     let sign = base64::encode(&data); | ||||
| 
 | ||||
|     HeaderValue::from_str(&format!( | ||||
|         "keyId=\"{key_id}\",algorithm=\"rsa-sha256\",headers=\"{signed_headers}\",signature=\"{signature}\"", | ||||
|  | ||||
| @ -24,9 +24,9 @@ pub trait Signer { | ||||
|     fn get_key_id(&self) -> String; | ||||
| 
 | ||||
|     /// Sign some data with the signer keypair
 | ||||
|     fn sign(&self, to_sign: String) -> Vec<u8>; | ||||
|     fn sign(&self, to_sign: &str) -> Vec<u8>; | ||||
|     /// Verify if the signature is valid
 | ||||
|     fn verify(&self, data: String, signature: Vec<u8>) -> bool; | ||||
|     fn verify(&self, data: &str, signature: &[u8]) -> bool; | ||||
| } | ||||
| 
 | ||||
| pub trait Signable { | ||||
| @ -37,9 +37,9 @@ pub trait Signable { | ||||
|     where | ||||
|         T: Signer; | ||||
| 
 | ||||
|     fn hash(data: String) -> String { | ||||
|         let bytes = data.into_bytes(); | ||||
|         hex::encode(sha256(&bytes[..])) | ||||
|     fn hash(data: &str) -> String { | ||||
|         let bytes = data.as_bytes(); | ||||
|         hex::encode(sha256(bytes)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -53,15 +53,15 @@ impl Signable for serde_json::Value { | ||||
|         }); | ||||
| 
 | ||||
|         let options_hash = Self::hash( | ||||
|             json!({ | ||||
|             &json!({ | ||||
|             "@context": "https://w3id.org/identity/v1", | ||||
|             "created": creation_date | ||||
|         }).to_string(), | ||||
|         ); | ||||
|         let document_hash = Self::hash(self.to_string()); | ||||
|         let document_hash = Self::hash(&self.to_string()); | ||||
|         let to_be_signed = options_hash + &document_hash; | ||||
| 
 | ||||
|         let signature = base64::encode(&creator.sign(to_be_signed)); | ||||
|         let signature = base64::encode(&creator.sign(&to_be_signed)); | ||||
| 
 | ||||
|         options["signatureValue"] = serde_json::Value::String(signature); | ||||
|         self["signature"] = options; | ||||
| @ -85,14 +85,14 @@ impl Signable for serde_json::Value { | ||||
|         }; | ||||
|         let creation_date = &signature_obj["created"]; | ||||
|         let options_hash = Self::hash( | ||||
|             json!({ | ||||
|             &json!({ | ||||
|             "@context": "https://w3id.org/identity/v1", | ||||
|             "created": creation_date | ||||
|         }).to_string(), | ||||
|         ); | ||||
|         let document_hash = Self::hash(self.to_string()); | ||||
|         let document_hash = Self::hash(&self.to_string()); | ||||
|         let to_be_signed = options_hash + &document_hash; | ||||
|         creator.verify(to_be_signed, signature) | ||||
|         creator.verify(&to_be_signed, &signature) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -105,15 +105,15 @@ pub enum SignatureValidity { | ||||
| } | ||||
| 
 | ||||
| impl SignatureValidity { | ||||
|     pub fn is_secure(&self) -> bool { | ||||
|         self == &SignatureValidity::Valid | ||||
|     pub fn is_secure(self) -> bool { | ||||
|         self == SignatureValidity::Valid | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>( | ||||
|     sender: &S, | ||||
|     all_headers: HeaderMap, | ||||
|     data: String, | ||||
|     all_headers: &HeaderMap, | ||||
|     data: &str, | ||||
| ) -> SignatureValidity { | ||||
|     let sig_header = all_headers.get_one("Signature"); | ||||
|     if sig_header.is_none() { | ||||
| @ -151,7 +151,7 @@ pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>( | ||||
|         .collect::<Vec<_>>() | ||||
|         .join("\n"); | ||||
| 
 | ||||
|     if !sender.verify(h, base64::decode(signature).unwrap_or(Vec::new())) { | ||||
|     if !sender.verify(&h, &base64::decode(signature).unwrap_or_default()) { | ||||
|         return SignatureValidity::Invalid; | ||||
|     } | ||||
|     if !headers.contains(&"digest") { | ||||
| @ -160,7 +160,7 @@ pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>( | ||||
|     } | ||||
|     let digest = all_headers.get_one("digest").unwrap_or(""); | ||||
|     let digest = request::Digest::from_header(digest); | ||||
|     if !digest.map(|d| d.verify(data)).unwrap_or(false) { | ||||
|     if !digest.map(|d| d.verify(&data)).unwrap_or(false) { | ||||
|         // signature was valid, but body content does not match its digest
 | ||||
|         SignatureValidity::Invalid | ||||
|     } else { | ||||
|  | ||||
| @ -16,17 +16,15 @@ pub fn random_hex() -> String { | ||||
| } | ||||
| 
 | ||||
| /// Remove non alphanumeric characters and CamelCase a string
 | ||||
| pub fn make_actor_id(name: String) -> String { | ||||
|     name.as_str() | ||||
|         .to_camel_case() | ||||
|         .to_string() | ||||
| pub fn make_actor_id(name: &str) -> String { | ||||
|     name.to_camel_case() | ||||
|         .chars() | ||||
|         .filter(|c| c.is_alphanumeric()) | ||||
|         .collect() | ||||
| } | ||||
| 
 | ||||
| pub fn requires_login(message: &str, url: Uri) -> Flash<Redirect> { | ||||
|     Flash::new(Redirect::to(format!("/login?m={}", gettext(message.to_string()))), "callback", url.to_string()) | ||||
| pub fn requires_login<T: Into<Uri<'static>>>(message: &str, url: T) -> Flash<Redirect> { | ||||
|     Flash::new(Redirect::to(format!("/login?m={}", gettext(message.to_string()))), "callback", url.into().to_string()) | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| @ -41,27 +39,26 @@ enum State { | ||||
| pub fn md_to_html(md: &str) -> (String, HashSet<String>, HashSet<String>) { | ||||
|     let parser = Parser::new_ext(md, Options::all()); | ||||
| 
 | ||||
|     let (parser, mentions, hashtags): (Vec<Vec<Event>>, Vec<Vec<String>>, Vec<Vec<String>>) = parser.map(|evt| match evt { | ||||
|     let (parser, mentions, hashtags): (Vec<Event>, Vec<String>, Vec<String>) = parser.map(|evt| match evt { | ||||
|         Event::Text(txt) => { | ||||
|             let (evts, _, _, _, new_mentions, new_hashtags) = txt.chars().fold((vec![], State::Ready, String::new(), 0, vec![], vec![]), |(mut events, state, text_acc, n, mut mentions, mut hashtags), c| { | ||||
|             let (evts, _, _, _, new_mentions, new_hashtags) = txt.chars().fold((vec![], State::Ready, String::new(), 0, vec![], vec![]), |(mut events, state, mut text_acc, n, mut mentions, mut hashtags), c| { | ||||
|                 match state { | ||||
|                     State::Mention => { | ||||
|                         let char_matches = c.is_alphanumeric() || c == '@' || c == '.' || c == '-' || c == '_'; | ||||
|                         if char_matches && (n < (txt.chars().count() - 1)) { | ||||
|                             (events, State::Mention, text_acc + c.to_string().as_ref(), n + 1, mentions, hashtags) | ||||
|                             text_acc.push(c); | ||||
|                             (events, State::Mention, text_acc, n + 1, mentions, hashtags) | ||||
|                         } else { | ||||
|                             let mention = if char_matches { | ||||
|                                 text_acc + c.to_string().as_ref() | ||||
|                             } else { | ||||
|                                 text_acc | ||||
|                             }; | ||||
|                             let short_mention = mention.clone(); | ||||
|                             let short_mention = short_mention.splitn(1, '@').nth(0).unwrap_or(""); | ||||
|                             let link = Tag::Link(format!("/@/{}/", mention).into(), short_mention.to_string().into()); | ||||
|                             if char_matches { | ||||
|                                 text_acc.push(c) | ||||
|                             } | ||||
|                             let mention = text_acc; | ||||
|                             let short_mention = mention.splitn(1, '@').nth(0).unwrap_or(""); | ||||
|                             let link = Tag::Link(format!("/@/{}/", &mention).into(), short_mention.to_owned().into()); | ||||
| 
 | ||||
|                             mentions.push(mention); | ||||
|                             mentions.push(mention.clone()); | ||||
|                             events.push(Event::Start(link.clone())); | ||||
|                             events.push(Event::Text(format!("@{}", short_mention).into())); | ||||
|                             events.push(Event::Text(format!("@{}", &short_mention).into())); | ||||
|                             events.push(Event::End(link)); | ||||
| 
 | ||||
|                             (events, State::Ready, c.to_string(), n + 1, mentions, hashtags) | ||||
| @ -70,24 +67,25 @@ pub fn md_to_html(md: &str) -> (String, HashSet<String>, HashSet<String>) { | ||||
|                     State::Hashtag => { | ||||
|                         let char_matches = c.is_alphanumeric(); | ||||
|                         if char_matches && (n < (txt.chars().count() -1)) { | ||||
|                             (events, State::Hashtag, text_acc + c.to_string().as_ref(), n+1, mentions, hashtags) | ||||
|                             text_acc.push(c); | ||||
|                             (events, State::Hashtag, text_acc, n+1, mentions, hashtags) | ||||
|                         } else { | ||||
|                             let hashtag = if char_matches { | ||||
|                                 text_acc + c.to_string().as_ref() | ||||
|                             } else { | ||||
|                                 text_acc | ||||
|                             }; | ||||
|                             let link = Tag::Link(format!("/tag/{}", hashtag.to_camel_case()).into(), hashtag.to_string().into()); | ||||
|                             if char_matches { | ||||
|                                 text_acc.push(c); | ||||
|                             } | ||||
|                             let hashtag = text_acc; | ||||
|                             let link = Tag::Link(format!("/tag/{}", &hashtag.to_camel_case()).into(), hashtag.to_owned().into()); | ||||
| 
 | ||||
|                             hashtags.push(hashtag.clone()); | ||||
|                             events.push(Event::Start(link.clone())); | ||||
|                             events.push(Event::Text(format!("#{}", hashtag).into())); | ||||
|                             events.push(Event::Text(format!("#{}", &hashtag).into())); | ||||
|                             events.push(Event::End(link)); | ||||
| 
 | ||||
|                             (events, State::Ready, c.to_string(), n + 1, mentions, hashtags) | ||||
|                         } | ||||
|                     } | ||||
|                     State::Ready => { | ||||
|                         text_acc.push(c); | ||||
|                         if c == '@' { | ||||
|                             events.push(Event::Text(text_acc.into())); | ||||
|                             (events, State::Mention, String::new(), n + 1, mentions, hashtags) | ||||
| @ -96,27 +94,28 @@ pub fn md_to_html(md: &str) -> (String, HashSet<String>, HashSet<String>) { | ||||
|                             (events, State::Hashtag, String::new(), n + 1, mentions, hashtags) | ||||
|                         } else if c.is_alphanumeric() { | ||||
|                             if n >= (txt.chars().count() - 1) { // Add the text after at the end, even if it is not followed by a mention.
 | ||||
|                                 events.push(Event::Text((text_acc.clone() + c.to_string().as_ref()).into())) | ||||
|                                 events.push(Event::Text(text_acc.clone().into())) | ||||
|                             } | ||||
|                             (events, State::Word, text_acc + c.to_string().as_ref(), n + 1, mentions, hashtags) | ||||
|                             (events, State::Word, text_acc, n + 1, mentions, hashtags) | ||||
|                         } else { | ||||
|                             if n >= (txt.chars().count() - 1) { // Add the text after at the end, even if it is not followed by a mention.
 | ||||
|                                 events.push(Event::Text((text_acc.clone() + c.to_string().as_ref()).into())) | ||||
|                                 events.push(Event::Text(text_acc.clone().into())) | ||||
|                             } | ||||
|                             (events, State::Ready, text_acc + c.to_string().as_ref(), n + 1, mentions, hashtags) | ||||
|                             (events, State::Ready, text_acc, n + 1, mentions, hashtags) | ||||
|                         } | ||||
|                     } | ||||
|                     State::Word => { | ||||
|                         text_acc.push(c); | ||||
|                         if c.is_alphanumeric() { | ||||
|                             if n >= (txt.chars().count() - 1) { // Add the text after at the end, even if it is not followed by a mention.
 | ||||
|                                 events.push(Event::Text((text_acc.clone() + c.to_string().as_ref()).into())) | ||||
|                                 events.push(Event::Text(text_acc.clone().into())) | ||||
|                             } | ||||
|                             (events, State::Word, text_acc + c.to_string().as_ref(), n + 1, mentions, hashtags) | ||||
|                             (events, State::Word, text_acc, n + 1, mentions, hashtags) | ||||
|                         } else { | ||||
|                             if n >= (txt.chars().count() - 1) { // Add the text after at the end, even if it is not followed by a mention.
 | ||||
|                                 events.push(Event::Text((text_acc.clone() + c.to_string().as_ref()).into())) | ||||
|                                 events.push(Event::Text(text_acc.clone().into())) | ||||
|                             } | ||||
|                             (events, State::Ready, text_acc + c.to_string().as_ref(), n + 1, mentions, hashtags) | ||||
|                             (events, State::Ready, text_acc, n + 1, mentions, hashtags) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @ -124,15 +123,15 @@ pub fn md_to_html(md: &str) -> (String, HashSet<String>, HashSet<String>) { | ||||
|             (evts, new_mentions, new_hashtags) | ||||
|         }, | ||||
|         _ => (vec![evt], vec![], vec![]) | ||||
|     }).fold((vec![],vec![],vec![]), |(mut parser, mut mention, mut hashtag), (p, m, h)| { | ||||
|         parser.push(p); | ||||
|         mention.push(m); | ||||
|         hashtag.push(h); | ||||
|     }).fold((vec![],vec![],vec![]), |(mut parser, mut mention, mut hashtag), (mut p, mut m, mut h)| { | ||||
|         parser.append(&mut p); | ||||
|         mention.append(&mut m); | ||||
|         hashtag.append(&mut h); | ||||
|         (parser, mention, hashtag) | ||||
|     }); | ||||
|     let parser = parser.into_iter().flatten(); | ||||
|     let mentions = mentions.into_iter().flatten().map(|m| String::from(m.trim())); | ||||
|     let hashtags = hashtags.into_iter().flatten().map(|h| String::from(h.trim())); | ||||
|     let parser = parser.into_iter(); | ||||
|     let mentions = mentions.into_iter().map(|m| String::from(m.trim())); | ||||
|     let hashtags = hashtags.into_iter().map(|h| String::from(h.trim())); | ||||
| 
 | ||||
|     // TODO: fetch mentionned profiles in background, if needed
 | ||||
| 
 | ||||
|  | ||||
| @ -42,7 +42,7 @@ pub struct NewApiToken { | ||||
| impl ApiToken { | ||||
|     get!(api_tokens); | ||||
|     insert!(api_tokens, NewApiToken); | ||||
|     find_by!(api_tokens, find_by_value, value as String); | ||||
|     find_by!(api_tokens, find_by_value, value as &str); | ||||
| 
 | ||||
|     pub fn can(&self, what: &'static str, scope: &'static str) -> bool { | ||||
|         let full_scope = what.to_owned() + ":" + scope; | ||||
| @ -78,11 +78,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApiToken { | ||||
| 
 | ||||
|         if auth_type == "Bearer" { | ||||
|             let conn = request.guard::<DbConn>().expect("Couldn't connect to DB"); | ||||
|             if let Some(token) = ApiToken::find_by_value(&*conn, val.to_string()) { | ||||
|             if let Some(token) = ApiToken::find_by_value(&*conn, val) { | ||||
|                 return Outcome::Success(token); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return Outcome::Forward(()); | ||||
|         Outcome::Forward(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -47,8 +47,8 @@ impl Provider<Connection> for App { | ||||
|             conn, | ||||
|             NewApp { | ||||
|                 name: data.name, | ||||
|                 client_id: client_id, | ||||
|                 client_secret: client_secret, | ||||
|                 client_id, | ||||
|                 client_secret, | ||||
|                 redirect_uri: data.redirect_uri, | ||||
|                 website: data.website, | ||||
|             }, | ||||
| @ -76,5 +76,5 @@ impl Provider<Connection> for App { | ||||
| impl App { | ||||
|     get!(apps); | ||||
|     insert!(apps, NewApp); | ||||
|     find_by!(apps, find_by_client_id, client_id as String); | ||||
|     find_by!(apps, find_by_client_id, client_id as &str); | ||||
| } | ||||
|  | ||||
| @ -58,13 +58,13 @@ pub struct NewBlog { | ||||
|     pub public_key: String, | ||||
| } | ||||
| 
 | ||||
| const BLOG_PREFIX: &'static str = "~"; | ||||
| const BLOG_PREFIX: &str = "~"; | ||||
| 
 | ||||
| impl Blog { | ||||
|     insert!(blogs, NewBlog); | ||||
|     get!(blogs); | ||||
|     find_by!(blogs, find_by_ap_url, ap_url as String); | ||||
|     find_by!(blogs, find_by_name, actor_id as String, instance_id as i32); | ||||
|     find_by!(blogs, find_by_ap_url, ap_url as &str); | ||||
|     find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32); | ||||
| 
 | ||||
|     pub fn get_instance(&self, conn: &Connection) -> Instance { | ||||
|         Instance::get(conn, self.instance_id).expect("Blog::get_instance: instance not found error") | ||||
| @ -93,28 +93,24 @@ impl Blog { | ||||
|             .expect("Blog::find_for_author: blog loading error") | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_local(conn: &Connection, name: String) -> Option<Blog> { | ||||
|     pub fn find_local(conn: &Connection, name: &str) -> Option<Blog> { | ||||
|         Blog::find_by_name(conn, name, Instance::local_id(conn)) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_fqn(conn: &Connection, fqn: String) -> Option<Blog> { | ||||
|         if fqn.contains("@") { | ||||
|     pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Option<Blog> { | ||||
|         if fqn.contains('@') { | ||||
|             // remote blog
 | ||||
|             match Instance::find_by_domain( | ||||
|                 conn, | ||||
|                 String::from( | ||||
|                     fqn.split("@") | ||||
|                         .last() | ||||
|                         .expect("Blog::find_by_fqn: unreachable"), | ||||
|                 ), | ||||
|                 fqn.split('@') | ||||
|                     .last() | ||||
|                     .expect("Blog::find_by_fqn: unreachable"), | ||||
|             ) { | ||||
|                 Some(instance) => match Blog::find_by_name( | ||||
|                     conn, | ||||
|                     String::from( | ||||
|                         fqn.split("@") | ||||
|                             .nth(0) | ||||
|                             .expect("Blog::find_by_fqn: unreachable"), | ||||
|                     ), | ||||
|                     fqn.split('@') | ||||
|                         .nth(0) | ||||
|                         .expect("Blog::find_by_fqn: unreachable"), | ||||
|                     instance.id, | ||||
|                 ) { | ||||
|                     Some(u) => Some(u), | ||||
| @ -128,8 +124,8 @@ impl Blog { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn fetch_from_webfinger(conn: &Connection, acct: String) -> Option<Blog> { | ||||
|         match resolve(acct.clone(), *USE_HTTPS) { | ||||
|     fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Option<Blog> { | ||||
|         match resolve(acct.to_owned(), *USE_HTTPS) { | ||||
|             Ok(wf) => wf | ||||
|                 .links | ||||
|                 .into_iter() | ||||
| @ -137,7 +133,7 @@ impl Blog { | ||||
|                 .and_then(|l| { | ||||
|                     Blog::fetch_from_url( | ||||
|                         conn, | ||||
|                         l.href | ||||
|                         &l.href | ||||
|                             .expect("Blog::fetch_from_webfinger: href not found error"), | ||||
|                     ) | ||||
|                 }), | ||||
| @ -148,9 +144,9 @@ impl Blog { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn fetch_from_url(conn: &Connection, url: String) -> Option<Blog> { | ||||
|     fn fetch_from_url(conn: &Connection, url: &str) -> Option<Blog> { | ||||
|         let req = Client::new() | ||||
|             .get(&url[..]) | ||||
|             .get(url) | ||||
|             .header( | ||||
|                 ACCEPT, | ||||
|                 HeaderValue::from_str( | ||||
| @ -173,27 +169,26 @@ impl Blog { | ||||
|                 json.custom_props = ap_sign; // without this workaround, publicKey is not correctly deserialized
 | ||||
|                 Some(Blog::from_activity( | ||||
|                     conn, | ||||
|                     json, | ||||
|                     Url::parse(url.as_ref()) | ||||
|                     &json, | ||||
|                     Url::parse(url) | ||||
|                         .expect("Blog::fetch_from_url: url parsing error") | ||||
|                         .host_str() | ||||
|                         .expect("Blog::fetch_from_url: host extraction error") | ||||
|                         .to_string(), | ||||
|                         .expect("Blog::fetch_from_url: host extraction error"), | ||||
|                 )) | ||||
|             } | ||||
|             Err(_) => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &Connection, acct: CustomGroup, inst: String) -> Blog { | ||||
|         let instance = match Instance::find_by_domain(conn, inst.clone()) { | ||||
|     fn from_activity(conn: &Connection, acct: &CustomGroup, inst: &str) -> Blog { | ||||
|         let instance = match Instance::find_by_domain(conn, inst) { | ||||
|             Some(instance) => instance, | ||||
|             None => { | ||||
|                 Instance::insert( | ||||
|                     conn, | ||||
|                     NewInstance { | ||||
|                         public_domain: inst.clone(), | ||||
|                         name: inst.clone(), | ||||
|                         public_domain: inst.to_owned(), | ||||
|                         name: inst.to_owned(), | ||||
|                         local: false, | ||||
|                         // We don't really care about all the following for remote instances
 | ||||
|                         long_description: SafeString::new(""), | ||||
| @ -251,72 +246,72 @@ impl Blog { | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, _conn: &Connection) -> CustomGroup { | ||||
|     pub fn to_activity(&self, _conn: &Connection) -> CustomGroup { | ||||
|         let mut blog = Group::default(); | ||||
|         blog.ap_actor_props | ||||
|             .set_preferred_username_string(self.actor_id.clone()) | ||||
|             .expect("Blog::into_activity: preferredUsername error"); | ||||
|             .expect("Blog::to_activity: preferredUsername error"); | ||||
|         blog.object_props | ||||
|             .set_name_string(self.title.clone()) | ||||
|             .expect("Blog::into_activity: name error"); | ||||
|             .expect("Blog::to_activity: name error"); | ||||
|         blog.ap_actor_props | ||||
|             .set_outbox_string(self.outbox_url.clone()) | ||||
|             .expect("Blog::into_activity: outbox error"); | ||||
|             .expect("Blog::to_activity: outbox error"); | ||||
|         blog.ap_actor_props | ||||
|             .set_inbox_string(self.inbox_url.clone()) | ||||
|             .expect("Blog::into_activity: inbox error"); | ||||
|             .expect("Blog::to_activity: inbox error"); | ||||
|         blog.object_props | ||||
|             .set_summary_string(self.summary.clone()) | ||||
|             .expect("Blog::into_activity: summary error"); | ||||
|             .expect("Blog::to_activity: summary error"); | ||||
|         blog.object_props | ||||
|             .set_id_string(self.ap_url.clone()) | ||||
|             .expect("Blog::into_activity: id error"); | ||||
|             .expect("Blog::to_activity: id error"); | ||||
| 
 | ||||
|         let mut public_key = PublicKey::default(); | ||||
|         public_key | ||||
|             .set_id_string(format!("{}#main-key", self.ap_url)) | ||||
|             .expect("Blog::into_activity: publicKey.id error"); | ||||
|             .expect("Blog::to_activity: publicKey.id error"); | ||||
|         public_key | ||||
|             .set_owner_string(self.ap_url.clone()) | ||||
|             .expect("Blog::into_activity: publicKey.owner error"); | ||||
|             .expect("Blog::to_activity: publicKey.owner error"); | ||||
|         public_key | ||||
|             .set_public_key_pem_string(self.public_key.clone()) | ||||
|             .expect("Blog::into_activity: publicKey.publicKeyPem error"); | ||||
|             .expect("Blog::to_activity: publicKey.publicKeyPem error"); | ||||
|         let mut ap_signature = ApSignature::default(); | ||||
|         ap_signature | ||||
|             .set_public_key_publickey(public_key) | ||||
|             .expect("Blog::into_activity: publicKey error"); | ||||
|             .expect("Blog::to_activity: publicKey error"); | ||||
| 
 | ||||
|         CustomGroup::new(blog, ap_signature) | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_boxes(&self, conn: &Connection) { | ||||
|         let instance = self.get_instance(conn); | ||||
|         if self.outbox_url.len() == 0 { | ||||
|         if self.outbox_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(blogs::outbox_url.eq(instance.compute_box( | ||||
|                     BLOG_PREFIX, | ||||
|                     self.actor_id.clone(), | ||||
|                     &self.actor_id, | ||||
|                     "outbox", | ||||
|                 ))) | ||||
|                 .execute(conn) | ||||
|                 .expect("Blog::update_boxes: outbox update error"); | ||||
|         } | ||||
| 
 | ||||
|         if self.inbox_url.len() == 0 { | ||||
|         if self.inbox_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(blogs::inbox_url.eq(instance.compute_box( | ||||
|                     BLOG_PREFIX, | ||||
|                     self.actor_id.clone(), | ||||
|                     &self.actor_id, | ||||
|                     "inbox", | ||||
|                 ))) | ||||
|                 .execute(conn) | ||||
|                 .expect("Blog::update_boxes: inbox update error"); | ||||
|         } | ||||
| 
 | ||||
|         if self.ap_url.len() == 0 { | ||||
|         if self.ap_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(blogs::ap_url.eq(instance.compute_box(BLOG_PREFIX, self.actor_id.clone(), ""))) | ||||
|                 .set(blogs::ap_url.eq(instance.compute_box(BLOG_PREFIX, &self.actor_id, ""))) | ||||
|                 .execute(conn) | ||||
|                 .expect("Blog::update_boxes: ap_url update error"); | ||||
|         } | ||||
| @ -367,7 +362,7 @@ impl Blog { | ||||
|                     mime_type: Some(String::from("application/atom+xml")), | ||||
|                     href: Some(self.get_instance(conn).compute_box( | ||||
|                         BLOG_PREFIX, | ||||
|                         self.actor_id.clone(), | ||||
|                         &self.actor_id, | ||||
|                         "feed.atom", | ||||
|                     )), | ||||
|                     template: None, | ||||
| @ -382,11 +377,11 @@ impl Blog { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn from_url(conn: &Connection, url: String) -> Option<Blog> { | ||||
|         Blog::find_by_ap_url(conn, url.clone()).or_else(|| { | ||||
|     pub fn from_url(conn: &Connection, url: &str) -> Option<Blog> { | ||||
|         Blog::find_by_ap_url(conn, url).or_else(|| { | ||||
|             // The requested blog was not in the DB
 | ||||
|             // We try to fetch it if it is remote
 | ||||
|             if Url::parse(url.as_ref()) | ||||
|             if Url::parse(url) | ||||
|                 .expect("Blog::from_url: ap_url parsing error") | ||||
|                 .host_str() | ||||
|                 .expect("Blog::from_url: host extraction error") != BASE_URL.as_str() | ||||
| @ -454,7 +449,7 @@ impl sign::Signer for Blog { | ||||
|         format!("{}#main-key", self.ap_url) | ||||
|     } | ||||
| 
 | ||||
|     fn sign(&self, to_sign: String) -> Vec<u8> { | ||||
|     fn sign(&self, to_sign: &str) -> Vec<u8> { | ||||
|         let key = self.get_keypair(); | ||||
|         let mut signer = | ||||
|             Signer::new(MessageDigest::sha256(), &key).expect("Blog::sign: initialization error"); | ||||
| @ -466,7 +461,7 @@ impl sign::Signer for Blog { | ||||
|             .expect("Blog::sign: finalization error") | ||||
|     } | ||||
| 
 | ||||
|     fn verify(&self, data: String, signature: Vec<u8>) -> bool { | ||||
|     fn verify(&self, data: &str, signature: &[u8]) -> bool { | ||||
|         let key = PKey::from_rsa( | ||||
|             Rsa::public_key_from_pem(self.public_key.as_ref()) | ||||
|                 .expect("Blog::verify: pem parsing error"), | ||||
| @ -491,12 +486,12 @@ impl NewBlog { | ||||
|     ) -> NewBlog { | ||||
|         let (pub_key, priv_key) = sign::gen_keypair(); | ||||
|         NewBlog { | ||||
|             actor_id: actor_id, | ||||
|             title: title, | ||||
|             summary: summary, | ||||
|             actor_id, | ||||
|             title, | ||||
|             summary, | ||||
|             outbox_url: String::from(""), | ||||
|             inbox_url: String::from(""), | ||||
|             instance_id: instance_id, | ||||
|             instance_id, | ||||
|             ap_url: String::from(""), | ||||
|             public_key: String::from_utf8(pub_key).expect("NewBlog::new_local: public key error"), | ||||
|             private_key: Some( | ||||
| @ -725,7 +720,7 @@ pub(crate) mod tests { | ||||
|             ); | ||||
| 
 | ||||
|             assert_eq!( | ||||
|                 Blog::find_local(conn, "SomeName".to_owned()).unwrap().id, | ||||
|                 Blog::find_local(conn, "SomeName").unwrap().id, | ||||
|                 blog.id | ||||
|             ); | ||||
| 
 | ||||
|  | ||||
| @ -46,7 +46,7 @@ impl Comment { | ||||
|     insert!(comments, NewComment); | ||||
|     get!(comments); | ||||
|     list_by!(comments, list_by_post, post_id as i32); | ||||
|     find_by!(comments, find_by_ap_url, ap_url as String); | ||||
|     find_by!(comments, find_by_ap_url, ap_url as &str); | ||||
| 
 | ||||
|     pub fn get_author(&self, conn: &Connection) -> User { | ||||
|         User::get(conn, self.author_id).expect("Comment::get_author: author error") | ||||
| @ -68,7 +68,7 @@ impl Comment { | ||||
|             .len() // TODO count in database?
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_json(&self, conn: &Connection, others: &Vec<Comment>) -> serde_json::Value { | ||||
|     pub fn to_json(&self, conn: &Connection, others: &[Comment]) -> serde_json::Value { | ||||
|         let mut json = serde_json::to_value(self).expect("Comment::to_json: serialization error"); | ||||
|         json["author"] = self.get_author(conn).to_json(conn); | ||||
|         let mentions = Mention::list_for_comment(conn, self.id) | ||||
| @ -76,7 +76,7 @@ impl Comment { | ||||
|             .map(|m| { | ||||
|                 m.get_mentioned(conn) | ||||
|                     .map(|u| u.get_fqn(conn)) | ||||
|                     .unwrap_or(String::new()) | ||||
|                     .unwrap_or_default() | ||||
|             }) | ||||
|             .collect::<Vec<String>>(); | ||||
|         json["mentions"] = serde_json::to_value(mentions).expect("Comment::to_json: mention error"); | ||||
| @ -106,53 +106,53 @@ impl Comment { | ||||
|         format!("{}comment/{}", self.get_post(conn).ap_url, self.id) | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> Note { | ||||
|     pub fn to_activity(&self, conn: &Connection) -> Note { | ||||
|         let (html, mentions, _hashtags) = utils::md_to_html(self.content.get().as_ref()); | ||||
| 
 | ||||
|         let author = User::get(conn, self.author_id).expect("Comment::into_activity: author error"); | ||||
|         let author = User::get(conn, self.author_id).expect("Comment::to_activity: author error"); | ||||
|         let mut note = Note::default(); | ||||
|         let to = vec![Id::new(PUBLIC_VISIBILTY.to_string())]; | ||||
| 
 | ||||
|         note.object_props | ||||
|             .set_id_string(self.ap_url.clone().unwrap_or(String::new())) | ||||
|             .expect("Comment::into_activity: id error"); | ||||
|             .set_id_string(self.ap_url.clone().unwrap_or_default()) | ||||
|             .expect("Comment::to_activity: id error"); | ||||
|         note.object_props | ||||
|             .set_summary_string(self.spoiler_text.clone()) | ||||
|             .expect("Comment::into_activity: summary error"); | ||||
|             .expect("Comment::to_activity: summary error"); | ||||
|         note.object_props | ||||
|             .set_content_string(html) | ||||
|             .expect("Comment::into_activity: content error"); | ||||
|             .expect("Comment::to_activity: content error"); | ||||
|         note.object_props | ||||
|             .set_in_reply_to_link(Id::new(self.in_response_to_id.map_or_else( | ||||
|                 || { | ||||
|                     Post::get(conn, self.post_id) | ||||
|                         .expect("Comment::into_activity: post error") | ||||
|                         .expect("Comment::to_activity: post error") | ||||
|                         .ap_url | ||||
|                 }, | ||||
|                 |id| { | ||||
|                     let comm = | ||||
|                         Comment::get(conn, id).expect("Comment::into_activity: comment error"); | ||||
|                     comm.ap_url.clone().unwrap_or(comm.compute_id(conn)) | ||||
|                         Comment::get(conn, id).expect("Comment::to_activity: comment error"); | ||||
|                     comm.ap_url.clone().unwrap_or_else(|| comm.compute_id(conn)) | ||||
|                 }, | ||||
|             ))) | ||||
|             .expect("Comment::into_activity: in_reply_to error"); | ||||
|             .expect("Comment::to_activity: in_reply_to error"); | ||||
|         note.object_props | ||||
|             .set_published_string(chrono::Utc::now().to_rfc3339()) | ||||
|             .expect("Comment::into_activity: published error"); | ||||
|             .expect("Comment::to_activity: published error"); | ||||
|         note.object_props | ||||
|             .set_attributed_to_link(author.clone().into_id()) | ||||
|             .expect("Comment::into_activity: attributed_to error"); | ||||
|             .expect("Comment::to_activity: attributed_to error"); | ||||
|         note.object_props | ||||
|             .set_to_link_vec(to.clone()) | ||||
|             .expect("Comment::into_activity: to error"); | ||||
|             .expect("Comment::to_activity: to error"); | ||||
|         note.object_props | ||||
|             .set_tag_link_vec( | ||||
|                 mentions | ||||
|                     .into_iter() | ||||
|                     .map(|m| Mention::build_activity(conn, m)) | ||||
|                     .map(|m| Mention::build_activity(conn, &m)) | ||||
|                     .collect::<Vec<link::Mention>>(), | ||||
|             ) | ||||
|             .expect("Comment::into_activity: tag error"); | ||||
|             .expect("Comment::to_activity: tag error"); | ||||
|         note | ||||
|     } | ||||
| 
 | ||||
| @ -160,7 +160,7 @@ impl Comment { | ||||
|         let author = | ||||
|             User::get(conn, self.author_id).expect("Comment::create_activity: author error"); | ||||
| 
 | ||||
|         let note = self.into_activity(conn); | ||||
|         let note = self.to_activity(conn); | ||||
|         let mut act = Create::default(); | ||||
|         act.create_props | ||||
|             .set_actor_link(author.into_id()) | ||||
| @ -196,11 +196,11 @@ impl FromActivity<Note, Connection> for Comment { | ||||
|             .object_props | ||||
|             .in_reply_to | ||||
|             .clone() | ||||
|             .expect("Comment::from_activity: not an answer error") | ||||
|             .expect("Comment::from_activity: not an answer error"); | ||||
|         let previous_url = previous_url | ||||
|             .as_str() | ||||
|             .expect("Comment::from_activity: in_reply_to parsing error") | ||||
|             .to_string(); | ||||
|         let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone()); | ||||
|             .expect("Comment::from_activity: in_reply_to parsing error"); | ||||
|         let previous_comment = Comment::find_by_ap_url(conn, previous_url); | ||||
| 
 | ||||
|         let comm = Comment::insert( | ||||
|             conn, | ||||
| @ -214,7 +214,7 @@ impl FromActivity<Note, Connection> for Comment { | ||||
|                 spoiler_text: note | ||||
|                     .object_props | ||||
|                     .summary_string() | ||||
|                     .unwrap_or(String::from("")), | ||||
|                     .unwrap_or_default(), | ||||
|                 ap_url: note.object_props.id_string().ok(), | ||||
|                 in_response_to_id: previous_comment.clone().map(|c| c.id), | ||||
|                 post_id: previous_comment.map(|c| c.post_id).unwrap_or_else(|| { | ||||
| @ -222,7 +222,7 @@ impl FromActivity<Note, Connection> for Comment { | ||||
|                         .expect("Comment::from_activity: post error") | ||||
|                         .id | ||||
|                 }), | ||||
|                 author_id: User::from_url(conn, actor.clone().into()) | ||||
|                 author_id: User::from_url(conn, actor.as_ref()) | ||||
|                     .expect("Comment::from_activity: author error") | ||||
|                     .id, | ||||
|                 sensitive: false, // "sensitive" is not a standard property, we need to think about how to support it with the activitypub crate
 | ||||
| @ -231,7 +231,7 @@ impl FromActivity<Note, Connection> for Comment { | ||||
| 
 | ||||
|         // save mentions
 | ||||
|         if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() { | ||||
|             for tag in tags.into_iter() { | ||||
|             for tag in tags { | ||||
|                 serde_json::from_value::<link::Mention>(tag) | ||||
|                     .map(|m| { | ||||
|                         let author = &Post::get(conn, comm.post_id) | ||||
| @ -242,7 +242,7 @@ impl FromActivity<Note, Connection> for Comment { | ||||
|                             .href_string() | ||||
|                             .expect("Comment::from_activity: no href error") | ||||
|                             != author.ap_url.clone(); | ||||
|                         Mention::from_activity(conn, m, comm.id, false, not_author) | ||||
|                         Mention::from_activity(conn, &m, comm.id, false, not_author) | ||||
|                     }) | ||||
|                     .ok(); | ||||
|             } | ||||
|  | ||||
| @ -37,7 +37,7 @@ pub struct NewFollow { | ||||
| impl Follow { | ||||
|     insert!(follows, NewFollow); | ||||
|     get!(follows); | ||||
|     find_by!(follows, find_by_ap_url, ap_url as String); | ||||
|     find_by!(follows, find_by_ap_url, ap_url as &str); | ||||
| 
 | ||||
|     pub fn find(conn: &Connection, from: i32, to: i32) -> Option<Follow> { | ||||
|         follows::table | ||||
| @ -47,28 +47,28 @@ impl Follow { | ||||
|             .ok() | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> FollowAct { | ||||
|     pub fn to_activity(&self, conn: &Connection) -> FollowAct { | ||||
|         let user = User::get(conn, self.follower_id) | ||||
|             .expect("Follow::into_activity: actor not found error"); | ||||
|             .expect("Follow::to_activity: actor not found error"); | ||||
|         let target = User::get(conn, self.following_id) | ||||
|             .expect("Follow::into_activity: target not found error"); | ||||
|             .expect("Follow::to_activity: target not found error"); | ||||
| 
 | ||||
|         let mut act = FollowAct::default(); | ||||
|         act.follow_props | ||||
|             .set_actor_link::<Id>(user.clone().into_id()) | ||||
|             .expect("Follow::into_activity: actor error"); | ||||
|             .expect("Follow::to_activity: actor error"); | ||||
|         act.follow_props | ||||
|             .set_object_object(user.into_activity(&*conn)) | ||||
|             .expect("Follow::into_activity: object error"); | ||||
|             .set_object_object(user.to_activity(&*conn)) | ||||
|             .expect("Follow::to_activity: object error"); | ||||
|         act.object_props | ||||
|             .set_id_string(self.ap_url.clone()) | ||||
|             .expect("Follow::into_activity: id error"); | ||||
|             .expect("Follow::to_activity: id error"); | ||||
|         act.object_props | ||||
|             .set_to_link(target.clone().into_id()) | ||||
|             .expect("Follow::into_activity: target error"); | ||||
|             .expect("Follow::to_activity: target error"); | ||||
|         act.object_props | ||||
|             .set_cc_link_vec::<Id>(vec![]) | ||||
|             .expect("Follow::into_activity: cc error"); | ||||
|             .expect("Follow::to_activity: cc error"); | ||||
|         act | ||||
|     } | ||||
| 
 | ||||
| @ -94,7 +94,7 @@ impl Follow { | ||||
|         ); | ||||
| 
 | ||||
|         let mut accept = Accept::default(); | ||||
|         let accept_id = ap_url(format!("{}/follow/{}/accept", BASE_URL.as_str(), res.id)); | ||||
|         let accept_id = ap_url(&format!("{}/follow/{}/accept", BASE_URL.as_str(), &res.id)); | ||||
|         accept | ||||
|             .object_props | ||||
|             .set_id_string(accept_id) | ||||
| @ -136,15 +136,14 @@ impl FromActivity<FollowAct, Connection> for Follow { | ||||
|                     .expect("Follow::from_activity: actor not found error") | ||||
|             }); | ||||
|         let from = | ||||
|             User::from_url(conn, from_id).expect("Follow::from_activity: actor not found error"); | ||||
|             User::from_url(conn, &from_id).expect("Follow::from_activity: actor not found error"); | ||||
|         match User::from_url( | ||||
|             conn, | ||||
|             follow | ||||
|                 .follow_props | ||||
|                 .object | ||||
|                 .as_str() | ||||
|                 .expect("Follow::from_activity: target url parsing error") | ||||
|                 .to_string(), | ||||
|                 .expect("Follow::from_activity: target url parsing error"), | ||||
|         ) { | ||||
|             Some(user) => Follow::accept_follow(conn, &from, &user, follow, from.id, user.id), | ||||
|             None => { | ||||
| @ -154,8 +153,7 @@ impl FromActivity<FollowAct, Connection> for Follow { | ||||
|                         .follow_props | ||||
|                         .object | ||||
|                         .as_str() | ||||
|                         .expect("Follow::from_activity: target url parsing error") | ||||
|                         .to_string(), | ||||
|                         .expect("Follow::from_activity: target url parsing error"), | ||||
|                 ).expect("Follow::from_activity: target not found error"); | ||||
|                 Follow::accept_follow(conn, &from, &blog, follow, from.id, blog.id) | ||||
|             } | ||||
| @ -201,12 +199,12 @@ impl Deletable<Connection, Undo> for Follow { | ||||
|             .set_id_string(format!("{}/undo", self.ap_url)) | ||||
|             .expect("Follow::delete: id error"); | ||||
|         undo.undo_props | ||||
|             .set_object_object(self.into_activity(conn)) | ||||
|             .set_object_object(self.to_activity(conn)) | ||||
|             .expect("Follow::delete: object error"); | ||||
|         undo | ||||
|     } | ||||
| 
 | ||||
|     fn delete_id(id: String, actor_id: String, conn: &Connection) { | ||||
|     fn delete_id(id: &str, actor_id: &str, conn: &Connection) { | ||||
|         if let Some(follow) = Follow::find_by_ap_url(conn, id) { | ||||
|             if let Some(user) = User::find_by_ap_url(conn, actor_id) { | ||||
|                 if user.id == follow.follower_id { | ||||
|  | ||||
| @ -1,235 +0,0 @@ | ||||
| use activitypub::{ | ||||
|     activity::{Accept, Follow as FollowAct, Undo}, | ||||
|     actor::Person, | ||||
|     Actor, | ||||
| }; | ||||
| use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; | ||||
| 
 | ||||
| <<<<<<< HEAD | ||||
| use plume_common::activity_pub::{broadcast, Id, IntoId, inbox::{FromActivity, Notify, WithInbox, Deletable}, sign::Signer}; | ||||
| use {BASE_URL, ap_url, Connection}; | ||||
| ======= | ||||
| >>>>>>> Run rustfmt and rename instanceTests to instance_tests | ||||
| use blogs::Blog; | ||||
| use notifications::*; | ||||
| use plume_common::activity_pub::{ | ||||
|     broadcast, | ||||
|     inbox::{Deletable, FromActivity, Notify, WithInbox}, | ||||
|     sign::Signer, | ||||
|     Id, IntoId, | ||||
| }; | ||||
| use schema::follows; | ||||
| use users::User; | ||||
| use Connection; | ||||
| 
 | ||||
| #[derive(Clone, Queryable, Identifiable, Associations)] | ||||
| #[belongs_to(User, foreign_key = "following_id")] | ||||
| pub struct Follow { | ||||
|     pub id: i32, | ||||
|     pub follower_id: i32, | ||||
|     pub following_id: i32, | ||||
|     pub ap_url: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Insertable)] | ||||
| #[table_name = "follows"] | ||||
| pub struct NewFollow { | ||||
|     pub follower_id: i32, | ||||
|     pub following_id: i32, | ||||
|     pub ap_url: String, | ||||
| } | ||||
| 
 | ||||
| impl Follow { | ||||
|     insert!(follows, NewFollow); | ||||
|     get!(follows); | ||||
|     find_by!(follows, find_by_ap_url, ap_url as String); | ||||
| 
 | ||||
|     pub fn find(conn: &Connection, from: i32, to: i32) -> Option<Follow> { | ||||
|         follows::table | ||||
|             .filter(follows::follower_id.eq(from)) | ||||
|             .filter(follows::following_id.eq(to)) | ||||
|             .get_result(conn) | ||||
|             .ok() | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> FollowAct { | ||||
|         let user = User::get(conn, self.follower_id) | ||||
|             .expect("Follow::into_activity: actor not found error"); | ||||
|         let target = User::get(conn, self.following_id) | ||||
|             .expect("Follow::into_activity: target not found error"); | ||||
| 
 | ||||
|         let mut act = FollowAct::default(); | ||||
|         act.follow_props | ||||
|             .set_actor_link::<Id>(user.clone().into_id()) | ||||
|             .expect("Follow::into_activity: actor error"); | ||||
|         act.follow_props | ||||
|             .set_object_object(user.into_activity(&*conn)) | ||||
|             .expect("Follow::into_activity: object error"); | ||||
|         act.object_props | ||||
|             .set_id_string(self.ap_url.clone()) | ||||
|             .expect("Follow::into_activity: id error"); | ||||
|         act.object_props | ||||
|             .set_to_link(target.clone().into_id()) | ||||
|             .expect("Follow::into_activity: target error"); | ||||
|         act.object_props | ||||
|             .set_cc_link_vec::<Id>(vec![]) | ||||
|             .expect("Follow::into_activity: cc error"); | ||||
|         act | ||||
|     } | ||||
| 
 | ||||
|     /// from -> The one sending the follow request
 | ||||
|     /// target -> The target of the request, responding with Accept
 | ||||
|     pub fn accept_follow<A: Signer + IntoId + Clone, B: Clone + WithInbox + Actor + IntoId>( | ||||
|         conn: &Connection, | ||||
|         from: &B, | ||||
|         target: &A, | ||||
|         follow: FollowAct, | ||||
|         from_id: i32, | ||||
|         target_id: i32, | ||||
|     ) -> Follow { | ||||
|         let from_url: String = from.clone().into_id().into(); | ||||
|         let target_url: String = target.clone().into_id().into(); | ||||
|         let res = Follow::insert( | ||||
|             conn, | ||||
|             NewFollow { | ||||
|                 follower_id: from_id, | ||||
|                 following_id: target_id, | ||||
|                 ap_url: format!("{}/follow/{}", from_url, target_url), | ||||
|             }, | ||||
|         ); | ||||
| 
 | ||||
|         let mut accept = Accept::default(); | ||||
| <<<<<<< HEAD | ||||
|         let accept_id = ap_url(format!("{}/follow/{}/accept", BASE_URL.as_str(), res.id)); | ||||
|         accept.object_props.set_id_string(accept_id).expect("Follow::accept_follow: id error"); | ||||
|         accept.object_props.set_to_link(from.clone().into_id()).expect("Follow::accept_follow: to error"); | ||||
|         accept.object_props.set_cc_link_vec::<Id>(vec![]).expect("Follow::accept_follow: cc error"); | ||||
|         accept.accept_props.set_actor_link::<Id>(target.clone().into_id()).expect("Follow::accept_follow: actor error"); | ||||
|         accept.accept_props.set_object_object(follow).expect("Follow::accept_follow: object error"); | ||||
| ======= | ||||
|         let accept_id = format!( | ||||
|             "{}#accept", | ||||
|             follow.object_props.id_string().unwrap_or(String::new()) | ||||
|         ); | ||||
|         accept | ||||
|             .object_props | ||||
|             .set_id_string(accept_id) | ||||
|             .expect("Follow::accept_follow: id error"); | ||||
|         accept | ||||
|             .object_props | ||||
|             .set_to_link(from.clone().into_id()) | ||||
|             .expect("Follow::accept_follow: to error"); | ||||
|         accept | ||||
|             .object_props | ||||
|             .set_cc_link_vec::<Id>(vec![]) | ||||
|             .expect("Follow::accept_follow: cc error"); | ||||
|         accept | ||||
|             .accept_props | ||||
|             .set_actor_link::<Id>(target.clone().into_id()) | ||||
|             .expect("Follow::accept_follow: actor error"); | ||||
|         accept | ||||
|             .accept_props | ||||
|             .set_object_object(follow) | ||||
|             .expect("Follow::accept_follow: object error"); | ||||
| >>>>>>> Run rustfmt and rename instanceTests to instance_tests | ||||
|         broadcast(&*target, accept, vec![from.clone()]); | ||||
|         res | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromActivity<FollowAct, Connection> for Follow { | ||||
|     fn from_activity(conn: &Connection, follow: FollowAct, _actor: Id) -> Follow { | ||||
|         let from_id = follow | ||||
|             .follow_props | ||||
|             .actor_link::<Id>() | ||||
|             .map(|l| l.into()) | ||||
|             .unwrap_or_else(|_| { | ||||
|                 follow | ||||
|                     .follow_props | ||||
|                     .actor_object::<Person>() | ||||
|                     .expect("Follow::from_activity: actor not found error") | ||||
|                     .object_props | ||||
|                     .id_string() | ||||
|                     .expect("Follow::from_activity: actor not found error") | ||||
|             }); | ||||
|         let from = | ||||
|             User::from_url(conn, from_id).expect("Follow::from_activity: actor not found error"); | ||||
|         match User::from_url( | ||||
|             conn, | ||||
|             follow | ||||
|                 .follow_props | ||||
|                 .object | ||||
|                 .as_str() | ||||
|                 .expect("Follow::from_activity: target url parsing error") | ||||
|                 .to_string(), | ||||
|         ) { | ||||
|             Some(user) => Follow::accept_follow(conn, &from, &user, follow, from.id, user.id), | ||||
|             None => { | ||||
|                 let blog = Blog::from_url( | ||||
|                     conn, | ||||
|                     follow | ||||
|                         .follow_props | ||||
|                         .object | ||||
|                         .as_str() | ||||
|                         .expect("Follow::from_activity: target url parsing error") | ||||
|                         .to_string(), | ||||
|                 ).expect("Follow::from_activity: target not found error"); | ||||
|                 Follow::accept_follow(conn, &from, &blog, follow, from.id, blog.id) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Notify<Connection> for Follow { | ||||
|     fn notify(&self, conn: &Connection) { | ||||
|         Notification::insert( | ||||
|             conn, | ||||
|             NewNotification { | ||||
|                 kind: notification_kind::FOLLOW.to_string(), | ||||
|                 object_id: self.id, | ||||
|                 user_id: self.following_id, | ||||
|             }, | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Deletable<Connection, Undo> for Follow { | ||||
|     fn delete(&self, conn: &Connection) -> Undo { | ||||
|         diesel::delete(self) | ||||
|             .execute(conn) | ||||
|             .expect("Follow::delete: follow deletion error"); | ||||
| 
 | ||||
|         // delete associated notification if any
 | ||||
|         if let Some(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) { | ||||
|             diesel::delete(¬if) | ||||
|                 .execute(conn) | ||||
|                 .expect("Follow::delete: notification deletion error"); | ||||
|         } | ||||
| 
 | ||||
|         let mut undo = Undo::default(); | ||||
|         undo.undo_props | ||||
|             .set_actor_link( | ||||
|                 User::get(conn, self.follower_id) | ||||
|                     .expect("Follow::delete: actor error") | ||||
|                     .into_id(), | ||||
|             ) | ||||
|             .expect("Follow::delete: actor error"); | ||||
|         undo.object_props | ||||
|             .set_id_string(format!("{}/undo", self.ap_url)) | ||||
|             .expect("Follow::delete: id error"); | ||||
|         undo.undo_props | ||||
|             .set_object_object(self.into_activity(conn)) | ||||
|             .expect("Follow::delete: object error"); | ||||
|         undo | ||||
|     } | ||||
| 
 | ||||
|     fn delete_id(id: String, actor_id: String, conn: &Connection) { | ||||
|         if let Some(follow) = Follow::find_by_ap_url(conn, id) { | ||||
|             if let Some(user) = User::find_by_ap_url(conn, actor_id) { | ||||
|                 if user.id == follow.follower_id { | ||||
|                     follow.delete(conn); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -74,7 +74,7 @@ impl Instance { | ||||
| 
 | ||||
|     insert!(instances, NewInstance); | ||||
|     get!(instances); | ||||
|     find_by!(instances, find_by_domain, public_domain as String); | ||||
|     find_by!(instances, find_by_domain, public_domain as &str); | ||||
| 
 | ||||
|     pub fn toggle_block(&self, conn: &Connection) { | ||||
|         diesel::update(self) | ||||
| @ -84,13 +84,13 @@ impl Instance { | ||||
|     } | ||||
| 
 | ||||
|     /// id: AP object id
 | ||||
|     pub fn is_blocked(conn: &Connection, id: String) -> bool { | ||||
|     pub fn is_blocked(conn: &Connection, id: &str) -> bool { | ||||
|         for block in instances::table | ||||
|             .filter(instances::blocked.eq(true)) | ||||
|             .get_results::<Instance>(conn) | ||||
|             .expect("Instance::is_blocked: loading error") | ||||
|         { | ||||
|             if id.starts_with(format!("https://{}/", block.public_domain).as_str()) { | ||||
|             if id.starts_with(&format!("https://{}/", block.public_domain)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
| @ -99,12 +99,12 @@ impl Instance { | ||||
|     } | ||||
| 
 | ||||
|     pub fn has_admin(&self, conn: &Connection) -> bool { | ||||
|         users::table | ||||
|         !users::table | ||||
|             .filter(users::instance_id.eq(self.id)) | ||||
|             .filter(users::is_admin.eq(true)) | ||||
|             .load::<User>(conn) | ||||
|             .expect("Instance::has_admin: loading error") | ||||
|             .len() > 0 | ||||
|             .is_empty() | ||||
|     } | ||||
| 
 | ||||
|     pub fn main_admin(&self, conn: &Connection) -> User { | ||||
| @ -118,11 +118,11 @@ impl Instance { | ||||
| 
 | ||||
|     pub fn compute_box( | ||||
|         &self, | ||||
|         prefix: &'static str, | ||||
|         name: String, | ||||
|         box_name: &'static str, | ||||
|         prefix: &str, | ||||
|         name: &str, | ||||
|         box_name: &str, | ||||
|     ) -> String { | ||||
|         ap_url(format!( | ||||
|         ap_url(&format!( | ||||
|             "{instance}/{prefix}/{name}/{box_name}", | ||||
|             instance = self.public_domain, | ||||
|             prefix = prefix, | ||||
| @ -219,7 +219,7 @@ pub(crate) mod tests { | ||||
|             .map(|inst| { | ||||
|                 ( | ||||
|                     inst.clone(), | ||||
|                     Instance::find_by_domain(conn, inst.public_domain.clone()) | ||||
|                     Instance::find_by_domain(conn, &inst.public_domain) | ||||
|                         .unwrap_or_else(|| Instance::insert(conn, inst)), | ||||
|                 ) | ||||
|             }) | ||||
| @ -332,12 +332,12 @@ pub(crate) mod tests { | ||||
|                 0 | ||||
|             ); | ||||
|             assert_eq!( | ||||
|                 Instance::is_blocked(conn, format!("https://{}/something", inst.public_domain)), | ||||
|                 Instance::is_blocked(conn, &format!("https://{}/something", inst.public_domain)), | ||||
|                 inst.blocked | ||||
|             ); | ||||
|             assert_eq!( | ||||
|                 Instance::is_blocked(conn, format!("https://{}a/something", inst.public_domain)), | ||||
|                 Instance::find_by_domain(conn, format!("{}a", inst.public_domain)) | ||||
|                 Instance::is_blocked(conn, &format!("https://{}a/something", inst.public_domain)), | ||||
|                 Instance::find_by_domain(conn, &format!("{}a", inst.public_domain)) | ||||
|                     .map(|inst| inst.blocked) | ||||
|                     .unwrap_or(false) | ||||
|             ); | ||||
| @ -346,12 +346,12 @@ pub(crate) mod tests { | ||||
|             let inst = Instance::get(conn, inst.id).unwrap(); | ||||
|             assert_eq!(inst.blocked, blocked); | ||||
|             assert_eq!( | ||||
|                 Instance::is_blocked(conn, format!("https://{}/something", inst.public_domain)), | ||||
|                 Instance::is_blocked(conn, &format!("https://{}/something", inst.public_domain)), | ||||
|                 inst.blocked | ||||
|             ); | ||||
|             assert_eq!( | ||||
|                 Instance::is_blocked(conn, format!("https://{}a/something", inst.public_domain)), | ||||
|                 Instance::find_by_domain(conn, format!("{}a", inst.public_domain)) | ||||
|                 Instance::is_blocked(conn, &format!("https://{}a/something", inst.public_domain)), | ||||
|                 Instance::find_by_domain(conn, &format!("{}a", inst.public_domain)) | ||||
|                     .map(|inst| inst.blocked) | ||||
|                     .unwrap_or(false) | ||||
|             ); | ||||
|  | ||||
| @ -31,6 +31,11 @@ extern crate diesel_migrations; | ||||
| 
 | ||||
| use std::env; | ||||
| 
 | ||||
| #[cfg(not(any(feature = "sqlite", feature = "postgres")))] | ||||
| compile_error!("Either feature \"sqlite\" or \"postgres\" must be enabled for this crate."); | ||||
| #[cfg(all(feature = "sqlite", feature = "postgres"))] | ||||
| compile_error!("Either feature \"sqlite\" or \"postgres\" must be enabled for this crate."); | ||||
| 
 | ||||
| #[cfg(all(feature = "sqlite", not(feature = "postgres")))] | ||||
| pub type Connection = diesel::SqliteConnection; | ||||
| 
 | ||||
| @ -51,7 +56,7 @@ pub type Connection = diesel::PgConnection; | ||||
| /// Model::name_of_the_function(connection, String::new(), 0);
 | ||||
| /// ```
 | ||||
| macro_rules! find_by { | ||||
|     ($table:ident, $fn:ident, $($col:ident as $type:ident),+) => { | ||||
|     ($table:ident, $fn:ident, $($col:ident as $type:ty),+) => { | ||||
|         /// Try to find a $table with a given $col
 | ||||
|         pub fn $fn(conn: &crate::Connection, $($col: $type),+) -> Option<Self> { | ||||
|             $table::table | ||||
| @ -77,7 +82,7 @@ macro_rules! find_by { | ||||
| /// Model::name_of_the_function(connection, String::new());
 | ||||
| /// ```
 | ||||
| macro_rules! list_by { | ||||
|     ($table:ident, $fn:ident, $($col:ident as $type:ident),+) => { | ||||
|     ($table:ident, $fn:ident, $($col:ident as $type:ty),+) => { | ||||
|         /// Try to find a $table with a given $col
 | ||||
|         pub fn $fn(conn: &crate::Connection, $($col: $type),+) -> Vec<Self> { | ||||
|             $table::table | ||||
| @ -200,9 +205,9 @@ macro_rules! last { | ||||
| } | ||||
| 
 | ||||
| lazy_static! { | ||||
|     pub static ref BASE_URL: String = env::var("BASE_URL").unwrap_or(format!( | ||||
|     pub static ref BASE_URL: String = env::var("BASE_URL").unwrap_or_else(|_| format!( | ||||
|         "127.0.0.1:{}", | ||||
|         env::var("ROCKET_PORT").unwrap_or(String::from("8000")) | ||||
|         env::var("ROCKET_PORT").unwrap_or_else(|_| String::from("8000")) | ||||
|     )); | ||||
|     pub static ref USE_HTTPS: bool = env::var("USE_HTTPS").map(|val| val == "1").unwrap_or(true); | ||||
| } | ||||
| @ -215,16 +220,16 @@ static DB_NAME: &str = "plume_tests"; | ||||
| #[cfg(all(feature = "postgres", not(feature = "sqlite")))] | ||||
| lazy_static! { | ||||
|     pub static ref DATABASE_URL: String = | ||||
|         env::var("DATABASE_URL").unwrap_or(format!("postgres://plume:plume@localhost/{}", DB_NAME)); | ||||
|         env::var("DATABASE_URL").unwrap_or_else(|_| format!("postgres://plume:plume@localhost/{}", DB_NAME)); | ||||
| } | ||||
| 
 | ||||
| #[cfg(all(feature = "sqlite", not(feature = "postgres")))] | ||||
| lazy_static! { | ||||
|     pub static ref DATABASE_URL: String = | ||||
|         env::var("DATABASE_URL").unwrap_or(format!("{}.sqlite", DB_NAME)); | ||||
|         env::var("DATABASE_URL").unwrap_or_else(|_| format!("{}.sqlite", DB_NAME)); | ||||
| } | ||||
| 
 | ||||
| pub fn ap_url(url: String) -> String { | ||||
| pub fn ap_url(url: &str) -> String { | ||||
|     let scheme = if *USE_HTTPS { "https" } else { "http" }; | ||||
|     format!("{}://{}", scheme, url) | ||||
| } | ||||
|  | ||||
| @ -32,11 +32,11 @@ pub struct NewLike { | ||||
| impl Like { | ||||
|     insert!(likes, NewLike); | ||||
|     get!(likes); | ||||
|     find_by!(likes, find_by_ap_url, ap_url as String); | ||||
|     find_by!(likes, find_by_ap_url, ap_url as &str); | ||||
|     find_by!(likes, find_by_user_on_post, user_id as i32, post_id as i32); | ||||
| 
 | ||||
|     pub fn update_ap_url(&self, conn: &Connection) { | ||||
|         if self.ap_url.len() == 0 { | ||||
|         if self.ap_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(likes::ap_url.eq(format!( | ||||
|                     "{}/like/{}", | ||||
| @ -48,31 +48,31 @@ impl Like { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> activity::Like { | ||||
|     pub fn to_activity(&self, conn: &Connection) -> activity::Like { | ||||
|         let mut act = activity::Like::default(); | ||||
|         act.like_props | ||||
|             .set_actor_link( | ||||
|                 User::get(conn, self.user_id) | ||||
|                     .expect("Like::into_activity: user error") | ||||
|                     .expect("Like::to_activity: user error") | ||||
|                     .into_id(), | ||||
|             ) | ||||
|             .expect("Like::into_activity: actor error"); | ||||
|             .expect("Like::to_activity: actor error"); | ||||
|         act.like_props | ||||
|             .set_object_link( | ||||
|                 Post::get(conn, self.post_id) | ||||
|                     .expect("Like::into_activity: post error") | ||||
|                     .expect("Like::to_activity: post error") | ||||
|                     .into_id(), | ||||
|             ) | ||||
|             .expect("Like::into_activity: object error"); | ||||
|             .expect("Like::to_activity: object error"); | ||||
|         act.object_props | ||||
|             .set_to_link(Id::new(PUBLIC_VISIBILTY.to_string())) | ||||
|             .expect("Like::into_activity: to error"); | ||||
|             .expect("Like::to_activity: to error"); | ||||
|         act.object_props | ||||
|             .set_cc_link_vec::<Id>(vec![]) | ||||
|             .expect("Like::into_activity: cc error"); | ||||
|             .expect("Like::to_activity: cc error"); | ||||
|         act.object_props | ||||
|             .set_id_string(self.ap_url.clone()) | ||||
|             .expect("Like::into_activity: id error"); | ||||
|             .expect("Like::to_activity: id error"); | ||||
| 
 | ||||
|         act | ||||
|     } | ||||
| @ -85,23 +85,21 @@ impl FromActivity<activity::Like, Connection> for Like { | ||||
|             like.like_props | ||||
|                 .actor | ||||
|                 .as_str() | ||||
|                 .expect("Like::from_activity: actor error") | ||||
|                 .to_string(), | ||||
|                 .expect("Like::from_activity: actor error"), | ||||
|         ); | ||||
|         let post = Post::find_by_ap_url( | ||||
|             conn, | ||||
|             like.like_props | ||||
|                 .object | ||||
|                 .as_str() | ||||
|                 .expect("Like::from_activity: object error") | ||||
|                 .to_string(), | ||||
|                 .expect("Like::from_activity: object error"), | ||||
|         ); | ||||
|         let res = Like::insert( | ||||
|             conn, | ||||
|             NewLike { | ||||
|                 post_id: post.expect("Like::from_activity: post error").id, | ||||
|                 user_id: liker.expect("Like::from_activity: user error").id, | ||||
|                 ap_url: like.object_props.id_string().unwrap_or(String::from("")), | ||||
|                 ap_url: like.object_props.id_string().unwrap_or_default(), | ||||
|             }, | ||||
|         ); | ||||
|         res.notify(conn); | ||||
| @ -147,7 +145,7 @@ impl Deletable<Connection, activity::Undo> for Like { | ||||
|             ) | ||||
|             .expect("Like::delete: actor error"); | ||||
|         act.undo_props | ||||
|             .set_object_object(self.into_activity(conn)) | ||||
|             .set_object_object(self.to_activity(conn)) | ||||
|             .expect("Like::delete: object error"); | ||||
|         act.object_props | ||||
|             .set_id_string(format!("{}#delete", self.ap_url)) | ||||
| @ -162,8 +160,8 @@ impl Deletable<Connection, activity::Undo> for Like { | ||||
|         act | ||||
|     } | ||||
| 
 | ||||
|     fn delete_id(id: String, actor_id: String, conn: &Connection) { | ||||
|         if let Some(like) = Like::find_by_ap_url(conn, id.into()) { | ||||
|     fn delete_id(id: &str, actor_id: &str, conn: &Connection) { | ||||
|         if let Some(like) = Like::find_by_ap_url(conn, id) { | ||||
|             if let Some(user) = User::find_by_ap_url(conn, actor_id) { | ||||
|                 if user.id == like.user_id { | ||||
|                     like.delete(conn); | ||||
|  | ||||
| @ -110,9 +110,9 @@ impl Media { | ||||
| 
 | ||||
|     pub fn url(&self, conn: &Connection) -> String { | ||||
|         if self.is_remote { | ||||
|             self.remote_url.clone().unwrap_or(String::new()) | ||||
|             self.remote_url.clone().unwrap_or_default() | ||||
|         } else { | ||||
|             ap_url(format!( | ||||
|             ap_url(&format!( | ||||
|                 "{}/{}", | ||||
|                 Instance::get_local(conn) | ||||
|                     .expect("Media::url: local instance not found error") | ||||
| @ -154,13 +154,13 @@ impl Media { | ||||
|     } | ||||
| 
 | ||||
|     // TODO: merge with save_remote?
 | ||||
|     pub fn from_activity(conn: &Connection, image: Image) -> Option<Media> { | ||||
|     pub fn from_activity(conn: &Connection, image: &Image) -> Option<Media> { | ||||
|         let remote_url = image.object_props.url_string().ok()?; | ||||
|         let ext = remote_url | ||||
|             .rsplit('.') | ||||
|             .next() | ||||
|             .map(|ext| ext.to_owned()) | ||||
|             .unwrap_or("png".to_owned()); | ||||
|             .unwrap_or_else(|| String::from("png")); | ||||
|         let path = | ||||
|             Path::new("static") | ||||
|                 .join("media") | ||||
| @ -189,7 +189,7 @@ impl Media { | ||||
|                         .ok()? | ||||
|                         .into_iter() | ||||
|                         .next()? | ||||
|                         .into(), | ||||
|                         .as_ref(), | ||||
|                 )?.id, | ||||
|             }, | ||||
|         )) | ||||
|  | ||||
| @ -30,7 +30,7 @@ pub struct NewMention { | ||||
| impl Mention { | ||||
|     insert!(mentions, NewMention); | ||||
|     get!(mentions); | ||||
|     find_by!(mentions, find_by_ap_url, ap_url as String); | ||||
|     find_by!(mentions, find_by_ap_url, ap_url as &str); | ||||
|     list_by!(mentions, list_for_user, mentioned_id as i32); | ||||
|     list_by!(mentions, list_for_post, post_id as i32); | ||||
|     list_by!(mentions, list_for_comment, comment_id as i32); | ||||
| @ -54,12 +54,12 @@ impl Mention { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn build_activity(conn: &Connection, ment: String) -> link::Mention { | ||||
|         let user = User::find_by_fqn(conn, ment.clone()); | ||||
|     pub fn build_activity(conn: &Connection, ment: &str) -> link::Mention { | ||||
|         let user = User::find_by_fqn(conn, ment); | ||||
|         let mut mention = link::Mention::default(); | ||||
|         mention | ||||
|             .link_props | ||||
|             .set_href_string(user.clone().map(|u| u.ap_url).unwrap_or(String::new())) | ||||
|             .set_href_string(user.clone().map(|u| u.ap_url).unwrap_or_default()) | ||||
|             .expect("Mention::build_activity: href error"); | ||||
|         mention | ||||
|             .link_props | ||||
| @ -73,13 +73,13 @@ impl Mention { | ||||
|         let mut mention = link::Mention::default(); | ||||
|         mention | ||||
|             .link_props | ||||
|             .set_href_string(user.clone().map(|u| u.ap_url).unwrap_or(String::new())) | ||||
|             .set_href_string(user.clone().map(|u| u.ap_url).unwrap_or_default()) | ||||
|             .expect("Mention::to_activity: href error"); | ||||
|         mention | ||||
|             .link_props | ||||
|             .set_name_string( | ||||
|                 user.map(|u| format!("@{}", u.get_fqn(conn))) | ||||
|                     .unwrap_or(String::new()), | ||||
|                     .unwrap_or_default(), | ||||
|             ) | ||||
|             .expect("Mention::to_activity: mention error"); | ||||
|         mention | ||||
| @ -87,23 +87,23 @@ impl Mention { | ||||
| 
 | ||||
|     pub fn from_activity( | ||||
|         conn: &Connection, | ||||
|         ment: link::Mention, | ||||
|         ment: &link::Mention, | ||||
|         inside: i32, | ||||
|         in_post: bool, | ||||
|         notify: bool, | ||||
|     ) -> Option<Self> { | ||||
|         let ap_url = ment.link_props.href_string().ok()?; | ||||
|         let mentioned = User::find_by_ap_url(conn, ap_url)?; | ||||
|         let mentioned = User::find_by_ap_url(conn, &ap_url)?; | ||||
| 
 | ||||
|         if in_post { | ||||
|             Post::get(conn, inside.clone().into()).map(|post| { | ||||
|             Post::get(conn, inside).map(|post| { | ||||
|                 let res = Mention::insert( | ||||
|                     conn, | ||||
|                     NewMention { | ||||
|                         mentioned_id: mentioned.id, | ||||
|                         post_id: Some(post.id), | ||||
|                         comment_id: None, | ||||
|                         ap_url: ment.link_props.href_string().unwrap_or(String::new()), | ||||
|                         ap_url: ment.link_props.href_string().unwrap_or_default(), | ||||
|                     }, | ||||
|                 ); | ||||
|                 if notify { | ||||
| @ -112,14 +112,14 @@ impl Mention { | ||||
|                 res | ||||
|             }) | ||||
|         } else { | ||||
|             Comment::get(conn, inside.into()).map(|comment| { | ||||
|             Comment::get(conn, inside).map(|comment| { | ||||
|                 let res = Mention::insert( | ||||
|                     conn, | ||||
|                     NewMention { | ||||
|                         mentioned_id: mentioned.id, | ||||
|                         post_id: None, | ||||
|                         comment_id: Some(comment.id), | ||||
|                         ap_url: ment.link_props.href_string().unwrap_or(String::new()), | ||||
|                         ap_url: ment.link_props.href_string().unwrap_or_default(), | ||||
|                     }, | ||||
|                 ); | ||||
|                 if notify { | ||||
| @ -132,7 +132,9 @@ impl Mention { | ||||
| 
 | ||||
|     pub fn delete(&self, conn: &Connection) { | ||||
|         //find related notifications and delete them
 | ||||
|         Notification::find(conn, notification_kind::MENTION, self.id).map(|n| n.delete(conn)); | ||||
|         if let Some(n) = Notification::find(conn, notification_kind::MENTION, self.id) { | ||||
|             n.delete(conn) | ||||
|         } | ||||
|         diesel::delete(self) | ||||
|             .execute(conn) | ||||
|             .expect("Mention::delete: mention deletion error"); | ||||
| @ -141,7 +143,7 @@ impl Mention { | ||||
| 
 | ||||
| impl Notify<Connection> for Mention { | ||||
|     fn notify(&self, conn: &Connection) { | ||||
|         self.get_mentioned(conn).map(|m| { | ||||
|         if let Some(m) = self.get_mentioned(conn) { | ||||
|             Notification::insert( | ||||
|                 conn, | ||||
|                 NewNotification { | ||||
| @ -150,6 +152,6 @@ impl Notify<Connection> for Mention { | ||||
|                     user_id: m.id, | ||||
|                 }, | ||||
|             ); | ||||
|         }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,11 +13,11 @@ use users::User; | ||||
| use Connection; | ||||
| 
 | ||||
| pub mod notification_kind { | ||||
|     pub const COMMENT: &'static str = "COMMENT"; | ||||
|     pub const FOLLOW: &'static str = "FOLLOW"; | ||||
|     pub const LIKE: &'static str = "LIKE"; | ||||
|     pub const MENTION: &'static str = "MENTION"; | ||||
|     pub const RESHARE: &'static str = "RESHARE"; | ||||
|     pub const COMMENT: &str = "COMMENT"; | ||||
|     pub const FOLLOW: &str = "FOLLOW"; | ||||
|     pub const LIKE: &str = "LIKE"; | ||||
|     pub const MENTION: &str = "MENTION"; | ||||
|     pub const RESHARE: &str = "RESHARE"; | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Queryable, Identifiable, Serialize)] | ||||
|  | ||||
| @ -119,7 +119,7 @@ impl<'a> Provider<(&'a Connection, Option<i32>)> for Post { | ||||
|                     }) | ||||
|                     .collect() | ||||
|             }) | ||||
|             .unwrap_or(vec![]) | ||||
|             .unwrap_or_default() | ||||
|     } | ||||
| 
 | ||||
|     fn create( | ||||
| @ -151,8 +151,8 @@ impl Post { | ||||
|     insert!(posts, NewPost); | ||||
|     get!(posts); | ||||
|     update!(posts); | ||||
|     find_by!(posts, find_by_slug, slug as String, blog_id as i32); | ||||
|     find_by!(posts, find_by_ap_url, ap_url as String); | ||||
|     find_by!(posts, find_by_slug, slug as &str, blog_id as i32); | ||||
|     find_by!(posts, find_by_ap_url, ap_url as &str); | ||||
| 
 | ||||
|     pub fn list_by_tag(conn: &Connection, tag: String, (min, max): (i32, i32)) -> Vec<Post> { | ||||
|         use schema::tags; | ||||
| @ -372,7 +372,7 @@ impl Post { | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_ap_url(&self, conn: &Connection) -> Post { | ||||
|         if self.ap_url.len() == 0 { | ||||
|         if self.ap_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(posts::ap_url.eq(self.compute_id(conn))) | ||||
|                 .execute(conn) | ||||
| @ -389,16 +389,15 @@ impl Post { | ||||
|             .into_iter() | ||||
|             .map(|a| a.get_followers(conn)) | ||||
|             .collect::<Vec<Vec<User>>>(); | ||||
|         let to = followers.into_iter().fold(vec![], |mut acc, f| { | ||||
|         followers.into_iter().fold(vec![], |mut acc, f| { | ||||
|             for x in f { | ||||
|                 acc.push(x.ap_url); | ||||
|             } | ||||
|             acc | ||||
|         }); | ||||
|         to | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> Article { | ||||
|     pub fn to_activity(&self, conn: &Connection) -> Article { | ||||
|         let mut to = self.get_receivers_urls(conn); | ||||
|         to.push(PUBLIC_VISIBILTY.to_string()); | ||||
| 
 | ||||
| @ -408,7 +407,7 @@ impl Post { | ||||
|             .collect::<Vec<serde_json::Value>>(); | ||||
|         let mut tags_json = Tag::for_post(conn, self.id) | ||||
|             .into_iter() | ||||
|             .map(|t| json!(t.into_activity(conn))) | ||||
|             .map(|t| json!(t.to_activity(conn))) | ||||
|             .collect::<Vec<serde_json::Value>>(); | ||||
|         mentions_json.append(&mut tags_json); | ||||
| 
 | ||||
| @ -416,11 +415,11 @@ impl Post { | ||||
|         article | ||||
|             .object_props | ||||
|             .set_name_string(self.title.clone()) | ||||
|             .expect("Post::into_activity: name error"); | ||||
|             .expect("Post::to_activity: name error"); | ||||
|         article | ||||
|             .object_props | ||||
|             .set_id_string(self.ap_url.clone()) | ||||
|             .expect("Post::into_activity: id error"); | ||||
|             .expect("Post::to_activity: id error"); | ||||
| 
 | ||||
|         let mut authors = self | ||||
|             .get_authors(conn) | ||||
| @ -431,76 +430,76 @@ impl Post { | ||||
|         article | ||||
|             .object_props | ||||
|             .set_attributed_to_link_vec::<Id>(authors) | ||||
|             .expect("Post::into_activity: attributedTo error"); | ||||
|             .expect("Post::to_activity: attributedTo error"); | ||||
|         article | ||||
|             .object_props | ||||
|             .set_content_string(self.content.get().clone()) | ||||
|             .expect("Post::into_activity: content error"); | ||||
|             .expect("Post::to_activity: content error"); | ||||
|         article | ||||
|             .ap_object_props | ||||
|             .set_source_object(Source { | ||||
|                 content: self.source.clone(), | ||||
|                 media_type: String::from("text/markdown"), | ||||
|             }) | ||||
|             .expect("Post::into_activity: source error"); | ||||
|             .expect("Post::to_activity: source error"); | ||||
|         article | ||||
|             .object_props | ||||
|             .set_published_utctime(Utc.from_utc_datetime(&self.creation_date)) | ||||
|             .expect("Post::into_activity: published error"); | ||||
|             .expect("Post::to_activity: published error"); | ||||
|         article | ||||
|             .object_props | ||||
|             .set_summary_string(self.subtitle.clone()) | ||||
|             .expect("Post::into_activity: summary error"); | ||||
|             .expect("Post::to_activity: summary error"); | ||||
|         article.object_props.tag = Some(json!(mentions_json)); | ||||
| 
 | ||||
|         if let Some(media_id) = self.cover_id { | ||||
|             let media = Media::get(conn, media_id).expect("Post::into_activity: get cover error"); | ||||
|             let media = Media::get(conn, media_id).expect("Post::to_activity: get cover error"); | ||||
|             let mut cover = Image::default(); | ||||
|             cover | ||||
|                 .object_props | ||||
|                 .set_url_string(media.url(conn)) | ||||
|                 .expect("Post::into_activity: icon.url error"); | ||||
|                 .expect("Post::to_activity: icon.url error"); | ||||
|             if media.sensitive { | ||||
|                 cover | ||||
|                     .object_props | ||||
|                     .set_summary_string(media.content_warning.unwrap_or(String::new())) | ||||
|                     .expect("Post::into_activity: icon.summary error"); | ||||
|                     .set_summary_string(media.content_warning.unwrap_or_default()) | ||||
|                     .expect("Post::to_activity: icon.summary error"); | ||||
|             } | ||||
|             cover | ||||
|                 .object_props | ||||
|                 .set_content_string(media.alt_text) | ||||
|                 .expect("Post::into_activity: icon.content error"); | ||||
|                 .expect("Post::to_activity: icon.content error"); | ||||
|             cover | ||||
|                 .object_props | ||||
|                 .set_attributed_to_link_vec(vec![ | ||||
|                     User::get(conn, media.owner_id) | ||||
|                         .expect("Post::into_activity: media owner not found") | ||||
|                         .expect("Post::to_activity: media owner not found") | ||||
|                         .into_id(), | ||||
|                 ]) | ||||
|                 .expect("Post::into_activity: icon.attributedTo error"); | ||||
|                 .expect("Post::to_activity: icon.attributedTo error"); | ||||
|             article | ||||
|                 .object_props | ||||
|                 .set_icon_object(cover) | ||||
|                 .expect("Post::into_activity: icon error"); | ||||
|                 .expect("Post::to_activity: icon error"); | ||||
|         } | ||||
| 
 | ||||
|         article | ||||
|             .object_props | ||||
|             .set_url_string(self.ap_url.clone()) | ||||
|             .expect("Post::into_activity: url error"); | ||||
|             .expect("Post::to_activity: url error"); | ||||
|         article | ||||
|             .object_props | ||||
|             .set_to_link_vec::<Id>(to.into_iter().map(Id::new).collect()) | ||||
|             .expect("Post::into_activity: to error"); | ||||
|             .expect("Post::to_activity: to error"); | ||||
|         article | ||||
|             .object_props | ||||
|             .set_cc_link_vec::<Id>(vec![]) | ||||
|             .expect("Post::into_activity: cc error"); | ||||
|             .expect("Post::to_activity: cc error"); | ||||
|         article | ||||
|     } | ||||
| 
 | ||||
|     pub fn create_activity(&self, conn: &Connection) -> Create { | ||||
|         let article = self.into_activity(conn); | ||||
|         let article = self.to_activity(conn); | ||||
|         let mut act = Create::default(); | ||||
|         act.object_props | ||||
|             .set_id_string(format!("{}activity", self.ap_url)) | ||||
| @ -531,7 +530,7 @@ impl Post { | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_activity(&self, conn: &Connection) -> Update { | ||||
|         let article = self.into_activity(conn); | ||||
|         let article = self.to_activity(conn); | ||||
|         let mut act = Update::default(); | ||||
|         act.object_props | ||||
|             .set_id_string(format!("{}/update-{}", self.ap_url, Utc::now().timestamp())) | ||||
| @ -561,12 +560,12 @@ impl Post { | ||||
|         act | ||||
|     } | ||||
| 
 | ||||
|     pub fn handle_update(conn: &Connection, updated: Article) { | ||||
|     pub fn handle_update(conn: &Connection, updated: &Article) { | ||||
|         let id = updated | ||||
|             .object_props | ||||
|             .id_string() | ||||
|             .expect("Post::handle_update: id error"); | ||||
|         let mut post = Post::find_by_ap_url(conn, id).expect("Post::handle_update: finding error"); | ||||
|         let mut post = Post::find_by_ap_url(conn, &id).expect("Post::handle_update: finding error"); | ||||
| 
 | ||||
|         if let Ok(title) = updated.object_props.name_string() { | ||||
|             post.slug = title.to_kebab_case(); | ||||
| @ -598,7 +597,7 @@ impl Post { | ||||
|             let mut mentions = vec![]; | ||||
|             let mut tags = vec![]; | ||||
|             let mut hashtags = vec![]; | ||||
|             for tag in mention_tags.into_iter() { | ||||
|             for tag in mention_tags { | ||||
|                 serde_json::from_value::<link::Mention>(tag.clone()) | ||||
|                     .map(|m| mentions.push(m)) | ||||
|                     .ok(); | ||||
| @ -632,7 +631,7 @@ impl Post { | ||||
|                     m.link_props | ||||
|                         .href_string() | ||||
|                         .ok() | ||||
|                         .and_then(|ap_url| User::find_by_ap_url(conn, ap_url)) | ||||
|                         .and_then(|ap_url| User::find_by_ap_url(conn, &ap_url)) | ||||
|                         .map(|u| u.id), | ||||
|                     m, | ||||
|                 ) | ||||
| @ -651,9 +650,9 @@ impl Post { | ||||
|             .iter() | ||||
|             .map(|m| m.mentioned_id) | ||||
|             .collect::<HashSet<_>>(); | ||||
|         for (m, id) in mentions.iter() { | ||||
|         for (m, id) in &mentions { | ||||
|             if !old_user_mentioned.contains(&id) { | ||||
|                 Mention::from_activity(&*conn, m.clone(), self.id, true, true); | ||||
|                 Mention::from_activity(&*conn, &m, self.id, true, true); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -689,13 +688,13 @@ impl Post { | ||||
|             }) | ||||
|             .collect::<HashSet<_>>(); | ||||
| 
 | ||||
|         for t in tags.into_iter() { | ||||
|         for t in tags { | ||||
|             if !t | ||||
|                 .name_string() | ||||
|                 .map(|n| old_tags_name.contains(&n)) | ||||
|                 .unwrap_or(true) | ||||
|             { | ||||
|                 Tag::from_activity(conn, t, self.id, false); | ||||
|                 Tag::from_activity(conn, &t, self.id, false); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -726,13 +725,13 @@ impl Post { | ||||
|             }) | ||||
|             .collect::<HashSet<_>>(); | ||||
| 
 | ||||
|         for t in tags.into_iter() { | ||||
|         for t in tags { | ||||
|             if !t | ||||
|                 .name_string() | ||||
|                 .map(|n| old_tags_name.contains(&n)) | ||||
|                 .unwrap_or(true) | ||||
|             { | ||||
|                 Tag::from_activity(conn, t, self.id, true); | ||||
|                 Tag::from_activity(conn, &t, self.id, true); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -757,7 +756,7 @@ impl Post { | ||||
|     } | ||||
| 
 | ||||
|     pub fn compute_id(&self, conn: &Connection) -> String { | ||||
|         ap_url(format!( | ||||
|         ap_url(&format!( | ||||
|             "{}/~/{}/{}/", | ||||
|             BASE_URL.as_str(), | ||||
|             self.get_blog(conn).get_fqn(conn), | ||||
| @ -770,7 +769,7 @@ impl FromActivity<Article, Connection> for Post { | ||||
|     fn from_activity(conn: &Connection, article: Article, _actor: Id) -> Post { | ||||
|         if let Some(post) = Post::find_by_ap_url( | ||||
|             conn, | ||||
|             article.object_props.id_string().unwrap_or(String::new()), | ||||
|             &article.object_props.id_string().unwrap_or_default(), | ||||
|         ) { | ||||
|             post | ||||
|         } else { | ||||
| @ -781,12 +780,12 @@ impl FromActivity<Article, Connection> for Post { | ||||
|                 .into_iter() | ||||
|                 .fold((None, vec![]), |(blog, mut authors), link| { | ||||
|                     let url: String = link.into(); | ||||
|                     match User::from_url(conn, url.clone()) { | ||||
|                     match User::from_url(conn, &url) { | ||||
|                         Some(user) => { | ||||
|                             authors.push(user); | ||||
|                             (blog, authors) | ||||
|                         } | ||||
|                         None => (blog.or_else(|| Blog::from_url(conn, url)), authors), | ||||
|                         None => (blog.or_else(|| Blog::from_url(conn, &url)), authors), | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
| @ -794,7 +793,7 @@ impl FromActivity<Article, Connection> for Post { | ||||
|                 .object_props | ||||
|                 .icon_object::<Image>() | ||||
|                 .ok() | ||||
|                 .and_then(|img| Media::from_activity(conn, img).map(|m| m.id)); | ||||
|                 .and_then(|img| Media::from_activity(conn, &img).map(|m| m.id)); | ||||
| 
 | ||||
|             let title = article | ||||
|                 .object_props | ||||
| @ -805,7 +804,7 @@ impl FromActivity<Article, Connection> for Post { | ||||
|                 NewPost { | ||||
|                     blog_id: blog.expect("Post::from_activity: blog not found error").id, | ||||
|                     slug: title.to_kebab_case(), | ||||
|                     title: title, | ||||
|                     title, | ||||
|                     content: SafeString::new( | ||||
|                         &article | ||||
|                             .object_props | ||||
| @ -815,7 +814,7 @@ impl FromActivity<Article, Connection> for Post { | ||||
|                     published: true, | ||||
|                     license: String::from("CC-BY-SA"), // TODO
 | ||||
|                     // 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().unwrap_or( | ||||
|                     ap_url: article.object_props.url_string().unwrap_or_else(|_| | ||||
|                         article | ||||
|                             .object_props | ||||
|                             .id_string() | ||||
| @ -841,7 +840,7 @@ impl FromActivity<Article, Connection> for Post { | ||||
|                 }, | ||||
|             ); | ||||
| 
 | ||||
|             for author in authors.into_iter() { | ||||
|             for author in authors { | ||||
|                 PostAuthor::insert( | ||||
|                     conn, | ||||
|                     NewPostAuthor { | ||||
| @ -858,9 +857,9 @@ impl FromActivity<Article, Connection> for Post { | ||||
|                 .map(|s| s.to_camel_case()) | ||||
|                 .collect::<HashSet<_>>(); | ||||
|             if let Some(serde_json::Value::Array(tags)) = article.object_props.tag.clone() { | ||||
|                 for tag in tags.into_iter() { | ||||
|                 for tag in tags { | ||||
|                     serde_json::from_value::<link::Mention>(tag.clone()) | ||||
|                         .map(|m| Mention::from_activity(conn, m, post.id, true, true)) | ||||
|                         .map(|m| Mention::from_activity(conn, &m, post.id, true, true)) | ||||
|                         .ok(); | ||||
| 
 | ||||
|                     serde_json::from_value::<Hashtag>(tag.clone()) | ||||
| @ -868,7 +867,7 @@ impl FromActivity<Article, Connection> for Post { | ||||
|                             let tag_name = t | ||||
|                                 .name_string() | ||||
|                                 .expect("Post::from_activity: tag name error"); | ||||
|                             Tag::from_activity(conn, t, post.id, hashtags.remove(&tag_name)); | ||||
|                             Tag::from_activity(conn, &t, post.id, hashtags.remove(&tag_name)); | ||||
|                         }) | ||||
|                         .ok(); | ||||
|                 } | ||||
| @ -910,7 +909,7 @@ impl Deletable<Connection, Delete> for Post { | ||||
|         act | ||||
|     } | ||||
| 
 | ||||
|     fn delete_id(id: String, actor_id: String, conn: &Connection) { | ||||
|     fn delete_id(id: &str, actor_id: &str, conn: &Connection) { | ||||
|         let actor = User::find_by_ap_url(conn, actor_id); | ||||
|         let post = Post::find_by_ap_url(conn, id); | ||||
|         let can_delete = actor | ||||
|  | ||||
| @ -32,7 +32,7 @@ pub struct NewReshare { | ||||
| impl Reshare { | ||||
|     insert!(reshares, NewReshare); | ||||
|     get!(reshares); | ||||
|     find_by!(reshares, find_by_ap_url, ap_url as String); | ||||
|     find_by!(reshares, find_by_ap_url, ap_url as &str); | ||||
|     find_by!( | ||||
|         reshares, | ||||
|         find_by_user_on_post, | ||||
| @ -41,7 +41,7 @@ impl Reshare { | ||||
|     ); | ||||
| 
 | ||||
|     pub fn update_ap_url(&self, conn: &Connection) { | ||||
|         if self.ap_url.len() == 0 { | ||||
|         if self.ap_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(reshares::ap_url.eq(format!( | ||||
|                         "{}/reshare/{}", | ||||
| @ -74,31 +74,31 @@ impl Reshare { | ||||
|         User::get(conn, self.user_id) | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> Announce { | ||||
|     pub fn to_activity(&self, conn: &Connection) -> Announce { | ||||
|         let mut act = Announce::default(); | ||||
|         act.announce_props | ||||
|             .set_actor_link( | ||||
|                 User::get(conn, self.user_id) | ||||
|                     .expect("Reshare::into_activity: user error") | ||||
|                     .expect("Reshare::to_activity: user error") | ||||
|                     .into_id(), | ||||
|             ) | ||||
|             .expect("Reshare::into_activity: actor error"); | ||||
|             .expect("Reshare::to_activity: actor error"); | ||||
|         act.announce_props | ||||
|             .set_object_link( | ||||
|                 Post::get(conn, self.post_id) | ||||
|                     .expect("Reshare::into_activity: post error") | ||||
|                     .expect("Reshare::to_activity: post error") | ||||
|                     .into_id(), | ||||
|             ) | ||||
|             .expect("Reshare::into_activity: object error"); | ||||
|             .expect("Reshare::to_activity: object error"); | ||||
|         act.object_props | ||||
|             .set_id_string(self.ap_url.clone()) | ||||
|             .expect("Reshare::into_activity: id error"); | ||||
|             .expect("Reshare::to_activity: id error"); | ||||
|         act.object_props | ||||
|             .set_to_link(Id::new(PUBLIC_VISIBILTY.to_string())) | ||||
|             .expect("Reshare::into_activity: to error"); | ||||
|             .expect("Reshare::to_activity: to error"); | ||||
|         act.object_props | ||||
|             .set_cc_link_vec::<Id>(vec![]) | ||||
|             .expect("Reshare::into_activity: cc error"); | ||||
|             .expect("Reshare::to_activity: cc error"); | ||||
| 
 | ||||
|         act | ||||
|     } | ||||
| @ -112,7 +112,7 @@ impl FromActivity<Announce, Connection> for Reshare { | ||||
|                 .announce_props | ||||
|                 .actor_link::<Id>() | ||||
|                 .expect("Reshare::from_activity: actor error") | ||||
|                 .into(), | ||||
|                 .as_ref(), | ||||
|         ); | ||||
|         let post = Post::find_by_ap_url( | ||||
|             conn, | ||||
| @ -120,7 +120,7 @@ impl FromActivity<Announce, Connection> for Reshare { | ||||
|                 .announce_props | ||||
|                 .object_link::<Id>() | ||||
|                 .expect("Reshare::from_activity: object error") | ||||
|                 .into(), | ||||
|                 .as_ref(), | ||||
|         ); | ||||
|         let reshare = Reshare::insert( | ||||
|             conn, | ||||
| @ -130,7 +130,7 @@ impl FromActivity<Announce, Connection> for Reshare { | ||||
|                 ap_url: announce | ||||
|                     .object_props | ||||
|                     .id_string() | ||||
|                     .unwrap_or(String::from("")), | ||||
|                     .unwrap_or_default(), | ||||
|             }, | ||||
|         ); | ||||
|         reshare.notify(conn); | ||||
| @ -176,7 +176,7 @@ impl Deletable<Connection, Undo> for Reshare { | ||||
|             ) | ||||
|             .expect("Reshare::delete: actor error"); | ||||
|         act.undo_props | ||||
|             .set_object_object(self.into_activity(conn)) | ||||
|             .set_object_object(self.to_activity(conn)) | ||||
|             .expect("Reshare::delete: object error"); | ||||
|         act.object_props | ||||
|             .set_id_string(format!("{}#delete", self.ap_url)) | ||||
| @ -191,7 +191,7 @@ impl Deletable<Connection, Undo> for Reshare { | ||||
|         act | ||||
|     } | ||||
| 
 | ||||
|     fn delete_id(id: String, actor_id: String, conn: &Connection) { | ||||
|     fn delete_id(id: &str, actor_id: &str, conn: &Connection) { | ||||
|         if let Some(reshare) = Reshare::find_by_ap_url(conn, id) { | ||||
|             if let Some(actor) = User::find_by_ap_url(conn, actor_id) { | ||||
|                 if actor.id == reshare.user_id { | ||||
|  | ||||
| @ -24,24 +24,24 @@ pub struct NewTag { | ||||
| impl Tag { | ||||
|     insert!(tags, NewTag); | ||||
|     get!(tags); | ||||
|     find_by!(tags, find_by_name, tag as String); | ||||
|     find_by!(tags, find_by_name, tag as &str); | ||||
|     list_by!(tags, for_post, post_id as i32); | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> Hashtag { | ||||
|     pub fn to_activity(&self, conn: &Connection) -> Hashtag { | ||||
|         let mut ht = Hashtag::default(); | ||||
|         ht.set_href_string(ap_url(format!( | ||||
|         ht.set_href_string(ap_url(&format!( | ||||
|             "{}/tag/{}", | ||||
|             Instance::get_local(conn) | ||||
|                 .expect("Tag::into_activity: local instance not found error") | ||||
|                 .expect("Tag::to_activity: local instance not found error") | ||||
|                 .public_domain, | ||||
|             self.tag | ||||
|         ))).expect("Tag::into_activity: href error"); | ||||
|         ))).expect("Tag::to_activity: href error"); | ||||
|         ht.set_name_string(self.tag.clone()) | ||||
|             .expect("Tag::into_activity: name error"); | ||||
|             .expect("Tag::to_activity: name error"); | ||||
|         ht | ||||
|     } | ||||
| 
 | ||||
|     pub fn from_activity(conn: &Connection, tag: Hashtag, post: i32, is_hashtag: bool) -> Tag { | ||||
|     pub fn from_activity(conn: &Connection, tag: &Hashtag, post: i32, is_hashtag: bool) -> Tag { | ||||
|         Tag::insert( | ||||
|             conn, | ||||
|             NewTag { | ||||
| @ -54,15 +54,15 @@ impl Tag { | ||||
| 
 | ||||
|     pub fn build_activity(conn: &Connection, tag: String) -> Hashtag { | ||||
|         let mut ht = Hashtag::default(); | ||||
|         ht.set_href_string(ap_url(format!( | ||||
|         ht.set_href_string(ap_url(&format!( | ||||
|             "{}/tag/{}", | ||||
|             Instance::get_local(conn) | ||||
|                 .expect("Tag::into_activity: local instance not found error") | ||||
|                 .expect("Tag::to_activity: local instance not found error") | ||||
|                 .public_domain, | ||||
|             tag | ||||
|         ))).expect("Tag::into_activity: href error"); | ||||
|         ))).expect("Tag::to_activity: href error"); | ||||
|         ht.set_name_string(tag) | ||||
|             .expect("Tag::into_activity: name error"); | ||||
|             .expect("Tag::to_activity: name error"); | ||||
|         ht | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -43,8 +43,6 @@ use safe_string::SafeString; | ||||
| use schema::users; | ||||
| use {ap_url, Connection, BASE_URL, USE_HTTPS}; | ||||
| 
 | ||||
| pub const AUTH_COOKIE: &'static str = "user_id"; | ||||
| 
 | ||||
| pub type CustomPerson = CustomObject<ApSignature, Person>; | ||||
| 
 | ||||
| #[derive(Queryable, Identifiable, Serialize, Deserialize, Clone, Debug)] | ||||
| @ -89,14 +87,15 @@ pub struct NewUser { | ||||
|     pub avatar_id: Option<i32>, | ||||
| } | ||||
| 
 | ||||
| const USER_PREFIX: &'static str = "@"; | ||||
| pub const AUTH_COOKIE: &str = "user_id"; | ||||
| const USER_PREFIX: &str = "@"; | ||||
| 
 | ||||
| impl User { | ||||
|     insert!(users, NewUser); | ||||
|     get!(users); | ||||
|     find_by!(users, find_by_email, email as String); | ||||
|     find_by!(users, find_by_name, username as String, instance_id as i32); | ||||
|     find_by!(users, find_by_ap_url, ap_url as String); | ||||
|     find_by!(users, find_by_email, email as &str); | ||||
|     find_by!(users, find_by_name, username as &str, instance_id as i32); | ||||
|     find_by!(users, find_by_ap_url, ap_url as &str); | ||||
| 
 | ||||
|     pub fn one_by_instance(conn: &Connection) -> Vec<User> { | ||||
|         users::table | ||||
| @ -125,8 +124,7 @@ impl User { | ||||
|                 .count() | ||||
|                 .load(conn) | ||||
|                 .expect("User::delete: count author error") | ||||
|                 .iter() | ||||
|                 .next() | ||||
|                 .first() | ||||
|                 .unwrap_or(&0) > &0; | ||||
|             if !has_other_authors { | ||||
|                 Post::get(conn, post_id) | ||||
| @ -178,28 +176,25 @@ impl User { | ||||
|             .len() // TODO count in database?
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_local(conn: &Connection, username: String) -> Option<User> { | ||||
|     pub fn find_local(conn: &Connection, username: &str) -> Option<User> { | ||||
|         User::find_by_name(conn, username, Instance::local_id(conn)) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_fqn(conn: &Connection, fqn: String) -> Option<User> { | ||||
|         if fqn.contains("@") { | ||||
|     pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Option<User> { | ||||
|         if fqn.contains('@') { | ||||
|             // remote user
 | ||||
|             match Instance::find_by_domain( | ||||
|                 conn, | ||||
|                 String::from( | ||||
|                     fqn.split("@") | ||||
|                         .last() | ||||
|                         .expect("User::find_by_fqn: host error"), | ||||
|                 ), | ||||
|                 fqn.split('@') | ||||
|                     .last() | ||||
|                     .expect("User::find_by_fqn: host error"), | ||||
|             ) { | ||||
|                 Some(instance) => match User::find_by_name( | ||||
|                     conn, | ||||
|                     String::from( | ||||
|                         fqn.split("@") | ||||
|                             .nth(0) | ||||
|                             .expect("User::find_by_fqn: name error"), | ||||
|                     ), | ||||
|                     fqn.split('@') | ||||
|                         .nth(0) | ||||
|                         .expect("User::find_by_fqn: name error") | ||||
|                     , | ||||
|                     instance.id, | ||||
|                 ) { | ||||
|                     Some(u) => Some(u), | ||||
| @ -213,8 +208,8 @@ impl User { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn fetch_from_webfinger(conn: &Connection, acct: String) -> Option<User> { | ||||
|         match resolve(acct.clone(), *USE_HTTPS) { | ||||
|     fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Option<User> { | ||||
|         match resolve(acct.to_owned(), *USE_HTTPS) { | ||||
|             Ok(wf) => wf | ||||
|                 .links | ||||
|                 .into_iter() | ||||
| @ -222,7 +217,7 @@ impl User { | ||||
|                 .and_then(|l| { | ||||
|                     User::fetch_from_url( | ||||
|                         conn, | ||||
|                         l.href | ||||
|                         &l.href | ||||
|                             .expect("User::fetch_from_webginfer: href not found error"), | ||||
|                     ) | ||||
|                 }), | ||||
| @ -233,9 +228,9 @@ impl User { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn fetch(url: String) -> Option<CustomPerson> { | ||||
|     fn fetch(url: &str) -> Option<CustomPerson> { | ||||
|         let req = Client::new() | ||||
|             .get(&url[..]) | ||||
|             .get(url) | ||||
|             .header( | ||||
|                 ACCEPT, | ||||
|                 HeaderValue::from_str( | ||||
| @ -270,29 +265,28 @@ impl User { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn fetch_from_url(conn: &Connection, url: String) -> Option<User> { | ||||
|         User::fetch(url.clone()).map(|json| { | ||||
|     pub fn fetch_from_url(conn: &Connection, url: &str) -> Option<User> { | ||||
|         User::fetch(url).map(|json| { | ||||
|             (User::from_activity( | ||||
|                 conn, | ||||
|                 json, | ||||
|                 Url::parse(url.as_ref()) | ||||
|                 &json, | ||||
|                 Url::parse(url) | ||||
|                     .expect("User::fetch_from_url: url error") | ||||
|                     .host_str() | ||||
|                     .expect("User::fetch_from_url: host error") | ||||
|                     .to_string(), | ||||
|                     .expect("User::fetch_from_url: host error"), | ||||
|             )) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &Connection, acct: CustomPerson, inst: String) -> User { | ||||
|         let instance = match Instance::find_by_domain(conn, inst.clone()) { | ||||
|     fn from_activity(conn: &Connection, acct: &CustomPerson, inst: &str) -> User { | ||||
|         let instance = match Instance::find_by_domain(conn, inst) { | ||||
|             Some(instance) => instance, | ||||
|             None => { | ||||
|                 Instance::insert( | ||||
|                     conn, | ||||
|                     NewInstance { | ||||
|                         name: inst.clone(), | ||||
|                         public_domain: inst.clone(), | ||||
|                         name: inst.to_owned(), | ||||
|                         public_domain: inst.to_owned(), | ||||
|                         local: false, | ||||
|                         // We don't really care about all the following for remote instances
 | ||||
|                         long_description: SafeString::new(""), | ||||
| @ -335,7 +329,7 @@ impl User { | ||||
|                         .object | ||||
|                         .object_props | ||||
|                         .summary_string() | ||||
|                         .unwrap_or(String::new()), | ||||
|                         .unwrap_or_default(), | ||||
|                 ), | ||||
|                 email: None, | ||||
|                 hashed_password: None, | ||||
| @ -385,7 +379,7 @@ impl User { | ||||
|     } | ||||
| 
 | ||||
|     pub fn refetch(&self, conn: &Connection) { | ||||
|         User::fetch(self.ap_url.clone()).map(|json| { | ||||
|         User::fetch(&self.ap_url.clone()).map(|json| { | ||||
|             let avatar = Media::save_remote( | ||||
|                 conn, | ||||
|                 json.object | ||||
| @ -425,7 +419,7 @@ impl User { | ||||
|                             .object | ||||
|                             .object_props | ||||
|                             .summary_string() | ||||
|                             .unwrap_or(String::new()), | ||||
|                             .unwrap_or_default(), | ||||
|                     )), | ||||
|                     users::followers_endpoint.eq(json | ||||
|                         .object | ||||
| @ -440,13 +434,13 @@ impl User { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     pub fn hash_pass(pass: String) -> String { | ||||
|         bcrypt::hash(pass.as_str(), 10).expect("User::hash_pass: hashing error") | ||||
|     pub fn hash_pass(pass: &str) -> String { | ||||
|         bcrypt::hash(pass, 10).expect("User::hash_pass: hashing error") | ||||
|     } | ||||
| 
 | ||||
|     pub fn auth(&self, pass: String) -> bool { | ||||
|     pub fn auth(&self, pass: &str) -> bool { | ||||
|         if let Ok(valid) = bcrypt::verify( | ||||
|             pass.as_str(), | ||||
|             pass, | ||||
|             self.hashed_password | ||||
|                 .clone() | ||||
|                 .expect("User::auth: no password error") | ||||
| @ -460,38 +454,38 @@ impl User { | ||||
| 
 | ||||
|     pub fn update_boxes(&self, conn: &Connection) { | ||||
|         let instance = self.get_instance(conn); | ||||
|         if self.outbox_url.len() == 0 { | ||||
|         if self.outbox_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(users::outbox_url.eq(instance.compute_box( | ||||
|                     USER_PREFIX, | ||||
|                     self.username.clone(), | ||||
|                     &self.username, | ||||
|                     "outbox", | ||||
|                 ))) | ||||
|                 .execute(conn) | ||||
|                 .expect("User::update_boxes: outbox update error"); | ||||
|         } | ||||
| 
 | ||||
|         if self.inbox_url.len() == 0 { | ||||
|         if self.inbox_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(users::inbox_url.eq(instance.compute_box( | ||||
|                     USER_PREFIX, | ||||
|                     self.username.clone(), | ||||
|                     &self.username, | ||||
|                     "inbox", | ||||
|                 ))) | ||||
|                 .execute(conn) | ||||
|                 .expect("User::update_boxes: inbox update error"); | ||||
|         } | ||||
| 
 | ||||
|         if self.ap_url.len() == 0 { | ||||
|         if self.ap_url.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(users::ap_url.eq(instance.compute_box(USER_PREFIX, self.username.clone(), ""))) | ||||
|                 .set(users::ap_url.eq(instance.compute_box(USER_PREFIX, &self.username, ""))) | ||||
|                 .execute(conn) | ||||
|                 .expect("User::update_boxes: ap_url update error"); | ||||
|         } | ||||
| 
 | ||||
|         if self.shared_inbox_url.is_none() { | ||||
|             diesel::update(self) | ||||
|                 .set(users::shared_inbox_url.eq(ap_url(format!( | ||||
|                 .set(users::shared_inbox_url.eq(ap_url(&format!( | ||||
|                         "{}/inbox", | ||||
|                         Instance::get_local(conn) | ||||
|                             .expect("User::update_boxes: local instance not found error") | ||||
| @ -501,11 +495,11 @@ impl User { | ||||
|                 .expect("User::update_boxes: shared inbox update error"); | ||||
|         } | ||||
| 
 | ||||
|         if self.followers_endpoint.len() == 0 { | ||||
|         if self.followers_endpoint.is_empty() { | ||||
|             diesel::update(self) | ||||
|                 .set(users::followers_endpoint.eq(instance.compute_box( | ||||
|                     USER_PREFIX, | ||||
|                     self.username.clone(), | ||||
|                     &self.username, | ||||
|                     "followers", | ||||
|                 ))) | ||||
|                 .execute(conn) | ||||
| @ -660,52 +654,52 @@ impl User { | ||||
| 
 | ||||
|     pub fn is_followed_by(&self, conn: &Connection, other_id: i32) -> bool { | ||||
|         use schema::follows; | ||||
|         follows::table | ||||
|         !follows::table | ||||
|             .filter(follows::follower_id.eq(other_id)) | ||||
|             .filter(follows::following_id.eq(self.id)) | ||||
|             .load::<Follow>(conn) | ||||
|             .expect("User::is_followed_by: loading error") | ||||
|             .len() > 0 // TODO count in database?
 | ||||
|             .is_empty() // TODO count in database?
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_following(&self, conn: &Connection, other_id: i32) -> bool { | ||||
|         use schema::follows; | ||||
|         follows::table | ||||
|         !follows::table | ||||
|             .filter(follows::follower_id.eq(self.id)) | ||||
|             .filter(follows::following_id.eq(other_id)) | ||||
|             .load::<Follow>(conn) | ||||
|             .expect("User::is_following: loading error") | ||||
|             .len() > 0 // TODO count in database?
 | ||||
|             .is_empty() // TODO count in database?
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn has_liked(&self, conn: &Connection, post: &Post) -> bool { | ||||
|         use schema::likes; | ||||
|         likes::table | ||||
|         !likes::table | ||||
|             .filter(likes::post_id.eq(post.id)) | ||||
|             .filter(likes::user_id.eq(self.id)) | ||||
|             .load::<Like>(conn) | ||||
|             .expect("User::has_liked: loading error") | ||||
|             .len() > 0 // TODO count in database?
 | ||||
|             .is_empty() // TODO count in database?
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn has_reshared(&self, conn: &Connection, post: &Post) -> bool { | ||||
|         use schema::reshares; | ||||
|         reshares::table | ||||
|         !reshares::table | ||||
|             .filter(reshares::post_id.eq(post.id)) | ||||
|             .filter(reshares::user_id.eq(self.id)) | ||||
|             .load::<Reshare>(conn) | ||||
|             .expect("User::has_reshared: loading error") | ||||
|             .len() > 0 // TODO count in database?
 | ||||
|             .is_empty() // TODO count in database?
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_author_in(&self, conn: &Connection, blog: Blog) -> bool { | ||||
|     pub fn is_author_in(&self, conn: &Connection, blog: &Blog) -> bool { | ||||
|         use schema::blog_authors; | ||||
|         blog_authors::table | ||||
|         !blog_authors::table | ||||
|             .filter(blog_authors::author_id.eq(self.id)) | ||||
|             .filter(blog_authors::blog_id.eq(blog.id)) | ||||
|             .load::<BlogAuthor>(conn) | ||||
|             .expect("User::is_author_in: loading error") | ||||
|             .len() > 0 // TODO count in database?
 | ||||
|             .is_empty() // TODO count in database?
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_keypair(&self) -> PKey<Private> { | ||||
| @ -719,64 +713,64 @@ impl User { | ||||
|         ).expect("User::get_keypair: private key deserialization error") | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &Connection) -> CustomPerson { | ||||
|     pub fn to_activity(&self, conn: &Connection) -> CustomPerson { | ||||
|         let mut actor = Person::default(); | ||||
|         actor | ||||
|             .object_props | ||||
|             .set_id_string(self.ap_url.clone()) | ||||
|             .expect("User::into_activity: id error"); | ||||
|             .expect("User::to_activity: id error"); | ||||
|         actor | ||||
|             .object_props | ||||
|             .set_name_string(self.display_name.clone()) | ||||
|             .expect("User::into_activity: name error"); | ||||
|             .expect("User::to_activity: name error"); | ||||
|         actor | ||||
|             .object_props | ||||
|             .set_summary_string(self.summary.get().clone()) | ||||
|             .expect("User::into_activity: summary error"); | ||||
|             .expect("User::to_activity: summary error"); | ||||
|         actor | ||||
|             .object_props | ||||
|             .set_url_string(self.ap_url.clone()) | ||||
|             .expect("User::into_activity: url error"); | ||||
|             .expect("User::to_activity: url error"); | ||||
|         actor | ||||
|             .ap_actor_props | ||||
|             .set_inbox_string(self.inbox_url.clone()) | ||||
|             .expect("User::into_activity: inbox error"); | ||||
|             .expect("User::to_activity: inbox error"); | ||||
|         actor | ||||
|             .ap_actor_props | ||||
|             .set_outbox_string(self.outbox_url.clone()) | ||||
|             .expect("User::into_activity: outbox error"); | ||||
|             .expect("User::to_activity: outbox error"); | ||||
|         actor | ||||
|             .ap_actor_props | ||||
|             .set_preferred_username_string(self.username.clone()) | ||||
|             .expect("User::into_activity: preferredUsername error"); | ||||
|             .expect("User::to_activity: preferredUsername error"); | ||||
|         actor | ||||
|             .ap_actor_props | ||||
|             .set_followers_string(self.followers_endpoint.clone()) | ||||
|             .expect("User::into_activity: followers error"); | ||||
|             .expect("User::to_activity: followers error"); | ||||
| 
 | ||||
|         let mut endpoints = Endpoint::default(); | ||||
|         endpoints | ||||
|             .set_shared_inbox_string(ap_url(format!("{}/inbox/", BASE_URL.as_str()))) | ||||
|             .expect("User::into_activity: endpoints.sharedInbox error"); | ||||
|             .set_shared_inbox_string(ap_url(&format!("{}/inbox/", BASE_URL.as_str()))) | ||||
|             .expect("User::to_activity: endpoints.sharedInbox error"); | ||||
|         actor | ||||
|             .ap_actor_props | ||||
|             .set_endpoints_endpoint(endpoints) | ||||
|             .expect("User::into_activity: endpoints error"); | ||||
|             .expect("User::to_activity: endpoints error"); | ||||
| 
 | ||||
|         let mut public_key = PublicKey::default(); | ||||
|         public_key | ||||
|             .set_id_string(format!("{}#main-key", self.ap_url)) | ||||
|             .expect("User::into_activity: publicKey.id error"); | ||||
|             .expect("User::to_activity: publicKey.id error"); | ||||
|         public_key | ||||
|             .set_owner_string(self.ap_url.clone()) | ||||
|             .expect("User::into_activity: publicKey.owner error"); | ||||
|             .expect("User::to_activity: publicKey.owner error"); | ||||
|         public_key | ||||
|             .set_public_key_pem_string(self.public_key.clone()) | ||||
|             .expect("User::into_activity: publicKey.publicKeyPem error"); | ||||
|             .expect("User::to_activity: publicKey.publicKeyPem error"); | ||||
|         let mut ap_signature = ApSignature::default(); | ||||
|         ap_signature | ||||
|             .set_public_key_publickey(public_key) | ||||
|             .expect("User::into_activity: publicKey error"); | ||||
|             .expect("User::to_activity: publicKey error"); | ||||
| 
 | ||||
|         let mut avatar = Image::default(); | ||||
|         avatar | ||||
| @ -784,13 +778,13 @@ impl User { | ||||
|             .set_url_string( | ||||
|                 self.avatar_id | ||||
|                     .and_then(|id| Media::get(conn, id).map(|m| m.url(conn))) | ||||
|                     .unwrap_or(String::new()), | ||||
|                     .unwrap_or_default(), | ||||
|             ) | ||||
|             .expect("User::into_activity: icon.url error"); | ||||
|             .expect("User::to_activity: icon.url error"); | ||||
|         actor | ||||
|             .object_props | ||||
|             .set_icon_object(avatar) | ||||
|             .expect("User::into_activity: icon error"); | ||||
|             .expect("User::to_activity: icon error"); | ||||
| 
 | ||||
|         CustomPerson::new(actor, ap_signature) | ||||
|     } | ||||
| @ -798,7 +792,7 @@ impl User { | ||||
|     pub fn to_json(&self, conn: &Connection) -> serde_json::Value { | ||||
|         let mut json = serde_json::to_value(self).expect("User::to_json: serializing error"); | ||||
|         json["fqn"] = serde_json::Value::String(self.get_fqn(conn)); | ||||
|         json["name"] = if self.display_name.len() > 0 { | ||||
|         json["name"] = if !self.display_name.is_empty() { | ||||
|             json!(self.display_name) | ||||
|         } else { | ||||
|             json!(self.get_fqn(conn)) | ||||
| @ -806,7 +800,7 @@ impl User { | ||||
|         json["avatar"] = json!( | ||||
|             self.avatar_id | ||||
|                 .and_then(|id| Media::get(conn, id).map(|m| m.url(conn))) | ||||
|                 .unwrap_or("/static/default-avatar.png".to_string()) | ||||
|                 .unwrap_or_else(|| String::from("/static/default-avatar.png")) | ||||
|         ); | ||||
|         json | ||||
|     } | ||||
| @ -831,7 +825,7 @@ impl User { | ||||
|                     mime_type: Some(String::from("application/atom+xml")), | ||||
|                     href: Some(self.get_instance(conn).compute_box( | ||||
|                         USER_PREFIX, | ||||
|                         self.username.clone(), | ||||
|                         &self.username, | ||||
|                         "feed.atom", | ||||
|                     )), | ||||
|                     template: None, | ||||
| @ -846,11 +840,11 @@ impl User { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn from_url(conn: &Connection, url: String) -> Option<User> { | ||||
|         User::find_by_ap_url(conn, url.clone()).or_else(|| { | ||||
|     pub fn from_url(conn: &Connection, url: &str) -> Option<User> { | ||||
|         User::find_by_ap_url(conn, url).or_else(|| { | ||||
|             // The requested user was not in the DB
 | ||||
|             // We try to fetch it if it is remote
 | ||||
|             if Url::parse(url.as_ref()) | ||||
|             if Url::parse(&url) | ||||
|                 .expect("User::from_url: url error") | ||||
|                 .host_str() | ||||
|                 .expect("User::from_url: host error") != BASE_URL.as_str() | ||||
| @ -916,7 +910,7 @@ impl Signer for User { | ||||
|         format!("{}#main-key", self.ap_url) | ||||
|     } | ||||
| 
 | ||||
|     fn sign(&self, to_sign: String) -> Vec<u8> { | ||||
|     fn sign(&self, to_sign: &str) -> Vec<u8> { | ||||
|         let key = self.get_keypair(); | ||||
|         let mut signer = sign::Signer::new(MessageDigest::sha256(), &key) | ||||
|             .expect("User::sign: initialization error"); | ||||
| @ -928,7 +922,7 @@ impl Signer for User { | ||||
|             .expect("User::sign: finalization error") | ||||
|     } | ||||
| 
 | ||||
|     fn verify(&self, data: String, signature: Vec<u8>) -> bool { | ||||
|     fn verify(&self, data: &str, signature: &[u8]) -> bool { | ||||
|         let key = PKey::from_rsa( | ||||
|             Rsa::public_key_from_pem(self.public_key.as_ref()) | ||||
|                 .expect("User::verify: pem parsing error"), | ||||
| @ -951,7 +945,7 @@ impl NewUser { | ||||
|         username: String, | ||||
|         display_name: String, | ||||
|         is_admin: bool, | ||||
|         summary: String, | ||||
|         summary: &str, | ||||
|         email: String, | ||||
|         password: String, | ||||
|     ) -> User { | ||||
| @ -959,12 +953,12 @@ impl NewUser { | ||||
|         User::insert( | ||||
|             conn, | ||||
|             NewUser { | ||||
|                 username: username, | ||||
|                 display_name: display_name, | ||||
|                 username, | ||||
|                 display_name, | ||||
|                 outbox_url: String::from(""), | ||||
|                 inbox_url: String::from(""), | ||||
|                 is_admin: is_admin, | ||||
|                 summary: SafeString::new(&summary), | ||||
|                 is_admin, | ||||
|                 summary: SafeString::new(summary), | ||||
|                 email: Some(email), | ||||
|                 hashed_password: Some(password), | ||||
|                 instance_id: Instance::local_id(conn), | ||||
| @ -998,7 +992,7 @@ pub(crate) mod tests { | ||||
|                 "admin".to_owned(), | ||||
|                 "The admin".to_owned(), | ||||
|                 true, | ||||
|                 "Hello there, I'm the admin".to_owned(), | ||||
|                 "Hello there, I'm the admin", | ||||
|                 "admin@example.com".to_owned(), | ||||
|                 "invalid_admin_password".to_owned(), | ||||
|             ), | ||||
| @ -1007,7 +1001,7 @@ pub(crate) mod tests { | ||||
|                 "user".to_owned(), | ||||
|                 "Some user".to_owned(), | ||||
|                 false, | ||||
|                 "Hello there, I'm no one".to_owned(), | ||||
|                 "Hello there, I'm no one", | ||||
|                 "user@example.com".to_owned(), | ||||
|                 "invalid_user_password".to_owned(), | ||||
|             ), | ||||
| @ -1016,7 +1010,7 @@ pub(crate) mod tests { | ||||
|                 "other".to_owned(), | ||||
|                 "Another user".to_owned(), | ||||
|                 false, | ||||
|                 "Hello there, I'm someone else".to_owned(), | ||||
|                 "Hello there, I'm someone else", | ||||
|                 "other@example.com".to_owned(), | ||||
|                 "invalid_other_password".to_owned(), | ||||
|             ), | ||||
| @ -1037,25 +1031,25 @@ pub(crate) mod tests { | ||||
|                 "test".to_owned(), | ||||
|                 "test user".to_owned(), | ||||
|                 false, | ||||
|                 "Hello I'm a test".to_owned(), | ||||
|                 "Hello I'm a test", | ||||
|                 "test@example.com".to_owned(), | ||||
|                 User::hash_pass("test_password".to_owned()), | ||||
|                 User::hash_pass("test_password"), | ||||
|             ); | ||||
|             test_user.update_boxes(conn); | ||||
| 
 | ||||
|             assert_eq!( | ||||
|                 test_user.id, | ||||
|                 User::find_by_name(conn, "test".to_owned(), Instance::local_id(conn)) | ||||
|                 User::find_by_name(conn, "test", Instance::local_id(conn)) | ||||
|                     .unwrap() | ||||
|                     .id | ||||
|             ); | ||||
|             assert_eq!( | ||||
|                 test_user.id, | ||||
|                 User::find_by_fqn(conn, test_user.get_fqn(conn)).unwrap().id | ||||
|                 User::find_by_fqn(conn, &test_user.get_fqn(conn)).unwrap().id | ||||
|             ); | ||||
|             assert_eq!( | ||||
|                 test_user.id, | ||||
|                 User::find_by_email(conn, "test@example.com".to_owned()) | ||||
|                 User::find_by_email(conn, "test@example.com") | ||||
|                     .unwrap() | ||||
|                     .id | ||||
|             ); | ||||
| @ -1063,7 +1057,7 @@ pub(crate) mod tests { | ||||
|                 test_user.id, | ||||
|                 User::find_by_ap_url( | ||||
|                     conn, | ||||
|                     format!( | ||||
|                     &format!( | ||||
|                         "https://{}/@/{}/", | ||||
|                         Instance::get_local(conn).unwrap().public_domain, | ||||
|                         "test" | ||||
| @ -1138,14 +1132,14 @@ pub(crate) mod tests { | ||||
|                 "test".to_owned(), | ||||
|                 "test user".to_owned(), | ||||
|                 false, | ||||
|                 "Hello I'm a test".to_owned(), | ||||
|                 "Hello I'm a test", | ||||
|                 "test@example.com".to_owned(), | ||||
|                 User::hash_pass("test_password".to_owned()), | ||||
|                 User::hash_pass("test_password"), | ||||
|             ); | ||||
|             test_user.update_boxes(conn); | ||||
| 
 | ||||
|             assert!(test_user.auth("test_password".to_owned())); | ||||
|             assert!(!test_user.auth("other_password".to_owned())); | ||||
|             assert!(test_user.auth("test_password")); | ||||
|             assert!(!test_user.auth("other_password")); | ||||
| 
 | ||||
|             Ok(()) | ||||
|         }); | ||||
|  | ||||
| @ -20,10 +20,10 @@ struct OAuthRequest { | ||||
| 
 | ||||
| #[get("/oauth2?<query>")] | ||||
| fn oauth(query: OAuthRequest, conn: DbConn) -> Json<serde_json::Value> { | ||||
|     let app = App::find_by_client_id(&*conn, query.client_id).expect("OAuth request from unknown client"); | ||||
|     let app = App::find_by_client_id(&*conn, &query.client_id).expect("OAuth request from unknown client"); | ||||
|     if app.client_secret == query.client_secret { | ||||
|         if let Some(user) = User::find_local(&*conn, query.username) { | ||||
|             if user.auth(query.password) { | ||||
|         if let Some(user) = User::find_local(&*conn, &query.username) { | ||||
|             if user.auth(&query.password) { | ||||
|                 let token = ApiToken::insert(&*conn, NewApiToken { | ||||
|                     app_id: app.id, | ||||
|                     user_id: user.id, | ||||
| @ -42,7 +42,7 @@ fn oauth(query: OAuthRequest, conn: DbConn) -> Json<serde_json::Value> { | ||||
|             // Making fake password verification to avoid different
 | ||||
|             // response times that would make it possible to know
 | ||||
|             // if a username is registered or not.
 | ||||
|             User::get(&*conn, 1).unwrap().auth(query.password); | ||||
|             User::get(&*conn, 1).unwrap().auth(&query.password); | ||||
|             Json(json!({ | ||||
|                 "error": "Invalid credentials" | ||||
|             })) | ||||
|  | ||||
							
								
								
									
										18
									
								
								src/inbox.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/inbox.rs
									
									
									
									
									
								
							| @ -48,11 +48,11 @@ pub trait Inbox { | ||||
|                 "Delete" => { | ||||
|                     let act: Delete = serde_json::from_value(act.clone())?; | ||||
|                     Post::delete_id( | ||||
|                         act.delete_props | ||||
|                         &act.delete_props | ||||
|                             .object_object::<Tombstone>()? | ||||
|                             .object_props | ||||
|                             .id_string()?, | ||||
|                         actor_id.into(), | ||||
|                         actor_id.as_ref(), | ||||
|                         conn, | ||||
|                     ); | ||||
|                     Ok(()) | ||||
| @ -77,33 +77,33 @@ pub trait Inbox { | ||||
|                     { | ||||
|                         "Like" => { | ||||
|                             likes::Like::delete_id( | ||||
|                                 act.undo_props | ||||
|                                 &act.undo_props | ||||
|                                     .object_object::<Like>()? | ||||
|                                     .object_props | ||||
|                                     .id_string()?, | ||||
|                                 actor_id.into(), | ||||
|                                 actor_id.as_ref(), | ||||
|                                 conn, | ||||
|                             ); | ||||
|                             Ok(()) | ||||
|                         } | ||||
|                         "Announce" => { | ||||
|                             Reshare::delete_id( | ||||
|                                 act.undo_props | ||||
|                                 &act.undo_props | ||||
|                                     .object_object::<Announce>()? | ||||
|                                     .object_props | ||||
|                                     .id_string()?, | ||||
|                                 actor_id.into(), | ||||
|                                 actor_id.as_ref(), | ||||
|                                 conn, | ||||
|                             ); | ||||
|                             Ok(()) | ||||
|                         } | ||||
|                         "Follow" => { | ||||
|                             Follow::delete_id( | ||||
|                                 act.undo_props | ||||
|                                 &act.undo_props | ||||
|                                     .object_object::<FollowAct>()? | ||||
|                                     .object_props | ||||
|                                     .id_string()?, | ||||
|                                 actor_id.into(), | ||||
|                                 actor_id.as_ref(), | ||||
|                                 conn, | ||||
|                             ); | ||||
|                             Ok(()) | ||||
| @ -113,7 +113,7 @@ pub trait Inbox { | ||||
|                 } | ||||
|                 "Update" => { | ||||
|                     let act: Update = serde_json::from_value(act.clone())?; | ||||
|                     Post::handle_update(conn, act.update_props.object_object()?); | ||||
|                     Post::handle_update(conn, &act.update_props.object_object()?); | ||||
|                     Ok(()) | ||||
|                 } | ||||
|                 _ => Err(InboxError::InvalidType)?, | ||||
|  | ||||
| @ -24,7 +24,7 @@ use routes::Page; | ||||
| 
 | ||||
| #[get("/~/<name>?<page>", rank = 2)] | ||||
| fn paginated_details(name: String, conn: DbConn, user: Option<User>, page: Page) -> Template { | ||||
|     may_fail!(user.map(|u| u.to_json(&*conn)), Blog::find_by_fqn(&*conn, name), "Requested blog couldn't be found", |blog| { | ||||
|     may_fail!(user.map(|u| u.to_json(&*conn)), Blog::find_by_fqn(&*conn, &name), "Requested blog couldn't be found", |blog| { | ||||
|         let posts = Post::blog_page(&*conn, &blog, page.limits()); | ||||
|         let articles = Post::get_for_blog(&*conn, &blog); | ||||
|         let authors = &blog.list_authors(&*conn); | ||||
| @ -32,7 +32,7 @@ fn paginated_details(name: String, conn: DbConn, user: Option<User>, page: Page) | ||||
|         Template::render("blogs/details", json!({ | ||||
|             "blog": &blog.to_json(&*conn), | ||||
|             "account": user.clone().map(|u| u.to_json(&*conn)), | ||||
|             "is_author": user.map(|x| x.is_author_in(&*conn, blog.clone())), | ||||
|             "is_author": user.map(|x| x.is_author_in(&*conn, &blog)), | ||||
|             "posts": posts.into_iter().map(|p| p.to_json(&*conn)).collect::<Vec<serde_json::Value>>(), | ||||
|             "authors": authors.into_iter().map(|u| u.to_json(&*conn)).collect::<Vec<serde_json::Value>>(), | ||||
|             "n_authors": authors.len(), | ||||
| @ -50,8 +50,8 @@ fn details(name: String, conn: DbConn, user: Option<User>) -> Template { | ||||
| 
 | ||||
| #[get("/~/<name>", rank = 1)] | ||||
| fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<CustomGroup>> { | ||||
|     let blog = Blog::find_local(&*conn, name)?; | ||||
|     Some(ActivityStream::new(blog.into_activity(&*conn))) | ||||
|     let blog = Blog::find_local(&*conn, &name)?; | ||||
|     Some(ActivityStream::new(blog.to_activity(&*conn))) | ||||
| } | ||||
| 
 | ||||
| #[get("/blogs/new")] | ||||
| @ -67,7 +67,7 @@ fn new(user: User, conn: DbConn) -> Template { | ||||
| fn new_auth() -> Flash<Redirect>{ | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to create a new blog", | ||||
|         uri!(new).into() | ||||
|         uri!(new) | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| @ -78,8 +78,8 @@ struct NewBlogForm { | ||||
| } | ||||
| 
 | ||||
| fn valid_slug(title: &str) -> Result<(), ValidationError> { | ||||
|     let slug = utils::make_actor_id(title.to_string()); | ||||
|     if slug.len() == 0 { | ||||
|     let slug = utils::make_actor_id(title); | ||||
|     if slug.is_empty() { | ||||
|         Err(ValidationError::new("empty_slug")) | ||||
|     } else { | ||||
|         Ok(()) | ||||
| @ -89,13 +89,13 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> { | ||||
| #[post("/blogs/new", data = "<data>")] | ||||
| fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Result<Redirect, Template> { | ||||
|     let form = data.get(); | ||||
|     let slug = utils::make_actor_id(form.title.to_string()); | ||||
|     let slug = utils::make_actor_id(&form.title); | ||||
| 
 | ||||
|     let mut errors = match form.validate() { | ||||
|         Ok(_) => ValidationErrors::new(), | ||||
|         Err(e) => e | ||||
|     }; | ||||
|     if let Some(_) = Blog::find_local(&*conn, slug.clone()) { | ||||
|     if Blog::find_local(&*conn, &slug).is_some() { | ||||
|         errors.add("title", ValidationError { | ||||
|             code: Cow::from("existing_slug"), | ||||
|             message: Some(Cow::from("A blog with the same name already exists.")), | ||||
| @ -131,8 +131,8 @@ fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Result<Re | ||||
| 
 | ||||
| #[post("/~/<name>/delete")] | ||||
| fn delete(conn: DbConn, name: String, user: Option<User>) -> Result<Redirect, Option<Template>>{ | ||||
|     let blog = Blog::find_local(&*conn, name).ok_or(None)?; | ||||
|     if user.map(|u| u.is_author_in(&*conn, blog.clone())).unwrap_or(false) { | ||||
|     let blog = Blog::find_local(&*conn, &name).ok_or(None)?; | ||||
|     if user.map(|u| u.is_author_in(&*conn, &blog)).unwrap_or(false) { | ||||
|         blog.delete(&conn); | ||||
|         Ok(Redirect::to(uri!(super::instance::index))) | ||||
|     } else { | ||||
| @ -144,17 +144,17 @@ fn delete(conn: DbConn, name: String, user: Option<User>) -> Result<Redirect, Op | ||||
| 
 | ||||
| #[get("/~/<name>/outbox")] | ||||
| fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> { | ||||
|     let blog = Blog::find_local(&*conn, name)?; | ||||
|     let blog = Blog::find_local(&*conn, &name)?; | ||||
|     Some(blog.outbox(&*conn)) | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<name>/atom.xml")] | ||||
| fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, name.clone())?; | ||||
|     let blog = Blog::find_by_fqn(&*conn, &name)?; | ||||
|     let feed = FeedBuilder::default() | ||||
|         .title(blog.title.clone()) | ||||
|         .id(Instance::get_local(&*conn).expect("blogs::atom_feed: local instance not found error") | ||||
|             .compute_box("~", name, "atom.xml")) | ||||
|             .compute_box("~", &name, "atom.xml")) | ||||
|         .entries(Post::get_recents_for_blog(&*conn, &blog, 15) | ||||
|             .into_iter() | ||||
|             .map(|p| super::post_to_atom(p, &*conn)) | ||||
|  | ||||
| @ -31,26 +31,26 @@ struct NewCommentForm { | ||||
| #[post("/~/<blog_name>/<slug>/comment", data = "<data>")] | ||||
| fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) | ||||
|     -> Result<Redirect, Option<Template>> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).ok_or(None)?; | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).ok_or(None)?; | ||||
|     let blog = Blog::find_by_fqn(&*conn, &blog_name).ok_or(None)?; | ||||
|     let post = Post::find_by_slug(&*conn, &slug, blog.id).ok_or(None)?; | ||||
|     let form = data.get(); | ||||
|     form.validate() | ||||
|         .map(|_| { | ||||
|             let (html, mentions, _hashtags) = utils::md_to_html(form.content.as_ref()); | ||||
|             let comm = Comment::insert(&*conn, NewComment { | ||||
|                 content: SafeString::new(html.as_ref()), | ||||
|                 in_response_to_id: form.responding_to.clone(), | ||||
|                 in_response_to_id: form.responding_to, | ||||
|                 post_id: post.id, | ||||
|                 author_id: user.id, | ||||
|                 ap_url: None, | ||||
|                 sensitive: form.warning.len() > 0, | ||||
|                 sensitive: !form.warning.is_empty(), | ||||
|                 spoiler_text: form.warning.clone() | ||||
|             }).update_ap_url(&*conn); | ||||
|             let new_comment = comm.create_activity(&*conn); | ||||
| 
 | ||||
|             // save mentions
 | ||||
|             for ment in mentions { | ||||
|                 Mention::from_activity(&*conn, Mention::build_activity(&*conn, ment), post.id, true, true); | ||||
|                 Mention::from_activity(&*conn, &Mention::build_activity(&*conn, &ment), post.id, true, true); | ||||
|             } | ||||
| 
 | ||||
|             // federate
 | ||||
| @ -76,7 +76,7 @@ fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, us | ||||
|                 "has_reshared": user.has_reshared(&*conn, &post), | ||||
|                 "account": user.to_json(&*conn), | ||||
|                 "date": &post.creation_date.timestamp(), | ||||
|                 "previous": form.responding_to.and_then(|r| Comment::get(&*conn, r)).map(|r| r.to_json(&*conn, &vec![])), | ||||
|                 "previous": form.responding_to.and_then(|r| Comment::get(&*conn, r)).map(|r| r.to_json(&*conn, &[])), | ||||
|                 "user_fqn": user.get_fqn(&*conn), | ||||
|                 "comment_form": form, | ||||
|                 "comment_errors": errors, | ||||
| @ -86,5 +86,5 @@ fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, us | ||||
| 
 | ||||
| #[get("/~/<_blog>/<_slug>/comment/<id>")] | ||||
| fn activity_pub(_blog: String, _slug: String, id: i32, _ap: ApRequest, conn: DbConn) -> Option<ActivityStream<Note>> { | ||||
|     Comment::get(&*conn, id).map(|c| ActivityStream::new(c.into_activity(&*conn))) | ||||
|     Comment::get(&*conn, id).map(|c| ActivityStream::new(c.to_activity(&*conn))) | ||||
| } | ||||
|  | ||||
| @ -191,7 +191,9 @@ fn admin_users_paginated(admin: Admin, conn: DbConn, page: Page) -> Template { | ||||
| 
 | ||||
| #[post("/admin/users/<id>/ban")] | ||||
| fn ban(_admin: Admin, conn: DbConn, id: i32) -> Redirect { | ||||
|     User::get(&*conn, id).map(|u| u.delete(&*conn)); | ||||
|     if let Some(u) = User::get(&*conn, id) { | ||||
|         u.delete(&*conn); | ||||
|     } | ||||
|     Redirect::to(uri!(admin_users)) | ||||
| } | ||||
| 
 | ||||
| @ -203,14 +205,14 @@ fn shared_inbox(conn: DbConn, data: String, headers: Headers) -> Result<String, | ||||
|     let actor_id = activity["actor"].as_str() | ||||
|         .or_else(|| activity["actor"]["id"].as_str()).ok_or(status::BadRequest(Some("Missing actor id for activity")))?; | ||||
| 
 | ||||
|     let actor = User::from_url(&conn, actor_id.to_owned()).expect("instance::shared_inbox: user error"); | ||||
|     if !verify_http_headers(&actor, headers.0.clone(), data).is_secure() && | ||||
|     let actor = User::from_url(&conn, actor_id).expect("instance::shared_inbox: user error"); | ||||
|     if !verify_http_headers(&actor, &headers.0, &data).is_secure() && | ||||
|         !act.clone().verify(&actor) { | ||||
|         println!("Rejected invalid activity supposedly from {}, with headers {:?}", actor.username, headers.0); | ||||
|         return Err(status::BadRequest(Some("Invalid signature"))); | ||||
|     } | ||||
| 
 | ||||
|     if Instance::is_blocked(&*conn, actor_id.to_string()) { | ||||
|     if Instance::is_blocked(&*conn, actor_id) { | ||||
|         return Ok(String::new()); | ||||
|     } | ||||
|     let instance = Instance::get_local(&*conn).expect("instance::shared_inbox: local instance not found error"); | ||||
|  | ||||
| @ -13,8 +13,8 @@ use plume_models::{ | ||||
| 
 | ||||
| #[post("/~/<blog>/<slug>/like")] | ||||
| fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Option<Redirect> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.clone())?; | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), b.id)?; | ||||
|     let b = Blog::find_by_fqn(&*conn, &blog)?; | ||||
|     let post = Post::find_by_slug(&*conn, &slug, b.id)?; | ||||
| 
 | ||||
|     if !user.has_liked(&*conn, &post) { | ||||
|         let like = likes::Like::insert(&*conn, likes::NewLike { | ||||
| @ -26,7 +26,7 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po | ||||
|         like.notify(&*conn); | ||||
| 
 | ||||
|         let dest = User::one_by_instance(&*conn); | ||||
|         let act = like.into_activity(&*conn); | ||||
|         let act = like.to_activity(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, act, dest))); | ||||
|     } else { | ||||
|         let like = likes::Like::find_by_user_on_post(&*conn, user.id, post.id).expect("likes::create: like exist but not found error"); | ||||
| @ -42,6 +42,6 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po | ||||
| fn create_auth(blog: String, slug: String) -> Flash<Redirect>{ | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to like a post", | ||||
|         uri!(create: blog = blog, slug = slug).into() | ||||
|         uri!(create: blog = blog, slug = slug) | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @ -37,7 +37,7 @@ fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Result<Redi | ||||
|                     .ok_or_else(|| status::BadRequest(Some("No file uploaded")))?.headers | ||||
|                     .filename.clone(); | ||||
|                 let ext = filename.and_then(|f| f.rsplit('.').next().map(|ext| ext.to_owned())) | ||||
|                     .unwrap_or("png".to_owned()); | ||||
|                     .unwrap_or_else(|| "png".to_owned()); | ||||
|                 let dest = format!("static/media/{}.{}", GUID::rand().to_string(), ext); | ||||
| 
 | ||||
|                 match fields[&"file".to_string()][0].data { | ||||
| @ -49,7 +49,7 @@ fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Result<Redi | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 let has_cw = read(&fields[&"cw".to_string()][0].data).len() > 0; | ||||
|                 let has_cw = !read(&fields[&"cw".to_string()][0].data).is_empty(); | ||||
|                 let media = Media::insert(&*conn, NewMedia { | ||||
|                     file_path: dest, | ||||
|                     alt_text: read(&fields[&"alt".to_string()][0].data), | ||||
|  | ||||
| @ -24,6 +24,6 @@ fn notifications(conn: DbConn, user: User) -> Template { | ||||
| fn notifications_auth() -> Flash<Redirect>{ | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to see your notifications", | ||||
|         uri!(notifications).into() | ||||
|         uri!(notifications) | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @ -38,14 +38,14 @@ fn details(blog: String, slug: String, conn: DbConn, user: Option<User>) -> Temp | ||||
| 
 | ||||
| #[get("/~/<blog>/<slug>?<query>")] | ||||
| fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User>, query: Option<CommentQuery>) -> Template { | ||||
|     may_fail!(user.map(|u| u.to_json(&*conn)), Blog::find_by_fqn(&*conn, blog), "Couldn't find this blog", |blog| { | ||||
|         may_fail!(user.map(|u| u.to_json(&*conn)), Post::find_by_slug(&*conn, slug, blog.id), "Couldn't find this post", |post| { | ||||
|     may_fail!(user.map(|u| u.to_json(&*conn)), Blog::find_by_fqn(&*conn, &blog), "Couldn't find this blog", |blog| { | ||||
|         may_fail!(user.map(|u| u.to_json(&*conn)), Post::find_by_slug(&*conn, &slug, blog.id), "Couldn't find this post", |post| { | ||||
|             if post.published || post.get_authors(&*conn).into_iter().any(|a| a.id == user.clone().map(|u| u.id).unwrap_or(0)) { | ||||
|                 let comments = Comment::list_by_post(&*conn, post.id); | ||||
|                 let comms = comments.clone(); | ||||
| 
 | ||||
|                 let previous = query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r) | ||||
|                     .expect("posts::details_reponse: Error retrieving previous comment").to_json(&*conn, &vec![]))); | ||||
|                     .expect("posts::details_reponse: Error retrieving previous comment").to_json(&*conn, &[]))); | ||||
|                 Template::render("posts/details", json!({ | ||||
|                     "author": post.get_authors(&*conn)[0].to_json(&*conn), | ||||
|                     "article": post.to_json(&*conn), | ||||
| @ -65,7 +65,7 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User> | ||||
|                     "default": { | ||||
|                         "warning": previous.map(|p| p["spoiler_text"].clone()) | ||||
|                     }, | ||||
|                     "user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or(String::new()), | ||||
|                     "user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or_default(), | ||||
|                     "is_author": user.clone().map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false), | ||||
|                     "is_following": user.map(|u| u.is_following(&*conn, post.get_authors(&*conn)[0].id)).unwrap_or(false), | ||||
|                     "comment_form": null, | ||||
| @ -82,10 +82,10 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User> | ||||
| 
 | ||||
| #[get("/~/<blog>/<slug>", rank = 3)] | ||||
| fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<Article>, Option<String>> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog).ok_or(None)?; | ||||
|     let post = Post::find_by_slug(&*conn, slug, blog.id).ok_or(None)?; | ||||
|     let blog = Blog::find_by_fqn(&*conn, &blog).ok_or(None)?; | ||||
|     let post = Post::find_by_slug(&*conn, &slug, blog.id).ok_or(None)?; | ||||
|     if post.published { | ||||
|         Ok(ActivityStream::new(post.into_activity(&*conn))) | ||||
|         Ok(ActivityStream::new(post.to_activity(&*conn))) | ||||
|     } else { | ||||
|         Err(Some(String::from("Not published yet."))) | ||||
|     } | ||||
| @ -95,15 +95,15 @@ fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> | ||||
| fn new_auth(blog: String) -> Flash<Redirect> { | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to write a new post", | ||||
|         uri!(new: blog = blog).into() | ||||
|         uri!(new: blog = blog) | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<blog>/new", rank = 1)] | ||||
| fn new(blog: String, user: User, conn: DbConn) -> Option<Template> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string())?; | ||||
|     let b = Blog::find_by_fqn(&*conn, &blog)?; | ||||
| 
 | ||||
|     if !user.is_author_in(&*conn, b.clone()) { | ||||
|     if !user.is_author_in(&*conn, &b) { | ||||
|         Some(Template::render("errors/403", json!({// TODO actually return 403 error code
 | ||||
|             "error_message": "You are not author in this blog." | ||||
|         }))) | ||||
| @ -123,15 +123,15 @@ fn new(blog: String, user: User, conn: DbConn) -> Option<Template> { | ||||
| 
 | ||||
| #[get("/~/<blog>/<slug>/edit")] | ||||
| fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Option<Template> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string())?; | ||||
|     let post = Post::find_by_slug(&*conn, slug, b.id)?; | ||||
|     let b = Blog::find_by_fqn(&*conn, &blog)?; | ||||
|     let post = Post::find_by_slug(&*conn, &slug, b.id)?; | ||||
| 
 | ||||
|     if !user.is_author_in(&*conn, b) { | ||||
|     if !user.is_author_in(&*conn, &b) { | ||||
|         Some(Template::render("errors/403", json!({// TODO actually return 403 error code
 | ||||
|             "error_message": "You are not author in this blog." | ||||
|         }))) | ||||
|     } else { | ||||
|         let source = if post.source.len() > 0 { | ||||
|         let source = if !post.source.is_empty() { | ||||
|             post.source | ||||
|         } else { | ||||
|             post.content.get().clone() // fallback to HTML if the markdown was not stored
 | ||||
| @ -165,8 +165,8 @@ fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Option<Template | ||||
| #[post("/~/<blog>/<slug>/edit", data = "<data>")] | ||||
| fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientForm<NewPostForm>, worker: State<Pool<ThunkWorker<()>>>) | ||||
|     -> Result<Redirect, Option<Template>> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string()).ok_or(None)?; | ||||
|     let mut post = Post::find_by_slug(&*conn, slug.clone(), b.id).ok_or(None)?; | ||||
|     let b = Blog::find_by_fqn(&*conn, &blog).ok_or(None)?; | ||||
|     let mut post = Post::find_by_slug(&*conn, &slug, b.id).ok_or(None)?; | ||||
| 
 | ||||
|     let form = data.get(); | ||||
|     let new_slug = if !post.published { | ||||
| @ -180,27 +180,25 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor | ||||
|         Err(e) => e | ||||
|     }; | ||||
| 
 | ||||
|     if new_slug != slug { | ||||
|         if let Some(_) = Post::find_by_slug(&*conn, new_slug.clone(), b.id) { | ||||
|             errors.add("title", ValidationError { | ||||
|                 code: Cow::from("existing_slug"), | ||||
|                 message: Some(Cow::from("A post with the same title already exists.")), | ||||
|                 params: HashMap::new() | ||||
|             }); | ||||
|         } | ||||
|     if new_slug != slug && Post::find_by_slug(&*conn, &new_slug, b.id).is_some() { | ||||
|         errors.add("title", ValidationError { | ||||
|             code: Cow::from("existing_slug"), | ||||
|             message: Some(Cow::from("A post with the same title already exists.")), | ||||
|             params: HashMap::new() | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     if errors.is_empty() { | ||||
|         if !user.is_author_in(&*conn, b) { | ||||
|         if !user.is_author_in(&*conn, &b) { | ||||
|             // actually it's not "Ok"…
 | ||||
|             Ok(Redirect::to(uri!(super::blogs::details: name = blog))) | ||||
|         } else { | ||||
|             let (content, mentions, hashtags) = utils::md_to_html(form.content.to_string().as_ref()); | ||||
| 
 | ||||
|             let license = if form.license.len() > 0 { | ||||
|             let license = if !form.license.is_empty() { | ||||
|                 form.license.to_string() | ||||
|             } else { | ||||
|                 Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or(String::from("CC-BY-SA")) | ||||
|                 Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or_else(|| String::from("CC-BY-SA")) | ||||
|             }; | ||||
| 
 | ||||
|             // update publication date if when this article is no longer a draft
 | ||||
| @ -223,10 +221,10 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor | ||||
|             let post = post.update_ap_url(&*conn); | ||||
| 
 | ||||
|             if post.published { | ||||
|                 post.update_mentions(&conn, mentions.into_iter().map(|m| Mention::build_activity(&conn, m)).collect()); | ||||
|                 post.update_mentions(&conn, mentions.into_iter().map(|m| Mention::build_activity(&conn, &m)).collect()); | ||||
|             } | ||||
| 
 | ||||
|             let tags = form.tags.split(",").map(|t| t.trim().to_camel_case()).filter(|t| t.len() > 0) | ||||
|             let tags = form.tags.split(',').map(|t| t.trim().to_camel_case()).filter(|t| !t.is_empty()) | ||||
|                 .collect::<HashSet<_>>().into_iter().map(|t| Tag::build_activity(&conn, t)).collect::<Vec<_>>(); | ||||
|             post.update_tags(&conn, tags); | ||||
| 
 | ||||
| @ -276,7 +274,7 @@ struct NewPostForm { | ||||
| 
 | ||||
| fn valid_slug(title: &str) -> Result<(), ValidationError> { | ||||
|     let slug = title.to_string().to_kebab_case(); | ||||
|     if slug.len() == 0 { | ||||
|     if slug.is_empty() { | ||||
|         Err(ValidationError::new("empty_slug")) | ||||
|     } else if slug == "new" { | ||||
|         Err(ValidationError::new("invalid_slug")) | ||||
| @ -287,7 +285,7 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> { | ||||
| 
 | ||||
| #[post("/~/<blog_name>/new", data = "<data>")] | ||||
| fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Option<Template>> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).ok_or(None)?; | ||||
|     let blog = Blog::find_by_fqn(&*conn, &blog_name).ok_or(None)?; | ||||
|     let form = data.get(); | ||||
|     let slug = form.title.to_string().to_kebab_case(); | ||||
| 
 | ||||
| @ -295,7 +293,7 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D | ||||
|         Ok(_) => ValidationErrors::new(), | ||||
|         Err(e) => e | ||||
|     }; | ||||
|     if let Some(_) = Post::find_by_slug(&*conn, slug.clone(), blog.id) { | ||||
|     if Post::find_by_slug(&*conn, &slug, blog.id).is_some() { | ||||
|         errors.add("title", ValidationError { | ||||
|             code: Cow::from("existing_slug"), | ||||
|             message: Some(Cow::from("A post with the same title already exists.")), | ||||
| @ -304,7 +302,7 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D | ||||
|     } | ||||
| 
 | ||||
|     if errors.is_empty() { | ||||
|         if !user.is_author_in(&*conn, blog.clone()) { | ||||
|         if !user.is_author_in(&*conn, &blog) { | ||||
|             // actually it's not "Ok"…
 | ||||
|             Ok(Redirect::to(uri!(super::blogs::details: name = blog_name))) | ||||
|         } else { | ||||
| @ -316,10 +314,10 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D | ||||
|                 title: form.title.to_string(), | ||||
|                 content: SafeString::new(&content), | ||||
|                 published: !form.draft, | ||||
|                 license: if form.license.len() > 0 { | ||||
|                 license: if !form.license.is_empty() { | ||||
|                     form.license.to_string() | ||||
|                 } else { | ||||
|                     Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or(String::from("CC-BY-SA")) | ||||
|                     Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or_else(||String::from("CC-BY-SA")) | ||||
|                 }, | ||||
|                 ap_url: "".to_string(), | ||||
|                 creation_date: None, | ||||
| @ -333,10 +331,13 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D | ||||
|                 author_id: user.id | ||||
|             }); | ||||
| 
 | ||||
|             let tags = form.tags.split(",").map(|t| t.trim().to_camel_case()).filter(|t| t.len() > 0).collect::<HashSet<_>>(); | ||||
|             let tags = form.tags.split(',') | ||||
|                 .map(|t| t.trim().to_camel_case()) | ||||
|                 .filter(|t| !t.is_empty()) | ||||
|                 .collect::<HashSet<_>>(); | ||||
|             for tag in tags { | ||||
|                 Tag::insert(&*conn, NewTag { | ||||
|                     tag: tag, | ||||
|                     tag, | ||||
|                     is_hashtag: false, | ||||
|                     post_id: post.id | ||||
|                 }); | ||||
| @ -350,8 +351,8 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D | ||||
|             } | ||||
| 
 | ||||
|             if post.published { | ||||
|                 for m in mentions.into_iter() { | ||||
|                     Mention::from_activity(&*conn, Mention::build_activity(&*conn, m), post.id, true, true); | ||||
|                 for m in mentions { | ||||
|                     Mention::from_activity(&*conn, &Mention::build_activity(&*conn, &m), post.id, true, true); | ||||
|                 } | ||||
| 
 | ||||
|                 let act = post.create_activity(&*conn); | ||||
| @ -377,8 +378,8 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D | ||||
| 
 | ||||
| #[post("/~/<blog_name>/<slug>/delete")] | ||||
| fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: State<Pool<ThunkWorker<()>>>) -> Redirect { | ||||
|     let post = Blog::find_by_fqn(&*conn, blog_name.clone()) | ||||
|         .and_then(|blog| Post::find_by_slug(&*conn, slug.clone(), blog.id)); | ||||
|     let post = Blog::find_by_fqn(&*conn, &blog_name) | ||||
|         .and_then(|blog| Post::find_by_slug(&*conn, &slug, blog.id)); | ||||
| 
 | ||||
|     if let Some(post) = post { | ||||
|         if !post.get_authors(&*conn).into_iter().any(|a| a.id == user.id) { | ||||
|  | ||||
| @ -13,8 +13,8 @@ use plume_models::{ | ||||
| 
 | ||||
| #[post("/~/<blog>/<slug>/reshare")] | ||||
| fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Option<Redirect> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.clone())?; | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), b.id)?; | ||||
|     let b = Blog::find_by_fqn(&*conn, &blog)?; | ||||
|     let post = Post::find_by_slug(&*conn, &slug, b.id)?; | ||||
| 
 | ||||
|     if !user.has_reshared(&*conn, &post) { | ||||
|         let reshare = Reshare::insert(&*conn, NewReshare { | ||||
| @ -26,7 +26,7 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po | ||||
|         reshare.notify(&*conn); | ||||
| 
 | ||||
|         let dest = User::one_by_instance(&*conn); | ||||
|         let act = reshare.into_activity(&*conn); | ||||
|         let act = reshare.to_activity(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, act, dest))); | ||||
|     } else { | ||||
|         let reshare = Reshare::find_by_user_on_post(&*conn, user.id, post.id) | ||||
| @ -43,6 +43,6 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po | ||||
| fn create_auth(blog: String, slug: String) -> Flash<Redirect> { | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to reshare a post", | ||||
|         uri!(create: blog = blog, slug = slug).into() | ||||
|         uri!(create: blog = blog, slug = slug) | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @ -49,15 +49,15 @@ struct LoginForm { | ||||
| #[post("/login", data = "<data>")] | ||||
| fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, Template> { | ||||
|     let form = data.get(); | ||||
|     let user = User::find_by_email(&*conn, form.email_or_name.to_string()) | ||||
|         .or_else(|| User::find_local(&*conn, form.email_or_name.to_string())); | ||||
|     let user = User::find_by_email(&*conn, &form.email_or_name) | ||||
|         .or_else(|| User::find_local(&*conn, &form.email_or_name)); | ||||
|     let mut errors = match form.validate() { | ||||
|         Ok(_) => ValidationErrors::new(), | ||||
|         Err(e) => e | ||||
|     }; | ||||
|     
 | ||||
|     if let Some(user) = user.clone() { | ||||
|         if !user.auth(form.password.clone()) { | ||||
|         if !user.auth(&form.password) { | ||||
|             let mut err = ValidationError::new("invalid_login"); | ||||
|             err.message = Some(Cow::from("Invalid username or password")); | ||||
|             errors.add("email_or_name", err) | ||||
| @ -65,7 +65,7 @@ fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage | ||||
|     } else { | ||||
|         // Fake password verification, only to avoid different login times
 | ||||
|         // that could be used to see if an email adress is registered or not
 | ||||
|         User::get(&*conn, 1).map(|u| u.auth(form.password.clone())); | ||||
|         User::get(&*conn, 1).map(|u| u.auth(&form.password)); | ||||
| 
 | ||||
|         let mut err = ValidationError::new("invalid_login"); | ||||
|         err.message = Some(Cow::from("Invalid username or password")); | ||||
| @ -83,7 +83,7 @@ fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage | ||||
|             } else { | ||||
|                 None | ||||
|             }) | ||||
|             .unwrap_or("/".to_owned()); | ||||
|             .unwrap_or_else(|| "/".to_owned()); | ||||
| 
 | ||||
|         let uri = Uri::parse(&destination) | ||||
|             .map(|x| x.into_owned()) | ||||
| @ -108,6 +108,8 @@ fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage | ||||
| 
 | ||||
| #[get("/logout")] | ||||
| fn delete(mut cookies: Cookies) -> Redirect { | ||||
|     cookies.get_private(AUTH_COOKIE).map(|cookie| cookies.remove_private(cookie)); | ||||
|     if let Some(cookie) = cookies.get_private(AUTH_COOKIE) { | ||||
|         cookies.remove_private(cookie); | ||||
|     } | ||||
|     Redirect::to("/") | ||||
| } | ||||
|  | ||||
| @ -29,7 +29,7 @@ use Worker; | ||||
| fn me(user: Option<User>) -> Result<Redirect, Flash<Redirect>> { | ||||
|     match user { | ||||
|         Some(user) => Ok(Redirect::to(uri!(details: name = user.username))), | ||||
|         None => Err(utils::requires_login("", uri!(me).into())), | ||||
|         None => Err(utils::requires_login("", uri!(me))), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -45,12 +45,12 @@ fn details( | ||||
| ) -> Template { | ||||
|     may_fail!( | ||||
|         account.map(|a| a.to_json(&*conn)), | ||||
|         User::find_by_fqn(&*conn, name), | ||||
|         User::find_by_fqn(&*conn, &name), | ||||
|         "Couldn't find requested user", | ||||
|         |user| { | ||||
|             let recents = Post::get_recents_for_author(&*conn, &user, 6); | ||||
|             let reshares = Reshare::get_recents_for_author(&*conn, &user, 6); | ||||
|             let user_id = user.id.clone(); | ||||
|             let user_id = user.id; | ||||
|             let n_followers = user.get_followers(&*conn).len(); | ||||
| 
 | ||||
|             if !user.get_instance(&*conn).local { | ||||
| @ -79,9 +79,9 @@ fn details( | ||||
|                 worker.execute(Thunk::of(move || { | ||||
|                     for user_id in user_clone.fetch_followers_ids() { | ||||
|                         let follower = | ||||
|                             User::find_by_ap_url(&*fecth_followers_conn, user_id.clone()) | ||||
|                             User::find_by_ap_url(&*fecth_followers_conn, &user_id) | ||||
|                                 .unwrap_or_else(|| { | ||||
|                                     User::fetch_from_url(&*fecth_followers_conn, user_id) | ||||
|                                     User::fetch_from_url(&*fecth_followers_conn, &user_id) | ||||
|                                         .expect("user::details: Couldn't fetch follower") | ||||
|                                 }); | ||||
|                         follows::Follow::insert( | ||||
| @ -139,13 +139,13 @@ fn dashboard(user: User, conn: DbConn) -> Template { | ||||
| fn dashboard_auth() -> Flash<Redirect> { | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to access your dashboard", | ||||
|         uri!(dashboard).into(), | ||||
|         uri!(dashboard), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| #[post("/@/<name>/follow")] | ||||
| fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Option<Redirect> { | ||||
|     let target = User::find_by_fqn(&*conn, name.clone())?; | ||||
|     let target = User::find_by_fqn(&*conn, &name)?; | ||||
|     if let Some(follow) = follows::Follow::find(&*conn, user.id, target.id) { | ||||
|         let delete_act = follow.delete(&*conn); | ||||
|         worker.execute(Thunk::of(move || { | ||||
| @ -162,7 +162,7 @@ fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Option<Redi | ||||
|         ); | ||||
|         f.notify(&*conn); | ||||
| 
 | ||||
|         let act = f.into_activity(&*conn); | ||||
|         let act = f.to_activity(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, act, vec![target]))); | ||||
|     } | ||||
|     Some(Redirect::to(uri!(details: name = name))) | ||||
| @ -172,7 +172,7 @@ fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Option<Redi | ||||
| fn follow_auth(name: String) -> Flash<Redirect> { | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to follow someone", | ||||
|         uri!(follow: name = name).into(), | ||||
|         uri!(follow: name = name), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| @ -180,10 +180,10 @@ fn follow_auth(name: String) -> Flash<Redirect> { | ||||
| fn followers_paginated(name: String, conn: DbConn, account: Option<User>, page: Page) -> Template { | ||||
|     may_fail!( | ||||
|         account.map(|a| a.to_json(&*conn)), | ||||
|         User::find_by_fqn(&*conn, name.clone()), | ||||
|         User::find_by_fqn(&*conn, &name), | ||||
|         "Couldn't find requested user", | ||||
|         |user| { | ||||
|             let user_id = user.id.clone(); | ||||
|             let user_id = user.id; | ||||
|             let followers_count = user.get_followers(&*conn).len(); | ||||
| 
 | ||||
|             Template::render( | ||||
| @ -216,8 +216,8 @@ fn activity_details( | ||||
|     conn: DbConn, | ||||
|     _ap: ApRequest, | ||||
| ) -> Option<ActivityStream<CustomPerson>> { | ||||
|     let user = User::find_local(&*conn, name)?; | ||||
|     Some(ActivityStream::new(user.into_activity(&*conn))) | ||||
|     let user = User::find_local(&*conn, &name)?; | ||||
|     Some(ActivityStream::new(user.to_activity(&*conn))) | ||||
| } | ||||
| 
 | ||||
| #[get("/users/new")] | ||||
| @ -235,7 +235,7 @@ fn new(user: Option<User>, conn: DbConn) -> Template { | ||||
| 
 | ||||
| #[get("/@/<name>/edit")] | ||||
| fn edit(name: String, user: User, conn: DbConn) -> Option<Template> { | ||||
|     if user.username == name && !name.contains("@") { | ||||
|     if user.username == name && !name.contains('@') { | ||||
|         Some(Template::render( | ||||
|             "users/edit", | ||||
|             json!({ | ||||
| @ -251,7 +251,7 @@ fn edit(name: String, user: User, conn: DbConn) -> Option<Template> { | ||||
| fn edit_auth(name: String) -> Flash<Redirect> { | ||||
|     utils::requires_login( | ||||
|         "You need to be logged in order to edit your profile", | ||||
|         uri!(edit: name = name).into(), | ||||
|         uri!(edit: name = name), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| @ -269,30 +269,30 @@ fn update(_name: String, conn: DbConn, user: User, data: LenientForm<UpdateUserF | ||||
|         data.get() | ||||
|             .display_name | ||||
|             .clone() | ||||
|             .unwrap_or(user.display_name.to_string()) | ||||
|             .unwrap_or_else(|| user.display_name.to_string()) | ||||
|             .to_string(), | ||||
|         data.get() | ||||
|             .email | ||||
|             .clone() | ||||
|             .unwrap_or(user.email.clone().unwrap()) | ||||
|             .unwrap_or_else(|| user.email.clone().unwrap()) | ||||
|             .to_string(), | ||||
|         data.get() | ||||
|             .summary | ||||
|             .clone() | ||||
|             .unwrap_or(user.summary.to_string()), | ||||
|             .unwrap_or_else(|| user.summary.to_string()), | ||||
|     ); | ||||
|     Redirect::to(uri!(me)) | ||||
| } | ||||
| 
 | ||||
| #[post("/@/<name>/delete")] | ||||
| fn delete(name: String, conn: DbConn, user: User, mut cookies: Cookies) -> Option<Redirect> { | ||||
|     let account = User::find_by_fqn(&*conn, name.clone())?; | ||||
|     let account = User::find_by_fqn(&*conn, &name)?; | ||||
|     if user.id == account.id { | ||||
|         account.delete(&*conn); | ||||
| 
 | ||||
|         cookies | ||||
|             .get_private(AUTH_COOKIE) | ||||
|             .map(|cookie| cookies.remove_private(cookie)); | ||||
|     if let Some(cookie) = cookies.get_private(AUTH_COOKIE) { | ||||
|         cookies.remove_private(cookie); | ||||
|     } | ||||
| 
 | ||||
|         Some(Redirect::to(uri!(super::instance::index))) | ||||
|     } else { | ||||
| @ -354,9 +354,9 @@ fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, Temp | ||||
|                 form.username.to_string(), | ||||
|                 form.username.to_string(), | ||||
|                 false, | ||||
|                 String::from(""), | ||||
|                 "", | ||||
|                 form.email.to_string(), | ||||
|                 User::hash_pass(form.password.to_string()), | ||||
|                 User::hash_pass(&form.password), | ||||
|             ).update_boxes(&*conn); | ||||
|             Redirect::to(uri!(super::session::new)) | ||||
|         }) | ||||
| @ -374,7 +374,7 @@ fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, Temp | ||||
| 
 | ||||
| #[get("/@/<name>/outbox")] | ||||
| fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> { | ||||
|     let user = User::find_local(&*conn, name)?; | ||||
|     let user = User::find_local(&*conn, &name)?; | ||||
|     Some(user.outbox(&*conn)) | ||||
| } | ||||
| 
 | ||||
| @ -385,9 +385,9 @@ fn inbox( | ||||
|     data: String, | ||||
|     headers: Headers, | ||||
| ) -> Result<String, Option<status::BadRequest<&'static str>>> { | ||||
|     let user = User::find_local(&*conn, name).ok_or(None)?; | ||||
|     let user = User::find_local(&*conn, &name).ok_or(None)?; | ||||
|     let act: serde_json::Value = | ||||
|         serde_json::from_str(&data[..]).expect("user::inbox: deserialization error"); | ||||
|         serde_json::from_str(&data).expect("user::inbox: deserialization error"); | ||||
| 
 | ||||
|     let activity = act.clone(); | ||||
|     let actor_id = activity["actor"] | ||||
| @ -397,8 +397,8 @@ fn inbox( | ||||
|             "Missing actor id for activity", | ||||
|         ))))?; | ||||
| 
 | ||||
|     let actor = User::from_url(&conn, actor_id.to_owned()).expect("user::inbox: user error"); | ||||
|     if !verify_http_headers(&actor, headers.0.clone(), data).is_secure() | ||||
|     let actor = User::from_url(&conn, actor_id).expect("user::inbox: user error"); | ||||
|     if !verify_http_headers(&actor, &headers.0, &data).is_secure() | ||||
|         && !act.clone().verify(&actor) | ||||
|     { | ||||
|         println!( | ||||
| @ -408,7 +408,7 @@ fn inbox( | ||||
|         return Err(Some(status::BadRequest(Some("Invalid signature")))); | ||||
|     } | ||||
| 
 | ||||
|     if Instance::is_blocked(&*conn, actor_id.to_string()) { | ||||
|     if Instance::is_blocked(&*conn, actor_id) { | ||||
|         return Ok(String::new()); | ||||
|     } | ||||
|     Ok(match user.received(&*conn, act) { | ||||
| @ -426,7 +426,7 @@ fn ap_followers( | ||||
|     conn: DbConn, | ||||
|     _ap: ApRequest, | ||||
| ) -> Option<ActivityStream<OrderedCollection>> { | ||||
|     let user = User::find_local(&*conn, name)?; | ||||
|     let user = User::find_local(&*conn, &name)?; | ||||
|     let followers = user | ||||
|         .get_followers(&*conn) | ||||
|         .into_iter() | ||||
| @ -448,12 +448,12 @@ fn ap_followers( | ||||
| 
 | ||||
| #[get("/@/<name>/atom.xml")] | ||||
| fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> { | ||||
|     let author = User::find_by_fqn(&*conn, name.clone())?; | ||||
|     let author = User::find_by_fqn(&*conn, &name)?; | ||||
|     let feed = FeedBuilder::default() | ||||
|         .title(author.display_name.clone()) | ||||
|         .id(Instance::get_local(&*conn) | ||||
|             .unwrap() | ||||
|             .compute_box("~", name, "atom.xml")) | ||||
|             .compute_box("~", &name, "atom.xml")) | ||||
|         .entries( | ||||
|             Post::get_recents_for_author(&*conn, &author, 15) | ||||
|                 .into_iter() | ||||
|  | ||||
| @ -11,7 +11,7 @@ fn nodeinfo() -> Content<String> { | ||||
|         "links": [ | ||||
|             { | ||||
|                 "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0", | ||||
|                 "href": ap_url(format!("{domain}/nodeinfo", domain = BASE_URL.as_str())) | ||||
|                 "href": ap_url(&format!("{domain}/nodeinfo", domain = BASE_URL.as_str())) | ||||
|             } | ||||
|         ] | ||||
|     }).to_string()) | ||||
| @ -24,7 +24,7 @@ fn host_meta() -> String { | ||||
|     <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"> | ||||
|         <Link rel="lrdd" type="application/xrd+xml" template="{url}"/> | ||||
|     </XRD> | ||||
|     "#, url = ap_url(format!("{domain}/.well-known/webfinger?resource={{uri}}", domain = BASE_URL.as_str())))
 | ||||
|     "#, url = ap_url(&format!("{domain}/.well-known/webfinger?resource={{uri}}", domain = BASE_URL.as_str())))
 | ||||
| } | ||||
| 
 | ||||
| #[derive(FromForm)] | ||||
| @ -40,9 +40,9 @@ impl Resolver<DbConn> for WebfingerResolver { | ||||
|     } | ||||
| 
 | ||||
|     fn find(acct: String, conn: DbConn) -> Result<Webfinger, ResolverError> { | ||||
|         match User::find_local(&*conn, acct.clone()) { | ||||
|         match User::find_local(&*conn, &acct) { | ||||
|             Some(usr) => Ok(usr.webfinger(&*conn)), | ||||
|             None => match Blog::find_local(&*conn, acct) { | ||||
|             None => match Blog::find_local(&*conn, &acct) { | ||||
|                 Some(blog) => Ok(blog.webfinger(&*conn)), | ||||
|                 None => Err(ResolverError::NotFound) | ||||
|             } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user