use super::AuthOtpSchema; use super::UserCacheSchema; use crate::{ AppState, PermissionsQueryDto, ResourceEnum, RolesDetailQueryDto, UsersDetailQueryDto, }; use anyhow::{Result, anyhow, bail}; use chrono::{Duration, Utc}; use surrealdb::sql::Thing; use tracing::instrument; use tracing::info; pub struct AuthRepository<'a> { pub state: &'a AppState, } impl<'a> AuthRepository<'a> { pub fn new(state: &'a AppState) -> Self { Self { state } } #[instrument(skip(self, user), err)] pub async fn query_store_user(&self, user: UsersDetailQueryDto) -> Result { if user.email.trim().is_empty() { bail!("Email is required"); } let table = ResourceEnum::UsersCache.to_string(); let user_id = user.email.clone(); let permissions: Vec = user.role.permissions.as_ref().unwrap_or(&vec![]).iter().filter_map(|p| p.as_ref().and_then(|pp| pp.name.clone())).collect(); let user_cache = UserCacheSchema { email: user_id.clone(), permissions, }; info!(query = %format!("DELETE FROM {} WHERE id = '{}'", table, user_id), "Executing SurrealDB query"); let _record: Option = self .state .surrealdb_mem .delete::>((table.clone(), user_id.clone())) .await?; info!(query = %format!("CREATE {}:{}", table, user_id), "Executing SurrealDB query"); let record: Option = self .state .surrealdb_mem .create((table, user_id)) .content(user_cache) .await?; match record { Some(_) => Ok("Success store user data".to_string()), None => bail!("Failed store user data"), } } #[instrument(skip(self, email), err)] pub async fn query_get_stored_user( &self, email: String, ) -> Result { info!(query = %format!("SELECT FROM {} WHERE id = '{}'", ResourceEnum::UsersCache.to_string(), email), "Executing SurrealDB query"); let user_cache: Option = self .state .surrealdb_mem .select((ResourceEnum::UsersCache.to_string(), email.clone())) .await?; match user_cache { Some(cache) => { let permissions_query_dto: Vec = cache .permissions .into_iter() .map(|name| PermissionsQueryDto { id: Some(Thing::from(( "app_permissions".to_string(), surrealdb::sql::Id::rand(), ))), name: Some(name), created_at: None, updated_at: None, }) .collect(); let role_detail_query_dto = RolesDetailQueryDto { id: Thing::from(("app_roles".to_string(), surrealdb::sql::Id::rand())), name: "CachedRole".to_string(), permissions: Some(permissions_query_dto.into_iter().map(Some).collect()), is_deleted: false, created_at: None, updated_at: None, }; Ok(UsersDetailQueryDto { id: Thing::from(("app_users".to_string(), email.clone())), fullname: "Cached User".to_string(), legal_name: None, email: cache.email, avatar: None, phone_number: String::new(), phone_for_verification: None, is_active: true, is_deleted: false, gender: None, birthdate: None, domicile: None, bio: None, last_education: None, linkedin_url: None, github_url: None, cv_url: None, portfolio_url: None, website_url: None, twitter_url: None, location: None, skills: None, experience: None, education: None, career_status: None, password: String::new(), role: role_detail_query_dto, created_at: String::new(), updated_at: String::new(), mentor_id: None, }) } None => bail!("No stored user data found"), } } #[instrument(skip(self, email), err)] pub async fn query_delete_stored_user(&self, email: String) -> Result { info!(query = %format!("DELETE FROM {} WHERE id = '{}'", ResourceEnum::UsersCache.to_string(), email), "Executing SurrealDB query"); let record: Option = self .state .surrealdb_mem .delete((ResourceEnum::UsersCache.to_string(), email)) .await?; match record { Some(_) => Ok("Success delete stored user".to_string()), None => bail!("Failed delete stored user"), } } #[instrument(skip(self, email), err)] pub async fn query_get_stored_otp(&self, email: String) -> Result { let table = ResourceEnum::OtpCache.to_string(); let key = (table.as_str(), email.as_str()); info!(query = %format!("SELECT FROM {} WHERE id = '{}'", table, email), "Executing SurrealDB query"); let result: Option = self.state.surrealdb_mem.select(key).await?; match result { Some(data) => match Utc::now() > data.expires_at { true => { info!(query = %format!("DELETE FROM {} WHERE id = '{}'", table, email), "Executing SurrealDB query"); let _ = self .state .surrealdb_mem .delete::>(key) .await?; Err(anyhow!("OTP expired")) } false => Ok(data.otp), }, None => bail!("No stored OTP found"), } } #[instrument(skip(self, email, otp), err)] pub async fn query_store_otp(&self, email: String, otp: u32) -> Result { let expires_at = Utc::now() + Duration::seconds(300); let table: String = ResourceEnum::OtpCache.to_string(); info!(query = %format!("CREATE {}:{}", table, email), "Executing SurrealDB query"); let record: Option = self .state .surrealdb_mem .create((table.as_str(), email.as_str())) .content(AuthOtpSchema { otp, expires_at }) .await?; match record { Some(_) => Ok("Success store otp".to_string()), None => bail!("Failed store otp"), } } #[instrument(skip(self, email), err)] pub async fn query_delete_stored_otp(&self, email: String) -> Result { info!(query = %format!("DELETE FROM {} WHERE id = '{}'", ResourceEnum::OtpCache.to_string(), email), "Executing SurrealDB query"); let record: Option = self .state .surrealdb_mem .delete((ResourceEnum::OtpCache.to_string(), email)) .await?; match record { Some(_) => Ok("Success delete stored otp".to_string()), None => bail!("Failed delete stored otp"), } } }