refactor(lib): migrate storage and security tier to central lib/config (auth, cache, db, email, jwt, payment, queue, rate_limit, storage)
zhenyi committed
Modified lib/auth/features/agreement/impls.rs lib/auth/features/agreement/impls.rs +1 −1 View lib/auth/features/agreement/impls.rs
@@ -2,9 +2,9 @@
22
33use super::usecase;
44use super::{AcceptAgreementCmd, AcceptAgreementResp};
5use crate::config::AgreementDoc;
65use crate::error::AuthError;
76use crate::features::AuthService;
7use config::auth::AgreementDoc;
88
99impl AuthService {
1010 pub fn list_agreements(&self) -> Vec<AgreementDoc> {
Modified lib/auth/features/agreement/usecase.rs lib/auth/features/agreement/usecase.rs +2 −2 View lib/auth/features/agreement/usecase.rs
@@ -6,13 +6,13 @@
66 ActiveModel as AcceptActiveModel, Entity as AcceptEntity,
77};
88
9use crate::config::AuthConfig;
109use crate::error::AuthError;
1110use crate::metrics::{self, record_agreement_accept};
11use config::auth::AuthConfig;
1212
1313use super::{AcceptAgreementCmd, AcceptAgreementResp};
1414
15pub fn list_agreements(config: &AuthConfig) -> Vec<crate::config::AgreementDoc> {
15pub fn list_agreements(config: &AuthConfig) -> Vec<config::auth::AgreementDoc> {
1616 vec![
1717 config.agreements.terms.clone(),
1818 config.agreements.privacy.clone(),
Modified lib/auth/features/email_code/usecase.rs lib/auth/features/email_code/usecase.rs +1 −1 View lib/auth/features/email_code/usecase.rs
@@ -14,10 +14,10 @@
1414use db::user::user_emails::{ActiveModel as EmailActiveModel, Entity as EmailEntity};
1515use db::user::users::{ActiveModel as UserActiveModel, Entity as UserEntity};
1616
17use crate::config::AuthConfig;
1817use crate::error::AuthError;
1918use crate::features::TempKey;
2019use crate::metrics::{self, EmailVerifyOutcome, record_email_code_verify};
20use config::auth::AuthConfig;
2121
2222use super::types::{IssueCodeArtifacts, IssueCodeCmd, VerifyCodeCmd, VerifyCodeResult};
2323
Modified lib/auth/features/oauth/oauth_apple/usecase.rs lib/auth/features/oauth/oauth_apple/usecase.rs +1 −1 View lib/auth/features/oauth/oauth_apple/usecase.rs
@@ -2,10 +2,10 @@
22use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
33use serde::Deserialize;
44
5use crate::config::OAuthProviderConfig;
65use crate::error::AuthError;
76use crate::features::oauth::types::{OAuthProvider, ProviderIdentity};
87use crate::metrics::{OAuthProvider as MetricsOAuth, OAuthProviderFetchTimer};
8use config::auth::OAuthProviderConfig;
99
1010#[derive(Debug, Deserialize)]
1111struct AppleTokenResp {
Modified lib/auth/features/oauth/oauth_github/usecase.rs lib/auth/features/oauth/oauth_github/usecase.rs +1 −1 View lib/auth/features/oauth/oauth_github/usecase.rs
@@ -1,9 +1,9 @@
11use serde::Deserialize;
22
3use crate::config::OAuthProviderConfig;
43use crate::error::AuthError;
54use crate::features::oauth::types::{OAuthProvider, ProviderIdentity};
65use crate::metrics::{OAuthProvider as MetricsOAuth, OAuthProviderFetchTimer};
6use config::auth::OAuthProviderConfig;
77
88#[derive(Debug, Deserialize)]
99struct TokenResp {
Modified lib/auth/features/oauth/oauth_google/usecase.rs lib/auth/features/oauth/oauth_google/usecase.rs +1 −1 View lib/auth/features/oauth/oauth_google/usecase.rs
@@ -1,10 +1,10 @@
11use serde::Deserialize;
22use time::OffsetDateTime;
33
4use crate::config::OAuthProviderConfig;
54use crate::error::AuthError;
65use crate::features::oauth::types::{OAuthProvider, ProviderIdentity};
76use crate::metrics::{OAuthProvider as MetricsOAuth, OAuthProviderFetchTimer};
7use config::auth::OAuthProviderConfig;
88
99#[derive(Debug, Deserialize)]
1010struct TokenResp {
Modified lib/auth/features/oauth/usecase.rs lib/auth/features/oauth/usecase.rs +1 −1 View lib/auth/features/oauth/usecase.rs
@@ -18,7 +18,6 @@
1818
1919use cache::redis::RedisError;
2020
21use crate::config::{AuthConfig, OAuthProviderConfig};
2221use crate::error::AuthError;
2322use crate::features::AuthService;
2423use crate::features::TempKey;
@@ -26,6 +25,7 @@
2625 self, JwtPairKind, OAuthAuthorizeOutcome, OAuthCallbackOutcome, OAuthProvider as MetricsOAuth,
2726 OAuthResolutionBranch,
2827};
28use config::auth::{AuthConfig, OAuthProviderConfig};
2929
3030use super::oauth_apple::fetch_apple_identity;
3131use super::oauth_github::fetch_github_identity;
Modified lib/auth/features/password_hash/usecase.rs lib/auth/features/password_hash/usecase.rs +1 −1 View lib/auth/features/password_hash/usecase.rs
@@ -3,9 +3,9 @@
33use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
44use rand::Rng;
55
6use crate::config::PasswordPolicy;
76use crate::error::AuthError;
87use crate::metrics::{PasswordOp, PasswordOpTimer, record_password_policy_rejection};
8use config::auth::PasswordPolicy;
99
1010use super::{PasswordHashString, PasswordInput, ValidatePasswordRequest};
1111
Modified lib/auth/features/register/usecase.rs lib/auth/features/register/usecase.rs +0 −6 View lib/auth/features/register/usecase.rs
@@ -227,9 +227,3 @@
227227 }
228228 result
229229}
230
231impl crate::config::AgreementsConfig {
232 pub fn iter(&self) -> impl Iterator<Item = &crate::config::AgreementDoc> {
233 vec![&self.terms, &self.privacy].into_iter()
234 }
235}
Modified lib/auth/features/mod.rs lib/auth/features/mod.rs +4 −4 View lib/auth/features/mod.rs
@@ -7,8 +7,8 @@
77use serde::{Deserialize, Serialize};
88use utoipa::ToSchema;
99
10use crate::config::{AuthConfig, SharedAuthConfig};
1110use crate::pow::PowService;
11use config::auth::{AuthConfig, SharedAuthConfig};
1212
1313pub mod agreement;
1414pub mod email_code;
@@ -87,10 +87,10 @@
8787 self.kv.clone()
8888 }
8989
90 fn rate_limit_settings(&self) -> Arc<rate_limit::RateLimitConfig> {
91 Arc::new(rate_limit::RateLimitConfig {
90 fn rate_limit_settings(&self) -> Arc<config::rate_limit::RateLimitConfig> {
91 Arc::new(config::rate_limit::RateLimitConfig {
9292 cookie_secure: self.config.cookie_secure,
93 ..rate_limit::RateLimitConfig::default()
93 ..config::rate_limit::RateLimitConfig::default()
9494 })
9595 }
9696}
Modified lib/auth/handler/agreement/list.rs lib/auth/handler/agreement/list.rs +1 −1 View lib/auth/handler/agreement/list.rs
@@ -1,8 +1,8 @@
11use axum::extract::State;
22
3use crate::config::AgreementDoc;
43use crate::error::AuthError;
54use crate::features::AuthService;
5use config::auth::AgreementDoc;
66use export::data::AppResponseData;
77
88#[utoipa::path(
Modified lib/auth/handler/cookies.rs lib/auth/handler/cookies.rs +1 −1 View lib/auth/handler/cookies.rs
@@ -4,8 +4,8 @@
44use axum_extra::extract::cookie::CookieJar;
55use axum_extra::extract::cookie::SameSite;
66
7use crate::config::AuthConfig;
87use crate::features::TempKey;
8use config::auth::AuthConfig;
99
1010pub(crate) fn temp_key_to_cookie(config: &AuthConfig, key: &TempKey) -> Cookie<'static> {
1111 let mut builder = Cookie::build((key.name.clone(), key.value.clone()))
Modified lib/auth/handler/openapi.rs lib/auth/handler/openapi.rs +1 −1 View lib/auth/handler/openapi.rs
@@ -1,6 +1,5 @@
11use utoipa::OpenApi;
22
3use crate::config::AgreementDoc;
43use crate::features::TempKey;
54use crate::features::email_code::types::{VerifyCodeCmd, VerifyCodeResult};
65use crate::features::forgot_password::types::{ForgotPasswordCmd, ForgotPasswordResp};
@@ -13,6 +12,7 @@
1312use crate::features::register::types::{PowProof, RegisterCmd, RegisterResp};
1413use crate::features::resend_code::types::{ResendCodeCmd, ResendCodeResp};
1514use crate::handler::pow::PowChallengeResp;
15use config::auth::AgreementDoc;
1616use export::auth::agreement::{AcceptAgreementCmd, AcceptAgreementResp};
1717use export::auth::logout::{LogoutCmd, LogoutResp};
1818use export::auth::refresh::{RefreshCmd, RefreshResp};
Deleted lib/auth/pow/config.rs lib/auth/pow/config.rs +0 −29
@@ -1,29 +0,0 @@
1use serde::{Deserialize, Serialize};
2use time::Duration;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct PowSettings {
6 pub hmac_secret: String,
7 pub algorithm: String,
8 pub cost: u32,
9 pub key_length: usize,
10 pub key_prefix: String,
11 pub memory_cost: Option<u32>,
12 pub parallelism: Option<u32>,
13 pub challenge_ttl: Duration,
14}
15
16impl Default for PowSettings {
17 fn default() -> Self {
18 Self {
19 hmac_secret: String::new(),
20 algorithm: "PBKDF2/SHA-256".to_string(),
21 cost: 100_000,
22 key_length: 32,
23 key_prefix: "00".to_string(),
24 memory_cost: None,
25 parallelism: None,
26 challenge_ttl: Duration::minutes(5),
27 }
28 }
29}
Modified lib/auth/pow/mod.rs lib/auth/pow/mod.rs +2 −2 View lib/auth/pow/mod.rs
@@ -1,7 +1,7 @@
1mod config;
21mod error;
32
4pub use config::PowSettings;
3use config::pow::PowSettings;
4
55pub use error::PowError;
66
77pub use altcha::{
Modified lib/auth/Cargo.toml lib/auth/Cargo.toml +1 −0 View lib/auth/Cargo.toml
@@ -13,6 +13,7 @@
1313name = "auth"
1414[dependencies]
1515export.workspace = true
16config.workspace = true
1617db.workspace = true
1718email.workspace = true
1819queue.workspace = true
Deleted lib/auth/config.rs lib/auth/config.rs +0 −197
@@ -1,197 +0,0 @@
1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use serde::{Deserialize, Serialize};
5use utoipa::ToSchema;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct PasswordPolicy {
9 pub min_length: usize,
10 #[serde(default = "default_true")]
11 pub require_upper: bool,
12 #[serde(default = "default_true")]
13 pub require_lower: bool,
14 #[serde(default = "default_true")]
15 pub require_digit: bool,
16 #[serde(default)]
17 pub require_symbol: bool,
18}
19
20fn default_true() -> bool {
21 true
22}
23
24impl Default for PasswordPolicy {
25 fn default() -> Self {
26 Self {
27 min_length: 8,
28 require_upper: true,
29 require_lower: true,
30 require_digit: true,
31 require_symbol: false,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
37pub struct OAuthProviderConfig {
38 #[serde(default)]
39 pub enabled: bool,
40 #[serde(default)]
41 pub client_id: String,
42 #[serde(default)]
43 pub client_secret: String,
44 #[serde(default)]
45 pub redirect_uri: String,
46 #[serde(default)]
47 pub scopes: Vec<String>,
48 #[serde(default)]
49 pub authorize_url: String,
50 #[serde(default)]
51 pub token_url: String,
52 #[serde(default)]
53 pub userinfo_url: String,
54 #[serde(default)]
55 pub emails_url: Option<String>,
56 #[serde(default)]
57 pub extra: BTreeMap<String, String>,
58}
59
60#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61pub struct OAuthConfig {
62 #[serde(default)]
63 pub google: Option<OAuthProviderConfig>,
64 #[serde(default)]
65 pub github: Option<OAuthProviderConfig>,
66 #[serde(default)]
67 pub apple: Option<OAuthProviderConfig>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
71pub struct AgreementDoc {
72 pub key: String,
73 pub version: String,
74 pub updated_at: String,
75 pub title: String,
76 pub content_md: String,
77}
78
79impl AgreementDoc {
80 pub fn placeholder(key: &str) -> Self {
81 Self {
82 key: key.to_string(),
83 version: "0.1.0".to_string(),
84 updated_at: "1970-01-01T00:00:00Z".to_string(),
85 title: format!("{} (placeholder)", key),
86 content_md: format!(
87 "# {}\n\nThis document is a placeholder. Please replace before production.",
88 key
89 ),
90 }
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct AgreementsConfig {
96 pub terms: AgreementDoc,
97 pub privacy: AgreementDoc,
98}
99
100impl Default for AgreementsConfig {
101 fn default() -> Self {
102 Self {
103 terms: AgreementDoc::placeholder("terms"),
104 privacy: AgreementDoc::placeholder("privacy"),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct AuthConfig {
111 #[serde(default)]
112 pub password_policy: PasswordPolicy,
113 #[serde(default = "default_verify_code_ttl")]
114 pub verify_code_ttl_secs: u64,
115 #[serde(default = "default_verify_code_len")]
116 pub verify_code_len: usize,
117 #[serde(default = "default_reset_token_ttl")]
118 pub reset_token_ttl_secs: u64,
119 #[serde(default = "default_reset_token_bytes")]
120 pub reset_token_bytes: usize,
121 #[serde(default = "default_cookie_prefix")]
122 pub cookie_name_prefix: String,
123 #[serde(default = "default_true_bool")]
124 pub cookie_secure: bool,
125 #[serde(default = "default_same_site")]
126 pub cookie_same_site: String,
127 #[serde(default)]
128 pub cookie_domain: Option<String>,
129 #[serde(default = "default_oauth_state_ttl")]
130 pub oauth_state_ttl_secs: u64,
131 #[serde(default = "default_email_rate_limit")]
132 pub email_code_rate_limit_secs: u64,
133 #[serde(default = "default_max_verify_attempts")]
134 pub max_verify_attempts: u32,
135 #[serde(default)]
136 pub auto_register_agreements: Vec<String>,
137 #[serde(default)]
138 pub oauth: OAuthConfig,
139 #[serde(default)]
140 pub agreements: AgreementsConfig,
141}
142
143fn default_verify_code_ttl() -> u64 {
144 600
145}
146fn default_verify_code_len() -> usize {
147 6
148}
149fn default_reset_token_ttl() -> u64 {
150 1800
151}
152fn default_reset_token_bytes() -> usize {
153 32
154}
155fn default_cookie_prefix() -> String {
156 "auth_".to_string()
157}
158fn default_true_bool() -> bool {
159 true
160}
161fn default_same_site() -> String {
162 "Strict".to_string()
163}
164fn default_oauth_state_ttl() -> u64 {
165 300
166}
167fn default_email_rate_limit() -> u64 {
168 60
169}
170fn default_max_verify_attempts() -> u32 {
171 5
172}
173
174impl Default for AuthConfig {
175 fn default() -> Self {
176 Self {
177 password_policy: PasswordPolicy::default(),
178 verify_code_ttl_secs: default_verify_code_ttl(),
179 verify_code_len: default_verify_code_len(),
180 reset_token_ttl_secs: default_reset_token_ttl(),
181 reset_token_bytes: default_reset_token_bytes(),
182 cookie_name_prefix: default_cookie_prefix(),
183 cookie_secure: default_true_bool(),
184 cookie_same_site: default_same_site(),
185 cookie_domain: None,
186 oauth_state_ttl_secs: default_oauth_state_ttl(),
187 email_code_rate_limit_secs: default_email_rate_limit(),
188 max_verify_attempts: default_max_verify_attempts(),
189 auto_register_agreements: Vec::new(),
190 oauth: OAuthConfig::default(),
191 agreements: AgreementsConfig::default(),
192 }
193 }
194}
195
196#[allow(dead_code)]
197pub type SharedAuthConfig = Arc<AuthConfig>;
Modified lib/auth/lib.rs lib/auth/lib.rs +1 −6 View lib/auth/lib.rs
@@ -1,14 +1,9 @@
1pub mod config;
21pub mod error;
32pub mod features;
43pub mod handler;
54pub mod metrics;
65pub mod pow;
76
8pub use config::{
9 AgreementDoc, AgreementsConfig, AuthConfig, OAuthConfig, OAuthProviderConfig, PasswordPolicy,
10 SharedAuthConfig,
11};
127pub use error::{AuthError, AuthResult};
138pub use handler::router as auth_router;
149pub use metrics::{
@@ -16,4 +11,4 @@
1611 OAuthResolutionBranch, PasswordOp, PasswordOpTimer, PowVerifyOutcome, Timer, ensure_described,
1712 record_outcome,
1813};
19pub use pow::{Challenge, PowError, PowService, PowSettings, Solution, VerifySolutionResult};
14pub use pow::{Challenge, PowError, PowService, Solution, VerifySolutionResult};
Deleted lib/cache/config/cache.rs lib/cache/config/cache.rs +0 −31
@@ -1,31 +0,0 @@
1use lombok::{Builder, Getter, Setter};
2
3#[derive(
4 serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Getter, Setter, Builder,
5)]
6#[serde(default)]
7pub struct AppCacheSettings {
8 pub enabled: bool,
9 pub ttl_seconds: u64,
10 pub max_capacity: usize,
11 pub max_size: usize,
12 pub enable_disk: bool,
13 pub disk_path: Option<String>,
14 pub disk_cache_max_size: usize,
15 pub disk_cache_max_capacity: usize,
16}
17
18impl Default for AppCacheSettings {
19 fn default() -> Self {
20 Self {
21 enabled: true,
22 ttl_seconds: 300,
23 max_capacity: 10_000,
24 max_size: 50_000_000,
25 enable_disk: false,
26 disk_path: None,
27 disk_cache_max_size: 200_000_000,
28 disk_cache_max_capacity: 100_000,
29 }
30 }
31}
Deleted lib/cache/config/mod.rs lib/cache/config/mod.rs +0 −2
@@ -1,2 +0,0 @@
1pub mod cache;
2pub mod redis;
Deleted lib/cache/config/redis.rs lib/cache/config/redis.rs +0 −33
@@ -1,33 +0,0 @@
1use lombok::{Builder, Getter, Setter};
2
3#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Getter, Setter, Builder)]
4#[serde(default)]
5pub struct AppRedisSettings {
6 pub urls: Vec<String>,
7 pub mode: AppRedisMode,
8 pub connection_timeout_ms: Option<u64>,
9 pub response_timeout_ms: Option<u64>,
10 pub password: Option<String>,
11 pub database: Option<i64>,
12}
13
14#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Default)]
15pub enum AppRedisMode {
16 #[default]
17 Single,
18 Cluster,
19 Sentinel,
20}
21
22impl Default for AppRedisSettings {
23 fn default() -> Self {
24 Self {
25 urls: vec!["redis://127.0.0.1:6379".to_string()],
26 mode: AppRedisMode::default(),
27 connection_timeout_ms: Some(2_000),
28 response_timeout_ms: Some(5_000),
29 password: None,
30 database: Some(0),
31 }
32 }
33}
Modified lib/cache/redis/mod.rs lib/cache/redis/mod.rs +1 −1 View lib/cache/redis/mod.rs
@@ -2,8 +2,8 @@
22
33pub use conn::RedisConnClone;
44
5use crate::config::redis::{AppRedisMode, AppRedisSettings};
65use crate::metrics;
6use config::cache::{AppRedisMode, AppRedisSettings};
77use conn::RedisConn;
88use redis::{
99 AsyncCommands, ConnectionAddr, IntoConnectionInfo, RedisError as RedisDriverError,
Modified lib/cache/Cargo.toml lib/cache/Cargo.toml +1 −0 View lib/cache/Cargo.toml
@@ -13,6 +13,7 @@
1313name = "cache"
1414[dependencies]
1515bincache = { workspace = true }
16config = { workspace = true }
1617redis = { workspace = true }
1718serde = { workspace = true }
1819serde_json = { workspace = true }
Modified lib/cache/hybrid.rs lib/cache/hybrid.rs +1 −1 View lib/cache/hybrid.rs
@@ -1,8 +1,8 @@
11use bincache::strategies::{Hybrid, Limits};
22use bincache::{Cache as Bincache, Noop};
33
4use crate::config::cache::AppCacheSettings;
54use crate::metrics;
5use config::cache::AppCacheSettings;
66use std::collections::HashMap;
77use std::path::Path;
88use std::sync::Arc;
Modified lib/cache/lib.rs lib/cache/lib.rs +1 −3 View lib/cache/lib.rs
@@ -1,11 +1,9 @@
1pub mod config;
21pub mod hybrid;
32pub mod metrics;
43pub mod redis;
54pub mod typed;
65
7use crate::config::cache::AppCacheSettings;
8use crate::config::redis::AppRedisSettings;
6use config::cache::{AppCacheSettings, AppRedisSettings};
97pub use hybrid::Cache;
108pub use redis::RedisCache;
119pub use typed::TypedCache;
Modified lib/db/payment/currency_rates.rs lib/db/payment/currency_rates.rs +0 −2 View lib/db/payment/currency_rates.rs
@@ -15,10 +15,8 @@
1515 pub quote_currency: String,
1616 #[sea_orm(unique_key = "base_quote_provider_date")]
1717 pub provider: PaymentProvider,
18 /// 快照日期(写入方归一化到 UTC 当日零点)
1918 #[sea_orm(unique_key = "base_quote_provider_date")]
2019 pub rate_date: OffsetDateTime,
21 /// 汇率:定点 i64,刻度 1e8(如 0.0723 → 7_230_000)
2220 pub rate: i64,
2321 pub created_at: OffsetDateTime,
2422}
Modified lib/db/payment/payment_intents.rs lib/db/payment/payment_intents.rs +0 −1 View lib/db/payment/payment_intents.rs
@@ -38,7 +38,6 @@
3838 #[sea_orm(default_value = "CNY")]
3939 pub currency: String,
4040 pub status: PaymentIntentStatus,
41 /// PSP 原始状态字符串(调试/对账用),以业务枚举 status 为准
4241 pub provider_status: Option<String>,
4342 #[sea_orm(indexed)]
4443 pub payment_method_id: Option<Uuid>,
Modified lib/db/payment/payment_methods.rs lib/db/payment/payment_methods.rs +0 −1 View lib/db/payment/payment_methods.rs
@@ -31,7 +31,6 @@
3131 #[sea_orm(unique)]
3232 pub provider_method_id: String,
3333 pub method_type: PaymentMethodType,
34 /// PCI 约束:仅存 token/brand/last4/fingerprint,禁止存完整卡号与 CVV
3534 pub card_brand: Option<String>,
3635 pub card_last4: Option<String>,
3736 pub card_exp_month: Option<i32>,
Modified lib/db/payment/payment_settlement_accounts.rs lib/db/payment/payment_settlement_accounts.rs +0 −1 View lib/db/payment/payment_settlement_accounts.rs
@@ -14,7 +14,6 @@
1414 pub provider_account_id: String,
1515 #[sea_orm(default_value = "CNY")]
1616 pub currency: String,
17 /// Airwallex 账户类型开放(virtual_account / funding_account 等),不枚举
1817 pub account_type: String,
1918 pub account_name: Option<String>,
2019 pub account_number_masked: Option<String>,
Modified lib/db/payment/payment_webhook_events.rs lib/db/payment/payment_webhook_events.rs +0 −2 View lib/db/payment/payment_webhook_events.rs
@@ -24,12 +24,10 @@
2424pub struct Model {
2525 #[sea_orm(primary_key)]
2626 pub id: Uuid,
27 /// (provider, provider_event_id) 复合唯一 = 幂等处理锚点
2827 #[sea_orm(unique_key = "provider_event")]
2928 pub provider: PaymentProvider,
3029 #[sea_orm(unique_key = "provider_event")]
3130 pub provider_event_id: String,
32 /// 例如 payment_intent.succeeded / refund.updated
3331 pub event_type: String,
3432 pub payload: JsonValue,
3533 pub status: WebhookEventStatus,
Modified lib/db/payment/subscriptions.rs lib/db/payment/subscriptions.rs +0 −2 View lib/db/payment/subscriptions.rs
@@ -39,9 +39,7 @@
3939 pub space_id: Uuid,
4040 #[sea_orm(unique_key = "space_product")]
4141 pub product_id: Uuid,
42 /// 订阅扣费主体(付费人)
4342 pub user_id: Uuid,
44 /// 每期扣费金额(最小单位)
4543 pub amount: i64,
4644 #[sea_orm(default_value = "CNY")]
4745 pub currency: String,
Modified lib/db/project/project.rs lib/db/project/project.rs +0 −2 View lib/db/project/project.rs
@@ -11,8 +11,6 @@
1111pub struct Model {
1212 #[sea_orm(primary_key, auto_increment = false)]
1313 pub id: Uuid,
14 /// Optional object channel bound to this project once it enters the channel tree.
15 /// `None` while created without a parent dir.
1614 pub channel_id: Option<Uuid>,
1715 pub space_id: Uuid,
1816 pub creator_id: Uuid,
Modified lib/db/Cargo.toml lib/db/Cargo.toml +1 −0 View lib/db/Cargo.toml
@@ -12,6 +12,7 @@
1212path = "lib.rs"
1313name = "db"
1414[dependencies]
15config = { workspace = true }
1516sea-orm = { workspace = true, features = ["schema-sync", "entity-registry"] }
1617serde = { workspace = true }
1718time = { workspace = true }
Deleted lib/db/config.rs lib/db/config.rs +0 −55
@@ -1,55 +0,0 @@
1use lombok::{Builder, Getter, Setter};
2
3#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Getter, Setter, Builder)]
4#[serde(default)]
5pub struct AppDatabaseSettings {
6 pub primary: AppDatabaseConnConfig,
7 pub replicas: Vec<AppDatabaseConnConfig>,
8 pub max_connections_total: u32,
9 pub query_timeout: u64,
10 pub auto_migrate: bool,
11 pub sqlx_logging: bool,
12 pub slow_query_threshold_ms: u64,
13}
14
15#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, Getter, Setter, Builder)]
16#[serde(default)]
17pub struct AppDatabaseConnConfig {
18 pub url: String,
19 pub max_connections: u32,
20 pub min_connections: u32,
21 pub max_lifetime: Option<u64>,
22 pub idle_timeout: Option<u64>,
23 pub acquire_timeout: Option<u64>,
24 pub schema: Option<String>,
25 pub application_name: Option<String>,
26}
27
28impl Default for AppDatabaseSettings {
29 fn default() -> Self {
30 Self {
31 primary: AppDatabaseConnConfig::default(),
32 replicas: vec![],
33 max_connections_total: 100,
34 query_timeout: 30,
35 auto_migrate: true,
36 sqlx_logging: false,
37 slow_query_threshold_ms: 500,
38 }
39 }
40}
41
42impl Default for AppDatabaseConnConfig {
43 fn default() -> Self {
44 Self {
45 url: String::new(),
46 max_connections: 20,
47 min_connections: 2,
48 max_lifetime: Some(1800),
49 idle_timeout: Some(600),
50 acquire_timeout: Some(5),
51 schema: None,
52 application_name: Some("gitdataai".to_string()),
53 }
54 }
55}
Modified lib/db/conn.rs lib/db/conn.rs +2 −2 View lib/db/conn.rs
@@ -1,6 +1,6 @@
11pub use sea_orm;
22
3use crate::config::{AppDatabaseConnConfig, AppDatabaseSettings};
3use config::db::{AppDatabaseConnConfig, AppDatabaseSettings};
44pub use sea_orm::DbErr as SqlxError;
55use sea_orm::{
66 ConnectOptions, ConnectionTrait, Database, DatabaseBackend, DatabaseConnection, DbErr,
@@ -250,7 +250,7 @@
250250#[cfg(test)]
251251mod tests {
252252 use super::*;
253 use crate::config::{AppDatabaseConnConfig, AppDatabaseSettings};
253 use config::db::{AppDatabaseConnConfig, AppDatabaseSettings};
254254
255255 fn conn_config(url: &str, max: u32, min: u32) -> AppDatabaseConnConfig {
256256 AppDatabaseConnConfig {
Modified lib/db/lib.rs lib/db/lib.rs +1 −1 View lib/db/lib.rs
@@ -5,6 +5,7 @@
55pub mod common;
66pub mod git;
77pub mod issue;
8pub mod live;
89pub mod oauth;
910pub mod papers;
1011pub mod payment;
@@ -19,6 +20,5 @@
1920pub mod user;
2021pub mod webhook;
2122
22pub mod config;
2323pub mod conn;
2424pub mod schema;
Modified lib/db/schema.rs lib/db/schema.rs +13 −25 View lib/db/schema.rs
@@ -1,18 +1,6 @@
11use crate::conn::DbError;
22use sea_orm::{ConnectionTrait, DatabaseBackend, DatabaseConnection};
33
4/// Entity-first schema bootstrap.
5///
6/// 1. `sync` the registered `db::*` entities against the database: creates any
7/// missing tables, columns, foreign keys and entity-declared indexes,
8/// idempotently and non-destructively (it never drops tables/columns).
9/// 2. For PostgreSQL, apply [`schema_extras.sql`] — the idempotent DDL for the
10/// constraints entity attributes cannot express (partial/overlapping unique
11/// indexes, composite non-unique indexes, CHECK constraints, legacy table
12/// drops). Kept in a single hand-maintained SQL file per project decision.
13///
14/// Gated at the call site by [`crate::config::AppDatabaseSettings::auto_migrate`],
15/// so production can keep the old DBA-managed policy by leaving the flag off.
164pub async fn sync_schema(db: &DatabaseConnection) -> Result<(), DbError> {
175 db.get_schema_registry("db::")
186 .sync(db)
@@ -44,25 +32,24 @@
4432 .is_some()
4533 }
4634
47 /// The registry prefix `"db::"` matches entities by `module_path` prefix.
48 /// Verify against a SQLite-compatible module (the full `db::*` registry is
49 /// not SQLite-creatable: `user_oauth_provider.scopes: Vec<String>` maps to
50 /// a Postgres-only `Array` column).
5135 #[tokio::test]
5236 async fn registry_prefix_matches_module() {
5337 let db = Database::connect("sqlite::memory:").await.unwrap();
5438
55 db.get_schema_registry("db::channel::").sync(&db).await.unwrap();
39 db.get_schema_registry("db::channel::")
40 .sync(&db)
41 .await
42 .unwrap();
5643 assert!(table_exists(&db, "channel").await);
5744 assert!(table_exists(&db, "channel_overwrite_role").await);
5845
59 // A wrong prefix must not register anything.
60 db.get_schema_registry("db::nope::").sync(&db).await.unwrap();
46 db.get_schema_registry("db::nope::")
47 .sync(&db)
48 .await
49 .unwrap();
6150 assert!(!table_exists(&db, "project").await);
6251 }
6352
64 /// Representative cross-module set: converges idempotently and orders
65 /// tables topologically (FKs are inline on SQLite).
6653 #[tokio::test]
6754 async fn entity_first_sync_converges_on_sqlite() {
6855 use crate::{
@@ -75,8 +62,6 @@
7562
7663 let db = Database::connect("sqlite::memory:").await.unwrap();
7764
78 // First pass creates the tables; second pass must also succeed and
79 // make no destructive changes (idempotent convergence).
8065 db.get_schema_builder()
8166 .register(user_entity::Entity)
8267 .register(channel_entity::Entity)
@@ -108,7 +93,10 @@
10893 "issue_reaction",
10994 "users",
11095 ] {
111 assert!(table_exists(&db, table).await, "table `{table}` should exist after sync");
96 assert!(
97 table_exists(&db, table).await,
98 "table `{table}` should exist after sync"
99 );
112100 }
113101 }
114}
No newline at end of file
102}
Modified lib/db/schema_extras.sql lib/db/schema_extras.sql +10 −1 View lib/db/schema_extras.sql
@@ -114,4 +114,13 @@
114114-- Payment composite indexes (multi-column; entity attributes are single-column).
115115-- ---------------------------------------------------------------------------
116116CREATE INDEX IF NOT EXISTS idx_payment_transaction_ref ON payment_transaction (ref_type, ref_id);
117CREATE INDEX IF NOT EXISTS idx_payment_webhook_status_created ON payment_webhook_event (status, created_at);
No newline at end of file
117CREATE INDEX IF NOT EXISTS idx_payment_webhook_status_created ON payment_webhook_event (status, created_at);
118
119-- ---------------------------------------------------------------------------
120-- LiveKit meet / participant indexes.
121-- Partial unique index: each channel has at most one active meet at a time.
122-- ---------------------------------------------------------------------------
123CREATE UNIQUE INDEX IF NOT EXISTS idx_meet_active_per_channel ON meet (channel_id) WHERE status = 1;
124CREATE UNIQUE INDEX IF NOT EXISTS idx_meet_room_name ON meet (room_name);
125CREATE INDEX IF NOT EXISTS idx_meet_participants_meet_user ON meet_participants (meet_id, user_id);
126CREATE INDEX IF NOT EXISTS idx_meet_participants_user_joined ON meet_participants (user_id, joined_at DESC);
No newline at end of file
Modified lib/email/Cargo.toml lib/email/Cargo.toml +1 −0 View lib/email/Cargo.toml
@@ -14,6 +14,7 @@
1414
1515[dependencies]
1616queue = { workspace = true }
17config = { workspace = true }
1718lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "tokio1-native-tls", "builder"] }
1819handlebars = "6"
1920base64 = "0.22"
Deleted lib/email/config.rs lib/email/config.rs +0 −75
@@ -1,75 +0,0 @@
1use lombok::{Builder, Getter, Setter};
2
3#[derive(
4 serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Getter, Setter, Builder,
5)]
6#[serde(default)]
7pub struct AppEmailSettings {
8 pub enabled: bool,
9 pub smtp_host: String,
10 pub smtp_port: u16,
11 pub smtp_username: Option<String>,
12 pub smtp_password: Option<String>,
13 pub from_address: String,
14 pub from_name: Option<String>,
15 pub connection_timeout_ms: u64,
16 pub response_timeout_ms: u64,
17 pub template_dir: Option<String>,
18 pub default_locale: String,
19 pub max_attachment_bytes: usize,
20 pub subscriber_group_id: String,
21 pub auto_offset_reset: String,
22}
23
24impl Default for AppEmailSettings {
25 fn default() -> Self {
26 Self {
27 enabled: true,
28 smtp_host: "127.0.0.1".to_string(),
29 smtp_port: 587,
30 smtp_username: None,
31 smtp_password: None,
32 from_address: "no-reply@example.com".to_string(),
33 from_name: Some("No Reply".to_string()),
34 connection_timeout_ms: 5_000,
35 response_timeout_ms: 10_000,
36 template_dir: None,
37 default_locale: "en".to_string(),
38 max_attachment_bytes: 25 * 1024 * 1024,
39 subscriber_group_id: "email-service".to_string(),
40 auto_offset_reset: "earliest".to_string(),
41 }
42 }
43}
44
45impl AppEmailSettings {
46 pub fn is_valid(&self) -> Result<(), String> {
47 if !self.enabled {
48 return Ok(());
49 }
50 if self.smtp_host.trim().is_empty() {
51 return Err("smtp_host must not be empty when enabled".to_string());
52 }
53 if self.smtp_port == 0 {
54 return Err("smtp_port must be > 0".to_string());
55 }
56 if self.from_address.trim().is_empty() {
57 return Err("from_address must not be empty".to_string());
58 }
59 if self.max_attachment_bytes == 0 {
60 return Err("max_attachment_bytes must be > 0".to_string());
61 }
62 match self.auto_offset_reset.as_str() {
63 "earliest" | "latest" | "none" => {}
64 other => {
65 return Err(format!(
66 "auto_offset_reset must be earliest|latest|none, got {other}"
67 ));
68 }
69 }
70 if self.subscriber_group_id.is_empty() {
71 return Err("subscriber_group_id must not be empty".to_string());
72 }
73 Ok(())
74 }
75}
Modified lib/email/lib.rs lib/email/lib.rs +0 −2 View lib/email/lib.rs
@@ -1,4 +1,3 @@
1pub mod config;
21pub mod error;
32pub mod events;
43pub mod metrics;
@@ -8,7 +7,6 @@
87pub mod template;
98pub mod transport;
109
11pub use config::AppEmailSettings;
1210pub use error::EmailError;
1311pub use events::{
1412 Attachment, BatchRecipient, EmailAddress, EmailFailed, EmailSent, RenderAndSendEmailRequest,
Modified lib/email/runtime.rs lib/email/runtime.rs +3 −2 View lib/email/runtime.rs
@@ -1,11 +1,12 @@
11use std::path::PathBuf;
22use std::sync::Arc;
33
4use queue::{AppQueueSettings, Queue, QueueBuilder};
4use config::email::AppEmailSettings;
5use config::queue::AppQueueSettings;
6use queue::{Queue, QueueBuilder};
57use tokio::signal;
68use tracing::{info, warn};
79
8use crate::config::AppEmailSettings;
910use crate::error::EmailError;
1011use crate::service::{EmailService, EmailServiceBuilder, EmailSubscribers};
1112
Modified lib/email/service.rs lib/email/service.rs +6 −3 View lib/email/service.rs
@@ -4,7 +4,6 @@
44use queue::{AutoOffsetReset, Queue, SubscribeHandle, SubscriptionMode};
55use tracing::{debug, error, warn};
66
7use crate::config::AppEmailSettings;
87use crate::error::EmailError;
98use crate::events::{
109 EmailFailed, EmailSent, RenderAndSendEmailRequest, SendBatchEmailRequest, SendEmailRequest,
@@ -14,6 +13,7 @@
1413use crate::mime::{MimeBuildOptions, build_message};
1514use crate::template::TemplateRegistry;
1615use crate::transport::EmailTransport;
16use config::email::AppEmailSettings;
1717
1818pub struct EmailService {
1919 inner: Arc<EmailServiceInner>,
@@ -376,7 +376,7 @@
376376#[cfg(test)]
377377mod tests {
378378 use super::*;
379 use crate::config::AppEmailSettings;
379 use config::email::AppEmailSettings;
380380
381381 fn cfg() -> AppEmailSettings {
382382 AppEmailSettings {
@@ -409,7 +409,10 @@
409409 let mut c = cfg();
410410 c.enabled = false;
411411 let queue_handle = rt
412 .block_on(queue::QueueBuilder::from_config(queue::AppQueueSettings::default()).build())
412 .block_on(
413 queue::QueueBuilder::from_config(config::queue::AppQueueSettings::default())
414 .build(),
415 )
413416 .unwrap()
414417 .expect("queue must build for test");
415418 let queue_arc = Arc::new(queue_handle);
Modified lib/email/transport.rs lib/email/transport.rs +1 −1 View lib/email/transport.rs
@@ -8,9 +8,9 @@
88use lettre::transport::smtp::authentication::Credentials;
99use lettre::transport::smtp::client::{Tls, TlsParameters};
1010
11use crate::config::AppEmailSettings;
1211use crate::error::EmailError;
1312use crate::metrics;
13use config::email::AppEmailSettings;
1414
1515pub struct EmailTransport {
1616 inner: AsyncSmtpTransport<Tokio1Executor>,
Modified lib/jwt/Cargo.toml lib/jwt/Cargo.toml +1 −0 View lib/jwt/Cargo.toml
@@ -14,6 +14,7 @@
1414
1515[dependencies]
1616axum.workspace = true
17config = { workspace = true }
1718db = { workspace = true }
1819sea-orm = { workspace = true }
1920thiserror = { workspace = true }
Deleted lib/jwt/config.rs lib/jwt/config.rs +0 −29
@@ -1,29 +0,0 @@
1use serde::{Deserialize, Serialize};
2use time::Duration;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct JwtSettings {
6 pub issuer: String,
7 pub audience: String,
8 pub access_ttl: Duration,
9 pub refresh_ttl: Duration,
10 pub grace_window: Duration,
11 pub clock_skew_seconds: i64,
12 pub default_grace_duration: Duration,
13 pub default_key_pair_algorithm: String,
14}
15
16impl Default for JwtSettings {
17 fn default() -> Self {
18 Self {
19 issuer: "untitled".to_string(),
20 audience: "untitled-api".to_string(),
21 access_ttl: Duration::minutes(15),
22 refresh_ttl: Duration::days(30),
23 grace_window: Duration::minutes(5),
24 clock_skew_seconds: 60,
25 default_grace_duration: Duration::days(7),
26 default_key_pair_algorithm: "EdDSA".to_string(),
27 }
28 }
29}
Modified lib/jwt/extractor.rs lib/jwt/extractor.rs +1 −1 View lib/jwt/extractor.rs
@@ -4,10 +4,10 @@
44use std::sync::Arc;
55
66use crate::claims::AccessClaims;
7use crate::config::JwtSettings;
87use crate::error::JwtError;
98use crate::keys::SigningKeyCache;
109use crate::rotation::{RotationContext, validate_access};
10use config::jwt::JwtSettings;
1111
1212#[derive(Debug, Clone)]
1313pub struct AuthUser {
Modified lib/jwt/handlers.rs lib/jwt/handlers.rs +1 −1 View lib/jwt/handlers.rs
@@ -6,10 +6,10 @@
66use serde::{Deserialize, Serialize};
77use std::sync::Arc;
88
9use crate::config::JwtSettings;
109use crate::error::JwtError;
1110use crate::keys::SigningKeyCache;
1211use crate::rotation::TokenPair;
12use config::jwt::JwtSettings;
1313
1414#[derive(Debug, Clone)]
1515pub struct JwtState {
Modified lib/jwt/keys.rs lib/jwt/keys.rs +4 −0 View lib/jwt/keys.rs
@@ -104,6 +104,10 @@
104104 self.keys.get(kid)
105105 }
106106
107 pub fn insert(&mut self, kid: Uuid, key: CachedKey) {
108 self.keys.insert(kid, key);
109 }
110
107111 pub fn current_kid(&self) -> Option<Uuid> {
108112 self.current_kid
109113 }
Modified lib/jwt/lib.rs lib/jwt/lib.rs +0 −2 View lib/jwt/lib.rs
@@ -1,5 +1,4 @@
11pub mod claims;
2pub mod config;
32pub mod error;
43pub mod extractor;
54pub mod grace;
@@ -15,7 +14,6 @@
1514mod tests;
1615
1716pub use claims::{AccessClaims, HasJti, HasUserExt, RefreshClaims};
18pub use config::JwtSettings;
1917pub use error::JwtError;
2018pub use extractor::{AuthAdmin, AuthOptional, AuthUser};
2119pub use handlers::{JwtState, LogoutRequest, TokenPairResponse};
Modified lib/jwt/rotation.rs lib/jwt/rotation.rs +1 −1 View lib/jwt/rotation.rs
@@ -3,7 +3,6 @@
33use uuid::Uuid;
44
55use crate::claims::{AccessClaims, RefreshClaims};
6use crate::config::JwtSettings;
76use crate::error::JwtError;
87use crate::keys::SigningKeyCache;
98use crate::metrics;
@@ -11,6 +10,7 @@
1110use crate::signer::{
1211 decode_access_token, decode_refresh_token, encode_access_token, encode_refresh_token,
1312};
13use config::jwt::JwtSettings;
1414
1515pub struct TokenPair {
1616 pub access_token: String,
Modified lib/payment/gateway/airwallex/client.rs lib/payment/gateway/airwallex/client.rs +1 −1 View lib/payment/gateway/airwallex/client.rs
@@ -3,7 +3,7 @@
33use async_trait::async_trait;
44use reqwest::Client as HttpClient;
55
6use crate::config::AirwallexConfig;
6use config::payment::AirwallexConfig;
77use crate::error::{PaymentError, PaymentResult};
88use crate::gateway::PaymentGateway;
99use crate::types::{CreateIntentRequest, CreateRefundRequest, Customer, PaymentIntent, Refund};
Modified lib/payment/gateway/airwallex/mod.rs lib/payment/gateway/airwallex/mod.rs +1 −2 View lib/payment/gateway/airwallex/mod.rs
@@ -2,5 +2,4 @@
22pub mod types;
33pub mod webhook;
44
5pub use client::AirwallexClient;
6pub use crate::config::AirwallexConfig;
No newline at end of file
5pub use client::AirwallexClient;
No newline at end of file
Modified lib/payment/repo/mod.rs lib/payment/repo/mod.rs +1 −1 View lib/payment/repo/mod.rs
@@ -20,7 +20,7 @@
2020#[async_trait]
2121pub trait WalletRepository: Send + Sync {
2222 async fn get_for_update(&self, user_id: Uuid) -> PaymentResult<Option<payment_wallets::Model>>;
23 async fn debit(&self, user_id: Uuid, amount: i64, expected_version: i32) -> PaymentResult<i64>;
23 async fn debit(&self, user_id: Uuid, amount: i64, expected_version: i32) -> PaymentResult<i64>;
2424 async fn credit(&self, user_id: Uuid, amount: i64) -> PaymentResult<i64>;
2525}
2626
Modified lib/payment/Cargo.toml lib/payment/Cargo.toml +1 −0 View lib/payment/Cargo.toml
@@ -13,6 +13,7 @@
1313name = "payment"
1414[dependencies]
1515db.workspace = true
16config.workspace = true
1617cache.workspace = true
1718jwt.workspace = true
1819export.workspace = true
Deleted lib/payment/config.rs lib/payment/config.rs +0 −45
@@ -1,45 +0,0 @@
1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct PaymentConfig {
7 #[serde(default = "default_currency")]
8 pub currency: String,
9 #[serde(default = "default_max_pagination")]
10 pub max_pagination: u64,
11 #[serde(default)]
12 pub airwallex: AirwallexConfig,
13}
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct AirwallexConfig {
17 #[serde(default)]
18 pub base_url: String,
19 #[serde(default)]
20 pub client_id: String,
21 #[serde(default)]
22 pub api_key: String,
23 #[serde(default)]
24 pub webhook_secret: String,
25}
26
27pub type SharedPaymentConfig = Arc<PaymentConfig>;
28
29impl Default for PaymentConfig {
30 fn default() -> Self {
31 Self {
32 currency: default_currency(),
33 max_pagination: default_max_pagination(),
34 airwallex: AirwallexConfig::default(),
35 }
36 }
37}
38
39fn default_currency() -> String {
40 "CNY".to_string()
41}
42
43fn default_max_pagination() -> u64 {
44 100
45}
Modified lib/payment/lib.rs lib/payment/lib.rs +0 −2 View lib/payment/lib.rs
@@ -1,4 +1,3 @@
1pub mod config;
21pub mod error;
32pub mod gateway;
43pub mod metrics;
@@ -6,7 +5,6 @@
65pub mod service;
76pub mod types;
87
9pub use config::{PaymentConfig, SharedPaymentConfig};
108pub use error::{PaymentError, PaymentResult};
119pub use gateway::PaymentGateway;
1210pub use service::PaymentService;
Modified lib/payment/service.rs lib/payment/service.rs +1 −1 View lib/payment/service.rs
@@ -4,8 +4,8 @@
44use db::conn::Db;
55use jwt::JwtState;
66
7use crate::config::{PaymentConfig, SharedPaymentConfig};
87use crate::gateway::PaymentGateway;
8use config::payment::{PaymentConfig, SharedPaymentConfig};
99
1010#[derive(Clone)]
1111pub struct PaymentService {
Modified lib/payment/types.rs lib/payment/types.rs +2 −2 View lib/payment/types.rs
@@ -12,8 +12,8 @@
1212pub struct CreateIntentRequest {
1313 pub order_id: Uuid,
1414 pub amount: Money,
15 pub merchant_order_id: String,
16 pub request_id: String,
15 pub merchant_order_id: String,
16 pub request_id: String,
1717 pub customer_id: Option<String>,
1818 pub metadata: Option<serde_json::Value>,
1919}
Modified lib/queue/Cargo.toml lib/queue/Cargo.toml +1 −0 View lib/queue/Cargo.toml
@@ -14,6 +14,7 @@
1414
1515[dependencies]
1616rdkafka = { version = "0.39", default-features = false, features = ["cmake-build", "libz", "tokio"] }
17config = { workspace = true }
1718tokio = { workspace = true }
1819tokio-util = { version = "0.7", features = ["rt"] }
1920serde = { workspace = true }
Deleted lib/queue/config.rs lib/queue/config.rs +0 −76
@@ -1,76 +0,0 @@
1use std::collections::HashMap;
2
3use lombok::{Builder, Getter, Setter};
4
5#[derive(
6 serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Getter, Setter, Builder,
7)]
8#[serde(default)]
9pub struct AppQueueSettings {
10 pub enabled: bool,
11 pub brokers: String,
12 pub client_id: String,
13 pub topic_prefix: String,
14 pub default_group_id: String,
15 pub message_timeout_ms: u32,
16 pub session_timeout_ms: u32,
17 pub auto_offset_reset: String,
18 pub producer_compression: String,
19 pub producer_acks: String,
20 pub publish_queue_timeout_ms: u32,
21 pub subscribe_max_retries: u32,
22 pub subscribe_graceful_shutdown_ms: u64,
23 pub additional_properties: HashMap<String, String>,
24}
25
26impl Default for AppQueueSettings {
27 fn default() -> Self {
28 Self {
29 enabled: true,
30 brokers: "127.0.0.1:9092".to_string(),
31 client_id: "untitled".to_string(),
32 topic_prefix: "untitled".to_string(),
33 default_group_id: "untitled".to_string(),
34 message_timeout_ms: 5_000,
35 session_timeout_ms: 10_000,
36 auto_offset_reset: "earliest".to_string(),
37 producer_compression: "lz4".to_string(),
38 producer_acks: "all".to_string(),
39 publish_queue_timeout_ms: 1_000,
40 subscribe_max_retries: 3,
41 subscribe_graceful_shutdown_ms: 5_000,
42 additional_properties: HashMap::new(),
43 }
44 }
45}
46
47impl AppQueueSettings {
48 pub fn is_valid(&self) -> Result<(), String> {
49 if !self.enabled {
50 return Ok(());
51 }
52 if self.brokers.trim().is_empty() {
53 return Err("brokers must not be empty when enabled".to_string());
54 }
55 if self.topic_prefix.is_empty() {
56 return Err("topic_prefix must not be empty".to_string());
57 }
58 match self.auto_offset_reset.as_str() {
59 "earliest" | "latest" | "none" => {}
60 other => {
61 return Err(format!(
62 "auto_offset_reset must be one of earliest|latest|none, got {other}"
63 ));
64 }
65 }
66 match self.producer_acks.as_str() {
67 "0" | "1" | "all" => {}
68 other => return Err(format!("producer_acks must be one of 0|1|all, got {other}")),
69 }
70 match self.producer_compression.as_str() {
71 "none" | "gzip" | "snappy" | "lz4" | "zstd" => {}
72 other => return Err(format!("producer_compression invalid: {other}")),
73 }
74 Ok(())
75 }
76}
Modified lib/queue/consumer.rs lib/queue/consumer.rs +1 −1 View lib/queue/consumer.rs
@@ -12,9 +12,9 @@
1212use tracing::{debug, error, warn};
1313use uuid::Uuid;
1414
15use crate::config::AppQueueSettings;
1615use crate::error::QueueError;
1716use crate::metrics;
17use config::queue::AppQueueSettings;
1818
1919pub type RawHandler =
2020 Arc<dyn Fn(RawMessage) -> BoxFuture<'static, Result<(), QueueError>> + Send + Sync>;
Modified lib/queue/lib.rs lib/queue/lib.rs +2 −2 View lib/queue/lib.rs
@@ -1,4 +1,3 @@
1pub mod config;
21pub mod consumer;
32pub mod error;
43pub mod metrics;
@@ -8,7 +7,8 @@
87
98use std::sync::Arc;
109
11pub use config::AppQueueSettings;
10use config::queue::AppQueueSettings;
11
1212pub use consumer::{AutoOffsetReset, RawMessage, SubscribeHandle, SubscriptionMode};
1313pub use error::QueueError;
1414pub use producer::KafkaPublisher;
Modified lib/queue/producer.rs lib/queue/producer.rs +1 −1 View lib/queue/producer.rs
@@ -8,9 +8,9 @@
88use tracing::warn;
99use uuid::Uuid;
1010
11use crate::config::AppQueueSettings;
1211use crate::error::QueueError;
1312use crate::metrics;
13use config::queue::AppQueueSettings;
1414
1515#[derive(Clone)]
1616pub struct KafkaPublisher {
Modified lib/rate_limit/Cargo.toml lib/rate_limit/Cargo.toml +1 −0 View lib/rate_limit/Cargo.toml
@@ -14,6 +14,7 @@
1414
1515[dependencies]
1616cache.workspace = true
17config.workspace = true
1718axum.workspace = true
1819axum-extra.workspace = true
1920serde.workspace = true
Deleted lib/rate_limit/config.rs lib/rate_limit/config.rs +0 −30
@@ -1,30 +0,0 @@
1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4pub struct RateLimitConfig {
5 pub default_limit: u64,
6 pub window_secs: i64,
7 pub cookie_name: String,
8 pub cookie_secure: bool,
9}
10
11impl Default for RateLimitConfig {
12 fn default() -> Self {
13 Self {
14 default_limit: 100,
15 window_secs: 60,
16 cookie_name: "rl_id".to_string(),
17 cookie_secure: true,
18 }
19 }
20}
21
22impl RateLimitConfig {
23 pub fn new(default_limit: u64, window_secs: i64) -> Self {
24 Self {
25 default_limit,
26 window_secs,
27 ..Self::default()
28 }
29 }
30}
Modified lib/rate_limit/lib.rs lib/rate_limit/lib.rs +0 −2 View lib/rate_limit/lib.rs
@@ -1,8 +1,6 @@
11pub mod blacklist;
2pub mod config;
32pub mod metrics;
43pub mod middleware;
54
65pub use blacklist::AccessTokenBlacklist;
7pub use config::RateLimitConfig;
86pub use middleware::{ClientId, RateLimitProvider, ratelimit};
Modified lib/rate_limit/middleware.rs lib/rate_limit/middleware.rs +1 −1 View lib/rate_limit/middleware.rs
@@ -12,8 +12,8 @@
1212use time::OffsetDateTime;
1313use uuid::Uuid;
1414
15use crate::config::RateLimitConfig;
1615use crate::metrics;
16use config::rate_limit::RateLimitConfig;
1717
1818pub const X_RATE_LIMIT_LIMIT: &str = "x-ratelimit-limit";
1919pub const X_RATE_LIMIT_REMAINING: &str = "x-ratelimit-remaining";
Modified lib/storage/Cargo.toml lib/storage/Cargo.toml +1 −0 View lib/storage/Cargo.toml
@@ -15,6 +15,7 @@
1515[dependencies]
1616async-trait = { workspace = true }
1717bytes = { workspace = true }
18config = { workspace = true }
1819futures = { workspace = true }
1920http = { workspace = true }
2021lombok = { workspace = true }
Deleted lib/storage/config.rs lib/storage/config.rs +0 −111
@@ -1,111 +0,0 @@
1use std::path::PathBuf;
2
3use lombok::{Builder, Getter, Setter};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
6#[serde(rename_all = "lowercase")]
7pub enum BackendType {
8 #[default]
9 Local,
10 S3,
11}
12
13#[derive(
14 Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, Getter, Setter, Builder,
15)]
16#[serde(default)]
17pub struct StorageSettings {
18 backend: BackendType,
19 local: LocalStorageSettings,
20 s3: S3StorageSettings,
21}
22
23impl Default for StorageSettings {
24 fn default() -> Self {
25 Self {
26 backend: BackendType::Local,
27 local: LocalStorageSettings::default(),
28 s3: S3StorageSettings::default(),
29 }
30 }
31}
32
33#[derive(
34 Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, Getter, Setter, Builder,
35)]
36#[serde(default)]
37pub struct LocalStorageSettings {
38 root_dir: PathBuf,
39}
40
41impl Default for LocalStorageSettings {
42 fn default() -> Self {
43 Self {
44 root_dir: PathBuf::from("./storage"),
45 }
46 }
47}
48
49#[derive(
50 Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, Getter, Setter, Builder,
51)]
52#[serde(default)]
53pub struct S3StorageSettings {
54 endpoint: Option<String>,
55 region: String,
56 bucket: String,
57 access_key_id: Option<String>,
58 secret_access_key: Option<String>,
59 prefix: Option<String>,
60 allow_http: bool,
61}
62
63impl Default for S3StorageSettings {
64 fn default() -> Self {
65 Self {
66 endpoint: None,
67 region: "us-east-1".to_string(),
68 bucket: String::new(),
69 access_key_id: None,
70 secret_access_key: None,
71 prefix: None,
72 allow_http: false,
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn storage_settings_defaults() {
83 let settings = StorageSettings::default();
84 assert_eq!(*settings.get_backend(), BackendType::Local);
85 assert_eq!(
86 settings.get_local().get_root_dir(),
87 &PathBuf::from("./storage")
88 );
89 assert_eq!(settings.get_s3().get_region(), "us-east-1");
90 assert_eq!(settings.get_s3().get_bucket(), "");
91 assert!(!settings.get_s3().get_allow_http());
92 assert!(settings.get_s3().get_endpoint().is_none());
93 assert!(settings.get_s3().get_access_key_id().is_none());
94 assert!(settings.get_s3().get_secret_access_key().is_none());
95 assert!(settings.get_s3().get_prefix().is_none());
96 }
97
98 #[test]
99 fn storage_settings_deserialize() {
100 let json = r#"{"backend":"s3","s3":{"bucket":"my-bucket","region":"cn-north-1","endpoint":"http://127.0.0.1:9000","allow_http":true}}"#;
101 let settings: StorageSettings = serde_json::from_str(json).unwrap();
102 assert_eq!(*settings.get_backend(), BackendType::S3);
103 assert_eq!(settings.get_s3().get_bucket(), "my-bucket");
104 assert_eq!(settings.get_s3().get_region(), "cn-north-1");
105 assert!(settings.get_s3().get_allow_http());
106 assert_eq!(
107 settings.get_local().get_root_dir(),
108 &PathBuf::from("./storage")
109 );
110 }
111}
Modified lib/storage/lib.rs lib/storage/lib.rs +0 −1 View lib/storage/lib.rs
@@ -1,4 +1,3 @@
1pub mod config;
21pub mod local;
32pub mod metrics;
43pub mod repo;
Modified lib/storage/local.rs lib/storage/local.rs +2 −2 View lib/storage/local.rs
@@ -10,9 +10,9 @@
1010use object_store::{GetResult, ObjectMeta, ObjectStore, PutResult};
1111use url::Url;
1212
13use crate::config::StorageSettings;
1413use crate::metrics;
1514use crate::repo::{StorageError, StorageRepository};
15use config::storage::StorageSettings;
1616
1717#[derive(Debug)]
1818pub struct LocalStorage {
@@ -150,8 +150,8 @@
150150 use object_store::path::Path;
151151 use tempfile::tempdir;
152152
153 use crate::config::{LocalStorageSettings, StorageSettings};
154153 use crate::repo::{StorageError, from_config};
154 use config::storage::{LocalStorageSettings, StorageSettings};
155155
156156 fn local_settings(dir: &std::path::Path) -> StorageSettings {
157157 let mut local = LocalStorageSettings::default();
Modified lib/storage/repo.rs lib/storage/repo.rs +2 −2 View lib/storage/repo.rs
@@ -7,7 +7,7 @@
77use object_store::{GetResult, MultipartUpload, ObjectMeta, PutResult};
88use url::Url;
99
10use crate::config::{BackendType, StorageSettings};
10use config::storage::{BackendType, StorageSettings};
1111
1212#[derive(Debug, thiserror::Error)]
1313pub enum StorageError {
@@ -52,7 +52,7 @@
5252#[cfg(test)]
5353mod tests {
5454 use super::*;
55 use crate::config::{LocalStorageSettings, S3StorageSettings};
55 use config::storage::{LocalStorageSettings, S3StorageSettings};
5656
5757 #[test]
5858 fn from_config_dispatches_local() {
Modified lib/storage/s3.rs lib/storage/s3.rs +1 −1 View lib/storage/s3.rs
@@ -13,9 +13,9 @@
1313use object_store::{GetResult, ObjectMeta, ObjectStore, PutResult};
1414use url::Url;
1515
16use crate::config::StorageSettings;
1716use crate::metrics;
1817use crate::repo::{StorageError, StorageRepository};
18use config::storage::StorageSettings;
1919
2020#[derive(Debug)]
2121pub struct S3Storage {