#![allow(dead_code)]
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
use async_trait::async_trait;
use navi_notifier_core::traits::StateStore;
use navi_notifier_core::StateError;
use navi_notifier_email::{EmailDestination, EmailDestinationConfig, EmailTls};
use serde_json::Value;
pub fn mailpit_email(smtp_host: String, smtp_port: u16) -> Result<EmailDestination, String> {
EmailDestination::new(EmailDestinationConfig {
smtp_host,
smtp_port,
tls: EmailTls::None,
username: None,
password: None,
from: "navi <navi@navi.local>".into(),
to: "you <you@navi.local>".into(),
})
.map_err(|e| format!("build email destination: {e}"))
}
pub async fn mailpit_review_request(
http: &reqwest::Client,
mailpit: &str,
expect: Option<&str>,
) -> Result<Option<String>, String> {
let resp = http
.get(format!("{mailpit}/api/v1/messages"))
.send()
.await
.map_err(|e| format!("mailpit query: {e}"))?;
let value = json_ok(resp, "mailpit query").await?;
let found = value["messages"]
.as_array()
.into_iter()
.flatten()
.filter_map(|m| m["Subject"].as_str())
.find(|s| s.contains("requested your review") && expect.is_none_or(|e| s.contains(e)))
.map(str::to_string);
Ok(found)
}
pub fn env(key: &str) -> Result<String, String> {
std::env::var(key)
.ok()
.filter(|v| !v.trim().is_empty())
.ok_or_else(|| format!("missing env var {key}"))
}
pub fn env_or(key: &str, default: &str) -> String {
std::env::var(key)
.ok()
.filter(|v| !v.trim().is_empty())
.unwrap_or_else(|| default.to_string())
}
pub async fn json_ok(resp: reqwest::Response, what: &str) -> Result<Value, String> {
let status = resp.status();
let text = resp
.text()
.await
.map_err(|e| format!("{what}: read body: {e}"))?;
if !status.is_success() {
return Err(format!("{what}: {status}: {text}"));
}
serde_json::from_str(&text).map_err(|e| format!("{what}: parse: {e}"))
}
#[derive(Default)]
pub struct MemState {
snapshots: Mutex<HashMap<String, Vec<u8>>>,
delivered: Mutex<HashSet<String>>,
cursors: Mutex<HashMap<String, String>>,
}
#[async_trait]
impl StateStore for MemState {
async fn get_snapshot(&self, s: &str, scope: &str) -> Result<Option<Vec<u8>>, StateError> {
Ok(self.snapshots.lock().unwrap().get(&k(s, scope)).cloned())
}
async fn put_snapshot(&self, s: &str, scope: &str, b: &[u8]) -> Result<(), StateError> {
self.snapshots
.lock()
.unwrap()
.insert(k(s, scope), b.to_vec());
Ok(())
}
async fn was_delivered(&self, key: &str) -> Result<bool, StateError> {
Ok(self.delivered.lock().unwrap().contains(key))
}
async fn mark_delivered(&self, key: &str) -> Result<(), StateError> {
self.delivered.lock().unwrap().insert(key.to_string());
Ok(())
}
async fn get_cursor(&self, s: &str, key: &str) -> Result<Option<String>, StateError> {
Ok(self.cursors.lock().unwrap().get(&k(s, key)).cloned())
}
async fn put_cursor(&self, s: &str, key: &str, v: &str) -> Result<(), StateError> {
self.cursors
.lock()
.unwrap()
.insert(k(s, key), v.to_string());
Ok(())
}
}
fn k(a: &str, b: &str) -> String {
format!("{a}\u{0}{b}")
}