chore: re-map project structure to using multi workspace paradigm
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
use surrealdb::{engine::remote::ws::Client, method::Query};
|
||||
|
||||
pub fn bind_filter_value(
|
||||
query: Query<'_, Client>,
|
||||
val: String,
|
||||
) -> Query<'_, Client> {
|
||||
if let Ok(b) = val.parse::<bool>() {
|
||||
query.bind(("filter", b))
|
||||
} else if let Ok(i) = val.parse::<i64>() {
|
||||
query.bind(("filter", i))
|
||||
} else {
|
||||
query.bind(("filter", val))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use crate::decode_access_token;
|
||||
use axum::http::{header::AUTHORIZATION, HeaderMap};
|
||||
|
||||
pub fn extract_email(headers: &HeaderMap) -> Option<String> {
|
||||
println!("📥 Received headers: {:?}", headers);
|
||||
|
||||
let auth_header = headers.get(AUTHORIZATION)?.to_str().ok()?;
|
||||
println!("🔍 Authorization Header: {}", auth_header);
|
||||
|
||||
let token = auth_header.strip_prefix("Bearer ")?;
|
||||
println!("🧪 Token: {}", token);
|
||||
|
||||
match decode_access_token(token) {
|
||||
Ok(data) => {
|
||||
println!("✅ Token claims: {:?}", data.claims);
|
||||
Some(data.claims.sub)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to decode token: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_email_token(token: String) -> Option<String> {
|
||||
let token_data = decode_access_token(&token).ok()?;
|
||||
Some(token_data.claims.sub)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub fn get_iso_date() -> String {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
now.to_rfc3339()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use rand::{rng, Rng};
|
||||
|
||||
pub struct OtpManager;
|
||||
|
||||
impl OtpManager {
|
||||
pub fn generate_otp() -> u32 {
|
||||
rng().random_range(100_000..1_000_000)
|
||||
}
|
||||
|
||||
pub fn validate_otp(stored_otp: u32, user_otp: u32) -> bool {
|
||||
stored_otp == user_otp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use anyhow::{bail, Result};
|
||||
use surrealdb::sql::Thing;
|
||||
|
||||
pub fn get_id(thing: &Thing) -> Result<(&str, &str)> {
|
||||
let table = thing.tb.as_str();
|
||||
let id = match &thing.id {
|
||||
surrealdb::sql::Id::String(s) => s.as_str(),
|
||||
_ => bail!("Unsupported ID type"),
|
||||
};
|
||||
Ok((table, id))
|
||||
}
|
||||
|
||||
pub fn extract_id(thing: &Thing) -> String {
|
||||
let id = thing.id.to_raw();
|
||||
id
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use imphnen_entities::*;
|
||||
use imphnen_libs::*;
|
||||
|
||||
pub mod bind_filter;
|
||||
pub mod extract_email;
|
||||
pub mod generate_date;
|
||||
pub mod generate_otp;
|
||||
pub mod get_id;
|
||||
pub mod make_thing;
|
||||
pub mod mock_test;
|
||||
pub mod query_list;
|
||||
pub mod response_format;
|
||||
pub mod validator;
|
||||
|
||||
pub use bind_filter::*;
|
||||
pub use extract_email::*;
|
||||
pub use generate_date::*;
|
||||
pub use generate_otp::*;
|
||||
pub use get_id::*;
|
||||
pub use imphnen_entities::*;
|
||||
pub use imphnen_libs::*;
|
||||
pub use make_thing::*;
|
||||
pub use mock_test::*;
|
||||
pub use query_list::*;
|
||||
pub use response_format::*;
|
||||
pub use validator::*;
|
||||
@@ -0,0 +1,5 @@
|
||||
use surrealdb::sql::Thing;
|
||||
|
||||
pub fn make_thing(table: &str, id: &str) -> Thing {
|
||||
Thing::from((table, id))
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use crate::AppState;
|
||||
use surrealdb::{
|
||||
Surreal,
|
||||
engine::{local::Mem, remote::ws::Ws},
|
||||
opt::auth::Root,
|
||||
};
|
||||
|
||||
pub async fn create_mock_app_state() -> AppState {
|
||||
let db_mem = Surreal::new::<Mem>(()).await.unwrap();
|
||||
let db_ws = Surreal::new::<Ws>("localhost:8000").await.unwrap();
|
||||
db_mem.use_ns("test").use_db("test").await.unwrap();
|
||||
db_ws
|
||||
.signin(Root {
|
||||
username: "root",
|
||||
password: "root",
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
db_ws.use_ns("test").use_db("test").await.unwrap();
|
||||
|
||||
AppState {
|
||||
surrealdb_mem: db_mem,
|
||||
surrealdb_ws: db_ws,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cleanup_db() {
|
||||
let app_state = create_mock_app_state().await;
|
||||
let _ = app_state
|
||||
.surrealdb_mem
|
||||
.query(
|
||||
r#"
|
||||
REMOVE TABLE app_users;
|
||||
REMOVE TABLE app_roles;
|
||||
REMOVE TABLE app_users_cache;
|
||||
REMOVE TABLE app_otp_cache;
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
let _ = app_state
|
||||
.surrealdb_ws
|
||||
.query(
|
||||
r#"
|
||||
REMOVE TABLE app_users;
|
||||
REMOVE TABLE app_roles;
|
||||
REMOVE TABLE app_users_cache;
|
||||
REMOVE TABLE app_otp_cache;
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
use super::bind_filter_value;
|
||||
use crate::{CountResult, MetaRequestDto, MetaResponseDto, ResponseListSuccessDto};
|
||||
use anyhow::{bail, Result};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use surrealdb::{engine::remote::ws::Client, Surreal};
|
||||
|
||||
pub async fn query_list_with_meta<T>(
|
||||
db: &Surreal<Client>,
|
||||
table: &str,
|
||||
meta: &MetaRequestDto,
|
||||
conditions: Vec<String>,
|
||||
custom_select: Option<String>,
|
||||
) -> Result<ResponseListSuccessDto<Vec<T>>>
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
let page = meta.page.unwrap_or(1);
|
||||
let per_page = meta.per_page.unwrap_or(10);
|
||||
if page < 1 || per_page < 1 {
|
||||
bail!("Invalid pagination: page and per_page must be greater than 0");
|
||||
}
|
||||
let start = (page - 1) * per_page;
|
||||
let sql = custom_select.unwrap_or_else(|| {
|
||||
let mut s = format!("SELECT * FROM {}", table);
|
||||
if !conditions.is_empty() {
|
||||
s.push_str(" WHERE ");
|
||||
s.push_str(&conditions.join(" AND "));
|
||||
}
|
||||
if let Some(sort_by) = &meta.sort_by {
|
||||
let order = match meta
|
||||
.order
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.to_uppercase()
|
||||
.as_str()
|
||||
{
|
||||
"DESC" => "DESC",
|
||||
_ => "ASC",
|
||||
};
|
||||
s.push_str(&format!(" ORDER BY {} {}", sort_by, order));
|
||||
}
|
||||
s.push_str(" LIMIT $per_page START $start");
|
||||
s
|
||||
});
|
||||
let mut query_exec = db.query(sql);
|
||||
if let Some(search) = &meta.search {
|
||||
if !search.is_empty() {
|
||||
query_exec = query_exec.bind(("search", search.clone()));
|
||||
}
|
||||
}
|
||||
if let Some(filter_val) = meta.filter.clone() {
|
||||
query_exec = bind_filter_value(query_exec, filter_val);
|
||||
}
|
||||
query_exec = query_exec
|
||||
.bind(("per_page", per_page))
|
||||
.bind(("start", start));
|
||||
let raw: Vec<T> = query_exec.await?.take(0)?;
|
||||
let mut count_sql = format!("SELECT count() FROM {}", table);
|
||||
if !conditions.is_empty() {
|
||||
count_sql.push_str(" WHERE ");
|
||||
count_sql.push_str(&conditions.join(" AND "));
|
||||
}
|
||||
let mut count_query = db.query(count_sql);
|
||||
if let Some(search) = &meta.search {
|
||||
if !search.is_empty() {
|
||||
count_query = count_query.bind(("search", search.clone()));
|
||||
}
|
||||
}
|
||||
if let Some(filter_val) = meta.filter.clone() {
|
||||
count_query = bind_filter_value(count_query, filter_val);
|
||||
}
|
||||
let count_result: Vec<CountResult> = count_query.await?.take(0)?;
|
||||
let total = count_result.first().map(|c| c.count);
|
||||
|
||||
let meta = MetaResponseDto {
|
||||
page: Some(page),
|
||||
per_page: Some(per_page),
|
||||
total,
|
||||
};
|
||||
Ok(ResponseListSuccessDto {
|
||||
data: raw,
|
||||
meta: Some(meta),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{ResponseListSuccessDto, ResponseSuccessDto};
|
||||
|
||||
pub fn success_response<T: Serialize>(params: ResponseSuccessDto<T>) -> Response {
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(json!({
|
||||
"data": params.data,
|
||||
"version": "0.1.0",
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
pub fn success_list_response<T: Serialize>(
|
||||
params: ResponseListSuccessDto<T>,
|
||||
) -> Response {
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(json!({
|
||||
"data": params.data,
|
||||
"meta": params.meta,
|
||||
"version": "0.1.0",
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
pub fn common_response(status: StatusCode, message: &str) -> Response {
|
||||
(
|
||||
status,
|
||||
Json(json!({
|
||||
"message": message,
|
||||
"version": "0.1.0",
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use axum::http::StatusCode;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn validate_request<T: Validate>(
|
||||
payload: &T,
|
||||
) -> Result<(), (StatusCode, String)> {
|
||||
if let Err(validation_errors) = payload.validate() {
|
||||
let error_messages: Vec<String> = validation_errors
|
||||
.field_errors()
|
||||
.iter()
|
||||
.flat_map(|(_, errors)| {
|
||||
errors.iter().map(move |error| {
|
||||
format!(
|
||||
"{}",
|
||||
error
|
||||
.message
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Invalid value".into())
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
return Err((StatusCode::BAD_REQUEST, error_messages.join(", ")));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user