From fbd5f188efdfe5c4c5a1eae2547e1dcf16fd01e6 Mon Sep 17 00:00:00 2001 From: Yang Jing Date: Mon, 23 Sep 2024 14:31:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=BA=93=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 4 +- crates/ultimate-api/src/v1/page.rs | 19 ++++++-- crates/ultimate-db/Cargo.toml | 3 +- crates/ultimate-db/src/base/crud_fns.rs | 8 +++- crates/ultimate-db/src/id.rs | 45 +++++++++++++++++-- crates/ultimate-grpc/src/utils.rs | 5 ++- crates/ultimate-web/src/error.rs | 7 ++- crates/ultimate-web/src/server.rs | 4 +- crates/ultimate/Cargo.toml | 4 +- crates/ultimate/examples/example-config.rs | 2 +- .../{ultimate_config.rs => configuration.rs} | 10 ++--- crates/ultimate/src/configuration/mod.rs | 20 ++++----- crates/ultimate/src/model.rs | 2 - crates/ultimate/src/run_mode.rs | 2 +- crates/ultimate/src/starter.rs | 2 +- crates/ultimate/src/trace/util.rs | 6 +-- examples/api-example/Cargo.toml | 2 +- examples/api-example/src/app.rs | 8 ++-- examples/api-example/src/auth/auth_serv.rs | 2 +- examples/api-example/src/ctx.rs | 2 +- 20 files changed, 105 insertions(+), 52 deletions(-) rename crates/ultimate/src/configuration/{ultimate_config.rs => configuration.rs} (93%) diff --git a/Cargo.toml b/Cargo.toml index 6b46a1f..7408f79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ hmac = "0.12" sha2 = "0.10" argon2 = "0.5" # -- JWT & JWE -josekit = "0.8" +josekit = "0.10" aliri = "0.6" # -- Async futures = "0.3" @@ -171,7 +171,7 @@ prost-build = "0.13" prost-types = "0.13" # -- opendal -opendal = { version = "0.49", features = ["services-obs"] } +opendal = { version = "0.50", features = ["services-obs"] } strum = { version = "0.26", features = ["derive"] } strum_macros = "0.26" diff --git a/crates/ultimate-api/src/v1/page.rs b/crates/ultimate-api/src/v1/page.rs index 5505c18..fb04a04 100644 --- a/crates/ultimate-api/src/v1/page.rs +++ b/crates/ultimate-api/src/v1/page.rs @@ -25,14 +25,23 @@ impl PagePayload { pub struct Pagination { #[prost(int64, tag = "1")] pub page: i64, + #[prost(int64, tag = "2")] pub page_size: i64, + + #[serde(default = "default_sort_bys")] #[prost(message, repeated, tag = "3")] pub sort_bys: ::prost::alloc::vec::Vec, + + #[serde(skip_serializing_if = "Option::is_none")] #[prost(int64, optional, tag = "4")] pub offset: ::core::option::Option, } +fn default_sort_bys() -> Vec { + vec![] +} + impl Pagination { pub fn page(&self) -> i64 { self.page @@ -101,7 +110,7 @@ pub struct SortBy { impl From for modql::filter::OrderBy { fn from(value: SortBy) -> Self { match value.d.try_into().unwrap_or_default() { - SortDirection::ASC => modql::filter::OrderBy::Asc(value.f), + SortDirection::ASC | SortDirection::UNSPECIFIED => modql::filter::OrderBy::Asc(value.f), SortDirection::DESC => modql::filter::OrderBy::Desc(value.f), } } @@ -111,7 +120,7 @@ impl From for modql::filter::OrderBy { impl From<&SortBy> for modql::filter::OrderBy { fn from(value: &SortBy) -> Self { match value.d.try_into().unwrap_or_default() { - SortDirection::ASC => modql::filter::OrderBy::Asc(value.f.clone()), + SortDirection::ASC | SortDirection::UNSPECIFIED => modql::filter::OrderBy::Asc(value.f.clone()), SortDirection::DESC => modql::filter::OrderBy::Desc(value.f.clone()), } } @@ -123,8 +132,9 @@ impl From<&SortBy> for modql::filter::OrderBy { #[repr(i32)] #[allow(non_camel_case_types)] pub enum SortDirection { - ASC = 0, - DESC = 1, + UNSPECIFIED = 0, + ASC = 1, + DESC = 2, } impl SortDirection { @@ -136,6 +146,7 @@ impl SortDirection { match self { SortDirection::ASC => "ASC", SortDirection::DESC => "DESC", + SortDirection::UNSPECIFIED => "UNSPECIFIED", } } /// Creates an enum from field names used in the ProtoBuf definition. diff --git a/crates/ultimate-db/Cargo.toml b/crates/ultimate-db/Cargo.toml index 288bad2..ac7afbd 100644 --- a/crates/ultimate-db/Cargo.toml +++ b/crates/ultimate-db/Cargo.toml @@ -14,6 +14,7 @@ workspace = true default = ["modql"] utoipa = ["dep:utoipa", "ultimate-api/utoipa"] modql = ["dep:modql", "ultimate-api/modql"] +uuid = ["dep:uuid"] [dependencies] ultimate-api = { workspace = true } @@ -27,7 +28,7 @@ log.workspace = true thiserror.workspace = true tokio.workspace = true tracing.workspace = true -uuid.workspace = true +uuid = { workspace = true, optional = true } sqlx.workspace = true sea-query-binder.workspace = true sea-query.workspace = true diff --git a/crates/ultimate-db/src/base/crud_fns.rs b/crates/ultimate-db/src/base/crud_fns.rs index af187d3..fac596a 100644 --- a/crates/ultimate-db/src/base/crud_fns.rs +++ b/crates/ultimate-db/src/base/crud_fns.rs @@ -97,7 +97,7 @@ where let sqlx_query = sqlx::query_with(&sql, values); let count = mm.dbx().execute(sqlx_query).await?; - if count == 0 { + if count == 1 { Ok(()) } else { // TODO 需要更有效的插入失败错误 @@ -137,7 +137,11 @@ where E: for<'r> FromRow<'r, PgRow> + Unpin + Send, E: HasSeaFields, { - let filter: FilterGroups = id.to_filter_node("id").into(); + let filter: FilterGroups = match id { + #[cfg(feature = "uuid")] + Id::Uuid(id) => crate::IdUuidFilter { id: Some(modql::filter::OpValString::Eq(id.to_string()).into()) }.into(), + _ => id.to_filter_node("id").into(), + }; find_unique::(mm, filter).await?.ok_or_else(|| Error::EntityNotFound { schema: MC::SCHEMA, entity: MC::TABLE, diff --git a/crates/ultimate-db/src/id.rs b/crates/ultimate-db/src/id.rs index e04b710..90c6226 100644 --- a/crates/ultimate-db/src/id.rs +++ b/crates/ultimate-db/src/id.rs @@ -1,9 +1,11 @@ use derive_more::derive::Display; -use modql::{field::HasSeaFields, filter::FilterNode}; +use modql::{ + field::HasSeaFields, + filter::{FilterNode, FilterNodes, OpValsString}, +}; use sea_query::SimpleExpr; use serde::{Deserialize, Serialize}; use sqlx::{postgres::PgRow, FromRow}; -use uuid::Uuid; pub trait DbRowType: HasSeaFields + for<'r> FromRow<'r, PgRow> + Unpin + Send {} @@ -13,7 +15,8 @@ pub enum Id { I32(i32), I64(i64), String(String), - Uuid(Uuid), + #[cfg(feature = "uuid")] + Uuid(uuid::Uuid), } impl Id { @@ -22,11 +25,18 @@ impl Id { Id::I32(id) => (col, *id).into(), Id::I64(id) => (col, *id).into(), Id::String(id) => (col, id).into(), + #[cfg(feature = "uuid")] Id::Uuid(id) => (col, id.to_string()).into(), } } } +#[derive(Debug, Default, Deserialize, FilterNodes)] +pub struct IdUuidFilter { + #[modql(cast_as = "uuid")] + pub id: Option, +} + impl From for FilterNode { fn from(id: Id) -> Self { id.to_filter_node("id") @@ -39,6 +49,7 @@ impl From for SimpleExpr { Id::I32(id) => SimpleExpr::Value(id.into()), Id::I64(id) => SimpleExpr::Value(id.into()), Id::String(id) => SimpleExpr::Value(id.into()), + #[cfg(feature = "uuid")] Id::Uuid(id) => SimpleExpr::Value(id.into()), } } @@ -68,6 +79,20 @@ impl From<&str> for Id { } } +#[cfg(feature = "uuid")] +impl From for Id { + fn from(value: uuid::Uuid) -> Self { + Id::Uuid(value) + } +} + +#[cfg(feature = "uuid")] +impl From<&uuid::Uuid> for Id { + fn from(value: &uuid::Uuid) -> Self { + Id::Uuid(*value) + } +} + pub fn to_vec_id(ids: I) -> Vec where V: Into, @@ -84,7 +109,10 @@ mod tests { struct TestModel { pub role_id: i32, pub user_id: i64, + #[cfg(feature = "uuid")] pub order_id: Uuid, + #[cfg(not(feature = "uuid"))] + pub order_id: String, pub dict_id: String, } @@ -92,7 +120,12 @@ mod tests { fn test_id() -> anyhow::Result<()> { let id = Id::I32(32); println!("id is {id}"); + + #[cfg(feature = "uuid")] let order_id = Id::Uuid(Uuid::now_v7()); + #[cfg(not(feature = "uuid"))] + let order_id = Id::String("123".to_string()); + println!("order id is {order_id}"); assert_eq!("32", serde_json::to_string(&id)?); assert_eq!(serde_json::to_string(&Id::String("abcdefg".into()))?, r#""abcdefg""#); @@ -100,7 +133,13 @@ mod tests { let tm = TestModel { role_id: 53, user_id: 2309457238947, + + #[cfg(feature = "uuid")] order_id: Uuid::now_v7(), + + #[cfg(not(feature = "uuid"))] + order_id: "123".to_string(), + dict_id: "system.run.mode".to_string(), }; diff --git a/crates/ultimate-grpc/src/utils.rs b/crates/ultimate-grpc/src/utils.rs index 1826943..0010efc 100644 --- a/crates/ultimate-grpc/src/utils.rs +++ b/crates/ultimate-grpc/src/utils.rs @@ -1,7 +1,7 @@ use futures::{Future, TryFutureExt}; use prost_types::FieldMask; use tonic::{metadata::MetadataMap, service::RoutesBuilder, transport::Server, Status}; -use tower_http::{compression::CompressionLayer, trace::TraceLayer}; +use tower_http::trace::TraceLayer; use ultimate::{ configuration::model::{GrpcConf, SecurityConf}, security::{jose::JwtPayload, SecurityUtils}, @@ -18,7 +18,8 @@ where { let grpc_addr = conf.server_addr.parse()?; - let mut b = Server::builder().layer(CompressionLayer::new()).layer(TraceLayer::new_for_grpc()); + #[allow(unused_mut)] + let mut b = Server::builder().layer(TraceLayer::new_for_grpc()); #[cfg(feature = "tonic-web")] let mut b = b.accept_http1(true).layer(tonic_web::GrpcWebLayer::new()); diff --git a/crates/ultimate-web/src/error.rs b/crates/ultimate-web/src/error.rs index e89c16b..fb53d66 100644 --- a/crates/ultimate-web/src/error.rs +++ b/crates/ultimate-web/src/error.rs @@ -3,7 +3,6 @@ use axum::response::IntoResponse; use axum::Json; use serde::Serialize; use serde_json::Value; -use ulid::Ulid; use ultimate::security; use ultimate::DataError; @@ -14,7 +13,7 @@ pub type AppResult = core::result::Result, AppError>; #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub struct AppError { /// A unique error ID. - pub err_id: Ulid, + pub err_id: ulid::Ulid, pub err_code: i32, @@ -28,11 +27,11 @@ pub struct AppError { impl AppError { pub fn new(error: impl Into) -> Self { - Self { err_id: Ulid::new(), err_code: 500, err_msg: error.into(), err_msg_detail: None } + Self { err_id: ulid::Ulid::new(), err_code: 500, err_msg: error.into(), err_msg_detail: None } } pub fn new_with_code(err_code: i32, err_msg: impl Into) -> Self { - Self { err_id: Ulid::new(), err_code, err_msg: err_msg.into(), err_msg_detail: None } + Self { err_id: ulid::Ulid::new(), err_code, err_msg: err_msg.into(), err_msg_detail: None } } pub fn with_err_code(mut self, err_code: i32) -> Self { diff --git a/crates/ultimate-web/src/server.rs b/crates/ultimate-web/src/server.rs index 6331788..83579c8 100644 --- a/crates/ultimate-web/src/server.rs +++ b/crates/ultimate-web/src/server.rs @@ -8,9 +8,9 @@ use tower_http::{ }; use tracing::info; -use ultimate::configuration::UltimateConfig; +use ultimate::configuration::Configuration; -pub async fn init_server(conf: Arc, app: Router) -> ultimate::Result<()> { +pub async fn init_server(conf: Arc, app: Router) -> ultimate::Result<()> { let make_service = app .layer(CompressionLayer::new()) .layer(CorsLayer::new().allow_methods(cors::Any).allow_origin(cors::Any)) diff --git a/crates/ultimate/Cargo.toml b/crates/ultimate/Cargo.toml index dec363b..829e054 100644 --- a/crates/ultimate/Cargo.toml +++ b/crates/ultimate/Cargo.toml @@ -23,9 +23,9 @@ tokio.workspace = true async-trait.workspace = true toml.workspace = true config.workspace = true -strum_macros.workspace = true +strum.workspace = true uuid = { workspace = true, optional = true } -ulid = { workspace = true, optional = true } +ulid = { workspace = true } log.workspace = true tracing.workspace = true # tracing-log.workspace = true diff --git a/crates/ultimate/examples/example-config.rs b/crates/ultimate/examples/example-config.rs index 4474501..b38e17f 100644 --- a/crates/ultimate/examples/example-config.rs +++ b/crates/ultimate/examples/example-config.rs @@ -10,5 +10,5 @@ fn main() { let config_state = starter::load_and_init(); - println!("Config content is:\n{}", toml::to_string(config_state.ultimate_config()).unwrap()); + println!("Config content is:\n{}", toml::to_string(config_state.configuration()).unwrap()); } diff --git a/crates/ultimate/src/configuration/ultimate_config.rs b/crates/ultimate/src/configuration/configuration.rs similarity index 93% rename from crates/ultimate/src/configuration/ultimate_config.rs rename to crates/ultimate/src/configuration/configuration.rs index 5fdb27d..be29f21 100644 --- a/crates/ultimate/src/configuration/ultimate_config.rs +++ b/crates/ultimate/src/configuration/configuration.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use super::model::{AppConf, DbConf, GrpcConf, SecurityConf, TraceConfig, WebConfig}; #[derive(Clone, Serialize, Deserialize)] -pub struct UltimateConfig { +pub struct Configuration { app: AppConf, security: SecurityConf, @@ -19,7 +19,7 @@ pub struct UltimateConfig { grpc: GrpcConf, } -impl UltimateConfig { +impl Configuration { pub fn app(&self) -> &AppConf { &self.app } @@ -45,11 +45,11 @@ impl UltimateConfig { } } -impl TryFrom<&Config> for UltimateConfig { +impl TryFrom<&Config> for Configuration { type Error = super::Error; fn try_from(c: &Config) -> std::result::Result { - let qc = c.get::("ultimate")?; + let qc = c.get::("ultimate")?; Ok(qc) } } @@ -124,7 +124,7 @@ mod tests { std::env::set_var("ULTIMATE__SECURITY__PWD__PWD_KEY", "80c9a35c0f231219ca14c44fe10c728d"); std::env::set_var("ULTIMATE__APP__NAME", "ultimate"); let c = load_config().unwrap(); - let qc = UltimateConfig::try_from(&c).unwrap(); + let qc = Configuration::try_from(&c).unwrap(); assert_eq!(qc.security().pwd().pwd_key(), b"80c9a35c0f231219ca14c44fe10c728d"); assert_eq!(qc.security().token().secret_key(), b"8462b1ec9af827ebed13926f8f1e5409774fa1a21a1c8f726a4a34cf7dcabaf2"); diff --git a/crates/ultimate/src/configuration/mod.rs b/crates/ultimate/src/configuration/mod.rs index a4d6ca1..450c49e 100644 --- a/crates/ultimate/src/configuration/mod.rs +++ b/crates/ultimate/src/configuration/mod.rs @@ -7,19 +7,19 @@ use std::{env, str::FromStr, sync::Arc}; use ultimate_common::string::b64u_decode; +mod configuration; mod error; pub mod model; -mod ultimate_config; mod util; pub(crate) use self::util::load_config; +pub use configuration::*; pub use error::{Error, Result}; -pub use ultimate_config::*; #[derive(Clone)] pub struct ConfigState { underling: Arc, - ultimate_config: Arc, + configuration: Arc, } impl ConfigState { @@ -56,20 +56,20 @@ impl ConfigState { /// pub fn load() -> Result { let c = load_config()?; - let ultimate_config = UltimateConfig::try_from(&c)?; + let ultimate_config = Configuration::try_from(&c)?; Ok(Self::new(Arc::new(c), Arc::new(ultimate_config))) } - pub(crate) fn new(underling: Arc, ultimate_config: Arc) -> Self { - Self { underling, ultimate_config } + pub(crate) fn new(underling: Arc, ultimate_config: Arc) -> Self { + Self { underling, configuration: ultimate_config } } - pub fn ultimate_config(&self) -> &UltimateConfig { - self.ultimate_config.as_ref() + pub fn configuration(&self) -> &Configuration { + self.configuration.as_ref() } - pub fn ultimate_config_clone(&self) -> Arc { - self.ultimate_config.clone() + pub fn ultimate_config_clone(&self) -> Arc { + self.configuration.clone() } pub fn underling(&self) -> &Config { diff --git a/crates/ultimate/src/model.rs b/crates/ultimate/src/model.rs index 8dc4b6f..781147d 100644 --- a/crates/ultimate/src/model.rs +++ b/crates/ultimate/src/model.rs @@ -24,13 +24,11 @@ impl IdUuidResult { } } -#[cfg(feature = "ulid")] #[derive(Debug, Serialize, Deserialize)] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub struct IdUlidResult { pub id: ulid::Ulid, } -#[cfg(feature = "ulid")] impl IdUlidResult { pub fn new(id: ulid::Ulid) -> Self { Self { id } diff --git a/crates/ultimate/src/run_mode.rs b/crates/ultimate/src/run_mode.rs index b39b329..3bf0775 100644 --- a/crates/ultimate/src/run_mode.rs +++ b/crates/ultimate/src/run_mode.rs @@ -2,7 +2,7 @@ use serde::{ de::{Unexpected, Visitor}, Deserialize, Deserializer, Serialize, }; -use strum_macros::AsRefStr; +use strum::AsRefStr; #[derive(Debug, Clone, PartialEq, Serialize, AsRefStr)] pub enum RunMode { diff --git a/crates/ultimate/src/starter.rs b/crates/ultimate/src/starter.rs index 8520f5f..9fdbed2 100644 --- a/crates/ultimate/src/starter.rs +++ b/crates/ultimate/src/starter.rs @@ -2,7 +2,7 @@ use crate::{configuration::ConfigState, trace}; pub fn load_and_init() -> ConfigState { let config_state = config_load(); - let ultimate_config = config_state.ultimate_config(); + let ultimate_config = config_state.configuration(); trace::init_trace(ultimate_config); config_state } diff --git a/crates/ultimate/src/trace/util.rs b/crates/ultimate/src/trace/util.rs index 9cdff47..264b505 100644 --- a/crates/ultimate/src/trace/util.rs +++ b/crates/ultimate/src/trace/util.rs @@ -1,9 +1,9 @@ use tracing::info; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -use crate::configuration::{model::LogWriterType, UltimateConfig}; +use crate::configuration::{model::LogWriterType, Configuration}; -pub fn init_trace(c: &UltimateConfig) { +pub fn init_trace(c: &Configuration) { if !c.trace().enable { return; } @@ -45,7 +45,7 @@ pub fn init_trace(c: &UltimateConfig) { } #[cfg(feature = "tracing-appender")] -pub fn init_log_appender(c: &UltimateConfig) -> tracing_appender::rolling::RollingFileAppender { +pub fn init_log_appender(c: &Configuration) -> tracing_appender::rolling::RollingFileAppender { use std::path::Path; let path = Path::new(&c.trace().log_dir); diff --git a/examples/api-example/Cargo.toml b/examples/api-example/Cargo.toml index 699a81c..bc913b5 100644 --- a/examples/api-example/Cargo.toml +++ b/examples/api-example/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [dependencies] ultimate-common = { workspace = true } -ultimate = { workspace = true, features = ["ulid"] } +ultimate = { workspace = true } ultimate-api = { workspace = true, features = ["utoipa"] } ultimate-web = { workspace = true } ultimate-db = { workspace = true } diff --git a/examples/api-example/src/app.rs b/examples/api-example/src/app.rs index cdb8786..cbdda00 100644 --- a/examples/api-example/src/app.rs +++ b/examples/api-example/src/app.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use derive_getters::Getters; use typed_builder::TypedBuilder; use ultimate::{ - configuration::{ConfigState, UltimateConfig}, + configuration::{ConfigState, Configuration}, ctx::Ctx, starter, }; @@ -18,8 +18,8 @@ pub struct AppState { } impl AppState { - pub fn ultimate_config(&self) -> &UltimateConfig { - self.config_state().ultimate_config() + pub fn configuration(&self) -> &Configuration { + self.config_state().configuration() } pub fn mm(&self) -> &ModelManager { @@ -37,7 +37,7 @@ impl AppState { pub async fn new_app_state() -> ultimate::Result { let config = starter::load_and_init(); - let db = DbState::from_config(config.ultimate_config().db()).await?; + let db = DbState::from_config(config.configuration().db()).await?; let app = AppState::builder().config_state(config).db_state(db).build(); Ok(app) } diff --git a/examples/api-example/src/auth/auth_serv.rs b/examples/api-example/src/auth/auth_serv.rs index 917b5ec..4229c55 100644 --- a/examples/api-example/src/auth/auth_serv.rs +++ b/examples/api-example/src/auth/auth_serv.rs @@ -27,7 +27,7 @@ impl AuthServ { let (u, uc) = user_serv.get_fetch_credential(UserFilter::from(&req)).await?; verify_pwd(&req.pwd, &uc.encrypted_pwd).await?; - let token = make_token(self.app.ultimate_config().security(), u.id)?; + let token = make_token(self.app.configuration().security(), u.id)?; Ok(LoginResp { token, token_type: TokenType::Bearer }) } } diff --git a/examples/api-example/src/ctx.rs b/examples/api-example/src/ctx.rs index 7c2c5e1..57415cc 100644 --- a/examples/api-example/src/ctx.rs +++ b/examples/api-example/src/ctx.rs @@ -34,7 +34,7 @@ impl FromRequestParts for CtxW { type Rejection = (StatusCode, Json); async fn from_request_parts(parts: &mut Parts, state: &AppState) -> core::result::Result { - match extract_session(parts, state.ultimate_config().security()) { + match extract_session(parts, state.configuration().security()) { Ok(ctx) => Ok(CtxW::new(state, ctx, Arc::new(RequestMetadata::from(&parts.headers)))), Err(e) => Err((StatusCode::UNAUTHORIZED, Json(e.into()))), }