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