zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use crate::prelude2::*;

pub mod models;

use sqlx::MySql;
use sqlx::Pool;
use sqlx::QueryBuilder;
use sqlx::Row;

use actix_web_flash_messages::FlashMessage;

use crate::commons::validatorr::validation_flatten;
use crate::core::auth0::UserDetails;
use crate::services::user_account_service::query_user_record;
use crate::utils;

use crate::services::user_service::models::UsersRecord;

#[derive(serde::Deserialize, validator::Validate)]
pub struct SingupFormData {
    #[validate(length(min = 2, max = 48, message = "must be between 2 to 48"))]
    username: String,
    #[validate(length(min = 8, max = 48, message = "must be between 8 to 48"))]
    useremail: Option<String>,
    password: String,
    passwd_repeat: String,
}

//  用户注册
pub async fn signup(
    form: web::Form<SingupFormData>,
    context: web::Data<AppContext>,
    request: HttpRequest,
) -> Result<HttpResponse> {
    if let Some(err) = validation_flatten(&form.0) {
        return request.json(200, R::invalid(err));
    }

    let username = form.0.username;
    let useremail = form.0.useremail;
    let passwd = form.0.password;
    let passwd_again = form.0.passwd_repeat;

    let request_with = request
        .headers()
        .get("X-Requested-With")
        .map(|val| val.to_str().unwrap_or_default())
        .unwrap_or("");

    if (get_by_username(&username, "passwd", context.mysql()).await?).is_some() {
        if request_with == "fetch" {
            return request.json(200, R::failed(400, "Username exists!"));
        } else {
            FlashMessage::info("Username exists!").send();
            return Ok(utils::see_other("/signup"));
        }
    }

    if let Some(ref e) = useremail {
        if !crate::commons::validatorr::to_valid_email(e) {
            if request_with == "fetch" {
                return request.json(200, R::failed(400, "邮箱格式有误!"));
            } else {
                FlashMessage::info("邮箱格式有误!").send();
                return Ok(utils::see_other("/signup"));
            }
        }
    }

    if passwd != passwd_again {
        if request_with == "fetch" {
            return request.json(200, R::failed(400, "输入密码不一致!"));
        } else {
            FlashMessage::info("输入密码不一致!").send();
            return Ok(utils::see_other("/signup"));
        }
    }

    save_user(username, passwd, useremail, context.mysql()).await?;

    if request_with == "fetch" {
        request.json(200, R::success(true, "Sign up successfully, please login."))
    } else {
        FlashMessage::info("Sign up successfully, please login.").send();

        Ok(utils::see_other("/login"))
    }
}

//  用户注册
pub async fn register(
    form: web::Form<SingupFormData>,
    context: web::Data<AppContext>,
    request: HttpRequest,
) -> impl Responder {
    if let Some(err) = validation_flatten(&form.0) {
        return request.json(200, R::invalid(err));
    }

    let username = form.0.username;
    let useremail = form.0.useremail;
    let passwd = form.0.password.to_string();

    if (get_by_username(&username, "passwd", context.mysql()).await?).is_some() {
        return request.json(200, R::failed(400, "用户名已被占用".to_string()));
    }

    save_user(username, passwd, useremail, context.mysql()).await?;

    request.json(200, R::ok(true))
}

pub async fn get_stored_credentials(
    username: &str,
    pool: &Pool<MySql>,
) -> Result<Option<(String, Option<String>, String)>> {
    let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
        "SELECT user_id, nick_name, password_hash, resource_id FROM t_users WHERE 1=1",
    );

    query_builder.push(" and username = ");
    query_builder.push_bind(username);

    let row = query_builder
        .build()
        .fetch_optional(pool)
        .await
        .context("Failed to perform a query to validate auth credentials.")?
        .map(|row| {
            let user_id: String = row.get("user_id");
            let password_hash: String = row.get("password_hash");
            let resource_id: String = row.get("resource_id");
            (user_id, Some(password_hash), resource_id)
        });

    if let Some(r) = row {
        Ok(Some(r))
    } else {
        Ok(None)
    }
}

pub async fn get_username(user_id: &str, pool: &Pool<MySql>) -> Result<Option<String>> {
    let mut query_builder: QueryBuilder<MySql> =
        QueryBuilder::new("SELECT username FROM t_users WHERE 1=1");

    query_builder.push(" and user_id = ");
    query_builder.push_bind(user_id);

    let row = query_builder
        .build()
        .fetch_optional(pool)
        .await
        .context("Failed to perform a query to retrieve a username.")?;

    if let Some(row) = row {
        let username: String = row.get("username");

        Ok(Some(username))
    } else {
        Ok(None)
    }
}

