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…
Reference in New Issue
Block a user