Start register logic

This commit is contained in:
Adrian Wozniak 2020-04-17 20:44:55 +02:00
parent 0e2cc62c30
commit b157d718c7
11 changed files with 158 additions and 51 deletions

View File

@ -35,3 +35,13 @@
display: block; display: block;
line-height: 32px; line-height: 32px;
} }
#register .error > p {
line-height: 1.4285;
color: var(--danger);
font-family: var(--font-medium);
text-align: center;
font-size: 14.5px;
border-top: 1px solid var(--danger);
margin-top: 15px;
}

View File

@ -142,6 +142,9 @@ pub enum Msg {
SignInRequest, SignInRequest,
BindClientRequest, BindClientRequest,
// sign up
SignUpRequest,
StyledSelectChanged(FieldId, StyledSelectChange), StyledSelectChanged(FieldId, StyledSelectChange),
ChangePage(model::Page), ChangePage(model::Page),

View File

@ -236,6 +236,7 @@ pub struct SignUpPage {
pub username: String, pub username: String,
pub email: String, pub email: String,
pub sign_up_success: bool, pub sign_up_success: bool,
pub error: String,
// touched // touched
pub username_touched: bool, pub username_touched: bool,
pub email_touched: bool, pub email_touched: bool,

View File

@ -27,6 +27,7 @@ impl StyledLinkBuilder {
self.children.push(child); self.children.push(child);
self self
} }
pub fn add_class<S>(mut self, name: S) -> Self pub fn add_class<S>(mut self, name: S) -> Self
where where
S: Into<String>, S: Into<String>,
@ -43,13 +44,12 @@ impl StyledLinkBuilder {
self self
} }
pub fn text<S>(mut self, s: S) -> Self pub fn text<S>(self, s: S) -> Self
where where
S: Into<String>, S: Into<String>,
{ {
let text: String = s.into(); let text: String = s.into();
self.children.push(span![text]); self.add_child(span![text])
self
} }
pub fn build(self) -> StyledLink { pub fn build(self) -> StyledLink {

View File

@ -121,6 +121,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
let register_link = StyledLink::build() let register_link = StyledLink::build()
.text("Register") .text("Register")
.href("/register") .href("/register")
.add_class("signUpLink")
.build() .build()
.into_node(); .into_node();
let submit_field = StyledField::build() let submit_field = StyledField::build()

View File

@ -2,6 +2,7 @@ use seed::{prelude::*, *};
use jirs_data::{SignUpFieldId, WsMsg}; use jirs_data::{SignUpFieldId, WsMsg};
use crate::api::send_ws_msg;
use crate::model::{Page, PageContent, SignUpPage}; use crate::model::{Page, PageContent, SignUpPage};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField; use crate::shared::styled_field::StyledField;
@ -37,9 +38,18 @@ pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders<Msg>
page.email = value; page.email = value;
page.email_touched = true; page.email_touched = true;
} }
Msg::SignUpRequest => {
send_ws_msg(WsMsg::SignUpRequest(
page.email.clone(),
page.username.clone(),
));
}
Msg::WsMsg(WsMsg::SignUpSuccess) => { Msg::WsMsg(WsMsg::SignUpSuccess) => {
page.sign_up_success = true; page.sign_up_success = true;
} }
Msg::WsMsg(WsMsg::SignUpPairTaken) => {
page.error = "Pair you give is either taken or is not matching".to_string();
}
_ => (), _ => (),
} }
} }
@ -80,7 +90,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
StyledButton::build() StyledButton::build()
.primary() .primary()
.text("Register") .text("Register")
.on_click(mouse_ev(Ev::Click, |_| Msg::SignInRequest)) .on_click(mouse_ev(Ev::Click, |_| Msg::SignUpRequest))
} }
.build() .build()
.into_node(); .into_node();
@ -88,6 +98,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
let sign_in_link = StyledLink::build() let sign_in_link = StyledLink::build()
.text("Sign In") .text("Sign In")
.href("/login") .href("/login")
.add_class("signInLink")
.build() .build()
.into_node(); .into_node();
@ -109,17 +120,24 @@ pub fn view(model: &model::Model) -> Node<Msg> {
span!["Why I don't see password?"] span!["Why I don't see password?"]
]; ];
let error_row = if page.error.is_empty() {
empty![]
} else {
div![class!["error"], p![page.error]]
};
let sign_up_form = StyledForm::build() let sign_up_form = StyledForm::build()
.heading("Sign In to your account") .heading("Sign In to your account")
.on_submit(ev(Ev::Submit, |ev| { .on_submit(ev(Ev::Submit, |ev| {
ev.stop_propagation(); ev.stop_propagation();
ev.prevent_default(); ev.prevent_default();
Msg::SignInRequest Msg::SignUpRequest
})) }))
.add_field(username_field) .add_field(username_field)
.add_field(email_field) .add_field(email_field)
.add_field(submit_field) .add_field(submit_field)
.add_field(no_pass_section) .add_field(no_pass_section)
.add_field(error_row)
.build() .build()
.into_node(); .into_node();
let children = vec![sign_up_form]; let children = vec![sign_up_form];

View File