pub async fn get_by_username(
    username: &str,
    reg_channel: &str,
    pool: &Pool<MySql>,
) -> Result<Option<String>> {
    let mut query_builder: QueryBuilder<MySql> =
        QueryBuilder::new("SELECT user_id FROM t_users WHERE 1=1");

    query_builder.push(" and username = ");
    query_builder.push_bind(username.to_lowercase());

    query_builder.push(" and login_type = ");
    query_builder.push_bind(reg_channel);

    let row = query_builder
        .build()
        .fetch_optional(pool)
        .await
        .context("Failed to perform a query to retrieve a username.")?;

    if let Some(row) = row {
        Ok(Some(row.get("user_id")))
    } else {
        Ok(None)
    }
}

// 用户详情
pub async fn get_user_details(pool: &Pool<MySql>, user_id: &str) -> Result<Option<UserDetails>> {
    if let Some(user_record) = query_user_record(pool, user_id).await? {
        Ok(Some(UserDetails {
            user_id: user_record.user_id.to_owned(),
            user_name: user_record.username.unwrap_or_default(),
            nick_name: user_record.nick_name.to_owned(),
            user_role: user_record.user_roles.unwrap_or_default(),
            resource_id: user_record.resource_id.to_owned(),
        }))
    } else {
        Ok(None)
    }
}

// 保存用户注册信息
pub async fn save_user(
    username: String,
    user_passwd: String,
    useremail: Option<String>,
    pool: &Pool<MySql>,
) -> Result<String> {
    let reg_channel = "passwd";
    let user_id = get_by_username(&username, reg_channel, pool).await?;

    if let Some(user_id) = user_id {
        return Ok(user_id);
    }

    let passwd_hash =
        crate::commons::compute_password_hash(&user_passwd).map_err(|e| anyhow::anyhow!(e))?;

    let r = new_user_record(
        &username,
        Some(passwd_hash),
        None,
        useremail.clone(),
        reg_channel,
    );

    UsersRecord::insert(pool, &r).await.map_err(|e| {
        log::error!("error2={:?}", e);
        anyhow::anyhow!(e)
    })?;

    Ok(r.user_id)
}

fn new_user_record(
    username: &str,
    passwd: Option<String>,
    real_name: Option<String>,
    useremail: Option<String>,
    login_type: &str,
) -> UsersRecord {
    let curr_time = crate::commons::timestamp_millis();
    let user_id = uuid::Uuid::now_v7().to_string();

    UsersRecord {
        user_id: user_id.clone(),
        resource_id: crate::commons::random_id(),
        username: Some(username.to_owned()),
        user_country: None,
        user_phone: None,
        nick_name: username.to_owned(),
        user_status: None,
        user_roles: Some("r_user".to_owned()),
        login_type: Some(login_type.to_owned()),
        password_hash: passwd,
        user_bio: None,
        user_maxim: None,
        real_name: real_name.clone(),
        gender: None,
        idno: None,
        user_phone_secondary: None,
        user_email: useremail.map(|email| email.trim().to_owned()),
        user_address: None,
        user_linkman: None,
        user_linkphone: None,
        created_at: Some(curr_time as u64),
        last_updated_at: Some(curr_time as u64),
        last_login_at: Some(curr_time as u64),
    }
}

// 保存用户注册信息
pub async fn save_user_by_google(
    username: String,
    useremail: Option<String>,
    real_name: Option<String>,
    pool: &Pool<MySql>,
) -> Result<String> {
    let reg_channel = "google";
    let user_id = get_by_username(&username, reg_channel, pool).await?;

    if let Some(user_id) = user_id {
        return Ok(user_id);
    }

    let r = new_user_record(&username, None, real_name, useremail.clone(), reg_channel);

    UsersRecord::insert(pool, &r).await.map_err(|e| {
        log::error!("error2={:?}", e);
        anyhow::anyhow!(e)
    })?;

    Ok(r.user_id)
}

// 保存用户注册信息
pub async fn save_user_by_github(
    username: String,
    useremail: Option<String>,
    pool: &Pool<MySql>,
) -> Result<String> {
    let reg_channel = "github";
    let user_id = get_by_username(&username, reg_channel, pool).await?;

    if let Some(user_id) = user_id {
        return Ok(user_id);
    }

    let r = new_user_record(&username, None, None, useremail.clone(), reg_channel);

    UsersRecord::insert(pool, &r).await.map_err(|e| {
        log::error!("error2={:?}", e);
        anyhow::anyhow!(e)
    })?;

    Ok(r.user_id)
}