Refactor and enhance SurrealDB integration and resource management
- Updated `lib.rs` to selectively expose specific entities and services for better clarity. - Improved SurrealDB client initialization with detailed logging in `surrealdb/mod.rs`. - Enhanced resource definitions in `resource.rs` with additional utility methods for better resource management. - Refactored user data retrieval logic in `auth_middleware/mod.rs` for improved readability and efficiency. - Cleaned up middleware exports in `lib.rs` for clearer API surface. - Added detailed comments and documentation throughout the SurrealDB module for better maintainability. - Updated tests to ensure compatibility with new changes and improved structure. - Introduced new permissions module structure in `imphnen-utils` for future enhancements.
This commit is contained in:
@@ -1,29 +1,83 @@
|
||||
//! Argon2 password hashing utilities.
|
||||
//!
|
||||
//! This module provides secure password hashing and verification using the Argon2 algorithm.
|
||||
//! The hashing parameters are configured for a balance between security and performance.
|
||||
|
||||
use argon2::{
|
||||
Argon2,
|
||||
password_hash::{
|
||||
Error, PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
|
||||
rand_core::OsRng,
|
||||
},
|
||||
Argon2,
|
||||
password_hash::{
|
||||
Error, PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
|
||||
rand_core::OsRng,
|
||||
},
|
||||
};
|
||||
|
||||
// Configuration constants for Argon2 hashing
|
||||
const MEMORY_COST: u32 = 1024; // 1MB
|
||||
const TIME_COST: u32 = 1; // 1 iteration
|
||||
const PARALLELISM: u32 = 1; // 1 thread
|
||||
|
||||
/// Hash a password using Argon2id algorithm.
|
||||
///
|
||||
/// This function generates a cryptographically secure salt and hashes the password
|
||||
/// with predefined parameters optimized for a balance of security and performance.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `password` - The plain text password to hash
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(String)` - The hashed password in PHC string format
|
||||
/// * `Err(Error)` - If hashing fails
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use imphnen_libs::hash_password;
|
||||
///
|
||||
/// let hash = hash_password("my_password")?;
|
||||
/// assert!(hash.starts_with("$argon2id$"));
|
||||
/// # Ok::<(), argon2::password_hash::Error>(())
|
||||
/// ```
|
||||
pub fn hash_password(password: &str) -> Result<String, Error> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::new(
|
||||
argon2::Algorithm::Argon2id,
|
||||
argon2::Version::V0x13,
|
||||
argon2::Params::new(1024, 1, 1, None).unwrap() // 1MB, 1 iteration, 1 thread (faster, less secure)
|
||||
);
|
||||
let password_hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)?
|
||||
.to_string();
|
||||
Ok(password_hash)
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::new(
|
||||
argon2::Algorithm::Argon2id,
|
||||
argon2::Version::V0x13,
|
||||
argon2::Params::new(MEMORY_COST, TIME_COST, PARALLELISM, None).unwrap(),
|
||||
);
|
||||
|
||||
let password_hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)?
|
||||
.to_string();
|
||||
Ok(password_hash)
|
||||
}
|
||||
|
||||
/// Verify a password against its hash.
|
||||
///
|
||||
/// This function checks if the provided password matches the given hash.
|
||||
/// Returns false for both incorrect passwords and invalid hash formats.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `password` - The plain text password to verify
|
||||
/// * `hash` - The hashed password in PHC string format
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(bool)` - true if password matches, false otherwise
|
||||
/// * `Err(Error)` - If hash parsing fails
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use imphnen_libs::{hash_password, verify_password};
|
||||
///
|
||||
/// let hash = hash_password("my_password")?;
|
||||
/// assert!(verify_password("my_password", &hash)?);
|
||||
/// assert!(!verify_password("wrong_password", &hash)?);
|
||||
/// # Ok::<(), argon2::password_hash::Error>(())
|
||||
/// ```
|
||||
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),
|
||||
}
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,80 @@
|
||||
//! Axum server initialization utilities.
|
||||
//!
|
||||
//! This module provides utilities for initializing and running an Axum web server
|
||||
//! with SurrealDB connections for both WebSocket and in-memory databases.
|
||||
|
||||
use crate::{surrealdb_init_mem, surrealdb_init_ws, SurrealMemClient, SurrealWsClient};
|
||||
use axum::{Router, serve};
|
||||
use std::{future::Future, net::SocketAddr};
|
||||
use tokio::net::TcpListener;
|
||||
use crate::enviroment::ENV;
|
||||
|
||||
/// Initialize and start the Axum server with SurrealDB connections.
|
||||
///
|
||||
/// This function sets up both WebSocket and in-memory SurrealDB connections,
|
||||
/// builds the router using the provided function, and starts the server.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `router_fn` - A function that takes SurrealDB clients and returns a Router
|
||||
///
|
||||
/// # Panics
|
||||
/// This function will panic if:
|
||||
/// - SurrealDB initialization fails
|
||||
/// - TCP listener binding fails
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use axum::Router;
|
||||
/// use imphnen_libs::{axum_init, SurrealWsClient, SurrealMemClient};
|
||||
///
|
||||
/// async fn create_router(ws: SurrealWsClient, mem: SurrealMemClient) -> Router {
|
||||
/// Router::new()
|
||||
/// // Add your routes here
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// axum_init(create_router).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn axum_init<F, Fut>(router_fn: F)
|
||||
where
|
||||
F: FnOnce(SurrealWsClient, SurrealMemClient) -> Fut,
|
||||
Fut: Future<Output = Router>,
|
||||
F: FnOnce(SurrealWsClient, SurrealMemClient) -> Fut,
|
||||
Fut: Future<Output = Router>,
|
||||
{
|
||||
let env = &ENV;
|
||||
let env = &ENV;
|
||||
|
||||
let surrealdb_ws = surrealdb_init_ws().await.expect("Failed surrealdb ws");
|
||||
// Initialize SurrealDB connections
|
||||
log::info!("Initializing SurrealDB connections...");
|
||||
let surrealdb_ws = surrealdb_init_ws()
|
||||
.await
|
||||
.expect("Failed to initialize SurrealDB WebSocket connection");
|
||||
|
||||
let surrealdb_mem = surrealdb_init_mem().await.expect("Failed surrealdb mem");
|
||||
let surrealdb_mem = surrealdb_init_mem()
|
||||
.await
|
||||
.expect("Failed to initialize SurrealDB in-memory connection");
|
||||
|
||||
let router = router_fn(surrealdb_ws, surrealdb_mem).await;
|
||||
log::info!("SurrealDB connections established successfully");
|
||||
|
||||
let port = env.port;
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
let listener = TcpListener::bind(&addr).await.unwrap();
|
||||
// Build the router
|
||||
let router = router_fn(surrealdb_ws, surrealdb_mem).await;
|
||||
|
||||
if let Err(err) = serve(listener, router).await {
|
||||
log::error!("Server failed to start: {}", err);
|
||||
}
|
||||
// Start the server
|
||||
let port = env.port;
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
log::info!("Starting server on {}", addr);
|
||||
|
||||
let listener = TcpListener::bind(&addr)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
log::error!("Failed to bind to address {}: {}", addr, e);
|
||||
panic!("Server binding failed: {}", e);
|
||||
});
|
||||
|
||||
log::info!("Server listening on {}", addr);
|
||||
|
||||
if let Err(err) = serve(listener, router).await {
|
||||
log::error!("Server encountered an error: {}", err);
|
||||
panic!("Server failed: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
//! Environment configuration module using once_cell::sync::Lazy for one-time loading.
|
||||
//!
|
||||
//! This module provides centralized configuration management for the application.
|
||||
//! All environment variables are loaded once at startup and cached for performance.
|
||||
|
||||
use std::env;
|
||||
use once_cell::sync::Lazy;
|
||||
// Logging for warnings if .env is missing
|
||||
use log::{warn, info};
|
||||
|
||||
/// Struct holding all environment configuration.
|
||||
///
|
||||
/// This struct contains all configuration values loaded from environment variables.
|
||||
/// Sensitive values are masked in debug output for security.
|
||||
#[derive(Clone)]
|
||||
pub struct Env {
|
||||
pub port: u16,
|
||||
@@ -16,7 +21,6 @@ pub struct Env {
|
||||
pub surrealdb_password: String,
|
||||
pub surrealdb_namespace: String,
|
||||
pub surrealdb_dbname: String,
|
||||
pub surrealdb_url_ws: String,
|
||||
pub smtp_email: String,
|
||||
pub smtp_password: String,
|
||||
pub smtp_name: String,
|
||||
@@ -48,7 +52,6 @@ impl std::fmt::Debug for Env {
|
||||
.field("surrealdb_password", &"***")
|
||||
.field("surrealdb_namespace", &self.surrealdb_namespace)
|
||||
.field("surrealdb_dbname", &self.surrealdb_dbname)
|
||||
.field("surrealdb_url_ws", &self.surrealdb_url_ws)
|
||||
.field("smtp_email", &self.smtp_email)
|
||||
.field("smtp_password", &"***")
|
||||
.field("smtp_name", &self.smtp_name)
|
||||
@@ -69,7 +72,17 @@ impl std::fmt::Debug for Env {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to get env var with warning if not set.
|
||||
/// Get environment variable with warning if not set.
|
||||
///
|
||||
/// This helper function attempts to read an environment variable and logs a warning
|
||||
/// if it's not set, falling back to the provided default value.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `key` - The environment variable name
|
||||
/// * `default` - The default value to use if the variable is not set
|
||||
///
|
||||
/// # Returns
|
||||
/// The environment variable value or the default
|
||||
fn get_env_with_warning(key: &str, default: &str) -> String {
|
||||
match env::var(key) {
|
||||
Ok(val) => val,
|
||||
@@ -80,49 +93,113 @@ fn get_env_with_warning(key: &str, default: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads environment variables from .env and system, only once.
|
||||
pub static ENV: Lazy<Env> = Lazy::new(|| {
|
||||
// Try to load .env file, log a warning if not found, proceed regardless.
|
||||
match dotenvy::dotenv() {
|
||||
Ok(_) => {}
|
||||
Err(dotenvy::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
warn!(".env file not found, falling back to system environment variables");
|
||||
/// Parse environment variable as u16 with fallback.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `key` - The environment variable name
|
||||
/// * `default` - The default numeric value
|
||||
///
|
||||
/// # Returns
|
||||
/// The parsed u16 value or the default if parsing fails
|
||||
fn get_env_u16_with_warning(key: &str, default: u16) -> u16 {
|
||||
match env::var(key) {
|
||||
Ok(val) => val.parse().unwrap_or_else(|_| {
|
||||
warn!("Environment variable '{}' has invalid value '{}'. Using default: {}", key, val, default);
|
||||
default
|
||||
}),
|
||||
Err(_) => {
|
||||
warn!("Environment variable '{}' is not set. Using default: {}", key, default);
|
||||
default
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse environment variable as bool with fallback.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `key` - The environment variable name
|
||||
/// * `default` - The default boolean value
|
||||
///
|
||||
/// # Returns
|
||||
/// The parsed boolean value or the default if parsing fails
|
||||
fn get_env_bool_with_warning(key: &str, default: bool) -> bool {
|
||||
match env::var(key) {
|
||||
Ok(val) => val.parse().unwrap_or_else(|_| {
|
||||
warn!("Environment variable '{}' has invalid value '{}'. Using default: {}", key, val, default);
|
||||
default
|
||||
}),
|
||||
Err(_) => {
|
||||
warn!("Environment variable '{}' is not set. Using default: {}", key, default);
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global environment configuration loaded once at startup.
|
||||
///
|
||||
/// This static variable loads all environment configuration exactly once
|
||||
/// and caches it for the lifetime of the application.
|
||||
pub static ENV: Lazy<Env> = Lazy::new(|| {
|
||||
// Load .env file if present
|
||||
load_dotenv_file();
|
||||
|
||||
let env = Env {
|
||||
port: get_env_with_warning("PORT", "3000")
|
||||
.parse()
|
||||
.unwrap_or(3000),
|
||||
// Server configuration
|
||||
port: get_env_u16_with_warning("PORT", 3000),
|
||||
|
||||
// JWT secrets
|
||||
access_token_secret: get_env_with_warning("ACCESS_TOKEN_SECRET", "default_access_secret"),
|
||||
refresh_token_secret: get_env_with_warning("REFRESH_TOKEN_SECRET", "default_refresh_secret"),
|
||||
|
||||
// SurrealDB configuration
|
||||
surrealdb_url: get_env_with_warning("SURREALDB_URL", "http://localhost:8000"),
|
||||
surrealdb_username: get_env_with_warning("SURREALDB_USERNAME", "root"),
|
||||
surrealdb_password: get_env_with_warning("SURREALDB_PASSWORD", "root"),
|
||||
surrealdb_namespace: get_env_with_warning("SURREALDB_NAMESPACE", "namespace"),
|
||||
surrealdb_dbname: get_env_with_warning("SURREALDB_DBNAME", "database"),
|
||||
|
||||
// SMTP configuration
|
||||
smtp_email: get_env_with_warning("SMTP_EMAIL", "no-reply@example.com"),
|
||||
smtp_password: get_env_with_warning("SMTP_PASSWORD", "default_smtp_password"),
|
||||
smtp_name: get_env_with_warning("SMTP_NAME", "MyApp SMTP"),
|
||||
smtp_host: get_env_with_warning("SMTP_HOST", "smtp.gmail.com"),
|
||||
|
||||
// Redis configuration
|
||||
redisdb_url: get_env_with_warning("REDISDB_URL", "localhost"),
|
||||
|
||||
// Frontend URL
|
||||
fe_url: get_env_with_warning("FE_URL", "http://localhost"),
|
||||
|
||||
// Environment
|
||||
rust_env: get_env_with_warning("RUST_ENV", "development"),
|
||||
|
||||
// MinIO configuration
|
||||
minio_endpoint: get_env_with_warning("MINIO_ENDPOINT", "http://localhost:9000"),
|
||||
minio_bucket_name: get_env_with_warning("MINIO_BUCKET_NAME", "imphnen-uploads"),
|
||||
minio_access_key: get_env_with_warning("MINIO_ACCESS_KEY", "minio_access"),
|
||||
minio_secret_key: get_env_with_warning("MINIO_SECRET_KEY", "minio_secret"),
|
||||
minio_region: get_env_with_warning("MINIO_REGION", "us-east-1"),
|
||||
minio_secure: get_env_with_warning("MINIO_SECURE", "false")
|
||||
.parse()
|
||||
.unwrap_or(false),
|
||||
surrealdb_url_ws: String::new(),
|
||||
minio_secure: get_env_bool_with_warning("MINIO_SECURE", false),
|
||||
|
||||
// Google OAuth 2.1
|
||||
google_client_id: get_env_with_warning("GOOGLE_CLIENT_ID", "default_google_client_id"),
|
||||
google_client_secret: get_env_with_warning("GOOGLE_CLIENT_SECRET", "default_google_client_secret"),
|
||||
google_redirect_url: get_env_with_warning("GOOGLE_REDIRECT_URL", "http://localhost:8000/api/v1/auth/google/callback"),
|
||||
};
|
||||
info!("Loaded environment configuration: {:?}", env);
|
||||
|
||||
info!("Environment configuration loaded successfully");
|
||||
env
|
||||
});
|
||||
|
||||
/// Load .env file if present, with appropriate logging.
|
||||
fn load_dotenv_file() {
|
||||
match dotenvy::dotenv() {
|
||||
Ok(path) => info!("Loaded environment file: {:?}", path),
|
||||
Err(dotenvy::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
warn!(".env file not found, falling back to system environment variables");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to load .env file: {}. Falling back to system environment variables", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,161 @@
|
||||
//! JWT token encoding and decoding utilities.
|
||||
//!
|
||||
//! This module provides functions for creating and validating JWT tokens
|
||||
//! for authentication purposes, including access tokens, refresh tokens,
|
||||
//! and password reset tokens.
|
||||
|
||||
use crate::enviroment::ENV;
|
||||
use axum::http::StatusCode;
|
||||
use chrono::{Duration, TimeDelta, Utc};
|
||||
use jsonwebtoken::{
|
||||
DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode,
|
||||
DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// JWT claims structure containing token payload information.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub exp: usize,
|
||||
pub iat: usize,
|
||||
pub sub: String,
|
||||
/// Expiration timestamp
|
||||
pub exp: usize,
|
||||
/// Issued at timestamp
|
||||
pub iat: usize,
|
||||
/// Subject (usually user identifier)
|
||||
pub sub: String,
|
||||
/// User ID
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
// Token configuration constants
|
||||
const ACCESS_TOKEN_DURATION_MINUTES: i64 = 15;
|
||||
const REFRESH_TOKEN_DURATION_DAYS: i64 = 1;
|
||||
const RESET_TOKEN_DURATION_MINUTES: i64 = 5;
|
||||
|
||||
// Lazy-initialized headers and keys for performance
|
||||
static ACCESS_HEADER: once_cell::sync::Lazy<Header> = once_cell::sync::Lazy::new(Header::default);
|
||||
static ACCESS_KEY: once_cell::sync::Lazy<EncodingKey> = once_cell::sync::Lazy::new(|| {
|
||||
EncodingKey::from_secret(ENV.access_token_secret.as_ref())
|
||||
EncodingKey::from_secret(ENV.access_token_secret.as_ref())
|
||||
});
|
||||
pub fn encode_access_token(sub: String, user_id: String) -> Result<String, StatusCode> {
|
||||
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, user_id };
|
||||
encode(
|
||||
&ACCESS_HEADER,
|
||||
&claim,
|
||||
&ACCESS_KEY,
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub fn encode_reset_password_token(sub: String, user_id: String) -> Result<String, StatusCode> {
|
||||
let env = &ENV;
|
||||
let secret: String = env.access_token_secret.clone();
|
||||
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, user_id };
|
||||
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;
|
||||
let secret: String = env.access_token_secret.clone();
|
||||
let result: Result<TokenData<Claims>, StatusCode> = decode(
|
||||
jwt_token,
|
||||
&DecodingKey::from_secret(secret.as_ref()),
|
||||
&Validation::default(),
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
|
||||
result
|
||||
}
|
||||
|
||||
static REFRESH_HEADER: once_cell::sync::Lazy<Header> = once_cell::sync::Lazy::new(Header::default);
|
||||
static REFRESH_KEY: once_cell::sync::Lazy<EncodingKey> = once_cell::sync::Lazy::new(|| {
|
||||
EncodingKey::from_secret(ENV.refresh_token_secret.as_ref())
|
||||
EncodingKey::from_secret(ENV.refresh_token_secret.as_ref())
|
||||
});
|
||||
|
||||
/// Create JWT claims with specified expiration duration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `sub` - Subject identifier
|
||||
/// * `user_id` - User ID
|
||||
/// * `duration` - Token validity duration
|
||||
///
|
||||
/// # Returns
|
||||
/// JWT claims structure
|
||||
fn create_claims(sub: String, user_id: String, duration: TimeDelta) -> Claims {
|
||||
let now = Utc::now();
|
||||
let exp: usize = (now + duration).timestamp() as usize;
|
||||
let iat: usize = now.timestamp() as usize;
|
||||
Claims { iat, exp, sub, user_id }
|
||||
}
|
||||
|
||||
/// Encode a JWT token with the specified header and key.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `claims` - JWT claims to encode
|
||||
/// * `header` - JWT header
|
||||
/// * `key` - Encoding key
|
||||
///
|
||||
/// # Returns
|
||||
/// Encoded JWT token or internal server error status
|
||||
fn encode_token(claims: &Claims, header: &Header, key: &EncodingKey) -> Result<String, StatusCode> {
|
||||
encode(header, claims, key).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
/// Decode a JWT token with the specified secret.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `token` - JWT token string
|
||||
/// * `secret` - Secret key for decoding
|
||||
///
|
||||
/// # Returns
|
||||
/// Decoded token data or internal server error status
|
||||
fn decode_token(token: &str, secret: &str) -> Result<TokenData<Claims>, StatusCode> {
|
||||
decode(
|
||||
token,
|
||||
&DecodingKey::from_secret(secret.as_ref()),
|
||||
&Validation::default(),
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
/// Encode an access token with 15-minute expiration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `sub` - Subject identifier
|
||||
/// * `user_id` - User ID
|
||||
///
|
||||
/// # Returns
|
||||
/// Encoded JWT access token
|
||||
pub fn encode_access_token(sub: String, user_id: String) -> Result<String, StatusCode> {
|
||||
let claims = create_claims(sub, user_id, Duration::minutes(ACCESS_TOKEN_DURATION_MINUTES));
|
||||
encode_token(&claims, &ACCESS_HEADER, &ACCESS_KEY)
|
||||
}
|
||||
|
||||
/// Encode a refresh token with 1-day expiration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `sub` - Subject identifier
|
||||
/// * `user_id` - User ID
|
||||
///
|
||||
/// # Returns
|
||||
/// Encoded JWT refresh token
|
||||
pub fn encode_refresh_token(sub: String, user_id: String) -> Result<String, StatusCode> {
|
||||
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, user_id };
|
||||
encode(
|
||||
&REFRESH_HEADER,
|
||||
&claim,
|
||||
&REFRESH_KEY,
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
let claims = create_claims(sub, user_id, Duration::days(REFRESH_TOKEN_DURATION_DAYS));
|
||||
encode_token(&claims, &REFRESH_HEADER, &REFRESH_KEY)
|
||||
}
|
||||
|
||||
pub fn decode_refresh_token(
|
||||
jwt_token: &str,
|
||||
) -> Result<TokenData<Claims>, StatusCode> {
|
||||
let env = &ENV;
|
||||
let secret: String = env.refresh_token_secret.clone();
|
||||
let result: Result<TokenData<Claims>, StatusCode> = decode(
|
||||
jwt_token,
|
||||
&DecodingKey::from_secret(secret.as_ref()),
|
||||
&Validation::default(),
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
|
||||
result // Explicitly return result
|
||||
/// Encode a password reset token with 5-minute expiration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `sub` - Subject identifier
|
||||
/// * `user_id` - User ID
|
||||
///
|
||||
/// # Returns
|
||||
/// Encoded JWT reset token
|
||||
pub fn encode_reset_password_token(sub: String, user_id: String) -> Result<String, StatusCode> {
|
||||
let claims = create_claims(sub, user_id, Duration::minutes(RESET_TOKEN_DURATION_MINUTES));
|
||||
let key = EncodingKey::from_secret(ENV.access_token_secret.as_ref());
|
||||
encode_token(&claims, &Header::default(), &key)
|
||||
}
|
||||
|
||||
/// Decode an access token.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `jwt_token` - JWT token string
|
||||
///
|
||||
/// # Returns
|
||||
/// Decoded token data containing claims
|
||||
pub fn decode_access_token(jwt_token: &str) -> Result<TokenData<Claims>, StatusCode> {
|
||||
decode_token(jwt_token, &ENV.access_token_secret)
|
||||
}
|
||||
|
||||
/// Decode a refresh token.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `jwt_token` - JWT token string
|
||||
///
|
||||
/// # Returns
|
||||
/// Decoded token data containing claims
|
||||
pub fn decode_refresh_token(jwt_token: &str) -> Result<TokenData<Claims>, StatusCode> {
|
||||
decode_token(jwt_token, &ENV.refresh_token_secret)
|
||||
}
|
||||
|
||||
/// Generate a simple JWT access token using user_id as both sub and user_id.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `user_id` - User identifier
|
||||
///
|
||||
/// # Returns
|
||||
/// Encoded JWT access token
|
||||
pub fn generate_jwt(user_id: &str) -> Result<String, StatusCode> {
|
||||
encode_access_token(user_id.to_string(), user_id.to_string())
|
||||
}
|
||||
|
||||
+113
-28
@@ -1,35 +1,120 @@
|
||||
//! Email sending utilities using Lettre SMTP client.
|
||||
//!
|
||||
//! This module provides functionality for sending emails through SMTP
|
||||
//! with proper error handling and logging.
|
||||
|
||||
use crate::enviroment::ENV;
|
||||
use lettre::message::Mailbox;
|
||||
use lettre::transport::smtp::authentication::Credentials;
|
||||
use lettre::{Message, SmtpTransport, Transport};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
pub fn send_email(
|
||||
to: &str,
|
||||
subject: &str,
|
||||
body: &str,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let env = &ENV;
|
||||
let host = env.smtp_host.clone();
|
||||
let sender_email = env.smtp_email.clone();
|
||||
let sender_name = env.smtp_name.clone();
|
||||
let sender_password = env.smtp_password.clone();
|
||||
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(_) => Ok(()),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
/// Custom error type for email operations.
|
||||
#[derive(Debug)]
|
||||
pub enum EmailError {
|
||||
/// SMTP configuration error
|
||||
SmtpConfig(String),
|
||||
/// Message building error
|
||||
MessageBuild(String),
|
||||
/// SMTP transport error
|
||||
Transport(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for EmailError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
EmailError::SmtpConfig(msg) => write!(f, "SMTP configuration error: {}", msg),
|
||||
EmailError::MessageBuild(msg) => write!(f, "Message building error: {}", msg),
|
||||
EmailError::Transport(msg) => write!(f, "SMTP transport error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EmailError {}
|
||||
|
||||
/// Send an email using the configured SMTP settings.
|
||||
///
|
||||
/// This function constructs and sends an email using the SMTP configuration
|
||||
/// from environment variables. It handles sender name normalization and
|
||||
/// proper error reporting.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `to` - Recipient email address
|
||||
/// * `subject` - Email subject line
|
||||
/// * `body` - Email body content (plain text)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(())` - Email sent successfully
|
||||
/// * `Err(EmailError)` - Email sending failed
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use imphnen_libs::send_email;
|
||||
///
|
||||
/// send_email("user@example.com", "Welcome!", "Hello, welcome to our service!")?;
|
||||
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn send_email(to: &str, subject: &str, body: &str) -> Result<(), Box<dyn Error>> {
|
||||
let env = &ENV;
|
||||
|
||||
// Build the email message
|
||||
let message = build_email_message(to, subject, body, env)?;
|
||||
|
||||
// Create SMTP transport
|
||||
let mailer = create_smtp_transport(env)?;
|
||||
|
||||
// Send the email
|
||||
mailer.send(&message).map_err(|e| {
|
||||
log::error!("Failed to send email to {}: {}", to, e);
|
||||
Box::new(EmailError::Transport(e.to_string())) as Box<dyn Error>
|
||||
})?;
|
||||
|
||||
log::info!("Email sent successfully to: {}", to);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build an email message with proper sender and recipient configuration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `to` - Recipient email address
|
||||
/// * `subject` - Email subject
|
||||
/// * `body` - Email body
|
||||
/// * `env` - Environment configuration
|
||||
///
|
||||
/// # Returns
|
||||
/// Email message or error
|
||||
fn build_email_message(
|
||||
to: &str,
|
||||
subject: &str,
|
||||
body: &str,
|
||||
env: &crate::enviroment::Env,
|
||||
) -> Result<Message, Box<dyn Error>> {
|
||||
let sender_name = env.smtp_name.replace("-", " "); // Normalize sender name
|
||||
|
||||
Message::builder()
|
||||
.from(Mailbox::new(Some(sender_name), env.smtp_email.parse()?))
|
||||
.to(to.parse()?)
|
||||
.subject(subject)
|
||||
.body(body.to_string())
|
||||
.map_err(|e| Box::new(EmailError::MessageBuild(e.to_string())) as Box<dyn Error>)
|
||||
}
|
||||
|
||||
/// Create SMTP transport with authentication.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `env` - Environment configuration
|
||||
///
|
||||
/// # Returns
|
||||
/// Configured SMTP transport or error
|
||||
fn create_smtp_transport(env: &crate::enviroment::Env) -> Result<SmtpTransport, Box<dyn Error>> {
|
||||
let credentials = Credentials::new(
|
||||
env.smtp_email.clone(),
|
||||
env.smtp_password.replace("-", " "), // Normalize password
|
||||
);
|
||||
|
||||
Ok(SmtpTransport::relay(&env.smtp_host)?
|
||||
.credentials(credentials)
|
||||
.build())
|
||||
|
||||
}
|
||||
|
||||
+29
-9
@@ -9,15 +9,35 @@ pub mod minio;
|
||||
pub mod services;
|
||||
pub mod surrealdb;
|
||||
|
||||
pub use argon::*;
|
||||
pub use axum::*;
|
||||
pub use enviroment::*;
|
||||
pub use imphnen_entities::*;
|
||||
pub use jsonwebtoken::*;
|
||||
pub use lettre::*;
|
||||
pub use minio::*;
|
||||
pub use services::*;
|
||||
pub use surrealdb::*;
|
||||
pub use argon::{hash_password, verify_password};
|
||||
pub use axum::axum_init;
|
||||
pub use enviroment::{ENV, Env};
|
||||
pub use imphnen_entities::{
|
||||
MessageResponseDto,
|
||||
MetaRequestDto,
|
||||
MetaResponseDto,
|
||||
ResponseSuccessDto,
|
||||
ResponseListSuccessDto,
|
||||
CountResult,
|
||||
Error,
|
||||
ExperienceDto,
|
||||
EducationDto,
|
||||
UsersDetailQueryDto,
|
||||
PermissionsEnum,
|
||||
PermissionsItemDto,
|
||||
PermissionsQueryDto,
|
||||
};
|
||||
pub use jsonwebtoken::{
|
||||
Claims, encode_access_token, encode_refresh_token, decode_access_token,
|
||||
decode_refresh_token, encode_reset_password_token, generate_jwt
|
||||
};
|
||||
pub use lettre::send_email;
|
||||
pub use minio::*; // Minio has many useful exports, keeping for now
|
||||
pub use services::{UserLookupService, AuthRepositoryTrait};
|
||||
pub use surrealdb::{
|
||||
surrealdb_init_ws, surrealdb_init_mem, SurrealWsClient, SurrealMemClient,
|
||||
ResourceEnum
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
|
||||
@@ -1,35 +1,105 @@
|
||||
//! SurrealDB client initialization and configuration.
|
||||
//!
|
||||
//! This module provides utilities for initializing SurrealDB connections
|
||||
//! for both WebSocket and in-memory databases, along with resource definitions.
|
||||
|
||||
use crate::enviroment::ENV;
|
||||
use surrealdb::engine::any;
|
||||
use surrealdb::engine::local::{Db, Mem};
|
||||
use surrealdb::opt::auth::Root;
|
||||
use surrealdb::{Result, Surreal};
|
||||
|
||||
/// Type alias for SurrealDB WebSocket client.
|
||||
pub type SurrealWsClient = Surreal<any::Any>;
|
||||
|
||||
/// Type alias for SurrealDB in-memory client.
|
||||
pub type SurrealMemClient = Surreal<Db>;
|
||||
|
||||
pub mod resource;
|
||||
pub use resource::*;
|
||||
|
||||
/// Initialize a SurrealDB WebSocket client connection.
|
||||
///
|
||||
/// This function creates a connection to a SurrealDB instance via WebSocket,
|
||||
/// authenticates with root credentials, and sets the namespace and database.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(SurrealWsClient)` - Successfully initialized WebSocket client
|
||||
/// * `Err(surrealdb::Error)` - Connection, authentication, or configuration failed
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use imphnen_libs::surrealdb_init_ws;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = surrealdb_init_ws().await?;
|
||||
/// // Use client for database operations
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn surrealdb_init_ws() -> Result<Surreal<any::Any>> {
|
||||
let env = &ENV;
|
||||
let db = any::connect(&env.surrealdb_url).await?;
|
||||
let env = &ENV;
|
||||
|
||||
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)
|
||||
log::info!("Initializing SurrealDB WebSocket connection to: {}", env.surrealdb_url);
|
||||
|
||||
// Connect to SurrealDB
|
||||
let db = any::connect(&env.surrealdb_url).await?;
|
||||
log::debug!("WebSocket connection established");
|
||||
|
||||
// Authenticate
|
||||
db.signin(Root {
|
||||
username: &env.surrealdb_username,
|
||||
password: &env.surrealdb_password,
|
||||
})
|
||||
.await?;
|
||||
log::debug!("Authentication successful");
|
||||
|
||||
// Configure namespace and database
|
||||
db.use_ns(&env.surrealdb_namespace)
|
||||
.use_db(&env.surrealdb_dbname)
|
||||
.await?;
|
||||
log::info!("SurrealDB WebSocket client initialized with namespace '{}' and database '{}'",
|
||||
env.surrealdb_namespace, env.surrealdb_dbname);
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
/// Initialize a SurrealDB in-memory client.
|
||||
///
|
||||
/// This function creates an in-memory SurrealDB instance and configures
|
||||
/// the namespace and database for use.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(SurrealMemClient)` - Successfully initialized in-memory client
|
||||
/// * `Err(surrealdb::Error)` - Initialization or configuration failed
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use imphnen_libs::surrealdb_init_mem;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = surrealdb_init_mem().await?;
|
||||
/// // Use client for in-memory database operations
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn surrealdb_init_mem() -> Result<SurrealMemClient> {
|
||||
let env = &ENV;
|
||||
let db = Surreal::new::<Mem>(()).await?;
|
||||
db.use_ns(&env.surrealdb_namespace)
|
||||
.use_db(&env.surrealdb_dbname)
|
||||
.await?;
|
||||
Ok(db)
|
||||
let env = &ENV;
|
||||
|
||||
log::info!("Initializing SurrealDB in-memory database");
|
||||
|
||||
// Create in-memory database
|
||||
let db = Surreal::new::<Mem>(()).await?;
|
||||
log::debug!("In-memory database created");
|
||||
|
||||
// Configure namespace and database
|
||||
db.use_ns(&env.surrealdb_namespace)
|
||||
.use_db(&env.surrealdb_dbname)
|
||||
.await?;
|
||||
log::info!("SurrealDB in-memory client initialized with namespace '{}' and database '{}'",
|
||||
env.surrealdb_namespace, env.surrealdb_dbname);
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
@@ -1,45 +1,138 @@
|
||||
//! SurrealDB resource definitions.
|
||||
//!
|
||||
//! This module defines the database table names used throughout the application.
|
||||
//! Each resource corresponds to a SurrealDB table with the "app_" prefix.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Database resource enumeration.
|
||||
///
|
||||
/// Represents all database tables used in the application.
|
||||
/// Each variant corresponds to a SurrealDB table name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ResourceEnum {
|
||||
OtpCache,
|
||||
UsersCache,
|
||||
GachaItems,
|
||||
GachaClaims,
|
||||
GachaRolls,
|
||||
GachaCredits,
|
||||
Users,
|
||||
Roles,
|
||||
Permissions,
|
||||
RolesPermissions,
|
||||
Events,
|
||||
Testimonials,
|
||||
Mentors,
|
||||
Teams,
|
||||
TeamMembers,
|
||||
TeamInvitations,
|
||||
/// OTP cache table for temporary authentication codes
|
||||
OtpCache,
|
||||
/// User cache table for user session data
|
||||
UsersCache,
|
||||
/// Gacha items table
|
||||
GachaItems,
|
||||
/// Gacha claims table for user item claims
|
||||
GachaClaims,
|
||||
/// Gacha rolls table for user roll history
|
||||
GachaRolls,
|
||||
/// Gacha credits table for user currency
|
||||
GachaCredits,
|
||||
/// Users table for user accounts
|
||||
Users,
|
||||
/// Roles table for user roles
|
||||
Roles,
|
||||
/// Permissions table for system permissions
|
||||
Permissions,
|
||||
/// Role-permission relationships table
|
||||
RolesPermissions,
|
||||
/// Events table for application events
|
||||
Events,
|
||||
/// Testimonials table for user testimonials
|
||||
Testimonials,
|
||||
/// Mentors table for mentor profiles
|
||||
Mentors,
|
||||
/// Teams table for user teams
|
||||
Teams,
|
||||
/// Team members table for team membership
|
||||
TeamMembers,
|
||||
/// Team invitations table for pending invitations
|
||||
TeamInvitations,
|
||||
}
|
||||
|
||||
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",
|
||||
ResourceEnum::GachaCredits => "app_gacha_credits",
|
||||
ResourceEnum::Events => "app_events",
|
||||
ResourceEnum::Testimonials => "app_testimonials",
|
||||
ResourceEnum::Mentors => "app_mentors",
|
||||
ResourceEnum::Teams => "app_teams",
|
||||
ResourceEnum::TeamMembers => "app_team_members",
|
||||
ResourceEnum::TeamInvitations => "app_team_invitations",
|
||||
};
|
||||
write!(f, "{str}")
|
||||
}
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let table_name = 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",
|
||||
ResourceEnum::GachaCredits => "app_gacha_credits",
|
||||
ResourceEnum::Events => "app_events",
|
||||
ResourceEnum::Testimonials => "app_testimonials",
|
||||
ResourceEnum::Mentors => "app_mentors",
|
||||
ResourceEnum::Teams => "app_teams",
|
||||
ResourceEnum::TeamMembers => "app_team_members",
|
||||
ResourceEnum::TeamInvitations => "app_team_invitations",
|
||||
};
|
||||
write!(f, "{}", table_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceEnum {
|
||||
/// Get the table name as a string slice.
|
||||
///
|
||||
/// # Returns
|
||||
/// The SurrealDB table name for this resource
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use imphnen_libs::ResourceEnum;
|
||||
///
|
||||
/// let users = ResourceEnum::Users;
|
||||
/// assert_eq!(users.as_str(), "app_users");
|
||||
/// ```
|
||||
pub fn as_str(&self) -> &'static 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",
|
||||
ResourceEnum::GachaCredits => "app_gacha_credits",
|
||||
ResourceEnum::Events => "app_events",
|
||||
ResourceEnum::Testimonials => "app_testimonials",
|
||||
ResourceEnum::Mentors => "app_mentors",
|
||||
ResourceEnum::Teams => "app_teams",
|
||||
ResourceEnum::TeamMembers => "app_team_members",
|
||||
ResourceEnum::TeamInvitations => "app_team_invitations",
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this resource is cache-related.
|
||||
///
|
||||
/// # Returns
|
||||
/// true if the resource is used for caching, false otherwise
|
||||
pub fn is_cache(&self) -> bool {
|
||||
matches!(self, ResourceEnum::OtpCache | ResourceEnum::UsersCache)
|
||||
}
|
||||
|
||||
/// Check if this resource is gacha-related.
|
||||
///
|
||||
/// # Returns
|
||||
/// true if the resource is part of the gacha system, false otherwise
|
||||
pub fn is_gacha(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ResourceEnum::GachaItems
|
||||
| ResourceEnum::GachaClaims
|
||||
| ResourceEnum::GachaRolls
|
||||
| ResourceEnum::GachaCredits
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if this resource is user-related.
|
||||
///
|
||||
/// # Returns
|
||||
/// true if the resource contains user data, false otherwise
|
||||
pub fn is_user_related(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ResourceEnum::Users | ResourceEnum::UsersCache | ResourceEnum::Mentors
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user