2018-10-10 07:46:20 -07:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
use lettre::{SendableEmail, Transport};
|
|
|
|
use lettre::smtp::{ClientSecurity, ConnectionReuseParameters, SmtpClient, SmtpTransport};
|
2018-04-21 09:59:11 -07:00
|
|
|
use lettre::smtp::authentication::{Credentials, Mechanism};
|
2018-09-25 06:56:44 -07:00
|
|
|
use lettre::smtp::client::net::ClientTlsParameters;
|
2018-04-24 09:57:06 -07:00
|
|
|
use lettre_email::{Email, EmailBuilder};
|
|
|
|
use native_tls::TlsConnector;
|
2018-04-21 09:59:11 -07:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
use config::Config;
|
|
|
|
use error::{Error, Result};
|
2018-10-10 07:46:20 -07:00
|
|
|
use logger::Logger;
|
2018-09-25 06:56:44 -07:00
|
|
|
use response::common::BallotCreatedLog;
|
|
|
|
use response::v1::VotingState;
|
|
|
|
use response::v2::BallotInfo;
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum Notification<'a> {
|
|
|
|
VotingState {
|
|
|
|
config: &'a Config,
|
|
|
|
log: BallotCreatedLog,
|
|
|
|
voting_state: VotingState,
|
|
|
|
},
|
|
|
|
BallotInfo {
|
|
|
|
config: &'a Config,
|
|
|
|
log: BallotCreatedLog,
|
|
|
|
ballot_info: BallotInfo,
|
|
|
|
},
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
impl<'a> Notification<'a> {
|
|
|
|
pub fn from_voting_state(
|
|
|
|
config: &'a Config,
|
|
|
|
log: BallotCreatedLog,
|
|
|
|
voting_state: VotingState,
|
|
|
|
) -> Self
|
|
|
|
{
|
|
|
|
Notification::VotingState { config, log, voting_state }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_ballot_info(
|
|
|
|
config: &'a Config,
|
|
|
|
log: BallotCreatedLog,
|
|
|
|
ballot_info: BallotInfo,
|
|
|
|
) -> Self
|
|
|
|
{
|
|
|
|
Notification::BallotInfo { config, log, ballot_info }
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
pub fn email_text(&self) -> String {
|
|
|
|
format!(
|
|
|
|
"Network: {:?}\n\
|
|
|
|
RPC Endpoint: {}\n\
|
|
|
|
Block Number: {}\n\
|
|
|
|
Contract: {}\n\
|
|
|
|
Version: {:?}\n\
|
|
|
|
Ballot ID: {}\n\
|
|
|
|
{}\n",
|
|
|
|
self.config().network,
|
|
|
|
self.config().endpoint,
|
|
|
|
self.log().block_number,
|
|
|
|
self.contract_name(),
|
|
|
|
self.config().version,
|
|
|
|
self.log().ballot_id,
|
|
|
|
self.email_body(),
|
|
|
|
)
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
fn config(&self) -> &Config {
|
|
|
|
match self {
|
|
|
|
Notification::VotingState { config, .. } => config,
|
|
|
|
Notification::BallotInfo { config, .. } => config,
|
|
|
|
}
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
pub fn log(&self) -> &BallotCreatedLog {
|
|
|
|
match self {
|
|
|
|
Notification::VotingState { log, .. } => log,
|
|
|
|
Notification::BallotInfo { log, .. } => log,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn contract_name(&self) -> String {
|
|
|
|
match self {
|
|
|
|
Notification::VotingState { voting_state, .. } => voting_state.contract_name(),
|
|
|
|
Notification::BallotInfo { ballot_info, .. } => ballot_info.contract_name(),
|
|
|
|
}
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
fn email_body(&self) -> String {
|
|
|
|
match self {
|
|
|
|
Notification::VotingState { voting_state, .. } => voting_state.email_text(),
|
|
|
|
Notification::BallotInfo { ballot_info, .. } => ballot_info.email_text(),
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Notifier<'a> {
|
|
|
|
config: &'a Config,
|
2018-09-25 06:56:44 -07:00
|
|
|
emailer: Option<SmtpTransport>,
|
2018-10-10 07:46:20 -07:00
|
|
|
logger: Arc<Mutex<Logger>>,
|
|
|
|
notification_count: usize,
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Notifier<'a> {
|
2018-10-10 07:46:20 -07:00
|
|
|
pub fn new(config: &'a Config, logger: Arc<Mutex<Logger>>) -> Result<Self> {
|
2018-09-25 06:56:44 -07:00
|
|
|
let emailer = if config.email_notifications {
|
|
|
|
let domain = config.smtp_host_domain.clone().unwrap();
|
|
|
|
let port = config.smtp_port.unwrap();
|
|
|
|
let addr = (domain.as_str(), port);
|
|
|
|
let security = {
|
|
|
|
let tls = TlsConnector::new().map_err(|e| Error::FailedToBuildTls(e))?;
|
|
|
|
let smtp_security_setup = ClientTlsParameters::new(domain.clone(), tls);
|
|
|
|
ClientSecurity::Required(smtp_security_setup)
|
2018-04-24 09:57:06 -07:00
|
|
|
};
|
2018-09-25 06:56:44 -07:00
|
|
|
let creds = Credentials::new(
|
|
|
|
config.smtp_username.clone().unwrap(),
|
|
|
|
config.smtp_password.clone().unwrap(),
|
2018-04-21 09:59:11 -07:00
|
|
|
);
|
2018-09-25 06:56:44 -07:00
|
|
|
let smtp = SmtpClient::new(addr, security)
|
|
|
|
.map_err(|e| Error::FailedToResolveSmtpHostDomain(e))?
|
2018-04-21 09:59:11 -07:00
|
|
|
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
|
|
|
|
.authentication_mechanism(Mechanism::Plain)
|
2018-09-25 06:56:44 -07:00
|
|
|
.credentials(creds)
|
|
|
|
.transport();
|
|
|
|
Some(smtp)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2018-10-10 07:46:20 -07:00
|
|
|
Ok(Notifier { config, emailer, logger, notification_count: 0 })
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
pub fn notify(&mut self, notif: &Notification) {
|
2018-10-10 07:46:20 -07:00
|
|
|
if self.config.log_emails {
|
|
|
|
self.logger.lock().unwrap().log_notification_email_body(notif);
|
2018-09-25 06:56:44 -07:00
|
|
|
} else {
|
2018-10-10 07:46:20 -07:00
|
|
|
self.logger.lock().unwrap().log_notification(notif);
|
2018-09-25 06:56:44 -07:00
|
|
|
}
|
|
|
|
if self.config.email_notifications {
|
|
|
|
for recipient in self.config.email_recipients.iter() {
|
|
|
|
let email: SendableEmail = match self.build_email(notif, recipient) {
|
|
|
|
Ok(email) => email.into(),
|
|
|
|
Err(e) => {
|
2018-10-10 07:46:20 -07:00
|
|
|
self.logger.lock().unwrap().log_failed_to_build_email(e);
|
2018-09-25 06:56:44 -07:00
|
|
|
continue;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
if let Err(e) = self.send_email(email) {
|
2018-10-10 07:46:20 -07:00
|
|
|
self.logger.lock().unwrap().log_failed_to_send_email(recipient, e);
|
2018-09-25 06:56:44 -07:00
|
|
|
} else {
|
2018-10-10 07:46:20 -07:00
|
|
|
self.logger.lock().unwrap().log_email_sent(recipient);
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-10 07:46:20 -07:00
|
|
|
self.notification_count += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reached_limit(&self) -> bool {
|
|
|
|
if let Some(limit) = self.config.notification_limit {
|
|
|
|
self.notification_count >= limit
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
fn build_email(&self, notif: &Notification, recipient: &str) -> Result<Email> {
|
|
|
|
let outgoing_email = self.config.outgoing_email_addr.clone().unwrap();
|
2018-04-21 09:59:11 -07:00
|
|
|
EmailBuilder::new()
|
2018-09-25 06:56:44 -07:00
|
|
|
.to(recipient)
|
|
|
|
.from(outgoing_email.as_str())
|
2018-04-21 09:59:11 -07:00
|
|
|
.subject("POA Network Governance Notification")
|
2018-09-25 06:56:44 -07:00
|
|
|
.text(notif.email_text())
|
2018-04-21 09:59:11 -07:00
|
|
|
.build()
|
2018-09-25 06:56:44 -07:00
|
|
|
.map_err(|e| Error::FailedToBuildEmail(e))
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
|
2018-09-25 06:56:44 -07:00
|
|
|
fn send_email(&mut self, email: SendableEmail) -> Result<()> {
|
|
|
|
if let Some(ref mut emailer) = self.emailer {
|
|
|
|
match emailer.send(email) {
|
|
|
|
Ok(_response) => Ok(()),
|
|
|
|
Err(e) => Err(Error::FailedToSendEmail(e)),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
unreachable!("Attempted to send email without SMTP client setup");
|
2018-04-21 09:59:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|