Actually validate forms

This commit is contained in:
Bat 2018-07-06 11:51:19 +02:00
parent c81bb9ec25
commit 153400959c
6 changed files with 122 additions and 73 deletions

View file

@ -1,4 +1,4 @@
-- Your SQL goes here
l-- Your SQL goes here
CREATE TABLE instances (
id SERIAL PRIMARY KEY,
local_domain VARCHAR NOT NULL,

View file

@ -5,7 +5,7 @@ use rocket::{
};
use rocket_contrib::Template;
use serde_json;
use validator::{Validate, ValidationError};
use validator::{Validate, ValidationError, ValidationErrors};
use plume_common::activity_pub::ActivityStream;
use plume_common::utils;
@ -66,15 +66,22 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> {
}
#[post("/blogs/new", data = "<data>")]
fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Redirect {
fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Result<Redirect, Template> {
let form = data.get();
let slug = utils::make_actor_id(form.title.to_string());
let slug_taken_err = Blog::find_local(&*conn, slug.clone()).ok_or(ValidationError::new("existing_slug"));
if Blog::find_local(&*conn, slug.clone()).is_some() || slug.len() == 0 {
Redirect::to(uri!(new))
} else {
let mut errors = match form.validate() {
Ok(_) => ValidationErrors::new(),
Err(e) => e
};
if let Err(e) = slug_taken_err {
errors.add("title", e)
}
if errors.is_empty() {
let blog = Blog::insert(&*conn, NewBlog::new_local(
slug.to_string(),
slug.clone(),
form.title.to_string(),
String::from(""),
Instance::local_id(&*conn)
@ -87,7 +94,12 @@ fn create(conn: DbConn, data: LenientForm<NewBlogForm>, user: User) -> Redirect
is_owner: true
});
Redirect::to(uri!(details: name = slug))
Ok(Redirect::to(uri!(details: name = slug.clone())))
} else {
Err(Template::render("blogs/new", json!({
"account": user,
"errors": errors.inner()
})))
}
}

View file

@ -2,6 +2,7 @@ use rocket::{
request::LenientForm,
response::Redirect
};
use rocket_contrib::Template;
use serde_json;
use validator::Validate;
@ -24,22 +25,44 @@ struct NewCommentForm {
}
#[post("/~/<blog_name>/<slug>/comment", data = "<data>")]
fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn) -> Redirect {
fn create(blog_name: String, slug: String, data: LenientForm<NewCommentForm>, user: User, conn: DbConn) -> 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();
let form = data.get();
form.validate()
.map(|_| {
let (new_comment, id) = NewComment::build()
.content(form.content.clone())
.in_response_to_id(form.responding_to.clone())
.post(post.clone())
.author(user.clone())
.create(&*conn);
let (new_comment, id) = NewComment::build()
.content(form.content.clone())
.in_response_to_id(form.responding_to.clone())
.post(post)
.author(user.clone())
.create(&*conn);
let instance = Instance::get_local(&*conn).unwrap();
instance.received(&*conn, serde_json::to_value(new_comment.clone()).expect("JSON serialization error"))
.expect("We are not compatible with ourselve: local broadcast failed (new comment)");
broadcast(&user, new_comment, user.get_followers(&*conn));
let instance = Instance::get_local(&*conn).unwrap();
instance.received(&*conn, serde_json::to_value(new_comment.clone()).expect("JSON serialization error"))
.expect("We are not compatible with ourselve: local broadcast failed (new comment)");
broadcast(&user, new_comment, user.get_followers(&*conn));
Redirect::to(format!("/~/{}/{}/#comment-{}", blog_name, slug, id))
})
.map_err(|errors| {
// TODO: de-duplicate this code
let comments = Comment::list_by_post(&*conn, post.id);
Redirect::to(format!("/~/{}/{}/#comment-{}", blog_name, slug, id))
Template::render("posts/details", json!({
"author": post.get_authors(&*conn)[0].to_json(&*conn),
"post": post,
"blog": blog,
"comments": comments.into_iter().map(|c| c.to_json(&*conn)).collect::<Vec<serde_json::Value>>(),
"n_likes": post.get_likes(&*conn).len(),
"has_liked": user.has_liked(&*conn, &post),
"n_reshares": post.get_reshares(&*conn).len(),
"has_reshared": user.has_reshared(&*conn, &post),
"account": user,
"date": &post.creation_date.timestamp(),
"previous": form.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn)),
"user_fqn": user.get_fqn(&*conn),
"errors": errors
}))
})
}

View file

