mirror of
https://github.com/DaLaw2/NetGuardia.git
synced 2026-08-24 14:10:28 +09:00
refactor(notification): invert telegram dependency via factory port
NotificationService used to import `crate::adapter::telegram::TelegramAdapter` directly to construct the adapter inside `test_telegram` — a hexagonal- architecture violation (core depending on a concrete adapter type). Introduce a new `AlertNotifierFactory` port. The adapter layer provides `TelegramAdapterFactory` which implements it and instantiates a fresh `TelegramAdapter` on each `create()` call (so the test path observes the most-recently-saved DB config — the original ad-hoc construction intent is preserved). NotificationService now depends only on the port. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1d935bf2da
commit
ab35992b87
@ -8,7 +8,7 @@ use reqwest::Client;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::interface::port::app_repo::AppRepo;
|
||||
use crate::interface::port::notification::{AlertNotifier, AlertPayload};
|
||||
use crate::interface::port::notification::{AlertNotifier, AlertNotifierFactory, AlertPayload};
|
||||
use crate::interface::port::secret_store::SecretStorePort;
|
||||
use crate::interface::port::setting::SettingRepo;
|
||||
use crate::model::config::constants::TELEGRAM_MAX_RETRIES;
|
||||
@ -262,3 +262,26 @@ impl AlertNotifier for TelegramAdapter {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapter-side factory that satisfies the `AlertNotifierFactory` port. Holds
|
||||
/// the same shared dependencies the long-lived adapter uses; each `create()`
|
||||
/// call instantiates a fresh `TelegramAdapter` so the test path observes
|
||||
/// whatever config the user just saved.
|
||||
pub struct TelegramAdapterFactory {
|
||||
notif: Arc<dyn SettingRepo>,
|
||||
repo: Arc<dyn AppRepo>,
|
||||
secrets: Option<Arc<dyn SecretStorePort>>,
|
||||
}
|
||||
|
||||
impl TelegramAdapterFactory {
|
||||
pub fn new(notif: Arc<dyn SettingRepo>, repo: Arc<dyn AppRepo>, secrets: Option<Arc<dyn SecretStorePort>>) -> Self {
|
||||
Self { notif, repo, secrets }
|
||||
}
|
||||
}
|
||||
|
||||
impl AlertNotifierFactory for TelegramAdapterFactory {
|
||||
fn create(&self) -> Result<Arc<dyn AlertNotifier>, Error> {
|
||||
let adapter = TelegramAdapter::new(self.notif.clone(), self.repo.clone(), self.secrets.clone())?;
|
||||
Ok(Arc::new(adapter))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,10 +2,9 @@ use std::sync::Arc;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::adapter::telegram::TelegramAdapter;
|
||||
use crate::core::email::scheduler::SmtpClient;
|
||||
use crate::interface::port::app_repo::AppRepo;
|
||||
use crate::interface::port::notification::AlertNotifier;
|
||||
use crate::interface::port::notification::AlertNotifierFactory;
|
||||
use crate::interface::port::secret_store::SecretStorePort;
|
||||
use crate::interface::port::setting::SettingRepo;
|
||||
use crate::model::error::Error;
|
||||
@ -17,11 +16,22 @@ pub struct NotificationService {
|
||||
notif: Arc<dyn SettingRepo>,
|
||||
repo: Arc<dyn AppRepo>,
|
||||
secrets: Arc<dyn SecretStorePort>,
|
||||
alert_notifier_factory: Arc<dyn AlertNotifierFactory>,
|
||||
}
|
||||
|
||||
impl NotificationService {
|
||||
pub fn new(notif: Arc<dyn SettingRepo>, repo: Arc<dyn AppRepo>, secrets: Arc<dyn SecretStorePort>) -> Self {
|
||||
Self { notif, repo, secrets }
|
||||
pub fn new(
|
||||
notif: Arc<dyn SettingRepo>,
|
||||
repo: Arc<dyn AppRepo>,
|
||||
secrets: Arc<dyn SecretStorePort>,
|
||||
alert_notifier_factory: Arc<dyn AlertNotifierFactory>,
|
||||
) -> Self {
|
||||
Self {
|
||||
notif,
|
||||
repo,
|
||||
secrets,
|
||||
alert_notifier_factory,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get Telegram config with redacted bot_token.
|
||||
@ -65,10 +75,13 @@ impl NotificationService {
|
||||
self.notif.set_notification_config("telegram", &config_json)
|
||||
}
|
||||
|
||||
/// Send a test Telegram message using current config.
|
||||
/// Send a test Telegram message using current config. The factory
|
||||
/// constructs a fresh notifier on every call so the test reflects the
|
||||
/// most-recently-saved config (the user typically clicks "test"
|
||||
/// immediately after `set_telegram_config`).
|
||||
pub async fn test_telegram(&self) -> Result<(), Error> {
|
||||
let adapter = TelegramAdapter::new(self.notif.clone(), self.repo.clone(), Some(self.secrets.clone()))?;
|
||||
adapter.send_test_message().await
|
||||
let notifier = self.alert_notifier_factory.create()?;
|
||||
notifier.send_test_message().await
|
||||
}
|
||||
|
||||
/// Send a test email using current SMTP config.
|
||||
|
||||
@ -18,7 +18,7 @@ use crate::core::auth::jwt::JwtService;
|
||||
use crate::adapter::access_control_adapter::EbpfAccessControlAdapter;
|
||||
use crate::adapter::ebpf::EbpfServices;
|
||||
use crate::adapter::persistence::Database;
|
||||
use crate::adapter::telegram::TelegramAdapter;
|
||||
use crate::adapter::telegram::{TelegramAdapter, TelegramAdapterFactory};
|
||||
use crate::core::acl_service::AclService;
|
||||
use crate::core::config_service::ConfigService;
|
||||
use crate::core::dns_filter_service::DnsFilterService;
|
||||
@ -45,7 +45,7 @@ use crate::interface::port::access_control_admin::AccessControlAdminPort;
|
||||
use crate::interface::port::app_repo::AppRepo;
|
||||
use crate::interface::port::dns_filter_api::DnsFilterPort;
|
||||
use crate::interface::port::geo_block_api::GeoBlockPort;
|
||||
use crate::interface::port::notification::AlertNotifier;
|
||||
use crate::interface::port::notification::{AlertNotifier, AlertNotifierFactory};
|
||||
use crate::interface::port::rate_limit_api::RateLimitPort;
|
||||
use crate::interface::port::secret_store::SecretStorePort;
|
||||
use crate::interface::port::setting::SettingRepo;
|
||||
@ -300,10 +300,16 @@ impl ServiceFactory {
|
||||
));
|
||||
let config_service =
|
||||
Arc::new(ConfigService::new(db.clone() as Arc<dyn AppRepo>).with_secret_store(secret_store_port.clone()));
|
||||
let notifier_factory: Arc<dyn AlertNotifierFactory> = Arc::new(TelegramAdapterFactory::new(
|
||||
db.clone() as Arc<dyn SettingRepo>,
|
||||
db.clone() as Arc<dyn AppRepo>,
|
||||
Some(secret_store_port.clone()),
|
||||
));
|
||||
let notification_service = Arc::new(NotificationService::new(
|
||||
db.clone() as Arc<dyn SettingRepo>,
|
||||
db.clone() as Arc<dyn AppRepo>,
|
||||
secret_store_port,
|
||||
notifier_factory,
|
||||
));
|
||||
|
||||
let suricata_manager = SuricataManager::new(app_config.clone());
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
use crate::model::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::model::error::Error;
|
||||
|
||||
/// Alert notification data sent by SOAR engine.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AlertPayload {
|
||||
@ -20,3 +23,15 @@ pub trait AlertNotifier: Send + Sync {
|
||||
async fn send_alert(&self, payload: &AlertPayload) -> Result<(), Error>;
|
||||
async fn send_test_message(&self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Port for constructing an `AlertNotifier` on demand.
|
||||
///
|
||||
/// `NotificationService::test_telegram` needs to test the *current* DB config,
|
||||
/// which can have been set after the service was wired. A pre-constructed
|
||||
/// notifier wouldn't reflect the new credentials, and `core/` cannot reach
|
||||
/// into `adapter/telegram` to build one. The factory inverts this: the
|
||||
/// implementation lives in the adapter layer, the core service depends only
|
||||
/// on this trait, and each call gets a fresh notifier reading the latest config.
|
||||
pub trait AlertNotifierFactory: Send + Sync {
|
||||
fn create(&self) -> Result<Arc<dyn AlertNotifier>, Error>;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user