Skip to content

Commit

Permalink
reach: Feat reach (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
4t145 authored Oct 10, 2023
1 parent c57234c commit ef634b0
Show file tree
Hide file tree
Showing 88 changed files with 6,313 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ members = [
"support/iam",
"support/auth",
"support/enhance-wasm",
"support/reach",
"services/*",
"sdk/*",
"clients/*",
]

[workspace.package]
Expand Down
1 change: 1 addition & 0 deletions basic/src/rbum/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod rbum_rel_agg_dto;
pub mod rbum_rel_attr_dto;
pub mod rbum_rel_dto;
pub mod rbum_rel_env_dto;
pub mod rbum_safe_dto;
pub mod rbum_set_cate_dto;
pub mod rbum_set_dto;
pub mod rbum_set_item_dto;
40 changes: 40 additions & 0 deletions basic/src/rbum/dto/rbum_safe_dto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use serde::{Serialize, Deserialize};
use tardis::{
chrono::{DateTime, Utc},
web::poem_openapi,
};
#[cfg(feature = "default")]
use tardis::db::sea_orm;
#[derive(Debug, Clone, Default, Serialize)]
#[cfg_attr(feature = "default", derive(poem_openapi::Object, sea_orm::FromQueryResult))]
pub struct RbumSafeSummaryResp {
pub id: String,
pub own_paths: String,
pub owner: String,
pub create_time: DateTime<Utc>,
pub update_time: DateTime<Utc>,
}

impl RbumSafeSummaryResp {
pub fn extends_to_detail_resp(self, owner_name: impl Into<String>) -> RbumSafeDetailResp {
RbumSafeDetailResp {
id: self.id,
own_paths: self.own_paths,
owner: self.owner,
owner_name: owner_name.into(),
create_time: self.create_time,
update_time: self.update_time,
}
}
}

#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "default", derive(poem_openapi::Object, sea_orm::FromQueryResult))]
pub struct RbumSafeDetailResp {
pub id: String,
pub own_paths: String,
pub owner: String,
pub owner_name: String,
pub create_time: DateTime<Utc>,
pub update_time: DateTime<Utc>,
}
21 changes: 21 additions & 0 deletions clients/hwsms/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "bios-client-hwsms"
version.workspace = true
authors.workspace = true
homepage.workspace = true
documentation.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
readme.workspace = true
publish.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["reach"]
reach = ["bios-reach"]
[dependencies]
serde.workspace = true
tardis = { workspace = true }
bios-reach = { optional = true, path = "../../support/reach" }
4 changes: 4 additions & 0 deletions clients/hwsms/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod send_sms;
pub use send_sms::*;
mod send_diff_sms;
pub use send_diff_sms::*;
38 changes: 38 additions & 0 deletions clients/hwsms/src/api/send_diff_sms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use serde::Serialize;
use tardis::{
basic::result::TardisResult,
url::Url,
web::reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION},
};

use crate::{model::*, SmsClient};

#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "camelCase")]
/// Referance: https://support.huaweicloud.com/api-msgsms/sms_05_0002.html
pub struct SmsClientBatchDiffSendRequest<'r> {
pub from: &'r str,
pub status_callback: Option<&'r str>,
pub sms_content: &'r [SmsContent<'r>],
pub extend: Option<&'r str>,
}

impl<'r> SmsClientBatchDiffSendRequest<'r> {
pub fn new(from: &'r str) -> Self {
Self { from, ..Default::default() }
}
}

impl SmsClient {
pub async fn send_diff_sms(&self, mut request: SmsClientBatchDiffSendRequest<'_>) -> TardisResult<SmsResponse<Vec<SmsId>>> {
const PATH: &str = "sms/batchSendDiffSms/v1";
let mut headers = HeaderMap::new();
request.status_callback = request.status_callback.or(self.status_callback.as_ref().map(Url::as_str));
headers.insert(AUTHORIZATION, HeaderValue::from_static(Self::AUTH_WSSE_HEADER_VALUE));
self.add_wsse_headers_to(&mut headers)?;
let mut url = self.base_url.clone();
url.set_path(PATH);
let resp = self.inner.post(url).headers(headers).json(&request).send().await?.json().await?;
Ok(resp)
}
}
50 changes: 50 additions & 0 deletions clients/hwsms/src/api/send_sms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use serde::Serialize;
use tardis::{basic::result::TardisResult, serde_json, url::Url, web::reqwest::header::HeaderMap};

use crate::{model::*, SmsClient};

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SendSmsRequest<'r> {
pub from: &'r str,
pub status_callback: Option<&'r str>,
pub extend: Option<&'r str>,
pub to: &'r str,
pub template_id: &'r str,
pub template_paras: Option<String>,
pub signature: Option<&'r str>,
}

