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)
}