feat: user management
This commit is contained in:
Generated
+114
-54
@@ -36,6 +36,18 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "affinitypool"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dde2a385b82232b559baeec740c37809051c596f9b56e7da0d0da2c8e8f54f6"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"num_cpus",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -142,12 +154,6 @@ dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.3"
|
||||
@@ -738,20 +744,6 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@@ -1074,6 +1066,19 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ext-sort"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf73e44617eab501beba39234441a194cf138629d3b6447f81f573e1c3d0a13"
|
||||
dependencies = [
|
||||
"log",
|
||||
"rayon",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@@ -2008,6 +2013,15 @@ version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@@ -2161,7 +2175,6 @@ dependencies = [
|
||||
"lettre",
|
||||
"log",
|
||||
"rand 0.9.0",
|
||||
"redis",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2704,6 +2717,18 @@ dependencies = [
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick_cache"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f8ed0655cbaf18a26966142ad23b95d8ab47221c50c4f73a1db7d0d2d6e3da8"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.7"
|
||||
@@ -2888,28 +2913,6 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffead9d0a0b45f3e0bc063a244b1779fd53a09d2c2f7282c186a016b1f10a778"
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "0.28.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e37ec3fd44bea2ec947ba6cc7634d7999a6590aca7c35827c250bc0de502bda6"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"bytes",
|
||||
"combine",
|
||||
"futures-util",
|
||||
"itoa",
|
||||
"num-bigint",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"ryu",
|
||||
"sha1_smol",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.10"
|
||||
@@ -3034,6 +3037,15 @@ dependencies = [
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revision"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f53179a035f881adad8c4d58a2c599c6b4a8325b989c68d178d7a34d1b1e4c"
|
||||
dependencies = [
|
||||
"revision-derive 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revision"
|
||||
version = "0.11.0"
|
||||
@@ -3043,12 +3055,23 @@ dependencies = [
|
||||
"chrono",
|
||||
"geo",
|
||||
"regex",
|
||||
"revision-derive",
|
||||
"revision-derive 0.11.0",
|
||||
"roaring",
|
||||
"rust_decimal",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revision-derive"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0ec466e5d8dca9965eb6871879677bef5590cf7525ad96cae14376efb75073"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revision-derive"
|
||||
version = "0.11.0"
|
||||
@@ -3114,6 +3137,17 @@ dependencies = [
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmpv"
|
||||
version = "1.3.0"
|
||||
@@ -3505,12 +3539,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
@@ -3752,7 +3780,7 @@ dependencies = [
|
||||
"pharos",
|
||||
"reblessive",
|
||||
"reqwest",
|
||||
"revision",
|
||||
"revision 0.11.0",
|
||||
"ring",
|
||||
"rust_decimal",
|
||||
"rustls",
|
||||
@@ -3782,6 +3810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ceb9f421e07af67b06c57ea7a8b08c3b4e4677de483435ca6d69bd00600a571"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"affinitypool",
|
||||
"ahash 0.8.11",
|
||||
"ammonia",
|
||||
"any_ascii",
|
||||
@@ -3801,6 +3830,7 @@ dependencies = [
|
||||
"dashmap",
|
||||
"deunicode",
|
||||
"dmp",
|
||||
"ext-sort",
|
||||
"fst",
|
||||
"futures",
|
||||
"fuzzy-matcher",
|
||||
@@ -3824,13 +3854,13 @@ dependencies = [
|
||||
"pharos",
|
||||
"phf",
|
||||
"pin-project-lite",
|
||||
"quick_cache",
|
||||
"quick_cache 0.5.2",
|
||||
"radix_trie",
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
"reblessive",
|
||||
"regex",
|
||||
"revision",
|
||||
"revision 0.11.0",
|
||||
"ring",
|
||||
"rmpv",
|
||||
"roaring",
|
||||
@@ -3847,7 +3877,9 @@ dependencies = [
|
||||
"storekey",
|
||||
"strsim",
|
||||
"subtle",
|
||||
"surrealkv",
|
||||
"sysinfo",
|
||||
"tempfile",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3856,12 +3888,34 @@ dependencies = [
|
||||
"unicase",
|
||||
"url",
|
||||
"uuid",
|
||||
"vart",
|
||||
"vart 0.8.1",
|
||||
"wasm-bindgen-futures",
|
||||
"wasmtimer",
|
||||
"ws_stream_wasm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "surrealkv"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28eec56aca077c245bf5f9e08876fdcce58b5361e77a0b94a92fd47e1990ad4"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"async-channel",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc32fast",
|
||||
"futures",
|
||||
"getrandom 0.2.15",
|
||||
"lru",
|
||||
"parking_lot",
|
||||
"quick_cache 0.6.12",
|
||||
"revision 0.10.0",
|
||||
"tokio",
|
||||
"vart 0.9.2",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -4498,6 +4552,12 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87782b74f898179396e93c0efabb38de0d58d50bbd47eae00c71b3a1144dbbae"
|
||||
|
||||
[[package]]
|
||||
name = "vart"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03dccea250abfe68c00eee55f95af111e041b75bc11796cb83d1c05c5029efd9"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
||||
+1
-2
@@ -14,9 +14,8 @@ jsonwebtoken = "9.3.1"
|
||||
chrono = "0.4.39"
|
||||
utoipa = { version = "5.3.1", features = ["axum_extras"] }
|
||||
utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] }
|
||||
redis = { version = "0.28.2", features = ["tokio-comp"] }
|
||||
lettre = { version = "0.11.12", features = ["tokio1-native-tls"] }
|
||||
surrealdb = { version = "2.2.1", features = ["protocol-http"] }
|
||||
surrealdb = { version = "2.2.1", features = ["kv-mem", "protocol-http"] }
|
||||
thiserror = "2.0.11"
|
||||
anyhow = "1.0.97"
|
||||
rand = "0.9.0"
|
||||
|
||||
+9
-3
@@ -1,4 +1,4 @@
|
||||
use crate::{AppState, Env, RedisClient, SurrealClient};
|
||||
use crate::{AppState, Env, SurrealMemClient, SurrealWsClient};
|
||||
use axum::{
|
||||
http::{header, HeaderValue, Method},
|
||||
Extension, Router,
|
||||
@@ -11,8 +11,14 @@ pub mod v2;
|
||||
|
||||
pub use v1::*;
|
||||
|
||||
pub async fn apps(surrealdb: SurrealClient, redisdb: RedisClient) -> Router {
|
||||
let state = AppState { surrealdb, redisdb };
|
||||
pub async fn apps(
|
||||
surrealdb_ws: SurrealWsClient,
|
||||
surrealdb_mem: SurrealMemClient,
|
||||
) -> Router {
|
||||
let state = AppState {
|
||||
surrealdb_ws,
|
||||
surrealdb_mem,
|
||||
};
|
||||
let env = Env::new();
|
||||
let cors_origins = match env.rust_env.as_str() {
|
||||
"development" => vec!["http://localhost:3000"],
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::{AppState, RedisKeyEnum, UsersSchema};
|
||||
use crate::{AppState, ResourceEnum, UsersSchema};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use redis::Commands;
|
||||
use chrono::{Duration, Utc};
|
||||
|
||||
use super::AuthOtpSchema;
|
||||
|
||||
pub struct AuthRepository<'a> {
|
||||
state: &'a AppState,
|
||||
@@ -11,76 +13,90 @@ impl<'a> AuthRepository<'a> {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
pub fn query_store_user_data(&self, user: UsersSchema) -> Result<String> {
|
||||
let redis_key = format!("{}:{}", RedisKeyEnum::User, user.email.clone());
|
||||
match &self.state.redisdb.get_connection().and_then(|mut conn| {
|
||||
conn.set_ex::<_, String, ()>(
|
||||
&redis_key,
|
||||
serde_json::to_string(&user).unwrap_or_default(),
|
||||
86400,
|
||||
)
|
||||
}) {
|
||||
Ok(_) => Ok("Success store user data".to_string()),
|
||||
Err(err) => Ok(format!("Redis storage failed: {}", err)),
|
||||
pub async fn query_store_user(&self, user: UsersSchema) -> Result<String> {
|
||||
let user_clone = user.clone();
|
||||
let record: Option<UsersSchema> = self
|
||||
.state
|
||||
.surrealdb_mem
|
||||
.update((ResourceEnum::UsersCache.to_string(), user.email))
|
||||
.content(user_clone)
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success store user data".to_string()),
|
||||
None => bail!("Failed store user data"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_get_stored_user(&self, email: String) -> Result<UsersSchema> {
|
||||
let redis_key = format!("{}:{}", RedisKeyEnum::User, email);
|
||||
let mut conn = self.state.redisdb.get_connection()?;
|
||||
let data: Option<String> = conn.get(&redis_key)?;
|
||||
match data {
|
||||
Some(user_json) => {
|
||||
let user: UsersSchema = serde_json::from_str(&user_json)?;
|
||||
Ok(user)
|
||||
}
|
||||
pub async fn query_get_stored_user(&self, email: String) -> Result<UsersSchema> {
|
||||
let user: Option<UsersSchema> = self
|
||||
.state
|
||||
.surrealdb_mem
|
||||
.select((ResourceEnum::UsersCache.to_string(), email))
|
||||
.await?;
|
||||
match user {
|
||||
Some(u) => Ok(u),
|
||||
None => bail!("No stored user data found"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_get_stored_otp(&self, email: String) -> Result<u32> {
|
||||
let redis_key = format!("{}:{}", RedisKeyEnum::Otp, email);
|
||||
let mut conn = match self.state.redisdb.get_connection() {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!("Failed to get Redis connection: {}", e))
|
||||
pub async fn query_delete_stored_user(&self, email: String) -> Result<String> {
|
||||
let record: Option<String> = 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"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_get_stored_otp(&self, email: String) -> Result<u32> {
|
||||
let otp: Option<AuthOtpSchema> = self
|
||||
.state
|
||||
.surrealdb_mem
|
||||
.select((ResourceEnum::OtpCache.to_string(), &email))
|
||||
.await?;
|
||||
match otp {
|
||||
Some(data) => {
|
||||
if Utc::now() > data.expires_at {
|
||||
let _: Option<AuthOtpSchema> = self
|
||||
.state
|
||||
.surrealdb_mem
|
||||
.delete((ResourceEnum::OtpCache.to_string(), &email))
|
||||
.await?;
|
||||
Err(anyhow!("OTP expired"))
|
||||
} else {
|
||||
Ok(data.otp)
|
||||
}
|
||||
}
|
||||
};
|
||||
let data: Option<String> = match conn.get(&redis_key) {
|
||||
Ok(data) => data,
|
||||
Err(e) => return Err(anyhow::anyhow!("Failed to get data from Redis: {}", e)),
|
||||
};
|
||||
match data {
|
||||
Some(otp_str) => match otp_str.parse::<u32>() {
|
||||
Ok(otp) => Ok(otp),
|
||||
Err(e) => Err(anyhow::anyhow!("Failed to parse OTP as u64: {}", e)),
|
||||
},
|
||||
None => Err(anyhow::anyhow!("No stored OTP found")),
|
||||
None => Err(anyhow!("No stored OTP found")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_store_otp(&self, email: String, otp: u32) -> Result<String> {
|
||||
let redis_key: String = format!("{}:{}", RedisKeyEnum::Otp, email);
|
||||
let mut conn = match self.state.redisdb.get_connection() {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => return Err(anyhow!("Failed to get Redis connection: {}", e)),
|
||||
};
|
||||
let otp_str: String = otp.to_string();
|
||||
match conn.set_ex::<_, _, ()>(&redis_key, &otp_str, 300) {
|
||||
Ok(_) => Ok("Success store otp".to_string()),
|
||||
Err(e) => Err(anyhow!("Failed to store OTP in Redis: {}", e)),
|
||||
pub async fn query_store_otp(&self, email: String, otp: u32) -> Result<String> {
|
||||
let expires_at = Utc::now() + Duration::seconds(300); // 5 menit
|
||||
let record: Option<AuthOtpSchema> = self
|
||||
.state
|
||||
.surrealdb_mem
|
||||
.create((ResourceEnum::OtpCache.to_string(), email))
|
||||
.content(AuthOtpSchema { otp, expires_at })
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success store otp".to_string()),
|
||||
None => bail!("Failed store otp"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_delete_stored_otp(&self, email: String) -> Result<String> {
|
||||
let redis_key = format!("{}:{}", RedisKeyEnum::Otp, email);
|
||||
let mut conn = match self.state.redisdb.get_connection() {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => return Err(anyhow!("Failed to get Redis connection: {}", e)),
|
||||
};
|
||||
match conn.del::<_, ()>(&redis_key) {
|
||||
Ok(_) => Ok("Successfully deleted OTP".to_string()),
|
||||
Err(e) => Err(anyhow!("Failed to delete OTP from Redis: {}", e)),
|
||||
pub async fn query_delete_stored_otp(&self, email: String) -> Result<String> {
|
||||
let record: Option<String> = 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthOtpSchema {
|
||||
pub otp: u32,
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -83,7 +83,7 @@ impl AuthService {
|
||||
},
|
||||
};
|
||||
|
||||
if let Err(_) = auth_repo.query_store_user_data(user) {
|
||||
if let Err(_) = auth_repo.query_store_user(user).await {
|
||||
return common_response(StatusCode::BAD_REQUEST, "Failed to store data");
|
||||
}
|
||||
|
||||
@@ -130,20 +130,37 @@ impl AuthService {
|
||||
|
||||
let otp = generate_otp::OtpManager::generate_otp();
|
||||
|
||||
auth_repo
|
||||
match auth_repo
|
||||
.query_store_otp(new_user.email.clone(), otp.clone())
|
||||
.unwrap();
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let message = format!("your otp code is {}", otp);
|
||||
if let Err(err) = send_email(&new_user.email, "OTP Verification", &message) {
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&err.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let message = format!("your otp code is {}", otp);
|
||||
let role_thing = Thing::from((
|
||||
ResourceEnum::Roles.to_string(),
|
||||
Id::String(Uuid::new_v4().to_string()),
|
||||
));
|
||||
|
||||
send_email(&new_user.email.clone(), "OTP Verification", &message).unwrap();
|
||||
|
||||
let role_thing =
|
||||
Thing::from((ResourceEnum::Roles.to_string(), Id::String("".to_string())));
|
||||
let user_thing = Thing::from((
|
||||
ResourceEnum::Users.to_string(),
|
||||
Id::String(Uuid::new_v4().to_string()),
|
||||
));
|
||||
|
||||
match user_repo
|
||||
.query_create_user(UsersSchema {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
id: user_thing,
|
||||
email: new_user.email.clone(),
|
||||
fullname: new_user.fullname.clone(),
|
||||
password: new_user.password.clone(),
|
||||
@@ -160,7 +177,7 @@ impl AuthService {
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => common_response(StatusCode::CREATED, "Registration successful"),
|
||||
Ok(msg) => common_response(StatusCode::CREATED, &msg),
|
||||
Err(err) => {
|
||||
common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string())
|
||||
}
|
||||
@@ -174,7 +191,7 @@ impl AuthService {
|
||||
let repository = AuthRepository::new(state);
|
||||
let otp = generate_otp::OtpManager::generate_otp();
|
||||
let message = format!("Your OTP code is {}", otp);
|
||||
match repository.query_store_otp(payload.email.clone(), otp) {
|
||||
match repository.query_store_otp(payload.email.clone(), otp).await {
|
||||
Ok(_) => match send_email(&payload.email, "OTP Verification", &message) {
|
||||
Ok(_) => common_response(StatusCode::OK, "OTP resent successfully"),
|
||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||
@@ -224,25 +241,26 @@ impl AuthService {
|
||||
let user_repo = UsersRepository::new(state);
|
||||
let auth_repo = AuthRepository::new(state);
|
||||
|
||||
match auth_repo.query_get_stored_otp(payload.email.clone()) {
|
||||
match auth_repo.query_get_stored_otp(payload.email.clone()).await {
|
||||
Ok(stored_otp) => {
|
||||
let user_otp = payload.otp;
|
||||
let is_otp_valid = stored_otp == user_otp;
|
||||
if is_otp_valid {
|
||||
match user_repo
|
||||
.query_active_inactive_user(UsersActiveInactiveSchema {
|
||||
email: payload.email.clone(),
|
||||
is_active: true,
|
||||
})
|
||||
.query_active_inactive_user(
|
||||
payload.email.clone(),
|
||||
UsersActiveInactiveSchema { is_active: true },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
if let Err(e) =
|
||||
auth_repo.query_delete_stored_otp(payload.email.clone())
|
||||
if let Err(e) = auth_repo
|
||||
.query_delete_stored_otp(payload.email.clone())
|
||||
.await
|
||||
{
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("Failed to delete OTP: {}", e),
|
||||
&e.to_string(),
|
||||
);
|
||||
}
|
||||
common_response(StatusCode::OK, "Email verified successfully")
|
||||
@@ -250,7 +268,10 @@ impl AuthService {
|
||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = auth_repo.query_delete_stored_otp(payload.email.clone()) {
|
||||
if let Err(e) = auth_repo
|
||||
.query_delete_stored_otp(payload.email.clone())
|
||||
.await
|
||||
{
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("Failed to delete OTP: {}", e),
|
||||
|
||||
@@ -4,10 +4,12 @@ pub mod auth_controller;
|
||||
pub mod auth_dto;
|
||||
pub mod auth_middleware;
|
||||
pub mod auth_repository;
|
||||
pub mod auth_schema;
|
||||
pub mod auth_service;
|
||||
|
||||
pub use auth_dto::*;
|
||||
pub use auth_repository::*;
|
||||
pub use auth_schema::*;
|
||||
pub use auth_service::*;
|
||||
|
||||
pub fn auth_router() -> Router {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{UsersActiveInactiveSchema, UsersSchema, UsersSetNewPasswordSchema};
|
||||
use crate::{AppState, ResourceEnum};
|
||||
use crate::{AppState, AuthOtpSchema, ResourceEnum};
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
pub struct UsersRepository<'a> {
|
||||
@@ -12,9 +12,28 @@ impl<'a> UsersRepository<'a> {
|
||||
}
|
||||
|
||||
pub async fn query_user_by_email(&self, email: String) -> Result<UsersSchema> {
|
||||
let db = &self.state.surrealdb;
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let sql = format!(
|
||||
"SELECT * FROM {} WHERE email = $email",
|
||||
ResourceEnum::Users.to_string()
|
||||
);
|
||||
let mut response: Vec<UsersSchema> = db
|
||||
.query(sql)
|
||||
.bind(("email", email.clone()))
|
||||
.await?
|
||||
.take(0)?;
|
||||
|
||||
if let Some(user) = response.pop() {
|
||||
Ok(user)
|
||||
} else {
|
||||
bail!("User not found")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_user_by_id(&self, id: String) -> Result<UsersSchema> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let result = db
|
||||
.select((ResourceEnum::Users.to_string(), email.clone()))
|
||||
.select((ResourceEnum::Users.to_string(), id.clone()))
|
||||
.await?;
|
||||
match result {
|
||||
Some(response) => Ok(response),
|
||||
@@ -23,9 +42,9 @@ impl<'a> UsersRepository<'a> {
|
||||
}
|
||||
|
||||
pub async fn query_create_user(&self, data: UsersSchema) -> Result<String> {
|
||||
let db = &self.state.surrealdb;
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record: Option<UsersSchema> = db
|
||||
.create((ResourceEnum::Users.to_string(), &data.id))
|
||||
.create(ResourceEnum::Users.to_string())
|
||||
.content(data)
|
||||
.await?;
|
||||
match record {
|
||||
@@ -34,17 +53,11 @@ impl<'a> UsersRepository<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_active_inactive_user(
|
||||
&self,
|
||||
data: UsersActiveInactiveSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb;
|
||||
let record: Option<UsersActiveInactiveSchema> = db
|
||||
.update((ResourceEnum::Users.to_string(), &data.email))
|
||||
.merge(UsersActiveInactiveSchema {
|
||||
email: data.email.clone(),
|
||||
is_active: data.is_active.clone(),
|
||||
})
|
||||
pub async fn query_update_user(&self, data: UsersSchema) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record: Option<UsersSchema> = db
|
||||
.update((ResourceEnum::Users.to_string(), &data.id.id.to_string()))
|
||||
.merge(data)
|
||||
.await?;
|
||||
match record {
|
||||
Some(_) => Ok("Success update user".into()),
|
||||
@@ -52,11 +65,32 @@ impl<'a> UsersRepository<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_active_inactive_user(
|
||||
&self,
|
||||
email: String,
|
||||
data: UsersActiveInactiveSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let user = self.query_user_by_email(email.clone()).await?;
|
||||
let table = user.id.tb.as_str();
|
||||
let id = user.id.id.to_string();
|
||||
let result: Option<AuthOtpSchema> = db
|
||||
.update((table, id))
|
||||
.merge(UsersActiveInactiveSchema {
|
||||
is_active: data.is_active,
|
||||
})
|
||||
.await?;
|
||||
match result {
|
||||
Some(_) => Ok("Success update user".to_string()),
|
||||
None => bail!("Failed to update user"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_update_password_user(
|
||||
&self,
|
||||
data: UsersSetNewPasswordSchema,
|
||||
) -> Result<String> {
|
||||
let db = &self.state.surrealdb;
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record: Option<UsersSetNewPasswordSchema> = db
|
||||
.update((ResourceEnum::Users.to_string(), &data.email))
|
||||
.merge(UsersSetNewPasswordSchema {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::{Id, Thing};
|
||||
|
||||
use crate::ResourceEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::{
|
||||
sql::{Id, Thing},
|
||||
Uuid,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersSchema {
|
||||
pub id: String,
|
||||
pub role_id: String,
|
||||
pub id: Thing,
|
||||
pub fullname: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
@@ -29,8 +30,10 @@ pub struct UsersSchema {
|
||||
impl Default for UsersSchema {
|
||||
fn default() -> Self {
|
||||
UsersSchema {
|
||||
id: String::new(),
|
||||
role_id: String::new(),
|
||||
id: Thing::from((
|
||||
ResourceEnum::Users.to_string(),
|
||||
Id::String(Uuid::new_v4().to_string()),
|
||||
)),
|
||||
fullname: String::new(),
|
||||
email: String::new(),
|
||||
password: String::new(),
|
||||
@@ -44,11 +47,11 @@ impl Default for UsersSchema {
|
||||
religion: None,
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
is_profile_completed: false,
|
||||
role: Thing::from((
|
||||
ResourceEnum::Roles.to_string(),
|
||||
Id::String("".to_string()),
|
||||
Id::String(Uuid::new_v4().to_string()),
|
||||
)),
|
||||
is_profile_completed: false,
|
||||
created_at: None,
|
||||
updated_at: None,
|
||||
}
|
||||
@@ -63,6 +66,5 @@ pub struct UsersSetNewPasswordSchema {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UsersActiveInactiveSchema {
|
||||
pub email: String,
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::{engine::remote::ws::Client, Surreal};
|
||||
use surrealdb::{
|
||||
engine::{local::Db, remote::ws::Client},
|
||||
Surreal,
|
||||
};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
@@ -37,11 +40,11 @@ pub struct ResponseListSuccessDto<T: Serialize> {
|
||||
pub meta: Option<MetaResponseDto>,
|
||||
}
|
||||
|
||||
pub type SurrealClient = Surreal<Client>;
|
||||
pub type RedisClient = redis::Client;
|
||||
pub type SurrealWsClient = Surreal<Client>;
|
||||
pub type SurrealMemClient = Surreal<Db>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub surrealdb: SurrealClient,
|
||||
pub redisdb: RedisClient,
|
||||
pub surrealdb_ws: SurrealWsClient,
|
||||
pub surrealdb_mem: SurrealMemClient,
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use crate::{redisdb_init, Env, RedisClient};
|
||||
use crate::{surrealdb_init, SurrealClient};
|
||||
use crate::{
|
||||
surrealdb_init_mem, surrealdb_init_ws, Env, SurrealMemClient, SurrealWsClient,
|
||||
};
|
||||
use axum::{serve, Router};
|
||||
use std::{future::Future, net::SocketAddr};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
pub async fn axum_init<F, Fut>(router_fn: F)
|
||||
where
|
||||
F: FnOnce(SurrealClient, RedisClient) -> Fut,
|
||||
F: FnOnce(SurrealWsClient, SurrealMemClient) -> Fut,
|
||||
Fut: Future<Output = Router>,
|
||||
{
|
||||
let env = Env::new();
|
||||
let surrealdb = surrealdb_init().await.expect("Failed surrealdb");
|
||||
let redisdb = redisdb_init().await.expect("Failed redisdb");
|
||||
let router = router_fn(surrealdb, redisdb).await;
|
||||
let surrealdb_ws = surrealdb_init_ws().await.expect("Failed surrealdb ws");
|
||||
let surrealdb_mem = surrealdb_init_mem().await.expect("Failed surrealdb mem");
|
||||
let router = router_fn(surrealdb_ws, surrealdb_mem).await;
|
||||
let port = env.port;
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
let listener = TcpListener::bind(&addr).await.unwrap();
|
||||
|
||||
@@ -3,7 +3,6 @@ pub mod axum;
|
||||
pub mod enviroment;
|
||||
pub mod jsonwebtoken;
|
||||
pub mod lettre;
|
||||
pub mod redisdb;
|
||||
pub mod surrealdb;
|
||||
|
||||
pub use argon::*;
|
||||
@@ -11,5 +10,4 @@ pub use axum::*;
|
||||
pub use enviroment::*;
|
||||
pub use jsonwebtoken::*;
|
||||
pub use lettre::*;
|
||||
pub use redisdb::*;
|
||||
pub use surrealdb::*;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RedisKeyEnum {
|
||||
User,
|
||||
Token,
|
||||
Otp,
|
||||
}
|
||||
|
||||
impl fmt::Display for RedisKeyEnum {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let str = match self {
|
||||
RedisKeyEnum::User => "user",
|
||||
RedisKeyEnum::Token => "token",
|
||||
RedisKeyEnum::Otp => "otp",
|
||||
};
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
use super::Env;
|
||||
use redis::Client;
|
||||
use redis::RedisResult;
|
||||
|
||||
pub mod key;
|
||||
pub use key::*;
|
||||
|
||||
pub async fn redisdb_init() -> RedisResult<Client> {
|
||||
let env = Env::new();
|
||||
let url = format!("redis://{}", env.redisdb_url);
|
||||
let client = Client::open(url)?;
|
||||
Ok(client)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::Env;
|
||||
use crate::SurrealClient;
|
||||
use crate::{SurrealMemClient, SurrealWsClient};
|
||||
use surrealdb::engine::local::Mem;
|
||||
use surrealdb::engine::remote::ws::{Client, Ws};
|
||||
use surrealdb::opt::auth::Root;
|
||||
use surrealdb::{Result, Surreal};
|
||||
@@ -7,7 +8,7 @@ use surrealdb::{Result, Surreal};
|
||||
pub mod resource;
|
||||
pub use resource::*;
|
||||
|
||||
pub async fn surrealdb_init() -> Result<SurrealClient> {
|
||||
pub async fn surrealdb_init_ws() -> Result<SurrealWsClient> {
|
||||
let env = Env::new();
|
||||
let db = Surreal::<Client>::init();
|
||||
db.connect::<Ws>(env.surrealdb_url.clone()).await?;
|
||||
@@ -21,3 +22,12 @@ pub async fn surrealdb_init() -> Result<SurrealClient> {
|
||||
.await?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub async fn surrealdb_init_mem() -> Result<SurrealMemClient> {
|
||||
let env = Env::new();
|
||||
let db = Surreal::new::<Mem>(()).await?;
|
||||
db.use_ns(&env.surrealdb_namespace)
|
||||
.use_db(&env.surrealdb_dbname)
|
||||
.await?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ResourceEnum {
|
||||
OtpCache,
|
||||
UsersCache,
|
||||
GachaItems,
|
||||
GachaClaims,
|
||||
GachaRolls,
|
||||
@@ -15,6 +17,8 @@ impl fmt::Display for ResourceEnum {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let str = match self {
|
||||
ResourceEnum::Users => "app_users",
|
||||
ResourceEnum::UsersCache => "app_users_cache",
|
||||
ResourceEnum::OtpCache => "app_otp_cache",
|
||||
ResourceEnum::Roles => "app_roles",
|
||||
ResourceEnum::Permissions => "app_permissions",
|
||||
ResourceEnum::RolesPermissions => "app_roles_permissions",
|
||||
|
||||
+4
-1
@@ -2,5 +2,8 @@ use najm_course_api::{apps, axum_init};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
axum_init(|db, redis| async { apps(db, redis).await }).await;
|
||||
axum_init(|surrealdb_ws, surrealdb_mem| async {
|
||||
apps(surrealdb_ws, surrealdb_mem).await
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user