diff --git a/Cargo.lock b/Cargo.lock index e6531bf..0336065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2314,7 +2314,7 @@ dependencies = [ [[package]] name = "quanweb" -version = "1.3.0" +version = "1.4.0" dependencies = [ "ammonia", "async-trait", @@ -2354,6 +2354,7 @@ dependencies = [ "serde_json", "serde_json5", "serde_with", + "smallvec", "smart-default", "str-macro", "strum", diff --git a/Cargo.toml b/Cargo.toml index 59f5355..c79dd5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quanweb" -version = "1.3.0" +version = "1.4.0" edition = "2021" rust-version = "1.76" default-run = "quanweb" @@ -59,6 +59,7 @@ serde-value = "0.7.0" serde_json = "1.0.127" serde_json5 = "0.1.0" serde_with = "3.9.0" +smallvec = "1.13.2" smart-default = "0.7.1" str-macro = "1.0.0" strum = { version = "0.26.3", features = ["derive", "strum_macros"] } diff --git a/src/api/errors.rs b/src/api/errors.rs index 7f23107..2fa9d84 100644 --- a/src/api/errors.rs +++ b/src/api/errors.rs @@ -38,7 +38,7 @@ pub enum ApiError { impl IntoResponse for ApiError { fn into_response(self) -> axum::response::Response { - tracing::debug!("To convert ApiError: {:?}", self); + tracing::debug!("To convert ApiError: {:#?}", self); let (status, message) = match self { Self::PathRejection(path_rejection) => { (StatusCode::NOT_FOUND, path_rejection.body_text()) diff --git a/src/api/minors.rs b/src/api/minors.rs index 2bad85f..26e9fb3 100644 --- a/src/api/minors.rs +++ b/src/api/minors.rs @@ -82,7 +82,7 @@ pub async fn update_presentation_partial( serde_json::from_value(value).map_err(ApiError::JsonExtractionError)?; let submitted_fields: Vec<&String> = jdata.keys().collect(); let set_clause = patch_data.gen_set_clause(&submitted_fields); - let args = patch_data.make_edgedb_object(id, &submitted_fields); + let args = patch_data.make_edgedb_args(id, &submitted_fields); let q = format!( "SELECT ( UPDATE Presentation FILTER .id = $id SET {{ {set_clause} }} @@ -118,7 +118,7 @@ pub async fn create_presentation( serde_json::from_value(value).map_err(ApiError::JsonExtractionError)?; post_data.validify().map_err(ApiError::ValidationErrors)?; let set_clause = post_data.gen_set_clause(); - let args = post_data.make_edgedb_object(); + let args = post_data.make_edgedb_args(); let q = format!( " SELECT ( @@ -329,7 +329,7 @@ pub async fn update_book_partial( patch_data.validify().map_err(ApiError::ValidationErrors)?; let submitted_fields: Vec<&String> = jdata.keys().collect(); let set_clause = patch_data.gen_set_clause(&submitted_fields); - let args = patch_data.make_edgedb_object(id, &submitted_fields); + let args = patch_data.make_edgedb_args(id, &submitted_fields); let q = format!( "SELECT ( UPDATE Book FILTER .id = $id SET {{ {set_clause} }} @@ -367,7 +367,7 @@ pub async fn create_book( serde_json::from_value(value).map_err(ApiError::JsonExtractionError)?; post_data.validify().map_err(ApiError::ValidationErrors)?; let set_clause = post_data.gen_set_clause(); - let args = post_data.make_edgedb_object(); + let args = post_data.make_edgedb_args(); let q = format!( " SELECT ( diff --git a/src/api/posts.rs b/src/api/posts.rs index 0b41289..3bfa794 100644 --- a/src/api/posts.rs +++ b/src/api/posts.rs @@ -5,6 +5,7 @@ use axum::{http::StatusCode, response::Result as AxumResult, Json}; use axum_extra::extract::WithRejection; use edgedb_tokio::Client as EdgeClient; use serde_json::{Map as JMap, Value}; +use tracing::debug; use uuid::Uuid; use validify::Validify; @@ -35,13 +36,13 @@ pub async fn list_posts( let search_tokens = split_search_query(other_query.q.as_deref()); let lower_search_tokens: Option> = search_tokens.map(|v| v.into_iter().map(|s| s.to_lowercase()).collect()); + let count = stores::blog::count_search_result_posts(lower_search_tokens.as_ref(), &db) + .await + .map_err(ApiError::EdgeDBQueryError)?; let posts = stores::blog::get_blogposts(lower_search_tokens.as_ref(), Some(offset), Some(limit), &db) .await .map_err(ApiError::EdgeDBQueryError)?; - let count = stores::blog::count_search_result_posts(lower_search_tokens.as_ref(), &db) - .await - .map_err(ApiError::EdgeDBQueryError)?; let total_pages = NonZeroU16::new((count as f64 / per_page as f64).ceil() as u16).unwrap_or(NonZeroU16::MIN); let links = gen_pagination_links(&paging, count, original_uri); @@ -105,7 +106,7 @@ pub async fn update_post_partial( let submitted_fields: Vec<&String> = jdata.keys().collect(); let set_clause = patch_data.gen_set_clause(&submitted_fields); let fields = DetailedBlogPost::fields_as_shape(); - let args = patch_data.make_edgedb_object(post_id, &submitted_fields); + let args = patch_data.make_edgedb_args(post_id, &submitted_fields); let q = format!( "SELECT ( UPDATE BlogPost @@ -115,8 +116,8 @@ pub async fn update_post_partial( }} ) {fields}" ); - tracing::debug!("To query: {}", q); - tracing::debug!("Query with params: {:#?}", args); + debug!("To query: {q}"); + debug!("Query with params: {args:#?}"); let updated_post: Option = db .query_single(&q, &args) .await @@ -146,7 +147,7 @@ pub async fn create_post( let submitted_fields: Vec<&String> = jdata.keys().collect(); let set_clause = post_data.gen_set_clause(&submitted_fields); let fields = DetailedBlogPost::fields_as_shape(); - let args = post_data.make_edgedb_object(&submitted_fields); + let args = post_data.make_edgedb_args(&submitted_fields); let q = format!( " SELECT ( diff --git a/src/api/structs.rs b/src/api/structs.rs index f9fd08e..6a6fe73 100644 --- a/src/api/structs.rs +++ b/src/api/structs.rs @@ -1,15 +1,15 @@ +use std::collections::HashMap; use std::num::NonZeroU16; -use edgedb_protocol::common::Cardinality as Cd; +use edgedb_protocol::named_args; use edgedb_protocol::value::Value as EValue; -use indexmap::indexmap; +use edgedb_protocol::value_opt::ValueOpt; use serde::{Deserialize, Serialize}; use uuid::Uuid; use validify::Validify; use super::macros::append_set_statement; use crate::models::DocFormat; -use crate::types::conversions::{edge_object_from_pairs, edge_object_from_simple_pairs}; use crate::types::ext::VecExt; use crate::utils::markdown::{make_excerpt, markdown_to_html}; @@ -88,59 +88,47 @@ impl BlogPostPatchData { lines.join(&format!(",\n{}", " ".repeat(8))) } - pub fn make_edgedb_object(&self, post_id: Uuid, submitted_fields: &Vec<&String>) -> EValue { - let mut pairs = indexmap! { - "id" => (Some(EValue::Uuid(post_id)), Cd::One), + pub fn make_edgedb_args( + &self, + post_id: Uuid, + submitted_fields: &Vec<&String>, + ) -> HashMap<&str, ValueOpt> { + let mut hm = named_args! { + "id" => post_id }; if submitted_fields.contains("title") { - pairs.insert( - "title", - (self.title.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("title", self.title.clone().into()); } if submitted_fields.contains("slug") { - pairs.insert("slug", (self.slug.clone().map(EValue::Str), Cd::AtMostOne)); + hm.insert("slug", self.slug.clone().into()); } if submitted_fields.contains("is_published") { - pairs.insert( - "is_published", - (self.is_published.map(EValue::Bool), Cd::AtMostOne), - ); + hm.insert("is_published", self.is_published.into()); } if submitted_fields.contains("format") { - pairs.insert( - "format", - (self.format.clone().map(EValue::from), Cd::AtMostOne), - ); + hm.insert("format", self.format.clone().into()); } if submitted_fields.contains("body") { - let body = self.body.clone(); - let html = body.as_ref().map(|b| markdown_to_html(b)); - let excerpt = body.as_ref().map(|b| make_excerpt(b)); - pairs.insert("body", (body.map(EValue::Str), Cd::AtMostOne)); - pairs.insert("html", (html.map(EValue::Str), Cd::AtMostOne)); - pairs.insert("excerpt", (excerpt.map(EValue::Str), Cd::AtMostOne)); + let html = self.body.as_ref().map(|b| markdown_to_html(b)); + let excerpt = self.body.as_ref().map(|b| make_excerpt(b)); + hm.insert("body", self.body.clone().into()); + hm.insert("html", html.into()); + hm.insert("excerpt", excerpt.into()); } if submitted_fields.contains("locale") { - pairs.insert( - "locale", - (self.locale.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("locale", self.locale.clone().into()); } if submitted_fields.contains("author") { - pairs.insert("author", (self.author.map(EValue::Uuid), Cd::AtMostOne)); + hm.insert("author", self.author.into()); } if submitted_fields.contains("og_image") { - pairs.insert( - "og_image", - (self.og_image.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("og_image", self.og_image.clone().into()); } if let Some(categories) = &self.categories { let categories: Vec = categories.iter().map(|&i| EValue::Uuid(i)).collect(); - pairs.insert("categories", (Some(EValue::Array(categories)), Cd::One)); + hm.insert("categories", categories.into()); } - edge_object_from_pairs(pairs) + hm } } @@ -184,51 +172,38 @@ impl BlogPostCreateData { lines.join(&sep) } - pub fn make_edgedb_object(&self, submitted_fields: &Vec<&String>) -> EValue { - let mut pairs = indexmap! { - "title" => (Some(EValue::Str(self.title.clone())), Cd::One), - "slug" => (Some(EValue::Str(self.slug.clone())), Cd::One), + pub fn make_edgedb_args(&self, submitted_fields: &Vec<&String>) -> HashMap<&str, ValueOpt> { + let mut hm = named_args! { + "title" => self.title.clone(), + "slug" => self.slug.clone() }; if submitted_fields.contains("is_published") { - pairs.insert( - "is_published", - (self.is_published.map(EValue::Bool), Cd::AtMostOne), - ); + hm.insert("is_published", self.is_published.into()); } if submitted_fields.contains("body") { - let body = self.body.clone(); - let html = body.as_ref().map(|v| markdown_to_html(v)); - let excerpt = body.as_ref().map(|v| make_excerpt(v)); - pairs.insert("body", (body.map(EValue::Str), Cd::AtMostOne)); - pairs.insert("html", (html.map(EValue::Str), Cd::AtMostOne)); - pairs.insert("excerpt", (excerpt.map(EValue::Str), Cd::AtMostOne)); + hm.insert("body", self.body.clone().into()); + let html = self.body.as_ref().map(|v| markdown_to_html(v)); + let excerpt = self.body.as_ref().map(|v| make_excerpt(v)); + hm.insert("html", html.into()); + hm.insert("excerpt ", excerpt.into()); } if submitted_fields.contains("format") { - pairs.insert( - "format", - (self.format.clone().map(EValue::from), Cd::AtMostOne), - ); + hm.insert("format", self.format.clone().into()); } if submitted_fields.contains("locale") { - pairs.insert( - "locale", - (self.locale.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("locale", self.locale.clone().into()); } if submitted_fields.contains("author") { - pairs.insert("author", (self.author.map(EValue::Uuid), Cd::AtMostOne)); + hm.insert("author", self.author.into()); } if submitted_fields.contains("og_image") { - pairs.insert( - "og_image", - (self.og_image.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("og_image", self.og_image.clone().into()); } if let Some(categories) = &self.categories { let categories: Vec = categories.iter().map(|&i| EValue::Uuid(i)).collect(); - pairs.insert("categories", (Some(EValue::Array(categories)), Cd::One)); + hm.insert("categories", categories.into()); } - edge_object_from_pairs(pairs) + hm } } @@ -248,27 +223,24 @@ impl BlogCategoryPatchData { let sep = format!(",\n{}", " ".repeat(12)); lines.join(&sep) } - - pub fn make_edgedb_object(&self, id: Uuid, submitted_fields: &Vec<&String>) -> EValue { - let mut pairs = indexmap!( - "id" => (Some(EValue::Uuid(id)), Cd::One), - ); + pub fn make_edgedb_args( + &self, + id: Uuid, + submitted_fields: &Vec<&String>, + ) -> HashMap<&str, ValueOpt> { + let mut hm = named_args! { + "id" => id + }; if submitted_fields.contains("title") { - pairs.insert( - "title", - (self.title.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("title", self.title.clone().into()); } if submitted_fields.contains("slug") { - pairs.insert("slug", (self.slug.clone().map(EValue::Str), Cd::AtMostOne)); + hm.insert("slug", self.slug.clone().into()); } if submitted_fields.contains("title_vi") { - pairs.insert( - "title_vi", - (self.title_vi.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("title_vi", self.title_vi.clone().into()); } - edge_object_from_pairs(pairs) + hm } } @@ -292,14 +264,13 @@ impl BlogCategoryCreateData { let sep = format!(",\n{}", " ".repeat(12)); lines.join(&sep) } - - pub fn make_edgedb_object(&self) -> EValue { - let pairs = indexmap! { - "title" => Some(EValue::from(self.title.clone())), - "slug" => Some(EValue::from(self.slug.clone())), - "title_vi" => Some(EValue::from(self.title_vi.clone())), + pub fn make_edgedb_args(&self) -> HashMap<&str, ValueOpt> { + let hm = named_args! { + "title" => self.title.clone(), + "slug" => self.slug.clone(), + "title_vi" => self.title_vi.clone() }; - edge_object_from_simple_pairs(pairs) + hm } } @@ -319,27 +290,24 @@ impl PresentationPatchData { let sep = format!(",\n{}", " ".repeat(12)); lines.join(&sep) } - - pub fn make_edgedb_object(&self, id: Uuid, submitted_fields: &Vec<&String>) -> EValue { - let mut pairs = indexmap!( - "id" => (Some(EValue::Uuid(id)), Cd::One), - ); + pub fn make_edgedb_args( + &self, + id: Uuid, + submitted_fields: &Vec<&String>, + ) -> HashMap<&str, ValueOpt> { + let mut hm = named_args! { + "id"=> id + }; if submitted_fields.contains("title") { - pairs.insert( - "title", - (self.title.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("title", self.title.clone().into()); } if submitted_fields.contains("url") { - pairs.insert("url", (self.url.clone().map(EValue::Str), Cd::AtMostOne)); + hm.insert("url", self.url.clone().into()); } if submitted_fields.contains("event") { - pairs.insert( - "event", - (self.event.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("event", self.event.clone().into()); } - edge_object_from_pairs(pairs) + hm } } @@ -363,14 +331,13 @@ impl PresentationCreateData { let sep = format!(",\n{}", " ".repeat(12)); lines.join(&sep) } - - pub fn make_edgedb_object(&self) -> EValue { - let pairs = indexmap! { - "title" => (Some(EValue::from(self.title.clone())), Cd::One), - "url" => (Some(EValue::from(self.url.clone())), Cd::One), - "event" => (self.event.clone().map(EValue::from), Cd::AtMostOne), + pub fn make_edgedb_args(&self) -> HashMap<&str, ValueOpt> { + let hm = named_args! { + "title" => self.title.clone(), + "url" => self.url.clone(), + "event" => self.event.clone() }; - edge_object_from_pairs(pairs) + hm } } @@ -404,26 +371,24 @@ impl BookPatchData { lines.join(&sep) } - pub fn make_edgedb_object(&self, id: Uuid, submitted_fields: &Vec<&String>) -> EValue { - let mut pairs = indexmap!( - "id" => (Some(EValue::Uuid(id)), Cd::One), - ); + pub fn make_edgedb_args( + &self, + id: Uuid, + submitted_fields: &Vec<&String>, + ) -> HashMap<&str, ValueOpt> { + let mut hm = named_args! { + "id" => id, + }; if submitted_fields.contains("title") { - pairs.insert( - "title", - (self.title.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("title", self.title.clone().into()); } if submitted_fields.contains("download_url") { - pairs.insert( - "download_url", - (self.download_url.clone().map(EValue::Str), Cd::AtMostOne), - ); + hm.insert("download_url", self.download_url.clone().into()); } if submitted_fields.contains("author") { - pairs.insert("author", (self.author.map(EValue::Uuid), Cd::One)); + hm.insert("author", self.author.into()); } - edge_object_from_pairs(pairs) + hm } } @@ -449,14 +414,14 @@ impl BookCreateData { lines.join(&sep) } - pub fn make_edgedb_object(&self) -> EValue { - let mut pairs = indexmap! { - "title" => (Some(EValue::from(self.title.clone())), Cd::One), - "download_url" => (Some(EValue::from(self.download_url.clone())), Cd::One), + pub fn make_edgedb_args(&self) -> HashMap<&str, ValueOpt> { + let mut hm = named_args! { + "title" => self.title.as_str(), + "download_url" => self.download_url.as_str() }; - if self.author.is_some() { - pairs.insert("author", (self.author.map(EValue::Uuid), Cd::One)); + if let Some(a) = self.author { + hm.insert("author", a.into()); } - edge_object_from_pairs(pairs) + hm } } diff --git a/src/api/views.rs b/src/api/views.rs index 1fe16a4..9e8fc46 100644 --- a/src/api/views.rs +++ b/src/api/views.rs @@ -118,7 +118,7 @@ pub async fn update_category_partial( serde_json::from_value(value).map_err(ApiError::JsonExtractionError)?; let submitted_fields: Vec<&String> = jdata.keys().collect(); let set_clause = patch_data.gen_set_clause(&submitted_fields); - let args = patch_data.make_edgedb_object(category_id, &submitted_fields); + let args = patch_data.make_edgedb_args(category_id, &submitted_fields); let fields = BlogCategory::fields_as_shape(); let q = format!( "SELECT ( @@ -159,7 +159,7 @@ pub async fn create_category( post_data.validify().map_err(ApiError::ValidationErrors)?; let set_clause = post_data.gen_set_clause(); let fields = BlogCategory::fields_as_shape(); - let args = post_data.make_edgedb_object(); + let args = post_data.make_edgedb_args(); let q = format!( " SELECT ( diff --git a/src/bin/tools.rs b/src/bin/tools.rs index 4747927..e93be55 100644 --- a/src/bin/tools.rs +++ b/src/bin/tools.rs @@ -2,13 +2,10 @@ use std::fs; use std::path::PathBuf; use clap::{Parser, Subcommand}; -use edgedb_protocol::common::Cardinality as Cd; -use edgedb_protocol::value::Value as EValue; -use indexmap::indexmap; +use edgedb_protocol::named_args; use miette::{miette, IntoDiagnostic, Result}; use syntect::highlighting::ThemeSet; use syntect::html::css_for_theme_with_class_style; -use tokio; use tracing::debug; use tracing_subscriber::{ filter::{EnvFilter, LevelFilter}, @@ -20,7 +17,6 @@ use uuid::Uuid; use quanweb::conf; use quanweb::consts::SYNTECT_CLASS_STYLE; use quanweb::db; -use quanweb::types::conversions::edge_object_from_pairs; const OUTPUT_PATH: &str = "static/css/syntect.css"; const SYNTECT_THEME: &str = "base16-ocean.dark"; @@ -83,12 +79,10 @@ async fn update_with_tuple(id: Uuid, client: &edgedb_tokio::Client) -> Result<() } async fn update_with_params(id: Uuid, client: &edgedb_tokio::Client) -> Result<()> { - let title = "Test with params".to_string(); - let pairs = indexmap!( - "id" => (Some(EValue::Uuid(id)), Cd::One), - "title" => (Some(EValue::Str(title)), Cd::One), - ); - let args = edge_object_from_pairs(pairs); + let args = named_args! { + "id" => id, + "title" => "Test with params" + }; let q_simple = "UPDATE BlogCategory FILTER .id = $id SET { title := $title }"; tracing::debug!("To query: {}", q_simple); tracing::debug!("With args: {:#?}", args); diff --git a/src/stores/blog.rs b/src/stores/blog.rs index 34f3253..33a5c8b 100644 --- a/src/stores/blog.rs +++ b/src/stores/blog.rs @@ -1,13 +1,15 @@ -use str_macro::str; -use edgedb_protocol::common::Cardinality as Cd; +use std::collections::HashMap; + use edgedb_protocol::model::Datetime as EDatetime; -use edgedb_protocol::value::Value as EValue; +use edgedb_protocol::named_args; +use edgedb_protocol::value_opt::ValueOpt; use edgedb_tokio::{Client, Error}; -use indexmap::{indexmap, IndexMap}; +use smallvec::SmallVec; +use str_macro::str; +use tracing::{debug, info}; use uuid::Uuid; use crate::models::{BlogCategory, DetailedBlogPost, MediumBlogPost, MiniBlogPost}; -use crate::types::conversions::{edge_object_from_pairs, edge_object_from_simple_pairs}; use crate::types::EdgeSelectable; pub async fn count_search_result_posts( @@ -79,30 +81,29 @@ pub async fn get_blogposts( } else { "" }; - let mut pairs = IndexMap::with_capacity(3); - let mut paging_lines: Vec = Vec::with_capacity(2); + let mut args = HashMap::new(); + let mut paging_lines: SmallVec<[_; 2]> = SmallVec::new(); if let Some(ss) = lower_search_tokens { - let search: Vec = ss.iter().map(|s| EValue::Str(s.into())).collect(); - pairs.insert("tokens", (Some(EValue::Array(search)), Cd::One)); + let v: Vec<&str> = ss.iter().map(|s| s.as_str()).collect(); + args.insert("tokens", ValueOpt::from(v)); } if let Some(offset) = offset { - pairs.insert("offset", (Some(EValue::Int64(offset)), Cd::One)); + args.insert("offset", ValueOpt::from(offset)); paging_lines.push(str!("OFFSET $offset")); } if let Some(limit) = limit { - pairs.insert("limit", (Some(EValue::Int64(limit)), Cd::One)); + args.insert("limit", ValueOpt::from(limit)); paging_lines.push(str!("LIMIT $limit")); } let paging_expr = paging_lines.join(" "); let fields = MediumBlogPost::fields_as_shape(); - let args = edge_object_from_pairs(pairs); let q = format!( "SELECT BlogPost {fields} {filter_line} ORDER BY .created_at DESC EMPTY FIRST {paging_expr}" ); - tracing::debug!("To query: {}", q); - tracing::debug!("With args: {:?}", args); + debug!("To query: {q}"); + debug!("With args: {args:?}"); let posts: Vec = client.query(&q, &args).await?; Ok(posts) } @@ -112,28 +113,23 @@ pub async fn get_published_posts( limit: Option, client: &Client, ) -> Result, Error> { - let mut pairs = IndexMap::with_capacity(2); + let mut args = HashMap::with_capacity(2); let mut paging_lines: Vec = Vec::with_capacity(2); if let Some(offset) = offset { - pairs.insert("offset", (Some(EValue::Int64(offset)), Cd::One)); + args.insert("offset", ValueOpt::from(offset)); paging_lines.push(str!("OFFSET $offset")); } if let Some(limit) = limit { - pairs.insert("limit", (Some(EValue::Int64(limit)), Cd::One)); + args.insert("limit", ValueOpt::from(limit)); paging_lines.push(str!("LIMIT $limit")); } let paging_expr = paging_lines.join(" "); let fields = MediumBlogPost::fields_as_shape(); - let args = if pairs.is_empty() { - EValue::Nothing - } else { - edge_object_from_pairs(pairs) - }; let q = format!( "SELECT BlogPost {fields} FILTER .is_published = true ORDER BY .created_at DESC EMPTY FIRST {paging_expr}" ); - tracing::info!("To query: {}", q); + info!("To query: {q}"); let posts: Vec = client.query(&q, &args).await?; Ok(posts) } @@ -146,23 +142,22 @@ pub async fn get_published_posts_under_category( ) -> Result, Error> { let mut filter_lines = vec![".is_published = true"]; let mut paging_lines: Vec = Vec::with_capacity(2); - let mut pairs = indexmap! {}; + let mut args: HashMap<&str, ValueOpt> = HashMap::new(); if let Some(slug) = cat_slug { filter_lines.push(".categories.slug = $slug"); - pairs.insert("slug", (Some(EValue::Str(slug)), Cd::One)); + args.insert("slug", slug.into()); } if let Some(offset) = offset { - pairs.insert("offset", (Some(EValue::Int64(offset)), Cd::One)); + args.insert("offset", offset.into()); paging_lines.push(str!("OFFSET $offset")); } if let Some(limit) = limit { - pairs.insert("limit", (Some(EValue::Int64(limit)), Cd::One)); + args.insert("limit", limit.into()); paging_lines.push(str!("LIMIT $limit")); } let filter_expr = filter_lines.join(" AND "); let paging_expr = paging_lines.join(" "); let fields = MediumBlogPost::fields_as_shape(); - let args = edge_object_from_pairs(pairs); let q = format!( "SELECT BlogPost {fields} @@ -187,24 +182,23 @@ pub async fn get_published_uncategorized_blogposts( limit: Option, client: &Client, ) -> Result, Error> { - let mut pairs = IndexMap::with_capacity(2); + let mut args: HashMap<&str, ValueOpt> = HashMap::with_capacity(2); let mut paging_lines: Vec = Vec::with_capacity(2); if let Some(offset) = offset { - pairs.insert("offset", (Some(EValue::Int64(offset)), Cd::One)); + args.insert("offset", offset.into()); paging_lines.push(str!("OFFSET $offset")); } if let Some(limit) = limit { - pairs.insert("limit", (Some(EValue::Int64(limit)), Cd::One)); + args.insert("limit", limit.into()); paging_lines.push(str!("LIMIT $limit")); } let paging_expr = paging_lines.join(" "); let fields = MediumBlogPost::fields_as_shape(); - let args = edge_object_from_pairs(pairs); let q = format!(" SELECT BlogPost {fields} FILTER .is_published = true AND NOT EXISTS .categories ORDER BY .created_at DESC EMPTY FIRST {paging_expr}"); - tracing::debug!("To query: {}", q); - tracing::debug!("With args: {:#?}", args); + debug!("To query: {q}"); + debug!("With args: {args:#?}"); let posts: Vec = client.query(&q, &args).await?; Ok(posts) } @@ -212,7 +206,7 @@ pub async fn get_published_uncategorized_blogposts( pub async fn count_published_uncategorized_posts(client: &Client) -> Result { let q = " SELECT count((SELECT BlogPost FILTER .is_published = true AND NOT EXISTS .categories))"; - tracing::debug!("To query: {}", q); + debug!("To query: {q}"); let count: i64 = client.query_required_single(q, &()).await?; Ok(count.try_into().unwrap_or(0)) } @@ -232,7 +226,7 @@ pub async fn get_blog_categories( pub async fn get_all_categories_count(client: &Client) -> Result { let q = "SELECT count(BlogCategory)"; - tracing::debug!("To query: {}", q); + debug!("To query: {q}"); let count: i64 = client.query_required_single(q, &()).await?; Ok(count.try_into().unwrap_or(0)) } @@ -269,21 +263,19 @@ pub async fn get_previous_post( ".created_at < $created_at", ".is_published = true", ]; - let edatime = EValue::Datetime(created_at); - let mut pairs = indexmap! { - "created_at" => Some(edatime), + let mut args = named_args! { + "created_at" => created_at }; if let Some(slug) = cat_slug { filter_lines.push(".categories.slug = $slug"); - pairs.insert("slug", Some(EValue::Str(slug.to_string()))); + args.insert("slug", slug.into()); } let filter_expr = filter_lines.join(" AND "); - let args = edge_object_from_simple_pairs(pairs); let fields = MiniBlogPost::fields_as_shape(); let q = format!("SELECT BlogPost {fields} FILTER {filter_expr} ORDER BY .created_at DESC LIMIT 1"); - tracing::debug!("To query: {}", q); + debug!("To query: {q}"); let post: Option = client.query_single(&q, &args).await?; Ok(post) } @@ -297,16 +289,14 @@ pub async fn get_next_post( ".created_at > $created_at", ".is_published = true", ]; - let edatime = EValue::Datetime(created_at); - let mut pairs = indexmap! { - "created_at" => Some(edatime), + let mut args = named_args! { + "created_at" => created_at }; if let Some(slug) = cat_slug { filter_lines.push(".categories.slug = $slug"); - pairs.insert("slug", Some(EValue::Str(slug.to_string()))); + args.insert("slug", slug.into()); } let filter_expr = filter_lines.join(" AND "); - let args = edge_object_from_simple_pairs(pairs); let fields = MiniBlogPost::fields_as_shape(); let q = diff --git a/src/stores/user.rs b/src/stores/user.rs index 178f93b..2929019 100644 --- a/src/stores/user.rs +++ b/src/stores/user.rs @@ -1,9 +1,10 @@ +use crate::models::{users::MiniUser, User}; use edgedb_tokio::{Client, Error}; -use crate::models::{User, users::MiniUser}; +use tracing::debug; pub async fn get_user_by_email(email: &str, client: &Client) -> Result, Error> { let q = "SELECT User {id, username, email, password, is_active, is_superuser} FILTER .email = $0 LIMIT 1"; - tracing::debug!("To query: {}", q); + debug!("To query: {q}"); let user: Option = client.query_single(q, &(email,)).await?; Ok(user) } diff --git a/src/types/conversions.rs b/src/types/conversions.rs index b5f73d1..70ee4b6 100644 --- a/src/types/conversions.rs +++ b/src/types/conversions.rs @@ -2,16 +2,11 @@ use std::collections::HashMap; use chrono::{DateTime, Utc}; use chrono_tz::Asia::Ho_Chi_Minh; -use edgedb_protocol::codec::ObjectShape; -use edgedb_protocol::common::Cardinality; use edgedb_protocol::model::Datetime as EDatetime; -use edgedb_protocol::value::Value as EValue; +use fluent_bundle::FluentValue; +use minijinja::value::{Kwargs, Value as MJValue, ValueKind as MJValueKind}; use serde::ser::Serializer; use serde::Serialize; -use minijinja::value::{Kwargs, Value as MJValue, ValueKind as MJValueKind}; -use fluent_bundle::FluentValue; - -use super::create_shape_element; /* Serde serializers to serialize EdgeDB's Datetime type */ pub fn serialize_edge_datetime(edt: &EDatetime, serializer: Se) -> Result @@ -36,60 +31,27 @@ where } } -// Ref: https://github.com/edgedb/edgedb-rust/blob/master/edgedb-protocol/src/value.rs#L100 -pub fn edge_object_from_simple_pairs(iter: impl IntoIterator) -> EValue -where - N: ToString, - V: Into>, -{ - let mut elements = Vec::new(); - let mut fields: Vec> = Vec::new(); - for (key, val) in iter.into_iter() { - elements.push(create_shape_element(key, Cardinality::One)); - fields.push(val.into()); - } - EValue::Object { - shape: ObjectShape::new(elements), - fields, - } -} - -pub fn edge_object_from_pairs(iter: impl IntoIterator) -> EValue -where - N: ToString, - V: Into>, -{ - let mut elements = Vec::new(); - let mut fields: Vec> = Vec::new(); - for (key, (val, cardinality)) in iter.into_iter() { - elements.push(create_shape_element(key, cardinality)); - fields.push(val.into()); - } - EValue::Object { - shape: ObjectShape::new(elements), - fields, - } -} - pub fn jinja_value_to_fluent_value(value: MJValue) -> FluentValue<'static> { match value.kind() { MJValueKind::Number => { let n: f64 = value.try_into().unwrap_or(0.0); FluentValue::from(n) - }, + } MJValueKind::String => { - let s: String = value.try_into().unwrap_or_default(); + let s = String::from(value); FluentValue::from(s) - }, + } MJValueKind::Bool => { let b: u8 = value.try_into().unwrap_or(0); FluentValue::from(b) - }, + } _ => FluentValue::None, } } -pub fn jinja_kwargs_to_fluent_args(kwargs: Kwargs) -> Option>> { +pub fn jinja_kwargs_to_fluent_args( + kwargs: Kwargs, +) -> Option>> { let mj_value: MJValue = kwargs.into(); let mut hm: HashMap> = HashMap::new(); let iter = mj_value.try_iter().ok()?; diff --git a/src/types/mod.rs b/src/types/mod.rs index d572888..c179ee5 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -11,8 +11,6 @@ use axum::http::header::{CONTENT_TYPE, LAST_MODIFIED}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use chrono::{DateTime, Utc}; -use edgedb_protocol::codec::ShapeElement; -use edgedb_protocol::common::Cardinality; use edgedb_tokio::Client; use http::Uri; use indexmap::IndexMap; @@ -223,16 +221,6 @@ pub trait EdgeSelectable { fn fields_as_shape() -> String; } -pub fn create_shape_element(name: N, cardinality: Cardinality) -> ShapeElement { - ShapeElement { - name: name.to_string(), - cardinality: Some(cardinality), - flag_link: false, - flag_link_property: false, - flag_implicit: false, - } -} - /// Codefence options. Follow slidev syntax. /// For example: {lines:true, start_line:4} #[derive(Debug, Deserialize, SmartDefault)]