user-service 0.4.1

A user management microservice.
Documentation
use base64::Engine;
use rand::{distributions::Alphanumeric, Rng};

use super::*;

#[derive(Deserialize, Debug)]
pub struct Request {
    pub user: RequestUser,
    pub verify: Option<RequestVerify>,
}

#[derive(Deserialize, Debug, Insertable)]
#[diesel(table_name = schema::user)]
pub struct RequestUser {
    pub email: String,
    pub password: String,
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub birthday: Option<time::Date>,
}

#[derive(Deserialize, Debug)]
pub struct RequestVerify {
    pub sender_name: String,
    // The callback URL to which a query string will be appended containing the verification token
    // Example Input: https://example.com/verify
    // Example Output: https://example.com/verify?token=123456
    pub callback: String,
}

#[derive(Serialize, Default)]
pub struct Response {
    pub id: i32,
}

#[derive(Serialize, Default)]
struct SendMail {
    sender_name: String,
    recipient: String,
    subject: String,
    base64_content: String,
}

#[post("")]
pub async fn route(
    db: web::Data<Database>,
    req: web::Json<Request>,
) -> Result<impl Responder, impl ResponseError> {
    let mut req = req.into_inner();

    if !valid(&req) {
        return Err(ErrorCode::BadRequest);
    }

    let hashed_password = hashing::generate(&req.user.password);
    req.user.password = hashed_password;

    let email = req.user.email.clone();

    let verified_value = !req.verify.is_some();
    let verified_exists = req.verify.is_some();

    let mut token_value = String::new();

    if req.verify.is_some() {
        token_value = rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(32)
            .map(char::from)
            .collect()
    }

    let token_value_moved = token_value.clone();

    let response: Result<i32, diesel::result::Error> = web::block(move || {
        use diesel::dsl::*;
        use schema::user::dsl::*;
        let mut conn = db.get_conn();

        conn.transaction(|conn| {
            let mut user_id = insert_into(user)
                .values((
                    email.eq(req.user.email),
                    password.eq(req.user.password),
                    first_name.eq(req.user.first_name),
                    last_name.eq(req.user.last_name),
                    birthday.eq(req.user.birthday),
                    verified.eq(verified_value),
                ))
                .returning(id)
                .get_result(conn);

            if user_id.is_err() {
                return user_id;
            }

            if verified_exists {
                user_id = insert_into(schema::verify::table)
                    .values((
                        schema::verify::user.eq(user_id.unwrap()),
                        schema::verify::token.eq(token_value_moved),
                    ))
                    .returning(schema::verify::user)
                    .get_result(conn);
            }

            user_id
        })
    })
    .await
    .unwrap();

    let response = match response {
        Err(_) => return Err(ErrorCode::Conflict),
        Ok(id) => Response { id },
    };

    if let Some(verify) = req.verify {
        let body = format!(
            "Please click the following link to verify your {} account: {}?token={}",
            verify.sender_name, verify.callback, token_value
        );

        let body = base64::engine::general_purpose::STANDARD.encode(body);

        reqwest::Client::new()
            .post(format!(
                "{}/v1/send",
                std::env::var("MAIL_SERVICE_HOST")
                    .expect("MAIL_SERVICE_HOST environment variable is not set")
            ))
            .json(&SendMail {
                sender_name: verify.sender_name,
                recipient: email,
                subject: "Email Verification".to_owned(),
                base64_content: body,
            })
            .send()
            .await
            .unwrap();
    }

    Ok(web::Json(response))
}

fn valid(req: &Request) -> bool {
    if !user_client::validate::email(&req.user.email) {
        return false;
    }

    if !user_client::validate::password(&req.user.password) {
        return false;
    }

    if req.user.first_name.is_some() {
        if !user_client::validate::first_name(req.user.first_name.as_ref().unwrap()) {
            return false;
        }
    }

    if req.user.last_name.is_some() {
        if !user_client::validate::last_name(req.user.last_name.as_ref().unwrap()) {
            return false;
        }
    }

    if req.user.birthday.is_some() {
        if !user_client::validate::birthday(&req.user.birthday.unwrap()) {
            return false;
        }
    }

    true
}