diff --git a/.cargo/config.toml b/.cargo/config.toml index 243ea97..c1ebc3f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,6 @@ # 设置运行 cargo 时的系统环境变量 [env] -RUST_LOG = "info,tower_http=debug,ultimate=debug,ultimate_common=debug" +RUST_LOG = "info,tower_http=debug,fruitbox_iam=debug,ultimate_web=debug,ultimate_db=debug,ultimate=debug,ultimate_common=debug" [alias] clippy-all = "clippy --all-features -- -D warnings" @@ -9,3 +9,27 @@ check-all = "check --all-features" # 设置 rustc 编译器参数 # [build] # rustflags = ["--cfg", "uuid_unstable"] + + +# On Windows +# ``` +# cargo install -f cargo-binutils +# rustup component add llvm-tools-preview +# ``` +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "link-arg=-fuse-ld=lld"] +[target.x86_64-pc-windows-gnu] +rustflags = ["-C", "link-arg=-fuse-ld=lld"] + +# On Linux: +# - Ubuntu, `sudo apt-get install lld clang` +# - Arch, `sudo pacman -S lld clang` +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld"] + +# On MacOS, `brew install michaeleisel/zld/zld` +#[target.x86_64-apple-darwin] +#rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"] +#[target.aarch64-apple-darwin] +#rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"] + diff --git a/.vscode/settings.json b/.vscode/settings.json index b40b51b..53af6d3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -77,5 +77,7 @@ "accessibility.signals.positionHasError": { "sound": "off" }, - "editor.tabSize": 2 + "editor.tabSize": 2, + "rust-analyzer.cargo.buildScripts.enable": true, + "editor.formatOnSave": true } diff --git a/Cargo.toml b/Cargo.toml index 368e72b..0107511 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [workspace.package] version = "0.1.0" edition = "2021" -rust-version = "1.79" +rust-version = "1.80" description = "Rust libraries of The ultimate-common" license-file = "LICENSE" repository = "https://gitee.com/yangbajing/ultimate-common" @@ -17,13 +17,17 @@ unsafe_code = "forbid" [workspace.dependencies] # -- projects begin cloud-storage = { version = "0.1", path = "crates/storage" } +weixin-common = { version = "0.1", path = "crates/weixin-common" } +weixin-sdk = { version = "0.1", path = "crates/weixin-sdk" } wework-sdk = { version = "0.1", path = "crates/wework-sdk" } huaweicloud-sdk-obs = { version = "0.1", path = "crates/huaweicloud-sdk-obs" } huaweicloud-sdk-core = { version = "0.1", path = "crates/huaweicloud-sdk-core" } ultimate-common = { version = "0.1", path = "crates/ultimate-common" } ultimate = { version = "0.1", path = "crates/ultimate" } +ultimate-api = { version = "0.1", path = "crates/ultimate-api" } ultimate-db = { version = "0.1", path = "crates/ultimate-db" } ultimate-web = { version = "0.1", path = "crates/ultimate-web" } +ultimate-grpc = { version = "0.1", path = "crates/ultimate-grpc" } # -- projects end # begin -- memory allocator tikv-jemallocator = "0.6" @@ -53,9 +57,10 @@ chrono = { version = "0.4", default-features = false, features = [ "clock", "serde", ] } -typed-builder = "0.19" +typed-builder = "0.20" derive-getters = "0.5" clap = { version = "4.5.7", features = ["derive"] } +o2o = { version = "0.4" } # -- Helpful macros for working with enums and strings enum-iterator = "2" # -- Error @@ -74,6 +79,7 @@ aliri = "0.6" # -- Async futures = "0.3" async-trait = "0.1" +async-stream = "0.3" tokio = { version = "1", features = [ "rt", "rt-multi-thread", @@ -119,21 +125,25 @@ urlencoding = "2.1" serde_urlencoded = "0.7" headers = "0.4" mime = "0.3" +http = "1.1" reqwest = { version = "0.12", features = ["json"] } hyper = "1" +hyper-util = "0.1" +tower = "0.5" tower-http = { version = "0.5", features = [ "fs", "trace", "cors", "compression-full", ] } +tower-service = { version = "0.3" } tower-cookies = "0.10" cookie = "0.18" axum = { version = "0.7", features = ["macros", "form"] } axum-extra = { version = "0.9", features = ["typed-header"] } axum-macros = { version = "0.4" } # openapi -utoipa = { version = "5.0.0-alpha", features = [ +utoipa = { version = "5.0.0-beta", features = [ "axum_extras", "chrono", "decimal", @@ -142,23 +152,28 @@ utoipa = { version = "5.0.0-alpha", features = [ "preserve_order", "preserve_path_order", ] } -utoipa-scalar = { version = "0.2.0-alpha", features = ["axum"] } +utoipa-scalar = { version = "0.2.0-beta", features = ["axum"] } # -- Dev/Test -asserhttp = { version = "0.7.1", features = ["reqwest"] } +asserhttp = { version = "0.7", features = ["reqwest"] } dotenvy = "0.15" # -- Data Science -polars = "0.41" +polars = "0.42" # An Excel/OpenDocument Spreadsheets reader and deserializer in pure rust calamine = "0.25" # -- RPC tonic = "0.12" +tonic-types = "0.12" tonic-reflection = "0.12" +tonic-web = "0.12" +tonic-build = { version = "0.12", features = ["prost"] } prost = "0.13" +prost-build = "0.13" prost-types = "0.13" + # -- opendal -opendal = { version = "0.48", features = ["services-obs"] } +opendal = { version = "0.49", features = ["services-obs"] } -# build-dependencies -# tonic-build = "0.12" strum = { version = "0.26", features = ["derive"] } strum_macros = "0.26" + +pretty_assertions = "1.4" diff --git a/crates/ultimate-api/Cargo.toml b/crates/ultimate-api/Cargo.toml new file mode 100644 index 0000000..a8ea729 --- /dev/null +++ b/crates/ultimate-api/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ultimate-api" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +description.workspace = true +license-file.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +serde = { workspace = true } +serde_repr = { workspace = true } +bytes = { workspace = true } +prost = { workspace = true } +utoipa = { workspace = true, optional = true } +modql = { workspace = true, optional = true } diff --git a/crates/ultimate-api/src/lib.rs b/crates/ultimate-api/src/lib.rs new file mode 100644 index 0000000..a3a6d96 --- /dev/null +++ b/crates/ultimate-api/src/lib.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/crates/ultimate-api/src/v1/mod.rs b/crates/ultimate-api/src/v1/mod.rs new file mode 100644 index 0000000..662f33a --- /dev/null +++ b/crates/ultimate-api/src/v1/mod.rs @@ -0,0 +1,3 @@ +mod page; + +pub use page::*; diff --git a/crates/ultimate-api/src/v1/page.rs b/crates/ultimate-api/src/v1/page.rs new file mode 100644 index 0000000..5505c18 --- /dev/null +++ b/crates/ultimate-api/src/v1/page.rs @@ -0,0 +1,177 @@ +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +#[derive(Clone, PartialEq, ::prost::Message, Serialize)] +pub struct OperationResponse { + #[prost(int32, tag = "1")] + pub code: i32, + #[prost(string, optional, tag = "2")] + pub message: ::core::option::Option<::prost::alloc::string::String>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct PagePayload { + pub page: Page, + pub items: Vec, +} + +impl PagePayload { + pub fn new(page: Page, items: Vec) -> Self { + Self { page, items } + } +} + +#[derive(Clone, PartialEq, ::prost::Message, Serialize, Deserialize)] +pub struct Pagination { + #[prost(int64, tag = "1")] + pub page: i64, + #[prost(int64, tag = "2")] + pub page_size: i64, + #[prost(message, repeated, tag = "3")] + pub sort_bys: ::prost::alloc::vec::Vec, + #[prost(int64, optional, tag = "4")] + pub offset: ::core::option::Option, +} + +impl Pagination { + pub fn page(&self) -> i64 { + self.page + } + + pub fn page_size(&self) -> i64 { + self.page_size + } + + pub fn sort_bys(&self) -> Vec<&SortBy> { + self.sort_bys.iter().collect() + } + + pub fn offset_value(&self) -> i64 { + if let Some(offset) = self.offset { + return offset; + } + let page = self.page(); + let page_size = self.page_size(); + if page < 2 { + return 0; + } + page_size * (page - 1) + } + + pub fn new_default() -> Self { + Self { + page: default_page(), + page_size: default_page_size(), + sort_bys: Default::default(), + offset: Default::default(), + } + } +} + +#[derive(Clone, Copy, PartialEq, ::prost::Message, Serialize, Deserialize)] +pub struct Page { + #[prost(int64, tag = "1")] + pub page: i64, + #[prost(int64, tag = "2")] + pub page_size: i64, + #[prost(int64, tag = "3")] + pub total_size: i64, + #[prost(int64, tag = "4")] + pub total_page: i64, +} + +impl Page { + pub fn new(pagination: &Pagination, total_size: i64) -> Self { + let page = pagination.page; + let page_size = pagination.page_size; + let total_page = if total_size == 0 { 0 } else { (total_size + page_size - 1) / page_size }; + Self { page, page_size, total_size, total_page } + } +} + +#[derive(Clone, PartialEq, ::prost::Message, Serialize, Deserialize)] +pub struct SortBy { + #[prost(string, tag = "1")] + pub f: ::prost::alloc::string::String, + #[prost(enumeration = "SortDirection", tag = "2")] + pub d: i32, +} + +#[cfg(feature = "modql")] +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::DESC => modql::filter::OrderBy::Desc(value.f), + } + } +} + +#[cfg(feature = "modql")] +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::DESC => modql::filter::OrderBy::Desc(value.f.clone()), + } + } +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration, Serialize_repr, Deserialize_repr, +)] +#[repr(i32)] +#[allow(non_camel_case_types)] +pub enum SortDirection { + ASC = 0, + DESC = 1, +} + +impl SortDirection { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SortDirection::ASC => "ASC", + SortDirection::DESC => "DESC", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "ASC" => Some(Self::ASC), + "DESC" => Some(Self::DESC), + _ => None, + } + } +} + +#[cfg(feature = "modql")] +impl From for modql::filter::ListOptions { + fn from(value: Pagination) -> Self { + let offset = Some(value.offset_value()); + let limit = Some(if value.page_size > 0 { value.page_size } else { default_page_size() }); + let order_bys = Some(modql::filter::OrderBys::new(value.sort_bys.into_iter().map(Into::into).collect())); + modql::filter::ListOptions { limit, offset, order_bys } + } +} + +#[cfg(feature = "modql")] +impl From<&Pagination> for modql::filter::ListOptions { + fn from(value: &Pagination) -> Self { + let offset = Some(value.offset_value()); + let limit = Some(if value.page_size > 0 { value.page_size } else { default_page_size() }); + let order_bys = Some(modql::filter::OrderBys::new(value.sort_bys.iter().map(|v| v.into()).collect())); + modql::filter::ListOptions { limit, offset, order_bys } + } +} + +fn default_page() -> i64 { + 1 +} + +fn default_page_size() -> i64 { + 20 +} diff --git a/crates/ultimate-common/Cargo.toml b/crates/ultimate-common/Cargo.toml index 221fd90..0e23db3 100644 --- a/crates/ultimate-common/Cargo.toml +++ b/crates/ultimate-common/Cargo.toml @@ -8,6 +8,9 @@ license-file.workspace = true repository.workspace = true readme = "README.md" +[features] +prost = ["dep:prost-types"] + [dependencies] thiserror.workspace = true serde.workspace = true diff --git a/crates/ultimate-common/src/time.rs b/crates/ultimate-common/src/time.rs index cfbb05a..bf2b1da 100644 --- a/crates/ultimate-common/src/time.rs +++ b/crates/ultimate-common/src/time.rs @@ -51,17 +51,21 @@ pub fn now_utc_plus_sec_str(sec: u64) -> Result { format_time(new_time) } +pub fn from_milliseconds(milliseconds: i64) -> DateTime { + DateTime::::MIN_UTC + Duration::milliseconds(milliseconds) +} + pub fn parse_utc(moment: &str) -> Result { let time = moment.parse::().unwrap(); Ok(time) } -#[cfg(feature = "prost-types")] +#[cfg(feature = "prost")] pub fn to_prost_timestamp(d: &UtcDateTime) -> prost_types::Timestamp { prost_types::Timestamp { seconds: d.timestamp(), nanos: d.timestamp_subsec_nanos() as i32 } } -#[cfg(feature = "prost-types")] +#[cfg(feature = "prost")] pub fn from_prost_timestamp(t: &prost_types::Timestamp) -> Option { DateTime::from_timestamp(t.seconds, t.nanos as u32) } diff --git a/crates/ultimate-db/Cargo.toml b/crates/ultimate-db/Cargo.toml index 0df063b..288bad2 100644 --- a/crates/ultimate-db/Cargo.toml +++ b/crates/ultimate-db/Cargo.toml @@ -11,9 +11,12 @@ repository.workspace = true workspace = true [features] -# default = ["utoipa"] +default = ["modql"] +utoipa = ["dep:utoipa", "ultimate-api/utoipa"] +modql = ["dep:modql", "ultimate-api/modql"] [dependencies] +ultimate-api = { workspace = true } ultimate-common.workspace = true ultimate.workspace = true derive_more.workspace = true @@ -28,7 +31,7 @@ uuid.workspace = true sqlx.workspace = true sea-query-binder.workspace = true sea-query.workspace = true -modql.workspace = true +modql = { workspace = true, optional = true } utoipa = { workspace = true, optional = true } [dev-dependencies] diff --git a/crates/ultimate-db/src/base/crud_fns.rs b/crates/ultimate-db/src/base/crud_fns.rs index 6ed2863..af187d3 100644 --- a/crates/ultimate-db/src/base/crud_fns.rs +++ b/crates/ultimate-db/src/base/crud_fns.rs @@ -1,17 +1,16 @@ use modql::field::{HasSeaFields, SeaField, SeaFields}; use modql::filter::{FilterGroups, ListOptions}; -use sea_query::{Condition, Expr, PostgresQueryBuilder, Query}; +use sea_query::{Condition, Expr, PostgresQueryBuilder, Query, SelectStatement}; use sea_query_binder::SqlxBinder; use sqlx::postgres::PgRow; use sqlx::FromRow; use sqlx::Row; +use ultimate_api::v1::{Page, PagePayload, Pagination}; use crate::base::{prep_fields_for_create, prep_fields_for_update, CommonIden, DbBmc}; -use crate::{Error, Page, PagePayload, Pagination, Result}; +use crate::{Error, Result}; use crate::{Id, ModelManager}; -use super::check_number_of_affected; - /// Create a new entity。需要自增主键ID pub async fn create(mm: &ModelManager, data: E) -> Result where @@ -132,17 +131,21 @@ where Ok(rows) } -pub async fn get_by_id(mm: &ModelManager, id: Id) -> Result +pub async fn find_by_id(mm: &ModelManager, id: Id) -> Result where MC: DbBmc, E: for<'r> FromRow<'r, PgRow> + Unpin + Send, E: HasSeaFields, { let filter: FilterGroups = id.to_filter_node("id").into(); - find::(mm, filter).await?.ok_or_else(|| Error::EntityNotFound { schema: MC::SCHEMA, entity: MC::TABLE, id }) + find_unique::(mm, filter).await?.ok_or_else(|| Error::EntityNotFound { + schema: MC::SCHEMA, + entity: MC::TABLE, + id, + }) } -pub async fn find(mm: &ModelManager, filter: F) -> Result> +pub async fn find_unique(mm: &ModelManager, filter: F) -> Result> where MC: DbBmc, E: for<'r> FromRow<'r, PgRow> + Unpin + Send, @@ -166,23 +169,33 @@ where Ok(entity) } -pub async fn list(mm: &ModelManager, filter: Option, list_options: Option) -> Result> +pub async fn find_first(mm: &ModelManager, filter: F) -> Result> where MC: DbBmc, + E: for<'r> FromRow<'r, PgRow> + Unpin + Send, + E: HasSeaFields, F: Into, +{ + let list = find_many::(mm, filter, None).await?; + Ok(list.into_iter().next()) +} + +pub async fn find_many(mm: &ModelManager, filter: F, list_options: Option) -> Result> +where + MC: DbBmc, E: for<'r> FromRow<'r, PgRow> + Unpin + Send, E: HasSeaFields, + F: Into, { // -- Build the query let mut query = Query::select(); query.from(MC::table_ref()).columns(E::sea_column_refs()); // condition from filter - if let Some(filter) = filter { - let filters: FilterGroups = filter.into(); - let cond: Condition = filters.try_into()?; - query.cond_where(cond); - } + let filters: FilterGroups = filter.into(); + let cond: Condition = filters.try_into()?; + query.cond_where(cond); + // list options let list_options = compute_list_options::(list_options)?; list_options.apply_to_sea_query(&mut query); @@ -196,7 +209,30 @@ where Ok(entities) } -pub async fn count(mm: &ModelManager, filter: Option) -> Result +pub async fn find_many_on(mm: &ModelManager, f: F) -> Result> +where + MC: DbBmc, + E: for<'r> FromRow<'r, PgRow> + Unpin + Send, + E: HasSeaFields, + F: FnOnce(&mut SelectStatement) -> Result<()>, +{ + // -- Build the query + let mut query = Query::select(); + query.from(MC::table_ref()).columns(E::sea_column_refs()); + + // condition from filter and list options + f(&mut query)?; + + // -- Execute the query + let (sql, values) = query.build_sqlx(PostgresQueryBuilder); + + let sqlx_query = sqlx::query_as_with::<_, E, _>(&sql, values); + let entities = mm.dbx().fetch_all(sqlx_query).await?; + + Ok(entities) +} + +pub async fn count(mm: &ModelManager, filter: F) -> Result where MC: DbBmc, F: Into, @@ -206,34 +242,53 @@ where let mut query = Query::select().from(MC::table_ref()).expr(Expr::col(sea_query::Asterisk).count()).to_owned(); // condition from filter - if let Some(filter) = filter { - let filters: FilterGroups = filter.into(); - let cond: Condition = filters.try_into()?; - query.cond_where(cond); - } + let filters: FilterGroups = filter.into(); + let cond: Condition = filters.try_into()?; + query.cond_where(cond); let query_str = query.to_string(PostgresQueryBuilder); let result = sqlx::query(&query_str).fetch_one(db).await.map_err(|_| Error::CountFail)?; let count: i64 = result.try_get("count").map_err(|_| Error::CountFail)?; + Ok(count) +} +pub async fn count_on(mm: &ModelManager, f: F) -> Result +where + MC: DbBmc, + F: FnOnce(&mut SelectStatement) -> Result<()>, +{ + // -- Build the query + let mut query = Query::select(); + query.from(MC::table_ref()); + query.expr(Expr::col(sea_query::Asterisk).count()); + + // -- condition from filter + f(&mut query)?; + + // -- Generate sql and values + let (sql, values) = query.build_sqlx(PostgresQueryBuilder); + println!("sql: {}, values: {:?}", sql, values); + let sqlx_query = sqlx::query_as_with::<_, (i64,), _>(&sql, values); + + // -- Execute the query + let (count,) = mm.dbx().fetch_one(sqlx_query).await.map_err(|_| Error::CountFail)?; Ok(count) } -pub async fn page(mm: &ModelManager, pagination: Pagination, filter: Option) -> Result> +pub async fn page(mm: &ModelManager, filter: F, pagination: Pagination) -> Result> where MC: DbBmc, F: Into, E: for<'r> FromRow<'r, PgRow> + Unpin + Send, E: HasSeaFields, { - let filter: Option = filter.map(|f| f.into()); - + let filter: FilterGroups = filter.into(); let total_size = count::(mm, filter.clone()).await?; - let records = list::(mm, filter, Some((&pagination).into())).await?; + let items = find_many::(mm, filter, Some((&pagination).into())).await?; - Ok(PagePayload::new(Page::new(&pagination, total_size), records)) + Ok(PagePayload::new(Page::new(&pagination, total_size), items)) } pub async fn update_by_id(mm: &ModelManager, id: Id, data: E) -> Result<()> @@ -337,8 +392,6 @@ where return Ok(0); } - let ids_len = ids.len(); - // -- Build query let mut query = Query::delete(); query.from_table(MC::table_ref()).and_where(Expr::col(CommonIden::Id).is_in(ids)); @@ -348,8 +401,26 @@ where let sqlx_query = sqlx::query_with(&sql, values); let n = mm.dbx().execute(sqlx_query).await?; - // -- Check result - check_number_of_affected::(ids_len, n) + Ok(n) +} + +pub async fn delete(mm: &ModelManager, filter: F) -> Result +where + MC: DbBmc, + F: Into, +{ + let mut query = Query::delete(); + query.from_table(MC::table_ref()); + let filters: FilterGroups = filter.into(); + let cond: Condition = filters.try_into()?; + query.cond_where(cond); + + // -- Execute query + let (sql, values) = query.build_sqlx(PostgresQueryBuilder); + let sqlx_query = sqlx::query_with(&sql, values); + let n = mm.dbx().execute(sqlx_query).await?; + + Ok(n) } pub fn compute_list_options(list_options: Option) -> Result @@ -370,7 +441,7 @@ where } // When None, return default else { - Ok(ListOptions { limit: Some(MC::LIST_LIMIT_DEFAULT), offset: None, order_bys: Some("id".into()) }) + Ok(ListOptions { limit: Some(MC::LIST_LIMIT_DEFAULT), offset: None, order_bys: None }) } } diff --git a/crates/ultimate-db/src/base/macro_utils.rs b/crates/ultimate-db/src/base/macro_utils.rs index 6c80afc..996a0ca 100644 --- a/crates/ultimate-db/src/base/macro_utils.rs +++ b/crates/ultimate-db/src/base/macro_utils.rs @@ -3,113 +3,123 @@ /// code for the custom implementations. #[macro_export] macro_rules! generate_common_bmc_fns { - ( - Bmc: $struct_name:ident, - Entity: $entity:ty, - $(ForCreate: $for_create:ty,)? - $(ForUpdate: $for_update:ty,)? - $(Filter: $filter:ty,)? - ) => { + ( + Bmc: $struct_name:ident, + Entity: $entity:ty, + $(ForCreate: $for_create:ty,)? + $(ForUpdate: $for_update:ty,)? + ) => { impl $struct_name { - $( - pub async fn create( - mm: &ultimate_db::ModelManager, - entity_c: $for_create, - ) -> ultimate_db::Result { - ultimate_db::base::create::(mm, entity_c).await - } + $( + pub async fn create( + mm: &ultimate_db::ModelManager, + entity_c: $for_create, + ) -> ultimate_db::Result { + ultimate_db::base::create::(mm, entity_c).await + } - pub async fn create_many( - mm: &ultimate_db::ModelManager, - entity_c: Vec<$for_create>, - ) -> ultimate_db::Result> { - ultimate_db::base::create_many::(mm, entity_c).await - } - )? + pub async fn create_many( + mm: &ultimate_db::ModelManager, + entity_c: Vec<$for_create>, + ) -> ultimate_db::Result> { + ultimate_db::base::create_many::(mm, entity_c).await + } + )? + + $( + pub async fn insert( + mm: &ultimate_db::ModelManager, + entity_c: $for_create, + ) -> ultimate_db::Result<()> { + ultimate_db::base::insert::(mm, entity_c).await + } - $( - pub async fn insert( - mm: &ultimate_db::ModelManager, - entity_c: $for_create, - ) -> ultimate_db::Result<()> { - ultimate_db::base::insert::(mm, entity_c).await - } + pub async fn insert_many( + mm: &ultimate_db::ModelManager, + entity_c: Vec<$for_create>, + ) -> ultimate_db::Result { + ultimate_db::base::insert_many::(mm, entity_c).await + } + )? - pub async fn insert_many( - mm: &ultimate_db::ModelManager, - entity_c: Vec<$for_create>, - ) -> ultimate_db::Result { - ultimate_db::base::insert_many::(mm, entity_c).await - } - )? + pub async fn find_by_id( + mm: &ultimate_db::ModelManager, + id: impl Into, + ) -> ultimate_db::Result<$entity> { + ultimate_db::base::find_by_id::(mm, id.into()).await + } - pub async fn get_by_id( + $( + pub async fn update_by_id( mm: &ultimate_db::ModelManager, id: impl Into, - ) -> ultimate_db::Result<$entity> { - ultimate_db::base::get_by_id::(mm, id.into()).await + entity_u: $for_update, + ) -> ultimate_db::Result<()> { + ultimate_db::base::update_by_id::(mm, id.into(), entity_u).await } + )? - $( - pub async fn find( - mm: &ultimate_db::ModelManager, - filter: $filter, - ) -> ultimate_db::Result> { - ultimate_db::base::find::(mm, filter).await - } - - pub async fn list( - mm: &ultimate_db::ModelManager, - filter: $filter, - pagination: Option<&ultimate_db::Pagination>, - ) -> ultimate_db::Result> { - ultimate_db::base::list::(mm, Some(filter), pagination.map(Into::into)).await - } + pub async fn delete_by_id( + mm: &ultimate_db::ModelManager, + id: impl Into, + ) -> ultimate_db::Result<()> { + ultimate_db::base::delete_by_id::(mm, id.into()).await + } - pub async fn count( - mm: &ultimate_db::ModelManager, - filter: $filter, - ) -> ultimate_db::Result { - ultimate_db::base::count::(mm, Some(filter)).await - } + pub async fn delete_by_ids( + mm: &ultimate_db::ModelManager, + ids: I, + ) -> ultimate_db::Result + where + V: Into, + I: IntoIterator, + { + let ids = ids.into_iter().map(|v| v.into()).collect(); + ultimate_db::base::delete_by_ids::(mm, ids).await + } + } + }; +} - pub async fn page( - mm: &ultimate_db::ModelManager, - pagination: ultimate_db::Pagination, - filter: $filter, - ) -> ultimate_db::Result> { - ultimate_db::base::page::(mm, pagination, Some(filter)).await - } - )? +#[macro_export] +macro_rules! generate_filter_bmc_fns { + ( + Bmc: $struct_name:ident, + Entity: $entity:ty, + $(Filter: $filter:ty,)? + ) => { + impl $struct_name { + $( + pub async fn find_unique( + mm: &ultimate_db::ModelManager, + filter: Vec<$filter>, + ) -> ultimate_db::Result> { + ultimate_db::base::find_unique::(mm, filter).await + } - $( - pub async fn update_by_id( - mm: &ultimate_db::ModelManager, - id: impl Into, - entity_u: $for_update, - ) -> ultimate_db::Result<()> { - ultimate_db::base::update_by_id::(mm, id.into(), entity_u).await - } - )? + pub async fn find_many( + mm: &ultimate_db::ModelManager, + filter: Vec<$filter>, + pagination: Option<&ultimate_api::v1::Pagination>, + ) -> ultimate_db::Result> { + ultimate_db::base::find_many::(mm, filter, pagination.map(Into::into)).await + } - pub async fn delete_by_id( - mm: &ultimate_db::ModelManager, - id: impl Into, - ) -> ultimate_db::Result<()> { - ultimate_db::base::delete_by_id::(mm, id.into()).await - } + pub async fn count( + mm: &ultimate_db::ModelManager, + filter: Vec<$filter>, + ) -> ultimate_db::Result { + ultimate_db::base::count::(mm, filter).await + } - pub async fn delete_by_ids( - mm: &ultimate_db::ModelManager, - ids: I, - ) -> ultimate_db::Result - where - V: Into, - I: IntoIterator, - { - let ids = ids.into_iter().map(|v| v.into()).collect(); - ultimate_db::base::delete_by_ids::(mm, ids).await - } + pub async fn page( + mm: &ultimate_db::ModelManager, + filter: Vec<$filter>, + pagination: ultimate_api::v1::Pagination, + ) -> ultimate_db::Result> { + ultimate_db::base::page::(mm, filter, pagination).await + } + )? } }; } diff --git a/crates/ultimate-db/src/lib.rs b/crates/ultimate-db/src/lib.rs index f1a71f5..65dcda8 100644 --- a/crates/ultimate-db/src/lib.rs +++ b/crates/ultimate-db/src/lib.rs @@ -1,4 +1,4 @@ -use ultimate::configuration::model::DbConfig; +use ultimate::configuration::model::DbConf; pub mod acs; pub mod base; @@ -6,14 +6,12 @@ mod error; mod id; mod model_manager; mod modql_utils; -mod page; pub mod store; pub use error::{Error, Result}; pub use id::*; pub use model_manager::*; pub use modql_utils::*; -pub use page::*; #[derive(Clone)] pub struct DbState { @@ -21,7 +19,7 @@ pub struct DbState { } impl DbState { - pub async fn from_config(db: &DbConfig) -> Result { + pub async fn from_config(db: &DbConf) -> Result { let mm = ModelManager::new(db).await?; Ok(DbState { mm }) } diff --git a/crates/ultimate-db/src/model_manager.rs b/crates/ultimate-db/src/model_manager.rs index 4fa6d89..f78001e 100644 --- a/crates/ultimate-db/src/model_manager.rs +++ b/crates/ultimate-db/src/model_manager.rs @@ -1,4 +1,4 @@ -use ultimate::{configuration::model::DbConfig, ctx::Ctx}; +use ultimate::{configuration::model::DbConf, ctx::Ctx}; use crate::store::{dbx::new_db_pool_from_config, Dbx}; @@ -12,7 +12,7 @@ pub struct ModelManager { impl ModelManager { /// Constructor - pub async fn new(db_config: &DbConfig) -> Result { + pub async fn new(db_config: &DbConf) -> Result { let db_pool = new_db_pool_from_config(db_config).await.map_err(|ex| Error::CantCreateModelManagerProvider(ex.to_string()))?; let dbx = Dbx::new(db_pool, false)?; diff --git a/crates/ultimate-db/src/page.rs b/crates/ultimate-db/src/page.rs deleted file mode 100644 index 4b4529b..0000000 --- a/crates/ultimate-db/src/page.rs +++ /dev/null @@ -1,149 +0,0 @@ -use modql::filter::{ListOptions, OrderBy, OrderBys}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize)] -pub struct PagePayload { - pub page: Page, - pub records: Vec, -} - -impl PagePayload { - pub fn new(page: Page, records: Vec) -> Self { - Self { page, records } - } -} - -#[derive(Debug, Clone, Serialize)] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -pub struct Page { - pub page: i64, - pub page_size: i64, - pub total_size: i64, - pub total_page: i64, -} - -impl Page { - pub fn new(pagination: &Pagination, total_size: i64) -> Self { - let page = pagination.page(); - let page_size = pagination.page_size(); - let total_page = if total_size == 0 { 0 } else { (total_size + page_size - 1) / page_size }; - Self { page, page_size, total_size, total_page } - } -} - -#[derive(Debug, Clone, Deserialize)] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -pub struct Pagination { - pub page: Option, - - pub page_size: Option, - - pub sort_bys: Option>, - - pub offset: Option, -} - -impl Pagination { - pub fn page(&self) -> i64 { - self.page.unwrap_or(default_page()) - } - - pub fn page_size(&self) -> i64 { - self.page_size.unwrap_or(default_page_size()) - } - - pub fn sort_bys(&self) -> Vec<&SortBy> { - match self.sort_bys.as_ref() { - Some(vs) => vs.iter().collect(), - None => vec![], - } - } - - pub fn offset(&self) -> i64 { - if let Some(offset) = self.offset { - return offset; - } - let page = self.page(); - let page_size = self.page_size(); - if page < 2 { - return 0; - } - page_size * (page - 1) - } -} - -impl Default for Pagination { - fn default() -> Self { - Self { - page: Some(default_page()), - page_size: Some(default_page_size()), - sort_bys: Default::default(), - offset: Default::default(), - } - } -} - -#[derive(Debug, Clone, Default, Deserialize)] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -pub struct SortBy { - /// 要排序字段 - pub f: String, - - /// 排序方法 - #[serde(default)] - pub d: SortByDirection, -} - -impl From for OrderBy { - fn from(value: SortBy) -> Self { - match value.d { - SortByDirection::asc => OrderBy::Asc(value.f), - SortByDirection::desc => OrderBy::Desc(value.f), - } - } -} - -impl From<&SortBy> for OrderBy { - fn from(value: &SortBy) -> Self { - match value.d { - SortByDirection::asc => OrderBy::Asc(value.f.clone()), - SortByDirection::desc => OrderBy::Desc(value.f.clone()), - } - } -} - -#[derive(Debug, Clone, Default, Deserialize)] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[allow(non_camel_case_types)] -pub enum SortByDirection { - #[default] - asc, - desc, -} - -impl From for ListOptions { - fn from(value: Pagination) -> Self { - let offset = Some(value.offset()); - let limit = Some(value.page_size.unwrap_or(default_page_size())); - let order_bys = value.sort_bys.map(|v| OrderBys::new(v.into_iter().map(Into::into).collect())); - - ListOptions { limit, offset, order_bys } - } -} - -impl From<&Pagination> for ListOptions { - fn from(value: &Pagination) -> Self { - let offset = Some(value.offset()); - let limit = Some(value.page_size.unwrap_or(default_page_size())); - let order_bys = value.sort_bys.as_ref().map(|v| OrderBys::new(v.iter().map(Into::into).collect())); - ListOptions { limit, offset, order_bys } - } -} - -fn default_page() -> i64 { - 1 -} - -fn default_page_size() -> i64 { - 20 -} diff --git a/crates/ultimate-db/src/store/dbx/mod.rs b/crates/ultimate-db/src/store/dbx/mod.rs index ad55a79..5b8b829 100644 --- a/crates/ultimate-db/src/store/dbx/mod.rs +++ b/crates/ultimate-db/src/store/dbx/mod.rs @@ -10,7 +10,7 @@ use sqlx::query::{Query, QueryAs}; use sqlx::{ConnectOptions, FromRow, IntoArguments, Pool, Postgres, Transaction}; use tokio::sync::Mutex; use tracing::trace; -use ultimate::configuration::model::DbConfig; +use ultimate::configuration::model::DbConf; mod error; @@ -19,7 +19,8 @@ pub use error::{Error, Result}; // endregion: --- Modules pub type Db = Pool; -pub async fn new_db_pool_from_config(c: &DbConfig) -> Result { + +pub async fn new_db_pool_from_config(c: &DbConf) -> Result { if !c.enable() { return Err(Error::ConfigInvalid("Need set ultimate.db.enable = true")); } diff --git a/crates/ultimate-grpc/Cargo.toml b/crates/ultimate-grpc/Cargo.toml new file mode 100644 index 0000000..2be2bbf --- /dev/null +++ b/crates/ultimate-grpc/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ultimate-grpc" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +description.workspace = true +license-file.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +ultimate = { workspace = true, features = ["tonic"] } +http = { workspace = true } +futures = { workspace = true } +tower-service = { workspace = true } +tonic = { workspace = true } +tonic-types = { workspace = true } +tonic-web = { workspace = true, optional = true } +tonic-reflection = { workspace = true, optional = true } +prost = { workspace = true } +prost-types = { workspace = true } diff --git a/crates/ultimate-grpc/src/lib.rs b/crates/ultimate-grpc/src/lib.rs new file mode 100644 index 0000000..b5614dd --- /dev/null +++ b/crates/ultimate-grpc/src/lib.rs @@ -0,0 +1 @@ +pub mod utils; diff --git a/crates/ultimate-grpc/src/utils.rs b/crates/ultimate-grpc/src/utils.rs new file mode 100644 index 0000000..eb70ad2 --- /dev/null +++ b/crates/ultimate-grpc/src/utils.rs @@ -0,0 +1,65 @@ +use futures::{Future, TryFutureExt}; +use prost_types::FieldMask; +use tonic::{metadata::MetadataMap, service::RoutesBuilder, transport::Server, Status}; +use ultimate::{ + configuration::model::{GrpcConf, SecurityConf}, + security::{jose::JwtPayload, SecurityUtils}, + DataError, +}; + +pub fn init_grpc_server<'b, F>( + conf: &GrpcConf, + _encoded_file_descriptor_sets: impl IntoIterator, + f: F, +) -> ultimate::Result>> +where + F: FnOnce(&mut RoutesBuilder), +{ + let grpc_addr = conf.server_addr.parse()?; + + #[cfg(not(feature = "tonic-web"))] + let mut b = Server::builder(); + + #[cfg(feature = "tonic-web")] + let mut b = Server::builder().accept_http1(true).layer(tonic_web::GrpcWebLayer::new()); + + let mut routes_builder = RoutesBuilder::default(); + f(&mut routes_builder); + + #[cfg(feature = "tonic-reflection")] + { + let rb = _encoded_file_descriptor_sets + .into_iter() + .fold(tonic_reflection::server::Builder::configure(), |rb, set| rb.register_encoded_file_descriptor_set(set)); + let service = rb.build_v1().unwrap(); + routes_builder.add_service(service); + } + + // let s = router.into_service(); + + let serve = b.add_routes(routes_builder.routes()).serve(grpc_addr).map_err(DataError::from); + Ok(serve) +} + +pub fn extract_jwt_payload_from_metadata( + sc: &SecurityConf, + metadata: &MetadataMap, +) -> Result { + let token = extract_token_from_metadata(metadata)?; + let (payload, _) = SecurityUtils::decrypt_jwt(sc.pwd(), token).map_err(|e| Status::unauthenticated(e.to_string()))?; + Ok(payload) +} + +pub fn extract_token_from_metadata(metadata: &MetadataMap) -> Result<&str, tonic::Status> { + let auth_header = + metadata.get("authorization").ok_or_else(|| Status::unauthenticated("Missing authorization header"))?; + let auth_str = auth_header.to_str().map_err(|_| Status::unauthenticated("Invalid authorization header"))?; + let offset = if auth_str.starts_with("Bearer ") { 7 } else { 0 }; + + Ok(&auth_str[offset..]) +} + +// 当 paths 为空或者 paths 包含以 path 开头的路径时返回 true,否则返回 false +pub fn field_mask_match_with(field_mask: &FieldMask, path: &str) -> bool { + field_mask.paths.is_empty() || field_mask.paths.iter().any(|p| p.starts_with(path)) +} diff --git a/crates/ultimate-web/Cargo.toml b/crates/ultimate-web/Cargo.toml index c1e297d..23ca34c 100644 --- a/crates/ultimate-web/Cargo.toml +++ b/crates/ultimate-web/Cargo.toml @@ -11,13 +11,14 @@ repository.workspace = true workspace = true [features] -default = ["with-ulid"] -with-ulid = ["ultimate/ulid"] +# default = ["with-uuid"] uuid = ["dep:uuid", "ultimate/uuid"] +tonic = ["dep:tonic", "ultimate/tonic"] [dependencies] ultimate-common.workspace = true ultimate.workspace = true +ultimate-api.workspace = true tokio.workspace = true tracing.workspace = true mime.workspace = true @@ -30,5 +31,5 @@ serde_json.workspace = true serde_with.workspace = true ulid.workspace = true uuid = { workspace = true, optional = true } -tonic = { workspace = true, optional = true } utoipa = { workspace = true, optional = true } +tonic = { workspace = true, optional = true } diff --git a/crates/ultimate-web/src/error.rs b/crates/ultimate-web/src/error.rs index 6989f45..e89c16b 100644 --- a/crates/ultimate-web/src/error.rs +++ b/crates/ultimate-web/src/error.rs @@ -69,8 +69,7 @@ impl From for AppError { DataError::ParseIntError(e) => Self::new(e.to_string()), DataError::IoError(e) => Self::new(e.to_string()), DataError::JsonError(e) => Self::new(e.to_string()), - #[cfg(feature = "tonic")] - DataError::GrpcTransportError(e) => Self::new(e.to_string()), + DataError::TaskJoinError(e) => Self::new(e.to_string()), } } } diff --git a/crates/ultimate-web/src/server.rs b/crates/ultimate-web/src/server.rs index b4c60ae..6331788 100644 --- a/crates/ultimate-web/src/server.rs +++ b/crates/ultimate-web/src/server.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use axum::Router; use tower_http::{ compression::CompressionLayer, @@ -8,7 +10,7 @@ use tracing::info; use ultimate::configuration::UltimateConfig; -pub async fn init_server(conf: &UltimateConfig, 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-web/src/util.rs b/crates/ultimate-web/src/util.rs index f75b6eb..6eb6e1e 100644 --- a/crates/ultimate-web/src/util.rs +++ b/crates/ultimate-web/src/util.rs @@ -7,7 +7,7 @@ use axum_extra::headers::{Authorization, HeaderMapExt}; use serde::de::DeserializeOwned; use serde::Serialize; use ulid::Ulid; -use ultimate::configuration::model::SecruityConfig; +use ultimate::configuration::model::SecurityConf; use ultimate::ctx::Ctx; use ultimate::security::{AccessToken, SecurityUtils}; use ultimate::{DataError, IdI64Result, IdUlidResult}; @@ -31,8 +31,8 @@ pub fn ok_ulid(id: Ulid) -> AppResult { Ok(IdUlidResult::new(id).into()) } -#[inline] #[cfg(feature = "uuid")] +#[inline] pub fn ok_uuid(id: uuid::Uuid) -> AppResult { Ok(ultimate::IdUuidResult::new(id).into()) } @@ -42,7 +42,7 @@ pub fn unauthorized_app_error(msg: impl Into) -> (StatusCode, Json Result { +pub fn extract_session(parts: &Parts, sc: &SecurityConf) -> Result { let req_time = time::now(); let token = if let Some(Authorization(bearer)) = parts.headers.typed_get::>() { diff --git a/crates/ultimate/Cargo.toml b/crates/ultimate/Cargo.toml index 51b43bd..dec363b 100644 --- a/crates/ultimate/Cargo.toml +++ b/crates/ultimate/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [features] default = ["tracing-appender"] +tonic = ["dep:tonic", "dep:prost"] [dependencies] ultimate-common.workspace = true @@ -36,9 +37,14 @@ josekit.workspace = true serde.workspace = true serde_json.workspace = true serde_with.workspace = true -tonic = { workspace = true, optional = true } +serde_repr.workspace = true utoipa = { workspace = true, optional = true } +tonic = { workspace = true, optional = true } +prost = { workspace = true, optional = true } [dev-dependencies] dotenvy.workspace = true anyhow.workspace = true + +[build-dependencies] +prost-build = { workspace = true } diff --git a/crates/ultimate/build.rs b/crates/ultimate/build.rs new file mode 100644 index 0000000..4322248 --- /dev/null +++ b/crates/ultimate/build.rs @@ -0,0 +1,13 @@ +// use std::{env, path::PathBuf}; + +fn main() { + // println!("cargo::rerun-if-changed=proto/ultimate/"); + + // let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + // prost_build::Config::new() + // .type_attribute("ultimate.v1.SortByDirection", r#"#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]"#) + // .file_descriptor_set_path(out_dir.join("ultimate_descriptor.bin")) + // .compile_protos(&["proto/ultimate/v1/ultimate.proto"], &["proto/ultimate"]) + // .unwrap(); +} diff --git a/crates/ultimate/src/configuration/model/app_config.rs b/crates/ultimate/src/configuration/model/app_conf.rs similarity index 86% rename from crates/ultimate/src/configuration/model/app_config.rs rename to crates/ultimate/src/configuration/model/app_conf.rs index 75fa7e8..a84a5a7 100644 --- a/crates/ultimate/src/configuration/model/app_config.rs +++ b/crates/ultimate/src/configuration/model/app_conf.rs @@ -3,12 +3,12 @@ use serde::{Deserialize, Serialize}; use crate::RunMode; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AppConfig { +pub struct AppConf { run_mode: RunMode, name: String, } -impl AppConfig { +impl AppConf { pub fn run_mode(&self) -> &RunMode { &self.run_mode } diff --git a/crates/ultimate/src/configuration/model/db_config.rs b/crates/ultimate/src/configuration/model/db_conf.rs similarity index 98% rename from crates/ultimate/src/configuration/model/db_config.rs rename to crates/ultimate/src/configuration/model/db_conf.rs index cab3599..1eaa2a3 100644 --- a/crates/ultimate/src/configuration/model/db_config.rs +++ b/crates/ultimate/src/configuration/model/db_conf.rs @@ -5,7 +5,7 @@ use std::time::Duration; use ultimate_common::model::sensitive::UriString; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DbConfig { +pub struct DbConf { enable: bool, /// The URI of the database @@ -17,6 +17,7 @@ pub struct DbConfig { socket: Option, database: Option, username: Option, + #[serde(skip_serializing)] password: Option, @@ -49,7 +50,7 @@ pub struct DbConfig { schema_search_path: Option, } -impl DbConfig { +impl DbConf { pub fn enable(&self) -> bool { self.enable } diff --git a/crates/ultimate/src/configuration/model/grpc_config.rs b/crates/ultimate/src/configuration/model/grpc_conf.rs similarity index 75% rename from crates/ultimate/src/configuration/model/grpc_config.rs rename to crates/ultimate/src/configuration/model/grpc_conf.rs index db58b2d..6b4a808 100644 --- a/crates/ultimate/src/configuration/model/grpc_config.rs +++ b/crates/ultimate/src/configuration/model/grpc_conf.rs @@ -4,18 +4,18 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default)] -pub struct GrpcConfig { +pub struct GrpcConf { pub enable: bool, pub server_addr: String, pub plaintext: bool, - pub clients: HashMap, + pub clients: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GrpcClientConfig { +pub struct GrpcClientConf { pub addr: String, pub plaintext: bool, diff --git a/crates/ultimate/src/configuration/model/mod.rs b/crates/ultimate/src/configuration/model/mod.rs index 205093e..56b3839 100644 --- a/crates/ultimate/src/configuration/model/mod.rs +++ b/crates/ultimate/src/configuration/model/mod.rs @@ -1,13 +1,13 @@ -mod app_config; -mod db_config; -mod grpc_config; +mod app_conf; +mod db_conf; +mod grpc_conf; mod security_config; mod trace_config; mod web_config; -pub use app_config::*; -pub use db_config::*; -pub use grpc_config::*; +pub use app_conf::*; +pub use db_conf::*; +pub use grpc_conf::*; pub use security_config::*; pub use trace_config::*; pub use web_config::*; diff --git a/crates/ultimate/src/configuration/model/security_config.rs b/crates/ultimate/src/configuration/model/security_config.rs index 54be27d..b6a0591 100644 --- a/crates/ultimate/src/configuration/model/security_config.rs +++ b/crates/ultimate/src/configuration/model/security_config.rs @@ -5,12 +5,12 @@ use ultimate_common::{ }; #[derive(Clone, Deserialize, Serialize)] -pub struct SecruityConfig { +pub struct SecurityConf { pwd: PwdConf, token: TokenConf, } -impl SecruityConfig { +impl SecurityConf { pub fn pwd(&self) -> &PwdConf { &self.pwd } diff --git a/crates/ultimate/src/configuration/ultimate_config.rs b/crates/ultimate/src/configuration/ultimate_config.rs index 8f285e0..5fdb27d 100644 --- a/crates/ultimate/src/configuration/ultimate_config.rs +++ b/crates/ultimate/src/configuration/ultimate_config.rs @@ -2,25 +2,25 @@ use config::Config; use serde::de::{Unexpected, Visitor}; use serde::{Deserialize, Deserializer, Serialize}; -use super::model::{AppConfig, DbConfig, GrpcConfig, SecruityConfig, TraceConfig, WebConfig}; +use super::model::{AppConf, DbConf, GrpcConf, SecurityConf, TraceConfig, WebConfig}; #[derive(Clone, Serialize, Deserialize)] pub struct UltimateConfig { - app: AppConfig, + app: AppConf, - security: SecruityConfig, + security: SecurityConf, - db: DbConfig, + db: DbConf, trace: TraceConfig, web: WebConfig, - grpc: GrpcConfig, + grpc: GrpcConf, } impl UltimateConfig { - pub fn app(&self) -> &AppConfig { + pub fn app(&self) -> &AppConf { &self.app } @@ -28,11 +28,11 @@ impl UltimateConfig { &self.web } - pub fn security(&self) -> &SecruityConfig { + pub fn security(&self) -> &SecurityConf { &self.security } - pub fn db(&self) -> &DbConfig { + pub fn db(&self) -> &DbConf { &self.db } @@ -40,7 +40,7 @@ impl UltimateConfig { &self.trace } - pub fn grpc(&self) -> &GrpcConfig { + pub fn grpc(&self) -> &GrpcConf { &self.grpc } } diff --git a/crates/ultimate/src/ctx.rs b/crates/ultimate/src/ctx.rs index 1609263..85ba94c 100644 --- a/crates/ultimate/src/ctx.rs +++ b/crates/ultimate/src/ctx.rs @@ -1,7 +1,6 @@ use std::{ops::Deref, sync::Arc}; use josekit::jwt::JwtPayload; - use ultimate_common::time::{self, DateTime, Duration, Utc, UtcDateTime}; use crate::DataError; @@ -122,12 +121,6 @@ impl Deref for Ctx { } } -// impl DerefMut for Ctx { -// fn deref_mut(&mut self) -> &mut Self::Target { -// &mut self.0 -// } -// } - impl TryFrom for Ctx { type Error = DataError; diff --git a/crates/ultimate/src/data_error.rs b/crates/ultimate/src/data_error.rs index 8893f1c..d6249f1 100644 --- a/crates/ultimate/src/data_error.rs +++ b/crates/ultimate/src/data_error.rs @@ -30,9 +30,8 @@ pub enum DataError { #[error(transparent)] JsonError(#[from] serde_json::Error), - #[cfg(feature = "tonic")] #[error(transparent)] - GrpcTransportError(#[from] tonic::transport::Error), + TaskJoinError(#[from] tokio::task::JoinError), } impl DataError { @@ -89,6 +88,20 @@ impl Serialize for DataError { } } +#[cfg(feature = "tonic")] +impl From for DataError { + fn from(value: prost::UnknownEnumValue) -> Self { + DataError::BizError { code: 400, msg: format!("Unknown enum value: {}", value) } + } +} + +#[cfg(feature = "tonic")] +impl From for DataError { + fn from(value: tonic::transport::Error) -> Self { + DataError::server_error(format!("Grpc transport error: {}", value)) + } +} + #[cfg(feature = "tonic")] impl From for DataError { fn from(value: tonic::Status) -> Self { @@ -127,7 +140,7 @@ impl From for tonic::Status { DataError::ParseIntError(ex) => tonic::Status::from_error(ex.into()), DataError::IoError(e) => tonic::Status::internal(e.to_string()), DataError::JsonError(ex) => tonic::Status::from_error(ex.into()), - DataError::GrpcTransportError(ex) => tonic::Status::from_error(ex.into()), + DataError::TaskJoinError(ex) => tonic::Status::from_error(ex.into()), } } } diff --git a/crates/ultimate/src/run_mode.rs b/crates/ultimate/src/run_mode.rs index 8756d2f..b39b329 100644 --- a/crates/ultimate/src/run_mode.rs +++ b/crates/ultimate/src/run_mode.rs @@ -35,7 +35,7 @@ impl<'de> Deserialize<'de> for RunMode { where D: Deserializer<'de>, { - static MSG: &str = "expect in ('dev', 'test', 'demo', 'prod')."; + static MSG: &str = "expect in ('DEV', 'TEST', 'DEMO', 'PROD')."; struct StrToRunMode; diff --git a/crates/ultimate/src/security/jose.rs b/crates/ultimate/src/security/jose.rs index 290cd55..3714fc3 100644 --- a/crates/ultimate/src/security/jose.rs +++ b/crates/ultimate/src/security/jose.rs @@ -82,7 +82,7 @@ mod tests { use super::*; use crate::configuration::{ load_config, - model::{KeyConf, SecruityConfig}, + model::{KeyConf, SecurityConf}, }; use std::{ sync::OnceLock, @@ -178,10 +178,10 @@ mod tests { // static PRIVATE_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/data/pem/EC_P-256_private.pem"); // static PUBLIC_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/data/pem/EC_P-256_public.pem"); static EXPIRES_AT: OnceLock = OnceLock::new(); - static SECURITY_CONFIG: OnceLock = OnceLock::new(); - fn helper() -> (&'static SecruityConfig, &'static SystemTime) { + static SECURITY_CONFIG: OnceLock = OnceLock::new(); + fn helper() -> (&'static SecurityConf, &'static SystemTime) { ( - SECURITY_CONFIG.get_or_init(|| load_config().unwrap().get::("ultimate.security").unwrap()), + SECURITY_CONFIG.get_or_init(|| load_config().unwrap().get::("ultimate.security").unwrap()), EXPIRES_AT.get_or_init(|| SystemTime::now().checked_add(Duration::from_secs(60 * 60 * 24 * 30)).unwrap()), ) } diff --git a/crates/weixin-common/Cargo.toml b/crates/weixin-common/Cargo.toml new file mode 100644 index 0000000..e16f4d7 --- /dev/null +++ b/crates/weixin-common/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "weixin-common" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +description.workspace = true +license-file.workspace = true +repository.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/weixin-common/src/lib.rs b/crates/weixin-common/src/lib.rs new file mode 100644 index 0000000..78ba4fe --- /dev/null +++ b/crates/weixin-common/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/crates/weixin-sdk/Cargo.toml b/crates/weixin-sdk/Cargo.toml new file mode 100644 index 0000000..42edf9f --- /dev/null +++ b/crates/weixin-sdk/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "weixin-sdk" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +description.workspace = true +license-file.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +weixin-common = { workspace = true } diff --git a/crates/weixin-sdk/src/lib.rs b/crates/weixin-sdk/src/lib.rs new file mode 100644 index 0000000..78ba4fe --- /dev/null +++ b/crates/weixin-sdk/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/crates/wework-sdk/Cargo.toml b/crates/wework-sdk/Cargo.toml index a426c06..6bb2682 100644 --- a/crates/wework-sdk/Cargo.toml +++ b/crates/wework-sdk/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true readme = "README.md" [dependencies] +weixin-common = { workspace = true } thiserror.workspace = true tokio.workspace = true tokio-stream.workspace = true @@ -25,7 +26,6 @@ strum.workspace = true [dev-dependencies] anyhow.workspace = true asserhttp = { workspace = true } -reqwest = { workspace = true } config.workspace = true dotenvy.workspace = true tracing-log.workspace = true diff --git a/examples/api-example/Cargo.toml b/examples/api-example/Cargo.toml index d233940..699a81c 100644 --- a/examples/api-example/Cargo.toml +++ b/examples/api-example/Cargo.toml @@ -17,6 +17,7 @@ workspace = true [dependencies] ultimate-common = { workspace = true } ultimate = { workspace = true, features = ["ulid"] } +ultimate-api = { workspace = true, features = ["utoipa"] } ultimate-web = { workspace = true } ultimate-db = { workspace = true } thiserror.workspace = true @@ -34,7 +35,10 @@ sea-query.workspace = true sea-query-binder.workspace = true modql.workspace = true enum-iterator.workspace = true -derive-new = "0.6" +derive-new = "0.7" [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator.workspace = true + +[dev-dependencies] +pretty_assertions.workspace = true diff --git a/examples/api-example/README.md b/examples/api-example/README.md index 8f799fe..3058554 100644 --- a/examples/api-example/README.md +++ b/examples/api-example/README.md @@ -1 +1,5 @@ # 使用 Rust 开发高效能的 API 服务 + +```sh +cargo run -p api-example --bin api-example +``` diff --git a/examples/api-example/bin/api-example.rs b/examples/api-example/bin/api-example.rs index 41dbd33..fd66a45 100644 --- a/examples/api-example/bin/api-example.rs +++ b/examples/api-example/bin/api-example.rs @@ -1,5 +1,4 @@ -use api_example::{router::new_api_router, state::new_app_state}; -use ultimate_web::server::init_server; +use api_example::{app::new_app_state, router::start_router}; #[cfg(not(target_env = "msvc"))] #[global_allocator] @@ -7,10 +6,8 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; #[tokio::main] async fn main() -> ultimate::Result<()> { - let state = new_app_state().await?; - let conf = state.ultimate_config(); - let router = new_api_router(state.clone()); + let app = new_app_state().await?; - init_server(conf, router).await?; + start_router(app).await?; Ok(()) } diff --git a/examples/api-example/src/state.rs b/examples/api-example/src/app.rs similarity index 94% rename from examples/api-example/src/state.rs rename to examples/api-example/src/app.rs index f769ed1..cdb8786 100644 --- a/examples/api-example/src/state.rs +++ b/examples/api-example/src/app.rs @@ -13,8 +13,8 @@ use crate::ctx::{CtxW, RequestMetadata}; #[derive(Clone, TypedBuilder, Getters)] pub struct AppState { - config_state: ConfigState, - db_state: DbState, + pub config_state: ConfigState, + pub db_state: DbState, } impl AppState { diff --git a/examples/api-example/src/auth/auth_serv.rs b/examples/api-example/src/auth/auth_serv.rs index 0e5061e..917b5ec 100644 --- a/examples/api-example/src/auth/auth_serv.rs +++ b/examples/api-example/src/auth/auth_serv.rs @@ -8,7 +8,7 @@ use ultimate::{security::pwd::verify_pwd, Result}; use ultimate_web::AppError; use crate::{ - state::AppState, + app::AppState, user::{UserFilter, UserServ}, util::make_token, }; diff --git a/examples/api-example/src/auth/web.rs b/examples/api-example/src/auth/web.rs index 5614eff..5a0ed1d 100644 --- a/examples/api-example/src/auth/web.rs +++ b/examples/api-example/src/auth/web.rs @@ -1,7 +1,7 @@ use axum::{routing::post, Json, Router}; use ultimate_web::{ok, AppResult}; -use crate::state::AppState; +use crate::app::AppState; use super::{AuthServ, LoginByPwdReq, LoginResp}; diff --git a/examples/api-example/src/ctx.rs b/examples/api-example/src/ctx.rs index fa4f935..7c2c5e1 100644 --- a/examples/api-example/src/ctx.rs +++ b/examples/api-example/src/ctx.rs @@ -11,7 +11,7 @@ use ultimate::ctx::Ctx; use ultimate_db::ModelManager; use ultimate_web::{extract_session, AppError}; -use crate::state::AppState; +use crate::app::AppState; static X_APP_VERSION: &str = "X-APP-VARSION"; static X_DEVICE_ID: &str = "X-DEVICE-ID"; diff --git a/examples/api-example/src/lib.rs b/examples/api-example/src/lib.rs index 2233b8d..d43468a 100644 --- a/examples/api-example/src/lib.rs +++ b/examples/api-example/src/lib.rs @@ -1,6 +1,6 @@ +pub mod app; mod auth; pub mod ctx; pub mod router; -pub mod state; mod user; mod util; diff --git a/examples/api-example/src/router.rs b/examples/api-example/src/router.rs index 18191b8..4381845 100644 --- a/examples/api-example/src/router.rs +++ b/examples/api-example/src/router.rs @@ -1,7 +1,16 @@ +use std::future::Future; + use axum::Router; +use ultimate_web::server::init_server; -use crate::{auth::auth_routes, state::AppState, user::user_routes}; +use crate::{app::AppState, auth::auth_routes, user::user_routes}; -pub fn new_api_router(app_state: AppState) -> Router { +fn new_api_router(app_state: AppState) -> Router { Router::new().nest("/v1/user", user_routes()).nest("/auth", auth_routes()).with_state(app_state) } + +pub fn start_router(app: AppState) -> impl Future> { + let conf = app.config_state.ultimate_config_clone(); + let router = new_api_router(app); + init_server(conf, router) +} diff --git a/examples/api-example/src/user/user_bmc.rs b/examples/api-example/src/user/user_bmc.rs index 081ba49..bd1f6a5 100644 --- a/examples/api-example/src/user/user_bmc.rs +++ b/examples/api-example/src/user/user_bmc.rs @@ -1,4 +1,4 @@ -use ultimate_db::{base::DbBmc, generate_common_bmc_fns}; +use ultimate_db::{base::DbBmc, generate_common_bmc_fns, generate_filter_bmc_fns}; use super::{User, UserFilter, UserForCreate, UserForUpdate}; @@ -13,5 +13,10 @@ generate_common_bmc_fns!( Entity: User, ForCreate: UserForCreate, ForUpdate: UserForUpdate, +); + +generate_filter_bmc_fns!( + Bmc: UserBmc, + Entity: User, Filter: UserFilter, ); diff --git a/examples/api-example/src/user/user_credential_bmc.rs b/examples/api-example/src/user/user_credential_bmc.rs index c86062b..7d52d1b 100644 --- a/examples/api-example/src/user/user_credential_bmc.rs +++ b/examples/api-example/src/user/user_credential_bmc.rs @@ -1,4 +1,4 @@ -use ultimate_db::{base::DbBmc, generate_common_bmc_fns}; +use ultimate_db::{base::DbBmc, generate_common_bmc_fns, generate_filter_bmc_fns}; use super::{UserCredential, UserCredentialFilter, UserCredentialForCreate, UserCredentialForUpdate}; @@ -13,5 +13,10 @@ generate_common_bmc_fns!( Entity: UserCredential, ForCreate: UserCredentialForCreate, ForUpdate: UserCredentialForUpdate, +); + +generate_filter_bmc_fns!( + Bmc: UserCredentialBmc, + Entity: UserCredential, Filter: UserCredentialFilter, ); diff --git a/examples/api-example/src/user/user_model.rs b/examples/api-example/src/user/user_model.rs index 897fb52..e342617 100644 --- a/examples/api-example/src/user/user_model.rs +++ b/examples/api-example/src/user/user_model.rs @@ -8,8 +8,9 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use sqlx::prelude::FromRow; use ultimate::{DataError, Result}; +use ultimate_api::v1::{Page, PagePayload, Pagination}; use ultimate_common::{regex, time::UtcDateTime}; -use ultimate_db::{to_sea_chrono_utc, DbRowType, Page, PagePayload, Pagination}; +use ultimate_db::{to_sea_chrono_utc, DbRowType}; #[derive(Debug, Serialize, FromRow, Fields)] #[enum_def] @@ -120,11 +121,11 @@ pub struct UserFilter { #[derive(Debug, Serialize)] pub struct UserPage { pub page: Page, - pub records: Vec, + pub items: Vec, } impl From> for UserPage { fn from(value: PagePayload) -> Self { - Self { page: value.page, records: value.records } + Self { page: value.page, items: value.items } } } diff --git a/examples/api-example/src/user/user_serv.rs b/examples/api-example/src/user/user_serv.rs index 27beb70..343914d 100644 --- a/examples/api-example/src/user/user_serv.rs +++ b/examples/api-example/src/user/user_serv.rs @@ -8,7 +8,7 @@ use derive_new::new; use ultimate::{DataError, Result}; use ultimate_web::AppError; -use crate::{ctx::CtxW, state::AppState}; +use crate::{app::AppState, ctx::CtxW}; use super::{ User, UserBmc, UserCredential, UserCredentialBmc, UserFilter, UserForCreate, UserForPage, UserForUpdate, UserPage, @@ -26,12 +26,13 @@ impl UserServ { } pub async fn page(&self, req: UserForPage) -> Result { - let page = UserBmc::page(self.ctx.mm(), req.page.unwrap_or_default(), req.filter.unwrap_or_default()).await?; + let page = + UserBmc::page(self.ctx.mm(), req.filter.into_iter().collect::>(), req.page.unwrap_or_default()).await?; Ok(page.into()) } - pub async fn get_by_id(&self, id: i64) -> Result { - let u = UserBmc::get_by_id(self.ctx.mm(), id).await?; + pub async fn find_by_id(&self, id: i64) -> Result { + let u = UserBmc::find_by_id(self.ctx.mm(), id).await?; Ok(u) } @@ -46,8 +47,9 @@ impl UserServ { } pub(crate) async fn get_fetch_credential(&self, req: UserFilter) -> Result<(User, UserCredential)> { - let u = UserBmc::find(self.ctx.mm(), req).await?.ok_or_else(|| DataError::not_found("User not exists."))?; - let uc = UserCredentialBmc::get_by_id(self.ctx.mm(), u.id).await?; + let u = + UserBmc::find_unique(self.ctx.mm(), vec![req]).await?.ok_or_else(|| DataError::not_found("User not exists."))?; + let uc = UserCredentialBmc::find_by_id(self.ctx.mm(), u.id).await?; Ok((u, uc)) } } diff --git a/examples/api-example/src/user/web.rs b/examples/api-example/src/user/web.rs index 0100fa9..e0e602b 100644 --- a/examples/api-example/src/user/web.rs +++ b/examples/api-example/src/user/web.rs @@ -6,7 +6,7 @@ use axum::{ use ultimate::IdI64Result; use ultimate_web::{ok, AppResult}; -use crate::state::AppState; +use crate::app::AppState; use super::{User, UserForCreate, UserForPage, UserForUpdate, UserPage, UserServ}; @@ -28,7 +28,7 @@ async fn page_user(user_serv: UserServ, Json(req): Json) -> AppResu } async fn get_user(user_serv: UserServ, Path(id): Path) -> AppResult { - let u = user_serv.get_by_id(id).await?; + let u = user_serv.find_by_id(id).await?; ok(u) } diff --git a/examples/api-example/src/util.rs b/examples/api-example/src/util.rs index 9754e99..35aef74 100644 --- a/examples/api-example/src/util.rs +++ b/examples/api-example/src/util.rs @@ -1,10 +1,10 @@ use ultimate::{ - configuration::model::SecruityConfig, + configuration::model::SecurityConf, security::{jose::JwtPayload, SecurityUtils}, DataError, Result, }; -pub fn make_token(sc: &SecruityConfig, uid: i64) -> Result { +pub fn make_token(sc: &SecurityConf, uid: i64) -> Result { let mut payload = JwtPayload::new(); payload.set_subject(uid.to_string()); diff --git a/examples/postgresql.conf b/examples/postgresql.conf deleted file mode 100644 index 955f382..0000000 --- a/examples/postgresql.conf +++ /dev/null @@ -1 +0,0 @@ -log_statement = "all" diff --git a/examples/software/postgres/scripts/03-ultimate-iam.sql b/examples/software/postgres/scripts/03-ultimate-iam.sql index c5c0662..917d5af 100644 --- a/examples/software/postgres/scripts/03-ultimate-iam.sql +++ b/examples/software/postgres/scripts/03-ultimate-iam.sql @@ -13,6 +13,7 @@ create table if not exists iam.user constraint user_uk_phone unique, name varchar, status int not null, + gender int not null, cid bigint not null, ctime timestamptz not null, mid bigint, @@ -20,7 +21,7 @@ create table if not exists iam.user constraint user_pk primary key (id) ); -- --- UserCredential +-- User Credential create table if not exists iam.user_credential ( id bigint not null @@ -32,15 +33,101 @@ create table if not exists iam.user_credential mtime timestamptz, constraint user_credential_pk primary key (id) ); - +-- +-- Role +create table if not exists iam.role +( + id bigserial not null, + name varchar(50) not null, + description text, + status int not null, + cid bigint not null, + ctime timestamptz not null, + mid bigint, + mtime timestamptz, + constraint role_pk primary key (id) +); +-- +-- Permission +create table if not exists iam.permission +( + id bigserial not null, + code varchar(255) not null + constraint permission_uk unique, + description text, + resource varchar(255) not null, + action varchar(255) not null, + cid bigint not null, + ctime timestamptz not null, + mid bigint, + mtime timestamptz, + constraint permission_pk primary key (id) +); +-- +-- User Role Relation +create table if not exists iam.user_role +( + user_id bigint not null, + role_id bigint not null, + cid bigint not null, + ctime timestamptz not null, + constraint user_role_pk primary key (user_id, role_id), + constraint user_role_fk_user foreign key (user_id) references iam.user (id), + constraint user_role_fk_role foreign key (role_id) references iam.role (id) +); +-- +-- Role Permission Relation +create table if not exists iam.role_permission +( + role_id bigint not null, + permission_id bigint not null, + cid bigint not null, + ctime timestamptz not null, + constraint role_permission_pk primary key (role_id, permission_id), + constraint role_permission_fk_role foreign key (role_id) references iam.role (id), + constraint role_permission_fk_permission foreign key (permission_id) references iam.permission (id) +); +-- -- initial data ------------------ -insert into iam."user" (id, email, phone, name, status, cid, ctime) -values (1, 'admin@ultimate.com', null, '超管', 100, 1, current_timestamp), - (10000, null, '13912345678', '普通用户', 100, 1, current_timestamp); +insert into iam."user" (id, email, phone, name, status, gender, cid, ctime) +values (1, 'admin@ultimate.com', null, '超管', 100, 0, 1, current_timestamp), + (10000, 'user@ultimate.com', '13912345678', '普通用户', 100, 0, 1, current_timestamp); insert into iam.user_credential (id, encrypted_pwd, cid, ctime) values (1, '#1#$argon2id$v=19$m=19456,t=2,p=1$hAPRw63nW4mdwOd0l0WnmA$wN1i4uYbL+h/FjsaMVae6n93A3LikkqJ4IwiAqr78x0', -- 密码为:2024.Ultimate 1, current_timestamp); -- 重置 user_id_seq,使新用户注册从ID为 10001 开始 alter sequence iam.user_id_seq restart 10001; +-- +-- 初始化数据 +insert into iam.role (id, name, description, cid, ctime) +values (1, '超级管理员', '拥有所有权限的角色', 1, current_timestamp), + (2, '普通用户', '基本权限的角色', 1, current_timestamp); + +insert into iam.permission (id, code, description, resource, action, cid, ctime) +values (1, '用户查看', '查看用户信息的权限', 'user', 'read', 1, current_timestamp), + (2, '用户创建', '创建用户的权限', 'user', 'create', 1, current_timestamp), + (3, '用户更新', '更新用户信息的权限', 'user', 'update', 1, current_timestamp), + (4, '用户删除', '删除用户的权限', 'user', 'delete', 1, current_timestamp); +-- +-- 为超级管理员分配所有权限 +insert into iam.role_permission (role_id, permission_id, cid, ctime) +values (1, 1, 1, current_timestamp), + (1, 2, 1, current_timestamp), + (1, 3, 1, current_timestamp), + (1, 4, 1, current_timestamp); +-- +-- 为普通用户分配查看权限 +insert into iam.role_permission (role_id, permission_id, cid, ctime) +values (2, 1, 1, current_timestamp); +-- +-- 为现有用户分配角色 +insert into iam.user_role (user_id, role_id, cid, ctime) +values (1, 1, 1, current_timestamp), -- 超管用户分配超级管理员角色 + (10000, 2, 1, current_timestamp); +-- 普通用户分配普通用户角色 +-- +-- 重置序列 +alter sequence iam.role_id_seq restart 3; +alter sequence iam.permission_id_seq restart 5;