impl<'r> SendSmsRequest<'r> {
pub fn new(from: &'r str, sms_content: SmsContent<'r>) -> Self {
let template_paras = if !sms_content.template_paras.is_empty() {
Some(serde_json::to_string(&sms_content.template_paras).expect("string[] to rust String shouldn't fail"))
} else {
None
};
Self {
from,
status_callback: None,
extend: None,
to: sms_content.to,
template_id: sms_content.template_id,
template_paras,
signature: sms_content.signature,
}
}
}

impl SmsClient {
pub async fn send_sms(&self, mut request: SendSmsRequest<'_>) -> TardisResult<SmsResponse<Vec<SmsId>>> {
tardis::log::trace!("send sms request: {:?}", request);
const PATH: &str = "sms/batchSendSms/v1";
request.status_callback = request.status_callback.or(self.status_callback.as_ref().map(Url::as_str));
let mut headers = HeaderMap::new();
self.add_wsse_headers_to(&mut headers)?;
let url = self.get_url(PATH);
let builder = self.inner.post(url).headers(headers).form(&request);
let resp = builder.send().await?.json().await?;
tardis::log::trace!("send sms response: {:?}", resp);
Ok(resp)
}
}
4 changes: 4 additions & 0 deletions clients/hwsms/src/ext/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Extented features
#[cfg(feature = "reach")]
mod reach;
71 changes: 71 additions & 0 deletions clients/hwsms/src/ext/reach.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::{collections::HashSet, sync::{Arc, OnceLock}};

use bios_reach::{
client::{GenericTemplate, SendChannel},
dto::{ContentReplace, ReachChannelKind}, config::ReachConfig, consts::MODULE_CODE,
};
use tardis::{
async_trait::async_trait,
basic::{error::TardisError, result::TardisResult}, TardisFuns,
};

use crate::{SendSmsRequest, SmsContent, SmsClient};

#[async_trait]
impl SendChannel for crate::SmsClient {
fn kind(&self) -> ReachChannelKind {
ReachChannelKind::Sms
}
async fn send(&self, template: GenericTemplate<'_>, content: &ContentReplace, to: &HashSet<&str>) -> TardisResult<()> {
let content = content.render_final_content::<20>(template.content);
tardis::log::trace!("send sms {content}");
let sms_content = SmsContent {
to: &to.iter().fold(
// 11 digits + 1 comma, it's an estimate
String::with_capacity(to.len() * 12),
|mut acc, x| {
if !acc.is_empty() {
acc.push(',');
}
acc.push_str(x);
acc
},
),
template_id: template.sms_template_id.ok_or_else(|| TardisError::conflict("template missing field template_id", "409-reach-bad-template"))?,
template_paras: vec![&content],
signature: template.sms_signature,
};
let from = template.sms_from.ok_or_else(|| TardisError::conflict("template missing field sms_from", "409-reach-bad-template"))?;
let request = SendSmsRequest::new(from, sms_content);
let resp = self.send_sms(request).await?;
if resp.is_error() {
use std::fmt::Write;
let mut error_buffer = String::new();
writeln!(&mut error_buffer, "send sms error [{code}]: {desc}.", code = resp.code, desc = resp.description).expect("write to string shouldn't fail");
if let Some(ids) = resp.result {
writeln!(&mut error_buffer, "Detail: ").expect("write to string shouldn't fail");
for detail in ids {
writeln!(&mut error_buffer, "{:?}", detail).expect("write to string shouldn't fail");
}
}
return Err(TardisError::conflict(&error_buffer, "409-reach-sms-error"));
}
Ok(())
}
}

impl crate::SmsClient {
pub fn from_reach_config() -> Arc<Self> {
static SMS_CLIENT: OnceLock<Arc<SmsClient>> = OnceLock::new();
SMS_CLIENT
.get_or_init(|| {
// this would block thread but it's ok
let config = TardisFuns::cs_config::<ReachConfig>(MODULE_CODE);
let sms_config = &config.sms;
let base_url = sms_config.base_url.parse().expect("invalid sms base url");
let callback_url = sms_config.status_call_back.as_ref().map(|x| x.parse().expect("invalid sms status_call_back url"));
SmsClient::new(base_url, &sms_config.app_key, &sms_config.app_secret, callback_url).into()
})
.clone()
}
}
67 changes: 67 additions & 0 deletions clients/hwsms/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! # Huawei Cloud Platform Sms Client
//! reference: https://support.huaweicloud.com/msgsms/index.html
use tardis::{
basic::result::TardisResult,
chrono::{Utc, SecondsFormat},
rand::random,
url::Url,
web::reqwest::{
header::{HeaderMap, HeaderValue, AUTHORIZATION},
Client,
}, crypto::rust_crypto::sha2::Sha256,
};
mod ext;
mod api;
pub use api::*;
mod model;
pub use model::*;
#[derive(Clone, Debug)]
pub struct SmsClient {
pub(crate) inner: Client,
pub app_key: String,
pub app_secret: String,
pub base_url: Url,
pub status_callback: Option<Url>,
}

