126 lines
3.8 KiB
Rust
126 lines
3.8 KiB
Rust
use std::sync::Arc;
|
|
|
|
use domain::jobs::JobKind;
|
|
use domain::ports::{Cipher, Mailer, SettingsRepository};
|
|
use domain::settings::{SmtpSettings, KEY_SMTP, SECRET_KEYS};
|
|
use domain::DomainError;
|
|
|
|
use crate::scheduler::validate_cron;
|
|
use crate::vuln_service::{DEFAULT_NOTIFY_MIN_SEVERITY, KEY_NOTIFY_MIN_SEVERITY};
|
|
use domain::vuln::Severity;
|
|
|
|
pub struct SettingsService {
|
|
repo: Arc<dyn SettingsRepository>,
|
|
cipher: Arc<dyn Cipher>,
|
|
mailer: Arc<dyn Mailer>,
|
|
}
|
|
|
|
impl SettingsService {
|
|
pub fn new(
|
|
repo: Arc<dyn SettingsRepository>,
|
|
cipher: Arc<dyn Cipher>,
|
|
mailer: Arc<dyn Mailer>,
|
|
) -> Self {
|
|
Self {
|
|
repo,
|
|
cipher,
|
|
mailer,
|
|
}
|
|
}
|
|
|
|
async fn get(&self, key: &str) -> Result<Option<String>, DomainError> {
|
|
let Some(raw) = self.repo.get(key).await? else {
|
|
return Ok(None);
|
|
};
|
|
if SECRET_KEYS.contains(&key) {
|
|
self.cipher.decrypt(&raw).map(Some)
|
|
} else {
|
|
Ok(Some(raw))
|
|
}
|
|
}
|
|
|
|
async fn set(&self, key: &str, value: &str) -> Result<(), DomainError> {
|
|
let stored = if SECRET_KEYS.contains(&key) {
|
|
self.cipher.encrypt(value)?
|
|
} else {
|
|
value.to_string()
|
|
};
|
|
self.repo.set(key, &stored).await
|
|
}
|
|
|
|
pub async fn smtp(&self) -> Result<Option<SmtpSettings>, DomainError> {
|
|
match self.get(KEY_SMTP).await? {
|
|
Some(json) => serde_json::from_str(&json)
|
|
.map(Some)
|
|
.map_err(|e| DomainError::Storage(e.to_string())),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
pub async fn set_smtp(&self, smtp: SmtpSettings) -> Result<(), DomainError> {
|
|
smtp.validate()?;
|
|
let json = serde_json::to_string(&smtp).map_err(|e| DomainError::Storage(e.to_string()))?;
|
|
self.set(KEY_SMTP, &json).await
|
|
}
|
|
|
|
/// Send a mail to the configured recipients (or `to` if given) using the stored SMTP settings.
|
|
pub async fn send_mail(
|
|
&self,
|
|
to: Option<Vec<String>>,
|
|
subject: &str,
|
|
body: &str,
|
|
) -> Result<(), DomainError> {
|
|
let smtp = self
|
|
.smtp()
|
|
.await?
|
|
.ok_or_else(|| DomainError::Validation("smtp is not configured".into()))?;
|
|
let to = to.unwrap_or_else(|| smtp.notify_to.clone());
|
|
if to.is_empty() {
|
|
return Err(DomainError::Validation("no recipients configured".into()));
|
|
}
|
|
self.mailer.send(&smtp, &to, subject, body).await
|
|
}
|
|
|
|
/// Cron expression (6 fields, seconds first) for a scheduled job kind, or None if disabled.
|
|
pub async fn schedule(&self, kind: JobKind) -> Result<Option<String>, DomainError> {
|
|
match self.get(&schedule_key(kind)).await? {
|
|
Some(v) if v.is_empty() => Ok(None),
|
|
Some(v) => Ok(Some(v)),
|
|
None => Ok(kind.default_schedule().map(String::from)),
|
|
}
|
|
}
|
|
|
|
pub async fn set_schedule(
|
|
&self,
|
|
kind: JobKind,
|
|
cron: Option<String>,
|
|
) -> Result<(), DomainError> {
|
|
let value = match cron.map(|c| c.trim().to_string()).filter(|c| !c.is_empty()) {
|
|
Some(c) => {
|
|
validate_cron(&c)?;
|
|
c
|
|
}
|
|
None => String::new(),
|
|
};
|
|
self.set(&schedule_key(kind), &value).await
|
|
}
|
|
}
|
|
|
|
impl SettingsService {
|
|
pub async fn notify_min_severity(&self) -> Result<Severity, DomainError> {
|
|
Ok(self
|
|
.get(KEY_NOTIFY_MIN_SEVERITY)
|
|
.await?
|
|
.map(|s| Severity::parse(&s))
|
|
.unwrap_or(DEFAULT_NOTIFY_MIN_SEVERITY))
|
|
}
|
|
|
|
pub async fn set_notify_min_severity(&self, severity: Severity) -> Result<(), DomainError> {
|
|
self.set(KEY_NOTIFY_MIN_SEVERITY, severity.as_str()).await
|
|
}
|
|
}
|
|
|
|
fn schedule_key(kind: JobKind) -> String {
|
|
format!("schedule.{}", kind.as_str())
|
|
}
|