Concurrently spawn an axum server, which is not doing much yet, in order to prepare for the creation, etc. of reminders.
133 lines
3.7 KiB
Rust
133 lines
3.7 KiB
Rust
use anyhow::Result;
|
|
use axum::{http::StatusCode, response::IntoResponse, Router};
|
|
use clap::Parser;
|
|
use diesel::{
|
|
prelude::*,
|
|
r2d2::{ConnectionManager, Pool, PooledConnection},
|
|
};
|
|
use lettre::{Message, SmtpTransport, Transport};
|
|
use std::{env, net::SocketAddr};
|
|
use time::{Duration, OffsetDateTime};
|
|
use tower::ServiceBuilder;
|
|
use tower_http::trace::TraceLayer;
|
|
use tracing::{debug, info, trace};
|
|
|
|
mod args;
|
|
mod config;
|
|
mod models;
|
|
mod schema;
|
|
|
|
use args::Args;
|
|
use config::Config;
|
|
use models::{NewReminder, Reminder};
|
|
|
|
fn remind(
|
|
db: &mut PooledConnection<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>(db)?;
|
|
|
|
if result.is_empty() {
|
|
info!("no reminders present, parking indefinitely");
|
|
std::thread::park();
|
|
} else {
|
|
for reminder in result {
|
|
trace!(?reminder, "checking reminder");
|
|
if reminder.planned <= OffsetDateTime::now_utc() {
|
|
let email = Message::builder()
|
|
.from("whakarite@localhost".parse()?)
|
|
.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(db)?;
|
|
|
|
debug!("email sent to {}", reminder.receiver);
|
|
} else {
|
|
let duration = reminder.planned - OffsetDateTime::now_utc();
|
|
info!(?duration, "parking reminder");
|
|
|
|
std::thread::park_timeout(<std::time::Duration>::try_from(duration)?);
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::unused_async)]
|
|
async fn not_found() -> impl IntoResponse {
|
|
(StatusCode::NOT_FOUND, "Page not found")
|
|
}
|
|
|
|
#[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")
|
|
);
|
|
|
|
let config = Config::load_config(args.config)?;
|
|
let db_pool = Pool::builder().build(ConnectionManager::<PgConnection>::new(format!(
|
|
"postgresql://{}:{}@{}:{}/{}",
|
|
config.database.user,
|
|
config.database.pass,
|
|
config.database.host,
|
|
config.database.port,
|
|
config.database.name
|
|
)))?;
|
|
|
|
let test_reminder = NewReminder {
|
|
created: OffsetDateTime::now_utc(),
|
|
planned: (OffsetDateTime::now_utc() + Duration::MINUTE),
|
|
title: "Test title",
|
|
message: "Test message",
|
|
receiver: "finga@localhost",
|
|
};
|
|
|
|
diesel::insert_into(schema::reminders::table)
|
|
.values(&test_reminder)
|
|
.execute(&mut db_pool.get()?)?;
|
|
|
|
std::thread::spawn(move || {
|
|
let mailer = SmtpTransport::unencrypted_localhost();
|
|
|
|
loop {
|
|
remind(&mut db_pool.get().unwrap(), &mailer).unwrap();
|
|
}
|
|
});
|
|
|
|
let app = Router::new()
|
|
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
|
|
.fallback(not_found);
|
|
|
|
let addr = SocketAddr::from((config.server.address, config.server.port));
|
|
|
|
info!("{} listening on {}", env!("CARGO_PKG_NAME"), addr);
|
|
|
|
axum::Server::bind(&addr)
|
|
.serve(app.into_make_service())
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|