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;
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,
BindClientRequest,
// sign up
SignUpRequest,
StyledSelectChanged(FieldId, StyledSelectChange),
ChangePage(model::Page),

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ use seed::{prelude::*, *};
use jirs_data::{SignUpFieldId, WsMsg};
use crate::api::send_ws_msg;
use crate::model::{Page, PageContent, SignUpPage};
use crate::shared::styled_button::StyledButton;
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_touched = true;
}
Msg::SignUpRequest => {
send_ws_msg(WsMsg::SignUpRequest(
page.email.clone(),
page.username.clone(),
));
}
Msg::WsMsg(WsMsg::SignUpSuccess) => {
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()
.primary()
.text("Register")
.on_click(mouse_ev(Ev::Click, |_| Msg::SignInRequest))
.on_click(mouse_ev(Ev::Click, |_| Msg::SignUpRequest))
}
.build()
.into_node();
@ -88,6 +98,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
let sign_in_link = StyledLink::build()
.text("Sign In")
.href("/login")
.add_class("signInLink")
.build()
.into_node();
@ -109,17 +120,24 @@ pub fn view(model: &model::Model) -> Node<Msg> {
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()
.heading("Sign In to your account")
.on_submit(ev(Ev::Submit, |ev| {
ev.stop_propagation();
ev.prevent_default();
Msg::SignInRequest
Msg::SignUpRequest
}))
.add_field(username_field)
.add_field(email_field)
.add_field(submit_field)
.add_field(no_pass_section)
.add_field(error_row)
.build()
.into_node();
let children = vec![sign_up_form];

View File

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

View File

@ -99,3 +99,49 @@ impl Handler<LoadIssueAssignees> for DbExecutor {
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 jirs_data::ErrorResponse;
const TOKEN_NOT_FOUND: &str = "Token not found";
const DATABASE_CONNECTION_FAILED: &str = "Database connection failed";
#[derive(Debug)]
pub enum ServiceErrors {
Unauthorized,
DatabaseConnectionLost,
DatabaseQueryFailed(String),
RecordNotFound(String),
}
impl ServiceErrors {
pub fn into_http_response(self) -> HttpResponse {
self.into()
}
}
impl Into<HttpResponse> for ServiceErrors {
fn into(self) -> HttpResponse {
match self {
ServiceErrors::Unauthorized => HttpResponse::Unauthorized().json(ErrorResponse {
errors: vec![TOKEN_NOT_FOUND.to_owned()],
}),
ServiceErrors::DatabaseConnectionLost => {
HttpResponse::InternalServerError().json(ErrorResponse {
errors: vec![DATABASE_CONNECTION_FAILED.to_owned()],
})
}
ServiceErrors::DatabaseQueryFailed(error) => {
HttpResponse::BadRequest().json(ErrorResponse {
errors: vec![error],
})
}
ServiceErrors::RecordNotFound(resource_name) => {
HttpResponse::BadRequest().json(ErrorResponse {
errors: vec![format!("Resource not found {}", resource_name)],
})
}
}
}
}
use actix_web::HttpResponse;
use jirs_data::ErrorResponse;
const TOKEN_NOT_FOUND: &str = "Token not found";
const DATABASE_CONNECTION_FAILED: &str = "Database connection failed";
#[derive(Debug)]
pub enum ServiceErrors {
Unauthorized,
DatabaseConnectionLost,
DatabaseQueryFailed(String),
RecordNotFound(String),
RegisterCollision,
}
impl ServiceErrors {
pub fn into_http_response(self) -> HttpResponse {
self.into()
}
}
impl Into<HttpResponse> for ServiceErrors {
fn into(self) -> HttpResponse {
match self {
ServiceErrors::Unauthorized => HttpResponse::Unauthorized().json(ErrorResponse {
errors: vec![TOKEN_NOT_FOUND.to_owned()],
}),
ServiceErrors::DatabaseConnectionLost => {
HttpResponse::InternalServerError().json(ErrorResponse {
errors: vec![DATABASE_CONNECTION_FAILED.to_owned()],
})
}
ServiceErrors::DatabaseQueryFailed(error) => {
HttpResponse::BadRequest().json(ErrorResponse {
errors: vec![error],
})
}
ServiceErrors::RecordNotFound(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))?
}
// register
WsMsg::SignUpRequest(email, username) => {
block_on(users::register(&self.db, &self.mail, username, email))?
}
// users
WsMsg::ProjectUsersRequest => {
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 crate::db::users::LoadProjectUsers;
use crate::db::users::{LoadProjectUsers, Register};
use crate::db::DbExecutor;
use crate::mail::MailExecutor;
use crate::ws::{current_user, WsResult};
pub async fn load_project_users(
@ -20,3 +21,17 @@ pub async fn load_project_users(
};
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)
}