Normalize panic message and return 400 or 404 when suitable
This commit is contained in:
		
							parent
							
								
									4e6f3209d5
								
							
						
					
					
						commit
						fd92383f87
					
				| @ -19,7 +19,7 @@ fn get(id: i32, conn: DbConn) -> Json<serde_json::Value> { | ||||
| 
 | ||||
| #[get("/posts")] | ||||
| fn list(conn: DbConn, uri: &Origin) -> Json<serde_json::Value> { | ||||
|     let query: PostEndpoint = serde_qs::from_str(uri.query().unwrap_or("")).expect("Invalid query string"); | ||||
|     let query: PostEndpoint = serde_qs::from_str(uri.query().unwrap_or("")).expect("api::list: invalid query error"); | ||||
|     let post = <Post as Provider<Connection>>::list(&*conn, query); | ||||
|     Json(json!(post)) | ||||
| } | ||||
|  | ||||
| @ -16,7 +16,7 @@ use plume_models::{ | ||||
| 
 | ||||
| pub trait Inbox { | ||||
|     fn received(&self, conn: &Connection, act: serde_json::Value) -> Result<(), Error> { | ||||
|         let actor_id = Id::new(act["actor"].as_str().unwrap_or_else(|| act["actor"]["id"].as_str().expect("No actor ID for incoming activity"))); | ||||
|         let actor_id = Id::new(act["actor"].as_str().unwrap_or_else(|| act["actor"]["id"].as_str().expect("Inbox::received: actor_id missing error"))); | ||||
|         match act["type"].as_str() { | ||||
|             Some(t) => { | ||||
|                 match t { | ||||
| @ -47,7 +47,7 @@ pub trait Inbox { | ||||
|                     }, | ||||
|                     "Undo" => { | ||||
|                         let act: Undo = serde_json::from_value(act.clone())?; | ||||
|                         match act.undo_props.object["type"].as_str().unwrap() { | ||||
|                         match act.undo_props.object["type"].as_str().expect("Inbox::received: undo without original type error") { | ||||
|                             "Like" => { | ||||
|                                 likes::Like::delete_id(act.undo_props.object_object::<Like>()?.object_props.id_string()?, conn); | ||||
|                                 Ok(()) | ||||
|  | ||||
| @ -56,7 +56,7 @@ fn init_pool() -> Option<DbPool> { | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let pool = init_pool().expect("Couldn't intialize database pool"); | ||||
|     let pool = init_pool().expect("main: database pool initialization error"); | ||||
|     rocket::ignite() | ||||
|         .mount("/", routes![ | ||||
|             routes::blogs::paginated_details, | ||||
| @ -176,6 +176,6 @@ fn main() { | ||||
|                     ("/login".to_owned(), "/login".to_owned(), rocket::http::Method::Post), | ||||
|                     ("/users/new".to_owned(), "/users/new".to_owned(), rocket::http::Method::Post), | ||||
|                 ]) | ||||
|                 .finalize().unwrap()) | ||||
|                 .finalize().expect("main: csrf fairing creation error")) | ||||
|         .launch(); | ||||
| } | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| use activitypub::collection::OrderedCollection; | ||||
| use atom_syndication::{Entry, FeedBuilder}; | ||||
| use rocket::{ | ||||
|     http::ContentType, | ||||
|     request::LenientForm, | ||||
|     response::{Redirect, Flash, content::Content}, | ||||
|     http::ContentType | ||||
|     response::{Redirect, Flash, content::Content} | ||||
| }; | ||||
| use rocket_contrib::Template; | ||||
| use serde_json; | ||||
| @ -49,9 +49,9 @@ fn details(name: String, conn: DbConn, user: Option<User>) -> Template { | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<name>", rank = 1)] | ||||
| fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> ActivityStream<CustomGroup> { | ||||
|     let blog = Blog::find_local(&*conn, name).unwrap(); | ||||
|     ActivityStream::new(blog.into_activity(&*conn)) | ||||
| fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<CustomGroup>> { | ||||
|     let blog = Blog::find_local(&*conn, name)?; | ||||
|     Some(ActivityStream::new(blog.into_activity(&*conn))) | ||||
| } | ||||
| 
 | ||||
| #[get("/blogs/new")] | ||||
| @ -130,22 +130,23 @@ fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Result<Re | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<name>/outbox")] | ||||
| fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> { | ||||
|     let blog = Blog::find_local(&*conn, name).unwrap(); | ||||
|     blog.outbox(&*conn) | ||||
| fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> { | ||||
|     let blog = Blog::find_local(&*conn, name)?; | ||||
|     Some(blog.outbox(&*conn)) | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<name>/atom.xml")] | ||||
| fn atom_feed(name: String, conn: DbConn) -> Content<String> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, name.clone()).expect("Unable to find blog"); | ||||
| fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, name.clone())?; | ||||
|     let feed = FeedBuilder::default() | ||||
|         .title(blog.title.clone()) | ||||
|         .id(Instance::get_local(&*conn).unwrap().compute_box("~", name, "atom.xml")) | ||||
|         .id(Instance::get_local(&*conn).expect("blogs::atom_feed: local instance not found error") | ||||
|             .compute_box("~", name, "atom.xml")) | ||||
|         .entries(Post::get_recents_for_blog(&*conn, &blog, 15) | ||||
|             .into_iter() | ||||
|             .map(|p| super::post_to_atom(p, &*conn)) | ||||
|             .collect::<Vec<Entry>>()) | ||||
|         .build() | ||||
|         .expect("Error building Atom feed"); | ||||
|     Content(ContentType::new("application", "atom+xml"), feed.to_string()) | ||||
|         .expect("blogs::atom_feed: feed creation error"); | ||||
|     Some(Content(ContentType::new("application", "atom+xml"), feed.to_string())) | ||||
| } | ||||
|  | ||||
| @ -28,9 +28,10 @@ struct NewCommentForm { | ||||
| } | ||||
| 
 | ||||
| #[post("/~/<blog_name>/<slug>/comment", data = "<data>")] | ||||
| fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Template> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).unwrap(); | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).unwrap(); | ||||
| fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) | ||||
|     -> Result<Redirect, Option<Template>> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).ok_or(None)?; | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).ok_or(None)?; | ||||
|     let form = data.get(); | ||||
|     form.validate() | ||||
|         .map(|_| { | ||||
| @ -63,7 +64,7 @@ fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, us | ||||
|             let comments = Comment::list_by_post(&*conn, post.id); | ||||
|             let comms = comments.clone(); | ||||
| 
 | ||||
|             Template::render("posts/details", json!({ | ||||
|             Some(Template::render("posts/details", json!({ | ||||
|                 "author": post.get_authors(&*conn)[0].to_json(&*conn), | ||||
|                 "post": post, | ||||
|                 "blog": blog, | ||||
| @ -74,10 +75,10 @@ fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, us | ||||
|                 "has_reshared": user.has_reshared(&*conn, &post), | ||||
|                 "account": user.to_json(&*conn), | ||||
|                 "date": &post.creation_date.timestamp(), | ||||
|                 "previous": form.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![])), | ||||
|                 "previous": form.responding_to.and_then(|r| Comment::get(&*conn, r)).map(|r| r.to_json(&*conn, &vec![])), | ||||
|                 "user_fqn": user.get_fqn(&*conn), | ||||
|                 "errors": errors | ||||
|             })) | ||||
|             }))) | ||||
|         }) | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -6,21 +6,21 @@ use plume_models::users::User; | ||||
| 
 | ||||
| #[catch(404)] | ||||
| fn not_found(req: &Request) -> Template { | ||||
|     let conn = req.guard::<DbConn>().expect("404: DbConn error"); | ||||
|     let conn = req.guard::<DbConn>().succeeded(); | ||||
|     let user = User::from_request(req).succeeded(); | ||||
|     Template::render("errors/404", json!({ | ||||
|         "error_message": "Page not found", | ||||
|         "account": user.map(|u| u.to_json(&*conn)) | ||||
|         "account": user.and_then(|u| conn.map(|conn| u.to_json(&*conn))) | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
| #[catch(500)] | ||||
| fn server_error(req: &Request) -> Template { | ||||
|     let conn = req.guard::<DbConn>().expect("500: DbConn error"); | ||||
|     let conn = req.guard::<DbConn>().succeeded(); | ||||
|     let user = User::from_request(req).succeeded(); | ||||
|     Template::render("errors/500", json!({ | ||||
|         "error_message": "Server error", | ||||
|         "account": user.map(|u| u.to_json(&*conn)) | ||||
|         "account": user.and_then(|u| conn.map(|conn| u.to_json(&*conn))) | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| use gettextrs::gettext; | ||||
| use rocket::{request::LenientForm, response::Redirect}; | ||||
| use rocket::{request::LenientForm, response::{status, Redirect}}; | ||||
| use rocket_contrib::{Json, Template}; | ||||
| use serde_json; | ||||
| use validator::{Validate}; | ||||
| @ -52,7 +52,7 @@ fn index(conn: DbConn, user: Option<User>) -> Template { | ||||
| 
 | ||||
| #[get("/local?<page>")] | ||||
| fn paginated_local(conn: DbConn, user: Option<User>, page: Page) -> Template { | ||||
|     let instance = Instance::get_local(&*conn).unwrap(); | ||||
|     let instance = Instance::get_local(&*conn).expect("instance::paginated_local: local instance not found error"); | ||||
|     let articles = Post::get_instance_page(&*conn, instance.id, page.limits()); | ||||
|     Template::render("instance/local", json!({ | ||||
|         "account": user.map(|u| u.to_json(&*conn)), | ||||
| @ -129,7 +129,7 @@ fn update_settings(conn: DbConn, admin: Admin, form: LenientForm<InstanceSetting | ||||
|     let form = form.get(); | ||||
|     form.validate() | ||||
|         .map(|_| { | ||||
|             let instance = Instance::get_local(&*conn).unwrap(); | ||||
|             let instance = Instance::get_local(&*conn).expect("instance::update_settings: local instance not found error"); | ||||
|             instance.update(&*conn, | ||||
|                 form.name.clone(), | ||||
|                 form.open_registrations, | ||||
| @ -196,31 +196,31 @@ fn ban(_admin: Admin, conn: DbConn, id: i32) -> Redirect { | ||||
| } | ||||
| 
 | ||||
| #[post("/inbox", data = "<data>")] | ||||
| fn shared_inbox(conn: DbConn, data: String, headers: Headers) -> String { | ||||
|     let act: serde_json::Value = serde_json::from_str(&data[..]).unwrap(); | ||||
| fn shared_inbox(conn: DbConn, data: String, headers: Headers) -> Result<String, status::BadRequest<&'static str>> { | ||||
|     let act: serde_json::Value = serde_json::from_str(&data[..]).expect("instance::shared_inbox: deserialization error"); | ||||
| 
 | ||||
|     let activity = act.clone(); | ||||
|     let actor_id = activity["actor"].as_str() | ||||
|         .unwrap_or_else(|| activity["actor"]["id"].as_str().expect("No actor ID for incoming activity, blocks by panicking")); | ||||
|         .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()).unwrap(); | ||||
|     let actor = User::from_url(&conn, actor_id.to_owned()).expect("instance::shared_inbox: user error"); | ||||
|     if !verify_http_headers(&actor, headers.0.clone(), data).is_secure() && | ||||
|         !act.clone().verify(&actor) { | ||||
|         println!("Rejected invalid activity supposedly from {}, with headers {:?}", actor.username, headers.0); | ||||
|         return "invalid signature".to_owned(); | ||||
|         return Err(status::BadRequest(Some("Invalid signature"))); | ||||
|     } | ||||
| 
 | ||||
|     if Instance::is_blocked(&*conn, actor_id.to_string()) { | ||||
|         return String::new(); | ||||
|         return Ok(String::new()); | ||||
|     } | ||||
|     let instance = Instance::get_local(&*conn).unwrap(); | ||||
|     match instance.received(&*conn, act) { | ||||
|     let instance = Instance::get_local(&*conn).expect("instance::shared_inbox: local instance not found error"); | ||||
|     Ok(match instance.received(&*conn, act) { | ||||
|         Ok(_) => String::new(), | ||||
|         Err(e) => { | ||||
|             println!("Shared inbox error: {}\n{}", e.as_fail(), e.backtrace()); | ||||
|             format!("Error: {}", e.as_fail()) | ||||
|         } | ||||
|     } | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| #[get("/nodeinfo")] | ||||
| @ -263,7 +263,7 @@ fn about(user: Option<User>, conn: DbConn) -> Template { | ||||
| 
 | ||||
| #[get("/manifest.json")] | ||||
| fn web_manifest(conn: DbConn) -> Json<serde_json::Value> { | ||||
|     let instance = Instance::get_local(&*conn).unwrap(); | ||||
|     let instance = Instance::get_local(&*conn).expect("instance::web_manifest: local instance not found error"); | ||||
|     Json(json!({ | ||||
|         "name": &instance.name, | ||||
|         "description": &instance.short_description, | ||||
|  | ||||
| @ -12,9 +12,9 @@ use plume_models::{ | ||||
| }; | ||||
| 
 | ||||
| #[post("/~/<blog>/<slug>/like")] | ||||
| fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Redirect { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.clone()).unwrap(); | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), b.id).unwrap(); | ||||
| fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Option<Redirect> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.clone())?; | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), b.id)?; | ||||
| 
 | ||||
|     if !user.has_liked(&*conn, &post) { | ||||
|         let like = likes::Like::insert(&*conn, likes::NewLike { | ||||
| @ -29,13 +29,13 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po | ||||
|         let act = like.into_activity(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, act, dest))); | ||||
|     } else { | ||||
|         let like = likes::Like::find_by_user_on_post(&*conn, user.id, post.id).unwrap(); | ||||
|         let like = likes::Like::find_by_user_on_post(&*conn, user.id, post.id).expect("likes::create: like exist but not found error"); | ||||
|         let delete_act = like.delete(&*conn); | ||||
|         let dest = User::one_by_instance(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, delete_act, dest))); | ||||
|     } | ||||
| 
 | ||||
|     Redirect::to(uri!(super::posts::details: blog = blog, slug = slug)) | ||||
|     Some(Redirect::to(uri!(super::posts::details: blog = blog, slug = slug))) | ||||
| } | ||||
| 
 | ||||
| #[post("/~/<blog>/<slug>/like", rank = 2)] | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| use guid_create::GUID; | ||||
| use multipart::server::{Multipart, save::{SavedData, SaveResult}}; | ||||
| use rocket::{Data, http::ContentType, response::Redirect}; | ||||
| use rocket::{Data, http::ContentType, response::{Redirect, status}}; | ||||
| use rocket_contrib::Template; | ||||
| use serde_json; | ||||
| use std::fs; | ||||
| @ -25,30 +25,27 @@ fn new(user: User, conn: DbConn) -> Template { | ||||
| } | ||||
| 
 | ||||
| #[post("/medias/new", data = "<data>")] | ||||
| fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Redirect { | ||||
| fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Result<Redirect, status::BadRequest<&'static str>> { | ||||
|     if ct.is_form_data() { | ||||
|         let (_, boundary) = ct.params().find(|&(k, _)| k == "boundary").expect("No boundary"); | ||||
|         let (_, boundary) = ct.params().find(|&(k, _)| k == "boundary").ok_or_else(|| status::BadRequest(Some("No boundary")))?; | ||||
| 
 | ||||
|         match Multipart::with_body(data.open(), boundary).save().temp() { | ||||
|             SaveResult::Full(entries) => { | ||||
|                 let fields = entries.fields; | ||||
| 
 | ||||
|                 let filename = fields.get(&"file".to_string()).unwrap().into_iter().next().unwrap().headers | ||||
|                     .filename.clone() | ||||
|                     .unwrap_or("x.png".to_string()); // PNG by default
 | ||||
|                 let ext = filename.rsplitn(2, ".") | ||||
|                     .next() | ||||
|                     .unwrap(); | ||||
|                 let filename = fields.get(&"file".to_string()).and_then(|v| v.into_iter().next()) | ||||
|                     .ok_or_else(|| status::BadRequest(Some("No file uploaded")))?.headers | ||||
|                     .filename.clone(); | ||||
|                 let ext = filename.and_then(|f| f.rsplit('.').next().map(|ext| ext.to_owned())) | ||||
|                     .unwrap_or("png".to_owned()); | ||||
|                 let dest = format!("static/media/{}.{}", GUID::rand().to_string(), ext); | ||||
| 
 | ||||
|                 if let SavedData::Bytes(ref bytes) = fields[&"file".to_string()][0].data { | ||||
|                     fs::write(&dest, bytes).expect("Couldn't save upload"); | ||||
|                 } else { | ||||
|                     if let SavedData::File(ref path, _) = fields[&"file".to_string()][0].data { | ||||
|                         fs::copy(path, &dest).expect("Couldn't copy temp upload"); | ||||
|                     } else { | ||||
|                         println!("not file"); | ||||
|                         return Redirect::to(uri!(new)); | ||||
|                 match fields[&"file".to_string()][0].data { | ||||
|                     SavedData::Bytes(ref bytes) => fs::write(&dest, bytes).expect("media::upload: Couldn't save upload"), | ||||
|                     SavedData::File(ref path, _) => {fs::copy(path, &dest).expect("media::upload: Couldn't copy upload");}, | ||||
|                     _ => { | ||||
|                         println!("not a file"); | ||||
|                         return Ok(Redirect::to(uri!(new))); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
| @ -67,16 +64,16 @@ fn upload(user: User, data: Data, ct: &ContentType, conn: DbConn) -> Redirect { | ||||
|                     owner_id: user.id | ||||
|                 }); | ||||
|                 println!("ok"); | ||||
|                 Redirect::to(uri!(details: id = media.id)) | ||||
|                 Ok(Redirect::to(uri!(details: id = media.id))) | ||||
|             }, | ||||
|             SaveResult::Partial(_, _) | SaveResult::Error(_) => { | ||||
|                 println!("partial err"); | ||||
|                 Redirect::to(uri!(new)) | ||||
|                 Ok(Redirect::to(uri!(new))) | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         println!("not form data"); | ||||
|         Redirect::to(uri!(new)) | ||||
|         Ok(Redirect::to(uri!(new))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -98,15 +95,15 @@ fn details(id: i32, user: User, conn: DbConn) -> Template { | ||||
| } | ||||
| 
 | ||||
| #[post("/medias/<id>/delete")] | ||||
| fn delete(id: i32, _user: User, conn: DbConn) -> Redirect { | ||||
|     let media = Media::get(&*conn, id).expect("Media to delete not found"); | ||||
| fn delete(id: i32, _user: User, conn: DbConn) -> Option<Redirect> { | ||||
|     let media = Media::get(&*conn, id)?; | ||||
|     media.delete(&*conn); | ||||
|     Redirect::to(uri!(list)) | ||||
|     Some(Redirect::to(uri!(list))) | ||||
| } | ||||
| 
 | ||||
| #[post("/medias/<id>/avatar")] | ||||
| fn set_avatar(id: i32, user: User, conn: DbConn) -> Redirect { | ||||
|     let media = Media::get(&*conn, id).expect("Media to delete not found"); | ||||
| fn set_avatar(id: i32, user: User, conn: DbConn) -> Option<Redirect> { | ||||
|     let media = Media::get(&*conn, id)?; | ||||
|     user.set_avatar(&*conn, media.id); | ||||
|     Redirect::to(uri!(details: id = id)) | ||||
|     Some(Redirect::to(uri!(details: id = id))) | ||||
| } | ||||
|  | ||||
| @ -58,7 +58,8 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User> | ||||
|                     "has_reshared": user.clone().map(|u| u.has_reshared(&*conn, &post)).unwrap_or(false), | ||||
|                     "account": &user.clone().map(|u| u.to_json(&*conn)), | ||||
|                     "date": &post.creation_date.timestamp(), | ||||
|                     "previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![]))), | ||||
|                     "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![]))), | ||||
|                     "user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or(String::new()), | ||||
|                     "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) | ||||
| @ -73,13 +74,13 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User> | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<blog>/<slug>", rank = 3)] | ||||
| fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<Article>, String> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog).unwrap(); | ||||
|     let post = Post::find_by_slug(&*conn, slug, blog.id).unwrap(); | ||||
| fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<Article>, Option<String>> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog).ok_or(None)?; | ||||
|     let post = Post::find_by_slug(&*conn, slug, blog.id).ok_or(None)?; | ||||
|     if post.published { | ||||
|         Ok(ActivityStream::new(post.into_activity(&*conn))) | ||||
|     } else { | ||||
|         Err(String::from("Not published yet.")) | ||||
|         Err(Some(String::from("Not published yet."))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -92,42 +93,42 @@ fn new_auth(blog: String) -> Flash<Redirect> { | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<blog>/new", rank = 1)] | ||||
| fn new(blog: String, user: User, conn: DbConn) -> Template { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string()).unwrap(); | ||||
| fn new(blog: String, user: User, conn: DbConn) -> Option<Template> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string())?; | ||||
| 
 | ||||
|     if !user.is_author_in(&*conn, b.clone()) { | ||||
|         Template::render("errors/403", json!({ | ||||
|         Some(Template::render("errors/403", json!({// TODO actually return 403 error code
 | ||||
|             "error_message": "You are not author in this blog." | ||||
|         })) | ||||
|         }))) | ||||
|     } else { | ||||
|         Template::render("posts/new", json!({ | ||||
|         Some(Template::render("posts/new", json!({ | ||||
|             "account": user.to_json(&*conn), | ||||
|             "instance": Instance::get_local(&*conn), | ||||
|             "editing": false, | ||||
|             "errors": null, | ||||
|             "form": null, | ||||
|             "is_draft": true, | ||||
|         })) | ||||
|         }))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<blog>/<slug>/edit")] | ||||
| fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Template { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string()); | ||||
|     let post = b.clone().and_then(|blog| Post::find_by_slug(&*conn, slug, blog.id)).expect("Post to edit not found"); | ||||
| fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Option<Template> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string())?; | ||||
|     let post = Post::find_by_slug(&*conn, slug, b.id)?; | ||||
| 
 | ||||
|     if !user.is_author_in(&*conn, b.clone().unwrap()) { | ||||
|         Template::render("errors/403", json!({ | ||||
|     if !user.is_author_in(&*conn, b) { | ||||
|         Some(Template::render("errors/403", json!({// TODO actually return 403 error code
 | ||||
|             "error_message": "You are not author in this blog." | ||||
|         })) | ||||
|         }))) | ||||
|     } else { | ||||
|         let source = if post.source.clone().len() > 0 { | ||||
|             post.source.clone() | ||||
|         let source = if post.source.len() > 0 { | ||||
|             post.source | ||||
|         } else { | ||||
|             post.content.clone().get().clone() // fallback to HTML if the markdown was not stored
 | ||||
|             post.content.get().clone() // fallback to HTML if the markdown was not stored
 | ||||
|         }; | ||||
| 
 | ||||
|         Template::render("posts/new", json!({ | ||||
|         Some(Template::render("posts/new", json!({ | ||||
|             "account": user.to_json(&*conn), | ||||
|             "instance": Instance::get_local(&*conn), | ||||
|             "editing": true, | ||||
| @ -145,14 +146,15 @@ fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Template { | ||||
|                 draft: true, | ||||
|             }, | ||||
|             "is_draft": !post.published | ||||
|         })) | ||||
|         }))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[post("/~/<blog>/<slug>/edit", data = "<data>")] | ||||
| fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientForm<NewPostForm>, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Template> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string()); | ||||
|     let mut post = b.clone().and_then(|blog| Post::find_by_slug(&*conn, slug.clone(), blog.id)).expect("Post to update not found"); | ||||
| fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientForm<NewPostForm>, worker: State<Pool<ThunkWorker<()>>>) | ||||
|     -> Result<Redirect, Option<Template>> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.to_string()).ok_or(None)?; | ||||
|     let mut post = Post::find_by_slug(&*conn, slug.clone(), b.id).ok_or(None)?; | ||||
| 
 | ||||
|     let form = data.get(); | ||||
|     let new_slug = if !post.published { | ||||
| @ -167,7 +169,7 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor | ||||
|     }; | ||||
| 
 | ||||
|     if new_slug != slug { | ||||
|         if let Some(_) = Post::find_by_slug(&*conn, new_slug.clone(), b.clone().unwrap().id) { | ||||
|         if let Some(_) = Post::find_by_slug(&*conn, new_slug.clone(), b.id) { | ||||
|             errors.add("title", ValidationError { | ||||
|                 code: Cow::from("existing_slug"), | ||||
|                 message: Some(Cow::from("A post with the same title already exists.")), | ||||
| @ -177,7 +179,7 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor | ||||
|     } | ||||
| 
 | ||||
|     if errors.is_empty() { | ||||
|         if !user.is_author_in(&*conn, b.clone().unwrap()) { | ||||
|         if !user.is_author_in(&*conn, b) { | ||||
|             // actually it's not "Ok"…
 | ||||
|             Ok(Redirect::to(uri!(super::blogs::details: name = blog))) | ||||
|         } else { | ||||
| @ -229,14 +231,14 @@ fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientFor | ||||
|             Ok(Redirect::to(uri!(details: blog = blog, slug = new_slug))) | ||||
|         } | ||||
|     } else { | ||||
|         Err(Template::render("posts/new", json!({ | ||||
|         Err(Some(Template::render("posts/new", json!({ | ||||
|             "account": user.to_json(&*conn), | ||||
|             "instance": Instance::get_local(&*conn), | ||||
|             "editing": true, | ||||
|             "errors": errors.inner(), | ||||
|             "form": form, | ||||
|             "is_draft": form.draft, | ||||
|         }))) | ||||
|         })))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -263,8 +265,8 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> { | ||||
| } | ||||
| 
 | ||||
| #[post("/~/<blog_name>/new", data = "<data>")] | ||||
| fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Template> { | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).unwrap(); | ||||
| 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 form = data.get(); | ||||
|     let slug = form.title.to_string().to_kebab_case(); | ||||
| 
 | ||||
| @ -331,14 +333,14 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D | ||||
|             Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug))) | ||||
|         } | ||||
|     } else { | ||||
|         Err(Template::render("posts/new", json!({ | ||||
|         Err(Some(Template::render("posts/new", json!({ | ||||
|             "account": user.to_json(&*conn), | ||||
|             "instance": Instance::get_local(&*conn), | ||||
|             "editing": false, | ||||
|             "errors": errors.inner(), | ||||
|             "form": form, | ||||
|             "is_draft": form.draft | ||||
|         }))) | ||||
|         })))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -12,9 +12,9 @@ use plume_models::{ | ||||
| }; | ||||
| 
 | ||||
| #[post("/~/<blog>/<slug>/reshare")] | ||||
| fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Redirect { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.clone()).unwrap(); | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), b.id).unwrap(); | ||||
| fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Pool<ThunkWorker<()>>>) -> Option<Redirect> { | ||||
|     let b = Blog::find_by_fqn(&*conn, blog.clone())?; | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), b.id)?; | ||||
| 
 | ||||
|     if !user.has_reshared(&*conn, &post) { | ||||
|         let reshare = Reshare::insert(&*conn, NewReshare { | ||||
| @ -29,13 +29,14 @@ fn create(blog: String, slug: String, user: User, conn: DbConn, worker: State<Po | ||||
|         let act = reshare.into_activity(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, act, dest))); | ||||
|     } else { | ||||
|         let reshare = Reshare::find_by_user_on_post(&*conn, user.id, post.id).unwrap(); | ||||
|         let reshare = Reshare::find_by_user_on_post(&*conn, user.id, post.id) | ||||
|             .expect("reshares::create: reshare exist but not found error"); | ||||
|         let delete_act = reshare.delete(&*conn); | ||||
|         let dest = User::one_by_instance(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, delete_act, dest))); | ||||
|     } | ||||
| 
 | ||||
|     Redirect::to(uri!(super::posts::details: blog = blog, slug = slug)) | ||||
|     Some(Redirect::to(uri!(super::posts::details: blog = blog, slug = slug))) | ||||
| } | ||||
| 
 | ||||
| #[post("/~/<blog>/<slug>/reshare", rank=1)] | ||||
|  | ||||
| @ -50,22 +50,23 @@ struct LoginForm { | ||||
| fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, Template> { | ||||
|     let form = data.get(); | ||||
|     let user = User::find_by_email(&*conn, form.email_or_name.to_string()) | ||||
|         .map(|u| Ok(u)) | ||||
|         .unwrap_or_else(|| User::find_local(&*conn, form.email_or_name.to_string()).map(|u| Ok(u)).unwrap_or(Err(()))); | ||||
| 
 | ||||
|         .or_else(|| User::find_local(&*conn, form.email_or_name.to_string())); | ||||
|     let mut errors = match form.validate() { | ||||
|         Ok(_) => ValidationErrors::new(), | ||||
|         Err(e) => e | ||||
|     }; | ||||
|     if let Err(_) = user.clone() { | ||||
|     
 | ||||
|     if let Some(user) = user.clone() { | ||||
|         if !user.auth(form.password.clone()) { | ||||
|             let mut err = ValidationError::new("invalid_login"); | ||||
|             err.message = Some(Cow::from("Invalid username or password")); | ||||
|             errors.add("email_or_name", err) | ||||
|         } | ||||
|     } else { | ||||
|         // Fake password verification, only to avoid different login times
 | ||||
|         // that could be used to see if an email adress is registered or not
 | ||||
|         User::get(&*conn, 1).map(|u| u.auth(form.password.clone())); | ||||
| 
 | ||||
|         let mut err = ValidationError::new("invalid_login"); | ||||
|         err.message = Some(Cow::from("Invalid username or password")); | ||||
|         errors.add("email_or_name", err) | ||||
|     } else if !user.clone().expect("User not found").auth(form.password.clone()) { | ||||
|         let mut err = ValidationError::new("invalid_login"); | ||||
|         err.message = Some(Cow::from("Invalid username or password")); | ||||
|         errors.add("email_or_name", err) | ||||
| @ -107,7 +108,6 @@ fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage | ||||
| 
 | ||||
| #[get("/logout")] | ||||
| fn delete(mut cookies: Cookies) -> Redirect { | ||||
|     let cookie = cookies.get_private(AUTH_COOKIE).unwrap(); | ||||
|     cookies.remove_private(cookie); | ||||
|     cookies.get_private(AUTH_COOKIE).map(|cookie| cookies.remove_private(cookie)); | ||||
|     Redirect::to("/") | ||||
| } | ||||
|  | ||||
| @ -10,19 +10,19 @@ use plume_models::{ | ||||
| use routes::Page; | ||||
| 
 | ||||
| #[get("/tag/<name>")] | ||||
| fn tag(user: Option<User>, conn: DbConn, name: String) -> Template { | ||||
| fn tag(user: Option<User>, conn: DbConn, name: String) -> Option<Template> { | ||||
|     paginated_tag(user, conn, name, Page::first()) | ||||
| } | ||||
| 
 | ||||
| #[get("/tag/<name>?<page>")] | ||||
| fn paginated_tag(user: Option<User>, conn: DbConn, name: String, page: Page) -> Template { | ||||
|     let tag = Tag::find_by_name(&*conn, name).expect("Rendering tags::tag: tag not found"); | ||||
| fn paginated_tag(user: Option<User>, conn: DbConn, name: String, page: Page) -> Option<Template> { | ||||
|     let tag = Tag::find_by_name(&*conn, name)?; | ||||
|     let posts = Post::list_by_tag(&*conn, tag.tag.clone(), page.limits()); | ||||
|     Template::render("tags/index", json!({ | ||||
|     Some(Template::render("tags/index", json!({ | ||||
|         "tag": tag.clone(), | ||||
|         "account": user.map(|u| u.to_json(&*conn)), | ||||
|         "articles": posts.into_iter().map(|p| p.to_json(&*conn)).collect::<Vec<serde_json::Value>>(), | ||||
|         "page": page.page, | ||||
|         "n_pages": Page::total(Post::count_for_tag(&*conn, tag.tag) as i32) | ||||
|     })) | ||||
|     }))) | ||||
| } | ||||
|  | ||||
| @ -6,7 +6,7 @@ use activitypub::{ | ||||
| use atom_syndication::{Entry, FeedBuilder}; | ||||
| use rocket::{ | ||||
|     request::LenientForm, | ||||
|     response::{Redirect, Flash, Content}, | ||||
|     response::{Content, Flash, Redirect, status}, | ||||
|     http::{ContentType, Cookies} | ||||
| }; | ||||
| use rocket_contrib::Template; | ||||
| @ -70,7 +70,7 @@ fn details(name: String, conn: DbConn, account: Option<User>, worker: Worker, fe | ||||
|             worker.execute(Thunk::of(move || { | ||||
|                 for user_id in user_clone.fetch_followers_ids() { | ||||
|                     let follower = User::find_by_ap_url(&*fecth_followers_conn, user_id.clone()) | ||||
|                         .unwrap_or_else(|| User::fetch_from_url(&*fecth_followers_conn, user_id).expect("Couldn't fetch follower")); | ||||
|                         .unwrap_or_else(|| User::fetch_from_url(&*fecth_followers_conn, user_id).expect("user::details: Couldn't fetch follower")); | ||||
|                     follows::Follow::insert(&*fecth_followers_conn, follows::NewFollow { | ||||
|                         follower_id: follower.id, | ||||
|                         following_id: user_clone.id, | ||||
| @ -121,8 +121,8 @@ fn dashboard_auth() -> Flash<Redirect> { | ||||
| } | ||||
| 
 | ||||
| #[post("/@/<name>/follow")] | ||||
| fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect { | ||||
|     let target = User::find_by_fqn(&*conn, name.clone()).unwrap(); | ||||
| fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Option<Redirect> { | ||||
|     let target = User::find_by_fqn(&*conn, name.clone())?; | ||||
|     if let Some(follow) = follows::Follow::find(&*conn, user.id, target.id) { | ||||
|         let delete_act = follow.delete(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, delete_act, vec![target]))); | ||||
| @ -137,7 +137,7 @@ fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect { | ||||
|         let act = f.into_activity(&*conn); | ||||
|         worker.execute(Thunk::of(move || broadcast(&user, act, vec![target]))); | ||||
|     } | ||||
|     Redirect::to(uri!(details: name = name)) | ||||
|     Some(Redirect::to(uri!(details: name = name))) | ||||
| } | ||||
| 
 | ||||
| #[post("/@/<name>/follow", rank = 2)] | ||||
| @ -176,9 +176,9 @@ fn followers(name: String, conn: DbConn, account: Option<User>) -> Template { | ||||
| 
 | ||||
| 
 | ||||
| #[get("/@/<name>", rank = 1)] | ||||
| fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> ActivityStream<CustomPerson> { | ||||
|     let user = User::find_local(&*conn, name).unwrap(); | ||||
|     ActivityStream::new(user.into_activity(&*conn)) | ||||
| fn activity_details(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<CustomPerson>> { | ||||
|     let user = User::find_local(&*conn, name)?; | ||||
|     Some(ActivityStream::new(user.into_activity(&*conn))) | ||||
| } | ||||
| 
 | ||||
| #[get("/users/new")] | ||||
| @ -228,17 +228,16 @@ fn update(_name: String, conn: DbConn, user: User, data: LenientForm<UpdateUserF | ||||
| } | ||||
| 
 | ||||
| #[post("/@/<name>/delete")] | ||||
| fn delete(name: String, conn: DbConn, user: User, mut cookies: Cookies) -> Redirect { | ||||
|     let account = User::find_by_fqn(&*conn, name.clone()).unwrap(); | ||||
| fn delete(name: String, conn: DbConn, user: User, mut cookies: Cookies) -> Option<Redirect> { | ||||
|     let account = User::find_by_fqn(&*conn, name.clone())?; | ||||
|     if user.id == account.id { | ||||
|         account.delete(&*conn); | ||||
| 
 | ||||
|         let cookie = cookies.get_private(AUTH_COOKIE).unwrap(); | ||||
|         cookies.remove_private(cookie); | ||||
|         cookies.get_private(AUTH_COOKIE).map(|cookie| cookies.remove_private(cookie)); | ||||
| 
 | ||||
|         Redirect::to(uri!(super::instance::index)) | ||||
|         Some(Redirect::to(uri!(super::instance::index))) | ||||
|     } else { | ||||
|         Redirect::to(uri!(edit: name = name)) | ||||
|         Some(Redirect::to(uri!(edit: name = name))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -291,54 +290,54 @@ fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, Temp | ||||
| } | ||||
| 
 | ||||
| #[get("/@/<name>/outbox")] | ||||
| fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> { | ||||
|     let user = User::find_local(&*conn, name).unwrap(); | ||||
|     user.outbox(&*conn) | ||||
| fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> { | ||||
|     let user = User::find_local(&*conn, name)?; | ||||
|     Some(user.outbox(&*conn)) | ||||
| } | ||||
| 
 | ||||
| #[post("/@/<name>/inbox", data = "<data>")] | ||||
| fn inbox(name: String, conn: DbConn, data: String, headers: Headers) -> String { | ||||
|     let user = User::find_local(&*conn, name).unwrap(); | ||||
|     let act: serde_json::Value = serde_json::from_str(&data[..]).unwrap(); | ||||
| fn inbox(name: String, conn: DbConn, data: String, headers: Headers) -> Result<String, Option<status::BadRequest<&'static str>>> { | ||||
|     let user = User::find_local(&*conn, name).ok_or(None)?; | ||||
|     let act: serde_json::Value = serde_json::from_str(&data[..]).expect("user::inbox: deserialization error"); | ||||
| 
 | ||||
|     let activity = act.clone(); | ||||
|     let actor_id = activity["actor"].as_str() | ||||
|         .unwrap_or_else(|| activity["actor"]["id"].as_str().expect("User: No actor ID for incoming activity, blocks by panicking")); | ||||
|         .or_else(|| activity["actor"]["id"].as_str()).ok_or(Some(status::BadRequest(Some("Missing actor id for activity"))))?; | ||||
| 
 | ||||
|     let actor = User::from_url(&conn, actor_id.to_owned()).unwrap(); | ||||
|     let actor = User::from_url(&conn, actor_id.to_owned()).expect("user::inbox: user error"); | ||||
|     if !verify_http_headers(&actor, headers.0.clone(), data).is_secure() && | ||||
|         !act.clone().verify(&actor) { | ||||
|         println!("Rejected invalid activity supposedly from {}, with headers {:?}", actor.username, headers.0); | ||||
|         return "invalid signature".to_owned(); | ||||
|         return Err(Some(status::BadRequest(Some("Invalid signature")))); | ||||
|     } | ||||
| 
 | ||||
|     if Instance::is_blocked(&*conn, actor_id.to_string()) { | ||||
|         return String::new(); | ||||
|         return Ok(String::new()); | ||||
|     } | ||||
|     match user.received(&*conn, act) { | ||||
|     Ok(match user.received(&*conn, act) { | ||||
|         Ok(_) => String::new(), | ||||
|         Err(e) => { | ||||
|             println!("User inbox error: {}\n{}", e.as_fail(), e.backtrace()); | ||||
|             format!("Error: {}", e.as_fail()) | ||||
|         } | ||||
|     } | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| #[get("/@/<name>/followers")] | ||||
| fn ap_followers(name: String, conn: DbConn, _ap: ApRequest) -> ActivityStream<OrderedCollection> { | ||||
|     let user = User::find_local(&*conn, name).unwrap(); | ||||
| fn ap_followers(name: String, conn: DbConn, _ap: ApRequest) -> Option<ActivityStream<OrderedCollection>> { | ||||
|     let user = User::find_local(&*conn, name)?; | ||||
|     let followers = user.get_followers(&*conn).into_iter().map(|f| Id::new(f.ap_url)).collect::<Vec<Id>>(); | ||||
| 
 | ||||
|     let mut coll = OrderedCollection::default(); | ||||
|     coll.object_props.set_id_string(user.followers_endpoint).expect("Follower collection: id error"); | ||||
|     coll.collection_props.set_total_items_u64(followers.len() as u64).expect("Follower collection: totalItems error"); | ||||
|     coll.collection_props.set_items_link_vec(followers).expect("Follower collection: items error"); | ||||
|     ActivityStream::new(coll) | ||||
|     coll.object_props.set_id_string(user.followers_endpoint).expect("user::ap_followers: id error"); | ||||
|     coll.collection_props.set_total_items_u64(followers.len() as u64).expect("user::ap_followers: totalItems error"); | ||||
|     coll.collection_props.set_items_link_vec(followers).expect("user::ap_followers items error"); | ||||
|     Some(ActivityStream::new(coll)) | ||||
| } | ||||
| 
 | ||||
| #[get("/@/<name>/atom.xml")] | ||||
| fn atom_feed(name: String, conn: DbConn) -> Content<String> { | ||||
|     let author = User::find_by_fqn(&*conn, name.clone()).expect("Unable to find author"); | ||||
| fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> { | ||||
|     let author = User::find_by_fqn(&*conn, name.clone())?; | ||||
|     let feed = FeedBuilder::default() | ||||
|         .title(author.display_name.clone()) | ||||
|         .id(Instance::get_local(&*conn).unwrap().compute_box("~", name, "atom.xml")) | ||||
| @ -347,6 +346,6 @@ fn atom_feed(name: String, conn: DbConn) -> Content<String> { | ||||
|             .map(|p| super::post_to_atom(p, &*conn)) | ||||
|             .collect::<Vec<Entry>>()) | ||||
|         .build() | ||||
|         .expect("Error building Atom feed"); | ||||
|     Content(ContentType::new("application", "atom+xml"), feed.to_string()) | ||||
|         .expect("user::atom_feed: Error building Atom feed"); | ||||
|     Some(Content(ContentType::new("application", "atom+xml"), feed.to_string())) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user