@ -530,7 +530,11 @@ pub enum WsMsg {
BindTokenCheck(Uuid), BindTokenCheck(Uuid),
BindTokenBad, BindTokenBad,
BindTokenOk(Uuid), BindTokenOk(Uuid),
// Sign up
SignUpRequest(EmailString, UsernameString),
SignUpSuccess, SignUpSuccess,
SignUpPairTaken,
// project page // project page
ProjectRequest, ProjectRequest,

View File

@ -99,3 +99,49 @@ impl Handler<LoadIssueAssignees> for DbExecutor {
Ok(vec) Ok(vec)
} }
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct Register {
pub name: String,
pub email: String,
}
impl Message for Register {
type Result = Result<(), ServiceErrors>;
}
impl Handler<Register> for DbExecutor {
type Result = Result<(), ServiceErrors>;
fn handle(&mut self, msg: Register, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::users::dsl::*;
let conn = &self
.pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let query = users
.filter(
email
.eq(msg.email.as_str())
.and(name.ne(msg.name.as_str()))
.or(email.ne(msg.email.as_str()).and(name.eq(msg.name.as_str())))
.or(email.eq(msg.email.as_str()).and(name.eq(msg.name.as_str()))),
)
.count();
info!(
"{}",
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
);
let matching: i64 = query.get_result(conn).unwrap_or(1);
if matching > 0 {
return Err(ServiceErrors::RegisterCollision);
}
Ok(())
}
}

View File

@ -1,45 +1,49 @@
use actix_web::HttpResponse; use actix_web::HttpResponse;
use jirs_data::ErrorResponse; use jirs_data::ErrorResponse;
const TOKEN_NOT_FOUND: &str = "Token not found"; const TOKEN_NOT_FOUND: &str = "Token not found";
const DATABASE_CONNECTION_FAILED: &str = "Database connection failed"; const DATABASE_CONNECTION_FAILED: &str = "Database connection failed";
#[derive(Debug)] #[derive(Debug)]
pub enum ServiceErrors { pub enum ServiceErrors {
Unauthorized, Unauthorized,
DatabaseConnectionLost, DatabaseConnectionLost,
DatabaseQueryFailed(String), DatabaseQueryFailed(String),
RecordNotFound(String), RecordNotFound(String),
} RegisterCollision,
}
impl ServiceErrors {
pub fn into_http_response(self) -> HttpResponse { impl ServiceErrors {
self.into() pub fn into_http_response(self) -> HttpResponse {
} self.into()
} }
}
impl Into<HttpResponse> for ServiceErrors {
fn into(self) -> HttpResponse { impl Into<HttpResponse> for ServiceErrors {
match self { fn into(self) -> HttpResponse {
ServiceErrors::Unauthorized => HttpResponse::Unauthorized().json(ErrorResponse { match self {
errors: vec![TOKEN_NOT_FOUND.to_owned()], ServiceErrors::Unauthorized => HttpResponse::Unauthorized().json(ErrorResponse {
}), errors: vec![TOKEN_NOT_FOUND.to_owned()],
ServiceErrors::DatabaseConnectionLost => { }),
HttpResponse::InternalServerError().json(ErrorResponse { ServiceErrors::DatabaseConnectionLost => {
errors: vec![DATABASE_CONNECTION_FAILED.to_owned()], HttpResponse::InternalServerError().json(ErrorResponse {
}) errors: vec![DATABASE_CONNECTION_FAILED.to_owned()],
} })
ServiceErrors::DatabaseQueryFailed(error) => { }
HttpResponse::BadRequest().json(ErrorResponse { ServiceErrors::DatabaseQueryFailed(error) => {
errors: vec![error], HttpResponse::BadRequest().json(ErrorResponse {
}) errors: vec![error],
} })
ServiceErrors::RecordNotFound(resource_name) => { }
HttpResponse::BadRequest().json(ErrorResponse { ServiceErrors::RecordNotFound(resource_name) => {
errors: vec![format!("Resource not found {}", resource_name)], HttpResponse::BadRequest().json(ErrorResponse {
}) errors: vec![format!("Resource not found {}", resource_name)],
} })
} }
} ServiceErrors::RegisterCollision => HttpResponse::Unauthorized().json(ErrorResponse {
} errors: vec!["Register collision".to_string()],
}),
}
}
}

View File

@ -90,6 +90,11 @@ impl WebSocketActor {
block_on(auth::authenticate(&self.db, &self.mail, name, email))? block_on(auth::authenticate(&self.db, &self.mail, name, email))?
} }
// register
WsMsg::SignUpRequest(email, username) => {
block_on(users::register(&self.db, &self.mail, username, email))?
}
// users // users
WsMsg::ProjectUsersRequest => { WsMsg::ProjectUsersRequest => {
block_on(users::load_project_users(&self.db, &self.current_user))? block_on(users::load_project_users(&self.db, &self.current_user))?

View File

@ -3,8 +3,9 @@ use actix_web::web::Data;
use jirs_data::WsMsg; use jirs_data::WsMsg;
use crate::db::users::LoadProjectUsers; use crate::db::users::{LoadProjectUsers, Register};
use crate::db::DbExecutor; use crate::db::DbExecutor;
use crate::mail::MailExecutor;
use crate::ws::{current_user, WsResult}; use crate::ws::{current_user, WsResult};
pub async fn load_project_users( pub async fn load_project_users(
@ -20,3 +21,17 @@ pub async fn load_project_users(
}; };
Ok(m) Ok(m)
} }
pub async fn register(
db: &Data<Addr<DbExecutor>>,
_mail: &Data<Addr<MailExecutor>>,
name: String,
email: String,
) -> WsResult {
let msg = match db.send(Register { name, email }).await {
Ok(Ok(_)) => Some(WsMsg::SignUpSuccess),
Ok(Err(_)) => Some(WsMsg::SignUpPairTaken),
_ => None,
};
Ok(msg)
}