remindrs/src/main.rs
finga 7ad6f610d1 Use once_cell to keep the configuration
In order to keep the configuration globally available use
`once_cell`. Also check for a valid email address in the part which is
receiving the data from the api.
2023-02-06 10:00:45 +01:00

143 lines
4.1 KiB
Rust

use anyhow::{anyhow, Error, Result};
use axum::{routing::post, Router};
use clap::Parser;
use diesel::{
prelude::*,
r2d2::{ConnectionManager, Pool},
};
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use lettre::{Message, SmtpTransport, Transport};
use std::{env, net::SocketAddr, sync::Arc, thread::JoinHandle, time::Duration};
use time::OffsetDateTime;
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;
use tracing::{debug, info, trace};
mod api;
mod args;
mod config;
mod models;
mod schema;
use args::Args;
use config::Config;
use models::{NewReminder, Reminder};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
fn get_connection_pool(config: &Config) -> Result<Pool<ConnectionManager<PgConnection>>> {
Ok(
Pool::builder().build(ConnectionManager::<PgConnection>::new(format!(
"postgresql://{}:{}@{}:{}/{}",
config.database.user,
config.database.pass,
config.database.host,
config.database.port,
config.database.name
)))?,
)
}
#[allow(clippy::cognitive_complexity)]
fn remind(
db_pool: &mut Pool<ConnectionManager<PgConnection>>,
mailer: &SmtpTransport,
) -> Result<()> {
info!("checking for reminders");
let result = schema::reminders::dsl::reminders
.filter(schema::reminders::executed.is_null())
.order(schema::reminders::planned.asc())
.load::<Reminder>(&mut db_pool.get()?)?;
if result.is_empty() {
info!("no reminders present, waiting for new reminder");
std::thread::park();
} else {
for reminder in result {
trace!(?reminder, "checking reminder");
if reminder.planned <= OffsetDateTime::now_utc() {
let email = Message::builder()
.from(Config::get().email.from.clone())
.to(reminder.receiver.parse()?)
.subject(&reminder.title)
.body(reminder.message.to_string())?;
mailer.send(&email)?;
diesel::update(&reminder)
.set(schema::reminders::executed.eq(Some(OffsetDateTime::now_utc())))
.execute(&mut db_pool.get()?)?;
debug!("email sent to {}", reminder.receiver);
} else {
let duration = reminder.planned - OffsetDateTime::now_utc();
info!(?duration, "parking reminder");
std::thread::park_timeout(<Duration>::try_from(duration)?);
return Ok(());
}
}
}
Ok(())
}
#[derive(Clone)]
pub struct AppState {
db_pool: Pool<ConnectionManager<PgConnection>>,
reminder: Arc<JoinHandle<Result<(), Error>>>,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", format!("{}", args.log_level));
}
tracing_subscriber::fmt::init();
info!(
"{} {} started",
env!("CARGO_BIN_NAME"),
env!("CARGO_PKG_VERSION")
);
Config::init(args.config)?;
let mut db_pool = get_connection_pool(Config::get())?;
trace!(migrations = ?db_pool
.get()?
.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!(e)), "running database migrations");
let reminder = std::thread::spawn(move || -> Result<(), Error> {
let mailer = SmtpTransport::unencrypted_localhost();
loop {
remind(&mut db_pool, &mailer)?;
}
});
let db_pool = get_connection_pool(Config::get())?;
let app = Router::new()
.route("/v1/reminder", post(api::create_reminder))
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
.fallback(api::not_found)
.with_state(AppState {
db_pool,
reminder: Arc::new(reminder),
});
let addr = SocketAddr::from((Config::get().server.address, Config::get().server.port));
info!("{} listening on {}", env!("CARGO_PKG_NAME"), addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}