Avoid panics (#392)

- Use `Result` as much as possible
- Display errors instead of panicking

TODO (maybe in another PR? this one is already quite big):
- Find a way to merge Ructe/ErrorPage types, so that we can have routes returning `Result<X, ErrorPage>` instead of panicking when we have an `Error`
- Display more details about the error, to make it easier to debug

(sorry, this isn't going to be fun to review, the diff is huge, but it is always the same changes)
This commit is contained in:
Baptiste Gelez
2018-12-29 09:36:07 +01:00
committed by GitHub
parent 4059a840be
commit 80a4dae8bd
50 changed files with 1879 additions and 1990 deletions
+197 -267
View File
@@ -26,7 +26,7 @@ use safe_string::SafeString;
use schema::blogs;
use search::Searcher;
use users::User;
use {Connection, BASE_URL, USE_HTTPS};
use {Connection, BASE_URL, USE_HTTPS, Error, Result};
pub type CustomGroup = CustomObject<ApSignature, Group>;
@@ -67,11 +67,11 @@ impl Blog {
find_by!(blogs, find_by_ap_url, ap_url as &str);
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
pub fn get_instance(&self, conn: &Connection) -> Instance {
Instance::get(conn, self.instance_id).expect("Blog::get_instance: instance not found error")
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
Instance::get(conn, self.instance_id)
}
pub fn list_authors(&self, conn: &Connection) -> Vec<User> {
pub fn list_authors(&self, conn: &Connection) -> Result<Vec<User>> {
use schema::blog_authors;
use schema::users;
let authors_ids = blog_authors::table
@@ -80,19 +80,19 @@ impl Blog {
users::table
.filter(users::id.eq_any(authors_ids))
.load::<User>(conn)
.expect("Blog::list_authors: author loading error")
.map_err(Error::from)
}
pub fn count_authors(&self, conn: &Connection) -> i64 {
pub fn count_authors(&self, conn: &Connection) -> Result<i64> {
use schema::blog_authors;
blog_authors::table
.filter(blog_authors::blog_id.eq(self.id))
.count()
.get_result(conn)
.expect("Blog::count_authors: count loading error")
.map_err(Error::from)
}
pub fn find_for_author(conn: &Connection, author: &User) -> Vec<Blog> {
pub fn find_for_author(conn: &Connection, author: &User) -> Result<Vec<Blog>> {
use schema::blog_authors;
let author_ids = blog_authors::table
.filter(blog_authors::author_id.eq(author.id))
@@ -100,62 +100,40 @@ impl Blog {
blogs::table
.filter(blogs::id.eq_any(author_ids))
.load::<Blog>(conn)
.expect("Blog::find_for_author: blog loading error")
.map_err(Error::from)
}
pub fn find_local(conn: &Connection, name: &str) -> Option<Blog> {
Blog::find_by_name(conn, name, Instance::local_id(conn))
pub fn find_local(conn: &Connection, name: &str) -> Result<Blog> {
Blog::find_by_name(conn, name, Instance::get_local(conn)?.id)
}
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Option<Blog> {
if fqn.contains('@') {
// remote blog
match Instance::find_by_domain(
conn,
fqn.split('@')
.last()
.expect("Blog::find_by_fqn: unreachable"),
) {
Some(instance) => match Blog::find_by_name(
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<Blog> {
let mut split_fqn = fqn.split('@');
let actor = split_fqn.next().ok_or(Error::InvalidValue)?;
if let Some(domain) = split_fqn.next() { // remote blog
Instance::find_by_domain(conn, domain)
.and_then(|instance| Blog::find_by_name(conn, actor, instance.id))
.or_else(|_| Blog::fetch_from_webfinger(conn, fqn))
} else { // local blog
Blog::find_local(conn, actor)
}
}
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<Blog> {
resolve(acct.to_owned(), *USE_HTTPS)?.links
.into_iter()
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
.ok_or(Error::Webfinger)
.and_then(|l| {
Blog::fetch_from_url(
conn,
fqn.split('@')
.nth(0)
.expect("Blog::find_by_fqn: unreachable"),
instance.id,
) {
Some(u) => Some(u),
None => Blog::fetch_from_webfinger(conn, fqn),
},
None => Blog::fetch_from_webfinger(conn, fqn),
}
} else {
// local blog
Blog::find_local(conn, fqn)
}
&l.href?
)
})
}
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Option<Blog> {
match resolve(acct.to_owned(), *USE_HTTPS) {
Ok(wf) => wf
.links
.into_iter()
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
.and_then(|l| {
Blog::fetch_from_url(
conn,
&l.href
.expect("Blog::fetch_from_webfinger: href not found error"),
)
}),
Err(details) => {
println!("{:?}", details);
None
}
}
}
fn fetch_from_url(conn: &Connection, url: &str) -> Option<Blog> {
let req = Client::new()
fn fetch_from_url(conn: &Connection, url: &str) -> Result<Blog> {
let mut res = Client::new()
.get(url)
.header(
ACCEPT,
@@ -164,139 +142,109 @@ impl Blog {
.into_iter()
.collect::<Vec<_>>()
.join(", "),
).expect("Blog::fetch_from_url: accept_header generation error"),
)?,
)
.send();
match req {
Ok(mut res) => {
let text = &res
.text()
.expect("Blog::fetch_from_url: body reading error");
let ap_sign: ApSignature =
serde_json::from_str(text).expect("Blog::fetch_from_url: body parsing error");
let mut json: CustomGroup =
serde_json::from_str(text).expect("Blog::fetch_from_url: body parsing error");
json.custom_props = ap_sign; // without this workaround, publicKey is not correctly deserialized
Some(Blog::from_activity(
conn,
&json,
Url::parse(url)
.expect("Blog::fetch_from_url: url parsing error")
.host_str()
.expect("Blog::fetch_from_url: host extraction error"),
))
}
Err(_) => None,
}
.send()?;
let text = &res.text()?;
let ap_sign: ApSignature =
serde_json::from_str(text)?;
let mut json: CustomGroup =
serde_json::from_str(text)?;
json.custom_props = ap_sign; // without this workaround, publicKey is not correctly deserialized
Blog::from_activity(
conn,
&json,
Url::parse(url)?.host_str()?,
)
}
fn from_activity(conn: &Connection, acct: &CustomGroup, inst: &str) -> Blog {
let instance = match Instance::find_by_domain(conn, inst) {
Some(instance) => instance,
None => {
Instance::insert(
conn,
NewInstance {
public_domain: inst.to_owned(),
name: inst.to_owned(),
local: false,
// We don't really care about all the following for remote instances
long_description: SafeString::new(""),
short_description: SafeString::new(""),
default_license: String::new(),
open_registrations: true,
short_description_html: String::new(),
long_description_html: String::new(),
},
)
}
};
fn from_activity(conn: &Connection, acct: &CustomGroup, inst: &str) -> Result<Blog> {
let instance = Instance::find_by_domain(conn, inst).or_else(|_|
Instance::insert(
conn,
NewInstance {
public_domain: inst.to_owned(),
name: inst.to_owned(),
local: false,
// We don't really care about all the following for remote instances
long_description: SafeString::new(""),
short_description: SafeString::new(""),
default_license: String::new(),
open_registrations: true,
short_description_html: String::new(),
long_description_html: String::new(),
},
)
)?;
Blog::insert(
conn,
NewBlog {
actor_id: acct
.object
.ap_actor_props
.preferred_username_string()
.expect("Blog::from_activity: preferredUsername error"),
.preferred_username_string()?,
title: acct
.object
.object_props
.name_string()
.expect("Blog::from_activity: name error"),
.name_string()?,
outbox_url: acct
.object
.ap_actor_props
.outbox_string()
.expect("Blog::from_activity: outbox error"),
.outbox_string()?,
inbox_url: acct
.object
.ap_actor_props
.inbox_string()
.expect("Blog::from_activity: inbox error"),
.inbox_string()?,
summary: acct
.object
.object_props
.summary_string()
.expect("Blog::from_activity: summary error"),
.summary_string()?,
instance_id: instance.id,
ap_url: acct
.object
.object_props
.id_string()
.expect("Blog::from_activity: id error"),
.id_string()?,
public_key: acct
.custom_props
.public_key_publickey()
.expect("Blog::from_activity: publicKey error")
.public_key_pem_string()
.expect("Blog::from_activity: publicKey.publicKeyPem error"),
.public_key_publickey()?
.public_key_pem_string()?,
private_key: None,
},
)
}
pub fn to_activity(&self, _conn: &Connection) -> CustomGroup {
pub fn to_activity(&self, _conn: &Connection) -> Result<CustomGroup> {
let mut blog = Group::default();
blog.ap_actor_props
.set_preferred_username_string(self.actor_id.clone())
.expect("Blog::to_activity: preferredUsername error");
.set_preferred_username_string(self.actor_id.clone())?;
blog.object_props
.set_name_string(self.title.clone())
.expect("Blog::to_activity: name error");
.set_name_string(self.title.clone())?;
blog.ap_actor_props
.set_outbox_string(self.outbox_url.clone())
.expect("Blog::to_activity: outbox error");
.set_outbox_string(self.outbox_url.clone())?;
blog.ap_actor_props
.set_inbox_string(self.inbox_url.clone())
.expect("Blog::to_activity: inbox error");
.set_inbox_string(self.inbox_url.clone())?;
blog.object_props
.set_summary_string(self.summary.clone())
.expect("Blog::to_activity: summary error");
.set_summary_string(self.summary.clone())?;
blog.object_props
.set_id_string(self.ap_url.clone())
.expect("Blog::to_activity: id error");
.set_id_string(self.ap_url.clone())?;
let mut public_key = PublicKey::default();
public_key
.set_id_string(format!("{}#main-key", self.ap_url))
.expect("Blog::to_activity: publicKey.id error");
.set_id_string(format!("{}#main-key", self.ap_url))?;
public_key
.set_owner_string(self.ap_url.clone())
.expect("Blog::to_activity: publicKey.owner error");
.set_owner_string(self.ap_url.clone())?;
public_key
.set_public_key_pem_string(self.public_key.clone())
.expect("Blog::to_activity: publicKey.publicKeyPem error");
.set_public_key_pem_string(self.public_key.clone())?;
let mut ap_signature = ApSignature::default();
ap_signature
.set_public_key_publickey(public_key)
.expect("Blog::to_activity: publicKey error");
.set_public_key_publickey(public_key)?;
CustomGroup::new(blog, ap_signature)
Ok(CustomGroup::new(blog, ap_signature))
}
pub fn update_boxes(&self, conn: &Connection) {
let instance = self.get_instance(conn);
pub fn update_boxes(&self, conn: &Connection) -> Result<()> {
let instance = self.get_instance(conn)?;
if self.outbox_url.is_empty() {
diesel::update(self)
.set(blogs::outbox_url.eq(instance.compute_box(
@@ -304,8 +252,7 @@ impl Blog {
&self.actor_id,
"outbox",
)))
.execute(conn)
.expect("Blog::update_boxes: outbox update error");
.execute(conn)?;
}
if self.inbox_url.is_empty() {
@@ -315,49 +262,45 @@ impl Blog {
&self.actor_id,
"inbox",
)))
.execute(conn)
.expect("Blog::update_boxes: inbox update error");
.execute(conn)?;
}
if self.ap_url.is_empty() {
diesel::update(self)
.set(blogs::ap_url.eq(instance.compute_box(BLOG_PREFIX, &self.actor_id, "")))
.execute(conn)
.expect("Blog::update_boxes: ap_url update error");
.execute(conn)?;
}
Ok(())
}
pub fn outbox(&self, conn: &Connection) -> ActivityStream<OrderedCollection> {
pub fn outbox(&self, conn: &Connection) -> Result<ActivityStream<OrderedCollection>> {
let mut coll = OrderedCollection::default();
coll.collection_props.items = serde_json::to_value(self.get_activities(conn))
.expect("Blog::outbox: activity serialization error");
coll.collection_props.items = serde_json::to_value(self.get_activities(conn)?)?;
coll.collection_props
.set_total_items_u64(self.get_activities(conn).len() as u64)
.expect("Blog::outbox: count serialization error");
ActivityStream::new(coll)
.set_total_items_u64(self.get_activities(conn)?.len() as u64)?;
Ok(ActivityStream::new(coll))
}
fn get_activities(&self, _conn: &Connection) -> Vec<serde_json::Value> {
vec![]
fn get_activities(&self, _conn: &Connection) -> Result<Vec<serde_json::Value>> {
Ok(vec![])
}
pub fn get_keypair(&self) -> PKey<Private> {
pub fn get_keypair(&self) -> Result<PKey<Private>> {
PKey::from_rsa(
Rsa::private_key_from_pem(
self.private_key
.clone()
.expect("Blog::get_keypair: private key not found error")
.clone()?
.as_ref(),
).expect("Blog::get_keypair: pem parsing error"),
).expect("Blog::get_keypair: private key deserialization error")
)?,
).map_err(Error::from)
}
pub fn webfinger(&self, conn: &Connection) -> Webfinger {
Webfinger {
pub fn webfinger(&self, conn: &Connection) -> Result<Webfinger> {
Ok(Webfinger {
subject: format!(
"acct:{}@{}",
self.actor_id,
self.get_instance(conn).public_domain
self.get_instance(conn)?.public_domain
),
aliases: vec![self.ap_url.clone()],
links: vec![
@@ -370,7 +313,7 @@ impl Blog {
Link {
rel: String::from("http://schemas.google.com/g/2010#updates-from"),
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,
&self.actor_id,
"feed.atom",
@@ -384,50 +327,41 @@ impl Blog {
template: None,
},
],
}
})
}
pub fn from_url(conn: &Connection, url: &str) -> Option<Blog> {
Blog::find_by_ap_url(conn, url).or_else(|| {
pub fn from_url(conn: &Connection, url: &str) -> Result<Blog> {
Blog::find_by_ap_url(conn, url).or_else(|_| {
// The requested blog was not in the DB
// We try to fetch it if it is remote
if Url::parse(url)
.expect("Blog::from_url: ap_url parsing error")
.host_str()
.expect("Blog::from_url: host extraction error") != BASE_URL.as_str()
{
if Url::parse(url)?.host_str()? != BASE_URL.as_str() {
Blog::fetch_from_url(conn, url)
} else {
None
Err(Error::NotFound)
}
})
}
pub fn get_fqn(&self, conn: &Connection) -> String {
if self.instance_id == Instance::local_id(conn) {
if self.instance_id == Instance::get_local(conn).ok().expect("Blog::get_fqn: local instance error").id {
self.actor_id.clone()
} else {
format!(
"{}@{}",
self.actor_id,
self.get_instance(conn).public_domain
self.get_instance(conn).ok().expect("Blog::get_fqn: instance error").public_domain
)
}
}
pub fn to_json(&self, conn: &Connection) -> serde_json::Value {
let mut json = serde_json::to_value(self).expect("Blog::to_json: serialization error");
json["fqn"] = json!(self.get_fqn(conn));
json
}
pub fn delete(&self, conn: &Connection, searcher: &Searcher) {
for post in Post::get_for_blog(conn, &self) {
post.delete(&(conn, searcher));
pub fn delete(&self, conn: &Connection, searcher: &Searcher) -> Result<()> {
for post in Post::get_for_blog(conn, &self)? {
post.delete(&(conn, searcher))?;
}
diesel::delete(self)
.execute(conn)
.expect("Blog::delete: blog deletion error");
.map(|_| ())
.map_err(Error::from)
}
}
@@ -455,35 +389,33 @@ impl WithInbox for Blog {
}
impl sign::Signer for Blog {
type Error = Error;
fn get_key_id(&self) -> String {
format!("{}#main-key", self.ap_url)
}
fn sign(&self, to_sign: &str) -> Vec<u8> {
let key = self.get_keypair();
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
let key = self.get_keypair()?;
let mut signer =
Signer::new(MessageDigest::sha256(), &key).expect("Blog::sign: initialization error");
Signer::new(MessageDigest::sha256(), &key)?;
signer
.update(to_sign.as_bytes())
.expect("Blog::sign: content insertion error");
.update(to_sign.as_bytes())?;
signer
.sign_to_vec()
.expect("Blog::sign: finalization error")
.map_err(Error::from)
}
fn verify(&self, data: &str, signature: &[u8]) -> bool {
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
let key = PKey::from_rsa(
Rsa::public_key_from_pem(self.public_key.as_ref())
.expect("Blog::verify: pem parsing error"),
).expect("Blog::verify: deserialization error");
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)
.expect("Blog::verify: initialization error");
Rsa::public_key_from_pem(self.public_key.as_ref())?
)?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
verifier
.update(data.as_bytes())
.expect("Blog::verify: content insertion error");
.update(data.as_bytes())?;
verifier
.verify(&signature)
.expect("Blog::verify: finalization error")
.map_err(Error::from)
}
}
@@ -493,9 +425,9 @@ impl NewBlog {
title: String,
summary: String,
instance_id: i32,
) -> NewBlog {
) -> Result<NewBlog> {
let (pub_key, priv_key) = sign::gen_keypair();
NewBlog {
Ok(NewBlog {
actor_id,
title,
summary,
@@ -503,11 +435,9 @@ impl NewBlog {
inbox_url: String::from(""),
instance_id,
ap_url: String::from(""),
public_key: String::from_utf8(pub_key).expect("NewBlog::new_local: public key error"),
private_key: Some(
String::from_utf8(priv_key).expect("NewBlog::new_local: private key error"),
),
}
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
})
}
}
@@ -529,23 +459,23 @@ pub(crate) mod tests {
"BlogName".to_owned(),
"Blog name".to_owned(),
"This is a small blog".to_owned(),
Instance::local_id(conn),
));
blog1.update_boxes(conn);
Instance::get_local(conn).unwrap().id
).unwrap()).unwrap();
blog1.update_boxes(conn).unwrap();
let blog2 = Blog::insert(conn, NewBlog::new_local(
"MyBlog".to_owned(),
"My blog".to_owned(),
"Welcome to my blog".to_owned(),
Instance::local_id(conn),
));
blog2.update_boxes(conn);
Instance::get_local(conn).unwrap().id
).unwrap()).unwrap();
blog2.update_boxes(conn).unwrap();
let blog3 = Blog::insert(conn, NewBlog::new_local(
"WhyILikePlume".to_owned(),
"Why I like Plume".to_owned(),
"In this blog I will explay you why I like Plume so much".to_owned(),
Instance::local_id(conn),
));
blog3.update_boxes(conn);
Instance::get_local(conn).unwrap().id
).unwrap()).unwrap();
blog3.update_boxes(conn).unwrap();
BlogAuthor::insert(
conn,
@@ -554,7 +484,7 @@ pub(crate) mod tests {
author_id: users[0].id,
is_owner: true,
},
);
).unwrap();
BlogAuthor::insert(
conn,
@@ -563,7 +493,7 @@ pub(crate) mod tests {
author_id: users[1].id,
is_owner: false,
},
);
).unwrap();
BlogAuthor::insert(
conn,
@@ -572,7 +502,7 @@ pub(crate) mod tests {
author_id: users[1].id,
is_owner: true,
},
);
).unwrap();
BlogAuthor::insert(
conn,
@@ -581,7 +511,7 @@ pub(crate) mod tests {
author_id: users[2].id,
is_owner: true,
},
);
).unwrap();
(users, vec![ blog1, blog2, blog3 ])
}
@@ -597,11 +527,11 @@ pub(crate) mod tests {
"SomeName".to_owned(),
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::local_id(conn),
),
);
Instance::get_local(conn).unwrap().id
).unwrap(),
).unwrap();
assert_eq!(blog.get_instance(conn).id, Instance::local_id(conn));
assert_eq!(blog.get_instance(conn).unwrap().id, Instance::get_local(conn).unwrap().id);
// TODO add tests for remote instance
Ok(())
@@ -620,20 +550,20 @@ pub(crate) mod tests {
"SomeName".to_owned(),
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::local_id(conn),
),
);
b1.update_boxes(conn);
Instance::get_local(conn).unwrap().id,
).unwrap(),
).unwrap();
b1.update_boxes(conn).unwrap();
let b2 = Blog::insert(
conn,
NewBlog::new_local(
"Blog".to_owned(),
"Blog".to_owned(),
"I've named my blog Blog".to_owned(),
Instance::local_id(conn),
),
);
b2.update_boxes(conn);
Instance::get_local(conn).unwrap().id
).unwrap(),
).unwrap();
b2.update_boxes(conn).unwrap();
let blog = vec![ b1, b2 ];
BlogAuthor::insert(
@@ -643,7 +573,7 @@ pub(crate) mod tests {
author_id: user[0].id,
is_owner: true,
},
);
).unwrap();
BlogAuthor::insert(
conn,
@@ -652,7 +582,7 @@ pub(crate) mod tests {
author_id: user[1].id,
is_owner: false,
},
);
).unwrap();
BlogAuthor::insert(
conn,
@@ -661,50 +591,50 @@ pub(crate) mod tests {
author_id: user[0].id,
is_owner: true,
},
);
).unwrap();
assert!(
blog[0]
.list_authors(conn)
.list_authors(conn).unwrap()
.iter()
.any(|a| a.id == user[0].id)
);
assert!(
blog[0]
.list_authors(conn)
.list_authors(conn).unwrap()
.iter()
.any(|a| a.id == user[1].id)
);
assert!(
blog[1]
.list_authors(conn)
.list_authors(conn).unwrap()
.iter()
.any(|a| a.id == user[0].id)
);
assert!(
!blog[1]
.list_authors(conn)
.list_authors(conn).unwrap()
.iter()
.any(|a| a.id == user[1].id)
);
assert!(
Blog::find_for_author(conn, &user[0])
Blog::find_for_author(conn, &user[0]).unwrap()
.iter()
.any(|b| b.id == blog[0].id)
);
assert!(
Blog::find_for_author(conn, &user[1])
Blog::find_for_author(conn, &user[1]).unwrap()
.iter()
.any(|b| b.id == blog[0].id)
);
assert!(
Blog::find_for_author(conn, &user[0])
Blog::find_for_author(conn, &user[0]).unwrap()
.iter()
.any(|b| b.id == blog[1].id)
);
assert!(
!Blog::find_for_author(conn, &user[1])
!Blog::find_for_author(conn, &user[1]).unwrap()
.iter()
.any(|b| b.id == blog[1].id)
);
@@ -725,9 +655,9 @@ pub(crate) mod tests {
"SomeName".to_owned(),
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::local_id(conn),
),
);
Instance::get_local(conn).unwrap().id,
).unwrap(),
).unwrap();
assert_eq!(
Blog::find_local(conn, "SomeName").unwrap().id,
@@ -750,9 +680,9 @@ pub(crate) mod tests {
"SomeName".to_owned(),
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::local_id(conn),
),
);
Instance::get_local(conn).unwrap().id,
).unwrap(),
).unwrap();
assert_eq!(blog.get_fqn(conn), "SomeName");
@@ -766,8 +696,8 @@ pub(crate) mod tests {
conn.test_transaction::<_, (), _>(|| {
let (_, blogs) = fill_database(conn);
blogs[0].delete(conn, &get_searcher());
assert!(Blog::get(conn, blogs[0].id).is_none());
blogs[0].delete(conn, &get_searcher()).unwrap();
assert!(Blog::get(conn, blogs[0].id).is_err());
Ok(())
});
@@ -786,20 +716,20 @@ pub(crate) mod tests {
"SomeName".to_owned(),
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::local_id(conn),
),
);
b1.update_boxes(conn);
Instance::get_local(conn).unwrap().id,
).unwrap(),
).unwrap();
b1.update_boxes(conn).unwrap();
let b2 = Blog::insert(
conn,
NewBlog::new_local(
"Blog".to_owned(),
"Blog".to_owned(),
"I've named my blog Blog".to_owned(),
Instance::local_id(conn),
),
);
b2.update_boxes(conn);
Instance::get_local(conn).unwrap().id,
).unwrap(),
).unwrap();
b2.update_boxes(conn).unwrap();
let blog = vec![ b1, b2 ];
BlogAuthor::insert(
@@ -809,7 +739,7 @@ pub(crate) mod tests {
author_id: user[0].id,
is_owner: true,
},
);
).unwrap();
BlogAuthor::insert(
conn,
@@ -818,7 +748,7 @@ pub(crate) mod tests {
author_id: user[1].id,
is_owner: false,
},
);
).unwrap();
BlogAuthor::insert(
conn,
@@ -827,13 +757,13 @@ pub(crate) mod tests {
author_id: user[0].id,
is_owner: true,
},
);
).unwrap();
user[0].delete(conn, &searcher);
assert!(Blog::get(conn, blog[0].id).is_some());
assert!(Blog::get(conn, blog[1].id).is_none());
user[1].delete(conn, &searcher);
assert!(Blog::get(conn, blog[0].id).is_none());
user[0].delete(conn, &searcher).unwrap();
assert!(Blog::get(conn, blog[0].id).is_ok());
assert!(Blog::get(conn, blog[1].id).is_err());
user[1].delete(conn, &searcher).unwrap();
assert!(Blog::get(conn, blog[0].id).is_err());
Ok(())
});