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:
+87
-79
@@ -21,20 +21,19 @@ use plume_models::{
|
||||
tags::*,
|
||||
users::User
|
||||
};
|
||||
use routes::comments::NewCommentForm;
|
||||
use routes::{errors::ErrorPage, comments::NewCommentForm};
|
||||
use template_utils::Ructe;
|
||||
use Worker;
|
||||
use Searcher;
|
||||
|
||||
#[get("/~/<blog>/<slug>?<responding_to>", rank = 4)]
|
||||
pub fn details(blog: String, slug: String, conn: DbConn, user: Option<User>, responding_to: Option<i32>, intl: I18n) -> Result<Ructe, Ructe> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &blog).ok_or_else(|| render!(errors::not_found(&(&*conn, &intl.catalog, user.clone()))))?;
|
||||
let post = Post::find_by_slug(&*conn, &slug, blog.id).ok_or_else(|| render!(errors::not_found(&(&*conn, &intl.catalog, user.clone()))))?;
|
||||
if post.published || post.get_authors(&*conn).into_iter().any(|a| a.id == user.clone().map(|u| u.id).unwrap_or(0)) {
|
||||
let comments = CommentTree::from_post(&*conn, &post, user.as_ref());
|
||||
pub fn details(blog: String, slug: String, conn: DbConn, user: Option<User>, responding_to: Option<i32>, intl: I18n) -> Result<Ructe, ErrorPage> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &blog)?;
|
||||
let post = Post::find_by_slug(&*conn, &slug, blog.id)?;
|
||||
if post.published || post.get_authors(&*conn)?.into_iter().any(|a| a.id == user.clone().map(|u| u.id).unwrap_or(0)) {
|
||||
let comments = CommentTree::from_post(&*conn, &post, user.as_ref())?;
|
||||
|
||||
let previous = responding_to.map(|r| Comment::get(&*conn, r)
|
||||
.expect("posts::details_reponse: Error retrieving previous comment"));
|
||||
let previous = responding_to.and_then(|r| Comment::get(&*conn, r).ok());
|
||||
|
||||
Ok(render!(posts::details(
|
||||
&(&*conn, &intl.catalog, user.clone()),
|
||||
@@ -42,14 +41,14 @@ pub fn details(blog: String, slug: String, conn: DbConn, user: Option<User>, res
|
||||
blog,
|
||||
&NewCommentForm {
|
||||
warning: previous.clone().map(|p| p.spoiler_text).unwrap_or_default(),
|
||||
content: previous.clone().map(|p| format!(
|
||||
content: previous.clone().and_then(|p| Some(format!(
|
||||
"@{} {}",
|
||||
p.get_author(&*conn).get_fqn(&*conn),
|
||||
Mention::list_for_comment(&*conn, p.id)
|
||||
p.get_author(&*conn).ok()?.get_fqn(&*conn),
|
||||
Mention::list_for_comment(&*conn, p.id).ok()?
|
||||
.into_iter()
|
||||
.filter_map(|m| {
|
||||
let user = user.clone();
|
||||
if let Some(mentioned) = m.get_mentioned(&*conn) {
|
||||
if let Ok(mentioned) = m.get_mentioned(&*conn) {
|
||||
if user.is_none() || mentioned.id != user.expect("posts::details_response: user error while listing mentions").id {
|
||||
Some(format!("@{}", mentioned.get_fqn(&*conn)))
|
||||
} else {
|
||||
@@ -59,22 +58,22 @@ pub fn details(blog: String, slug: String, conn: DbConn, user: Option<User>, res
|
||||
None
|
||||
}
|
||||
}).collect::<Vec<String>>().join(" "))
|
||||
).unwrap_or_default(),
|
||||
)).unwrap_or_default(),
|
||||
..NewCommentForm::default()
|
||||
},
|
||||
ValidationErrors::default(),
|
||||
Tag::for_post(&*conn, post.id),
|
||||
Tag::for_post(&*conn, post.id)?,
|
||||
comments,
|
||||
previous,
|
||||
post.count_likes(&*conn),
|
||||
post.count_reshares(&*conn),
|
||||
user.clone().map(|u| u.has_liked(&*conn, &post)).unwrap_or(false),
|
||||
user.clone().map(|u| u.has_reshared(&*conn, &post)).unwrap_or(false),
|
||||
user.map(|u| u.is_following(&*conn, post.get_authors(&*conn)[0].id)).unwrap_or(false),
|
||||
post.get_authors(&*conn)[0].clone()
|
||||
post.count_likes(&*conn)?,
|
||||
post.count_reshares(&*conn)?,
|
||||
user.clone().and_then(|u| u.has_liked(&*conn, &post).ok()).unwrap_or(false),
|
||||
user.clone().and_then(|u| u.has_reshared(&*conn, &post).ok()).unwrap_or(false),
|
||||
user.and_then(|u| u.is_following(&*conn, post.get_authors(&*conn).ok()?[0].id).ok()).unwrap_or(false),
|
||||
post.get_authors(&*conn)?[0].clone()
|
||||
)))
|
||||
} else {
|
||||
Err(render!(errors::not_authorized(
|
||||
Ok(render!(errors::not_authorized(
|
||||
&(&*conn, &intl.catalog, user.clone()),
|
||||
"This post isn't published yet."
|
||||
)))
|
||||
@@ -83,10 +82,10 @@ pub fn details(blog: String, slug: String, conn: DbConn, user: Option<User>, res
|
||||
|
||||
#[get("/~/<blog>/<slug>", rank = 3)]
|
||||
pub fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<LicensedArticle>, Option<String>> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &blog).ok_or(None)?;
|
||||
let post = Post::find_by_slug(&*conn, &slug, blog.id).ok_or(None)?;
|
||||
let blog = Blog::find_by_fqn(&*conn, &blog).map_err(|_| None)?;
|
||||
let post = Post::find_by_slug(&*conn, &slug, blog.id).map_err(|_| None)?;
|
||||
if post.published {
|
||||
Ok(ActivityStream::new(post.to_activity(&*conn)))
|
||||
Ok(ActivityStream::new(post.to_activity(&*conn).map_err(|_| String::from("Post serialization error"))?))
|
||||
} else {
|
||||
Err(Some(String::from("Not published yet.")))
|
||||
}
|
||||
@@ -101,23 +100,23 @@ pub fn new_auth(blog: String, i18n: I18n) -> Flash<Redirect> {
|
||||
}
|
||||
|
||||
#[get("/~/<blog>/new", rank = 1)]
|
||||
pub fn new(blog: String, user: User, conn: DbConn, intl: I18n) -> Option<Ructe> {
|
||||
pub fn new(blog: String, user: User, conn: DbConn, intl: I18n) -> Result<Ructe, ErrorPage> {
|
||||
let b = Blog::find_by_fqn(&*conn, &blog)?;
|
||||
|
||||
if !user.is_author_in(&*conn, &b) {
|
||||
if !user.is_author_in(&*conn, &b)? {
|
||||
// TODO actually return 403 error code
|
||||
Some(render!(errors::not_authorized(
|
||||
Ok(render!(errors::not_authorized(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
"You are not author in this blog."
|
||||
)))
|
||||
} else {
|
||||
let medias = Media::for_user(&*conn, user.id);
|
||||
Some(render!(posts::new(
|
||||
let medias = Media::for_user(&*conn, user.id)?;
|
||||
Ok(render!(posts::new(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
b,
|
||||
false,
|
||||
&NewPostForm {
|
||||
license: Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or_else(||String::from("CC-BY-SA")),
|
||||
license: Instance::get_local(&*conn)?.default_license,
|
||||
..NewPostForm::default()
|
||||
},
|
||||
true,
|
||||
@@ -129,12 +128,12 @@ pub fn new(blog: String, user: User, conn: DbConn, intl: I18n) -> Option<Ructe>
|
||||
}
|
||||
|
||||
#[get("/~/<blog>/<slug>/edit")]
|
||||
pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) -> Option<Ructe> {
|
||||
pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) -> Result<Ructe, ErrorPage> {
|
||||
let b = Blog::find_by_fqn(&*conn, &blog)?;
|
||||
let post = Post::find_by_slug(&*conn, &slug, b.id)?;
|
||||
|
||||
if !user.is_author_in(&*conn, &b) {
|
||||
Some(render!(errors::not_authorized(
|
||||
if !user.is_author_in(&*conn, &b)? {
|
||||
Ok(render!(errors::not_authorized(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
"You are not author in this blog."
|
||||
)))
|
||||
@@ -145,8 +144,8 @@ pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) ->
|
||||
post.content.get().clone() // fallback to HTML if the markdown was not stored
|
||||
};
|
||||
|
||||
let medias = Media::for_user(&*conn, user.id);
|
||||
Some(render!(posts::new(
|
||||
let medias = Media::for_user(&*conn, user.id)?;
|
||||
Ok(render!(posts::new(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
b,
|
||||
true,
|
||||
@@ -154,7 +153,7 @@ pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) ->
|
||||
title: post.title.clone(),
|
||||
subtitle: post.subtitle.clone(),
|
||||
content: source,
|
||||
tags: Tag::for_post(&*conn, post.id)
|
||||
tags: Tag::for_post(&*conn, post.id)?
|
||||
.into_iter()
|
||||
.filter_map(|t| if !t.is_hashtag {Some(t.tag)} else {None})
|
||||
.collect::<Vec<String>>()
|
||||
@@ -173,9 +172,9 @@ pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) ->
|
||||
|
||||
#[post("/~/<blog>/<slug>/edit", data = "<form>")]
|
||||
pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: LenientForm<NewPostForm>, worker: Worker, intl: I18n, searcher: Searcher)
|
||||
-> Result<Redirect, Option<Ructe>> {
|
||||
let b = Blog::find_by_fqn(&*conn, &blog).ok_or(None)?;
|
||||
let mut post = Post::find_by_slug(&*conn, &slug, b.id).ok_or(None)?;
|
||||
-> Result<Redirect, Ructe> {
|
||||
let b = Blog::find_by_fqn(&*conn, &blog).expect("post::update: blog error");
|
||||
let mut post = Post::find_by_slug(&*conn, &slug, b.id).expect("post::update: find by slug error");
|
||||
|
||||
let new_slug = if !post.published {
|
||||
form.title.to_string().to_kebab_case()
|
||||
@@ -188,7 +187,7 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
||||
Err(e) => e
|
||||
};
|
||||
|
||||
if new_slug != slug && Post::find_by_slug(&*conn, &new_slug, b.id).is_some() {
|
||||
if new_slug != slug && Post::find_by_slug(&*conn, &new_slug, b.id).is_ok() {
|
||||
errors.add("title", ValidationError {
|
||||
code: Cow::from("existing_slug"),
|
||||
message: Some(Cow::from("A post with the same title already exists.")),
|
||||
@@ -197,7 +196,7 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
if !user.is_author_in(&*conn, &b) {
|
||||
if !user.is_author_in(&*conn, &b).expect("posts::update: is author in error") {
|
||||
// actually it's not "Ok"…
|
||||
Ok(Redirect::to(uri!(super::blogs::details: name = blog, page = _)))
|
||||
} else {
|
||||
@@ -219,29 +218,30 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
||||
post.source = form.content.clone();
|
||||
post.license = form.license.clone();
|
||||
post.cover_id = form.cover;
|
||||
post.update(&*conn, &searcher);
|
||||
let post = post.update_ap_url(&*conn);
|
||||
post.update(&*conn, &searcher).expect("post::update: update error");;
|
||||
let post = post.update_ap_url(&*conn).expect("post::update: update ap url error");
|
||||
|
||||
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().filter_map(|m| Mention::build_activity(&conn, &m).ok()).collect())
|
||||
.expect("post::update: mentions error");;
|
||||
}
|
||||
|
||||
let tags = form.tags.split(',').map(|t| t.trim().to_camel_case()).filter(|t| !t.is_empty())
|
||||
.collect::<HashSet<_>>().into_iter().map(|t| Tag::build_activity(&conn, t)).collect::<Vec<_>>();
|
||||
post.update_tags(&conn, tags);
|
||||
.collect::<HashSet<_>>().into_iter().filter_map(|t| Tag::build_activity(&conn, t).ok()).collect::<Vec<_>>();
|
||||
post.update_tags(&conn, tags).expect("post::update: tags error");
|
||||
|
||||
let hashtags = hashtags.into_iter().map(|h| h.to_camel_case()).collect::<HashSet<_>>()
|
||||
.into_iter().map(|t| Tag::build_activity(&conn, t)).collect::<Vec<_>>();
|
||||
post.update_hashtags(&conn, hashtags);
|
||||
.into_iter().filter_map(|t| Tag::build_activity(&conn, t).ok()).collect::<Vec<_>>();
|
||||
post.update_hashtags(&conn, hashtags).expect("post::update: hashtags error");
|
||||
|
||||
if post.published {
|
||||
if newly_published {
|
||||
let act = post.create_activity(&conn);
|
||||
let dest = User::one_by_instance(&*conn);
|
||||
let act = post.create_activity(&conn).expect("post::update: act error");
|
||||
let dest = User::one_by_instance(&*conn).expect("post::update: dest error");
|
||||
worker.execute(move || broadcast(&user, act, dest));
|
||||
} else {
|
||||
let act = post.update_activity(&*conn);
|
||||
let dest = User::one_by_instance(&*conn);
|
||||
let act = post.update_activity(&*conn).expect("post::update: act error");
|
||||
let dest = User::one_by_instance(&*conn).expect("posts::update: dest error");
|
||||
worker.execute(move || broadcast(&user, act, dest));
|
||||
}
|
||||
}
|
||||
@@ -249,8 +249,8 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
||||
Ok(Redirect::to(uri!(details: blog = blog, slug = new_slug, responding_to = _)))
|
||||
}
|
||||
} else {
|
||||
let medias = Media::for_user(&*conn, user.id);
|
||||
let temp = render!(posts::new(
|
||||
let medias = Media::for_user(&*conn, user.id).expect("posts:update: medias error");
|
||||
Err(render!(posts::new(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
b,
|
||||
true,
|
||||
@@ -259,8 +259,7 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
||||
Some(post),
|
||||
errors.clone(),
|
||||
medias.clone()
|
||||
));
|
||||
Err(Some(temp))
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,15 +287,15 @@ pub fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
||||
}
|
||||
|
||||
#[post("/~/<blog_name>/new", data = "<form>")]
|
||||
pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, conn: DbConn, worker: Worker, intl: I18n, searcher: Searcher) -> Result<Redirect, Option<Ructe>> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &blog_name).ok_or(None)?;
|
||||
pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, conn: DbConn, worker: Worker, intl: I18n, searcher: Searcher) -> Result<Redirect, Result<Ructe, ErrorPage>> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &blog_name).expect("post::create: blog error");;
|
||||
let slug = form.title.to_string().to_kebab_case();
|
||||
|
||||
let mut errors = match form.validate() {
|
||||
Ok(_) => ValidationErrors::new(),
|
||||
Err(e) => e
|
||||
};
|
||||
if Post::find_by_slug(&*conn, &slug, blog.id).is_some() {
|
||||
if Post::find_by_slug(&*conn, &slug, blog.id).is_ok() {
|
||||
errors.add("title", ValidationError {
|
||||
code: Cow::from("existing_slug"),
|
||||
message: Some(Cow::from("A post with the same title already exists.")),
|
||||
@@ -305,11 +304,14 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, con
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
if !user.is_author_in(&*conn, &blog) {
|
||||
if !user.is_author_in(&*conn, &blog).expect("post::create: is author in error") {
|
||||
// actually it's not "Ok"…
|
||||
Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _)))
|
||||
} else {
|
||||
let (content, mentions, hashtags) = utils::md_to_html(form.content.to_string().as_ref(), &Instance::get_local(&conn).expect("posts::create: Error getting l ocal instance").public_domain);
|
||||
let (content, mentions, hashtags) = utils::md_to_html(
|
||||
form.content.to_string().as_ref(),
|
||||
&Instance::get_local(&conn).expect("post::create: local instance error").public_domain
|
||||
);
|
||||
|
||||
let post = Post::insert(&*conn, NewPost {
|
||||
blog_id: blog.id,
|
||||
@@ -325,12 +327,12 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, con
|
||||
cover_id: form.cover,
|
||||
},
|
||||
&searcher,
|
||||
);
|
||||
let post = post.update_ap_url(&*conn);
|
||||
).expect("post::create: post save error");
|
||||
let post = post.update_ap_url(&*conn).expect("post::create: update ap url error");
|
||||
PostAuthor::insert(&*conn, NewPostAuthor {
|
||||
post_id: post.id,
|
||||
author_id: user.id
|
||||
});
|
||||
}).expect("post::create: author save error");
|
||||
|
||||
let tags = form.tags.split(',')
|
||||
.map(|t| t.trim().to_camel_case())
|
||||
@@ -341,31 +343,37 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, con
|
||||
tag,
|
||||
is_hashtag: false,
|
||||
post_id: post.id
|
||||
});
|
||||
}).expect("post::create: tags save error");
|
||||
}
|
||||
for hashtag in hashtags {
|
||||
Tag::insert(&*conn, NewTag {
|
||||
tag: hashtag.to_camel_case(),
|
||||
is_hashtag: true,
|
||||
post_id: post.id
|
||||
});
|
||||
}).expect("post::create: hashtags save error");
|
||||
}
|
||||
|
||||
if post.published {
|
||||
for m in mentions {
|
||||
Mention::from_activity(&*conn, &Mention::build_activity(&*conn, &m), post.id, true, true);
|
||||
Mention::from_activity(
|
||||
&*conn,
|
||||
&Mention::build_activity(&*conn, &m).expect("post::create: mention build error"),
|
||||
post.id,
|
||||
true,
|
||||
true
|
||||
).expect("post::create: mention save error");
|
||||
}
|
||||
|
||||
let act = post.create_activity(&*conn);
|
||||
let dest = User::one_by_instance(&*conn);
|
||||
let act = post.create_activity(&*conn).expect("posts::create: activity error");
|
||||
let dest = User::one_by_instance(&*conn).expect("posts::create: dest error");
|
||||
worker.execute(move || broadcast(&user, act, dest));
|
||||
}
|
||||
|
||||
Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug, responding_to = _)))
|
||||
}
|
||||
} else {
|
||||
let medias = Media::for_user(&*conn, user.id);
|
||||
Err(Some(render!(posts::new(
|
||||
let medias = Media::for_user(&*conn, user.id).expect("posts::create: medias error");
|
||||
Err(Ok(render!(posts::new(
|
||||
&(&*conn, &intl.catalog, Some(user)),
|
||||
blog,
|
||||
false,
|
||||
@@ -379,21 +387,21 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, con
|
||||
}
|
||||
|
||||
#[post("/~/<blog_name>/<slug>/delete")]
|
||||
pub fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: Worker, searcher: Searcher) -> Redirect {
|
||||
pub fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: Worker, searcher: Searcher) -> Result<Redirect, ErrorPage> {
|
||||
let post = Blog::find_by_fqn(&*conn, &blog_name)
|
||||
.and_then(|blog| Post::find_by_slug(&*conn, &slug, blog.id));
|
||||
|
||||
if let Some(post) = post {
|
||||
if !post.get_authors(&*conn).into_iter().any(|a| a.id == user.id) {
|
||||
Redirect::to(uri!(details: blog = blog_name.clone(), slug = slug.clone(), responding_to = _))
|
||||
if let Ok(post) = post {
|
||||
if !post.get_authors(&*conn)?.into_iter().any(|a| a.id == user.id) {
|
||||
Ok(Redirect::to(uri!(details: blog = blog_name.clone(), slug = slug.clone(), responding_to = _)))
|
||||
} else {
|
||||
let dest = User::one_by_instance(&*conn);
|
||||
let delete_activity = post.delete(&(&conn, &searcher));
|
||||
let dest = User::one_by_instance(&*conn)?;
|
||||
let delete_activity = post.delete(&(&conn, &searcher))?;
|
||||
worker.execute(move || broadcast(&user, delete_activity, dest));
|
||||
|
||||
Redirect::to(uri!(super::blogs::details: name = blog_name, page = _))
|
||||
Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _)))
|
||||
}
|
||||
} else {
|
||||
Redirect::to(uri!(super::blogs::details: name = blog_name, page = _))
|
||||
Ok(Redirect::to(uri!(super::blogs::details: name = blog_name, page = _)))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user