forked from boos/BoosAPI
feat: polish console shell and notification settings
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::error::AppResult;
|
||||
use crate::error::{AppError, AppResult};
|
||||
use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc};
|
||||
use lettre::{
|
||||
message::Mailbox, transport::smtp::authentication::Credentials, Message, SmtpTransport,
|
||||
@@ -36,13 +36,19 @@ pub struct Notification {
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NotificationConfig {
|
||||
telegram_enabled: bool,
|
||||
telegram_bot_token: Option<String>,
|
||||
telegram_chat_id: Option<String>,
|
||||
telegram_thread_id: Option<i64>,
|
||||
wecom_enabled: bool,
|
||||
wecom_webhook: Option<String>,
|
||||
feishu_enabled: bool,
|
||||
feishu_webhook: Option<String>,
|
||||
webhook_enabled: bool,
|
||||
webhook_url: Option<String>,
|
||||
bark_enabled: bool,
|
||||
bark_url: Option<String>,
|
||||
serverchan_enabled: bool,
|
||||
serverchan_key: Option<String>,
|
||||
smtp: SmtpConfig,
|
||||
cooldown_sec: i64,
|
||||
@@ -50,8 +56,10 @@ struct NotificationConfig {
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct SmtpConfig {
|
||||
enabled: bool,
|
||||
host: Option<String>,
|
||||
port: Option<u16>,
|
||||
secure: Option<bool>,
|
||||
user: Option<String>,
|
||||
pass: Option<String>,
|
||||
from: Option<String>,
|
||||
@@ -100,6 +108,33 @@ impl NotificationService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_test(&self) -> AppResult<Vec<String>> {
|
||||
Self::ensure_schema(&self.db).await?;
|
||||
let config = self.load_config().await?;
|
||||
let channels = self.enabled_channels(&config);
|
||||
if channels.is_empty() {
|
||||
return Err(AppError::BadRequest(
|
||||
"请至少配置并启用一个通知通道".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let notif = Notification {
|
||||
title: "BossAI 通知测试".to_string(),
|
||||
message: "通知通道配置正常。这条消息来自管理后台的测试发送。".to_string(),
|
||||
level: NotificationLevel::Info,
|
||||
channel: None,
|
||||
signature: None,
|
||||
};
|
||||
|
||||
for channel in &channels {
|
||||
self.send_channel(channel, ¬if, &config)
|
||||
.await
|
||||
.map_err(|err| AppError::BadRequest(format!("{channel} 通知测试失败: {err}")))?;
|
||||
}
|
||||
|
||||
Ok(channels)
|
||||
}
|
||||
|
||||
async fn load_config(&self) -> AppResult<NotificationConfig> {
|
||||
let rows: Vec<(String, Option<String>)> =
|
||||
sqlx::query_as("SELECT key, value FROM settings WHERE key LIKE 'notify_%'")
|
||||
@@ -112,18 +147,26 @@ impl NotificationService {
|
||||
.collect();
|
||||
|
||||
Ok(NotificationConfig {
|
||||
telegram_enabled: setting_bool(&map, "notify_telegram_enabled", true),
|
||||
telegram_bot_token: setting(&map, "notify_telegram_bot_token"),
|
||||
telegram_chat_id: setting(&map, "notify_telegram_chat_id"),
|
||||
telegram_thread_id: setting(&map, "notify_telegram_thread_id")
|
||||
.and_then(|value| value.parse().ok()),
|
||||
wecom_enabled: setting_bool(&map, "notify_wecom_enabled", true),
|
||||
wecom_webhook: setting(&map, "notify_wecom_webhook"),
|
||||
feishu_enabled: setting_bool(&map, "notify_feishu_enabled", true),
|
||||
feishu_webhook: setting(&map, "notify_feishu_webhook"),
|
||||
webhook_enabled: setting_bool(&map, "notify_webhook_enabled", true),
|
||||
webhook_url: setting(&map, "notify_webhook_url"),
|
||||
bark_enabled: setting_bool(&map, "notify_bark_enabled", true),
|
||||
bark_url: setting(&map, "notify_bark_url"),
|
||||
serverchan_enabled: setting_bool(&map, "notify_serverchan_enabled", true),
|
||||
serverchan_key: setting(&map, "notify_serverchan_key"),
|
||||
smtp: SmtpConfig {
|
||||
enabled: setting_bool(&map, "notify_smtp_enabled", true),
|
||||
host: setting(&map, "notify_smtp_host"),
|
||||
port: setting(&map, "notify_smtp_port").and_then(|value| value.parse().ok()),
|
||||
secure: setting(&map, "notify_smtp_secure").map(|value| parse_bool(&value)),
|
||||
user: setting(&map, "notify_smtp_user"),
|
||||
pass: setting(&map, "notify_smtp_pass"),
|
||||
from: setting(&map, "notify_smtp_from"),
|
||||
@@ -188,25 +231,28 @@ impl NotificationService {
|
||||
|
||||
fn enabled_channels(&self, config: &NotificationConfig) -> Vec<String> {
|
||||
let mut channels = Vec::new();
|
||||
if config.telegram_bot_token.is_some() && config.telegram_chat_id.is_some() {
|
||||
if config.telegram_enabled
|
||||
&& config.telegram_bot_token.is_some()
|
||||
&& config.telegram_chat_id.is_some()
|
||||
{
|
||||
channels.push("telegram".to_string());
|
||||
}
|
||||
if config.wecom_webhook.is_some() {
|
||||
if config.wecom_enabled && config.wecom_webhook.is_some() {
|
||||
channels.push("wecom".to_string());
|
||||
}
|
||||
if config.feishu_webhook.is_some() {
|
||||
if config.feishu_enabled && config.feishu_webhook.is_some() {
|
||||
channels.push("feishu".to_string());
|
||||
}
|
||||
if config.webhook_url.is_some() {
|
||||
if config.webhook_enabled && config.webhook_url.is_some() {
|
||||
channels.push("webhook".to_string());
|
||||
}
|
||||
if config.bark_url.is_some() {
|
||||
if config.bark_enabled && config.bark_url.is_some() {
|
||||
channels.push("bark".to_string());
|
||||
}
|
||||
if config.serverchan_key.is_some() {
|
||||
if config.serverchan_enabled && config.serverchan_key.is_some() {
|
||||
channels.push("serverchan".to_string());
|
||||
}
|
||||
if config.smtp.is_configured() {
|
||||
if config.smtp.enabled && config.smtp.is_configured() {
|
||||
channels.push("smtp".to_string());
|
||||
}
|
||||
channels
|
||||
@@ -385,6 +431,7 @@ impl SmtpConfig {
|
||||
fn send(self, notif: &Notification) -> anyhow::Result<()> {
|
||||
let host = required(self.host.as_deref(), "smtp host")?;
|
||||
let port = self.port.unwrap_or(587);
|
||||
let secure = self.secure.unwrap_or(true);
|
||||
let user = required(self.user.as_deref(), "smtp user")?;
|
||||
let pass = required(self.pass.as_deref(), "smtp pass")?;
|
||||
let from = required(self.from.as_deref(), "smtp from")?;
|
||||
@@ -400,7 +447,12 @@ impl SmtpConfig {
|
||||
))
|
||||
.body(format_notification_text(notif))?;
|
||||
|
||||
let mailer = SmtpTransport::relay(host)?
|
||||
let builder = if secure {
|
||||
SmtpTransport::relay(host)?
|
||||
} else {
|
||||
SmtpTransport::builder_dangerous(host)
|
||||
};
|
||||
let mailer = builder
|
||||
.port(port)
|
||||
.credentials(Credentials::new(user.to_string(), pass.to_string()))
|
||||
.build();
|
||||
@@ -413,6 +465,17 @@ fn setting(map: &HashMap<String, String>, key: &str) -> Option<String> {
|
||||
map.get(key).map(|value| value.trim().to_string())
|
||||
}
|
||||
|
||||
fn setting_bool(map: &HashMap<String, String>, key: &str, default: bool) -> bool {
|
||||
map.get(key).map(|value| parse_bool(value)).unwrap_or(default)
|
||||
}
|
||||
|
||||
fn parse_bool(value: &str) -> bool {
|
||||
matches!(
|
||||
value.trim().to_ascii_lowercase().as_str(),
|
||||
"1" | "true" | "yes" | "on" | "enabled"
|
||||
)
|
||||
}
|
||||
|
||||
fn required<'a>(value: Option<&'a str>, name: &str) -> anyhow::Result<&'a str> {
|
||||
value.ok_or_else(|| anyhow::anyhow!("{name} is not configured"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user