chore: normalize code
This commit is contained in:
@@ -14,4 +14,4 @@ anyhow.workspace = true
|
||||
axum-test.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
validator.workspace = true
|
||||
validator.workspace = true
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
pub trait Crud<T, Args> {
|
||||
fn list(&self, _args: Args) -> T {
|
||||
unimplemented!("list() is not implemented for this type")
|
||||
}
|
||||
fn detail(&self, _args: Args) -> T {
|
||||
unimplemented!("detail() is not implemented for this type")
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,20 @@ use imphnen_entities::*;
|
||||
use imphnen_libs::*;
|
||||
|
||||
pub mod bind_filter;
|
||||
pub mod crud;
|
||||
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_builder;
|
||||
pub mod query_list;
|
||||
pub mod response_format;
|
||||
pub mod validator;
|
||||
|
||||
pub use bind_filter::*;
|
||||
pub use crud::*;
|
||||
pub use extract_email::*;
|
||||
pub use generate_date::*;
|
||||
pub use generate_otp::*;
|
||||
@@ -21,6 +24,7 @@ pub use imphnen_entities::*;
|
||||
pub use imphnen_libs::*;
|
||||
pub use make_thing::*;
|
||||
pub use mock_test::*;
|
||||
pub use query_builder::*;
|
||||
pub use query_list::*;
|
||||
pub use response_format::*;
|
||||
pub use validator::*;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
use imphnen_libs::MetaRequestDto;
|
||||
|
||||
pub struct ListQueryBuilder {
|
||||
resource: String,
|
||||
conditions: Vec<String>,
|
||||
limit: usize,
|
||||
start: usize,
|
||||
order_by: Option<String>,
|
||||
order: Option<String>,
|
||||
fetch: Vec<String>,
|
||||
select_fields: Vec<String>,
|
||||
}
|
||||
|
||||
impl ListQueryBuilder {
|
||||
pub fn from_meta(
|
||||
resource: impl Into<String>,
|
||||
meta: &MetaRequestDto,
|
||||
search_field: impl Into<String>,
|
||||
select_fields: Option<Vec<&str>>,
|
||||
) -> Self {
|
||||
let mut builder = Self::new(resource)
|
||||
.with_search(meta.search.as_deref(), &search_field.into())
|
||||
.with_filter(meta.filter_by.as_deref(), meta.filter.as_deref())
|
||||
.with_sorting(meta.sort_by.as_deref(), meta.order.as_deref())
|
||||
.with_pagination(meta.page, meta.per_page);
|
||||
|
||||
if let Some(fields) = select_fields {
|
||||
builder = builder.with_select_fields(fields);
|
||||
}
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn new(resource: impl Into<String>) -> Self {
|
||||
Self {
|
||||
resource: resource.into(),
|
||||
conditions: vec!["is_deleted = false".into()],
|
||||
limit: 10,
|
||||
start: 0,
|
||||
order_by: None,
|
||||
order: None,
|
||||
fetch: vec![],
|
||||
select_fields: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_select_fields(mut self, fields: Vec<&str>) -> Self {
|
||||
self.select_fields = fields.into_iter().map(String::from).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_search(mut self, search: Option<&str>, field: &str) -> Self {
|
||||
if let Some(search) = search {
|
||||
if !search.is_empty() {
|
||||
self
|
||||
.conditions
|
||||
.push(format!("string::contains({} ?? '', $search)", field));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_filter(mut self, field: Option<&str>, value: Option<&str>) -> Self {
|
||||
if let (Some(f), Some(v)) = (field, value) {
|
||||
if !v.is_empty() {
|
||||
self.conditions.push(format!("{} = $filter", f));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_pagination(
|
||||
mut self,
|
||||
page: Option<u64>,
|
||||
per_page: Option<u64>,
|
||||
) -> Self {
|
||||
let limit = per_page.unwrap_or(10).max(1);
|
||||
let page = page.unwrap_or(1).max(1);
|
||||
self.limit = limit as usize;
|
||||
self.start = ((page - 1) * limit) as usize;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_sorting(mut self, sort_by: Option<&str>, order: Option<&str>) -> Self {
|
||||
self.order_by = sort_by.map(|s| s.to_string());
|
||||
self.order = order.map(|o| o.to_uppercase());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_fetch(mut self, fetch: impl Into<String>) -> Self {
|
||||
self.fetch.push(fetch.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> String {
|
||||
let where_clause = if !self.conditions.is_empty() {
|
||||
format!("WHERE {}", self.conditions.join(" AND "))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let order_clause = if let Some(field) = self.order_by {
|
||||
let ord = self.order.unwrap_or_else(|| "ASC".into());
|
||||
format!("ORDER BY {} {}", field, ord)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let fetch_clause = if !self.fetch.is_empty() {
|
||||
format!("FETCH {}", self.fetch.join(", "))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let select_clause = if self.select_fields.is_empty() {
|
||||
"*".to_string()
|
||||
} else {
|
||||
self.select_fields.join(", ")
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"
|
||||
SELECT {} FROM {}
|
||||
{}
|
||||
{}
|
||||
LIMIT {} START {}
|
||||
{}
|
||||
"#,
|
||||
select_clause,
|
||||
self.resource,
|
||||
where_clause,
|
||||
order_clause,
|
||||
self.limit,
|
||||
self.start,
|
||||
fetch_clause
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::bind_filter_value;
|
||||
use super::{ListQueryBuilder, 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};
|
||||
use anyhow::Result;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use surrealdb::{Surreal, engine::remote::ws::Client};
|
||||
|
||||
pub async fn query_list_with_meta<T>(
|
||||
db: &Surreal<Client>,
|
||||
@@ -10,75 +10,61 @@ pub async fn query_list_with_meta<T>(
|
||||
meta: &MetaRequestDto,
|
||||
conditions: Vec<String>,
|
||||
custom_select: Option<String>,
|
||||
search_field: &str,
|
||||
select_fields: Option<Vec<&str>>,
|
||||
) -> 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 page = meta.page.unwrap_or(1).max(1);
|
||||
let per_page = meta.per_page.unwrap_or(10).max(1);
|
||||
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
|
||||
ListQueryBuilder::from_meta(table, meta, search_field, select_fields).build()
|
||||
});
|
||||
|
||||
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);
|
||||
if let Some(filter_val) = &meta.filter {
|
||||
query_exec = bind_filter_value(query_exec, filter_val.clone());
|
||||
}
|
||||
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);
|
||||
|
||||
let mut count_query = db.query(format!(
|
||||
"SELECT count() FROM {} {}",
|
||||
table,
|
||||
if conditions.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("WHERE {}", conditions.join(" AND "))
|
||||
}
|
||||
));
|
||||
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);
|
||||
if let Some(filter_val) = &meta.filter {
|
||||
count_query = bind_filter_value(count_query, filter_val.clone());
|
||||
}
|
||||
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),
|
||||
meta: Some(MetaResponseDto {
|
||||
page: Some(page),
|
||||
per_page: Some(per_page),
|
||||
total,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user