chore: re-map project structure to using multi workspace paradigm

This commit is contained in:
Maulana Sodiqin
2025-04-06 13:25:44 +07:00
parent de9c5a6dc1
commit b4bf78f0d6
87 changed files with 325 additions and 192 deletions
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "imphnen-libs"
version = "0.1.0"
edition = "2024"
[dependencies]
imphnen-entities = { path = "../imphnen-entities" }
argon2.workspace = true
axum.workspace = true
tokio.workspace = true
chrono.workspace = true
jsonwebtoken.workspace = true
serde.workspace = true
lettre.workspace = true
surrealdb = { workspace = true, features = ["kv-mem"] }
+25
View File
@@ -0,0 +1,25 @@
use argon2::{
password_hash::{
rand_core::OsRng, Error, PasswordHash, PasswordHasher, PasswordVerifier,
SaltString,
},
Argon2,
};
pub fn hash_password(password: &str) -> Result<String, Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_bytes(), &salt)?
.to_string();
Ok(password_hash)
}
pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
let parsed_hash = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}
+25
View File
@@ -0,0 +1,25 @@
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(SurrealWsClient, SurrealMemClient) -> Fut,
Fut: Future<Output = Router>,
{
let env = Env::new();
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();
println!("Listening on http://{}", addr);
match serve(listener, router).await {
Ok(_) => println!("Server stopped gracefully."),
Err(err) => println!("Server encountered an error: {}", err),
}
}
+67
View File
@@ -0,0 +1,67 @@
use std::env;
pub struct Env {
pub port: u16,
pub access_token_secret: String,
pub refresh_token_secret: String,
pub surrealdb_url: String,
pub surrealdb_username: String,
pub surrealdb_password: String,
pub surrealdb_namespace: String,
pub surrealdb_dbname: String,
pub smtp_email: String,
pub smtp_password: String,
pub smtp_name: String,
pub smtp_host: String,
pub redisdb_url: String,
pub fe_url: String,
pub rust_env: String,
pub minio_endpoint: String,
pub minio_bucket_name: String,
pub minio_access_key: String,
pub minio_secret_key: String,
}
impl Env {
pub fn new() -> Self {
Self {
port: env::var("PORT")
.unwrap_or_else(|_| "3000".to_string())
.parse()
.unwrap_or(3000),
access_token_secret: env::var("ACCESS_TOKEN_SECRET")
.unwrap_or_else(|_| "default_access_secret".to_string()),
refresh_token_secret: env::var("REFRESH_TOKEN_SECRET")
.unwrap_or_else(|_| "default_refresh_secret".to_string()),
surrealdb_url: env::var("SURREALDB_URL")
.unwrap_or_else(|_| "http://localhost:8000".to_string()),
surrealdb_username: env::var("SURREALDB_USERNAME")
.unwrap_or_else(|_| "root".to_string()),
surrealdb_password: env::var("SURREALDB_PASSWORD")
.unwrap_or_else(|_| "password".to_string()),
surrealdb_namespace: env::var("SURREALDB_NAMESPACE")
.unwrap_or_else(|_| "namespace".to_string()),
surrealdb_dbname: env::var("SURREALDB_DBNAME")
.unwrap_or_else(|_| "database".to_string()),
smtp_email: env::var("SMTP_EMAIL")
.unwrap_or_else(|_| "no-reply@example.com".to_string()),
smtp_password: env::var("SMTP_PASSWORD")
.unwrap_or_else(|_| "default_smtp_password".to_string()),
smtp_name: env::var("SMTP_NAME").unwrap_or_else(|_| "MyApp SMTP".to_string()),
smtp_host: env::var("SMTP_HOST")
.unwrap_or_else(|_| "smtp.gmail.com".to_string()),
redisdb_url: env::var("REDISDB_URL")
.unwrap_or_else(|_| "localhost".to_string()),
fe_url: env::var("FE_URL").unwrap_or_else(|_| "http://localhost".to_string()),
rust_env: env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()),
minio_endpoint: env::var("MINIO_ENDPOINT")
.unwrap_or_else(|_| "http://localhost:9000".to_string()),
minio_bucket_name: env::var("MINIO_BUCKET_NAME")
.unwrap_or_else(|_| "default_bucket".to_string()),
minio_access_key: env::var("MINIO_ACCESS_KEY")
.unwrap_or_else(|_| "minio_access".to_string()),
minio_secret_key: env::var("MINIO_SECRET_KEY")
.unwrap_or_else(|_| "minio_secret".to_string()),
}
}
}
+90
View File
@@ -0,0 +1,90 @@
use super::Env;
use axum::http::StatusCode;
use chrono::{Duration, TimeDelta, Utc};
use jsonwebtoken::{
decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
pub exp: usize,
pub iat: usize,
pub sub: String,
}
pub fn encode_access_token(sub: String) -> Result<String, StatusCode> {
let env = Env::new();
let secret: String = env.access_token_secret;
let now = Utc::now();
let expire: TimeDelta = Duration::minutes(15);
let exp: usize = (now + expire).timestamp() as usize;
let iat: usize = now.timestamp() as usize;
let claim = Claims { iat, exp, sub };
encode(
&Header::default(),
&claim,
&EncodingKey::from_secret(secret.as_ref()),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn encode_reset_password_token(sub: String) -> Result<String, StatusCode> {
let env = Env::new();
let secret: String = env.access_token_secret;
let now = Utc::now();
let expire: TimeDelta = Duration::minutes(5);
let exp: usize = (now + expire).timestamp() as usize;
let iat: usize = now.timestamp() as usize;
let claim = Claims { iat, exp, sub };
encode(
&Header::default(),
&claim,
&EncodingKey::from_secret(secret.as_ref()),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn decode_access_token(
jwt_token: &str,
) -> Result<TokenData<Claims>, StatusCode> {
let env = Env::new();
let secret: String = env.access_token_secret;
let result: Result<TokenData<Claims>, StatusCode> = decode(
&jwt_token,
&DecodingKey::from_secret(secret.as_ref()),
&Validation::default(),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
result
}
pub fn encode_refresh_token(sub: String) -> Result<String, StatusCode> {
let env = Env::new();
let secret: String = env.refresh_token_secret;
let now = Utc::now();
let expire: TimeDelta = Duration::days(1);
let exp: usize = (now + expire).timestamp() as usize;
let iat: usize = now.timestamp() as usize;
let claim = Claims { iat, exp, sub };
encode(
&Header::default(),
&claim,
&EncodingKey::from_secret(secret.as_ref()),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn decode_refresh_token(
jwt_token: &str,
) -> Result<TokenData<Claims>, StatusCode> {
let env = Env::new();
let secret: String = env.refresh_token_secret;
let result: Result<TokenData<Claims>, StatusCode> = decode(
&jwt_token,
&DecodingKey::from_secret(secret.as_ref()),
&Validation::default(),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
result
}
+41
View File
@@ -0,0 +1,41 @@
use super::Env;
use lettre::message::Mailbox;
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
use std::error::Error;
pub fn send_email(
to: &str,
subject: &str,
body: &str,
) -> Result<(), Box<dyn Error>> {
let env = Env::new();
let host = env.smtp_host;
let sender_email = env.smtp_email;
let sender_name = env.smtp_name;
let sender_password = env.smtp_password;
let recipient_email = to;
let email = Message::builder()
.from(Mailbox::new(
Some(sender_name.replace("-", " ")),
sender_email.parse()?,
))
.to(recipient_email.parse()?)
.subject(subject)
.body(body.to_string())?;
let smtp_credentials =
Credentials::new(sender_email, sender_password.replace("-", " "));
let mailer = SmtpTransport::relay(&host)?
.credentials(smtp_credentials)
.build();
match mailer.send(&email) {
Ok(_) => {
println!("Email sent successfully to {}", to);
Ok(())
}
Err(e) => {
println!("Failed to send email: {}", e);
Err(Box::new(e))
}
}
}
+16
View File
@@ -0,0 +1,16 @@
use imphnen_entities::*;
pub mod argon;
pub mod axum;
pub mod enviroment;
pub mod jsonwebtoken;
pub mod lettre;
pub mod surrealdb;
pub use argon::*;
pub use axum::*;
pub use enviroment::*;
pub use imphnen_entities::*;
pub use jsonwebtoken::*;
pub use lettre::*;
pub use surrealdb::*;
+33
View File
@@ -0,0 +1,33 @@
use super::Env;
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};
pub mod resource;
pub use resource::*;
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?;
db.signin(Root {
username: &env.surrealdb_username,
password: &env.surrealdb_password,
})
.await?;
db.use_ns(env.surrealdb_namespace.clone())
.use_db(env.surrealdb_dbname.clone())
.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)
}
+31
View File
@@ -0,0 +1,31 @@
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResourceEnum {
OtpCache,
UsersCache,
GachaItems,
GachaClaims,
GachaRolls,
Users,
Roles,
Permissions,
RolesPermissions,
}
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",
ResourceEnum::GachaItems => "app_gacha_items",
ResourceEnum::GachaClaims => "app_gacha_claims",
ResourceEnum::GachaRolls => "app_gacha_rolls",
};
write!(f, "{}", str)
}
}