impl SmsClient {
const AUTH_WSSE_HEADER_VALUE: &'static str = r#"WSSE realm="SDP",profile="UsernameToken",type="Appkey""#;
fn add_wsse_headers_to(&self, headers: &mut HeaderMap) -> TardisResult<()> {
use tardis::crypto::{crypto_base64::TardisCryptoBase64, crypto_digest::TardisCryptoDigest};
const WSSE_HEADER_NAME: &str = "X-WSSE";
const BASE64: TardisCryptoBase64 = TardisCryptoBase64;
const DIGEST: TardisCryptoDigest = TardisCryptoDigest;
let username = &self.app_key;
// actually iso-8601
let created = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
// and random 1~128bit number
let nonce = format!("{:X}", random::<u128>());
let digest_raw = format!("{}{}{}", nonce, created, &self.app_secret);
let password_digest = BASE64.encode_raw(DIGEST.digest_raw(digest_raw.as_bytes(), Sha256::new())?);
let wsse_header = format!(r#"UsernameToken Username="{username}",PasswordDigest="{password_digest}",Nonce="{nonce}",Created="{created}""#);
let wsse_header = HeaderValue::from_str(&wsse_header).expect("Fail to build sms header, maybe there are unexpected char in app_key.");
headers.insert(AUTHORIZATION, HeaderValue::from_static(Self::AUTH_WSSE_HEADER_VALUE));
headers.insert(WSSE_HEADER_NAME, wsse_header);
Ok(())
}
fn get_url(&self, path: &str) -> Url {
let mut new_url = self.base_url.clone();
let origin_path = new_url.path();
let new_path = [origin_path.trim_end_matches('/'), path.trim_start_matches('/')].join("/");
new_url.set_path(&new_path);
new_url
}
pub fn new(base_url: Url, app_key: impl Into<String>, app_secret: impl Into<String>, status_callback: Option<Url>) -> Self {
let app_key: String = app_key.into();
let app_secret: String = app_secret.into();

SmsClient {
inner: Default::default(),
base_url,
app_key,
app_secret,
status_callback,
}
}
}
40 changes: 40 additions & 0 deletions clients/hwsms/src/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SmsResponse<T> {
pub code: String,
pub description: String,
pub result: Option<T>,
}

impl<T> SmsResponse<T> {
pub fn is_error(&self) -> bool {
self.code.starts_with('E')
}
pub fn is_ok(&self) -> bool {
self.code == "000000"
}
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SmsId {
pub sms_msg_id: String,
pub from: String,
pub origin_to: String,
pub status: String,
pub create_time: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
/// see: [SmsClientBatchDiffSendRequest]
///
/// reference: https://support.huaweicloud.com/api-msgsms/sms_05_0002.html#ZH-CN_TOPIC_0000001430352905__table4039578
pub struct SmsContent<'r> {
pub to: &'r str,
pub template_id: &'r str,
pub template_paras: Vec<&'r str>,
pub signature: Option<&'r str>,
}
20 changes: 15 additions & 5 deletions sdk/invoke/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,26 @@ name = "bios_sdk_invoke"
path = "src/lib.rs"

[features]
default = ["spi_kv", "spi_log", "spi_search"]
spi_kv = []
spi_log = []
spi_search = []
default = ["spi_kv", "spi_log", "spi_search", "iam"]
spi_base = []
spi_kv = ["spi_base"]
spi_log = ["spi_base"]
spi_search = ["spi_base"]
iam = []
macro = ["dep:simple-invoke-client-macro"]
reldb-core=["tardis/reldb-core"]

[dependencies]
serde.workspace = true
lazy_static.workspace = true
itertools.workspace = true
tardis = { workspace = true, features = ["web-server"] }
tardis = { workspace = true, features = ["web-server", "web-client", "crypto"] }
simple-invoke-client-macro = { path = "../simple-invoke-client-macro", optional = true}
[dev-dependencies]
tardis = { workspace = true, features = ["test"] }
simple-invoke-client-macro = { path = "../simple-invoke-client-macro" }


[[test]]
name = "test_macros"
features = ["macro"]
Loading

0 comments on commit ef634b0

Please sign in to comment.