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:
+102
-134
@@ -18,7 +18,7 @@ use posts::Post;
|
||||
use safe_string::SafeString;
|
||||
use schema::comments;
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Queryable, Identifiable, Serialize, Clone)]
|
||||
pub struct Comment {
|
||||
@@ -53,150 +53,125 @@ impl Comment {
|
||||
list_by!(comments, list_by_post, post_id as i32);
|
||||
find_by!(comments, find_by_ap_url, ap_url as &str);
|
||||
|
||||
pub fn get_author(&self, conn: &Connection) -> User {
|
||||
User::get(conn, self.author_id).expect("Comment::get_author: author error")
|
||||
pub fn get_author(&self, conn: &Connection) -> Result<User> {
|
||||
User::get(conn, self.author_id)
|
||||
}
|
||||
|
||||
pub fn get_post(&self, conn: &Connection) -> Post {
|
||||
Post::get(conn, self.post_id).expect("Comment::get_post: post error")
|
||||
pub fn get_post(&self, conn: &Connection) -> Result<Post> {
|
||||
Post::get(conn, self.post_id)
|
||||
}
|
||||
|
||||
pub fn count_local(conn: &Connection) -> i64 {
|
||||
pub fn count_local(conn: &Connection) -> Result<i64> {
|
||||
use schema::users;
|
||||
let local_authors = users::table
|
||||
.filter(users::instance_id.eq(Instance::local_id(conn)))
|
||||
.filter(users::instance_id.eq(Instance::get_local(conn)?.id))
|
||||
.select(users::id);
|
||||
comments::table
|
||||
.filter(comments::author_id.eq_any(local_authors))
|
||||
.count()
|
||||
.get_result(conn)
|
||||
.expect("Comment::count_local: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn get_responses(&self, conn: &Connection) -> Vec<Comment> {
|
||||
pub fn get_responses(&self, conn: &Connection) -> Result<Vec<Comment>> {
|
||||
comments::table.filter(comments::in_response_to_id.eq(self.id))
|
||||
.load::<Comment>(conn)
|
||||
.expect("Comment::get_responses: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn update_ap_url(&self, conn: &Connection) -> Comment {
|
||||
pub fn update_ap_url(&self, conn: &Connection) -> Result<Comment> {
|
||||
if self.ap_url.is_none() {
|
||||
diesel::update(self)
|
||||
.set(comments::ap_url.eq(self.compute_id(conn)))
|
||||
.execute(conn)
|
||||
.expect("Comment::update_ap_url: update error");
|
||||
Comment::get(conn, self.id).expect("Comment::update_ap_url: get error")
|
||||
.set(comments::ap_url.eq(self.compute_id(conn)?))
|
||||
.execute(conn)?;
|
||||
Comment::get(conn, self.id)
|
||||
} else {
|
||||
self.clone()
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_id(&self, conn: &Connection) -> String {
|
||||
format!("{}comment/{}", self.get_post(conn).ap_url, self.id)
|
||||
pub fn compute_id(&self, conn: &Connection) -> Result<String> {
|
||||
Ok(format!("{}comment/{}", self.get_post(conn)?.ap_url, self.id))
|
||||
}
|
||||
|
||||
pub fn can_see(&self, conn: &Connection, user: Option<&User>) -> bool {
|
||||
self.public_visibility ||
|
||||
user.as_ref().map(|u| CommentSeers::can_see(conn, self, u)).unwrap_or(false)
|
||||
user.as_ref().map(|u| CommentSeers::can_see(conn, self, u).unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> Note {
|
||||
pub fn to_activity(&self, conn: &Connection) -> Result<Note> {
|
||||
let (html, mentions, _hashtags) = utils::md_to_html(self.content.get().as_ref(),
|
||||
&Instance::get_local(conn)
|
||||
.expect("Comment::to_activity: instance error")
|
||||
.public_domain);
|
||||
&Instance::get_local(conn)?.public_domain);
|
||||
|
||||
let author = User::get(conn, self.author_id).expect("Comment::to_activity: author error");
|
||||
let author = User::get(conn, self.author_id)?;
|
||||
let mut note = Note::default();
|
||||
let to = vec![Id::new(PUBLIC_VISIBILTY.to_string())];
|
||||
|
||||
note.object_props
|
||||
.set_id_string(self.ap_url.clone().unwrap_or_default())
|
||||
.expect("Comment::to_activity: id error");
|
||||
.set_id_string(self.ap_url.clone().unwrap_or_default())?;
|
||||
note.object_props
|
||||
.set_summary_string(self.spoiler_text.clone())
|
||||
.expect("Comment::to_activity: summary error");
|
||||
.set_summary_string(self.spoiler_text.clone())?;
|
||||
note.object_props
|
||||
.set_content_string(html)
|
||||
.expect("Comment::to_activity: content error");
|
||||
.set_content_string(html)?;
|
||||
note.object_props
|
||||
.set_in_reply_to_link(Id::new(self.in_response_to_id.map_or_else(
|
||||
|| {
|
||||
Post::get(conn, self.post_id)
|
||||
.expect("Comment::to_activity: post error")
|
||||
.ap_url
|
||||
},
|
||||
|id| {
|
||||
let comm =
|
||||
Comment::get(conn, id).expect("Comment::to_activity: comment error");
|
||||
comm.ap_url.clone().unwrap_or_else(|| comm.compute_id(conn))
|
||||
},
|
||||
)))
|
||||
.expect("Comment::to_activity: in_reply_to error");
|
||||
|| Ok(Post::get(conn, self.post_id)?.ap_url),
|
||||
|id| Ok(Comment::get(conn, id)?.compute_id(conn)?) as Result<String>,
|
||||
)?))?;
|
||||
note.object_props
|
||||
.set_published_string(chrono::Utc::now().to_rfc3339())
|
||||
.expect("Comment::to_activity: published error");
|
||||
.set_published_string(chrono::Utc::now().to_rfc3339())?;
|
||||
note.object_props
|
||||
.set_attributed_to_link(author.clone().into_id())
|
||||
.expect("Comment::to_activity: attributed_to error");
|
||||
.set_attributed_to_link(author.clone().into_id())?;
|
||||
note.object_props
|
||||
.set_to_link_vec(to.clone())
|
||||
.expect("Comment::to_activity: to error");
|
||||
.set_to_link_vec(to.clone())?;
|
||||
note.object_props
|
||||
.set_tag_link_vec(
|
||||
mentions
|
||||
.into_iter()
|
||||
.map(|m| Mention::build_activity(conn, &m))
|
||||
.filter_map(|m| Mention::build_activity(conn, &m).ok())
|
||||
.collect::<Vec<link::Mention>>(),
|
||||
)
|
||||
.expect("Comment::to_activity: tag error");
|
||||
note
|
||||
)?;
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
pub fn create_activity(&self, conn: &Connection) -> Create {
|
||||
pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
|
||||
let author =
|
||||
User::get(conn, self.author_id).expect("Comment::create_activity: author error");
|
||||
User::get(conn, self.author_id)?;
|
||||
|
||||
let note = self.to_activity(conn);
|
||||
let note = self.to_activity(conn)?;
|
||||
let mut act = Create::default();
|
||||
act.create_props
|
||||
.set_actor_link(author.into_id())
|
||||
.expect("Comment::create_activity: actor error");
|
||||
.set_actor_link(author.into_id())?;
|
||||
act.create_props
|
||||
.set_object_object(note.clone())
|
||||
.expect("Comment::create_activity: object error");
|
||||
.set_object_object(note.clone())?;
|
||||
act.object_props
|
||||
.set_id_string(format!(
|
||||
"{}/activity",
|
||||
self.ap_url
|
||||
.clone()
|
||||
.expect("Comment::create_activity: ap_url error")
|
||||
))
|
||||
.expect("Comment::create_activity: id error");
|
||||
.clone()?,
|
||||
))?;
|
||||
act.object_props
|
||||
.set_to_link_vec(
|
||||
note.object_props
|
||||
.to_link_vec::<Id>()
|
||||
.expect("Comment::create_activity: id error"),
|
||||
)
|
||||
.expect("Comment::create_activity: to error");
|
||||
.to_link_vec::<Id>()?,
|
||||
)?;
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Comment::create_activity: cc error");
|
||||
act
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
Ok(act)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromActivity<Note, Connection> for Comment {
|
||||
fn from_activity(conn: &Connection, note: Note, actor: Id) -> Comment {
|
||||
type Error = Error;
|
||||
|
||||
fn from_activity(conn: &Connection, note: Note, actor: Id) -> Result<Comment> {
|
||||
let comm = {
|
||||
let previous_url = note
|
||||
.object_props
|
||||
.in_reply_to
|
||||
.as_ref()
|
||||
.expect("Comment::from_activity: not an answer error")
|
||||
.as_str()
|
||||
.expect("Comment::from_activity: in_reply_to parsing error");
|
||||
.as_ref()?
|
||||
.as_str()?;
|
||||
let previous_comment = Comment::find_by_ap_url(conn, previous_url);
|
||||
|
||||
let is_public = |v: &Option<serde_json::Value>| match v.as_ref().unwrap_or(&serde_json::Value::Null) {
|
||||
@@ -216,42 +191,35 @@ impl FromActivity<Note, Connection> for Comment {
|
||||
content: SafeString::new(
|
||||
¬e
|
||||
.object_props
|
||||
.content_string()
|
||||
.expect("Comment::from_activity: content deserialization error"),
|
||||
.content_string()?
|
||||
),
|
||||
spoiler_text: note
|
||||
.object_props
|
||||
.summary_string()
|
||||
.unwrap_or_default(),
|
||||
ap_url: note.object_props.id_string().ok(),
|
||||
in_response_to_id: previous_comment.clone().map(|c| c.id),
|
||||
post_id: previous_comment.map(|c| c.post_id).unwrap_or_else(|| {
|
||||
Post::find_by_ap_url(conn, previous_url)
|
||||
.expect("Comment::from_activity: post error")
|
||||
.id
|
||||
}),
|
||||
author_id: User::from_url(conn, actor.as_ref())
|
||||
.expect("Comment::from_activity: author error")
|
||||
.id,
|
||||
in_response_to_id: previous_comment.iter().map(|c| c.id).next(),
|
||||
post_id: previous_comment.map(|c| c.post_id)
|
||||
.or_else(|_| Ok(Post::find_by_ap_url(conn, previous_url)?.id) as Result<i32>)?,
|
||||
author_id: User::from_url(conn, actor.as_ref())?.id,
|
||||
sensitive: false, // "sensitive" is not a standard property, we need to think about how to support it with the activitypub crate
|
||||
public_visibility
|
||||
},
|
||||
);
|
||||
)?;
|
||||
|
||||
// save mentions
|
||||
if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() {
|
||||
for tag in tags {
|
||||
serde_json::from_value::<link::Mention>(tag)
|
||||
.map(|m| {
|
||||
let author = &Post::get(conn, comm.post_id)
|
||||
.expect("Comment::from_activity: error")
|
||||
.get_authors(conn)[0];
|
||||
.map_err(Error::from)
|
||||
.and_then(|m| {
|
||||
let author = &Post::get(conn, comm.post_id)?
|
||||
.get_authors(conn)?[0];
|
||||
let not_author = m
|
||||
.link_props
|
||||
.href_string()
|
||||
.expect("Comment::from_activity: no href error")
|
||||
.href_string()?
|
||||
!= author.ap_url.clone();
|
||||
Mention::from_activity(conn, &m, comm.id, false, not_author)
|
||||
Ok(Mention::from_activity(conn, &m, comm.id, false, not_author)?)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -279,13 +247,13 @@ impl FromActivity<Note, Connection> for Comment {
|
||||
let receivers_ap_url = to.chain(cc).chain(bto).chain(bcc)
|
||||
.collect::<HashSet<_>>()//remove duplicates (don't do a query more than once)
|
||||
.into_iter()
|
||||
.map(|v| if let Some(user) = User::from_url(conn,&v) {
|
||||
.map(|v| if let Ok(user) = User::from_url(conn,&v) {
|
||||
vec![user]
|
||||
} else {
|
||||
vec![]// TODO try to fetch collection
|
||||
})
|
||||
.flatten()
|
||||
.filter(|u| u.get_instance(conn).local)
|
||||
.filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false))
|
||||
.collect::<HashSet<User>>();//remove duplicates (prevent db error)
|
||||
|
||||
for user in &receivers_ap_url {
|
||||
@@ -295,18 +263,20 @@ impl FromActivity<Note, Connection> for Comment {
|
||||
comment_id: comm.id,
|
||||
user_id: user.id
|
||||
}
|
||||
);
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
comm.notify(conn);
|
||||
comm
|
||||
comm.notify(conn)?;
|
||||
Ok(comm)
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify<Connection> for Comment {
|
||||
fn notify(&self, conn: &Connection) {
|
||||
for author in self.get_post(conn).get_authors(conn) {
|
||||
type Error = Error;
|
||||
|
||||
fn notify(&self, conn: &Connection) -> Result<()> {
|
||||
for author in self.get_post(conn)?.get_authors(conn)? {
|
||||
Notification::insert(
|
||||
conn,
|
||||
NewNotification {
|
||||
@@ -314,8 +284,9 @@ impl Notify<Connection> for Comment {
|
||||
object_id: self.id,
|
||||
user_id: author.id,
|
||||
},
|
||||
);
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,67 +296,64 @@ pub struct CommentTree {
|
||||
}
|
||||
|
||||
impl CommentTree {
|
||||
pub fn from_post(conn: &Connection, p: &Post, user: Option<&User>) -> Vec<Self> {
|
||||
Comment::list_by_post(conn, p.id).into_iter()
|
||||
pub fn from_post(conn: &Connection, p: &Post, user: Option<&User>) -> Result<Vec<Self>> {
|
||||
Ok(Comment::list_by_post(conn, p.id)?.into_iter()
|
||||
.filter(|c| c.in_response_to_id.is_none())
|
||||
.filter(|c| c.can_see(conn, user))
|
||||
.map(|c| Self::from_comment(conn, c, user))
|
||||
.collect()
|
||||
.filter_map(|c| Self::from_comment(conn, c, user).ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn from_comment(conn: &Connection, comment: Comment, user: Option<&User>) -> Self {
|
||||
let responses = comment.get_responses(conn).into_iter()
|
||||
pub fn from_comment(conn: &Connection, comment: Comment, user: Option<&User>) -> Result<Self> {
|
||||
let responses = comment.get_responses(conn)?.into_iter()
|
||||
.filter(|c| c.can_see(conn, user))
|
||||
.map(|c| Self::from_comment(conn, c, user))
|
||||
.filter_map(|c| Self::from_comment(conn, c, user).ok())
|
||||
.collect();
|
||||
CommentTree {
|
||||
Ok(CommentTree {
|
||||
comment,
|
||||
responses,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deletable<Connection, Delete> for Comment {
|
||||
fn delete(&self, conn: &Connection) -> Delete {
|
||||
type Error = Error;
|
||||
|
||||
fn delete(&self, conn: &Connection) -> Result<Delete> {
|
||||
let mut act = Delete::default();
|
||||
act.delete_props
|
||||
.set_actor_link(self.get_author(conn).into_id())
|
||||
.expect("Comment::delete: actor error");
|
||||
.set_actor_link(self.get_author(conn)?.into_id())?;
|
||||
|
||||
let mut tombstone = Tombstone::default();
|
||||
tombstone
|
||||
.object_props
|
||||
.set_id_string(self.ap_url.clone().expect("Comment::delete: no ap_url"))
|
||||
.expect("Comment::delete: object.id error");
|
||||
.set_id_string(self.ap_url.clone()?)?;
|
||||
act.delete_props
|
||||
.set_object_object(tombstone)
|
||||
.expect("Comment::delete: object error");
|
||||
.set_object_object(tombstone)?;
|
||||
|
||||
act.object_props
|
||||
.set_id_string(format!("{}#delete", self.ap_url.clone().unwrap()))
|
||||
.expect("Comment::delete: id error");
|
||||
.set_id_string(format!("{}#delete", self.ap_url.clone().unwrap()))?;
|
||||
act.object_props
|
||||
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILTY)])
|
||||
.expect("Comment::delete: to error");
|
||||
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILTY)])?;
|
||||
|
||||
for m in Mention::list_for_comment(&conn, self.id) {
|
||||
m.delete(conn);
|
||||
for m in Mention::list_for_comment(&conn, self.id)? {
|
||||
m.delete(conn)?;
|
||||
}
|
||||
diesel::update(comments::table).filter(comments::in_response_to_id.eq(self.id))
|
||||
.set(comments::in_response_to_id.eq(self.in_response_to_id))
|
||||
.execute(conn)
|
||||
.expect("Comment::delete: DB error could not update other comments");
|
||||
.execute(conn)?;
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Comment::delete: DB error");
|
||||
act
|
||||
.execute(conn)?;
|
||||
Ok(act)
|
||||
}
|
||||
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) {
|
||||
let actor = User::find_by_ap_url(conn, actor_id);
|
||||
let comment = Comment::find_by_ap_url(conn, id);
|
||||
if let Some(comment) = comment.filter(|c| c.author_id == actor.unwrap().id) {
|
||||
comment.delete(conn);
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) -> Result<Delete> {
|
||||
let actor = User::find_by_ap_url(conn, actor_id)?;
|
||||
let comment = Comment::find_by_ap_url(conn, id)?;
|
||||
if comment.author_id == actor.id {
|
||||
comment.delete(conn)
|
||||
} else {
|
||||
Err(Error::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user