2024-10-06 14:08:26 -04:00
|
|
|
use std::{
|
|
|
|
fmt::Debug,
|
|
|
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
|
|
|
};
|
2024-10-03 15:27:30 -04:00
|
|
|
|
2024-10-06 14:08:26 -04:00
|
|
|
use blake3::Hash;
|
|
|
|
use serde::Serialize;
|
2024-08-06 11:08:15 -04:00
|
|
|
use sqlx::prelude::FromRow;
|
2024-10-06 14:08:26 -04:00
|
|
|
use time::OffsetDateTime;
|
2024-10-03 15:27:30 -04:00
|
|
|
use tracing::error;
|
2024-09-29 09:57:02 -04:00
|
|
|
|
|
|
|
use crate::models::AppError;
|
2024-08-06 11:08:15 -04:00
|
|
|
|
|
|
|
use super::DbPool;
|
|
|
|
|
2024-10-03 15:27:30 -04:00
|
|
|
#[derive(Clone)]
|
2024-08-06 11:08:15 -04:00
|
|
|
pub struct NewUserEntity {
|
2024-09-29 09:57:02 -04:00
|
|
|
pub username: String,
|
|
|
|
pub password: String,
|
2024-08-06 11:08:15 -04:00
|
|
|
pub email: String,
|
|
|
|
pub name: String,
|
|
|
|
}
|
|
|
|
|
2024-10-03 15:27:30 -04:00
|
|
|
impl Debug for NewUserEntity {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
f.debug_struct("NewUserEntity")
|
|
|
|
.field("username", &self.username)
|
|
|
|
.field("password", &"********")
|
|
|
|
.field("email", &self.email)
|
|
|
|
.field("name", &self.name)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-06 11:08:15 -04:00
|
|
|
#[allow(dead_code)]
|
|
|
|
#[derive(Debug, FromRow)]
|
|
|
|
pub struct UserEntity {
|
|
|
|
pub id: i32,
|
|
|
|
pub name: String,
|
2024-08-22 17:29:24 -04:00
|
|
|
pub email: String,
|
2024-08-06 11:08:15 -04:00
|
|
|
pub status_id: i32,
|
|
|
|
}
|
|
|
|
|
2024-09-29 09:57:02 -04:00
|
|
|
pub async fn insert_new_user(
|
|
|
|
pool: &DbPool,
|
|
|
|
new_user: NewUserEntity,
|
|
|
|
) -> Result<UserEntity, AppError> {
|
|
|
|
let NewUserEntity {
|
|
|
|
username,
|
|
|
|
password,
|
|
|
|
email,
|
|
|
|
name,
|
2024-10-03 15:27:30 -04:00
|
|
|
} = new_user.clone();
|
2024-08-06 11:08:15 -04:00
|
|
|
|
2024-09-29 09:57:02 -04:00
|
|
|
sqlx::query_as::<_, UserEntity>("INSERT INTO public.user (username, email, password, name) VALUES ($1, $2, $3, $4) RETURNING id, username, email, name, status_id;")
|
|
|
|
.bind(username)
|
|
|
|
.bind(email)
|
|
|
|
.bind(password)
|
|
|
|
.bind(name)
|
|
|
|
.fetch_one(pool).await
|
|
|
|
.map_err(|err| {
|
2024-10-06 07:14:44 -04:00
|
|
|
error!(?err, record = ?new_user, "Cannot insert new user record");
|
2024-10-03 15:27:30 -04:00
|
|
|
|
2024-09-29 09:57:02 -04:00
|
|
|
AppError::from(err)
|
|
|
|
})
|
|
|
|
}
|
2024-08-06 11:08:15 -04:00
|
|
|
|
2024-10-05 08:09:46 -04:00
|
|
|
#[derive(Debug, FromRow)]
|
2024-10-06 14:08:26 -04:00
|
|
|
pub struct UserIdAndHashedPasswordEntity {
|
2024-10-05 08:09:46 -04:00
|
|
|
pub id: i32,
|
|
|
|
pub password: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_username_and_password_by_username(
|
|
|
|
pool: &DbPool,
|
|
|
|
username: String,
|
2024-10-06 14:08:26 -04:00
|
|
|
) -> Result<UserIdAndHashedPasswordEntity, AppError> {
|
|
|
|
sqlx::query_as::<_, UserIdAndHashedPasswordEntity>(
|
|
|
|
"SELECT id, password FROM public.user WHERE username = $1;",
|
2024-10-05 08:09:46 -04:00
|
|
|
)
|
|
|
|
.bind(username)
|
|
|
|
.fetch_one(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|err| {
|
2024-10-06 07:14:44 -04:00
|
|
|
error!(?err, "Unable to find user");
|
2024-10-05 08:09:46 -04:00
|
|
|
AppError::from(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-29 09:57:02 -04:00
|
|
|
pub async fn verify_user(pool: &DbPool, user_id: i32) -> Result<(), AppError> {
|
|
|
|
sqlx::query("UPDATE public.user SET status_id = 1, updated_at = now() WHERE id = $1;")
|
|
|
|
.bind(user_id)
|
|
|
|
.execute(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|err| {
|
2024-10-06 07:14:44 -04:00
|
|
|
error!(?err, user_id, "Error verifying user");
|
2024-09-29 09:57:02 -04:00
|
|
|
AppError::from(err)
|
|
|
|
})
|
|
|
|
.map(|_| ())
|
|
|
|
}
|
2024-10-06 14:08:26 -04:00
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct NewUserAuthTokenEntity {
|
|
|
|
pub user_id: i32,
|
|
|
|
pub token_hash: Hash,
|
|
|
|
pub expires_at: SystemTime,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, FromRow)]
|
|
|
|
pub struct TmpUserAuthTokenEntity {
|
|
|
|
pub id: i32,
|
|
|
|
pub user_id: i32,
|
|
|
|
pub token_hash: String,
|
|
|
|
pub expires_at: OffsetDateTime,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize)]
|
|
|
|
pub struct UserAuthTokenEntity {
|
|
|
|
pub id: i32,
|
|
|
|
pub user_id: i32,
|
|
|
|
pub token_hash: String,
|
|
|
|
pub expires_at: SystemTime,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TmpUserAuthTokenEntity> for UserAuthTokenEntity {
|
|
|
|
fn from(other: TmpUserAuthTokenEntity) -> Self {
|
|
|
|
let TmpUserAuthTokenEntity {
|
|
|
|
id,
|
|
|
|
user_id,
|
|
|
|
token_hash,
|
|
|
|
expires_at,
|
|
|
|
} = other;
|
|
|
|
|
|
|
|
Self {
|
|
|
|
id,
|
|
|
|
user_id,
|
|
|
|
token_hash,
|
|
|
|
expires_at: UNIX_EPOCH
|
|
|
|
.checked_add(Duration::new(
|
|
|
|
expires_at.clone().unix_timestamp().try_into().unwrap(),
|
|
|
|
expires_at.nanosecond(),
|
|
|
|
))
|
|
|
|
.unwrap(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn insert_new_user_auth_token(
|
|
|
|
pool: &DbPool,
|
|
|
|
new_user_auth_token: NewUserAuthTokenEntity,
|
|
|
|
) -> Result<UserAuthTokenEntity, AppError> {
|
|
|
|
let NewUserAuthTokenEntity {
|
|
|
|
user_id,
|
|
|
|
token_hash,
|
|
|
|
expires_at,
|
|
|
|
} = new_user_auth_token.clone();
|
|
|
|
|
|
|
|
let token_hash = token_hash.to_string();
|
|
|
|
let expires_at = OffsetDateTime::from(expires_at);
|
|
|
|
|
|
|
|
sqlx::query_as::<_, TmpUserAuthTokenEntity>("INSERT INTO public.user_auth_token (user_id, token_hash, expires_at) VALUES ($1, $2, $3) RETURNING id, user_id, token_hash, expires_at;")
|
|
|
|
.bind(user_id)
|
|
|
|
.bind(token_hash)
|
|
|
|
.bind(expires_at)
|
|
|
|
.fetch_one(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|err| {
|
|
|
|
error!(?err, record = ?new_user_auth_token, "Cannot insert new user auth token record");
|
|
|
|
AppError::from(err)
|
|
|
|
})
|
|
|
|
.map(From::from)
|
|
|
|
}
|