use std::{ fmt::Debug, time::{Duration, SystemTime, UNIX_EPOCH}, }; use blake3::Hash; use serde::Serialize; use sqlx::prelude::FromRow; use time::OffsetDateTime; use tracing::error; use crate::models::AppError; use super::DbPool; #[derive(Clone)] pub struct NewUserEntity { pub username: String, pub password: String, pub email: String, pub name: String, } 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() } } #[allow(dead_code)] #[derive(Debug, FromRow)] pub struct UserEntity { pub id: i32, pub name: String, pub email: String, pub status_id: i32, } pub async fn insert_new_user( pool: &DbPool, new_user: NewUserEntity, ) -> Result { let NewUserEntity { username, password, email, name, } = new_user.clone(); 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| { error!(?err, record = ?new_user, "Cannot insert new user record"); AppError::from(err) }) } #[derive(Debug, FromRow)] pub struct UserIdAndHashedPasswordEntity { pub id: i32, pub password: String, } pub async fn get_username_and_password_by_username( pool: &DbPool, username: String, ) -> Result { sqlx::query_as::<_, UserIdAndHashedPasswordEntity>( "SELECT id, password FROM public.user WHERE username = $1;", ) .bind(username) .fetch_one(pool) .await .map_err(|err| { error!(?err, "Unable to find user"); AppError::from(err) }) } 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| { error!(?err, user_id, "Error verifying user"); AppError::from(err) }) .map(|_| ()) } #[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 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 { 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) }