release_checker/application/src/request.rs

227 lines
7.3 KiB
Rust

use crate::configuration::VcsType;
use crate::request::models::{Current, GithubRelease};
use crate::request::notification::Notification;
use crate::request::versioning::{SemverVersionComparer, VersionComparer};
use crate::{Print, Settings};
use std::cmp::Ordering;
pub fn init<'a>(
settings: &'a Settings,
console: &'a dyn Print,
notification: &'a dyn Notification,
) -> RequestHandler<'a> {
let request_gateway = RequestGateway::new(settings, notification);
RequestHandler {
settings,
request_gateway,
console,
}
}
pub struct RequestHandler<'a> {
pub settings: &'a Settings,
console: &'a dyn Print,
request_gateway: RequestGateway<'a>,
}
pub struct RequestGateway<'a> {
pub settings: &'a Settings,
pub notification: &'a dyn Notification,
}
impl<'a> RequestHandler<'a> {
pub fn execute(&self) {
let currents = self.request_gateway.get_currents();
let mut send_alerts_for = Vec::<&Current>::new();
for current in currents.iter() {
//TODO: This needs to be rethought a bit
let api_url = match &self.settings.github_api_override {
Some(api) => api,
None => &current.api_root_url,
};
// TODO: only latest release is supported now, but it needs to be modified so we can roll from our current release to the upstream current, and maybe gather all fixes, so it can be sent if versions is out of sync. Maybe just max it out to the last 10 if upstream and local current differs with more than 10 (if that is possible to make without enumerating all pages that /releases brings back to get the span between upstream and current)
let latest_release_api_url = format!(
"{}repos/{}/{}/releases/latest", // api_root_url contains protocol - this is very much hard coded to Github right now, so i wont make any fancy factory or something. When there is added another provider i will think on how to do this the correct way
api_url, current.owner_name, current.repo_name
);
let release = reqwest::blocking::get(latest_release_api_url)
.unwrap()
.json::<GithubRelease>()
.unwrap();
// Compare
// For now we only have one comparer: semver comparer, so it is hardcoded here. This is going to be changed when more is added
let comparer = SemverVersionComparer;
let ordering = comparer.compare(current.version.as_str(), release.tag_name.as_str());
if ordering == Ordering::Less {
send_alerts_for.push(current);
} else {
println!(
"Current version for {:?} is alligned with or larger than CVS",
current
);
}
}
// send alerts
}
}
impl<'a> RequestGateway<'a> {
pub fn new(settings: &'a Settings, notification: &'a dyn Notification) -> RequestGateway<'a> {
RequestGateway {
settings,
notification,
}
}
pub fn get_currents(&self) -> Vec<Current> {
let connection = self.settings.database.get_connection();
let sql = "
SELECT C.name, C.vcs_type, V.api_root_url, O.owner_name, O.repo_name, C.version, C.contact
FROM Origins O
JOIN Currents C ON O.name = C.name AND O.vcs_type = C.vcs_type
JOIN VcsTypes V ON O.vcs_type = V.vcs_type";
let mut prepared = connection.prepare(sql).unwrap();
let iter = prepared
.query_map([], |row| {
let vcs: String = row.get(1).unwrap();
Ok(Current {
name: row.get(0).unwrap(),
vcs_type: match vcs.as_str() {
"github" => VcsType::Github,
_ => VcsType::Unknown,
},
api_root_url: row.get(2).unwrap(),
owner_name: row.get(3).unwrap(),
repo_name: row.get(4).unwrap(),
version: row.get(5).unwrap(),
contact: row.get(6).unwrap(),
})
})
.unwrap();
iter.map(|x| x.unwrap()).collect()
}
}
pub mod notification {
use crate::request::models::Current;
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
pub trait Notification {
fn send(&self, currents: &Vec<Current>);
}
pub struct EmailNotification {
pub from: String,
pub smtp_username: String,
pub smtp_password: String,
pub smtp_relay: String,
}
impl Notification for EmailNotification {
fn send(&self, currents: &Vec<Current>) {
for current in currents {
let email = Message::builder()
.from(self.from.parse().unwrap())
.to(current.contact.parse().unwrap())
.subject("Version checker: something has changed")
.body(String::from(""))
.unwrap();
let creds = Credentials::new(
self.smtp_username.to_string(),
self.smtp_password.to_string(),
);
let mailer = SmtpTransport::relay(self.smtp_relay.as_str())
.unwrap()
.credentials(creds)
.build();
match mailer.send(&email) {
Ok(_) => println!("Email sent successfully!"),
Err(e) => panic!("Could not send email: {:?}", e), //Todo: this shouldn't panic
}
}
}
}
}
mod versioning {
use semver::Version;
use std::cmp::Ordering;
pub trait VersionComparer {
fn compare(&self, v1: &str, v2: &str) -> Ordering;
}
pub struct SemverVersionComparer;
impl VersionComparer for SemverVersionComparer {
fn compare(&self, v1: &str, v2: &str) -> Ordering {
let v1 = Version::parse(v1).unwrap();
let v2 = Version::parse(v2).unwrap();
v1.cmp(&v2)
}
}
}
pub mod models {
use crate::configuration::VcsType;
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct GithubRelease {
pub tag_name: String,
pub name: String,
}
#[derive(Debug)]
pub struct Current {
pub name: String,
pub owner_name: String,
pub repo_name: String,
pub vcs_type: VcsType,
pub api_root_url: String,
pub version: String,
pub contact: String,
}
}
#[cfg(test)]
mod tests {
use crate::request::versioning::{SemverVersionComparer, VersionComparer};
use std::cmp::Ordering;
#[test]
fn rest_equal_semver() {
let comparer = SemverVersionComparer;
let ordering = comparer.compare("1.2.3", "1.2.3");
assert_eq!(ordering, Ordering::Equal);
}
#[test]
fn rest_greater_semver() {
let comparer = SemverVersionComparer;
let ordering = comparer.compare("1.2.4", "1.2.3");
assert_eq!(ordering, Ordering::Greater);
}
#[test]
fn rest_less_semver() {
let comparer = SemverVersionComparer;
let ordering = comparer.compare("1.2.3", "1.2.4");
assert_eq!(ordering, Ordering::Less);
}
}