perf: improve compression reliability and deployment safety
This commit is contained in:
121
src/api/admin.rs
121
src/api/admin.rs
@@ -11,7 +11,7 @@ use axum::extract::{ConnectInfo, Path, Query, State};
|
||||
use axum::http::HeaderMap;
|
||||
use axum::routing::{get, post, put};
|
||||
use axum::{Json, Router};
|
||||
use chrono::{DateTime, Datelike, Duration, FixedOffset, Timelike, TimeZone, Utc};
|
||||
use chrono::{DateTime, Datelike, Duration, FixedOffset, TimeZone, Timelike, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
@@ -108,7 +108,10 @@ async fn resolve_user_id(state: &AppState, identifier: &str) -> Result<Uuid, App
|
||||
.map_err(|err| AppError::new(ErrorCode::Internal, "查询用户失败").with_source(err))?;
|
||||
|
||||
if ids.len() > 1 {
|
||||
return Err(AppError::new(ErrorCode::InvalidRequest, "用户 ID 前缀不唯一"));
|
||||
return Err(AppError::new(
|
||||
ErrorCode::InvalidRequest,
|
||||
"用户 ID 前缀不唯一",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +263,10 @@ async fn list_users(
|
||||
let limit = query.limit.unwrap_or(20).clamp(1, 100);
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let offset = (page - 1) * limit;
|
||||
let search = query.search.map(|s| s.trim().to_string()).filter(|s| !s.is_empty());
|
||||
let search = query
|
||||
.search
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
let total: i64 = if let Some(search) = &search {
|
||||
let keyword = format!("%{}%", search);
|
||||
@@ -414,7 +420,10 @@ async fn list_tasks(
|
||||
let limit = query.limit.unwrap_or(20).clamp(1, 100);
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let offset = (page - 1) * limit;
|
||||
let status = query.status.map(|s| s.trim().to_string()).filter(|s| !s.is_empty());
|
||||
let status = query
|
||||
.status
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
let total: i64 = if let Some(status) = &status {
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM tasks WHERE status::text = $1")
|
||||
@@ -723,7 +732,12 @@ async fn grant_credits(
|
||||
.map_err(|err| AppError::new(ErrorCode::Internal, "查询订阅失败").with_source(err))?;
|
||||
|
||||
let (subscription_id, period_start, period_end, plan_id) = if let Some(sub) = sub {
|
||||
(Some(sub.id), sub.current_period_start, sub.current_period_end, Some(sub.plan_id))
|
||||
(
|
||||
Some(sub.id),
|
||||
sub.current_period_start,
|
||||
sub.current_period_end,
|
||||
Some(sub.plan_id),
|
||||
)
|
||||
} else {
|
||||
let (start, end) = billing::current_month_period_utc8(Utc::now());
|
||||
(None, start, end, None)
|
||||
@@ -853,7 +867,10 @@ async fn create_manual_subscription(
|
||||
|
||||
let months = req.months.unwrap_or(1);
|
||||
if months <= 0 || months > 24 {
|
||||
return Err(AppError::new(ErrorCode::InvalidRequest, "months 需在 1-24 之间"));
|
||||
return Err(AppError::new(
|
||||
ErrorCode::InvalidRequest,
|
||||
"months 需在 1-24 之间",
|
||||
));
|
||||
}
|
||||
|
||||
let user_id = resolve_user_id(&state, &req.user_id).await?;
|
||||
@@ -1012,7 +1029,10 @@ fn days_in_month(tz: FixedOffset, year: i32, month: u32) -> u32 {
|
||||
} else {
|
||||
(year, month + 1)
|
||||
};
|
||||
let first_next = tz.with_ymd_and_hms(next_year, next_month, 1, 0, 0, 0).single().unwrap();
|
||||
let first_next = tz
|
||||
.with_ymd_and_hms(next_year, next_month, 1, 0, 0, 0)
|
||||
.single()
|
||||
.unwrap();
|
||||
let last = first_next - Duration::days(1);
|
||||
last.day()
|
||||
}
|
||||
@@ -1155,29 +1175,30 @@ async fn get_stripe_config(
|
||||
let (_jar, _admin_id) = require_admin(&state, jar, &headers, ip).await?;
|
||||
|
||||
let stored = settings::load_system_config::<StripeConfigStored>(&state, "stripe").await?;
|
||||
let (secret_key_configured, webhook_secret_configured, secret_key_prefix) = if let Some(cfg) = stored {
|
||||
(
|
||||
cfg.secret_key_encrypted.as_ref().is_some(),
|
||||
cfg.webhook_secret_encrypted.as_ref().is_some(),
|
||||
cfg.secret_key_prefix,
|
||||
)
|
||||
} else {
|
||||
let env_secret = state
|
||||
.config
|
||||
.stripe_secret_key
|
||||
.as_ref()
|
||||
.filter(|v| !v.trim().is_empty());
|
||||
let env_webhook = state
|
||||
.config
|
||||
.stripe_webhook_secret
|
||||
.as_ref()
|
||||
.filter(|v| !v.trim().is_empty());
|
||||
(
|
||||
env_secret.is_some(),
|
||||
env_webhook.is_some(),
|
||||
env_secret.map(|value| mask_secret(value)),
|
||||
)
|
||||
};
|
||||
let (secret_key_configured, webhook_secret_configured, secret_key_prefix) =
|
||||
if let Some(cfg) = stored {
|
||||
(
|
||||
cfg.secret_key_encrypted.as_ref().is_some(),
|
||||
cfg.webhook_secret_encrypted.as_ref().is_some(),
|
||||
cfg.secret_key_prefix,
|
||||
)
|
||||
} else {
|
||||
let env_secret = state
|
||||
.config
|
||||
.stripe_secret_key
|
||||
.as_ref()
|
||||
.filter(|v| !v.trim().is_empty());
|
||||
let env_webhook = state
|
||||
.config
|
||||
.stripe_webhook_secret
|
||||
.as_ref()
|
||||
.filter(|v| !v.trim().is_empty());
|
||||
(
|
||||
env_secret.is_some(),
|
||||
env_webhook.is_some(),
|
||||
env_secret.map(|value| mask_secret(value)),
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Json(Envelope {
|
||||
success: true,
|
||||
@@ -1227,7 +1248,8 @@ async fn update_stripe_config(
|
||||
if webhook_secret.is_empty() {
|
||||
stored.webhook_secret_encrypted = None;
|
||||
} else {
|
||||
stored.webhook_secret_encrypted = Some(settings::encrypt_secret(&state, &webhook_secret)?);
|
||||
stored.webhook_secret_encrypted =
|
||||
Some(settings::encrypt_secret(&state, &webhook_secret)?);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,10 +1365,16 @@ async fn update_mail_config(
|
||||
|
||||
if req.provider.eq_ignore_ascii_case("custom") {
|
||||
let custom = req.custom_smtp.as_ref().ok_or_else(|| {
|
||||
AppError::new(ErrorCode::InvalidRequest, "自定义 SMTP 需要填写 host/port/encryption")
|
||||
AppError::new(
|
||||
ErrorCode::InvalidRequest,
|
||||
"自定义 SMTP 需要填写 host/port/encryption",
|
||||
)
|
||||
})?;
|
||||
if custom.host.trim().is_empty() {
|
||||
return Err(AppError::new(ErrorCode::InvalidRequest, "SMTP host 不能为空"));
|
||||
return Err(AppError::new(
|
||||
ErrorCode::InvalidRequest,
|
||||
"SMTP host 不能为空",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1387,8 +1415,9 @@ async fn update_mail_config(
|
||||
settings::upsert_system_config(
|
||||
&state,
|
||||
"mail",
|
||||
serde_json::to_value(&stored)
|
||||
.map_err(|err| AppError::new(ErrorCode::Internal, "序列化邮件配置失败").with_source(err))?,
|
||||
serde_json::to_value(&stored).map_err(|err| {
|
||||
AppError::new(ErrorCode::Internal, "序列化邮件配置失败").with_source(err)
|
||||
})?,
|
||||
Some("邮件服务配置"),
|
||||
Some(admin_id),
|
||||
)
|
||||
@@ -1423,15 +1452,11 @@ async fn test_mail(
|
||||
let ip = context::client_ip(&headers, addr.ip());
|
||||
let (_jar, admin_id) = require_admin(&state, jar, &headers, ip).await?;
|
||||
|
||||
let to = if let Some(to) = req.to.as_ref().map(|v| v.trim().to_string()) {
|
||||
if to.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(to)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let to = req
|
||||
.to
|
||||
.as_ref()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
|
||||
let recipient = if let Some(to) = to {
|
||||
to
|
||||
@@ -1440,13 +1465,17 @@ async fn test_mail(
|
||||
.bind(admin_id)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|err| AppError::new(ErrorCode::Internal, "查询管理员邮箱失败").with_source(err))?;
|
||||
.map_err(|err| {
|
||||
AppError::new(ErrorCode::Internal, "查询管理员邮箱失败").with_source(err)
|
||||
})?;
|
||||
email
|
||||
};
|
||||
|
||||
mail::send_test_email(&state, &recipient)
|
||||
.await
|
||||
.map_err(|err| AppError::new(ErrorCode::MailSendFailed, "测试邮件发送失败").with_source(err))?;
|
||||
.map_err(|err| {
|
||||
AppError::new(ErrorCode::MailSendFailed, "测试邮件发送失败").with_source(err)
|
||||
})?;
|
||||
|
||||
Ok(Json(Envelope {
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user