@ -4,7 +4,7 @@ use rocket::request::LenientForm;
use rocket::response::{Redirect, Flash};
use rocket_contrib::Template;
use serde_json;
use validator::{Validate, ValidationError};
use validator::{Validate, ValidationError, ValidationErrors};
use plume_common::activity_pub::{broadcast, ActivityStream};
use plume_common::utils;
@ -94,22 +94,32 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> {
let slug = title.to_string().to_kebab_case();
if slug.len() == 0 {
Err(ValidationError::new("empty_slug"))
} else if slug == "new" {
Err(ValidationError::new("invalid_slug"))
} else {
Ok(())
}
}
#[post("/~/<blog_name>/new", data = "<data>")]
fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn) -> Redirect {
fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: DbConn) -> Result<Redirect, Template> {
let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).unwrap();
let form = data.get();
let slug = form.title.to_string().to_kebab_case();
let slug_taken_err = Blog::find_local(&*conn, slug.clone()).ok_or(ValidationError::new("existing_slug"));
let mut errors = match form.validate() {
Ok(_) => ValidationErrors::new(),
Err(e) => e
};
if let Err(e) = slug_taken_err {
errors.add("title", e)
}
if !user.is_author_in(&*conn, blog.clone()) {
Redirect::to(uri!(super::blogs::details: name = blog_name))
} else {
if slug == "new" || Post::find_by_slug(&*conn, slug.clone(), blog.id).is_some() {
Redirect::to(uri!(new: blog = blog_name))
if errors.is_empty() {
if !user.is_author_in(&*conn, blog.clone()) {
// actually it's not "Ok"…
Ok(Redirect::to(uri!(super::blogs::details: name = blog_name)))
} else {
let (content, mentions) = utils::md_to_html(form.content.to_string().as_ref());
@ -135,7 +145,12 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D
let act = post.create_activity(&*conn);
broadcast(&user, act, user.get_followers(&*conn));
Redirect::to(uri!(details: blog = blog_name, slug = slug))
Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug)))
}
} else {
Err(Template::render("posts/new", json!({
"account": user,
"errors": errors.inner()
})))
}
}

View file

@ -1,11 +1,10 @@
use gettextrs::gettext;
use rocket::{
http::{Cookie, Cookies, uri::Uri},
response::{Redirect, status::NotFound},
response::Redirect,
request::{LenientForm,FlashMessage}
};
use rocket_contrib::Template;
use validator::{Validate, ValidationError};
use validator::{Validate, ValidationError, ValidationErrors};
use plume_models::{
db_conn::DbConn,
@ -42,28 +41,33 @@ struct LoginForm {
}
#[post("/login", data = "<data>")]
fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, NotFound<String>> {
fn create(conn: DbConn, data: LenientForm<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, Template> {
let form = data.get();
let user = match User::find_by_email(&*conn, form.email_or_name.to_string()) {
Some(usr) => Ok(usr),
None => match User::find_local(&*conn, form.email_or_name.to_string()) {
Some(usr) => Ok(usr),
None => Err(gettext("Invalid username or password"))
}
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(())));
let mut errors = match form.validate() {
Ok(_) => ValidationErrors::new(),
Err(e) => e
};
match user {
Ok(usr) => {
if usr.auth(form.password.to_string()) {
cookies.add_private(Cookie::new(AUTH_COOKIE, usr.id.to_string()));
Ok(Redirect::to(Uri::new(flash
.and_then(|f| if f.name() == "callback" { Some(f.msg().to_owned()) } else { None })
.unwrap_or("/".to_owned()))
))
} else {
Err(NotFound(gettext("Invalid username or password")))
}
},
Err(e) => Err(NotFound(String::from(e)))
if let Err(_) = user.clone() {
errors.add("email_or_name", ValidationError::new("invalid_login"))
} else if !user.clone().expect("User not found").auth(form.password.clone()) {
errors.add("email_or_name", ValidationError::new("invalid_login"))
}
if errors.is_empty() {
cookies.add_private(Cookie::new(AUTH_COOKIE, user.unwrap().id.to_string()));
Ok(Redirect::to(Uri::new(flash
.and_then(|f| if f.name() == "callback" { Some(f.msg().to_owned()) } else { None })
.unwrap_or("/".to_owned()))
))
} else {
Err(Template::render("session/login", json!({
"account": user,
"errors": errors.inner()
})))
}
}

View file

@ -180,29 +180,24 @@ fn passwords_match(form: &NewUserForm) -> Result<(), ValidationError> {
}
#[post("/users/new", data = "<data>")]
fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, String> {
fn create(conn: DbConn, data: LenientForm<NewUserForm>) -> Result<Redirect, Template> {
let form = data.get();
if form.username.clone().len() < 1 {
Err(String::from("Username is required"))
} else if form.email.clone().len() < 1 {
Err(String::from("Email is required"))
} else if form.password.clone().len() < 8 {
Err(String::from("Password should be at least 8 characters long"))
} else if form.password == form.password_confirmation {
NewUser::new_local(
&*conn,
form.username.to_string(),
form.username.to_string(),
false,
String::from(""),
form.email.to_string(),
User::hash_pass(form.password.to_string())
).update_boxes(&*conn);
Ok(Redirect::to(uri!(super::session::new)))
} else {
Err(String::from("Passwords don't match"))
}
form.validate()
.map(|_| {
NewUser::new_local(
&*conn,
form.username.to_string(),
form.username.to_string(),
false,
String::from(""),
form.email.to_string(),
User::hash_pass(form.password.to_string())
).update_boxes(&*conn);
Redirect::to(uri!(super::session::new))
})
.map_err(|e| Template::render("users/new", json!({
"errors": e.inner()
})))
}
#[get("/@/<name>/outbox")]