use chrono::prelude::*;
use ::log::*; use std::collections::HashMap;
use std::ops::Sub;
use std::{thread, time};
use crate::db;
use crate::utils;
use crate::messages::Msg;
#[derive(Debug)]
pub struct Registration {
email: String,
password: String,
repeat_password: String,
token: String,
}
impl Registration {
pub fn instance(email: &str, password: &str, repeat_password: &str, token: &str) -> Registration {
Registration {
email: email.to_string(),
password: password.to_string(),
repeat_password: repeat_password.to_string(),
token: token.to_string()
}
}
pub fn new(email: &str, pwd: &str, repeat_pwd: &str) -> Result<Msg, Vec<Msg>> {
let new_regn = Self::instance(email, pwd, repeat_pwd, &utils::generate_token());
match new_regn.validate() {
Ok(_) => {
new_regn.add()
},
Err(messages) => {
error!("{:?} occurred in Registration::new(..)", messages);
Err(messages)
}
}
}
pub fn from_map(map: &HashMap<String, String>) -> Result<Msg, Vec<Msg>> {
Self::new(
map.get("email").unwrap(),
map.get("password").unwrap(),
map.get("repeat-password").unwrap()
)
}
pub fn from_json_map(map: &serde_json::map::Map<String, serde_json::value::Value>) -> Result<Msg, Vec<Msg>> {
Self::new(
map.get("email").unwrap().as_str().unwrap(),
map.get("password").unwrap().as_str().unwrap(),
map.get("repeat-password").unwrap().as_str().unwrap(),
)
}
pub fn from_email_id(id: &str) -> Registration { Self::instance(id, "", "", "")
}
pub fn email(&self) -> String {
self.email.clone()
}
fn password(&self) -> String {
self.password.clone()
}
pub fn password_hashed(&self) -> String {
utils::hash(&self.password())
}
pub fn token(&self) -> String {
self.token.clone()
}
pub fn initial_status(&self) -> String {
Msg::ConfirmationPending.description()
}
pub fn initial_remarks(&self) -> String {
Msg::RegistrationInitialRemarks.description()
}
fn add(&self) -> Result<Msg, Vec<Msg>> {
match db::add_registration(&self) {
Ok(row_count) => {
self.send_registration_confirmation_email();
debug!("New Registration success: {:?} row(s) inserted", row_count);
Ok(Msg::EmailSentToCompleteRegistration)
}
Err(error) => {
error!("{:?} occurred in Registration::add(..)", error);
Err(vec![Msg::EmailExist])
}
}
}
fn send_registration_confirmation_email(&self) {
let email_enabled: bool = super::app_config("email_enabled") == "true".to_string();
if !email_enabled { return (); }
let email_from = super::app_config("email_from");
let message_body = format!("Registration Confirmation Token is: {}.\n\nPlease copy this token into confirmation window to complete registration", self.token());
let email_to = self.email();
let subject = "Login-app Registration Confirmation Email";
let email = utils::Email::new(&email_from, &email_to, &subject, &message_body);
email.send();
}
pub fn wait_to_remove_unconfirmed(delay: u64, email: String ) {
debug!("wait_to_removed_unconfirmed({:?}, {:?}).......................<<", &delay, &email);
thread::spawn(move || {
let duration = time::Duration::from_millis(delay+3000); debug!("Time before sleep: {:?}", time::Instant::now());
thread::sleep(duration);
debug!("Time after sleep: {:?}", time::Instant::now());
match db::get_unconfirmed_user_record_for(&email) {
Ok(user_record) => {
debug!("User Record: {:?}", user_record);
Self::delete_expired_registration(&email)
},
Err(message) => {
error!("{:?} occurred in Registration::wait_to_remove_unconfirmed()!", message);
Err(Msg::GetUnconfirmedUserRecordFailed)
}
}
});
}
pub fn cancel(email: &str) -> Result<Msg, Msg> { debug!("cancel({:?})........................<<", &email);
if db::email_status_record_count(email, "active") < 1 {
return Err(Msg::NoEmailFoundCancellationFailed);
}
match db::cancel_registration(email) {
Ok(row_count) => {
debug!("{:?} row(s) deleted; Cancellation success", row_count);
Ok(Msg::CancellationSuccess)
},
Err(message) => {
error!("{:?} occurred in Registration::cancel(..)", message);
Err(Msg::CancellationFailed)
}
}
}
pub fn delete(email: &str) -> Result<Msg, Msg> { debug!("delete({:?})........................<<", &email);
if db::email_status_record_count(email, "active") < 1 {
return Err(Msg::NoEmailFoundDeleteFailed);
}
match db::delete_registration(email) {
Ok(row_count) => {
debug!("{:?} row(s) deleted; Cancellation success", row_count);
Ok(Msg::DeletionSuccess)
},
Err(message) => {
error!("{:?} occurred in Registration::delete(..)", message);
Err(Msg::DeletionFailed)
}
}
}
pub fn delete_expired_registration(email: &str) -> Result<Msg, Msg> {
debug!("delete_expired_registration({:?}).........................<<", &email);
if !Self::is_registration_expired(email) {
return Err(Msg::ExpiryCheckFailed);
}
match db::delete_registration(email) {
Ok(row_count) => {
debug!("{:?} row(s) deleted; delete expired registration success", row_count);
Ok(Msg::DeleteExpiredRegistrationSuccess)
},
Err(message) => {
error!("{:?} occurred in Registration::delete_expired_registration()", message);
Err(Msg::DeleteExpiredRegistrationFailed)
}
}
}
fn is_registration_expired(email: &str) -> bool {
debug!("is_registration_expired({:?})............................<<", &email);
match db::get_user_record_for(email) {
Ok(user_record) => {
debug!("User Record: {:?}", user_record);
let created_on = DateTime::parse_from_str(&user_record.created_on(), "%Y-%m-%d %H:%M:%S%.9f %z").unwrap();
Self::is_waiting_time_elapsed_since(created_on)
},
Err(message) => {
error!("{:?} occurred in is_registration_expired", message);
false
}
}
}
fn is_waiting_time_elapsed_since(created_on: DateTime<FixedOffset>) -> bool {
debug!("is_waiting_time_elapsed_since({:?})...................<<", &created_on);
let time_now_str = Local::now().to_string();
let time_now = DateTime::parse_from_str(&time_now_str, "%Y-%m-%d %H:%M:%S%.9f %z").unwrap();
debug!("Time now: {:?}", &time_now);
let elapsed_time = time_now.sub(created_on);
let waiting_time: i64 = super::app_config("token_validity_time").parse().unwrap();
let waiting_unit = super::app_config("token_validity_unit");
debug!("Elapsed time: {:?}", &elapsed_time);
match waiting_unit.as_ref() {
"minutes" => elapsed_time.num_minutes() >= waiting_time,
"seconds" => elapsed_time.num_seconds() >= waiting_time,
_ => false,
}
}
pub fn confirm(token: &str) -> Result<Msg, Msg> {
debug!("confirm( {:?} ).............................<<", token);
if db::token_count(token) < 1 {
return Err(Msg::InvalidToken);
}
let mut user = db::get_user_for_token(token)?;
if user.status() != Msg::ConfirmationPending.description() {
error!("Registration is already confirmed");
return Err(Msg::RegistrationAlreadyConfirmed);
}
let clear_token = "";
user.set_status(&Msg::Active.description());
let row_count = user.update_with_token(&clear_token);
if row_count == 0 {
error!("{:?} row(s) affected in registration::confirm(..) while updating status", row_count);
Err(Msg::RegistrationConfirmationFailed)
} else {
debug!("{:?} row(s) updated with confirmed status", row_count);
Ok(Msg::RegistrationConfirmed)
}
}
fn validate(&self) -> Result<Msg, Vec<Msg>> {
debug!("validate().............................<<");
let mut errors = vec![];
if db::email_status_record_count(&self.email, "active") != 0 {
error!("email NOT available");
errors.push(Msg::EmailExist);
}
if self.password != self.repeat_password {
error!("passwords DO NOT match");
errors.push(Msg::PasswordsDoNotMatch);
}
if !utils::Validator::validate_email_id_pattern(self.email.clone()) {
error!("INVALID email pattern");
errors.push(Msg::InvalidEmailPattern);
}
let mut messages = utils::Validator::validate_password(self.password.clone());
errors.append(&mut messages);
if errors.is_empty() {
Ok(Msg::ValidationsPassed)
} else {
Err(errors)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample1() -> Registration {
Registration::instance("unit test", "pass1", "pass2", "")
}
fn sample2() -> Registration {
Registration::instance("unit test", "pass1", "pass1", "")
}
#[test]
fn utest_passwords_do_not_match() {
let mut result = vec![];
if let Err(errors) = sample1().validate() { result = errors; }
assert!( result.contains(&Msg::PasswordsDoNotMatch) )
}
#[test]
fn utest_passwords_match() {
let mut result = vec![];
if let Err(errors) = sample2().validate() { result = errors; }
assert!( !result.contains(&Msg::PasswordsDoNotMatch) )
}
#[test]
fn utest_email_available() {
let regn = Registration::from_email_id("unit test");
let mut result = vec![];
if let Err(errors) = regn.validate() { result = errors; }
assert!( !result.contains(&Msg::EmailExist) )
}
#[test]
fn utest_invalid_token() {
let result = Registration::confirm("an invalid token").unwrap_or_else(|error| error);
assert_eq!(result, Msg::InvalidToken);
}
}